]> git.codecow.com Git - libnemo.git/commitdiff
Back up WIP
authorChris Duncan <chris@zoso.dev>
Fri, 15 May 2026 23:02:11 +0000 (16:02 -0700)
committerChris Duncan <chris@zoso.dev>
Fri, 15 May 2026 23:02:11 +0000 (16:02 -0700)
src/lib/ledger/connect.ts
src/lib/ledger/index.ts
src/lib/ledger/version.ts [new file with mode: 0644]

index 4faae9866386f641724bf2a8ca487b595ecaacb1..a123c8ab4c0bd0f067cc4fb1254c76717e25b5ed 100644 (file)
@@ -1,14 +1,8 @@
 //! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
-import { APDU_CODES, LedgerResponse, LedgerStatus } from '.'
-import { bytes, dec } from '../convert'
-import { queue } from './queue'
-
-interface LedgerVersionResponse extends LedgerResponse {
-       name: string | null,
-       version: string | null
-}
+import { LedgerStatus, LedgerTransport } from '.'
+import { version } from './version'
 
 /**
  * Check if the Nano app is currently open and set device status accordingly.
@@ -20,64 +14,29 @@ interface LedgerVersionResponse extends LedgerResponse {
  * - LOCKED: Nano app is open but the device locked after a timeout
  * - CONNECTED: Nano app is open and listening
  */
