export class Ledger {
static #listenTimeout: 30000 = 30000
static #openTimeout: 3000 = 3000
- static #polling: number | NodeJS.Timeout = 0
+ static #polling: number | NodeJS.Timeout
static #status: LedgerStatus = 'DISCONNECTED'
static #transport: typeof TransportHID | typeof TransportBLE | typeof TransportUSB
static #ADPU_CODES: { [key: string]: number } = Object.freeze({
}
try {
const version = await this.#version()
- globalThis.navigator?.hid?.addEventListener('connect', console.log)
- globalThis.navigator?.usb?.addEventListener('connect', console.log)
- globalThis.navigator?.hid?.addEventListener('disconnect', console.log)
- globalThis.navigator?.usb?.addEventListener('disconnect', console.log)
- globalThis.navigator?.hid?.addEventListener('connect', this.#onConnectHid)
- globalThis.navigator?.usb?.addEventListener('connect', this.#onConnectUsb)
- globalThis.navigator?.hid?.addEventListener('disconnect', this.#onDisconnectHid)
- globalThis.navigator?.usb?.addEventListener('disconnect', this.#onDisconnectUsb)
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.#polling ??= setInterval(() => this.connect(), 1000)
+ 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 = 'DISCONNECTED'
+ this.#status = 'BUSY'
}
- } else {
- this.#status = 'BUSY'
}
} catch (err) {
console.error(err)
+ clearInterval(this.#polling)
this.#status = 'DISCONNECTED'
}
- console.log(this.status)
return this.status
}
* Clears Ledger connections from all device interfaces.
*/
static disconnect (): void {
+ clearInterval(this.#polling)
setTimeout(async () => {
const hidDevices = await globalThis.navigator?.hid?.getDevices?.() ?? []
for (const device of hidDevices) {
return new Promise(r => setTimeout(r, 1000, { status: this.#STATUS_CODES[response] }))
}
- static #onConnectHid = async (e: HIDConnectionEvent): Promise<void> => {
- console.log('onConnectHid')
- console.log(e)
- if (e.device?.vendorId === this.UsbVendorId) {
- console.log('Ledger connected via HID')
- await this.connect()
- this.#polling = setInterval(this.connect, 1000)
- const { hid } = globalThis.navigator
- hid.addEventListener('disconnect', this.#onDisconnectHid)
- hid.removeEventListener('connect', this.#onConnectHid)
- }
- }
-
- static #onDisconnectHid = async (e: HIDConnectionEvent): Promise<void> => {
- console.log('onDisconnectHid')
- console.log(e)
- if (e.device?.vendorId === this.UsbVendorId) {
- console.log('Ledger disconnected via HID')
- clearInterval(this.#polling)
- await this.connect()
- const { hid } = globalThis.navigator
- hid.addEventListener('connect', this.#onConnectHid)
- hid.removeEventListener('disconnect', this.#onDisconnectHid)
- this.#status = 'DISCONNECTED'
- }
- }
-
- static #onConnectUsb = async (e: USBConnectionEvent): Promise<void> => {
- console.log('onConnectUsb')
- console.log(e)
- if (e.device?.vendorId === this.UsbVendorId) {
- console.log('Ledger connected via USB')
- await this.connect()
- this.#polling = setInterval(this.connect, 1000)
- const { usb } = globalThis.navigator
- usb.addEventListener('disconnect', this.#onDisconnectUsb)
- usb.removeEventListener('connect', this.#onConnectUsb)
- }
- }
-
- static #onDisconnectUsb = async (e: USBConnectionEvent): Promise<void> => {
- console.log('onDisconnectUsb')
- console.log(e)
- if (e.device?.vendorId === this.UsbVendorId) {
- console.log('Ledger disconnected via USB')
- clearInterval(this.#polling)
- await this.connect()
- const { usb } = globalThis.navigator
- usb.addEventListener('connect', this.#onConnectUsb)
- usb.removeEventListener('disconnect', this.#onDisconnectUsb)
- this.#status = 'DISCONNECTED'
- }
- }
-
/**
* Open the Nano app by launching a user flow.
*