-
Notifications
You must be signed in to change notification settings - Fork 295
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f0370af
commit 2b2fd73
Showing
9 changed files
with
352 additions
and
216 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
source/main/ipc/hardwareWallets/ledger/deviceDetection/deviceDetection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid-noevents'; | ||
|
||
import { logger } from '../../../../utils/logging'; | ||
import { DeviceTracker } from './deviceTracker'; | ||
import { detectDevices as useEventDrivenDetection } from './eventDrivenDetection'; | ||
import { detectDevices as usePollingDrivenDetection } from './pollingDrivenDetection'; | ||
import { Detector, TrackedDevice, DectorUnsubscriber } from './types'; | ||
|
||
type Payload = { | ||
type: 'add' | 'remove'; | ||
} & TrackedDevice; | ||
|
||
export const deviceDetection = ( | ||
onAdd: (arg0: Payload) => void, | ||
onRemove: (arg0: Payload) => void | ||
) => { | ||
Promise.resolve(DeviceTracker.getDevices()).then((devices) => { | ||
// this needs to run asynchronously so the subscription is defined during this phase | ||
for (const device of devices) { | ||
onAdd({ | ||
type: 'add', | ||
...DeviceTracker.getTrackedDeviceByPath(device.path), | ||
}); | ||
} | ||
}); | ||
|
||
const handleOnAdd = (trackedDevice: TrackedDevice) => | ||
onAdd({ type: 'add', ...trackedDevice }); | ||
const handleOnRemove = (trackedDevice: TrackedDevice) => | ||
onRemove({ type: 'remove', ...trackedDevice }); | ||
|
||
let detectDevices: Detector; | ||
|
||
if (TransportNodeHid.isSupported()) { | ||
logger.info('[HW-DEBUG] Using usb-detection'); | ||
|
||
detectDevices = useEventDrivenDetection; | ||
} else { | ||
logger.info('[HW-DEBUG] Using polling'); | ||
|
||
detectDevices = usePollingDrivenDetection; | ||
} | ||
|
||
detectDevices(handleOnAdd, handleOnRemove); | ||
}; | ||
|
||
export const waitForDevice = () => { | ||
return new Promise<TrackedDevice>(async (resolve) => { | ||
const currentDevices = await DeviceTracker.getDevices(); | ||
|
||
for (const device of currentDevices) { | ||
return resolve(DeviceTracker.getTrackedDeviceByPath(device.path)); | ||
} | ||
|
||
let detectDevices: Detector; | ||
let unsubscribe: DectorUnsubscriber = null; | ||
|
||
if (TransportNodeHid.isSupported()) { | ||
logger.info('[HW-DEBUG] Using usb-detection'); | ||
|
||
detectDevices = useEventDrivenDetection; | ||
} else { | ||
logger.info('[HW-DEBUG] Using polling'); | ||
|
||
detectDevices = usePollingDrivenDetection; | ||
} | ||
|
||
const handleOnAdd = (trackedDevice: TrackedDevice) => { | ||
if (unsubscribe) { | ||
unsubscribe(); | ||
} | ||
|
||
return resolve(trackedDevice); | ||
}; | ||
|
||
const handleOnRemove = () => false; | ||
|
||
unsubscribe = detectDevices(handleOnAdd, handleOnRemove); | ||
}); | ||
}; |
95 changes: 95 additions & 0 deletions
95
source/main/ipc/hardwareWallets/ledger/deviceDetection/deviceTracker.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { getDevices } from '@ledgerhq/hw-transport-node-hid-noevents'; | ||
import { identifyUSBProductId } from '@ledgerhq/devices'; | ||
|
||
import { logger } from '../../../../utils/logging'; | ||
import { Device, TrackedDevice } from './types'; | ||
|
||
export class DeviceTracker { | ||
knownDevices: Map<string, TrackedDevice>; | ||
|
||
static getUniqueDevices() { | ||
return [...new Set<string>(getDevices().map((d: Device) => d.path))]; | ||
} | ||
|
||
static getDeviceByPath(path: string): Device { | ||
return getDevices().find((d: Device) => d.path === path); | ||
} | ||
|
||
static getTrackedDeviceByPath(path: string) { | ||
const device = DeviceTracker.getDeviceByPath(path); | ||
|
||
const descriptor: string = device.path; | ||
const deviceModel = (identifyUSBProductId( | ||
device.productId | ||
) as unknown) as string; | ||
|
||
return { device, deviceModel, descriptor } as TrackedDevice; | ||
} | ||
|
||
static getDevices() { | ||
return getDevices(); | ||
} | ||
|
||
constructor() { | ||
this.knownDevices = new Map(); | ||
|
||
getDevices().forEach((d) => this.knownDevices.set(d.path, d)); | ||
} | ||
|
||
findNewDevice() { | ||
const currentDevices = DeviceTracker.getUniqueDevices(); | ||
const [newDevicePath] = currentDevices.filter( | ||
(d) => !this.knownDevices.has(d) | ||
); | ||
const knownDevicesPath = Array.from(this.knownDevices.keys()); | ||
|
||
if (newDevicePath) { | ||
const newDevice = DeviceTracker.getTrackedDeviceByPath(newDevicePath); | ||
this.knownDevices.set(newDevicePath, newDevice); | ||
|
||
logger.info('[HW-DEBUG] DeviceTracker - New device found:', { | ||
newDevicePath, | ||
currentDevices, | ||
knownDevicesPath, | ||
}); | ||
|
||
return newDevice; | ||
} | ||
|
||
logger.info('[HW-DEBUG] DeviceTracker - No new device found:', { | ||
currentDevices, | ||
knownDevicesPath, | ||
}); | ||
|
||
return null; | ||
} | ||
|
||
findRemovedDevice() { | ||
const currentDevices = DeviceTracker.getUniqueDevices(); | ||
const [removedDevicePath] = Array.from(this.knownDevices.keys()) | ||
.filter((d) => !currentDevices.includes(d)) | ||
.map((d) => d); | ||
const knownDevicesPath = Array.from(this.knownDevices.keys()); | ||
|
||
if (removedDevicePath) { | ||
const removedDevice = this.knownDevices.get(removedDevicePath); | ||
this.knownDevices.delete(removedDevicePath); | ||
|
||
logger.info('[HW-DEBUG] DeviceTracker - Removed device found:', { | ||
removedDevicePath, | ||
removedDevice, | ||
currentDevices, | ||
knownDevicesPath, | ||
}); | ||
|
||
return removedDevice; | ||
} | ||
|
||
logger.info('[HW-DEBUG] DeviceTracker - No removed device found:', { | ||
currentDevices, | ||
knownDevicesPath, | ||
}); | ||
|
||
return null; | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
source/main/ipc/hardwareWallets/ledger/deviceDetection/eventDrivenDetection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { ledgerUSBVendorId } from '@ledgerhq/devices'; | ||
import usbDetect from 'usb-detection'; | ||
|
||
import { logger } from '../../../../utils/logging'; | ||
import { DeviceTracker } from './deviceTracker'; | ||
import { Detector } from './types'; | ||
|
||
const deviceToLog = ({ productId, locationId, deviceAddress }) => | ||
`productId=${productId} locationId=${locationId} deviceAddress=${deviceAddress}`; | ||
|
||
let isMonitoring = false; | ||
|
||
const monitorUSBDevices = () => { | ||
if (!isMonitoring) { | ||
isMonitoring = true; | ||
usbDetect.startMonitoring(); | ||
} | ||
}; | ||
|
||
const stopMonitoring = () => { | ||
if (isMonitoring) { | ||
// redeem the monitoring so the process can be terminated. | ||
usbDetect.stopMonitoring(); | ||
} | ||
}; | ||
|
||
// No better way for now. see https://github.com/LedgerHQ/ledgerjs/issues/434 | ||
process.on('exit', () => { | ||
stopMonitoring(); | ||
}); | ||
|
||
const addEvent = `add:${ledgerUSBVendorId}`; | ||
const removeEvent = `remove:${ledgerUSBVendorId}`; | ||
|
||
export const detectDevices: Detector = (onAdd, onRemove) => { | ||
let timeout; | ||
|
||
monitorUSBDevices(); | ||
|
||
const deviceTracker = new DeviceTracker(); | ||
|
||
const add = (device: usbDetect.Device) => { | ||
logger.info( | ||
`[HW-DEBUG] USB-DETECTION ADDED DEVICE: ${deviceToLog(device)}` | ||
); | ||
|
||
if (!timeout) { | ||
// a time is needed for the device to actually be connectable over HID.. | ||
// we also take this time to not emit the device yet and potentially cancel it if a remove happens. | ||
timeout = setTimeout(() => { | ||
const newDevice = deviceTracker.findNewDevice(); | ||
|
||
if (newDevice) { | ||
onAdd(newDevice); | ||
} | ||
|
||
timeout = null; | ||
}, 1500); | ||
} | ||
}; | ||
|
||
const remove = (device: usbDetect.Device) => { | ||
logger.info( | ||
`[HW-DEBUG] USB-DETECTION REMOVED DEVICE: ${deviceToLog(device)}` | ||
); | ||
|
||
if (timeout) { | ||
clearTimeout(timeout); | ||
timeout = null; | ||
} else { | ||
const removedDevice = deviceTracker.findNewDevice(); | ||
|
||
if (removedDevice) { | ||
onRemove(removedDevice); | ||
} | ||
} | ||
}; | ||
|
||
usbDetect.on(addEvent, add); | ||
usbDetect.on(removeEvent, remove); | ||
|
||
return () => { | ||
if (timeout) clearTimeout(timeout); | ||
// @ts-expect-error not all EventEmitter methods are covered in its definition file | ||
usbDetect.off(addEvent, add); | ||
// @ts-expect-error not all EventEmitter methods are covered in its definition file | ||
usbDetect.off(removeEvent, remove); | ||
}; | ||
}; |
1 change: 1 addition & 0 deletions
1
source/main/ipc/hardwareWallets/ledger/deviceDetection/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { deviceDetection, waitForDevice } from './deviceDetection'; |
40 changes: 40 additions & 0 deletions
40
source/main/ipc/hardwareWallets/ledger/deviceDetection/pollingDrivenDetection.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { logger } from '../../../../utils/logging'; | ||
import { DeviceTracker } from './deviceTracker'; | ||
import { Detector } from './types'; | ||
|
||
export const detectDevices: Detector = (onAdd, onRemove) => { | ||
let timer; | ||
|
||
const stopPolling = () => { | ||
if (timer) { | ||
clearInterval(timer); | ||
} | ||
}; | ||
|
||
process.on('exit', () => { | ||
stopPolling(); | ||
}); | ||
|
||
const deviceTracker = new DeviceTracker(); | ||
|
||
const runPolling = () => { | ||
logger.info('[HW-DEBUG] Polling devices'); | ||
const newDevice = deviceTracker.findNewDevice(); | ||
|
||
if (newDevice) { | ||
onAdd(newDevice); | ||
} | ||
|
||
const removedDevice = deviceTracker.findNewDevice(); | ||
|
||
if (removedDevice) { | ||
onRemove(removedDevice); | ||
} | ||
}; | ||
|
||
timer = setInterval(runPolling, 1000); | ||
|
||
return () => { | ||
stopPolling(); | ||
}; | ||
}; |
Oops, something went wrong.