Skip to content

Commit 71549a4

Browse files
committed
Windows USB support
1 parent b6d1775 commit 71549a4

File tree

9 files changed

+351
-67
lines changed

9 files changed

+351
-67
lines changed

README.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ This project containts libs to get information about system.
1010
#### USB
1111
| Name | Type | Linux | Mac OS | Windows (10+) | Description |
1212
|---------------|-----------------|-------|--------|---------------|---------------------------------------------------|
13-
|`bus` | string \| null || || Identifies the USB bus the device is connected to |
14-
|`deviceId` | string \| null || | | Unique identifier assigned to the USB device |
15-
|`id` | string \| null || || Unique identifier for the USB device |
16-
|`name` | string \| null || || Human-readable name of the USB device |
17-
|`type` | string \| null || || Categorizes the USB device based on functionality |
18-
|`removable` | boolean \| null || || Indicates if the device is removable |
19-
|`vendor` | string \| null || || Identifies the vendor |
20-
|`manufacturer` | string \| null || || Specifies the device's manufacturer. |
21-
|`maxPower` | string \| null || || Maximum power the device can draw (in mA) |
22-
|`serialNumber` | string \| null || || Unique serial number assigned by the manufacturer |
13+
|`bus` | string \| null |||| Identifies the USB bus the device is connected to |
14+
|`deviceId` | string \| null ||| | Unique identifier assigned to the USB device |
15+
|`id` | string \| null |||| Unique identifier for the USB device |
16+
|`name` | string \| null |||| Human-readable name of the USB device |
17+
|`type` | string \| null |||| Categorizes the USB device based on functionality |
18+
|`removable` | boolean \| null |||| Indicates if the device is removable |
19+
|`vendor` | string \| null |||| Identifies the vendor |
20+
|`manufacturer` | string \| null |||| Specifies the device's manufacturer. |
21+
|`maxPower` | string \| null |||| Maximum power the device can draw (in mA) |
22+
|`serialNumber` | string \| null |||| Unique serial number assigned by the manufacturer |
2323

