Skip to content

Commit

Permalink
feat: open ledger popup window
Browse files Browse the repository at this point in the history
* feat: open ledger popup window
* feat: refactor ledger wrapper methods
* feat: trigger ledger command automatically
* feat: add auth challenge ledger signing flow
  • Loading branch information
dawidsowardx authored and xstelea committed May 16, 2023
1 parent dd8c1a6 commit f3fdfe2
Show file tree
Hide file tree
Showing 25 changed files with 676 additions and 249 deletions.
13 changes: 12 additions & 1 deletion src/chrome/background/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createMessage } from '../messages/create-message'
import { MessageClient } from '../messages/message-client'
import { openParingPopup } from '../helpers/open-pairing-popup'
import { AppLogger } from 'utils/logger'
import { LedgerTabWatcher } from './ledger-tab-watcher'

const backgroundLogger = {
debug: (...args: string[]) => console.log(JSON.stringify(args, null, 2)),
Expand Down Expand Up @@ -38,8 +39,13 @@ const handleStorageChange = (changes: {
)
}

const ledgerTabWatcher = LedgerTabWatcher()

const messageHandler = MessageClient(
BackgroundMessageHandler({ logger: backgroundLogger }),
BackgroundMessageHandler({
logger: backgroundLogger,
ledgerTabWatcher: ledgerTabWatcher,
}),
'background',
{ logger: backgroundLogger }
)
Expand All @@ -55,9 +61,14 @@ const handleConnectionPasswordChange = (connectionPassword?: string) =>
}, config.popup.closeDelayTime)
})

const tabRemovedListener = (tabId: number) =>
ledgerTabWatcher.triggerTabRemoval(tabId)

chrome.runtime.onMessage.addListener((message, sender) => {
messageHandler.onMessage(message, sender.tab?.id)
})

chrome.tabs.onRemoved.addListener(tabRemovedListener)
chrome.storage.onChanged.addListener(handleStorageChange)
chrome.action.onClicked.addListener(openParingPopup)
chrome.runtime.onInstalled.addListener(handleOnInstallExtension)
Expand Down
31 changes: 31 additions & 0 deletions src/chrome/background/ledger-tab-watcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createMessage } from 'chrome/messages/create-message'
import { sendMessage } from 'chrome/messages/send-message'

export const LedgerTabWatcher = () => {
let tabId: number | undefined
let messageId: string | undefined
return {
setWatchedTab: (_tabId: number, _messageId: string) => {
tabId = _tabId
messageId = _messageId
},
triggerTabRemoval: (justRemovedTabId: number) => {
if (!messageId || !tabId || justRemovedTabId !== tabId) {
return
}

sendMessage(
createMessage.confirmationError('ledger', messageId, {
reason: 'tabClosed',
})
)

tabId = undefined
messageId = undefined
},
restoreInitial: () => {
tabId = undefined
messageId = undefined
},
}
}
37 changes: 21 additions & 16 deletions src/chrome/background/message-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import {
} from '../messages/_types'
import { getConnectionPassword as getConnectionPasswordFn } from '../helpers/get-connection-password'
import { config } from 'config'
import { createOrFocusTab } from 'chrome/helpers/create-or-focus-tab'
import { sendMessage } from 'chrome/messages/send-message'
import { createMessage } from 'chrome/messages/create-message'
import { createAlignedPopupWindow } from 'chrome/helpers/create-popup-window'
import { sendMessageToTab } from 'chrome/helpers/send-message-to-tab'
import { createAndFocusTab } from 'chrome/helpers/create-and-focus-tab'
import { LedgerTabWatcher } from './ledger-tab-watcher'

export type BackgroundMessageHandler = ReturnType<
typeof BackgroundMessageHandler
>
export const BackgroundMessageHandler =
({
logger,
ledgerTabWatcher,
getConnectionPassword = getConnectionPasswordFn,
closePopup = closePopupFn,
openParingPopup = openParingPopupFn,
}: Partial<{
logger?: AppLogger
ledgerTabWatcher: ReturnType<typeof LedgerTabWatcher>
getConnectionPassword: () => ResultAsync<any, Error>
closePopup: () => ResultAsync<any, Error>
openParingPopup: () => ResultAsync<any, Error>
Expand Down Expand Up @@ -70,21 +73,23 @@ export const BackgroundMessageHandler =
}))
}

case messageDiscriminator.walletToLedger:
return createOrFocusTab(config.popup.pages.ledger)
case messageDiscriminator.convertPopupToTab: {
ledgerTabWatcher?.restoreInitial()

return createAndFocusTab(config.popup.pages.ledger)
.andThen((tab) => {
const tabRemovedListener = (tabId: number) => {
if (tabId === tab.id) {
chrome.tabs.onRemoved.removeListener(tabRemovedListener)
sendMessage(
createMessage.confirmationError('ledger', message.messageId, {
reason: 'tabClosed',
})
)
}
}
ledgerTabWatcher?.setWatchedTab(tab.id!, message.data.messageId)
return sendMessageToTab(tab.id!, message.data)
})
.map(() => ({ sendConfirmation: false }))
.mapErr(() => ({ reason: 'failedToOpenLedgerTab' }))
}

