@mcesystems/usb-device-listener 1.0.0 → 1.0.1
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/LICENSE +15 -0
- package/package.json +69 -40
- package/{build/Release → prebuilds/win32-x64}/@mcesystems+usb-device-listener.node +0 -0
- package/examples/example.d.ts +0 -2
- package/examples/example.d.ts.map +0 -1
- package/examples/example.js +0 -187
- package/examples/example.js.map +0 -7
- package/examples/list-devices.d.ts +0 -2
- package/examples/list-devices.d.ts.map +0 -1
- package/examples/list-devices.js +0 -162
- package/examples/list-devices.js.map +0 -7
- /package/{device-filter.d.ts → dist/device-filter.d.ts} +0 -0
- /package/{device-filter.d.ts.map → dist/device-filter.d.ts.map} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.d.ts.map → dist/index.d.ts.map} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{index.js.map → dist/index.js.map} +0 -0
- /package/{types.d.ts → dist/types.d.ts} +0 -0
- /package/{types.d.ts.map → dist/types.d.ts.map} +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 MCE Systems
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,40 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcesystems/usb-device-listener",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Native Windows USB device listener using PnP notifications without custom drivers",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/types.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/types.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"usb",
|
|
16
|
+
"device",
|
|
17
|
+
"listener",
|
|
18
|
+
"monitor",
|
|
19
|
+
"hotplug",
|
|
20
|
+
"pnp",
|
|
21
|
+
"windows",
|
|
22
|
+
"native",
|
|
23
|
+
"addon",
|
|
24
|
+
"n-api",
|
|
25
|
+
"typescript",
|
|
26
|
+
"esm",
|
|
27
|
+
"device-detection",
|
|
28
|
+
"hardware"
|
|
29
|
+
],
|
|
30
|
+
"author": "USB Device Listener Contributors",
|
|
31
|
+
"license": "ISC",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"node-addon-api": "^8.2.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^24.10.1",
|
|
37
|
+
"cross-env": "^10.1.0",
|
|
38
|
+
"esbuild": "^0.27.0",
|
|
39
|
+
"esbuild-plugin-copy": "^2.1.1",
|
|
40
|
+
"node-gyp": "^10.3.1",
|
|
41
|
+
"node-gyp-build": "^4.8.4",
|
|
42
|
+
"prebuildify": "^6.0.1",
|
|
43
|
+
"rimraf": "^6.1.2",
|
|
44
|
+
"tsx": "^4.21.0",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"vitest": "^2.1.8"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"prebuilds",
|
|
51
|
+
"build/Release/*.node",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"gypfile": true,
|
|
55
|
+
"os": [
|
|
56
|
+
"win32"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"prebuild:gyp": "prebuildify --napi --strip --target 25.2.1",
|
|
60
|
+
"build": "tsx esbuild.config.ts && tsc --emitDeclarationOnly",
|
|
61
|
+
"rebuild": "node-gyp rebuild",
|
|
62
|
+
"clean": "node-gyp clean && rimraf dist",
|
|
63
|
+
"check:types": "tsc --noEmit",
|
|
64
|
+
"pack": "npm pack",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:watch": "vitest",
|
|
67
|
+
"listDevices": "cross-env DEBUG=* tsx ./src/examples/list-devices.ts"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
Binary file
|
package/examples/example.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"example.d.ts","sourceRoot":"","sources":["../../src/examples/example.ts"],"names":[],"mappings":""}
|
package/examples/example.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
// src/device-filter.ts
|
|
7
|
-
function toHexString(value) {
|
|
8
|
-
return value.toString(16).toUpperCase().padStart(4, "0");
|
|
9
|
-
}
|
|
10
|
-
function matchesDevice(device, target) {
|
|
11
|
-
const deviceVid = toHexString(device.vid);
|
|
12
|
-
const devicePid = toHexString(device.pid);
|
|
13
|
-
const targetVid = target.vid.toUpperCase();
|
|
14
|
-
const targetPid = target.pid.toUpperCase();
|
|
15
|
-
return deviceVid === targetVid && devicePid === targetPid;
|
|
16
|
-
}
|
|
17
|
-
function matchesAnyDevice(device, targets) {
|
|
18
|
-
return targets.some((target) => matchesDevice(device, target));
|
|
19
|
-
}
|
|
20
|
-
function shouldNotifyDevice(device, config2) {
|
|
21
|
-
if (config2.ignoredDevices && config2.ignoredDevices.length > 0) {
|
|
22
|
-
if (matchesAnyDevice(device, config2.ignoredDevices)) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (config2.listenOnlyDevices && config2.listenOnlyDevices.length > 0) {
|
|
27
|
-
if (!matchesAnyDevice(device, config2.listenOnlyDevices)) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (config2.targetDevices && config2.targetDevices.length > 0) {
|
|
32
|
-
if (!matchesAnyDevice(device, config2.targetDevices)) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (config2.logicalPortMap && Object.keys(config2.logicalPortMap).length > 0) {
|
|
37
|
-
if (!(device.locationInfo in config2.logicalPortMap)) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
function applyLogicalPortMapping(device, config2) {
|
|
44
|
-
if (config2.logicalPortMap && device.locationInfo in config2.logicalPortMap) {
|
|
45
|
-
return {
|
|
46
|
-
...device,
|
|
47
|
-
logicalPort: config2.logicalPortMap[device.locationInfo]
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return device;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/index.ts
|
|
54
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
55
|
-
var __dirname = dirname(__filename);
|
|
56
|
-
var packageRoot = dirname(__dirname);
|
|
57
|
-
function loadNativeAddon() {
|
|
58
|
-
const require2 = createRequire(import.meta.url);
|
|
59
|
-
const addonPath = join(packageRoot, "build", "Release", "@mcesystems+usb-device-listener.node");
|
|
60
|
-
try {
|
|
61
|
-
return require2(addonPath);
|
|
62
|
-
} catch {
|
|
63
|
-
try {
|
|
64
|
-
const nodeGypBuild = require2("node-gyp-build");
|
|
65
|
-
return nodeGypBuild(packageRoot);
|
|
66
|
-
} catch {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`Failed to load native addon. Tried:
|
|
69
|
-
1. Direct path: ${addonPath}
|
|
70
|
-
2. node-gyp-build search
|
|
71
|
-
Make sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
var addon = loadNativeAddon();
|
|
77
|
-
var UsbDeviceListenerImpl = class {
|
|
78
|
-
config = {};
|
|
79
|
-
userAddCallback = null;
|
|
80
|
-
userRemoveCallback = null;
|
|
81
|
-
/**
|
|
82
|
-
* Start listening for USB device events
|
|
83
|
-
*/
|
|
84
|
-
startListening(config2) {
|
|
85
|
-
if (typeof config2 !== "object" || config2 === null) {
|
|
86
|
-
throw new TypeError("Config must be an object");
|
|
87
|
-
}
|
|
88
|
-
this.config = config2;
|
|
89
|
-
addon.startListening();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Stop listening for USB device events
|
|
93
|
-
*/
|
|
94
|
-
stopListening() {
|
|
95
|
-
addon.stopListening();
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Register callback for device connection events
|
|
99
|
-
*/
|
|
100
|
-
onDeviceAdd(callback) {
|
|
101
|
-
if (typeof callback !== "function") {
|
|
102
|
-
throw new TypeError("Callback must be a function");
|
|
103
|
-
}
|
|
104
|
-
this.userAddCallback = callback;
|
|
105
|
-
addon.onDeviceAdd((device) => {
|
|
106
|
-
if (shouldNotifyDevice(device, this.config)) {
|
|
107
|
-
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
108
|
-
this.userAddCallback?.(enrichedDevice);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Register callback for device disconnection events
|
|
114
|
-
*/
|
|
115
|
-
onDeviceRemove(callback) {
|
|
116
|
-
if (typeof callback !== "function") {
|
|
117
|
-
throw new TypeError("Callback must be a function");
|
|
118
|
-
}
|
|
119
|
-
this.userRemoveCallback = callback;
|
|
120
|
-
addon.onDeviceRemove((device) => {
|
|
121
|
-
if (shouldNotifyDevice(device, this.config)) {
|
|
122
|
-
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
123
|
-
this.userRemoveCallback?.(enrichedDevice);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* List all currently connected USB devices
|
|
129
|
-
*/
|
|
130
|
-
listDevices() {
|
|
131
|
-
return addon.listDevices();
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var usbDeviceListener = new UsbDeviceListenerImpl();
|
|
135
|
-
var index_default = usbDeviceListener;
|
|
136
|
-
|
|
137
|
-
// src/examples/example.ts
|
|
138
|
-
var config = {
|
|
139
|
-
logicalPortMap: {
|
|
140
|
-
"Port_#0005.Hub_#0002": 1,
|
|
141
|
-
"Port_#0006.Hub_#0002": 2
|
|
142
|
-
},
|
|
143
|
-
ignoredDevices: [],
|
|
144
|
-
listenOnlyDevices: []
|
|
145
|
-
};
|
|
146
|
-
index_default.onDeviceAdd((device) => {
|
|
147
|
-
console.log("Device connected:");
|
|
148
|
-
console.log(" Device ID:", device.deviceId);
|
|
149
|
-
console.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
150
|
-
console.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
151
|
-
console.log(" Location:", device.locationInfo);
|
|
152
|
-
console.log(" Logical Port:", device.logicalPort !== null ? device.logicalPort : "Not mapped");
|
|
153
|
-
console.log("");
|
|
154
|
-
});
|
|
155
|
-
index_default.onDeviceRemove((device) => {
|
|
156
|
-
console.log("Device disconnected:");
|
|
157
|
-
console.log(" Device ID:", device.deviceId);
|
|
158
|
-
console.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
159
|
-
console.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
160
|
-
console.log(" Location:", device.locationInfo);
|
|
161
|
-
console.log(" Logical Port:", device.logicalPort !== null ? device.logicalPort : "Not mapped");
|
|
162
|
-
console.log("");
|
|
163
|
-
});
|
|
164
|
-
try {
|
|
165
|
-
console.log("Starting USB device listener...");
|
|
166
|
-
console.log("Config:", JSON.stringify(config, null, 2));
|
|
167
|
-
console.log("");
|
|
168
|
-
index_default.startListening(config);
|
|
169
|
-
console.log("Listening for USB device events. Press Ctrl+C to stop.");
|
|
170
|
-
console.log("");
|
|
171
|
-
} catch (error) {
|
|
172
|
-
if (error instanceof Error) {
|
|
173
|
-
console.error("Error starting listener:", error.message);
|
|
174
|
-
}
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
process.on("SIGINT", () => {
|
|
178
|
-
console.log("\nStopping listener...");
|
|
179
|
-
index_default.stopListening();
|
|
180
|
-
process.exit(0);
|
|
181
|
-
});
|
|
182
|
-
process.on("SIGTERM", () => {
|
|
183
|
-
console.log("\nStopping listener...");
|
|
184
|
-
index_default.stopListening();
|
|
185
|
-
process.exit(0);
|
|
186
|
-
});
|
|
187
|
-
//# sourceMappingURL=example.js.map
|
package/examples/example.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.ts", "../../src/device-filter.ts", "../../src/examples/example.ts"],
|
|
4
|
-
"sourcesContent": ["\nimport { createRequire } from \"node:module\";\nimport { dirname, join } 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\n\t// Try to load from the known production location first\n\tconst addonPath = join(packageRoot, \"build\", \"Release\", \"@mcesystems+usb-device-listener.node\");\n\n\ttry {\n\t\treturn require(addonPath);\n\t} catch {\n\t\t// Fallback to node-gyp-build for development (if available)\n\t\ttry {\n\t\t\tconst nodeGypBuild = require(\"node-gyp-build\");\n\t\t\treturn nodeGypBuild(packageRoot);\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to load native addon. Tried:\\n 1. Direct path: ${addonPath}\\n 2. node-gyp-build search\\nMake sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`\n\t\t\t);\n\t\t}\n\t}\n}\n\nconst addon = loadNativeAddon();\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", "import usbListener, { type DeviceInfo, type ListenerConfig } from \"../index\";\n\n// Type-safe configuration - no casting needed!\nconst config: ListenerConfig = {\n\tlogicalPortMap: {\n\t\t\"Port_#0005.Hub_#0002\": 1,\n\t\t\"Port_#0006.Hub_#0002\": 2,\n\t},\n\tignoredDevices: [],\n\tlistenOnlyDevices: [],\n};\n\n// Type-safe callback with full IntelliSense\nusbListener.onDeviceAdd((device: DeviceInfo) => {\n\tconsole.log(\"Device connected:\");\n\tconsole.log(\" Device ID:\", device.deviceId);\n\tconsole.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\tconsole.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\tconsole.log(\" Location:\", device.locationInfo);\n\tconsole.log(\" Logical Port:\", device.logicalPort !== null ? device.logicalPort : \"Not mapped\");\n\tconsole.log(\"\");\n});\n\nusbListener.onDeviceRemove((device: DeviceInfo) => {\n\tconsole.log(\"Device disconnected:\");\n\tconsole.log(\" Device ID:\", device.deviceId);\n\tconsole.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\tconsole.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\tconsole.log(\" Location:\", device.locationInfo);\n\tconsole.log(\" Logical Port:\", device.logicalPort !== null ? device.logicalPort : \"Not mapped\");\n\tconsole.log(\"\");\n});\n\ntry {\n\tconsole.log(\"Starting USB device listener...\");\n\tconsole.log(\"Config:\", JSON.stringify(config, null, 2));\n\tconsole.log(\"\");\n\n\tusbListener.startListening(config);\n\n\tconsole.log(\"Listening for USB device events. Press Ctrl+C to stop.\");\n\tconsole.log(\"\");\n} catch (error) {\n\tif (error instanceof Error) {\n\t\tconsole.error(\"Error starting listener:\", error.message);\n\t}\n\tprocess.exit(1);\n}\n\nprocess.on(\"SIGINT\", () => {\n\tconsole.log(\"\\nStopping listener...\");\n\tusbListener.stopListening();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n\tconsole.log(\"\\nStopping listener...\");\n\tusbListener.stopListening();\n\tprocess.exit(0);\n});\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["config", "require", "config"]
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list-devices.d.ts","sourceRoot":"","sources":["../../src/examples/list-devices.ts"],"names":[],"mappings":""}
|
package/examples/list-devices.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
// src/device-filter.ts
|
|
7
|
-
function toHexString(value) {
|
|
8
|
-
return value.toString(16).toUpperCase().padStart(4, "0");
|
|
9
|
-
}
|
|
10
|
-
function matchesDevice(device, target) {
|
|
11
|
-
const deviceVid = toHexString(device.vid);
|
|
12
|
-
const devicePid = toHexString(device.pid);
|
|
13
|
-
const targetVid = target.vid.toUpperCase();
|
|
14
|
-
const targetPid = target.pid.toUpperCase();
|
|
15
|
-
return deviceVid === targetVid && devicePid === targetPid;
|
|
16
|
-
}
|
|
17
|
-
function matchesAnyDevice(device, targets) {
|
|
18
|
-
return targets.some((target) => matchesDevice(device, target));
|
|
19
|
-
}
|
|
20
|
-
function shouldNotifyDevice(device, config) {
|
|
21
|
-
if (config.ignoredDevices && config.ignoredDevices.length > 0) {
|
|
22
|
-
if (matchesAnyDevice(device, config.ignoredDevices)) {
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
if (config.listenOnlyDevices && config.listenOnlyDevices.length > 0) {
|
|
27
|
-
if (!matchesAnyDevice(device, config.listenOnlyDevices)) {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (config.targetDevices && config.targetDevices.length > 0) {
|
|
32
|
-
if (!matchesAnyDevice(device, config.targetDevices)) {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (config.logicalPortMap && Object.keys(config.logicalPortMap).length > 0) {
|
|
37
|
-
if (!(device.locationInfo in config.logicalPortMap)) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
function applyLogicalPortMapping(device, config) {
|
|
44
|
-
if (config.logicalPortMap && device.locationInfo in config.logicalPortMap) {
|
|
45
|
-
return {
|
|
46
|
-
...device,
|
|
47
|
-
logicalPort: config.logicalPortMap[device.locationInfo]
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return device;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/index.ts
|
|
54
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
55
|
-
var __dirname = dirname(__filename);
|
|
56
|
-
var packageRoot = dirname(__dirname);
|
|
57
|
-
function loadNativeAddon() {
|
|
58
|
-
const require2 = createRequire(import.meta.url);
|
|
59
|
-
const addonPath = join(packageRoot, "build", "Release", "@mcesystems+usb-device-listener.node");
|
|
60
|
-
try {
|
|
61
|
-
return require2(addonPath);
|
|
62
|
-
} catch {
|
|
63
|
-
try {
|
|
64
|
-
const nodeGypBuild = require2("node-gyp-build");
|
|
65
|
-
return nodeGypBuild(packageRoot);
|
|
66
|
-
} catch {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`Failed to load native addon. Tried:
|
|
69
|
-
1. Direct path: ${addonPath}
|
|
70
|
-
2. node-gyp-build search
|
|
71
|
-
Make sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
var addon = loadNativeAddon();
|
|
77
|
-
var UsbDeviceListenerImpl = class {
|
|
78
|
-
config = {};
|
|
79
|
-
userAddCallback = null;
|
|
80
|
-
userRemoveCallback = null;
|
|
81
|
-
/**
|
|
82
|
-
* Start listening for USB device events
|
|
83
|
-
*/
|
|
84
|
-
startListening(config) {
|
|
85
|
-
if (typeof config !== "object" || config === null) {
|
|
86
|
-
throw new TypeError("Config must be an object");
|
|
87
|
-
}
|
|
88
|
-
this.config = config;
|
|
89
|
-
addon.startListening();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Stop listening for USB device events
|
|
93
|
-
*/
|
|
94
|
-
stopListening() {
|
|
95
|
-
addon.stopListening();
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Register callback for device connection events
|
|
99
|
-
*/
|
|
100
|
-
onDeviceAdd(callback) {
|
|
101
|
-
if (typeof callback !== "function") {
|
|
102
|
-
throw new TypeError("Callback must be a function");
|
|
103
|
-
}
|
|
104
|
-
this.userAddCallback = callback;
|
|
105
|
-
addon.onDeviceAdd((device) => {
|
|
106
|
-
if (shouldNotifyDevice(device, this.config)) {
|
|
107
|
-
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
108
|
-
this.userAddCallback?.(enrichedDevice);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Register callback for device disconnection events
|
|
114
|
-
*/
|
|
115
|
-
onDeviceRemove(callback) {
|
|
116
|
-
if (typeof callback !== "function") {
|
|
117
|
-
throw new TypeError("Callback must be a function");
|
|
118
|
-
}
|
|
119
|
-
this.userRemoveCallback = callback;
|
|
120
|
-
addon.onDeviceRemove((device) => {
|
|
121
|
-
if (shouldNotifyDevice(device, this.config)) {
|
|
122
|
-
const enrichedDevice = applyLogicalPortMapping(device, this.config);
|
|
123
|
-
this.userRemoveCallback?.(enrichedDevice);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* List all currently connected USB devices
|
|
129
|
-
*/
|
|
130
|
-
listDevices() {
|
|
131
|
-
return addon.listDevices();
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var usbDeviceListener = new UsbDeviceListenerImpl();
|
|
135
|
-
var index_default = usbDeviceListener;
|
|
136
|
-
|
|
137
|
-
// src/examples/list-devices.ts
|
|
138
|
-
console.log("Listing all connected USB devices:\n");
|
|
139
|
-
try {
|
|
140
|
-
const devices = index_default.listDevices();
|
|
141
|
-
if (devices.length === 0) {
|
|
142
|
-
console.log("No USB devices found.");
|
|
143
|
-
} else {
|
|
144
|
-
devices.forEach((device, index) => {
|
|
145
|
-
console.log(`Device ${index + 1}:`);
|
|
146
|
-
console.log(` Device ID: ${device.deviceId}`);
|
|
147
|
-
console.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
148
|
-
console.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, "0")}`);
|
|
149
|
-
console.log(` Location Info (mapping key): ${device.locationInfo}`);
|
|
150
|
-
console.log("");
|
|
151
|
-
});
|
|
152
|
-
console.log(`Total devices: ${devices.length}`);
|
|
153
|
-
console.log(
|
|
154
|
-
'\nTo add a device to your config, use the "Location Info" as the key in the logicalPortMap.'
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (error instanceof Error) {
|
|
159
|
-
console.error("Error listing devices:", error.message);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
//# sourceMappingURL=list-devices.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/index.ts", "../../src/device-filter.ts", "../../src/examples/list-devices.ts"],
|
|
4
|
-
"sourcesContent": ["\nimport { createRequire } from \"node:module\";\nimport { dirname, join } 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\n\t// Try to load from the known production location first\n\tconst addonPath = join(packageRoot, \"build\", \"Release\", \"@mcesystems+usb-device-listener.node\");\n\n\ttry {\n\t\treturn require(addonPath);\n\t} catch {\n\t\t// Fallback to node-gyp-build for development (if available)\n\t\ttry {\n\t\t\tconst nodeGypBuild = require(\"node-gyp-build\");\n\t\t\treturn nodeGypBuild(packageRoot);\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to load native addon. Tried:\\n 1. Direct path: ${addonPath}\\n 2. node-gyp-build search\\nMake sure the addon is built with 'npm run rebuild' or 'npm run prebuild'`\n\t\t\t);\n\t\t}\n\t}\n}\n\nconst addon = loadNativeAddon();\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", "import usbListener, { type DeviceInfo } from \"../index\";\n\nconsole.log(\"Listing all connected USB devices:\\n\");\n\ntry {\n\tconst devices: DeviceInfo[] = usbListener.listDevices();\n\n\tif (devices.length === 0) {\n\t\tconsole.log(\"No USB devices found.\");\n\t} else {\n\t\tdevices.forEach((device: DeviceInfo, index: number) => {\n\t\t\tconsole.log(`Device ${index + 1}:`);\n\t\t\tconsole.log(` Device ID: ${device.deviceId}`);\n\t\t\tconsole.log(` VID: 0x${device.vid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\t\t\tconsole.log(` PID: 0x${device.pid.toString(16).toUpperCase().padStart(4, \"0\")}`);\n\t\t\tconsole.log(` Location Info (mapping key): ${device.locationInfo}`);\n\t\t\tconsole.log(\"\");\n\t\t});\n\n\t\tconsole.log(`Total devices: ${devices.length}`);\n\t\tconsole.log(\n\t\t\t'\\nTo add a device to your config, use the \"Location Info\" as the key in the logicalPortMap.'\n\t\t);\n\t}\n} catch (error) {\n\tif (error instanceof Error) {\n\t\tconsole.error(\"Error listing devices:\", error.message);\n\t}\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["require"]
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|