@companion-surface/base 1.0.0 → 1.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.0](https://github.com/bitfocus/companion-surface-api/compare/companion-surface-base-v1.0.0...companion-surface-base-v1.1.0) (2026-01-05)
4
+
5
+
6
+ ### Features
7
+
8
+ * refine surfaceId generation ([#33](https://github.com/bitfocus/companion-surface-api/issues/33)) ([8f23f82](https://github.com/bitfocus/companion-surface-api/commit/8f23f8265fad79f31b243a2d67412b21935eadc2))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * remove unused StableDeviceIdGenerator ([b237c01](https://github.com/bitfocus/companion-surface-api/commit/b237c01c2ff702c709c674444adce1a3fd8df271))
14
+
3
15
  ## [1.0.0](https://github.com/bitfocus/companion-surface-api/compare/companion-surface-base-v0.7.0...companion-surface-base-v1.0.0) (2025-12-15)
4
16
 
5
17
 
@@ -1,8 +1,13 @@
1
1
  import type EventEmitter from 'node:events';
2
- import type { DiscoveredSurfaceInfo } from './plugin.js';
2
+ import type { DetectionSurfaceInfo } from './plugin.js';
3
3
  export interface SurfacePluginDetectionEvents<TInfo> {
4
4
  /** Emitted when surfaces are detected */
5
- surfacesAdded: [surfaceInfos: DiscoveredSurfaceInfo<TInfo>[]];
5
+ surfacesAdded: [surfaceInfos: DetectionSurfaceInfo<TInfo>[]];
6
+ /**
7
+ * Emitted when discovered surfaces are lost
8
+ * Note: This is important to call, to allow the id to be reused by a newly detected surface later on
9
+ */
10
+ surfacesRemoved: [deviceHandles: string[]];
6
11
  }
7
12
  /**
8
13
  * For some plugins which only support using a builtin detection mechanism, this can be used to provide the detection info
@@ -18,6 +23,6 @@ export interface SurfacePluginDetection<TInfo> extends EventEmitter<SurfacePlugi
18
23
  * You can use this to cleanup any resources/handles for this surface, as it will not be used further
19
24
  * @param surfaceInfo The info about the surface which was rejected
20
25
  */
21
- rejectSurface(surfaceInfo: DiscoveredSurfaceInfo<TInfo>): void;
26
+ rejectSurface(surfaceInfo: DetectionSurfaceInfo<TInfo>): void;
22
27
  }
23
28
  //# sourceMappingURL=detection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../src/surface-api/detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExD,MAAM,WAAW,4BAA4B,CAAC,KAAK;IAClD,yCAAyC;IACzC,aAAa,EAAE,CAAC,YAAY,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;CAE7D;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK,CAAE,SAAQ,YAAY,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACvG;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5B;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;CAC9D"}
1
+ {"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../src/surface-api/detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,MAAM,WAAW,4BAA4B,CAAC,KAAK;IAClD,yCAAyC;IACzC,aAAa,EAAE,CAAC,YAAY,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAC5D;;;OAGG;IACH,eAAe,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAAA;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,KAAK,CAAE,SAAQ,YAAY,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC;IACvG;;;OAGG;IACH,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAE5B;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,oBAAoB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;CAC7D"}
@@ -44,7 +44,7 @@ export interface SurfacePlugin<TInfo> {
44
44
  * Perform a scan for devices, but not open them
45
45
  * Note: This should only be used if the plugin uses a protocol where we don't have other handling for
46
46
  */
47
- scanForSurfaces?: () => Promise<DiscoveredSurfaceInfo<TInfo>[]>;
47
+ scanForSurfaces?: () => Promise<DetectionSurfaceInfo<TInfo>[]>;
48
48
  /**
49
49
  * Open a discovered/known surface
50
50
  * This can be called for multiple surfaces in parallel
@@ -60,9 +60,16 @@ export interface SurfacePlugin<TInfo> {
60
60
  */
61
61
  export interface DiscoveredSurfaceInfo<TInfo> {
62
62
  /**
63
- * Unique id of the surface. Typically a serialnumber
63
+ * Desired id of the surface. Typically a serialnumber.
64
+ * It may not be opened with this id, as collisions may be resolved by the host
65
+ * This does not have to be unique, if collisions are found it will be given a suffix to make it unique
64
66
  */
65
67
  surfaceId: string;
68
+ /**
69
+ * Set this to true if the surface id is known to not be unique and should always be given a suffix
70
+ * Otherwise, a suffix will only be added if a collision is detected
71
+ */
72
+ surfaceIdIsNotUnique?: boolean;
66
73
  /**
67
74
  * Human friendly description of the surface. Typically a model name
68
75
  */
@@ -72,4 +79,21 @@ export interface DiscoveredSurfaceInfo<TInfo> {
72
79
  */
73
80
  pluginInfo: TInfo;
74
81
  }
82
+ /**
83
+ * Information about a surface discovered via a detection/remote mechanism.
84
+ * Extends {@link DiscoveredSurfaceInfo} with a stable tracking handle (`deviceHandle`)
85
+ * that can be used to re-identify the same physical device between scans.
86
+ */
87
+ export interface DetectionSurfaceInfo<TInfo> extends DiscoveredSurfaceInfo<TInfo> {
88
+ /**
89
+ * A stable unique identifier for the device.
90
+ * This is used to identify the same physical device between scans and operations, until it is disconnected. It is not shown to the user
91
+ *
92
+ * This is the same logical concept that other interfaces may refer to as `devicePath`,
93
+ * but is named `deviceHandle` here because, depending on the transport or platform,
94
+ * it may not literally be a filesystem path. For USB devices it will typically be the
95
+ * device path, while for other transports it can be another stable identifier.
96
+ */
97
+ deviceHandle: string;
98
+ }
75
99
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/surface-api/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,KAAK,CAAC,CAAA;IAElD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAA;IAE5C;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAErB;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAExB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,qBAAqB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAEnF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAE/D;;;;;;;OAOG;IACH,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAC1G;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC3C;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,UAAU,EAAE,KAAK,CAAA;CACjB"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/surface-api/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAC5D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,KAAK,CAAC,CAAA;IAElD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAA;IAE5C;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAErB;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAExB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,qBAAqB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;IAEnF;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAE9D;;;;;;;OAOG;IACH,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;CAC1G;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC3C;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,UAAU,EAAE,KAAK,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB,CAAC,KAAK,CAAE,SAAQ,qBAAqB,CAAC,KAAK,CAAC;IAChF;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAA;CACpB"}
@@ -1,9 +1,9 @@
1
1
  import type EventEmitter from 'node:events';
2
2
  import type { OptionsObject, SomeCompanionInputField } from './input.js';
3
3
  import type { RemoteSurfaceConnectionInfo } from './types.js';
4
- import type { DiscoveredSurfaceInfo } from './plugin.js';
4
+ import type { DetectionSurfaceInfo } from './plugin.js';
5
5
  export interface SurfacePluginRemoteEvents<TInfo> {
6
- surfacesConnected: [surfaceInfos: DiscoveredSurfaceInfo<TInfo>[]];
6
+ surfacesConnected: [surfaceInfos: DetectionSurfaceInfo<TInfo>[]];
7
7
  /**
8
8
  * Fired when surface discovery detects new surfaces
9
9
  * This is used as suggestions in the ui of remote surfaces the user can setup
@@ -49,7 +49,7 @@ export interface SurfacePluginRemote<TInfo> extends EventEmitter<SurfacePluginRe
49
49
  * You can use this to cleanup any resources/handles for this surface, as it will not be used further
50
50
  * @param surfaceInfo The info about the surface which was rejected
51
51
  */
52
- rejectSurface(surfaceInfo: DiscoveredSurfaceInfo<TInfo>): void;
52
+ rejectSurface(surfaceInfo: DetectionSurfaceInfo<TInfo>): void;
53
53
  }
54
54
  export interface DiscoveredRemoteSurfaceInfo {
55
55
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/surface-api/remote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAExD,MAAM,WAAW,yBAAyB,CAAC,KAAK;IAC/C,iBAAiB,EAAE,CAAC,YAAY,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAGjE;;;OAGG;IACH,gBAAgB,EAAE,CAAC,eAAe,EAAE,2BAA2B,EAAE,CAAC,CAAA;IAClE;;;OAGG;IACH,oBAAoB,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAAA;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,KAAK,CAAE,SAAQ,YAAY,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACjG;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,uBAAuB,EAAE,CAAA;IAEhD;;;;;;;OAOG;IACH,QAAQ,CAAC,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpD;;;OAGG;IACH,gBAAgB,CAAC,eAAe,EAAE,2BAA2B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE/E;;;OAGG;IACH,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;CAC9D;AAED,MAAM,WAAW,2BAA2B;IAC3C;;OAEG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAKnB;;;OAGG;IACH,MAAM,EAAE,aAAa,CAAA;CACrB"}
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/surface-api/remote.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,aAAa,CAAA;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AACxE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAEvD,MAAM,WAAW,yBAAyB,CAAC,KAAK;IAC/C,iBAAiB,EAAE,CAAC,YAAY,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;IAGhE;;;OAGG;IACH,gBAAgB,EAAE,CAAC,eAAe,EAAE,2BAA2B,EAAE,CAAC,CAAA;IAClE;;;OAGG;IACH,oBAAoB,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC,CAAA;CAC/C;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,KAAK,CAAE,SAAQ,YAAY,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACjG;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,uBAAuB,EAAE,CAAA;IAEhD;;;;;;;OAOG;IACH,QAAQ,CAAC,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAA;IAEpD;;;OAGG;IACH,gBAAgB,CAAC,eAAe,EAAE,2BAA2B,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE/E;;;OAGG;IACH,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD;;;;OAIG;IACH,aAAa,CAAC,WAAW,EAAE,oBAAoB,CAAC,KAAK,CAAC,GAAG,IAAI,CAAA;CAC7D;AAED,MAAM,WAAW,2BAA2B;IAC3C;;OAEG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAKnB;;;OAGG;IACH,MAAM,EAAE,aAAa,CAAA;CACrB"}
package/dist/util.d.ts CHANGED
@@ -5,25 +5,4 @@ export interface RgbColor {
5
5
  b: number;
6
6
  }
7
7
  export declare function parseColor(color: string | undefined): RgbColor;
8
- /**
9
- * Generator for stable fake serial numbers for usb devices without hardware serials.
10
- *
11
- * Generates deterministic serials by hashing just the uniqueness key and an index.
12
- */
13
- export declare class StableDeviceIdGenerator {
14
- #private;
15
- /**
16
- * Generate a stable serial number for a device without a hardware serial.
17
- *
18
- * @param uniquenessKey - Identifier for the device type (e.g., "vendorId:productId")
19
- * @param path - Device path (e.g., "/dev/hidraw0")
20
- * @returns A stable fake serial number
21
- */
22
- generateId(uniquenessKey: string, devicePath: string): string;
23
- /**
24
- * Prune cache entries for devices that were not seen during the current scan.
25
- * This should be called once after processing all devices in a scan.
26
- */
27
- pruneNotSeen(): void;
28
- }
29
8
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAEA,wBAAgB,WAAW,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAE3C;AAED,MAAM,WAAW,QAAQ;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACT;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAM9D;AAED;;;;GAIG;AACH,qBAAa,uBAAuB;;IAiBnC;;;;;;OAMG;IACH,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAqB7D;;;OAGG;IACH,YAAY,IAAI,IAAI;CAiBpB"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,EAAE,EAAE,KAAK,GAAG,IAAI,CAE3C;AAED,MAAM,WAAW,QAAQ;IACxB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACT;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAM9D"}
package/dist/util.js CHANGED
@@ -1,4 +1,3 @@
1
- import { createHash } from 'node:crypto';
2
1
  export function assertNever(_v) {
3
2
  // Nothing to do
4
3
  }
@@ -8,69 +7,4 @@ export function parseColor(color) {
8
7
  const b = color ? parseInt(color.substr(5, 2), 16) : 0;
9
8
  return { r, g, b };
10
9
  }
11
- /**
12
- * Generator for stable fake serial numbers for usb devices without hardware serials.
13
- *
14
- * Generates deterministic serials by hashing just the uniqueness key and an index.
15
- */
16
- export class StableDeviceIdGenerator {
17
- /**
18
- * Cache of device info to fake serial
19
- * Map: `${uniquenessKey}||${devicePath}` -> string
20
- */
21
- #previousForDevicePath = new Map();
22
- /**
23
- * Track which device paths were seen during the current scan
24
- * Set of keys: `${uniquenessKey}||${devicePath}`
25
- */
26
- #seenThisScan = new Set();
27
- /**
28
- * Track which serialnumbers were returned during this scan
29
- */
30
- #returnedThisScan = new Set();
31
- /**
32
- * Generate a stable serial number for a device without a hardware serial.
33
- *
34
- * @param uniquenessKey - Identifier for the device type (e.g., "vendorId:productId")
35
- * @param path - Device path (e.g., "/dev/hidraw0")
36
- * @returns A stable fake serial number
37
- */
38
- generateId(uniquenessKey, devicePath) {
39
- const pathCacheKey = `${uniquenessKey}||${devicePath}`;
40
- // Mark as seen during this scan
41
- this.#seenThisScan.add(pathCacheKey);
42
- // If there is something cached against the devicePath, use that
43
- const cachedSerial = this.#previousForDevicePath.get(pathCacheKey);
44
- if (cachedSerial)
45
- return cachedSerial;
46
- // Loop until we find a non-colliding ID
47
- for (let i = 0;; i++) {
48
- const fakeSerial = createHash('sha1').update(`${uniquenessKey}||${i}`).digest('hex');
49
- if (!this.#returnedThisScan.has(fakeSerial)) {
50
- this.#returnedThisScan.add(fakeSerial);
51
- this.#previousForDevicePath.set(pathCacheKey, fakeSerial);
52
- return fakeSerial;
53
- }
54
- }
55
- }
56
- /**
57
- * Prune cache entries for devices that were not seen during the current scan.
58
- * This should be called once after processing all devices in a scan.
59
- */
60
- pruneNotSeen() {
61
- // Remove path cache entries that weren't seen
62
- for (const [key] of this.#previousForDevicePath.entries()) {
63
- if (!this.#seenThisScan.has(key)) {
64
- this.#previousForDevicePath.delete(key);
65
- }
66
- }
67
- // Clear the seen set for the next scan
68
- this.#seenThisScan.clear();
69
- this.#returnedThisScan.clear();
70
- // Prepopulate the returned set with existing serials to avoid collisions
71
- for (const serial of this.#previousForDevicePath.values()) {
72
- this.#returnedThisScan.add(serial);
73
- }
74
- }
75
- }
76
10
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,UAAU,WAAW,CAAC,EAAS;IACpC,gBAAgB;AACjB,CAAC;AAQD,MAAM,UAAU,UAAU,CAAC,KAAyB;IACnD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,uBAAuB;IACnC;;;OAGG;IACH,sBAAsB,GAAG,IAAI,GAAG,EAAkB,CAAA;IAElD;;;OAGG;IACH,aAAa,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC;;OAEG;IACH,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAA;IAErC;;;;;;OAMG;IACH,UAAU,CAAC,aAAqB,EAAE,UAAkB;QACnD,MAAM,YAAY,GAAG,GAAG,aAAa,KAAK,UAAU,EAAE,CAAA;QAEtD,gCAAgC;QAChC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAEpC,gEAAgE;QAChE,MAAM,YAAY,GAAG,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QAClE,IAAI,YAAY;YAAE,OAAO,YAAY,CAAA;QAErC,wCAAwC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAI,CAAC,EAAE,EAAE,CAAC;YACvB,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,GAAG,aAAa,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACpF,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;gBACtC,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;gBACzD,OAAO,UAAU,CAAA;YAClB,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,YAAY;QACX,8CAA8C;QAC9C,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,EAAE,CAAC;YAC3D,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClC,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACxC,CAAC;QACF,CAAC;QAED,uCAAuC;QACvC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,CAAC,iBAAiB,CAAC,KAAK,EAAE,CAAA;QAE9B,yEAAyE;QACzE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,EAAE,CAAC;YAC3D,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACnC,CAAC;IACF,CAAC;CACD"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,EAAS;IACpC,gBAAgB;AACjB,CAAC;AAQD,MAAM,UAAU,UAAU,CAAC,KAAyB;IACnD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAEtD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AACnB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-surface/base",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/bitfocus/companion-surface-api",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=util.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/util.test.ts"],"names":[],"mappings":""}
@@ -1,213 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { StableDeviceIdGenerator } from '../util.js';
3
- describe('StableDeviceIdGenerator', () => {
4
- test('generates different IDs for different uniqueness keys', () => {
5
- const generator = new StableDeviceIdGenerator();
6
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
7
- const id2 = generator.generateId('1234:9999', '/dev/hidraw1');
8
- expect(id1).not.toBe(id2);
9
- expect(id1).toHaveLength(40); // SHA1 hex digest length
10
- expect(id2).toHaveLength(40);
11
- });
12
- test('generates different IDs for same uniqueness key with different paths', () => {
13
- const generator = new StableDeviceIdGenerator();
14
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
15
- const id2 = generator.generateId('1234:5678', '/dev/hidraw1');
16
- expect(id1).not.toBe(id2);
17
- });
18
- test('returns same ID for same device path on repeated calls', () => {
19
- const generator = new StableDeviceIdGenerator();
20
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
21
- const id2 = generator.generateId('1234:5678', '/dev/hidraw0');
22
- expect(id1).toBe(id2);
23
- });
24
- test('generates stable IDs with counter increment', () => {
25
- const generator = new StableDeviceIdGenerator();
26
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
27
- const id2 = generator.generateId('1234:5678', '/dev/hidraw1');
28
- const id3 = generator.generateId('1234:5678', '/dev/hidraw2');
29
- // All should be unique
30
- expect(new Set([id1, id2, id3]).size).toBe(3);
31
- });
32
- test('separate instances have independent state', () => {
33
- const generator1 = new StableDeviceIdGenerator();
34
- const generator2 = new StableDeviceIdGenerator();
35
- const id1 = generator1.generateId('1234:5678', '/dev/hidraw0');
36
- const id2 = generator2.generateId('1234:5678', '/dev/hidraw1');
37
- // Same inputs in different instances should produce same ID
38
- expect(id1).toBe(id2);
39
- });
40
- test('handles multiple devices with same uniqueness key in batch', () => {
41
- const generator = new StableDeviceIdGenerator();
42
- // Simulate 3 identical devices (same vendor/product) with different paths
43
- const ids = [
44
- generator.generateId('1234:5678', '/dev/hidraw0'),
45
- generator.generateId('1234:5678', '/dev/hidraw1'),
46
- generator.generateId('1234:5678', '/dev/hidraw2'),
47
- ];
48
- // All should be unique
49
- expect(new Set(ids).size).toBe(3);
50
- // Requesting same path again should return cached ID
51
- expect(generator.generateId('1234:5678', '/dev/hidraw1')).toBe(ids[1]);
52
- });
53
- test('generates valid hex SHA1 hashes', () => {
54
- const generator = new StableDeviceIdGenerator();
55
- const id = generator.generateId('1234:5678', '/dev/hidraw0');
56
- // Should be 40 character hex string
57
- expect(id).toMatch(/^[0-9a-f]{40}$/);
58
- });
59
- test('deduplicates multiple endpoints of same device', () => {
60
- const generator = new StableDeviceIdGenerator();
61
- // Simulate same device with multiple HID endpoints (same path)
62
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
63
- const id2 = generator.generateId('1234:5678', '/dev/hidraw0');
64
- const id3 = generator.generateId('1234:5678', '/dev/hidraw0');
65
- expect(id1).toBe(id2);
66
- expect(id2).toBe(id3);
67
- });
68
- test('handles empty uniqueness key', () => {
69
- const generator = new StableDeviceIdGenerator();
70
- const id1 = generator.generateId('', '/dev/hidraw0');
71
- const id2 = generator.generateId('', '/dev/hidraw1');
72
- expect(id1).not.toBe(id2);
73
- expect(id1).toHaveLength(40);
74
- });
75
- test('real-world scenario: 2 identical Stream Decks', () => {
76
- const generator = new StableDeviceIdGenerator();
77
- // Same vendor:product, different USB paths
78
- const deck1 = generator.generateId('4057:96', '/dev/hidraw0');
79
- const deck2 = generator.generateId('4057:96', '/dev/hidraw1');
80
- expect(deck1).not.toBe(deck2);
81
- expect(deck1).toHaveLength(40);
82
- expect(deck2).toHaveLength(40);
83
- // Re-enumerating same devices should return same IDs
84
- expect(generator.generateId('4057:96', '/dev/hidraw0')).toBe(deck1);
85
- expect(generator.generateId('4057:96', '/dev/hidraw1')).toBe(deck2);
86
- });
87
- test('pruneNotSeen removes devices not seen in current scan', () => {
88
- const generator = new StableDeviceIdGenerator();
89
- // First scan: 3 devices
90
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
91
- const id2 = generator.generateId('1234:5678', '/dev/hidraw1');
92
- const id3 = generator.generateId('1234:5678', '/dev/hidraw2');
93
- generator.pruneNotSeen();
94
- // Second scan: only 2 devices (hidraw1 disconnected)
95
- const id1Again = generator.generateId('1234:5678', '/dev/hidraw0');
96
- const id3Again = generator.generateId('1234:5678', '/dev/hidraw2');
97
- // IDs should be preserved for devices still present
98
- expect(id1Again).toBe(id1);
99
- expect(id3Again).toBe(id3);
100
- generator.pruneNotSeen();
101
- // Third scan: hidraw1 reconnects, should get a NEW ID (not cached)
102
- const id1Third = generator.generateId('1234:5678', '/dev/hidraw0');
103
- const id2Reconnect = generator.generateId('1234:5678', '/dev/hidraw1');
104
- const id3Third = generator.generateId('1234:5678', '/dev/hidraw2');
105
- expect(id1Third).toBe(id1);
106
- expect(id3Third).toBe(id3);
107
- // id2Reconnect can reuse the same hash since it was pruned and counter is deterministic
108
- // It gets index 1 again which produces the same hash as before
109
- expect(id2Reconnect).toBe(id2);
110
- });
111
- test('pruneNotSeen clears scan tracking sets', () => {
112
- const generator = new StableDeviceIdGenerator();
113
- // First scan
114
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
115
- const id2 = generator.generateId('1234:5678', '/dev/hidraw1');
116
- generator.pruneNotSeen();
117
- // Second scan - should be able to reuse index 0, 1 etc
118
- const id3 = generator.generateId('5678:1234', '/dev/hidraw3');
119
- const id4 = generator.generateId('5678:1234', '/dev/hidraw4');
120
- // All should be unique
121
- expect(new Set([id1, id2, id3, id4]).size).toBe(4);
122
- });
123
- test('state preserved across multiple scan cycles', () => {
124
- const generator = new StableDeviceIdGenerator();
125
- // Scan 1: 3 devices
126
- const scan1_dev0 = generator.generateId('1234:5678', '/dev/hidraw0');
127
- const scan1_dev1 = generator.generateId('1234:5678', '/dev/hidraw1');
128
- const scan1_dev2 = generator.generateId('1234:5678', '/dev/hidraw2');
129
- generator.pruneNotSeen();
130
- // Scan 2: same 3 devices
131
- const scan2_dev0 = generator.generateId('1234:5678', '/dev/hidraw0');
132
- const scan2_dev1 = generator.generateId('1234:5678', '/dev/hidraw1');
133
- const scan2_dev2 = generator.generateId('1234:5678', '/dev/hidraw2');
134
- generator.pruneNotSeen();
135
- // Scan 3: same 3 devices
136
- const scan3_dev0 = generator.generateId('1234:5678', '/dev/hidraw0');
137
- const scan3_dev1 = generator.generateId('1234:5678', '/dev/hidraw1');
138
- const scan3_dev2 = generator.generateId('1234:5678', '/dev/hidraw2');
139
- generator.pruneNotSeen();
140
- // All scans should return identical IDs for same paths
141
- expect(scan2_dev0).toBe(scan1_dev0);
142
- expect(scan2_dev1).toBe(scan1_dev1);
143
- expect(scan2_dev2).toBe(scan1_dev2);
144
- expect(scan3_dev0).toBe(scan1_dev0);
145
- expect(scan3_dev1).toBe(scan1_dev1);
146
- expect(scan3_dev2).toBe(scan1_dev2);
147
- });
148
- test('device reconnection at different path reuses counter', () => {
149
- const generator = new StableDeviceIdGenerator();
150
- // Scan 1: device at hidraw0
151
- const id1 = generator.generateId('1234:5678', '/dev/hidraw0');
152
- generator.pruneNotSeen();
153
- // Scan 2: device disconnected (not seen)
154
- generator.pruneNotSeen();
155
- // Scan 3: same device type reconnects at different path
156
- const id2 = generator.generateId('1234:5678', '/dev/hidraw5');
157
- generator.pruneNotSeen();
158
- // Gets same ID because counter resets and it's index 0 again for this uniqueness key
159
- expect(id2).toBe(id1);
160
- // But now if we add another device of same type, it gets different ID
161
- const id3 = generator.generateId('1234:5678', '/dev/hidraw6');
162
- expect(id3).not.toBe(id2);
163
- });
164
- test('handles partial device changes between scans', () => {
165
- const generator = new StableDeviceIdGenerator();
166
- // Scan 1: 4 devices
167
- const dev0_scan1 = generator.generateId('1234:5678', '/dev/hidraw0');
168
- const _dev1_scan1 = generator.generateId('1234:5678', '/dev/hidraw1');
169
- const _dev2_scan1 = generator.generateId('1234:5678', '/dev/hidraw2');
170
- const dev3_scan1 = generator.generateId('1234:5678', '/dev/hidraw3');
171
- generator.pruneNotSeen();
172
- // Scan 2: hidraw1 and hidraw2 removed, hidraw4 added
173
- const dev0_scan2 = generator.generateId('1234:5678', '/dev/hidraw0');
174
- const dev3_scan2 = generator.generateId('1234:5678', '/dev/hidraw3');
175
- const dev4_scan2 = generator.generateId('1234:5678', '/dev/hidraw4');
176
- generator.pruneNotSeen();
177
- // Persistent devices keep their IDs
178
- expect(dev0_scan2).toBe(dev0_scan1);
179
- expect(dev3_scan2).toBe(dev3_scan1);
180
- // New device gets next available counter index (reuses counter after reset)
181
- // After reset, counter 0 goes to dev0, counter 1 goes to dev3, counter 2 goes to dev4
182
- expect(dev4_scan2).not.toBe(dev0_scan1);
183
- expect(dev4_scan2).not.toBe(dev3_scan1);
184
- // All current IDs are unique
185
- expect(new Set([dev0_scan2, dev3_scan2, dev4_scan2]).size).toBe(3);
186
- });
187
- test('multiple device types tracked independently', () => {
188
- const generator = new StableDeviceIdGenerator();
189
- // Scan 1: different device types
190
- const typeA_dev0 = generator.generateId('1111:2222', '/dev/hidraw0');
191
- const typeB_dev1 = generator.generateId('3333:4444', '/dev/hidraw1');
192
- const _typeA_dev2 = generator.generateId('1111:2222', '/dev/hidraw2');
193
- generator.pruneNotSeen();
194
- // Scan 2: one device of each type removed
195
- const typeA_dev0_s2 = generator.generateId('1111:2222', '/dev/hidraw0');
196
- const typeB_dev1_s2 = generator.generateId('3333:4444', '/dev/hidraw1');
197
- generator.pruneNotSeen();
198
- // IDs should be preserved
199
- expect(typeA_dev0_s2).toBe(typeA_dev0);
200
- expect(typeB_dev1_s2).toBe(typeB_dev1);
201
- // Scan 3: add another typeA device at new path
202
- const typeA_dev0_s3 = generator.generateId('1111:2222', '/dev/hidraw0');
203
- const typeB_dev1_s3 = generator.generateId('3333:4444', '/dev/hidraw1');
204
- const typeA_dev3_s3 = generator.generateId('1111:2222', '/dev/hidraw3');
205
- expect(typeA_dev0_s3).toBe(typeA_dev0);
206
- expect(typeB_dev1_s3).toBe(typeB_dev1);
207
- // New path for typeA gets next counter index (different from dev0)
208
- expect(typeA_dev3_s3).not.toBe(typeA_dev0);
209
- // All current devices have unique IDs
210
- expect(new Set([typeA_dev0_s3, typeB_dev1_s3, typeA_dev3_s3]).size).toBe(3);
211
- });
212
- });
213
- //# sourceMappingURL=util.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"util.test.js","sourceRoot":"","sources":["../../src/__tests__/util.test.ts"],"names":[],"mappings":"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"}