chrome.tabs.onRemoved.addListener(tabRemovedListener)
case messageDiscriminator.walletToLedger:
ledgerTabWatcher?.restoreInitial()
return createAlignedPopupWindow(config.popup.pages.ledger)
.andThen((tab) => {
ledgerTabWatcher?.setWatchedTab(tab.id!, message.messageId)

return sendMessageWithConfirmation(
{ ...message, source: 'background' },
Expand Down
12 changes: 7 additions & 5 deletions src/chrome/dev-tools/components/WalletSimulator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { Box, Button, Header, Text } from 'components'
import {
getDerivePublicKeyPayload,
getDeviceInfoPayload,
getSignTransactionPayload,
getSignChallengePayload,
getImportFromOlympiaPayload,
getSignEd25519TransactionPayload,
getSignSecp256k1TransactionPayload,
} from '../example'
import { getConnectionPassword } from 'chrome/helpers/get-connection-password'
export const WalletSimulator = () => {
Expand All @@ -21,10 +22,11 @@ export const WalletSimulator = () => {

const messages = {
'Import from Olympia': getImportFromOlympiaPayload(),
'Get UDI from Ledger': getDeviceInfoPayload(),
'Get Public Key from Ledger': getDerivePublicKeyPayload(),
'Sign Transaction on Ledger': getSignTransactionPayload(),
'Sign Challenge on Ledger': getSignChallengePayload(),
'Get UDI': getDeviceInfoPayload(),
'Get Public Key': getDerivePublicKeyPayload(),
'Sign TX (Secp256k1)': getSignSecp256k1TransactionPayload(),
'Sign TX (Curve25519)': getSignEd25519TransactionPayload(),
'Sign Auth': getSignChallengePayload(),
}

useEffect(() => {
Expand Down
73 changes: 46 additions & 27 deletions src/chrome/dev-tools/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,42 +47,61 @@ export const getDerivePublicKeyPayload = (): LedgerPublicKeyRequest => ({
},
})

export const getSignTransactionPayload = (): LedgerSignTransactionRequest => ({
interactionId: crypto.randomUUID(),
discriminator: 'signTransaction',
signers: [
{
curve: 'curve25519',
derivationPath: 'm/44H/1022H/10H/525H/1460H/0H',
export const getSignEd25519TransactionPayload =
(): LedgerSignTransactionRequest => ({
interactionId: crypto.randomUUID(),
discriminator: 'signTransaction',
signers: [
{
curve: 'curve25519',
derivationPath: 'm/44H/1022H/10H/525H/1460H/0H',
},
{
curve: 'curve25519',
derivationPath: 'm/44H/1022H/10H/525H/1460H/1H',
},
],
ledgerDevice: {
name: 'My Ledger Device',
model: 'nanoS',
id: '41ac202687326a4fc6cb677e9fd92d08b91ce46c669950d58790d4d5e583adc0',
},
{
curve: 'curve25519',
derivationPath: 'm/44H/1022H/10H/525H/1460H/1H',
displayHash: true,
compiledTransactionIntent: compiledTxHex.createAccount,
mode: 'verbose',
})

export const getSignSecp256k1TransactionPayload =
(): LedgerSignTransactionRequest => ({
interactionId: crypto.randomUUID(),
discriminator: 'signTransaction',
signers: [
{
curve: 'secp256k1',
derivationPath: 'm/44H/1022H/10H/525H/1238H',
},
],
ledgerDevice: {
name: 'My Ledger Device',
model: 'nanoS',
id: '41ac202687326a4fc6cb677e9fd92d08b91ce46c669950d58790d4d5e583adc0',
},
],
ledgerDevice: {
name: 'My Ledger Device',
model: 'nanoS',
id: '41ac202687326a4fc6cb677e9fd92d08b91ce46c669950d58790d4d5e583adc0',
},
displayHash: true,
compiledTransactionIntent: compiledTxHex.createAccount,
mode: 'verbose',
})
displayHash: true,
compiledTransactionIntent: compiledTxHex.createAccount,
mode: 'verbose',
})

export const getSignChallengePayload = (): LedgerSignChallengeRequest => ({
interactionId: crypto.randomUUID(),
discriminator: 'signChallenge',
signers: [
{
curve: 'curve25519',
derivationPath: 'm/44H/1022H/10H/525H/1460H/0H',
},
],
derivationPaths: ['m/44H/1022H/10H/525H/1460H/0H'],
ledgerDevice: {
name: 'My Ledger Device',
model: 'nanoS',
id: '41ac202687326a4fc6cb677e9fd92d08b91ce46c669950d58790d4d5e583adc0',
},
challenge: '',
challenge: '140100298edb9cadd80423c1e15e2521d44f51be8b6a7953c9f03d83a8220071',
origin: 'https://rola.xrd',
dAppDefinitionAddress:
'account_tdx_b_1p8ahenyznrqy2w0tyg00r82rwuxys6z8kmrhh37c7maqpydx7p',
})
4 changes: 4 additions & 0 deletions src/chrome/helpers/create-and-focus-tab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createTab } from './create-tab'

export const createAndFocusTab = (url: string) =>
createTab(url).map((tab) => chrome.tabs.update(tab.id!, { active: true }))
24 changes: 6 additions & 18 deletions src/chrome/helpers/create-or-focus-tab.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import { ResultAsync } from 'neverthrow'
import { getExtensionTabsByUrl } from './get-extension-tabs-by-url'
import { createTab } from './create-tab'

export const createOrFocusTab = (url: string) =>
getExtensionTabsByUrl(url).andThen((tabs) =>
ResultAsync.fromSafePromise(
tabs.length > 0 && tabs[0].id
? chrome.tabs.update(tabs[0].id, { active: true })
: new Promise<chrome.tabs.Tab>((resolve) => {
chrome.tabs.create({ url }, async (tab) => {
const listener = (
tabId: number,
info: chrome.tabs.TabChangeInfo
) => {
if (info.status === 'complete' && tabId === tab.id) {
chrome.tabs.onUpdated.removeListener(listener)
resolve(tab)
}
}
chrome.tabs.onUpdated.addListener(listener)
})
})
)
tabs.length > 0 && tabs[0].id
? ResultAsync.fromSafePromise(
chrome.tabs.update(tabs[0].id, { active: true })
)
: createTab(url)
)
25 changes: 24 additions & 1 deletion src/chrome/helpers/create-popup-window.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
import { config } from 'config'
import { ResultAsync } from 'neverthrow'
import { getActiveWindow } from './get-active-window'

export const createAlignedPopupWindow = (pagePath: string) =>
getActiveWindow()
.andThen(({ width, left, height, top }) =>
createPopupWindow(pagePath, { width, left, height, top })
)
.andThen((popupWindow) =>
ResultAsync.fromSafePromise(
new Promise<chrome.tabs.Tab>((resolve) => {
const listener = (tabId: number, info: chrome.tabs.TabChangeInfo) => {
if (
info.status === 'complete' &&
tabId === popupWindow?.tabs?.[0].id
) {
chrome.tabs.onUpdated.removeListener(listener)
resolve(popupWindow?.tabs?.[0])
}
}
chrome.tabs.onUpdated.addListener(listener)
})
)
)

export const createPopupWindow = (
pagePath: string,
Expand All @@ -14,7 +37,7 @@ export const createPopupWindow = (
new Promise((resolve) => {
chrome.windows.create(
{
url: chrome.runtime.getURL(pagePath),
url: `${chrome.runtime.getURL(pagePath)}?isPopupWindow=true`,
type: 'popup',
width: config.popup.width,
height: config.popup.height,
Expand Down
16 changes: 16 additions & 0 deletions src/chrome/helpers/create-tab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ResultAsync } from 'neverthrow'

export const createTab = (url: string) =>
ResultAsync.fromSafePromise(
new Promise<chrome.tabs.Tab>((resolve) => {
chrome.tabs.create({ url }, async (tab) => {
const listener = (tabId: number, info: chrome.tabs.TabChangeInfo) => {
if (info.status === 'complete' && tabId === tab.id) {
chrome.tabs.onUpdated.removeListener(listener)
resolve(tab)
}
}
chrome.tabs.onUpdated.addListener(listener)
})
})
)
6 changes: 6 additions & 0 deletions src/chrome/messages/_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const messageDiscriminator = {
dAppRequest: 'dAppRequest',
ledgerResponse: 'ledgerResponse',
walletToLedger: 'walletToLedger',
convertPopupToTab: 'convertPopupToTab',
walletResponse: 'walletResponse',
toContentScript: 'toContentScript',
walletMessage: 'walletMessage',
Expand Down Expand Up @@ -72,6 +73,11 @@ export type Messages = {
{ messageEvent: MessageLifeCycleEvent; interactionId: string }
>

[messageDiscriminator.convertPopupToTab]: MessageBuilder<
MessageDiscriminator['convertPopupToTab'],
{ data: Messages['walletToLedger'] }
>

[messageDiscriminator.getConnectionPassword]: MessageBuilder<
MessageDiscriminator['getConnectionPassword'],
{}
Expand Down
6 changes: 6 additions & 0 deletions src/chrome/messages/create-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ export const createMessage = {
messageId: crypto.randomUUID(),
data: message,
}),
convertPopupToTab: (message: Messages['walletToLedger']) => ({
source: 'popup',
discriminator: messageDiscriminator.convertPopupToTab,
messageId: crypto.randomUUID(),
data: message,
}),
ledgerResponse: (message: LedgerResponse): Messages['ledgerResponse'] => ({
source: 'ledger',
discriminator: messageDiscriminator.ledgerResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/components/link/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const Link = styled('a', {
fontSize: '16px',
color: 'white',
'&:visited': {
color: '$primary',
color: 'white',
},
})
Loading

0 comments on commit f3fdfe2

Please sign in to comment.