// @flow import createSemaphore from 'semaphore' import type Transport from '@ledgerhq/hw-transport' import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' import { retry } from './promise' // all open to device must use openDevice so we can prevent race conditions // and guarantee we do one device access at a time. It also will handle the .close() // NOTE optim: in the future we can debounce the close & reuse the same transport instance. type WithDevice = (devicePath: string) => (job: (Transport<*>) => Promise) => Promise const semaphorePerDevice = {} export const withDevice: WithDevice = devicePath => { const sem = semaphorePerDevice[devicePath] || (semaphorePerDevice[devicePath] = createSemaphore(1)) return job => takeSemaphorePromise(sem, async () => { const t = await retry(() => TransportNodeHid.open(devicePath), { maxRetry: 1 }) try { const res = await job(t) // $FlowFixMe return res } finally { await t.close() } }) } function takeSemaphorePromise(sem, f: () => Promise): Promise { return new Promise((resolve, reject) => { sem.take(() => { f().then( r => { sem.leave() resolve(r) }, e => { sem.leave() reject(e) }, ) }) }) }