diff --git a/.changelog/2201.trivial.md b/.changelog/2201.trivial.md
new file mode 100644
index 0000000000..1f1565907e
--- /dev/null
+++ b/.changelog/2201.trivial.md
@@ -0,0 +1 @@
+Deduplicate logic in TransportWebUSB.create and requestDevice
diff --git a/extension/src/ExtLedgerAccessPopup/ExtLedgerAccessPopup.tsx b/extension/src/ExtLedgerAccessPopup/ExtLedgerAccessPopup.tsx
index bd47f3f99c..3f0427cea8 100644
--- a/extension/src/ExtLedgerAccessPopup/ExtLedgerAccessPopup.tsx
+++ b/extension/src/ExtLedgerAccessPopup/ExtLedgerAccessPopup.tsx
@@ -10,10 +10,10 @@ import { Header } from 'app/components/Header'
import { ErrorFormatter } from 'app/components/ErrorFormatter'
import { AlertBox } from 'app/components/AlertBox'
import { WalletErrors } from 'types/errors'
-import { requestDevice } from 'app/lib/ledger'
import logotype from '../../../public/Icon Blue 192.png'
import { CountdownButton } from 'app/components/CountdownButton'
-import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
+import { getUSBTransport } from '../../../src/app/state/importaccounts/saga'
+import { runSaga } from 'redux-saga'
type ConnectionStatus = 'connected' | 'disconnected' | 'connecting' | 'error'
type ConnectionStatusIconPros = {
@@ -49,14 +49,11 @@ export function ExtLedgerAccessPopup() {
const handleConnect = async () => {
setConnection('connecting')
try {
- const device = await requestDevice()
- const transport = await TransportWebUSB.create()
- if (device && transport) {
- setConnection('connected')
- // Used to redirect after reopening wallet
- window.localStorage.setItem('oasis_wallet_granted_usb_ledger_timestamp', Date.now().toString())
- setTimeout(() => window.close(), 5_000)
- }
+ await (await runSaga({}, getUSBTransport).toPromise()).close() // Get access permissions
+ setConnection('connected')
+ // Used to redirect after reopening wallet
+ window.localStorage.setItem('oasis_wallet_granted_usb_ledger_timestamp', Date.now().toString())
+ setTimeout(() => window.close(), 5_000)
} catch {
setConnection('error')
}
diff --git a/extension/src/ExtLedgerAccessPopup/__tests__/index.test.tsx b/extension/src/ExtLedgerAccessPopup/__tests__/index.test.tsx
index 905c79db0e..3b0e4daa18 100644
--- a/extension/src/ExtLedgerAccessPopup/__tests__/index.test.tsx
+++ b/extension/src/ExtLedgerAccessPopup/__tests__/index.test.tsx
@@ -1,11 +1,9 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
-import { requestDevice } from 'app/lib/ledger'
import { ExtLedgerAccessPopup } from '../ExtLedgerAccessPopup'
import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
-jest.mock('app/lib/ledger')
jest.mock('@ledgerhq/hw-transport')
describe('', () => {
@@ -16,7 +14,6 @@ describe('', () => {
})
it('should render success state', async () => {
- jest.mocked(requestDevice).mockResolvedValue({} as USBDevice)
jest.mocked(TransportWebUSB.create).mockResolvedValue({} as TransportWebUSB)
render()
@@ -28,7 +25,6 @@ describe('', () => {
})
it('should render error state', async () => {
- jest.mocked(requestDevice).mockRejectedValue(new Error('error'))
jest.mocked(TransportWebUSB.create).mockRejectedValue(new Error('error'))
render()
diff --git a/jest.config.js b/jest.config.js
index dc8d9deff9..9afad85b11 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -24,9 +24,7 @@ const config = {
},
],
},
- transformIgnorePatterns: [
- '/node_modules/(?!(@ledgerhq/hw-transport-webusb|cborg|grommet/es6|grommet-icons/es6)/)',
- ],
+ transformIgnorePatterns: ['/node_modules/(?!(cborg|grommet/es6|grommet-icons/es6)/)'],
testMatch: [
'/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
diff --git a/package.json b/package.json
index 14c2177764..881fd5b0c1 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,9 @@
"@capacitor/ios": "6.0.0",
"@capawesome/capacitor-app-update": "6.0.0",
"@ethereumjs/util": "9.0.3",
- "@ledgerhq/hw-transport-webusb": "6.29.4",
+ "@ledgerhq/hw-transport-web-ble": "6.29.8",
+ "@ledgerhq/hw-transport-webhid": "6.30.4",
+ "@ledgerhq/hw-transport-webusb": "6.29.8",
"@metamask/browser-passworder": "=3.0.0",
"@metamask/jazzicon": "2.0.0",
"@oasisprotocol/client": "1.2.0",
diff --git a/src/app/lib/__tests__/ledger.test.ts b/src/app/lib/__tests__/ledger.test.ts
index 9824cb496b..32dcb92357 100644
--- a/src/app/lib/__tests__/ledger.test.ts
+++ b/src/app/lib/__tests__/ledger.test.ts
@@ -1,11 +1,9 @@
-import { canAccessBle, Ledger, LedgerSigner, requestDevice } from '../ledger'
+import { canAccessBle, Ledger, LedgerSigner } from '../ledger'
import OasisApp from '@oasisprotocol/ledger'
import { WalletError, WalletErrors } from 'types/errors'
import { Wallet, WalletType } from 'app/state/wallet/types'
-import { isSupported, requestLedgerDevice } from '@ledgerhq/hw-transport-webusb/lib-es/webusb'
import BleTransport from '@oasisprotocol/ionic-ledger-hw-transport-ble/lib'
-jest.mock('@ledgerhq/hw-transport-webusb/lib-es/webusb')
jest.mock('@oasisprotocol/ionic-ledger-hw-transport-ble/lib', () => {
return {
isEnabled: jest.fn(),
@@ -22,16 +20,6 @@ function mockAppIsOpen(appName: string) {
appInfo.mockResolvedValueOnce({ appName: appName, return_code: 0x9000, error_message: '' })
}
-describe('Extension access', () => {
- it('should return a ledger device when web usb is supported', async () => {
- const device = {} as USBDevice
- jest.mocked(isSupported).mockResolvedValue(true)
- jest.mocked(requestLedgerDevice).mockResolvedValue(device)
- const result = await requestDevice()
- expect(result).toBe(device)
- })
-})
-
describe('Ledger Library', () => {
afterEach(() => {
jest.resetAllMocks()
diff --git a/src/app/lib/ledger.ts b/src/app/lib/ledger.ts
index 6dbc2f0bdd..bfb3208f2c 100644
--- a/src/app/lib/ledger.ts
+++ b/src/app/lib/ledger.ts
@@ -5,7 +5,7 @@ import { LedgerWalletType, Wallet, WalletType } from 'app/state/wallet/types'
import { WalletError, WalletErrors } from 'types/errors'
import { hex2uint, publicKeyToAddress } from './helpers'
import type Transport from '@ledgerhq/hw-transport'
-import { isSupported, requestLedgerDevice } from '@ledgerhq/hw-transport-webusb/lib-es/webusb'
+import TransportWebUSB from '@ledgerhq/hw-transport-web-ble'
import BleTransport from '@oasisprotocol/ionic-ledger-hw-transport-ble/lib'
import { runtimeIs } from 'app/lib/runtimeIs'
@@ -16,7 +16,7 @@ interface LedgerAccount {
}
export async function canAccessNavigatorUsb(): Promise {
- return await isSupported()
+ return await TransportWebUSB.isSupported()
}
export async function canAccessBle(): Promise {
@@ -26,12 +26,6 @@ export async function canAccessBle(): Promise {
return hasBLE && hasLEScan
}
-export async function requestDevice(): Promise {
- if (await isSupported()) {
- return await requestLedgerDevice()
- }
-}
-
function successOrThrowWalletError(response: Response, message: string) {
try {
return successOrThrow(response)
diff --git a/src/app/pages/OpenWalletPage/Features/FromLedger/index.tsx b/src/app/pages/OpenWalletPage/Features/FromLedger/index.tsx
index 416e1f2328..40072965c9 100644
--- a/src/app/pages/OpenWalletPage/Features/FromLedger/index.tsx
+++ b/src/app/pages/OpenWalletPage/Features/FromLedger/index.tsx
@@ -6,8 +6,9 @@ import { Button } from 'grommet/es6/components/Button'
import { Text } from 'grommet/es6/components/Text'
import { canAccessBle, canAccessNavigatorUsb } from '../../../../lib/ledger'
import { useTranslation } from 'react-i18next'
-import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
import { runtimeIs } from 'app/lib/runtimeIs'
+import { runSaga } from 'redux-saga'
+import { getUSBTransport } from '../../../../state/importaccounts/saga'
type SelectOpenMethodProps = {
openLedgerAccessPopup?: () => void
@@ -35,11 +36,12 @@ export function FromLedger({ openLedgerAccessPopup }: SelectOpenMethodProps) {
useEffect(() => {
if (openLedgerAccessPopup) {
// In default ext popup this gets auto-accepted / auto-rejected. In a tab or persistent popup it would
- // prompt user to select a ledger device. TransportWebUSB.create seems to match requestDevice called in
- // openLedgerAccessPopup.
- // If TransportWebUSB.create() is rejected then call openLedgerAccessPopup and requestDevice. When user
+ // prompt user to select a ledger device.
+ // If TransportWebUSB.create() is rejected then call openLedgerAccessPopup. When user
// confirms the prompt tell them to come back here. TransportWebUSB.create() will resolve.
- TransportWebUSB.create()
+ runSaga({}, getUSBTransport)
+ .toPromise()
+ .then(t => t.close())
.then(() => setHasUsbLedgerAccess(true))
.catch(() => setHasUsbLedgerAccess(false))
} else {
diff --git a/src/app/state/importaccounts/saga.ts b/src/app/state/importaccounts/saga.ts
index 568053041e..ea6c5d118f 100644
--- a/src/app/state/importaccounts/saga.ts
+++ b/src/app/state/importaccounts/saga.ts
@@ -1,5 +1,5 @@
import { PayloadAction } from '@reduxjs/toolkit'
-import TransportWebUSB from '@ledgerhq/hw-transport-webusb'
+import TransportWebUSB from '@ledgerhq/hw-transport-web-ble'
import * as oasis from '@oasisprotocol/client'
import { publicKeyToAddress, uint2hex } from 'app/lib/helpers'
import { Ledger, LedgerSigner } from 'app/lib/ledger'
@@ -59,7 +59,7 @@ function* getBluetoothTransport(device?: ScanResult) {
}
}
-function* getUSBTransport() {
+export function* getUSBTransport() {
const isSupported = yield* call([TransportWebUSB, TransportWebUSB.isSupported])
if (!isSupported) {
throw new WalletError(WalletErrors.USBTransportNotSupported, 'TransportWebUSB unsupported')
diff --git a/src/types/typed-redux-runSaga.d.ts b/src/types/typed-redux-runSaga.d.ts
new file mode 100644
index 0000000000..4fbf245709
--- /dev/null
+++ b/src/types/typed-redux-runSaga.d.ts
@@ -0,0 +1,24 @@
+/** Improve types - based on typed-redux-saga's fork() */
+import { RunSagaOptions, Task } from 'redux-saga'
+// eslint-disable-next-line no-restricted-imports
+import { SagaReturnType } from 'redux-saga/effects'
+
+interface FixedTask extends Task {
+ result: () => T | undefined
+ toPromise: () => Promise
+}
+
+declare module 'redux-saga' {
+ /**
+ * Allows starting sagas outside the Redux middleware environment. Useful if you
+ * want to connect a Saga to external input/output, other than store actions.
+ *
+ * `runSaga` returns a Task object. Just like the one returned from a `fork`
+ * effect.
+ */
+ export function runSaga any>(
+ options: RunSagaOptions,
+ fn: Fn,
+ ...args: Args
+ ): FixedTask>
+}
diff --git a/yarn.lock b/yarn.lock
index 80b5b8b32d..2b46e757de 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1706,6 +1706,16 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@ledgerhq/devices@8.4.8":
+ version "8.4.8"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.8.tgz#0eee2e6b7f946dedd7adebb10dcb2dc54c1aaf87"
+ integrity sha512-joodpi1lTIoPswpH6DBtlAieEfP0xB1NlM8RY3xj82EtO8eU1IRqTQz7wtsbstXJ1FdLRg/CJ5OL41omdBsrZQ==
+ dependencies:
+ "@ledgerhq/errors" "^6.23.0"
+ "@ledgerhq/logs" "^6.13.0"
+ rxjs "^7.8.1"
+ semver "^7.3.5"
+
"@ledgerhq/devices@^8.4.4":
version "8.4.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-8.4.4.tgz#0d195c1650fe57da2fad7f0d9074a0190947cd6f"
@@ -1721,17 +1731,43 @@
resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.19.1.tgz#d9ac45ad4ff839e468b8f63766e665537aaede58"
integrity sha512-75yK7Nnit/Gp7gdrJAz0ipp31CCgncRp+evWt6QawQEtQKYEDfGo10QywgrrBBixeRxwnMy1DP6g2oCWRf1bjw==
-"@ledgerhq/hw-transport-webusb@6.29.4":
- version "6.29.4"
- resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.29.4.tgz#5926421b8db4b474c7aba4851f21ddd4ad4bcc70"
- integrity sha512-HoGF1LlBT9HEGBQy2XeCHrFdv/FEOZU0+J+yfKcgAQIAiASr2MLvdzwoJbUS8h6Gn+vc+/BjzBSO3JNn7Loqbg==
+"@ledgerhq/errors@^6.23.0":
+ version "6.23.0"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-6.23.0.tgz#fae7b61095ce4c033394d16085b5f14ca9f9d738"
+ integrity sha512-bM7tPYShPThtBy1Y+9D28iquOeP5W5s4p7KKD/cUQoaVaPibrtC7Mm4u+IeSlH4WGvFJkTmv0vmZJajuZtM79A==
+
+"@ledgerhq/hw-transport-web-ble@6.29.8":
+ version "6.29.8"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.29.8.tgz#57d3baa2f4e3cf265b0cd539dcefebb51a667453"
+ integrity sha512-xDcgH0avx1rQyUqIbVVyCTM8LEoEC+ASniTg6lR+Qvfwuks3f7wQEyEhpmrvZxwKpuATPpGDr54DYq3JRDH6Tg==
dependencies:
- "@ledgerhq/devices" "^8.4.4"
- "@ledgerhq/errors" "^6.19.1"
- "@ledgerhq/hw-transport" "^6.31.4"
- "@ledgerhq/logs" "^6.12.0"
+ "@ledgerhq/devices" "8.4.8"
+ "@ledgerhq/errors" "^6.23.0"
+ "@ledgerhq/hw-transport" "^6.31.8"
+ "@ledgerhq/logs" "^6.13.0"
+ rxjs "^7.8.1"
+
+"@ledgerhq/hw-transport-webhid@6.30.4":
+ version "6.30.4"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.30.4.tgz#2f3b3039e7c4fa56692ba606412827c958874dea"
+ integrity sha512-A13E89U1wfWx8reA9ciwm/+SsbGM163yl1UXuWUQ521hIDAAwVbpnRhwaQT4XjTmDbWhvtT2RYRCoOWWO7N6GA==
+ dependencies:
+ "@ledgerhq/devices" "8.4.8"
+ "@ledgerhq/errors" "^6.23.0"
+ "@ledgerhq/hw-transport" "^6.31.8"
+ "@ledgerhq/logs" "^6.13.0"
-"@ledgerhq/hw-transport@^6.1.0", "@ledgerhq/hw-transport@^6.28.8", "@ledgerhq/hw-transport@^6.31.4":
+"@ledgerhq/hw-transport-webusb@6.29.8":
+ version "6.29.8"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.29.8.tgz#8dbda97cc124c4b3efc570bb04767558c499c460"
+ integrity sha512-hAFTnJYB++Ey8qlpGBL2ZHBX4ULQOcBzYzNBcrKUNXqlDaWODtc5uyPCyfGmfUjLnVvZoarDa3h+zDVVQCUHVA==
+ dependencies:
+ "@ledgerhq/devices" "8.4.8"
+ "@ledgerhq/errors" "^6.23.0"
+ "@ledgerhq/hw-transport" "^6.31.8"
+ "@ledgerhq/logs" "^6.13.0"
+
+"@ledgerhq/hw-transport@^6.1.0", "@ledgerhq/hw-transport@^6.28.8", "@ledgerhq/hw-transport@^6.31.4", "@ledgerhq/hw-transport@^6.31.8":
version "6.31.4"
resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-6.31.4.tgz#9b23a6de4a4caaa5c24b149c2dea8adde46f0eb1"
integrity sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A==
@@ -1746,6 +1782,11 @@
resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.12.0.tgz#ad903528bf3687a44da435d7b2479d724d374f5d"
integrity sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==
+"@ledgerhq/logs@^6.13.0":
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-6.13.0.tgz#0b083af64b6b85db0630c7b940a0ed74ff6262b6"
+ integrity sha512-4+qRW2Pc8V+btL0QEmdB2X+uyx0kOWMWE1/LWsq5sZy3Q5tpi4eItJS6mB0XL3wGW59RQ+8bchNQQ1OW/va8Og==
+
"@lezer/common@^0.15.0", "@lezer/common@^0.15.7":
version "0.15.12"
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.12.tgz#2f21aec551dd5fd7d24eb069f90f54d5bc6ee5e9"