*/
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 }[] = []
: 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)
})
}
+ /**
+ * 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.
*/
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'
console.warn('Error polling Ledger device')
this.#status = 'DISCONNECTED'
} finally {
- setTimeout(() => this.#poll(), 500)
+ this.#isPolling ? setTimeout(() => this.#poll(), 500) : void 0
}
}
}
})
}
-
- static {
- this.isUnsupported ? void 0 : this.#poll().catch(() => { })
- }
}