From: Chris Duncan Date: Fri, 15 May 2026 20:18:04 +0000 (-0700) Subject: Move Ledger command queue to its own file. X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=86cb6b900609071adbc8411fa353a948fb407ff5;p=libnemo.git Move Ledger command queue to its own file. --- diff --git a/src/lib/ledger/index.ts b/src/lib/ledger/index.ts index 1a8ae46..6d2c858 100644 --- a/src/lib/ledger/index.ts +++ b/src/lib/ledger/index.ts @@ -11,6 +11,7 @@ import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from '../constants' import { bytes, dec, hex, utf8 } from '../convert' import { Rpc } from '../rpc' import { Wallet } from '../wallet' +import { enqueue } from './queue' type LedgerStatus = 'UNSUPPORTED' | 'DISCONNECTED' | 'BUSY' | 'LOCKED' | 'CONNECTED' @@ -42,11 +43,9 @@ interface LedgerSignResponse extends LedgerResponse { * https://github.com/roosmaa/ledger-app-nano/blob/master/doc/nano.md */ 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 }[] = [] static #status: LedgerStatus = 'DISCONNECTED' static #transport: typeof TransportHID | typeof TransportBLE | typeof TransportUSB static #ADPU_CODES: Record = Object.freeze({ @@ -129,7 +128,7 @@ export class Ledger { * @returns Response object containing command status, public key, and address */ static async account (index: number = 0, show: boolean = false): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { try { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') @@ -207,7 +206,7 @@ export class Ledger { * connection updates. */ static async disconnect (): Promise { - this.#enqueue(async () => { + enqueue(async () => { try { this.#isPolling = false const hidDevices = await navigator?.hid?.getDevices?.() ?? [] @@ -369,7 +368,7 @@ export class Ledger { * @returns Status of command */ static async #cacheBlock (index: number = 0, block: Block): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { try { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') @@ -426,7 +425,7 @@ export class Ledger { * @returns Status of command */ static async #close (): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout) const response = await transport .send(0xb0, 0xa7, this.#ADPU_CODES.paramUnused, this.#ADPU_CODES.paramUnused) @@ -473,24 +472,6 @@ export class Ledger { return this.#status } - /** - * Serially executes asynchronous functions. - */ - static async #enqueue (task: () => Promise): Promise { - const process = () => { - const next = this.#queue.shift() - if (next == null) return this.#isIdle = true - const { task, resolve, reject } = next - this.#isIdle = !task - task?.().then(resolve).catch(reject).finally(process) - } - if (typeof task !== 'function') throw new TypeError('task is not a function') - return new Promise((resolve, reject) => { - this.#queue.push({ task, resolve, reject }) - if (this.#isIdle) process() - }) - } - /** * Open the Nano app by launching a user flow. * @@ -505,7 +486,7 @@ export class Ledger { * @returns Status of command */ static async #open (): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { const name = new TextEncoder().encode('Nano') const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout) const response = await transport @@ -570,7 +551,7 @@ export class Ledger { if (block.signature !== undefined) { throw new TypeError('Block signature already exists', { cause: block.signature }) } - return this.#enqueue(async () => { + return enqueue(async () => { try { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') @@ -623,7 +604,7 @@ export class Ledger { * @returns {Promise} Status and signature */ static async #signNonce (index: number, nonce: Uint8Array): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) { throw new TypeError('Invalid account index') } @@ -664,7 +645,7 @@ export class Ledger { * @returns Status, process name, and version */ static async #version (): Promise { - return this.#enqueue(async () => { + return enqueue(async () => { try { const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout) const response = await transport diff --git a/src/lib/ledger/queue.ts b/src/lib/ledger/queue.ts new file mode 100644 index 0000000..c1de428 --- /dev/null +++ b/src/lib/ledger/queue.ts @@ -0,0 +1,24 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +const queue: { task: Function, resolve: Function, reject: Function }[] = [] + +let isIdle: boolean = true + +/** +* Serially executes asynchronous functions. +*/ +export async function enqueue (task: () => Promise): Promise { + const process = () => { + const next = queue.shift() + if (next == null) return isIdle = true + const { task, resolve, reject } = next + isIdle = !task + task?.().then(resolve).catch(reject).finally(process) + } + if (typeof task !== 'function') throw new TypeError('task is not a function') + return new Promise((resolve, reject) => { + queue.push({ task, resolve, reject }) + if (isIdle) process() + }) +}