@mcesystems/usb-device-listener 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +155 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -16
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// dist/index.js
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
default: () => index_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
var import_node_module = require("node:module");
|
|
27
|
+
var import_node_path = require("node:path");
|
|
28
|
+
var import_node_url = require("node:url");
|
|
29
|
+
var import_meta = {};
|
|
30
|
+
function toHexString(value) {
|
|
31
|
+
return value.toString(16).toUpperCase().padStart(4, "0");
|
|
32
|
+
}
|
|
33
|
+
function matchesDevice(device, target) {
|
|
34
|
+
const deviceVid = toHexString(device.vid);
|
|
35
|
+
const devicePid = toHexString(device.pid);
|
|
36
|
+
const targetVid = target.vid.toUpperCase();
|
|
37
|
+
const targetPid = target.pid.toUpperCase();
|
|
38
|
+
return deviceVid === targetVid && devicePid === targetPid;
|
|
39
|
+
}
|
|
40
|
+
function matchesAnyDevice(device, targets) {
|
|
41
|
+
return targets.some((target) => matchesDevice(device, target));
|
|
42
|
+
}
|
|
43
|
+
function shouldNotifyDevice(device, config) {
|
|
44
|
+
if (config.ignoredDevices && config.ignoredDevices.length > 0) {
|
|
45
|
+
if (matchesAnyDevice(device, config.ignoredDevices)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (config.listenOnlyDevices && config.listenOnlyDevices.length > 0) {
|
|
50
|
+
if (!matchesAnyDevice(device, config.listenOnlyDevices)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (config.targetDevices && config.targetDevices.length > 0) {
|
|
55
|
+
if (!matchesAnyDevice(device, config.targetDevices)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (config.logicalPortMap && Object.keys(config.logicalPortMap).length > 0) {
|
|
60
|
+
if (!(device.locationInfo in config.logicalPortMap)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
function applyLogicalPortMapping(device, config) {
|
|
67
|
+
if (config.logicalPortMap && device.locationInfo in config.logicalPortMap) {
|
|
68
|
+
return {
|
|
69
|
+
...device,
|
|
70
|
+
logicalPort: config.logicalPortMap[device.locationInfo]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return device;
|
|
74
|
+
}
|
|
75
|
+
var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
76
|
+
var __dirname = (0, import_node_path.dirname)(__filename);
|
|
77
|
+
var packageRoot = (0, import_node_path.dirname)(__dirname);
|
|
78
|
+
function loadNativeAddon() {
|
|
79
|
+
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
80
|
+
return require2("node-gyp-build")(process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? packageRoot);
|
|
81
|
+
}
|
|
82
|
+
var addon;
|
|
83
|
+
try {
|
|
84
|
+
addon = loadNativeAddon();
|
|
85
|
+
} catch (_error) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Failed to load native addon. Tried:
|
|
88
|
+
1. Direct path: ${process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? __dirname}
|
|
89
|
+
2. node-gyp-build search
|
|
90
|
+
Make sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (!addon) {
|
|
94
|
+
throw new Error("Failed to load native addon");
|
|
95
|
+
}
|
|
96
|
+
var UsbDeviceListenerImpl = class {
|
|
97
|
+
config = {};
|
|
98
|
+
userAddCallback = null;
|
|
99
|
+
userRemoveCallback = null;
|
|
100
|
+
/**
|
|
101
|
+
* Start listening for USB device events
|
|
102
|
+
*/
|
|
103
|
+
startListening(config) {
|
|
104
|
+
if (typeof config !== "object" || config === null) {
|
|
105
|
+
throw new TypeError("Config must be an object");
|
|
106
|
+
}
|
|
107
|
+
this.config = config;
|
|
108
|
+
addon.startListening();
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Stop listening for USB device events
|
|
112
|
+
*/
|
|
113
|
+
stopListening() {
|
|
114
|
+
addon.stopListening();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Register callback for device connection events
|
|
118
|
+
*/
|
|
119
|
+
onDeviceAdd(callback) {
|
|
120
|
+
if (typeof callback !== "function") {
|
|
121
|
+
throw new TypeError("Callback must be a function");
|
|
122
|
+
}
|
|
123
|
+
this.userAddCallback = callback;
|
|
124
|
+
addon.onDeviceAdd((device) => {
|
|
125
|
+
if (shouldNotifyDevice(device, this.config)) {
|
|
126
|
+
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
127
|
+
this.userAddCallback?.(enrichedDevice);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Register callback for device disconnection events
|
|
133
|
+
*/
|
|
134
|
+
onDeviceRemove(callback) {
|
|
135
|
+
if (typeof callback !== "function") {
|
|
136
|
+
throw new TypeError("Callback must be a function");
|
|
137
|
+
}
|
|
138
|
+
this.userRemoveCallback = callback;
|
|
139
|
+
addon.onDeviceRemove((device) => {
|
|
140
|
+
if (shouldNotifyDevice(device, this.config)) {
|
|
141
|
+
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
142
|
+
this.userRemoveCallback?.(enrichedDevice);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* List all currently connected USB devices
|
|
148
|
+
*/
|
|
149
|
+
listDevices() {
|
|
150
|
+
return addon.listDevices();
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
var usbDeviceListener = new UsbDeviceListenerImpl();
|
|
154
|
+
var index_default = usbDeviceListener;
|
|
155
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/device-filter.ts"],
|
|
4
|
+
"sourcesContent": ["import { createRequire } from \"node:module\";\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyLogicalPortMapping, shouldNotifyDevice } from \"./device-filter\";\nimport type {\n\tDeviceAddCallback,\n\tDeviceInfo,\n\tDeviceRemoveCallback,\n\tListenerConfig,\n\tUsbDeviceListener,\n} from \"./types\";\n\n/**\n * Native addon interface\n * This is the raw C++ addon loaded via N-API\n */\ninterface NativeAddon {\n\tstartListening(): void;\n\tstopListening(): void;\n\tonDeviceAdd(callback: DeviceAddCallback): void;\n\tonDeviceRemove(callback: DeviceRemoveCallback): void;\n\tlistDevices(): DeviceInfo[];\n}\n\n// ESM compatibility: get __dirname equivalent\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst packageRoot = dirname(__dirname);\n\n/**\n * Load native addon\n * In development: uses node-gyp-build to find the addon\n * In production: loads directly from the known build location\n */\nfunction loadNativeAddon(): NativeAddon {\n\tconst require = createRequire(import.meta.url);\n\treturn require(\"node-gyp-build\")(process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? packageRoot);\n}\nlet addon: NativeAddon;\ntry {\n\taddon = loadNativeAddon();\n} catch (_error) {\n\tthrow new Error(\n\t\t`Failed to load native addon. Tried:\\n 1. Direct path: ${process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? __dirname}\\n 2. node-gyp-build search\\nMake sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`\n\t);\n}\nif (!addon) {\n\tthrow new Error(\"Failed to load native addon\");\n}\n\n/**\n * USB Device Listener implementation\n * Provides a type-safe wrapper around the native C++ addon\n */\nclass UsbDeviceListenerImpl implements UsbDeviceListener {\n\tprivate config: ListenerConfig = {};\n\tprivate userAddCallback: DeviceAddCallback | null = null;\n\tprivate userRemoveCallback: DeviceRemoveCallback | null = null;\n\n\t/**\n\t * Start listening for USB device events\n\t */\n\tpublic startListening(config: ListenerConfig): void {\n\t\tif (typeof config !== \"object\" || config === null) {\n\t\t\tthrow new TypeError(\"Config must be an object\");\n\t\t}\n\t\tthis.config = config;\n\t\taddon.startListening();\n\t}\n\n\t/**\n\t * Stop listening for USB device events\n\t */\n\tpublic stopListening(): void {\n\t\taddon.stopListening();\n\t}\n\n\t/**\n\t * Register callback for device connection events\n\t */\n\tpublic onDeviceAdd(callback: DeviceAddCallback): void {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t}\n\t\tthis.userAddCallback = callback;\n\n\t\t// Set up internal callback that filters devices\n\t\taddon.onDeviceAdd((device: DeviceInfo) => {\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\n\t\t\t\tthis.userAddCallback?.(enrichedDevice);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Register callback for device disconnection events\n\t */\n\tpublic onDeviceRemove(callback: DeviceRemoveCallback): void {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t}\n\t\tthis.userRemoveCallback = callback;\n\n\t\t// Set up internal callback that filters devices\n\t\taddon.onDeviceRemove((device: DeviceInfo) => {\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\n\t\t\t\tthis.userRemoveCallback?.(enrichedDevice);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * List all currently connected USB devices\n\t */\n\tpublic listDevices(): DeviceInfo[] {\n\t\treturn addon.listDevices();\n\t}\n}\n\n// Export singleton instance\nconst usbDeviceListener: UsbDeviceListener = new UsbDeviceListenerImpl();\n\nexport default usbDeviceListener;\nexport type {\n\tDeviceInfo,\n\tDeviceAddCallback,\n\tDeviceRemoveCallback,\n\tListenerConfig,\n\tTargetDevice,\n\tUsbDeviceListener,\n} from \"./types\";\n", "import type { DeviceInfo, ListenerConfig, TargetDevice } from \"./types\";\n\n/**\n * Convert a decimal VID/PID to uppercase hex string for comparison\n */\nfunction toHexString(value: number): string {\n\treturn value.toString(16).toUpperCase().padStart(4, \"0\");\n}\n\n/**\n * Check if a device matches a target device filter by VID/PID\n */\nfunction matchesDevice(device: DeviceInfo, target: TargetDevice): boolean {\n\tconst deviceVid = toHexString(device.vid);\n\tconst devicePid = toHexString(device.pid);\n\tconst targetVid = target.vid.toUpperCase();\n\tconst targetPid = target.pid.toUpperCase();\n\n\treturn deviceVid === targetVid && devicePid === targetPid;\n}\n\n/**\n * Check if a device matches any device in a list of target devices\n */\nfunction matchesAnyDevice(device: DeviceInfo, targets: TargetDevice[]): boolean {\n\treturn targets.some((target) => matchesDevice(device, target));\n}\n\n/**\n * Determine if a device notification should be sent based on the configuration.\n *\n * Filter priority (highest to lowest):\n * 1. ignoredDevices - if device matches, always return false\n * 2. listenOnlyDevices - if specified, device must match at least one\n * 3. targetDevices - if specified, device must match at least one\n * 4. logicalPortMap - if specified, device location must be in the map\n *\n * @param device - The device information from the native addon\n * @param config - The listener configuration\n * @returns true if the device should trigger a notification, false otherwise\n */\nexport function shouldNotifyDevice(device: DeviceInfo, config: ListenerConfig): boolean {\n\t// Priority 1: Check ignoredDevices (highest priority - always blocks)\n\tif (config.ignoredDevices && config.ignoredDevices.length > 0) {\n\t\tif (matchesAnyDevice(device, config.ignoredDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 2: Check listenOnlyDevices (if specified, device must match)\n\tif (config.listenOnlyDevices && config.listenOnlyDevices.length > 0) {\n\t\tif (!matchesAnyDevice(device, config.listenOnlyDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 3: Check targetDevices (if specified, device must match)\n\tif (config.targetDevices && config.targetDevices.length > 0) {\n\t\tif (!matchesAnyDevice(device, config.targetDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 4: Check logicalPortMap (if specified, device must be mapped)\n\tif (config.logicalPortMap && Object.keys(config.logicalPortMap).length > 0) {\n\t\tif (!(device.locationInfo in config.logicalPortMap)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Apply logical port mapping to a device if configured\n *\n * @param device - The device information from the native addon\n * @param config - The listener configuration\n * @returns Device info with logicalPort set if mapped, otherwise unchanged\n */\nexport function applyLogicalPortMapping(device: DeviceInfo, config: ListenerConfig): DeviceInfo {\n\tif (config.logicalPortMap && device.locationInfo in config.logicalPortMap) {\n\t\treturn {\n\t\t\t...device,\n\t\t\tlogicalPort: config.logicalPortMap[device.locationInfo],\n\t\t};\n\t}\n\treturn device;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["require"]
|
|
7
|
+
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAKX,iBAAiB,EACjB,MAAM,SAAS,CAAC;AAiHjB,QAAA,MAAM,iBAAiB,EAAE,iBAA+C,CAAC;AAEzE,eAAe,iBAAiB,CAAC;AACjC,YAAY,EACX,UAAU,EACV,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,iBAAiB,GACjB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
6
|
// src/device-filter.ts
|
|
@@ -56,24 +56,22 @@ var __dirname = dirname(__filename);
|
|
|
56
56
|
var packageRoot = dirname(__dirname);
|
|
57
57
|
function loadNativeAddon() {
|
|
58
58
|
const require2 = createRequire(import.meta.url);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`Failed to load native addon. Tried:
|
|
69
|
-
1. Direct path: ${addonPath}
|
|
59
|
+
return require2("node-gyp-build")(process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? packageRoot);
|
|
60
|
+
}
|
|
61
|
+
var addon;
|
|
62
|
+
try {
|
|
63
|
+
addon = loadNativeAddon();
|
|
64
|
+
} catch (_error) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Failed to load native addon. Tried:
|
|
67
|
+
1. Direct path: ${process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? __dirname}
|
|
70
68
|
2. node-gyp-build search
|
|
71
69
|
Make sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (!addon) {
|
|
73
|
+
throw new Error("Failed to load native addon");
|
|
75
74
|
}
|
|
76
|
-
var addon = loadNativeAddon();
|
|
77
75
|
var UsbDeviceListenerImpl = class {
|
|
78
76
|
config = {};
|
|
79
77
|
userAddCallback = null;
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/device-filter.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["import { createRequire } from \"node:module\";\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyLogicalPortMapping, shouldNotifyDevice } from \"./device-filter\";\nimport type {\n\tDeviceAddCallback,\n\tDeviceInfo,\n\tDeviceRemoveCallback,\n\tListenerConfig,\n\tUsbDeviceListener,\n} from \"./types\";\n\n/**\n * Native addon interface\n * This is the raw C++ addon loaded via N-API\n */\ninterface NativeAddon {\n\tstartListening(): void;\n\tstopListening(): void;\n\tonDeviceAdd(callback: DeviceAddCallback): void;\n\tonDeviceRemove(callback: DeviceRemoveCallback): void;\n\tlistDevices(): DeviceInfo[];\n}\n\n// ESM compatibility: get __dirname equivalent\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst packageRoot = dirname(__dirname);\n\n/**\n * Load native addon\n * In development: uses node-gyp-build to find the addon\n * In production: loads directly from the known build location\n */\nfunction loadNativeAddon(): NativeAddon {\n\tconst require = createRequire(import.meta.url);\n\treturn require(\"node-gyp-build\")(process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? packageRoot);\n}\nlet addon: NativeAddon;\ntry {\n\taddon = loadNativeAddon();\n} catch (_error) {\n\tthrow new Error(\n\t\t`Failed to load native addon. Tried:\\n 1. Direct path: ${process.env.USB_DEVICE_LISTENER_ADDON_PATH ?? __dirname}\\n 2. node-gyp-build search\\nMake sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`\n\t);\n}\nif (!addon) {\n\tthrow new Error(\"Failed to load native addon\");\n}\n\n/**\n * USB Device Listener implementation\n * Provides a type-safe wrapper around the native C++ addon\n */\nclass UsbDeviceListenerImpl implements UsbDeviceListener {\n\tprivate config: ListenerConfig = {};\n\tprivate userAddCallback: DeviceAddCallback | null = null;\n\tprivate userRemoveCallback: DeviceRemoveCallback | null = null;\n\n\t/**\n\t * Start listening for USB device events\n\t */\n\tpublic startListening(config: ListenerConfig): void {\n\t\tif (typeof config !== \"object\" || config === null) {\n\t\t\tthrow new TypeError(\"Config must be an object\");\n\t\t}\n\t\tthis.config = config;\n\t\taddon.startListening();\n\t}\n\n\t/**\n\t * Stop listening for USB device events\n\t */\n\tpublic stopListening(): void {\n\t\taddon.stopListening();\n\t}\n\n\t/**\n\t * Register callback for device connection events\n\t */\n\tpublic onDeviceAdd(callback: DeviceAddCallback): void {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t}\n\t\tthis.userAddCallback = callback;\n\n\t\t// Set up internal callback that filters devices\n\t\taddon.onDeviceAdd((device: DeviceInfo) => {\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\n\t\t\t\tthis.userAddCallback?.(enrichedDevice);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Register callback for device disconnection events\n\t */\n\tpublic onDeviceRemove(callback: DeviceRemoveCallback): void {\n\t\tif (typeof callback !== \"function\") {\n\t\t\tthrow new TypeError(\"Callback must be a function\");\n\t\t}\n\t\tthis.userRemoveCallback = callback;\n\n\t\t// Set up internal callback that filters devices\n\t\taddon.onDeviceRemove((device: DeviceInfo) => {\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\n\t\t\t\tthis.userRemoveCallback?.(enrichedDevice);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * List all currently connected USB devices\n\t */\n\tpublic listDevices(): DeviceInfo[] {\n\t\treturn addon.listDevices();\n\t}\n}\n\n// Export singleton instance\nconst usbDeviceListener: UsbDeviceListener = new UsbDeviceListenerImpl();\n\nexport default usbDeviceListener;\nexport type {\n\tDeviceInfo,\n\tDeviceAddCallback,\n\tDeviceRemoveCallback,\n\tListenerConfig,\n\tTargetDevice,\n\tUsbDeviceListener,\n} from \"./types\";\n", "import type { DeviceInfo, ListenerConfig, TargetDevice } from \"./types\";\n\n/**\n * Convert a decimal VID/PID to uppercase hex string for comparison\n */\nfunction toHexString(value: number): string {\n\treturn value.toString(16).toUpperCase().padStart(4, \"0\");\n}\n\n/**\n * Check if a device matches a target device filter by VID/PID\n */\nfunction matchesDevice(device: DeviceInfo, target: TargetDevice): boolean {\n\tconst deviceVid = toHexString(device.vid);\n\tconst devicePid = toHexString(device.pid);\n\tconst targetVid = target.vid.toUpperCase();\n\tconst targetPid = target.pid.toUpperCase();\n\n\treturn deviceVid === targetVid && devicePid === targetPid;\n}\n\n/**\n * Check if a device matches any device in a list of target devices\n */\nfunction matchesAnyDevice(device: DeviceInfo, targets: TargetDevice[]): boolean {\n\treturn targets.some((target) => matchesDevice(device, target));\n}\n\n/**\n * Determine if a device notification should be sent based on the configuration.\n *\n * Filter priority (highest to lowest):\n * 1. ignoredDevices - if device matches, always return false\n * 2. listenOnlyDevices - if specified, device must match at least one\n * 3. targetDevices - if specified, device must match at least one\n * 4. logicalPortMap - if specified, device location must be in the map\n *\n * @param device - The device information from the native addon\n * @param config - The listener configuration\n * @returns true if the device should trigger a notification, false otherwise\n */\nexport function shouldNotifyDevice(device: DeviceInfo, config: ListenerConfig): boolean {\n\t// Priority 1: Check ignoredDevices (highest priority - always blocks)\n\tif (config.ignoredDevices && config.ignoredDevices.length > 0) {\n\t\tif (matchesAnyDevice(device, config.ignoredDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 2: Check listenOnlyDevices (if specified, device must match)\n\tif (config.listenOnlyDevices && config.listenOnlyDevices.length > 0) {\n\t\tif (!matchesAnyDevice(device, config.listenOnlyDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 3: Check targetDevices (if specified, device must match)\n\tif (config.targetDevices && config.targetDevices.length > 0) {\n\t\tif (!matchesAnyDevice(device, config.targetDevices)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Priority 4: Check logicalPortMap (if specified, device must be mapped)\n\tif (config.logicalPortMap && Object.keys(config.logicalPortMap).length > 0) {\n\t\tif (!(device.locationInfo in config.logicalPortMap)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Apply logical port mapping to a device if configured\n *\n * @param device - The device information from the native addon\n * @param config - The listener configuration\n * @returns Device info with logicalPort set if mapped, otherwise unchanged\n */\nexport function applyLogicalPortMapping(device: DeviceInfo, config: ListenerConfig): DeviceInfo {\n\tif (config.logicalPortMap && device.locationInfo in config.logicalPortMap) {\n\t\treturn {\n\t\t\t...device,\n\t\t\tlogicalPort: config.logicalPortMap[device.locationInfo],\n\t\t};\n\t}\n\treturn device;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["require"]
|
|
7
7
|
}
|
package/package.json
CHANGED