@mcesystems/usb-device-listener 1.0.30 → 1.0.32

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/types.d.ts CHANGED
@@ -8,10 +8,23 @@
8
8
  */
9
9
  export interface DeviceInfo {
10
10
  /**
11
- * Windows device instance ID
12
- * Format: "USB\VID_xxxx&PID_xxxx\..."
11
+ * Device identifier
12
+ * For devices with USB serial numbers: the serial number (formatted with dash for iOS style)
13
+ * For devices without serial: platform-specific format "USB\VID_xxxx&PID_xxxx\..."
14
+ * iOS devices: formatted as "XXXXXXXX-XXXXXXXXXXXXXXXX" (e.g., "00008030-001234567890402E")
13
15
  */
14
16
  deviceId: string;
17
+ /**
18
+ * Raw USB serial number (without formatting)
19
+ * Empty string if the device doesn't expose a serial number
20
+ */
21
+ serialNumber: string;
22
+ /**
23
+ * USB product name / device description
24
+ * Examples: "iPhone", "SAMSUNG_Android", "Arduino Uno"
25
+ * Empty string if not available
26
+ */
27
+ deviceName: string;
15
28
  /**
16
29
  * USB Vendor ID (decimal)
17
30
  * Use .toString(16) to convert to hex string
@@ -1 +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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;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/native/addon.cc CHANGED
@@ -97,6 +97,8 @@ Napi::Value ListDevices(const Napi::CallbackInfo& info) {
97
97
  for (size_t i = 0; i < devices.size(); i++) {
98
98
  Napi::Object obj = Napi::Object::New(env);
99
99
  obj.Set("deviceId", Napi::String::New(env, devices[i].deviceId));
100
+ obj.Set("serialNumber", Napi::String::New(env, devices[i].serialNumber));
101
+ obj.Set("deviceName", Napi::String::New(env, devices[i].deviceName));
100
102
  obj.Set("vid", Napi::Number::New(env, devices[i].vid));
101
103
  obj.Set("pid", Napi::Number::New(env, devices[i].pid));
102
104
  obj.Set("locationInfo", Napi::String::New(env, devices[i].locationInfo));
@@ -14,7 +14,9 @@
14
14
  * Platform-agnostic: used by both Windows and macOS implementations
15
15
  */
16
16
  struct DeviceInfo {
17
- std::string deviceId; // Platform-specific device instance ID
17
+ std::string deviceId; // Device ID (serial number if available, otherwise platform-specific)
18
+ std::string serialNumber; // USB serial number (raw, without formatting)
19
+ std::string deviceName; // USB product name / device description
18
20
  uint16_t vid{0}; // Vendor ID
19
21
  uint16_t pid{0}; // Product ID
20
22
  std::string locationInfo; // Physical port location (platform-specific format)
@@ -25,6 +25,99 @@ static const char* GetUSBDeviceClassName() {
25
25
  return kIOUSBDeviceClassName;
26
26
  }
27
27
 
28
+ /**
29
+ * Get a string property from an IOKit service
30
+ * Returns empty string if the property is not available
31
+ */
32
+ static std::string GetStringProperty(io_service_t service, CFStringRef propertyName) {
33
+ CFStringRef stringRef = (CFStringRef)IORegistryEntryCreateCFProperty(
34
+ service, propertyName, kCFAllocatorDefault, 0);
35
+
36
+ if (!stringRef) {
37
+ return "";
38
+ }
39
+
40
+ // Convert CFString to std::string
41
+ CFIndex length = CFStringGetLength(stringRef);
42
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
43
+ std::string result(maxSize, '\0');
44
+
45
+ if (CFStringGetCString(stringRef, &result[0], maxSize, kCFStringEncodingUTF8)) {
46
+ result.resize(strlen(result.c_str()));
47
+ } else {
48
+ result.clear();
49
+ }
50
+
51
+ CFRelease(stringRef);
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Get the USB product name / device name from the IOKit service
57
+ * Returns empty string if not available
58
+ */
59
+ static std::string GetDeviceName(io_service_t service) {
60
+ // Try kUSBProductString first (standard USB property)
61
+ std::string name = GetStringProperty(service, CFSTR(kUSBProductString));
62
+
63
+ // Fallback to "USB Product Name"
64
+ if (name.empty()) {
65
+ name = GetStringProperty(service, CFSTR("USB Product Name"));
66
+ }
67
+
68
+ // Fallback to IOKit device name
69
+ if (name.empty()) {
70
+ io_name_t deviceName;
71
+ if (IORegistryEntryGetName(service, deviceName) == KERN_SUCCESS) {
72
+ name = deviceName;
73
+ }
74
+ }
75
+
76
+ return name;
77
+ }
78
+
79
+ /**
80
+ * Get the USB serial number from the IOKit service
81
+ * Returns empty string if serial number is not available
82
+ */
83
+ static std::string GetSerialNumber(io_service_t service) {
84
+ // Try kUSBSerialNumberString first (standard USB property)
85
+ std::string serial = GetStringProperty(service, CFSTR(kUSBSerialNumberString));
86
+
87
+ // Fallback to "USB Serial Number" property name
88
+ if (serial.empty()) {
89
+ serial = GetStringProperty(service, CFSTR("USB Serial Number"));
90
+ }
91
+
92
+ return serial;
93
+ }
94
+
95
+ /**
96
+ * Format serial number in iOS-style dash-separated format
97
+ * iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
98
+ * Example: 00008030-001234567890402E
99
+ */
100
+ static std::string FormatSerialAsDeviceId(const std::string& serial) {
101
+ if (serial.empty()) {
102
+ return "";
103
+ }
104
+
105
+ // iOS serial numbers are typically 24-25 chars without dashes
106
+ // Format: first 8 chars, dash, remaining chars
107
+ // If it already contains dashes, return as-is
108
+ if (serial.find('-') != std::string::npos) {
109
+ return serial;
110
+ }
111
+
112
+ // iOS UDID format: 8 chars + dash + rest (typically 16 chars)
113
+ if (serial.length() >= 24) {
114
+ return serial.substr(0, 8) + "-" + serial.substr(8);
115
+ }
116
+
117
+ // For shorter serials (like Android), return as-is
118
+ return serial;
119
+ }
120
+
28
121
  USBListener::USBListener() = default;
29
122
 
30
123
  USBListener::~USBListener() {
@@ -229,6 +322,8 @@ void USBListener::HandleDeviceAdded(io_iterator_t iterator) {
229
322
  auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
230
323
  Napi::Object obj = Napi::Object::New(env);
231
324
  obj.Set("deviceId", Napi::String::New(env, data->deviceId));
325
+ obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
326
+ obj.Set("deviceName", Napi::String::New(env, data->deviceName));
232
327
  obj.Set("vid", Napi::Number::New(env, data->vid));
233
328
  obj.Set("pid", Napi::Number::New(env, data->pid));
234
329
  obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
@@ -280,6 +375,8 @@ void USBListener::HandleDeviceRemoved(io_iterator_t iterator) {
280
375
  auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
281
376
  Napi::Object obj = Napi::Object::New(env);
282
377
  obj.Set("deviceId", Napi::String::New(env, data->deviceId));
378
+ obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
379
+ obj.Set("deviceName", Napi::String::New(env, data->deviceName));
283
380
  obj.Set("vid", Napi::Number::New(env, data->vid));
284
381
  obj.Set("pid", Napi::Number::New(env, data->pid));
285
382
  obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
@@ -336,12 +433,24 @@ bool USBListener::GetDeviceInfoFromService(io_service_t service, DeviceInfo& inf
336
433
  // Get Location ID (used for physical port identification)
337
434
  info.locationInfo = GetLocationId(service);
338
435
 
339
- // Create a unique device ID combining VID, PID, and location
340
- std::ostringstream ss;
341
- ss << "USB\\VID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.vid
342
- << "&PID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.pid
343
- << "\\" << info.locationInfo;
344
- info.deviceId = ss.str();
436
+ // Get USB serial number
437
+ info.serialNumber = GetSerialNumber(service);
438
+
439
+ // Get device name
440
+ info.deviceName = GetDeviceName(service);
441
+
442
+ // Use serial number as device ID if available (formatted for iOS style)
443
+ // Otherwise fall back to VID/PID/location combination
444
+ if (!info.serialNumber.empty()) {
445
+ info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
446
+ } else {
447
+ // Fallback: Create a unique device ID combining VID, PID, and location
448
+ std::ostringstream ss;
449
+ ss << "USB\\VID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.vid
450
+ << "&PID_" << std::uppercase << std::hex << std::setw(4) << std::setfill('0') << info.pid
451
+ << "\\" << info.locationInfo;
452
+ info.deviceId = ss.str();
453
+ }
345
454
 
346
455
  return true;
347
456
  }
@@ -187,6 +187,8 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
187
187
  auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
188
188
  Napi::Object obj = Napi::Object::New(env);
189
189
  obj.Set("deviceId", Napi::String::New(env, data->deviceId));
190
+ obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
191
+ obj.Set("deviceName", Napi::String::New(env, data->deviceName));
190
192
  obj.Set("vid", Napi::Number::New(env, data->vid));
191
193
  obj.Set("pid", Napi::Number::New(env, data->pid));
192
194
  obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
@@ -203,6 +205,8 @@ void USBListener::HandleDeviceChange(WPARAM wParam, LPARAM lParam) {
203
205
  auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
204
206
  Napi::Object obj = Napi::Object::New(env);
205
207
  obj.Set("deviceId", Napi::String::New(env, data->deviceId));
208
+ obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
209
+ obj.Set("deviceName", Napi::String::New(env, data->deviceName));
206
210
  obj.Set("vid", Napi::Number::New(env, data->vid));
207
211
  obj.Set("pid", Napi::Number::New(env, data->pid));
208
212
  obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
@@ -267,9 +271,9 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
267
271
  if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
268
272
  if (_wcsicmp(instanceId, deviceIdW.c_str()) == 0) {
269
273
  std::wstring idW = instanceId;
270
- info.deviceId = std::string(idW.begin(), idW.end());
274
+ std::string fullDeviceId = std::string(idW.begin(), idW.end());
271
275
 
272
- if (!GetVidPid(info.deviceId, info.vid, info.pid)) {
276
+ if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
273
277
  SetupDiDestroyDeviceInfoList(deviceInfoSet);
274
278
  return false;
275
279
  }
@@ -280,6 +284,17 @@ bool USBListener::GetDeviceInfo(const std::string& devicePath, DeviceInfo& info)
280
284
  return false;
281
285
  }
282
286
 
287
+ // Get serial number and use as device ID if available
288
+ info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
289
+ if (!info.serialNumber.empty()) {
290
+ info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
291
+ } else {
292
+ info.deviceId = fullDeviceId;
293
+ }
294
+
295
+ // Get device name
296
+ info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
297
+
283
298
  SetupDiDestroyDeviceInfoList(deviceInfoSet);
284
299
  return true;
285
300
  }
@@ -324,6 +339,78 @@ bool USBListener::GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t
324
339
  }
325
340
  }
326
341
 
342
+ /**
343
+ * Get device description/name from Windows registry
344
+ */
345
+ std::string USBListener::GetDeviceName(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData) {
346
+ WCHAR buffer[MAX_PATH];
347
+
348
+ // Try to get device description first (usually the friendly name)
349
+ if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &devInfoData, SPDRP_DEVICEDESC,
350
+ nullptr, reinterpret_cast<PBYTE>(buffer), sizeof(buffer), nullptr)) {
351
+ std::wstring nameW = buffer;
352
+ return std::string(nameW.begin(), nameW.end());
353
+ }
354
+
355
+ // Fallback to friendly name
356
+ if (SetupDiGetDeviceRegistryPropertyW(deviceInfoSet, &devInfoData, SPDRP_FRIENDLYNAME,
357
+ nullptr, reinterpret_cast<PBYTE>(buffer), sizeof(buffer), nullptr)) {
358
+ std::wstring nameW = buffer;
359
+ return std::string(nameW.begin(), nameW.end());
360
+ }
361
+
362
+ return "";
363
+ }
364
+
365
+ /**
366
+ * Get USB serial number from device instance ID
367
+ * The serial number is typically the last part after the last backslash
368
+ */
369
+ std::string USBListener::GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData) {
370
+ WCHAR instanceId[MAX_PATH];
371
+ if (!SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
372
+ return "";
373
+ }
374
+
375
+ std::wstring idW = instanceId;
376
+ std::string deviceId(idW.begin(), idW.end());
377
+
378
+ // The serial number is typically after the last backslash in the device instance ID
379
+ // Format: USB\VID_XXXX&PID_XXXX\SERIAL_NUMBER
380
+ size_t lastBackslash = deviceId.rfind('\\');
381
+ if (lastBackslash != std::string::npos && lastBackslash + 1 < deviceId.length()) {
382
+ return deviceId.substr(lastBackslash + 1);
383
+ }
384
+
385
+ return "";
386
+ }
387
+
388
+ /**
389
+ * Format serial number in iOS-style dash-separated format
390
+ * iOS UDIDs are typically 24-25 characters and formatted as: XXXXXXXX-XXXXXXXXXXXXXXXX
391
+ * Example: 00008030-001234567890402E
392
+ */
393
+ static std::string FormatSerialAsDeviceId(const std::string& serial) {
394
+ if (serial.empty()) {
395
+ return "";
396
+ }
397
+
398
+ // iOS serial numbers are typically 24-25 chars without dashes
399
+ // Format: first 8 chars, dash, remaining chars
400
+ // If it already contains dashes, return as-is
401
+ if (serial.find('-') != std::string::npos) {
402
+ return serial;
403
+ }
404
+
405
+ // iOS UDID format: 8 chars + dash + rest (typically 16 chars)
406
+ if (serial.length() >= 24) {
407
+ return serial.substr(0, 8) + "-" + serial.substr(8);
408
+ }
409
+
410
+ // For shorter serials (like Android), return as-is
411
+ return serial;
412
+ }
413
+
327
414
  void USBListener::EnumerateConnectedDevices() {
328
415
  HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
329
416
  if (deviceInfoSet == INVALID_HANDLE_VALUE) {
@@ -338,9 +425,9 @@ void USBListener::EnumerateConnectedDevices() {
338
425
  if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
339
426
  DeviceInfo info;
340
427
  std::wstring idW = instanceId;
341
- info.deviceId = std::string(idW.begin(), idW.end());
428
+ std::string fullDeviceId = std::string(idW.begin(), idW.end());
342
429
 
343
- if (!GetVidPid(info.deviceId, info.vid, info.pid)) {
430
+ if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
344
431
  continue;
345
432
  }
346
433
 
@@ -349,6 +436,17 @@ void USBListener::EnumerateConnectedDevices() {
349
436
  continue;
350
437
  }
351
438
 
439
+ // Get serial number and use as device ID if available
440
+ info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
441
+ if (!info.serialNumber.empty()) {
442
+ info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
443
+ } else {
444
+ info.deviceId = fullDeviceId;
445
+ }
446
+
447
+ // Get device name
448
+ info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
449
+
352
450
  // Cache device with exclusive lock
353
451
  {
354
452
  std::unique_lock lock(m_cacheMutex);
@@ -359,6 +457,8 @@ void USBListener::EnumerateConnectedDevices() {
359
457
  auto callback = [](Napi::Env env, Napi::Function jsCallback, DeviceInfo* data) {
360
458
  Napi::Object obj = Napi::Object::New(env);
361
459
  obj.Set("deviceId", Napi::String::New(env, data->deviceId));
460
+ obj.Set("serialNumber", Napi::String::New(env, data->serialNumber));
461
+ obj.Set("deviceName", Napi::String::New(env, data->deviceName));
362
462
  obj.Set("vid", Napi::Number::New(env, data->vid));
363
463
  obj.Set("pid", Napi::Number::New(env, data->pid));
364
464
  obj.Set("locationInfo", Napi::String::New(env, data->locationInfo));
@@ -394,9 +494,9 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
394
494
  if (SetupDiGetDeviceInstanceId(deviceInfoSet, &devInfoData, instanceId, MAX_PATH, nullptr)) {
395
495
  DeviceInfo info;
396
496
  std::wstring idW = instanceId;
397
- info.deviceId = std::string(idW.begin(), idW.end());
497
+ std::string fullDeviceId = std::string(idW.begin(), idW.end());
398
498
 
399
- if (!GetVidPid(info.deviceId, info.vid, info.pid)) {
499
+ if (!GetVidPid(fullDeviceId, info.vid, info.pid)) {
400
500
  continue;
401
501
  }
402
502
 
@@ -405,6 +505,17 @@ std::vector<DeviceInfo> USBListener::ListAllDevices() {
405
505
  continue;
406
506
  }
407
507
 
508
+ // Get serial number and use as device ID if available
509
+ info.serialNumber = GetSerialNumber(deviceInfoSet, devInfoData);
510
+ if (!info.serialNumber.empty()) {
511
+ info.deviceId = FormatSerialAsDeviceId(info.serialNumber);
512
+ } else {
513
+ info.deviceId = fullDeviceId;
514
+ }
515
+
516
+ // Get device name
517
+ info.deviceName = GetDeviceName(deviceInfoSet, devInfoData);
518
+
408
519
  devices.push_back(info);
409
520
  }
410
521
  }
@@ -74,6 +74,8 @@ private:
74
74
  bool GetDeviceInfo(const std::string& devicePath, DeviceInfo& info); // Get device info from Windows API (for connections)
75
75
  bool GetDeviceInfoFromPath(const std::string& devicePath, DeviceInfo& info); // Parse device info from path (for disconnections)
76
76
  bool GetLocationInfo(DEVINST devInst, std::string& locationInfo); // Get physical port location
77
+ std::string GetSerialNumber(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData); // Get USB serial number
78
+ std::string GetDeviceName(HDEVINFO deviceInfoSet, SP_DEVINFO_DATA& devInfoData); // Get device description/name
77
79
  bool GetVidPid(const std::string& deviceId, uint16_t& vid, uint16_t& pid); // Parse VID/PID from device ID string
78
80
  void EnumerateConnectedDevices(); // Enumerate devices on startup
79
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcesystems/usb-device-listener",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Native cross-platform USB device listener for Windows and macOS",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",