-export async function connect (): Promise<LedgerStatus> {
+export async function connect (transport: LedgerTransport): Promise<LedgerStatus> {
+       let status = 'DISCONNECTED'
        try {
-               const v = await version()
+               const v = await version(transport)
                if (v.status === 'LOCKED_DEVICE') {
-                       this.#setStatus('LOCKED')
+                       status = 'LOCKED'
                } else if (v.status !== 'OK') {
-                       this.#setStatus('DISCONNECTED')
+                       status = 'DISCONNECTED'
                } else if (v.name === 'Nano') {
-                       const { status } = await this.account()
-                       if (status === 'OK') {
-                               this.#setStatus('CONNECTED')
+                       const a = await account()
+                       if (a.status === 'OK') {
+                               status = 'CONNECTED'
                        } else if (status === 'SECURITY_STATUS_NOT_SATISFIED') {
-                               this.#setStatus('LOCKED')
+                               status = 'LOCKED'
                        } else {
-                               this.#setStatus('DISCONNECTED')
+                               status = 'DISCONNECTED'
                        }
                } else {
-                       this.#setStatus('BUSY')
+                       status = 'BUSY'
                }
        } catch (err) {
                console.error('Ledger.#connect()', err)
-               this.#setStatus('DISCONNECTED')
+               status = 'DISCONNECTED'
        }
-       return this.#status
-}
-
-/**
- * Get the version of the current process. If a specific app is running, get
- * the app version. Otherwise, get the Ledger BOLOS version instead.
- *
- * https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps#get-information
- *
- * @returns Status, process name, and version
- */
-async function version (): Promise<LedgerVersionResponse> {
-       return queue(async () => {
-               try {
-                       const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
-                       const response = await transport
-                               .send(0xb0, APDU_CODES.version, APDU_CODES.paramUnused, APDU_CODES.paramUnused)
-                               .catch((err: any) => dec.toBytes(err.statusCode))
-                               .finally(async () => await transport.close()) as Uint8Array
-
-                       const statusCode = bytes.toDec(response.slice(-2)) as number
-                       const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
-                       if (status !== 'OK') {
-                               return { status, name: null, version: null }
-                       }
-
-                       const nameLength = response[1]
-                       const name = response.slice(2, 2 + nameLength).toString()
-                       const versionLength = response[2 + nameLength]
-                       const version = response.slice(2 + nameLength + 1, 2 + nameLength + 1 + versionLength).toString()
-
-                       return { status, name, version }
-               } catch (err: any) {
-                       console.error('Ledger.#version()', err)
-                       return { status: err.message, name: null, version: null }
-               }
-       })
+       return status
 }
index 09e3cd9773dc736f82c02fbe56b2a6fbe8dc967e..66d687307573a38327fb19188608d966ed1c6fe9 100644 (file)
@@ -14,6 +14,7 @@ import { Wallet } from '../wallet'
 import { queue } from './queue'
 
 export type LedgerStatus = 'UNSUPPORTED' | 'DISCONNECTED' | 'BUSY' | 'LOCKED' | 'CONNECTED'
+export type LedgerTransport = typeof TransportHID | typeof TransportBLE | typeof TransportUSB
 
 export interface LedgerResponse {
        status: string
@@ -39,6 +40,15 @@ export const APDU_CODES: Record<string, number> = Object.freeze({
        signNonce: 0x05,
        paramUnused: 0x00
 })
+export const STATUS_CODES: Readonly<Record<number, string>> = Object.freeze({
+       ...Object.fromEntries(Object.entries(StatusCodes).map(([k, v]) => [+v, k])),
+       0x6807: 'APPLICATION_NOT_INSTALLED',
+       0x6d00: 'APPLICATION_ALREADY_LAUNCHED',
+       0x6a81: 'INVALID_SIGNATURE',
+       0x6a82: 'CACHE_MISS'
+})
+export const listenTimeout: 30000 = 30000
+export const openTimeout: 3000 = 3000
 
 /**
 * Ledger hardware wallet created by communicating with a Ledger device via ADPU
@@ -50,8 +60,6 @@ export const APDU_CODES: Record<string, number> = Object.freeze({
 */
 export class Ledger {
        static #isPolling: boolean = false
-       static #listenTimeout: 30000 = 30000
-       static #openTimeout: 3000 = 3000
        static #status: LedgerStatus = 'DISCONNECTED'
        static #transport: typeof TransportHID | typeof TransportBLE | typeof TransportUSB
        static #DERIVATION_PATH: Uint8Array = new Uint8Array([
@@ -59,13 +67,6 @@ export class Ledger {
                ...dec.toBytes(BIP44_PURPOSE + HARDENED_OFFSET, 4),
                ...dec.toBytes(BIP44_COIN_NANO + HARDENED_OFFSET, 4)
        ])
-       static #STATUS_CODES: Readonly<Record<number, string>> = Object.freeze({
-               ...Object.fromEntries(Object.entries(StatusCodes).map(([k, v]) => [+v, k])),
-               0x6807: 'APPLICATION_NOT_INSTALLED',
-               0x6d00: 'APPLICATION_ALREADY_LAUNCHED',
-               0x6a81: 'INVALID_SIGNATURE',
-               0x6a82: 'CACHE_MISS'
-       })
 
        // Compose event emission for status changes
        static #eventTarget = new EventTarget()
@@ -139,7 +140,7 @@ export class Ledger {
                                        .finally(async () => await transport.close()) as Uint8Array
 
                                const statusCode = bytes.toDec(response.slice(-2)) as number
-                               const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+                               const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
                                if (status !== 'OK') {
                                        return { status, publicKey: null, address: null }
                                }
@@ -399,7 +400,7 @@ export class Ledger {
                                        .catch((err: any) => err.statusCode)
                                        .finally(async () => await transport.close()) as number
 
-                               return { status: this.#STATUS_CODES[response] }
+                               return { status: STATUS_CODES[response] }
                        } catch (err: any) {
                                console.error('Ledger.#cacheBlock()', err)
                                return { status: err.message }
@@ -428,7 +429,7 @@ export class Ledger {
                                .then((res: Buffer) => bytes.toDec(res))
                                .catch((err: any) => err.statusCode)
                                .finally(async () => await transport.close()) as number
-                       return new Promise(r => setTimeout(r, 1000, { status: this.#STATUS_CODES[response] }))
+                       return new Promise(r => setTimeout(r, 1000, { status: STATUS_CODES[response] }))
                })
        }
 
@@ -454,7 +455,7 @@ export class Ledger {
                                .then((res: Buffer) => bytes.toDec(res))
                                .catch((err: any) => err.statusCode)
                                .finally(async () => await transport.close()) as number
-                       return new Promise(r => setTimeout(r, 1000, { status: this.#STATUS_CODES[response] }))
+                       return new Promise(r => setTimeout(r, 1000, { status: STATUS_CODES[response] }))
                })
        }
 
@@ -537,7 +538,7 @@ export class Ledger {
                                        .finally(async () => await transport.close()) as Uint8Array
 
                                const statusCode = bytes.toDec(response.slice(-2)) as number
-                               const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+                               const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
 
                                if (response.byteLength === 2) {
                                        return { status, signature: null }
@@ -582,7 +583,7 @@ export class Ledger {
                                .finally(async () => await transport.close()) as Uint8Array
 
                        const statusCode = bytes.toDec(response.slice(-2)) as number
-                       const status = this.#STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+                       const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
 
                        if (response.byteLength === 2) {
                                return { status, signature: null }
diff --git a/src/lib/ledger/version.ts b/src/lib/ledger/version.ts
new file mode 100644 (file)
index 0000000..b643156
--- /dev/null
@@ -0,0 +1,46 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+import { APDU_CODES, LedgerResponse, LedgerTransport, STATUS_CODES, listenTimeout, openTimeout } from '.'
+import { bytes, dec } from '../convert'
+import { queue } from './queue'
+
+interface LedgerVersionResponse extends LedgerResponse {
+       name: string | null,
+       version: string | null
+}
+/**
+ * Get the version of the current process. If a specific app is running, get
+ * the app version. Otherwise, get the Ledger BOLOS version instead.
+ *
+ * https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps#get-information
+ *
+ * @returns Status, process name, and version
+ */
+export async function version (transport: LedgerTransport): Promise<LedgerVersionResponse> {
+       return queue(async () => {
+               try {
+                       const t = await transport.create(openTimeout, listenTimeout)
+                       const response = await t
+                               .send(0xb0, APDU_CODES.version, APDU_CODES.paramUnused, APDU_CODES.paramUnused)
+                               .catch((err: any) => dec.toBytes(err.statusCode))
+                               .finally(async () => await t.close()) as Uint8Array
+
+                       const statusCode = bytes.toDec(response.slice(-2)) as number
+                       const status = STATUS_CODES[statusCode] ?? 'UNKNOWN_ERROR'
+                       if (status !== 'OK') {
+                               return { status, name: null, version: null }
+                       }
+
+                       const nameLength = response[1]
+                       const name = response.slice(2, 2 + nameLength).toString()
+                       const versionLength = response[2 + nameLength]
+                       const version = response.slice(2 + nameLength + 1, 2 + nameLength + 1 + versionLength).toString()
+
+                       return { status, name, version }
+               } catch (err: any) {
+                       console.error('Ledger.#version()', err)
+                       return { status: err.message, name: null, version: null }
+               }
+       })
+}