@mcesystems/usb-device-listener 1.0.13 → 1.0.15

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.
@@ -0,0 +1,24 @@
1
+ import type { DeviceInfo, ListenerConfig } from "./types";
2
+ /**
3
+ * Determine if a device notification should be sent based on the configuration.
4
+ *
5
+ * Filter priority (highest to lowest):
6
+ * 1. ignoredDevices - if device matches, always return false
7
+ * 2. listenOnlyDevices - if specified, device must match at least one
8
+ * 3. targetDevices - if specified, device must match at least one
9
+ * 4. logicalPortMap - if specified, device location must be in the map
10
+ *
11
+ * @param device - The device information from the native addon
12
+ * @param config - The listener configuration
13
+ * @returns true if the device should trigger a notification, false otherwise
14
+ */
15
+ export declare function shouldNotifyDevice(device: DeviceInfo, config: ListenerConfig): boolean;
16
+ /**
17
+ * Apply logical port mapping to a device if configured
18
+ *
19
+ * @param device - The device information from the native addon
20
+ * @param config - The listener configuration
21
+ * @returns Device info with logicalPort set if mapped, otherwise unchanged
22
+ */
23
+ export declare function applyLogicalPortMapping(device: DeviceInfo, config: ListenerConfig): DeviceInfo;
24
+ //# sourceMappingURL=device-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-filter.d.ts","sourceRoot":"","sources":["../src/device-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAC;AA4BxE;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,OAAO,CA8BtF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,UAAU,CAQ9F"}
@@ -0,0 +1,32 @@
1
+ import type { DeviceAddCallback, DeviceInfo, DeviceRemoveCallback, ListenerConfig, UsbDeviceListenerI } from "./types";
2
+ /**
3
+ * USB Device Listener implementation
4
+ * Provides a type-safe wrapper around the native C++ addon
5
+ */
6
+ export default class UsbDeviceListener implements UsbDeviceListenerI {
7
+ private config;
8
+ private userAddCallback;
9
+ private userRemoveCallback;
10
+ /**
11
+ * Start listening for USB device events
12
+ */
13
+ startListening(config: ListenerConfig): void;
14
+ /**
15
+ * Stop listening for USB device events
16
+ */
17
+ stopListening(): void;
18
+ /**
19
+ * Register callback for device connection events
20
+ */
21
+ onDeviceAdd(callback: DeviceAddCallback): void;
22
+ /**
23
+ * Register callback for device disconnection events
24
+ */
25
+ onDeviceRemove(callback: DeviceRemoveCallback): void;
26
+ /**
27
+ * List all currently connected USB devices
28
+ */
29
+ listDevices(): DeviceInfo[];
30
+ }
31
+ export type { DeviceInfo, DeviceAddCallback, DeviceRemoveCallback, ListenerConfig, TargetDevice, } from "./types";
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACX,iBAAiB,EACjB,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,MAAM,SAAS,CAAC;AAoBjB;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,kBAAkB;IACnE,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,eAAe,CAAkC;IACzD,OAAO,CAAC,kBAAkB,CAAqC;IAE/D;;OAEG;IACI,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAQnD;;OAEG;IACI,aAAa,IAAI,IAAI;IAI5B;;OAEG;IACI,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI;IAerD;;OAEG;IACI,cAAc,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAe3D;;OAEG;IACI,WAAW,IAAI,UAAU,EAAE;CAGlC;AAED,YAAY,EACX,UAAU,EACV,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,YAAY,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,120 @@
1
+ // src/index.ts
2
+ import { createRequire } from "node:module";
3
+ import path 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 = path.dirname(__filename);
56
+ var require2 = createRequire(import.meta.url);
57
+ var packageRoot = path.join(__dirname, "..");
58
+ var addon = require2("node-gyp-build")(packageRoot);
59
+ var { listDevices, onDeviceAdd, onDeviceRemove, startListening, stopListening } = addon;
60
+ var UsbDeviceListener = class {
61
+ config = {};
62
+ userAddCallback = null;
63
+ userRemoveCallback = null;
64
+ /**
65
+ * Start listening for USB device events
66
+ */
67
+ startListening(config) {
68
+ if (typeof config !== "object" || config === null) {
69
+ throw new TypeError("Config must be an object");
70
+ }
71
+ this.config = config;
72
+ startListening();
73
+ }
74
+ /**
75
+ * Stop listening for USB device events
76
+ */
77
+ stopListening() {
78
+ stopListening();
79
+ }
80
+ /**
81
+ * Register callback for device connection events
82
+ */
83
+ onDeviceAdd(callback) {
84
+ if (typeof callback !== "function") {
85
+ throw new TypeError("Callback must be a function");
86
+ }
87
+ this.userAddCallback = callback;
88
+ onDeviceAdd((device) => {
89
+ if (shouldNotifyDevice(device, this.config)) {
90
+ const enrichedDevice = applyLogicalPortMapping(device, this.config);
91
+ this.userAddCallback?.(enrichedDevice);
92
+ }
93
+ });
94
+ }
95
+ /**
96
+ * Register callback for device disconnection events
97
+ */
98
+ onDeviceRemove(callback) {
99
+ if (typeof callback !== "function") {
100
+ throw new TypeError("Callback must be a function");
101
+ }
102
+ this.userRemoveCallback = callback;
103
+ onDeviceRemove((device) => {
104
+ if (shouldNotifyDevice(device, this.config)) {
105
+ const enrichedDevice = applyLogicalPortMapping(device, this.config);
106
+ this.userRemoveCallback?.(enrichedDevice);
107
+ }
108
+ });
109
+ }
110
+ /**
111
+ * List all currently connected USB devices
112
+ */
113
+ listDevices() {
114
+ return listDevices();
115
+ }
116
+ };
117
+ export {
118
+ UsbDeviceListener as default
119
+ };
120
+ //# sourceMappingURL=index.js.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\";\r\nimport path from \"node:path\";\r\nimport { fileURLToPath } from \"node:url\";\r\nimport { applyLogicalPortMapping, shouldNotifyDevice } from \"./device-filter\";\r\nimport type {\r\n\tDeviceAddCallback,\r\n\tDeviceInfo,\r\n\tDeviceRemoveCallback,\r\n\tListenerConfig,\r\n\tUsbDeviceListenerI,\r\n} from \"./types\";\r\n\r\ninterface NativeAddon {\r\n\tstartListening(): void;\r\n\tstopListening(): void;\r\n\tonDeviceAdd(callback: DeviceAddCallback): void;\r\n\tonDeviceRemove(callback: DeviceRemoveCallback): void;\r\n\tlistDevices(): DeviceInfo[];\r\n}\r\n\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n// Load native addon using require (CJS) from ESM context\r\nconst require = createRequire(import.meta.url);\r\nconst packageRoot = path.join(__dirname, \"..\");\r\nconst addon: NativeAddon = require(\"node-gyp-build\")(packageRoot);\r\n\r\nconst { listDevices, onDeviceAdd, onDeviceRemove, startListening, stopListening } = addon;\r\n\r\n/**\r\n * USB Device Listener implementation\r\n * Provides a type-safe wrapper around the native C++ addon\r\n */\r\nexport default class UsbDeviceListener implements UsbDeviceListenerI {\r\n\tprivate config: ListenerConfig = {};\r\n\tprivate userAddCallback: DeviceAddCallback | null = null;\r\n\tprivate userRemoveCallback: DeviceRemoveCallback | null = null;\r\n\r\n\t/**\r\n\t * Start listening for USB device events\r\n\t */\r\n\tpublic startListening(config: ListenerConfig): void {\r\n\t\tif (typeof config !== \"object\" || config === null) {\r\n\t\t\tthrow new TypeError(\"Config must be an object\");\r\n\t\t}\r\n\t\tthis.config = config;\r\n\t\tstartListening();\r\n\t}\r\n\r\n\t/**\r\n\t * Stop listening for USB device events\r\n\t */\r\n\tpublic stopListening(): void {\r\n\t\tstopListening();\r\n\t}\r\n\r\n\t/**\r\n\t * Register callback for device connection events\r\n\t */\r\n\tpublic onDeviceAdd(callback: DeviceAddCallback): void {\r\n\t\tif (typeof callback !== \"function\") {\r\n\t\t\tthrow new TypeError(\"Callback must be a function\");\r\n\t\t}\r\n\t\tthis.userAddCallback = callback;\r\n\r\n\t\t// Set up internal callback that filters devices\r\n\t\tonDeviceAdd((device: DeviceInfo) => {\r\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\r\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\r\n\t\t\t\tthis.userAddCallback?.(enrichedDevice);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Register callback for device disconnection events\r\n\t */\r\n\tpublic onDeviceRemove(callback: DeviceRemoveCallback): void {\r\n\t\tif (typeof callback !== \"function\") {\r\n\t\t\tthrow new TypeError(\"Callback must be a function\");\r\n\t\t}\r\n\t\tthis.userRemoveCallback = callback;\r\n\r\n\t\t// Set up internal callback that filters devices\r\n\t\tonDeviceRemove((device: DeviceInfo) => {\r\n\t\t\tif (shouldNotifyDevice(device, this.config)) {\r\n\t\t\t\tconst enrichedDevice = applyLogicalPortMapping(device, this.config);\r\n\t\t\t\tthis.userRemoveCallback?.(enrichedDevice);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * List all currently connected USB devices\r\n\t */\r\n\tpublic listDevices(): DeviceInfo[] {\r\n\t\treturn listDevices();\r\n\t}\r\n}\r\n\r\nexport type {\r\n\tDeviceInfo,\r\n\tDeviceAddCallback,\r\n\tDeviceRemoveCallback,\r\n\tListenerConfig,\r\n\tTargetDevice,\r\n} from \"./types\";\r\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
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * USB Device Listener Type Definitions
3
+ *
4
+ * Type-safe interface for monitoring USB device connections and disconnections
5
+ */
6
+ /**
7
+ * Device information returned by the listener
8
+ */
9
+ export interface DeviceInfo {
10
+ /**
11
+ * Windows device instance ID
12
+ * Format: "USB\VID_xxxx&PID_xxxx\..."
13
+ */
14
+ deviceId: string;
15
+ /**
16
+ * USB Vendor ID (decimal)
17
+ * Use .toString(16) to convert to hex string
18
+ */
19
+ vid: number;
20
+ /**
21
+ * USB Product ID (decimal)
22
+ * Use .toString(16) to convert to hex string
23
+ */
24
+ pid: number;
25
+ /**
26
+ * Physical USB port location
27
+ * Format: "Port_#xxxx.Hub_#yyyy"
28
+ * This value is stable and can be used as a key for logical port mapping
29
+ */
30
+ locationInfo: string;
31
+ /**
32
+ * User-defined logical port number from configuration
33
+ * null if not mapped in logicalPortMap
34
+ */
35
+ logicalPort: number | null;
36
+ }
37
+ /**
38
+ * Target device filter by VID/PID
39
+ */
40
+ export interface TargetDevice {
41
+ /**
42
+ * USB Vendor ID as hex string (e.g., "04E8")
43
+ */
44
+ vid: string;
45
+ /**
46
+ * USB Product ID as hex string (e.g., "6860")
47
+ */
48
+ pid: string;
49
+ }
50
+ /**
51
+ * Configuration for USB device listener
52
+ */
53
+ export interface ListenerConfig {
54
+ /**
55
+ * Map physical port locations to logical port numbers
56
+ *
57
+ * Key: Location info string (e.g., "Port_#0005.Hub_#0002")
58
+ * Value: Logical port number (e.g., 1, 2, 3)
59
+ *
60
+ * Use listDevices() to discover location strings for your setup
61
+ *
62
+ * @example
63
+ * {
64
+ * "Port_#0005.Hub_#0002": 1,
65
+ * "Port_#0006.Hub_#0002": 2
66
+ * }
67
+ */
68
+ logicalPortMap?: Record<string, number>;
69
+ /**
70
+ * Filter to monitor only specific devices by VID/PID
71
+ *
72
+ * Empty array or undefined = monitor all USB devices
73
+ *
74
+ * @example
75
+ * [
76
+ * { vid: "04E8", pid: "6860" }, // Samsung device
77
+ * { vid: "2341", pid: "0043" } // Arduino Uno
78
+ * ]
79
+ */
80
+ targetDevices?: TargetDevice[];
81
+ /**
82
+ * Devices to ignore by VID/PID
83
+ *
84
+ * Devices matching any entry will not trigger events,
85
+ * even if they match other filters.
86
+ * This takes highest priority over all other filters.
87
+ *
88
+ * @example
89
+ * [
90
+ * { vid: "1234", pid: "5678" } // Ignore this specific device
91
+ * ]
92
+ */
93
+ ignoredDevices?: TargetDevice[];
94
+ /**
95
+ * Only monitor these specific devices by VID/PID
96
+ *
97
+ * Empty array or undefined = monitor all devices (subject to other filters)
98
+ * When set, only devices matching an entry will trigger events.
99
+ *
100
+ * @example
101
+ * [
102
+ * { vid: "04E8", pid: "6860" } // Only listen to this device
103
+ * ]
104
+ */
105
+ listenOnlyDevices?: TargetDevice[];
106
+ }
107
+ /**
108
+ * Callback type for device add events
109
+ */
110
+ export type DeviceAddCallback = (deviceInfo: DeviceInfo) => void;
111
+ /**
112
+ * Callback type for device remove events
113
+ */
114
+ export type DeviceRemoveCallback = (deviceInfo: DeviceInfo) => void;
115
+ /**
116
+ * USB Device Listener Module
117
+ */
118
+ export interface UsbDeviceListenerI {
119
+ /**
120
+ * Start listening for USB device events
121
+ *
122
+ * Creates a background thread that monitors Windows device change messages.
123
+ * Callbacks registered with onDeviceAdd/onDeviceRemove will be invoked
124
+ * when matching devices are connected or disconnected.
125
+ *
126
+ * @param config - Listener configuration
127
+ * @throws {TypeError} If config is not an object
128
+ * @throws {Error} If listener is already running
129
+ *
130
+ * @example
131
+ * listener.startListening({
132
+ * logicalPortMap: { "Port_#0005.Hub_#0002": 1 },
133
+ * targetDevices: [{ vid: "04E8", pid: "6860" }]
134
+ * });
135
+ */
136
+ startListening(config: ListenerConfig): void;
137
+ /**
138
+ * Stop listening for USB device events
139
+ *
140
+ * Stops the background thread and cleans up resources.
141
+ * Safe to call multiple times.
142
+ *
143
+ * @example
144
+ * listener.stopListening();
145
+ */
146
+ stopListening(): void;
147
+ /**
148
+ * Register callback for device connection events
149
+ *
150
+ * The callback will be invoked on the Node.js main thread whenever
151
+ * a matching USB device is connected.
152
+ *
153
+ * @param callback - Function to call when device is connected
154
+ * @throws {TypeError} If callback is not a function
155
+ *
156
+ * @example
157
+ * listener.onDeviceAdd((device) => {
158
+ * console.log(`Device ${device.vid.toString(16)}:${device.pid.toString(16)}`);
159
+ * console.log(`Connected to port ${device.logicalPort}`);
160
+ * });
161
+ */
162
+ onDeviceAdd(callback: DeviceAddCallback): void;
163
+ /**
164
+ * Register callback for device disconnection events
165
+ *
166
+ * The callback will be invoked on the Node.js main thread whenever
167
+ * a matching USB device is disconnected.
168
+ *
169
+ * @param callback - Function to call when device is disconnected
170
+ * @throws {TypeError} If callback is not a function
171
+ *
172
+ * @example
173
+ * listener.onDeviceRemove((device) => {
174
+ * console.log(`Port ${device.logicalPort} disconnected`);
175
+ * });
176
+ */
177
+ onDeviceRemove(callback: DeviceRemoveCallback): void;
178
+ /**
179
+ * List all currently connected USB devices
180
+ *
181
+ * Returns information about all USB devices currently connected to the system.
182
+ * Useful for discovering device locations and initial state.
183
+ *
184
+ * @returns Array of device information objects
185
+ *
186
+ * @example
187
+ * const devices = listener.listDevices();
188
+ * devices.forEach(device => {
189
+ * console.log(`Location: ${device.locationInfo}`);
190
+ * console.log(`VID:PID = ${device.vid.toString(16)}:${device.pid.toString(16)}`);
191
+ * });
192
+ */
193
+ listDevices(): DeviceInfo[];
194
+ }
195
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IAEhC;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,YAAY,EAAE,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAE7C;;;;;;;;OAQG;IACH,aAAa,IAAI,IAAI,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAE/C;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAErD;;;;;;;;;;;;;;OAcG;IACH,WAAW,IAAI,UAAU,EAAE,CAAC;CAC5B"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@mcesystems/usb-device-listener",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Native Windows USB device listener using PnP notifications without custom drivers",
5
5
  "type": "module",
