-
Notifications
You must be signed in to change notification settings - Fork 379
Only poll USB devices list when a change is detected #191
Changes from 6 commits
33a2dee
4442a98
f070025
8566412
9483bd9
199a2ee
9b5451b
3b897f3
83f13c3
c862d38
b98dfe6
c440e5e
64dad39
ae6f5c5
666f6ba
96c1e35
a1987cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,31 @@ | ||
// @flow | ||
|
||
import EventEmitter from "events"; | ||
import usbDetect from "usb-detection"; | ||
import getDevices from "./getDevices"; | ||
|
||
const VENDOR_ID = 11415; // Ledger's Vendor ID for filtering | ||
const MAX_ATTEMPTS = 10; | ||
|
||
export default ( | ||
delay: number, | ||
listenDevicesPollingSkip: () => boolean | ||
listenDevicesPollingSkip: () => boolean, | ||
debugMode: boolean | ||
): { | ||
events: EventEmitter, | ||
stop: () => void | ||
} => { | ||
const events = new EventEmitter(); | ||
events.setMaxListeners(0); | ||
|
||
let timeoutDetection; | ||
let listDevices = getDevices(); | ||
|
||
const debug = (...args) => { | ||
if (debugMode && args[0]) { | ||
console.log("[listenDevices]", ...args); | ||
} | ||
}; | ||
|
||
const flatDevice = d => d.path; | ||
|
||
const getFlatDevices = () => [ | ||
|
@@ -27,37 +37,93 @@ export default ( | |
|
||
let lastDevices = getFlatDevices(); | ||
|
||
const checkDevices = () => { | ||
const poll = (type, attempt = 1) => { | ||
let changeFound = false; | ||
|
||
if (!listenDevicesPollingSkip()) { | ||
debug(`Polling for ${type} [attempt ${attempt}/${MAX_ATTEMPTS}]`); | ||
|
||
const currentDevices = getFlatDevices(); | ||
|
||
const newDevices = currentDevices.filter(d => !lastDevices.includes(d)); | ||
const removeDevices = lastDevices.filter( | ||
d => !currentDevices.includes(d) | ||
); | ||
if (type === "add") { | ||
const newDevices = currentDevices.filter(d => !lastDevices.includes(d)); | ||
|
||
if (newDevices.length > 0) { | ||
debug("New device found:", newDevices); | ||
|
||
listDevices = getDevices(); | ||
events.emit("add", getDeviceByPaths(newDevices)); | ||
|
||
if (newDevices.length > 0) { | ||
listDevices = getDevices(); | ||
events.emit("add", getDeviceByPaths(newDevices)); | ||
changeFound = true; | ||
} else { | ||
debug("No new device found"); | ||
} | ||
} | ||
|
||
if (removeDevices.length > 0) { | ||
events.emit("remove", getDeviceByPaths(removeDevices)); | ||
listDevices = listDevices.filter( | ||
d => !removeDevices.includes(flatDevice(d)) | ||
if (type === "remove") { | ||
const removeDevices = lastDevices.filter( | ||
d => !currentDevices.includes(d) | ||
); | ||
|
||
if (removeDevices.length > 0) { | ||
debug("Removed device found:", removeDevices); | ||
|
||
events.emit("remove", getDeviceByPaths(removeDevices)); | ||
listDevices = listDevices.filter( | ||
d => !removeDevices.includes(flatDevice(d)) | ||
); | ||
|
||
changeFound = true; | ||
} else { | ||
debug("No removed device found"); | ||
} | ||
} | ||
|
||
if (changeFound) { | ||
lastDevices = currentDevices; | ||
} else { | ||
if (attempt < MAX_ATTEMPTS) { | ||
const newDelay = delay * attempt; | ||
|
||
debug(`Repolling ${type} in ${newDelay}ms`); | ||
|
||
setTimeout(() => { | ||
poll(type, attempt + 1); | ||
}, newDelay); | ||
} else { | ||
debug(`Giving up after ${attempt} attempts`); | ||
} | ||
} | ||
} else { | ||
debug(`Polling skipped, retrying in ${delay}ms`); | ||
|
||
lastDevices = currentDevices; | ||
setTimeout(() => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this timeout does not get cleared if the whole listen is stop()-ed |
||
poll(type); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have to poll multiple times because event happen too soon? Is it always the case, if so it would be great to estimate a minimal time and do an initial timeout to likely never have to poll twice. 🤔 I think it would maybe preferable to refactor things using lodash e.g.
not sure what delay will be , if it make sense to be the same 1s that was before 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
From my tests I have to poll a second time (after 500ms) only for device removal on macOS and Windows, but I have no idea how much this behavior is consistent from machine to machine 🤔 |
||
}, delay); | ||
} | ||
setTimeout(checkDevices, delay); | ||
}; | ||
|
||
timeoutDetection = setTimeout(checkDevices, delay); | ||
debug("Starting to monitor USB for ledger devices"); | ||
usbDetect.startMonitoring(); | ||
|
||
// Detect add | ||
usbDetect.on(`add:${VENDOR_ID}`, device => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we should filter like it was here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a pre-filter, taking advantage of the vendor ID filtering offered by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 |
||
debug("Device add detected:", device.deviceName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and this device don't have the path ? 😢 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it doesn't 😭 MadLittleMods/node-usb-detection#19 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🆗 |
||
|
||
poll("add"); | ||
}); | ||
|
||
// Detect remove | ||
usbDetect.on(`remove:${VENDOR_ID}`, device => { | ||
debug("Device removal detected:", device.deviceName); | ||
|
||
poll("remove"); | ||
}); | ||
|
||
return { | ||
stop: () => { | ||
clearTimeout(timeoutDetection); | ||
debug("Stopping USB monitoring"); | ||
usbDetect.stopMonitoring(); | ||
}, | ||
events | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be better if we would allow to set a log function instead. I moved to this paradigm in ledgerjs recently too, because on Ledger Live we use a custom logger, which is not console.*