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'
* 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<string, number> = Object.freeze({
* @returns Response object containing command status, public key, and address
*/
static async account (index: number = 0, show: boolean = false): Promise<LedgerAccountResponse> {
- return this.#enqueue(async () => {
+ return enqueue(async () => {
try {
if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {
throw new TypeError('Invalid account index')
* connection updates.
*/
static async disconnect (): Promise<void> {
- this.#enqueue(async () => {
+ enqueue(async () => {
try {
this.#isPolling = false
const hidDevices = await navigator?.hid?.getDevices?.() ?? []
* @returns Status of command
*/
static async #cacheBlock (index: number = 0, block: Block): Promise<LedgerResponse> {
- return this.#enqueue(async () => {
+ return enqueue(async () => {
try {
if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {
throw new TypeError('Invalid account index')
* @returns Status of command
*/
static async #close (): Promise<LedgerResponse> {
- 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)
return this.#status
}
- /**
- * Serially executes asynchronous functions.
- */
- static async #enqueue<T> (task: () => Promise<T>): Promise<T> {
- 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<T>((resolve, reject) => {
- this.#queue.push({ task, resolve, reject })
- if (this.#isIdle) process()
- })
- }
-
/**
* Open the Nano app by launching a user flow.
*
* @returns Status of command
*/
static async #open (): Promise<LedgerResponse> {
- 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
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')
* @returns {Promise} Status and signature
*/
static async #signNonce (index: number, nonce: Uint8Array<ArrayBuffer>): Promise<LedgerSignResponse> {
- return this.#enqueue(async () => {
+ return enqueue(async () => {
if (typeof index !== 'number' || index < 0 || index >= HARDENED_OFFSET) {
throw new TypeError('Invalid account index')
}
* @returns Status, process name, and version
*/
static async #version (): Promise<LedgerVersionResponse> {
- return this.#enqueue(async () => {
+ return enqueue(async () => {
try {
const transport = await this.#transport.create(this.#openTimeout, this.#listenTimeout)
const response = await transport
--- /dev/null
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@codecow.com>
+//! 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<T> (task: () => Promise<T>): Promise<T> {
+ 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<T>((resolve, reject) => {
+ queue.push({ task, resolve, reject })
+ if (isIdle) process()
+ })
+}