@xkeys-lib/core
Version:
NPM package to interact with the X-keys panels
198 lines • 8.54 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericXKeysWatcher = void 0;
const events_1 = require("events");
/**
* Set up a watcher for newly connected X-keys panels.
* Note: It is highly recommended to set up a listener for the disconnected event on the X-keys panel, to clean up after a disconnected device.
*/
class GenericXKeysWatcher extends events_1.EventEmitter {
constructor(_options) {
super();
this._options = _options;
this.updateConnectedDevicesTimeout = null;
this.updateConnectedDevicesIsRunning = false;
this.updateConnectedDevicesRunAgain = false;
this.seenDevices = new Set();
this.setupXkeys = new Map();
/** A value that is incremented whenever we expect to find a new or removed device in updateConnectedDevices(). */
this.shouldFindChangedReTries = 0;
this.isActive = true;
this.debug = false;
/** A list of the devices we've called setupNewDevice() for */
// private setupXkeysPanels: XKeys[] = []
this.prevConnectedIdentifiers = {};
/** Unique unitIds grouped into productId groups. */
this.uniqueIds = new Map();
// Do a sweep for all currently connected X-keys panels:
this.triggerUpdateConnectedDevices(false);
}
get options() {
var _a, _b, _c, _d, _e, _f;
return {
automaticUnitIdMode: (_b = (_a = this._options) === null || _a === void 0 ? void 0 : _a.automaticUnitIdMode) !== null && _b !== void 0 ? _b : false,
usePolling: (_d = (_c = this._options) === null || _c === void 0 ? void 0 : _c.usePolling) !== null && _d !== void 0 ? _d : false,
pollingInterval: (_f = (_e = this._options) === null || _e === void 0 ? void 0 : _e.pollingInterval) !== null && _f !== void 0 ? _f : 1000,
};
}
/**
* Stop the watcher
* @param closeAllDevices Set to false in order to NOT close all devices. Use this if you only want to stop the watching. Defaults to true
*/
async stop(closeAllDevices = true) {
// To be implemented by the subclass and call super.stop() at the end
this.isActive = false;
if (closeAllDevices) {
// In order for an application to close gracefully,
// we need to close all devices that we've called setupXkeysPanel() on:
await Promise.all(Array.from(this.seenDevices.keys()).map(async (device) => this.handleRemovedDevice(device)));
}
}
triggerUpdateConnectedDevices(somethingWasAddedOrRemoved) {
if (somethingWasAddedOrRemoved) {
this.shouldFindChangedReTries++;
}
if (this.updateConnectedDevicesIsRunning) {
// It is already running, so we'll run it again later, when it's done:
this.updateConnectedDevicesRunAgain = true;
return;
}
else if (this.updateConnectedDevicesTimeout) {
// It is already scheduled to run.
if (somethingWasAddedOrRemoved) {
// Set it to run now:
clearTimeout(this.updateConnectedDevicesTimeout);
this.updateConnectedDevicesTimeout = null;
}
else {
return;
}
}
if (!this.updateConnectedDevicesTimeout) {
this.updateConnectedDevicesRunAgain = false;
this.updateConnectedDevicesTimeout = setTimeout(() => {
this.updateConnectedDevicesTimeout = null;
this.updateConnectedDevicesIsRunning = true;
this.updateConnectedDevices()
.catch(console.error)
.finally(() => {
this.updateConnectedDevicesIsRunning = false;
if (this.updateConnectedDevicesRunAgain)
this.triggerUpdateConnectedDevices(false);
});
}, somethingWasAddedOrRemoved ? 10 : Math.min(this.options.pollingInterval * 0.5, 300));
}
}
async updateConnectedDevices() {
this.debugLog('updateConnectedDevices');
const connectedDevices = await this.getConnectedDevices();
let removed = 0;
let added = 0;
// Removed devices:
for (const device of this.seenDevices.keys()) {
if (!connectedDevices.has(device)) {
// A device has been removed
this.debugLog('removed');
removed++;
await this.handleRemovedDevice(device);
}
}
// Added devices:
for (const connectedDevice of connectedDevices.keys()) {
if (!this.seenDevices.has(connectedDevice)) {
// A device has been added
this.debugLog('added');
added++;
this.seenDevices.add(connectedDevice);
this.handleNewDevice(connectedDevice);
}
}
if (this.shouldFindChangedReTries > 0 && (added === 0 || removed === 0)) {
// We expected to find something changed, but didn't.
// Try again later:
this.shouldFindChangedReTries--;
this.triggerUpdateConnectedDevices(false);
}
else {
this.shouldFindChangedReTries = 0;
}
}
handleNewDevice(device) {
// This is called when a new device has been added / connected
this.setupXkeysPanel(device)
.then(async (xKeysPanel) => {
// Since this is async, check if the panel is still connected:
if (this.seenDevices.has(device)) {
await this.setupNewDevice(device, xKeysPanel);
}
else {
await this.handleRemovedDevice(device);
}
})
.catch((err) => {
this.emit('error', err);
});
}
async handleRemovedDevice(device) {
// This is called when a device has been removed / disconnected
this.seenDevices.delete(device);
const xkeys = this.setupXkeys.get(device);
this.debugLog('aa');
if (xkeys) {
this.debugLog('bb');
await xkeys._handleDeviceDisconnected();
this.setupXkeys.delete(device);
}
}
async setupNewDevice(device, xKeysPanel) {
// Store for future reference:
this.setupXkeys.set(device, xKeysPanel);
xKeysPanel.once('disconnected', () => {
this.handleRemovedDevice(device).catch((e) => this.emit('error', e));
});
// this.setupXkeysPanels.push(xkeysPanel)
if (this.options.automaticUnitIdMode) {
if (xKeysPanel.unitId === 0) {
// if it is 0, we assume that it's new from the factory and can be safely changed
xKeysPanel.setUnitId(this._getNextUniqueId(xKeysPanel)); // the lookup-cache is stored either in memory, or preferably on disk
}
// the PID+UID pair is enough to uniquely identify a panel.
const uniqueIdentifier = xKeysPanel.uniqueId;
const previousXKeysPanel = this.prevConnectedIdentifiers[uniqueIdentifier];
if (previousXKeysPanel) {
// This panel has been connected before.
// We want the XKeys-instance to emit a 'reconnected' event.
// This means that we kill off the newly created xkeysPanel, and
await previousXKeysPanel._handleDeviceReconnected(xKeysPanel._getHIDDevice(), xKeysPanel._getDeviceInfo());
}
else {
// It seems that this panel hasn't been connected before
this.emit('connected', xKeysPanel);
this.prevConnectedIdentifiers[uniqueIdentifier] = xKeysPanel;
}
}
else {
// Default behavior:
this.emit('connected', xKeysPanel);
}
}
_getNextUniqueId(xkeysPanel) {
let nextId = this.uniqueIds.get(xkeysPanel.info.productId);
if (!nextId) {
nextId = 32; // Starting at 32
}
else {
nextId++;
}
if (nextId > 255)
throw new Error('No more unique ids available!');
this.uniqueIds.set(xkeysPanel.info.productId, nextId);
return nextId;
}
debugLog(...args) {
if (this.debug)
console.log(...args);
}
}
exports.GenericXKeysWatcher = GenericXKeysWatcher;
//# sourceMappingURL=watcher.js.map