6
- "main": "src/index.ts",
7
- "types": "src/index.ts",
6
+ "main": "dist/index.js",
7
+ "types": "dist/types.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
- "import": "./src/index.ts",
11
- "types": "./src/index.ts"
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/types.d.ts"
12
12
  }
13
13
  },
14
14
  "keywords": [
@@ -49,11 +49,9 @@
49
49
  "vitest": "^2.1.8"
50
50
  },
51
51
  "files": [
52
- "src",
52
+ "dist",
53
53
  "prebuilds",
54
- "native",
55
- "binding.gyp",
56
- "tsconfig.json",
54
+ "build/Release/*.node",
57
55
  "README.md"
58
56
  ],
59
57
  "gypfile": true,
@@ -61,7 +59,7 @@
61
59
  "win32"
62
60
  ],
63
61
  "scripts": {
64
- "prebuild:gyp": "prebuildify --electron --strip --target 25.2.1",
62
+ "prebuild:gyp": "prebuildify --napi --strip --target 25.2.1",
65
63
  "build": "tsx esbuild.config.ts && tsc --emitDeclarationOnly",
66
64
  "rebuild": "node-gyp rebuild",
67
65
  "install": "node-gyp-build",
package/binding.gyp DELETED
@@ -1,41 +0,0 @@
1
- {
2
- "targets": [
3
- {
4
- "target_name": "usb_device_listener",
5
- "sources": [
6
- "native/addon.cc",
7
- "native/usb_listener.cc"
8
- ],
9
- "include_dirs": [
10
- "<!@(node -p \"require('node-addon-api').include\")"
11
- ],
12
- "dependencies": [
13
- "<!(node -p \"require('node-addon-api').gyp\")"
14
- ],
15
- "defines": [
16
- "NAPI_DISABLE_CPP_EXCEPTIONS",
17
- "UNICODE",
18
- "_UNICODE"
19
- ],
20
- "conditions": [
21
- [
22
- "OS=='win'",
23
- {
24
- "libraries": [
25
- "Setupapi.lib",
26
- "Cfgmgr32.lib"
27
- ],
28
- "msvs_settings": {
29
- "VCCLCompilerTool": {
30
- "ExceptionHandling": 1,
31
- "AdditionalOptions": [
32
- "/std:c++20"
33
- ]
34
- }
35
- }
36
- }
37
- ]
38
- ]
39
- }
40
- ]
41
- }
package/native/addon.cc DELETED
@@ -1,118 +0,0 @@
1
- #include <napi.h>
2
- #include "usb_listener.h"
3
- #include <memory>
4
-
5
- static std::unique_ptr<USBListener> g_listener;
6
-
7
- Napi::Value StartListening(const Napi::CallbackInfo& info) {
8
- Napi::Env env = info.Env();
9
-
10
- if (!g_listener) {
11
- g_listener = std::make_unique<USBListener>();
12
- }
13
-
14
- std::string errorMsg;
15
- if (!g_listener->StartListening(errorMsg)) {
16
- Napi::Error::New(env, errorMsg).ThrowAsJavaScriptException();
17
- return env.Undefined();
18
- }
19
-
20
- return env.Undefined();
21
- }
22
-
23
- Napi::Value StopListening(const Napi::CallbackInfo& info) {
24
- Napi::Env env = info.Env();
25
-
26
- if (g_listener) {
27
- g_listener->StopListening();
28
- }
29
-
30
- return env.Undefined();
31
- }
32
-
33
- Napi::Value OnDeviceAdd(const Napi::CallbackInfo& info) {
34
- Napi::Env env = info.Env();
35
-
36
- if (info.Length() < 1 || !info[0].IsFunction()) {
37
- Napi::TypeError::New(env, "Callback function expected").ThrowAsJavaScriptException();
38
- return env.Undefined();
39
- }
40
-
41
- Napi::Function callback = info[0].As<Napi::Function>();
42
-
43
- if (!g_listener) {
44
- g_listener = std::make_unique<USBListener>();
45
- }
46
-
47
- Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
48
- env,
49
- callback,
50
- "DeviceAddCallback",
51
- 0,
52
- 1
53
- );
54
-
55
- g_listener->SetAddCallback(tsfn);
56
-
57
- return env.Undefined();
58
- }
59
-
60
- Napi::Value OnDeviceRemove(const Napi::CallbackInfo& info) {
61
- Napi::Env env = info.Env();
62
-
63
- if (info.Length() < 1 || !info[0].IsFunction()) {
64
- Napi::TypeError::New(env, "Callback function expected").ThrowAsJavaScriptException();
65
- return env.Undefined();
66
- }
67
-
68
- Napi::Function callback = info[0].As<Napi::Function>();
69
-
70
- if (!g_listener) {
71
- g_listener = std::make_unique<USBListener>();
72
- }
73
-
74
- Napi::ThreadSafeFunction tsfn = Napi::ThreadSafeFunction::New(
75
- env,
76
- callback,
77
- "DeviceRemoveCallback",
78
- 0,
79
- 1
80
- );
81
-
82
- g_listener->SetRemoveCallback(tsfn);
83
-
84
- return env.Undefined();
85
- }
86
-
87
- Napi::Value ListDevices(const Napi::CallbackInfo& info) {
88
- Napi::Env env = info.Env();
89
-
90
- if (!g_listener) {
91
- g_listener = std::make_unique<USBListener>();
92
- }
93
-
94
- std::vector<DeviceInfo> devices = g_listener->ListAllDevices();
95
-
96
- Napi::Array result = Napi::Array::New(env, devices.size());
97
- for (size_t i = 0; i < devices.size(); i++) {
98
- Napi::Object obj = Napi::Object::New(env);
99
- obj.Set("deviceId", Napi::String::New(env, devices[i].deviceId));
100
- obj.Set("vid", Napi::Number::New(env, devices[i].vid));
101
- obj.Set("pid", Napi::Number::New(env, devices[i].pid));
102
- obj.Set("locationInfo", Napi::String::New(env, devices[i].locationInfo));
103
- result[i] = obj;
104
- }
105
-
106
- return result;
107
- }
108
-
109
- Napi::Object Init(Napi::Env env, Napi::Object exports) {
110
- exports.Set("startListening", Napi::Function::New(env, StartListening));
111
- exports.Set("stopListening", Napi::Function::New(env, StopListening));
112
- exports.Set("onDeviceAdd", Napi::Function::New(env, OnDeviceAdd));
113
- exports.Set("onDeviceRemove", Napi::Function::New(env, OnDeviceRemove));
114
- exports.Set("listDevices", Napi::Function::New(env, ListDevices));
115
- return exports;
116
- }
117
-
118
- NODE_API_MODULE(usb_device_listener, Init)