]> git.codecow.com Git - libnemo.git/commitdiff
Refactor Ledger browser support check to static sync method and deprecate redundant...
authorChris Duncan <chris@zoso.dev>
Fri, 25 Jul 2025 12:10:41 +0000 (05:10 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 25 Jul 2025 12:10:41 +0000 (05:10 -0700)
src/lib/wallets/ledger-wallet.ts
src/types.d.ts
test/test.ledger.mjs

index a813183ac33cee7672fad7dacf883e8de664e84c..24f882212517e9ee93c4f2c908514c6301e7c8bf 100644 (file)
@@ -18,11 +18,6 @@ import { DeviceStatus, KeyPair, LedgerAccountResponse, LedgerResponse, LedgerSig
 * calls. This wallet does not feature any seed nor mnemonic phrase as all\r
 * private keys are held in the secure chip of the device. As such, the user\r
 * is responsible for using Ledger technology to back up these pieces of data.\r
-*\r
-* Usage of this wallet is generally controlled by calling functions of the\r
-* `ledger` object. For example, the wallet interface should have a button to\r
-* initiate a device connection by calling `wallet.ledger.connect()`. For more\r
-* information, refer to the ledger.js service file.\r
 */\r
 export class LedgerWallet extends Wallet {\r
        static #isInternal: boolean = false\r
@@ -49,25 +44,44 @@ export class LedgerWallet extends Wallet {
        }\r
 \r
        /**\r
-       * Check which transport protocols are supported by the browser and set the\r
+       * Check which transport protocols are supported by the browser and return the\r
        * transport type according to the following priorities: Bluetooth, USB, HID.\r
        */\r
-       async checkBrowserSupport (): Promise<typeof TransportBLE | typeof TransportUSB | typeof TransportHID> {\r
+       static checkBrowserSupport (): typeof TransportBLE | typeof TransportUSB | typeof TransportHID {\r
                console.log('Checking browser Ledger support...')\r
                try {\r
-                       if (await TransportBLE.isSupported()) {\r
+                       if (typeof globalThis.navigator?.bluetooth?.getDevices === 'function') {\r
                                return TransportBLE\r
                        }\r
-                       if (await TransportUSB.isSupported()) {\r
+                       if (typeof globalThis.navigator?.usb?.getDevices === 'function') {\r
                                return TransportUSB\r
                        }\r
-                       if (await TransportHID.isSupported()) {\r
+                       if (typeof globalThis.navigator?.hid?.getDevices === 'function') {\r
                                return TransportHID\r
                        }\r
                } catch { }\r
                throw new Error('Unsupported browser')\r
        }\r
 \r
+       /**\r
+       * Creates a new Ledger hardware wallet communication layer by dynamically\r
+       * importing the ledger.js service.\r
+       *\r
+       * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object\r
+       */\r
+       static async create (): Promise<LedgerWallet> {\r
+               try {\r
+                       const transport = LedgerWallet.checkBrowserSupport()\r
+                       const id = await Entropy.create(16)\r
+                       LedgerWallet.#isInternal = true\r
+                       const wallet = new this(id)\r
+                       wallet.DynamicTransport = transport\r
+                       return wallet\r
+               } catch (err) {\r
+                       throw new Error('failed to initialize Ledger wallet', { cause: err })\r
+               }\r
+       }\r
+\r
        /**\r
        * Check if the Nano app is currently open and set device status accordingly.\r
        *\r
@@ -96,20 +110,6 @@ export class LedgerWallet extends Wallet {
                return this.status\r
        }\r
 \r
-       /**\r
-       * Creates a new Ledger hardware wallet communication layer by dynamically\r
-       * importing the ledger.js service.\r
-       *\r
-       * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object\r
-       */\r
-       static async create (): Promise<LedgerWallet> {\r
-               const id = await Entropy.create(16)\r
-               LedgerWallet.#isInternal = true\r
-               const wallet = new this(id)\r
-               await wallet.init()\r
-               return wallet\r
-       }\r
-\r
        /**\r
        * Removes encrypted secrets in storage and releases variable references to\r
        * allow garbage collection.\r
@@ -121,15 +121,6 @@ export class LedgerWallet extends Wallet {
                }\r
        }\r
 \r
-       async init (): Promise<void> {\r
-               try {\r
-                       this.DynamicTransport = await this.checkBrowserSupport()\r
-                       // await this.connect()\r
-               } catch (err) {\r
-                       throw new Error('Failed to initialize Ledger wallet', { cause: err })\r
-               }\r
-       }\r
-\r
        /**\r
        * Revokes permission granted by the user to access the Ledger device.\r
        *\r
@@ -188,11 +179,16 @@ export class LedgerWallet extends Wallet {
                if (typeof id !== 'string' || id === '') {\r
                        throw new TypeError('Wallet ID is required to restore')\r
                }\r
-               LedgerWallet.#isInternal = true\r
-               id = id.replace('libnemo_', '')\r
-               const wallet = new this(await Entropy.import(id))\r
-               await wallet.init()\r
-               return wallet\r
+               try {\r
+                       const transport = LedgerWallet.checkBrowserSupport()\r
+                       LedgerWallet.#isInternal = true\r
+                       const wallet = new this(await Entropy.import(id))\r
+                       wallet.DynamicTransport = transport\r
+                       return wallet\r
+               } catch (err) {\r
+                       console.error(err)\r
+                       throw new Error('failed to restore wallet', { cause: err })\r
+               }\r
        }\r
 \r
        /**\r
index 6180d2f81e4ffada9be8706affd99cb26fac9ee5..e3c0e25507086e310de7f83859d14fbdba980d93 100644 (file)
@@ -920,11 +920,6 @@ interface LedgerSignResponse extends LedgerResponse {
 * calls. This wallet does not feature any seed nor mnemonic phrase as all
 * private keys are held in the secure chip of the device. As such, the user
 * is responsible for using Ledger technology to back up these pieces of data.
-*
-* Usage of this wallet is generally controlled by calling functions of the
-* `ledger` object. For example, the wallet interface should have a button to
-* initiate a device connection by calling `wallet.ledger.connect()`. For more
-* information, refer to the ledger.js service file.
 */
 export declare class LedgerWallet extends Wallet {
        #private
@@ -934,10 +929,17 @@ export declare class LedgerWallet extends Wallet {
        DynamicTransport: typeof TransportBLE | typeof TransportUSB | typeof TransportHID
        private constructor ()
        /**
-       * Check which transport protocols are supported by the browser and set the
+       * Check which transport protocols are supported by the browser and return the
        * transport type according to the following priorities: Bluetooth, USB, HID.
        */
-       checkBrowserSupport (): Promise<typeof TransportBLE | typeof TransportUSB | typeof TransportHID>
+       static checkBrowserSupport (): typeof TransportBLE | typeof TransportUSB | typeof TransportHID
+       /**
+       * Creates a new Ledger hardware wallet communication layer by dynamically
+       * importing the ledger.js service.
+       *
+       * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object
+       */
+       static create (): Promise<LedgerWallet>
        /**
        * Check if the Nano app is currently open and set device status accordingly.
        *
@@ -949,13 +951,6 @@ export declare class LedgerWallet extends Wallet {
        */
        connect (): Promise<DeviceStatus>
        /**
-       * Creates a new Ledger hardware wallet communication layer by dynamically
-       * importing the ledger.js service.
-       *
-       * @returns {LedgerWallet} A wallet containing accounts and a Ledger device communication object
-       */
-       static create (): Promise<LedgerWallet>
-       /**
        * Removes encrypted secrets in storage and releases variable references to
        * allow garbage collection.
        */
index 384c3d44bed30bc02d587c9ecf578bc12cc190c1..c334655c5dbdcbb3ae9250153f348a374bd063ad 100644 (file)
@@ -35,13 +35,19 @@ if (isNode) {
 
 const rpc = new Rpc(env.NODE_URL ?? '', env.API_KEY_NAME)
 
+let isUnsupported = true
+try {
+       LedgerWallet.checkBrowserSupport()
+       isUnsupported = false
+} catch {}
+
 /**
 * HID interactions require user gestures, so to reduce clicks, the variables
 * shared among tests like wallet and account are declared at the top-level.
 */
 await Promise.all([
        /* node:coverage disable */
-       suite('Ledger hardware wallet', { skip: false || isNode }, async () => {
+       suite('Ledger hardware wallet', { skip: false || isNode || isUnsupported }, async () => {
 
                let wallet, account, openBlock, sendBlock, receiveBlock