]> git.codecow.com Git - libnemo.git/commitdiff
Separate public connect from private implementation. Start rewiring connection pollin...
authorChris Duncan <chris@zoso.dev>
Mon, 22 Sep 2025 21:02:58 +0000 (14:02 -0700)
committerChris Duncan <chris@zoso.dev>
Mon, 22 Sep 2025 21:02:58 +0000 (14:02 -0700)
src/lib/ledger.ts

index b8fd1cb67ce214672a2f0cedddb0d79e95caa94a..a0fc5f0a6dbb6881336e2a6ad666e69a38457c6a 100644 (file)
@@ -42,6 +42,7 @@ interface LedgerSignResponse extends LedgerResponse {
 */
 export class Ledger {
        static #isIdle: boolean = true
+       static #isPolling: boolean = false
        static #listenTimeout: 30000 = 30000
        static #openTimeout: 3000 = 3000
        static #queue: { task: Function, resolve: Function, reject: Function }[] = []
@@ -188,36 +189,20 @@ export class Ledger {
                                        : TransportUSB
                        }
                }
-               try {
-                       const version = await this.#version()
-                       if (version.status !== 'OK') {
-                               this.#status = 'DISCONNECTED'
-                       } else {
-                               if (version.name === 'Nano') {
-                                       const { status } = await this.account()
-                                       if (status === 'OK') {
-                                               this.#status = 'CONNECTED'
-                                       } else if (status === 'SECURITY_STATUS_NOT_SATISFIED') {
-                                               this.#status = 'LOCKED'
-                                       } else {
-                                               this.#status = 'DISCONNECTED'
-                                       }
-                               } else {
-                                       this.#status = 'BUSY'
-                               }
-                       }
-               } catch (err) {
-                       console.error('Ledger.connect()', err)
-                       this.#status = 'DISCONNECTED'
+               const status = await this.#connect()
+               if (!this.isUnsupported && !this.#isPolling) {
+                       this.#isPolling = true
+                       this.#poll().catch(() => { })
                }
-               console.log(this.#status)
-               return this.#status
+               return status
        }
 
        /**
-       * Clears Ledger connections from HID and USB interfaces.
+       * Clears Ledger connections from HID and USB interfaces and stops polling for
+       * connection updates.
        */
        static disconnect (): void {
+               this.#isPolling = false
                setTimeout(async () => {
                        const hidDevices = (await navigator?.hid?.getDevices?.() ?? [])
                                .filter(device => device.vendorId === this.ledgerVendorId)
@@ -415,6 +400,43 @@ export class Ledger {
                })
        }
 
+       /**
+       * Check if the Nano app is currently open and set device status accordingly.
+       *
+       * @returns Device status as follows:
+       * - UNSUPPORTED: Platform does not support any Ledger transport protocols
+       * - DISCONNECTED: Failed to communicate properly with the app
+       * - BUSY: Nano app is not currently open
+       * - LOCKED: Nano app is open but the device locked after a timeout
+       * - CONNECTED: Nano app is open and listening
+       */
+       static async #connect (): Promise<LedgerStatus> {
+               try {
+                       const version = await this.#version()
+                       if (version.status !== 'OK') {
+                               this.#status = 'DISCONNECTED'
+                       } else {
+                               if (version.name === 'Nano') {
+                                       const { status } = await this.account()
+                                       if (status === 'OK') {
+                                               this.#status = 'CONNECTED'
+                                       } else if (status === 'SECURITY_STATUS_NOT_SATISFIED') {
+                                               this.#status = 'LOCKED'
+                                       } else {
+                                               this.#status = 'DISCONNECTED'
+                                       }
+                               } else {
+                                       this.#status = 'BUSY'
+                               }
+                       }
+               } catch (err) {
+                       console.error('Ledger.#connect()', err)
+                       this.#status = 'DISCONNECTED'
+               }
+               console.log(this.#status)
+               return this.#status
+       }
+
        /**
        * Serially executes asynchronous functions.
        */
@@ -476,9 +498,9 @@ export class Ledger {
                        const isUsbPaired = (await navigator.usb?.getDevices?.() ?? [])
                                .some(device => device.vendorId === this.ledgerVendorId)
                        if (this.#transport === TransportHID && isHidPaired) {
-                               await this.connect()
+                               await this.#connect()
                        } else if (this.#transport === TransportUSB && isUsbPaired) {
-                               await this.connect()
+                               await this.#connect()
                        } else {
                                console.log('No Ledger devices paired on current interface')
                                this.#status = 'DISCONNECTED'
@@ -487,7 +509,7 @@ export class Ledger {
                        console.warn('Error polling Ledger device')
                        this.#status = 'DISCONNECTED'
                } finally {
-                       setTimeout(() => this.#poll(), 500)
+                       this.#isPolling ? setTimeout(() => this.#poll(), 500) : void 0
                }
        }
 
@@ -621,8 +643,4 @@ export class Ledger {
                        }
                })
        }
-
-       static {
-               this.isUnsupported ? void 0 : this.#poll().catch(() => { })
-       }
 }