2424
### Install
2525
```sh
@@ -44,8 +44,14 @@ const myUsbDevices = async() => {
4444
3. Run playground ```yarn start```
4545

4646
### TODO
47-
- [ ] Support linux USB devices
48-
- [ ] Support windows USB devices
47+
#### USB
48+
- [ ] Support Linux USB devices
49+
- [X] Support Windows USB devices
50+
51+
#### Audio
52+
- [ ] Support linux audio devices
53+
- [ ] Support Mac OS audio devices
54+
- [ ] Support Windows audio devices
4955

5056
### License
5157
Further details see [LICENSE](LICENSE) file.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "spci-libs",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"description": "System information library",
55
"author": "Tagir Khadzhiev <aspedm@gmail.com>",
66
"license": "MIT",
@@ -11,8 +11,8 @@
1111
"types": "./dist/index.d.ts",
1212
"scripts": {
1313
"start": "node --experimental-modules playground/index.js",
14-
"clean": "rm -rf dist",
15-
"build": "yarn clean && node_modules/.bin/rollup --config",
14+
"clean": "rimraf dist",
15+
"build": "yarn clean && rollup --config",
1616
"prepublishOnly": "yarn build"
1717
},
1818
"devDependencies": {
@@ -23,6 +23,7 @@
2323
"@rollup/plugin-commonjs": "25.0.7",
2424
"@rollup/plugin-json": "6.1.0",
2525
"@rollup/plugin-node-resolve": "15.2.3",
26+
"@rollup/plugin-terser": "0.4.4",
2627
"@rollup/plugin-typescript": "11.1.6",
2728
"@types/lodash": "4.14.202",
2829
"@types/node": "20.10.8",
@@ -40,10 +41,10 @@
4041
"eslint-plugin-react": "7.33.2",
4142
"eslint-plugin-react-hooks": "4.6.0",
4243
"prettier": "3.2.5",
44+
"rimraf": "5.0.5",
4345
"rollup": "4.12.0",
4446
"rollup-plugin-commonjs": "10.1.0",
4547
"rollup-plugin-dts": "6.1.0",
46-
"rollup-plugin-terser": "7.0.2",
4748
"typescript": "5.3.3"
4849
},
4950
"dependencies": {

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { babel } from '@rollup/plugin-babel';
22
import commonjs from '@rollup/plugin-commonjs';
33
import json from '@rollup/plugin-json';
44
import { nodeResolve } from '@rollup/plugin-node-resolve';
5+
import terser from '@rollup/plugin-terser';
56
import { dts } from 'rollup-plugin-dts';
6-
import { terser } from 'rollup-plugin-terser';
77

88
import pkg from './package.json' assert { type: 'json' };
99

src/system/usb/README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@ This class returned array of detected USB devices
44
### Fields
55
Returned fields depends of OS.
66

7-
| Name | Type | Linux | Mac OS | Windows | Description |
8-
|---------------|-----------------|-------|--------|---------|---------------------------------------------------|
9-
|`bus` | string \| null || || Identifies the USB bus the device is connected to |
10-
|`deviceId` | string \| null || | | Unique identifier assigned to the USB device |
11-
|`id` | string \| null || || Unique identifier for the USB device |
12-
|`name` | string \| null || || Human-readable name of the USB device |
13-
|`type` | string \| null || || Categorizes the USB device based on functionality |
14-
|`removable` | boolean \| null || || Indicates if the device is removable |
15-
|`vendor` | string \| null || || Identifies the vendor |
16-
|`manufacturer` | string \| null || || Specifies the device's manufacturer. |
17-
|`maxPower` | string \| null || || Maximum power the device can draw (in mA) |
18-
|`serialNumber` | string \| null || || Unique serial number assigned by the manufacturer |
7+
| Name | Type | Linux | Mac OS | Windows (10+) | Description |
8+
|---------------|-----------------|-------|--------|---------------|---------------------------------------------------|
9+
|`bus` | string \| null ||| | Identifies the USB bus the device is connected to |
10+
|`deviceId` | string \| null ||| | Unique identifier assigned to the USB device |
11+
|`id` | string \| null ||| | Unique identifier for the USB device |
12+
|`name` | string \| null ||| | Human-readable name of the USB device |
13+
|`type` | string \| null ||| | Categorizes the USB device based on functionality |
14+
|`removable` | boolean \| null ||| | Indicates if the device is removable |
15+
|`vendor` | string \| null ||| | Identifies the vendor |
16+
|`manufacturer` | string \| null ||| | Specifies the device's manufacturer. |
17+
|`maxPower` | string \| null ||| | Maximum power the device can draw (in mA) |
18+
|`serialNumber` | string \| null ||| | Unique serial number assigned by the manufacturer |
1919

2020

2121
### How to use

src/system/usb/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ export enum USB_DEVICE_TYPE {
3333
MOUSE = 'Mouse',
3434
MICROPHONE = 'Microphone',
3535
STORAGE = 'Storage',
36+
USB_COMPOSITE_DEVICE = 'USB Composite Device',
3637
}

src/system/usb/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { plafromDetector } from '../../helpers/platformDetector';
22

33
import { ISpciUsb, ISpciUsbDevice } from './interface';
44
import Macos from './macos';
5+
import Windows from './windows';
56

67
class Usb {
78
private PLATFORM: ISpciUsb | null = null;
@@ -10,7 +11,7 @@ class Usb {
1011
this.PLATFORM = plafromDetector<ISpciUsb | null>({
1112
linux: null,
1213
macos: new Macos(),
13-
windows: null,
14+
windows: new Windows(),
1415
});
1516
}
1617

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { USB_DEVICE_TYPE } from '../../config';
2+
3+
class DeviceTypeMapper {
4+
private static WINDOWS_DEVICE_TYPES_MAP: Record<string, USB_DEVICE_TYPE> = {
5+
iphone: USB_DEVICE_TYPE.PHONE,
6+
ipad: USB_DEVICE_TYPE.TABLET,
7+
magsafe: USB_DEVICE_TYPE.MAGSAFE,
8+
earpods: USB_DEVICE_TYPE.HEADSET,
9+
usbstor: USB_DEVICE_TYPE.STORAGE,
10+
usbaudio: USB_DEVICE_TYPE.AUDIO,
11+
disk: USB_DEVICE_TYPE.STORAGE,
12+
usbhub: USB_DEVICE_TYPE.HUB,
13+
bthusb: USB_DEVICE_TYPE.BLUETOOTH,
14+
usbccgp: USB_DEVICE_TYPE.USB_COMPOSITE_DEVICE,
15+
wudfwpdmtp: USB_DEVICE_TYPE.PHONE,
16+
controller: USB_DEVICE_TYPE.CONTROLLER,
17+
};
18+
19+
/**
20+
* Map windows style usb device type to universal format
21+
* @param {string} type
22+
* @param {string} service
23+
* @returns {string}
24+
*/
25+
public static getFriendlyNameType(type: string, service: string): string {
26+
if (typeof type !== 'string') return type;
27+
28+
const foundDevice = Object.keys(this.WINDOWS_DEVICE_TYPES_MAP).find(keyword => type.includes(keyword));
29+
30+
return foundDevice ? this.WINDOWS_DEVICE_TYPES_MAP[foundDevice] : service;
31+
}
32+
}
33+
34+
export default DeviceTypeMapper;

src/system/usb/windows/index.ts

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,120 @@
1+
import get from 'lodash/get';
2+
import shelljs from 'shelljs';
3+
4+
import { DEFAULT_FIELDS_VALUES } from '../config';
15
import { ISpciUsb, ISpciUsbDevice } from '../interface';
26

7+
import DeviceTypeMapper from './deviceTypeMapper';
8+
39
class Windows implements ISpciUsb {
10+
private USB_FIELDS: ISpciUsbDevice = { ...DEFAULT_FIELDS_VALUES };
11+
12+
private USB_BLOCK_LIST: string[] = [];
13+
14+
private CMD: string =
15+
"Get-PnpDevice -PresentOnly | Where-Object { $_.InstanceId -match '^USB' } | Select-Object InstanceId, Status, ClassGuid, Manufacturer, DeviceId, FriendlyName, HardwareId, Service, Description | ConvertTo-JSON";
16+
17+
/**
18+
* @param {string} output
19+
* @returns {Record<string, string>[] | null}
20+
*/
21+
private parseOutput(output: string): Record<string, string>[] | null {
22+
if (typeof output !== 'string' || output.length === 0) return null;
23+
24+
try {
25+
const result: Record<string, string>[] = JSON.parse(output);
26+
return result;
27+
} catch (error) {
28+
console.error('Error while parse Windows output', error);
29+
return null;
30+
}
31+
}
32+
33+
/**
34+
* @param {string | null} name
35+
* @returns {boolean}
36+
*/
37+
private invalidDevice(name: string | null): boolean {
38+
if (typeof name !== 'string') return true;
39+
const value = name.toLowerCase();
40+
41+
const result = this.USB_BLOCK_LIST.some(item => item.includes(value));
42+
return result;
43+
}
44+
45+
/**
46+
* @param { Record<string, string>} usbObj
47+
* @param {number} id - Fake id
48+
* @returns {ISpciUsbDevice | null}
49+
*/
50+
private fillUsbFields(usbObj: Record<string, string>, id: number): ISpciUsbDevice {
51+
try {
52+
const name = get(usbObj, 'FriendlyName', null);
53+
const invalidDevice = this.invalidDevice(name);
54+
if (invalidDevice) {
55+
return null;
56+
}
57+
58+
const deviceId = get(usbObj, 'InstanceId', null);
59+
const type = `${get(usbObj, 'Service', '').toLowerCase()} ${get(usbObj, 'FriendlyName', '').toLowerCase()}`;
60+
const manufacturer = get(usbObj, 'Manufacturer', null);
61+
62+
const usbFields: ISpciUsbDevice = {
63+
...this.USB_FIELDS,
64+
id: id.toString(),
65+
name,
66+
deviceId,
67+
type: DeviceTypeMapper.getFriendlyNameType(type, get(usbObj, 'Service', '')),
68+
manufacturer,
69+
};
70+
71+
return usbFields;
72+
} catch (error) {
73+
console.error('Error while fill usb fields');
74+
return null;
75+
}
76+
}
77+
78+
/**
79+
* @returns {Promise<ISpciUsbDevice[]>}
80+
*/
81+
private async getDevicesFromTerminal(): Promise<ISpciUsbDevice[]> {
82+
return new Promise((resolve, reject) => {
83+
const result: ISpciUsbDevice[] = [];
84+
const responce = shelljs.exec(`powershell.exe -Command "${this.CMD}"`, {
85+
async: false,
86+
silent: true,
87+
});
88+
89+
if (responce.code !== 0) {
90+
console.error('Error while get data from terminal. Code:', responce.code);
91+
reject(responce.stderr);
92+
}
93+
94+
const devices = this.parseOutput(responce.stdout);
95+
if (devices === null) {
96+
reject(new Error('Error while parse windows output'));
97+
}
98+
99+
devices.forEach((device, index) => {
100+
const usb = this.fillUsbFields(device, index + 1);
101+
102+
if (usb !== null) {
103+
result.push(usb);
104+
}
105+
});
106+
107+
resolve(result);
108+
});
109+
}
110+
4111
/**
5112
* Get windows usb devices
6113
* @returns {Promise<ISpciUsbDevice>}
7114
*/
8115
public async getInfo(): Promise<ISpciUsbDevice[]> {
9-
return [];
116+
const result = await this.getDevicesFromTerminal();
117+
return result;
10118
}
11119
}
12120

0 commit comments

Comments
 (0)