homebridge-plugin-utils 1.33.0 → 1.35.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/build/tsconfig.json +6 -4
- package/dist/backpressure.d.ts +79 -0
- package/dist/backpressure.js +137 -0
- package/dist/backpressure.js.map +1 -0
- package/dist/featureoptions.d.ts +3 -3
- package/dist/featureoptions.js +122 -97
- package/dist/featureoptions.js.map +1 -1
- package/dist/ffmpeg/codecs.js +26 -20
- package/dist/ffmpeg/codecs.js.map +1 -1
- package/dist/ffmpeg/exec.d.ts +1 -1
- package/dist/ffmpeg/exec.js.map +1 -1
- package/dist/ffmpeg/fmp4.d.ts +21 -2
- package/dist/ffmpeg/fmp4.js +55 -2
- package/dist/ffmpeg/fmp4.js.map +1 -1
- package/dist/ffmpeg/options.d.ts +6 -1
- package/dist/ffmpeg/options.js +13 -14
- package/dist/ffmpeg/options.js.map +1 -1
- package/dist/ffmpeg/process.d.ts +26 -14
- package/dist/ffmpeg/process.js +60 -36
- package/dist/ffmpeg/process.js.map +1 -1
- package/dist/ffmpeg/record.d.ts +121 -122
- package/dist/ffmpeg/record.js +363 -285
- package/dist/ffmpeg/record.js.map +1 -1
- package/dist/ffmpeg/rtp.d.ts +2 -2
- package/dist/ffmpeg/rtp.js +9 -2
- package/dist/ffmpeg/rtp.js.map +1 -1
- package/dist/ffmpeg/settings.d.ts +1 -0
- package/dist/ffmpeg/settings.js +3 -0
- package/dist/ffmpeg/settings.js.map +1 -1
- package/dist/ffmpeg/stream.d.ts +1 -1
- package/dist/ffmpeg/stream.js +6 -4
- package/dist/ffmpeg/stream.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mqttclient.js +6 -5
- package/dist/mqttclient.js.map +1 -1
- package/dist/service.d.ts +1 -1
- package/dist/service.js +10 -4
- package/dist/service.js.map +1 -1
- package/dist/ui/featureoptions.js +122 -97
- package/dist/ui/featureoptions.js.map +1 -1
- package/dist/util.d.ts +18 -19
- package/dist/util.js +43 -21
- package/dist/util.js.map +1 -1
- package/package.json +7 -8
package/build/tsconfig.json
CHANGED
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
"lib": [
|
|
14
14
|
|
|
15
15
|
"DOM",
|
|
16
|
-
"
|
|
16
|
+
"ES2024",
|
|
17
|
+
"ESNext.Array",
|
|
18
|
+
"ESNext.Collection",
|
|
19
|
+
"ESNext.Iterator"
|
|
17
20
|
],
|
|
18
21
|
|
|
19
|
-
"module": "
|
|
20
|
-
"moduleResolution":"node",
|
|
22
|
+
"module": "nodenext",
|
|
21
23
|
"sourceMap": true,
|
|
22
24
|
"strict": true,
|
|
23
|
-
"target": "
|
|
25
|
+
"target": "ES2024"
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
/* When creating your own tsconfig.json, use the following as a starting point to inherit these defaults:
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backpressure-aware write queue for Node.js writable streams.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a utility class for writing data to a writable stream while respecting backpressure signals. It maintains an internal queue and serializes writes,
|
|
5
|
+
* pausing when the stream signals it isn't ready and resuming when it drains. This is particularly useful when feeding data from an event-driven source (e.g., fMP4
|
|
6
|
+
* segments from a livestream) into a process stdin that may not consume data as fast as it arrives.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import type { Nullable } from "./util.js";
|
|
11
|
+
import type { Writable } from "node:stream";
|
|
12
|
+
/**
|
|
13
|
+
* A backpressure-aware write queue that serializes writes to a writable stream, pausing when the stream signals backpressure and resuming on drain.
|
|
14
|
+
*
|
|
15
|
+
* The stream is resolved lazily via a getter function on each write, allowing the writer to be created before the stream exists and to handle stream replacement across
|
|
16
|
+
* process restarts.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* // Create a writer that feeds segments to an FFmpeg process stdin.
|
|
22
|
+
* const writer = new BackpressureWriter(() => ffmpegProcess.stdin ?? null);
|
|
23
|
+
*
|
|
24
|
+
* // Enqueue segments as they arrive from a livestream source.
|
|
25
|
+
* livestream.on("segment", (segment) => writer.write(segment));
|
|
26
|
+
*
|
|
27
|
+
* // When the session ends, close the writer to release pending data.
|
|
28
|
+
* writer.close();
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @category Utilities
|
|
32
|
+
*/
|
|
33
|
+
export declare class BackpressureWriter {
|
|
34
|
+
private drainListener;
|
|
35
|
+
private drainStream;
|
|
36
|
+
private readonly getStream;
|
|
37
|
+
private isClosed;
|
|
38
|
+
private isWriting;
|
|
39
|
+
private readonly onWrite;
|
|
40
|
+
private readonly queue;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a new backpressure-aware write queue.
|
|
43
|
+
*
|
|
44
|
+
* @param getStream - A function that returns the current writable stream, or `null` if the stream is unavailable. Evaluated on each write attempt, allowing the
|
|
45
|
+
* writer to be created before the stream exists or to track a stream that changes across process restarts. For a static stream, wrap it in an
|
|
46
|
+
* arrow function: `() => stream`.
|
|
47
|
+
* @param onWrite - Optional. A callback invoked after each segment is successfully written to the underlying stream. Useful for tracking write statistics.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* // Lazy resolution...the stream is resolved on each write.
|
|
53
|
+
* const writer = new BackpressureWriter(() => this.ffmpegProcess?.stdin ?? null, () => segmentCount++);
|
|
54
|
+
*
|
|
55
|
+
* // Static stream...wrap in an arrow function.
|
|
56
|
+
* const writer = new BackpressureWriter(() => stream);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
constructor(getStream: () => Nullable<Writable>, onWrite?: () => void);
|
|
60
|
+
/**
|
|
61
|
+
* Enqueues data to be written to the stream. If the stream is available and not under backpressure, the data is written immediately. Otherwise, it is queued and
|
|
62
|
+
* written when the stream signals it is ready via the drain event.
|
|
63
|
+
*
|
|
64
|
+
* @param data - The buffer to write to the stream.
|
|
65
|
+
*
|
|
66
|
+
* @returns Returns `true` if the data was accepted (stream is available), `false` if the stream is unavailable or the writer has been closed.
|
|
67
|
+
*/
|
|
68
|
+
write(data: Buffer): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Closes the writer, clearing any pending data and removing drain listeners. After closing, all subsequent writes are rejected. This should be called when the
|
|
71
|
+
* underlying stream is being shut down or the session is ending.
|
|
72
|
+
*/
|
|
73
|
+
close(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the number of segments currently queued and waiting to be written.
|
|
76
|
+
*/
|
|
77
|
+
get pending(): number;
|
|
78
|
+
private processQueue;
|
|
79
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* backpressure.ts: Backpressure-aware write queue for Node.js writable streams.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A backpressure-aware write queue that serializes writes to a writable stream, pausing when the stream signals backpressure and resuming on drain.
|
|
7
|
+
*
|
|
8
|
+
* The stream is resolved lazily via a getter function on each write, allowing the writer to be created before the stream exists and to handle stream replacement across
|
|
9
|
+
* process restarts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Create a writer that feeds segments to an FFmpeg process stdin.
|
|
15
|
+
* const writer = new BackpressureWriter(() => ffmpegProcess.stdin ?? null);
|
|
16
|
+
*
|
|
17
|
+
* // Enqueue segments as they arrive from a livestream source.
|
|
18
|
+
* livestream.on("segment", (segment) => writer.write(segment));
|
|
19
|
+
*
|
|
20
|
+
* // When the session ends, close the writer to release pending data.
|
|
21
|
+
* writer.close();
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @category Utilities
|
|
25
|
+
*/
|
|
26
|
+
export class BackpressureWriter {
|
|
27
|
+
drainListener;
|
|
28
|
+
drainStream;
|
|
29
|
+
getStream;
|
|
30
|
+
isClosed;
|
|
31
|
+
isWriting;
|
|
32
|
+
onWrite;
|
|
33
|
+
queue;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new backpressure-aware write queue.
|
|
36
|
+
*
|
|
37
|
+
* @param getStream - A function that returns the current writable stream, or `null` if the stream is unavailable. Evaluated on each write attempt, allowing the
|
|
38
|
+
* writer to be created before the stream exists or to track a stream that changes across process restarts. For a static stream, wrap it in an
|
|
39
|
+
* arrow function: `() => stream`.
|
|
40
|
+
* @param onWrite - Optional. A callback invoked after each segment is successfully written to the underlying stream. Useful for tracking write statistics.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* // Lazy resolution...the stream is resolved on each write.
|
|
46
|
+
* const writer = new BackpressureWriter(() => this.ffmpegProcess?.stdin ?? null, () => segmentCount++);
|
|
47
|
+
*
|
|
48
|
+
* // Static stream...wrap in an arrow function.
|
|
49
|
+
* const writer = new BackpressureWriter(() => stream);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
constructor(getStream, onWrite) {
|
|
53
|
+
this.drainListener = null;
|
|
54
|
+
this.drainStream = null;
|
|
55
|
+
this.getStream = getStream;
|
|
56
|
+
this.isClosed = false;
|
|
57
|
+
this.isWriting = false;
|
|
58
|
+
this.onWrite = onWrite ?? null;
|
|
59
|
+
this.queue = [];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Enqueues data to be written to the stream. If the stream is available and not under backpressure, the data is written immediately. Otherwise, it is queued and
|
|
63
|
+
* written when the stream signals it is ready via the drain event.
|
|
64
|
+
*
|
|
65
|
+
* @param data - The buffer to write to the stream.
|
|
66
|
+
*
|
|
67
|
+
* @returns Returns `true` if the data was accepted (stream is available), `false` if the stream is unavailable or the writer has been closed.
|
|
68
|
+
*/
|
|
69
|
+
write(data) {
|
|
70
|
+
// If the writer has been closed or the stream is unavailable or not writable, reject the write.
|
|
71
|
+
const stream = this.isClosed ? null : this.getStream();
|
|
72
|
+
if (!stream?.writable) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// Add the data to the queue and process it.
|
|
76
|
+
this.queue.push(data);
|
|
77
|
+
this.processQueue();
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Closes the writer, clearing any pending data and removing drain listeners. After closing, all subsequent writes are rejected. This should be called when the
|
|
82
|
+
* underlying stream is being shut down or the session is ending.
|
|
83
|
+
*/
|
|
84
|
+
close() {
|
|
85
|
+
this.isClosed = true;
|
|
86
|
+
this.isWriting = false;
|
|
87
|
+
this.queue.length = 0;
|
|
88
|
+
// Remove any pending drain listener.
|
|
89
|
+
if (this.drainListener && this.drainStream) {
|
|
90
|
+
this.drainStream.off("drain", this.drainListener);
|
|
91
|
+
this.drainListener = null;
|
|
92
|
+
this.drainStream = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns the number of segments currently queued and waiting to be written.
|
|
97
|
+
*/
|
|
98
|
+
get pending() {
|
|
99
|
+
return this.queue.length;
|
|
100
|
+
}
|
|
101
|
+
// Process the write queue. Dequeues segments and writes them to the stream, respecting backpressure by waiting for drain events before continuing. The synchronous
|
|
102
|
+
// success path uses a loop rather than recursion to avoid growing the call stack when processing a burst of queued segments (e.g., initial timeshift buffer flush).
|
|
103
|
+
processQueue() {
|
|
104
|
+
// If we already have a write in progress, we're done...the drain listener will resume processing.
|
|
105
|
+
if (this.isWriting) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Process as many queued segments as the stream can accept without backpressure.
|
|
109
|
+
while (this.queue.length) {
|
|
110
|
+
// Resolve the stream. If it's gone or no longer writable, we can't write.
|
|
111
|
+
const stream = this.isClosed ? null : this.getStream();
|
|
112
|
+
if (!stream?.writable) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Dequeue and write.
|
|
116
|
+
this.isWriting = true;
|
|
117
|
+
const segment = this.queue.shift();
|
|
118
|
+
if (!stream.write(segment)) {
|
|
119
|
+
// The stream signaled backpressure. Wait for drain before processing the next segment.
|
|
120
|
+
this.drainStream = stream;
|
|
121
|
+
this.drainListener = () => {
|
|
122
|
+
this.drainStream = null;
|
|
123
|
+
this.drainListener = null;
|
|
124
|
+
this.isWriting = false;
|
|
125
|
+
this.onWrite?.();
|
|
126
|
+
this.processQueue();
|
|
127
|
+
};
|
|
128
|
+
stream.once("drain", this.drainListener);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Write succeeded immediately. Notify the caller and continue to the next segment.
|
|
132
|
+
this.isWriting = false;
|
|
133
|
+
this.onWrite?.();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=backpressure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backpressure.js","sourceRoot":"","sources":["../src/backpressure.ts"],"names":[],"mappings":"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"}
|
package/dist/featureoptions.d.ts
CHANGED
|
@@ -85,6 +85,7 @@ export declare class FeatureOptions {
|
|
|
85
85
|
private _configuredOptions;
|
|
86
86
|
private _groups;
|
|
87
87
|
private _options;
|
|
88
|
+
private configLookup;
|
|
88
89
|
private defaults;
|
|
89
90
|
private valueOptions;
|
|
90
91
|
/**
|
|
@@ -256,9 +257,8 @@ export declare class FeatureOptions {
|
|
|
256
257
|
*/
|
|
257
258
|
set options(options: Record<string, FeatureOptionEntry[]>);
|
|
258
259
|
private generateDefaults;
|
|
260
|
+
private resolveScope;
|
|
259
261
|
private optionInfo;
|
|
260
|
-
private isOptionEnabled;
|
|
261
|
-
private optionRegex;
|
|
262
262
|
private parseOptionNumeric;
|
|
263
|
-
private
|
|
263
|
+
private buildConfigIndex;
|
|
264
264
|
}
|
package/dist/featureoptions.js
CHANGED
|
@@ -51,6 +51,7 @@ export class FeatureOptions {
|
|
|
51
51
|
_configuredOptions;
|
|
52
52
|
_groups;
|
|
53
53
|
_options;
|
|
54
|
+
configLookup;
|
|
54
55
|
defaults;
|
|
55
56
|
valueOptions;
|
|
56
57
|
/**
|
|
@@ -72,6 +73,7 @@ export class FeatureOptions {
|
|
|
72
73
|
this._configuredOptions = [];
|
|
73
74
|
this._groups = {};
|
|
74
75
|
this._options = {};
|
|
76
|
+
this.configLookup = new Map();
|
|
75
77
|
this.defaultReturnValue = false;
|
|
76
78
|
this.defaults = {};
|
|
77
79
|
this.valueOptions = {};
|
|
@@ -121,8 +123,7 @@ export class FeatureOptions {
|
|
|
121
123
|
* @returns Returns true if the option has been explicitly configured, false otherwise.
|
|
122
124
|
*/
|
|
123
125
|
exists(option, id) {
|
|
124
|
-
|
|
125
|
-
return this.configuredOptions.some(x => regex.test(x));
|
|
126
|
+
return this.configLookup.has(option.toLowerCase() + (id ? "." + id.toLowerCase() : ""));
|
|
126
127
|
}
|
|
127
128
|
/**
|
|
128
129
|
* Return a fully formed feature option string.
|
|
@@ -238,53 +239,22 @@ export class FeatureOptions {
|
|
|
238
239
|
if (!this.isValue(option)) {
|
|
239
240
|
return null;
|
|
240
241
|
}
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
for (const entry of this.configuredOptions) {
|
|
247
|
-
const regexMatch = regex.exec(entry);
|
|
248
|
-
if (regexMatch) {
|
|
249
|
-
// If the option is enabled, return the value. Otherwise, we have nothing.
|
|
250
|
-
return (regexMatch[1].toLowerCase() === "enable") ? regexMatch[2] : null;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return undefined;
|
|
254
|
-
};
|
|
255
|
-
// Check to see if we have a device-level value first.
|
|
256
|
-
if (device) {
|
|
257
|
-
const value = getValue(option, device);
|
|
258
|
-
// The option's been explicitly disabled.
|
|
259
|
-
if (value === null) {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
if (value) {
|
|
263
|
-
return value;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
// Now check to see if we have an controller-level value.
|
|
267
|
-
if (controller) {
|
|
268
|
-
const value = getValue(option, controller);
|
|
269
|
-
// The option's been explicitly disabled.
|
|
270
|
-
if (value === null) {
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
if (value) {
|
|
274
|
-
return value;
|
|
275
|
-
}
|
|
242
|
+
// Resolve the option through the scope hierarchy in a single traversal. This gives us the scope, enabled state, and raw value in one pass.
|
|
243
|
+
const resolved = this.resolveScope(option, device, controller);
|
|
244
|
+
// If the option has been explicitly disabled at any scope, or wasn't configured and its default is disabled, there's no value.
|
|
245
|
+
if (!resolved.enabled) {
|
|
246
|
+
return null;
|
|
276
247
|
}
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return value;
|
|
248
|
+
// If we found an explicit value in the index, return it.
|
|
249
|
+
if (resolved.optionValue) {
|
|
250
|
+
return resolved.optionValue;
|
|
281
251
|
}
|
|
282
|
-
// The option'
|
|
283
|
-
if (
|
|
284
|
-
return null;
|
|
252
|
+
// The option is enabled but has no explicit value. If it wasn't configured at any scope (scope is "none"), fall back to the registered default value.
|
|
253
|
+
if (resolved.scope === "none") {
|
|
254
|
+
return this.valueOptions[option.toLowerCase()]?.toString() ?? null;
|
|
285
255
|
}
|
|
286
|
-
//
|
|
287
|
-
return
|
|
256
|
+
// The option is enabled at an explicit scope but no value was provided...return undefined to indicate "enabled, no value."
|
|
257
|
+
return undefined;
|
|
288
258
|
}
|
|
289
259
|
/**
|
|
290
260
|
* Return the list of available feature option categories.
|
|
@@ -301,6 +271,8 @@ export class FeatureOptions {
|
|
|
301
271
|
*/
|
|
302
272
|
set categories(category) {
|
|
303
273
|
this._categories = category;
|
|
274
|
+
// Regenerate defaults and the lookup index.
|
|
275
|
+
this.generateDefaults();
|
|
304
276
|
}
|
|
305
277
|
/**
|
|
306
278
|
* Return the list of currently configured feature options.
|
|
@@ -318,6 +290,8 @@ export class FeatureOptions {
|
|
|
318
290
|
set configuredOptions(options) {
|
|
319
291
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
320
292
|
this._configuredOptions = options ?? [];
|
|
293
|
+
// Regenerate defaults and the lookup index.
|
|
294
|
+
this.generateDefaults();
|
|
321
295
|
}
|
|
322
296
|
/**
|
|
323
297
|
* Return the list of available feature option groups.
|
|
@@ -343,10 +317,11 @@ export class FeatureOptions {
|
|
|
343
317
|
set options(options) {
|
|
344
318
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
345
319
|
this._options = options ?? {};
|
|
346
|
-
// Regenerate
|
|
320
|
+
// Regenerate defaults and the lookup index.
|
|
347
321
|
this.generateDefaults();
|
|
348
322
|
}
|
|
349
|
-
//
|
|
323
|
+
// Rebuild the defaults, groups, value options, and lookup index from the current categories, options, and configured options. All three property setters call this so
|
|
324
|
+
// that state is always consistent regardless of which setter is called or in what order.
|
|
350
325
|
generateDefaults() {
|
|
351
326
|
this.defaults = {};
|
|
352
327
|
this._groups = {};
|
|
@@ -375,58 +350,47 @@ export class FeatureOptions {
|
|
|
375
350
|
}
|
|
376
351
|
}
|
|
377
352
|
}
|
|
353
|
+
// Rebuild the lookup index now that we know which options are value-centric.
|
|
354
|
+
this.buildConfigIndex();
|
|
378
355
|
}
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
356
|
+
// Resolves a feature option through the scope hierarchy in a single traversal. Returns the scope where the option was found, whether it's enabled, and the raw string
|
|
357
|
+
// value for value-centric options. This is the core resolution method that both test()/scope() and value() build on, eliminating the need for separate traversals.
|
|
358
|
+
//
|
|
359
|
+
// There are a couple of ways to enable and disable options. The rules of the road are:
|
|
360
|
+
//
|
|
361
|
+
// 1. Explicitly disabling or enabling an option on the controller propagates to all the devices that are managed by that controller.
|
|
362
|
+
//
|
|
363
|
+
// 2. Explicitly disabling or enabling an option on a device always overrides the above. This means that it's possible to disable an option for a controller, and all
|
|
364
|
+
// the devices that are managed by it, and then override that behavior on a single device that it's managing.
|
|
365
|
+
resolveScope(option, device, controller) {
|
|
366
|
+
const normalizedOption = option.toLowerCase();
|
|
367
|
+
let entry;
|
|
388
368
|
// Check to see if we have a device-level option first.
|
|
389
|
-
if (device
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
return {
|
|
369
|
+
if (device) {
|
|
370
|
+
entry = this.configLookup.get(normalizedOption + "." + device.toLowerCase());
|
|
371
|
+
if (entry) {
|
|
372
|
+
return { enabled: entry.enabled, optionValue: entry.value, scope: "device" };
|
|
393
373
|
}
|
|
394
374
|
}
|
|
395
|
-
// Now check to see if we have
|
|
396
|
-
if (controller
|
|
397
|
-
|
|
398
|
-
if (
|
|
399
|
-
return {
|
|
375
|
+
// Now check to see if we have a controller-level option.
|
|
376
|
+
if (controller) {
|
|
377
|
+
entry = this.configLookup.get(normalizedOption + "." + controller.toLowerCase());
|
|
378
|
+
if (entry) {
|
|
379
|
+
return { enabled: entry.enabled, optionValue: entry.value, scope: "controller" };
|
|
400
380
|
}
|
|
401
381
|
}
|
|
402
382
|
// Finally, we check for a global-level value.
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
return { scope: "global", value: value };
|
|
407
|
-
}
|
|
383
|
+
entry = this.configLookup.get(normalizedOption);
|
|
384
|
+
if (entry) {
|
|
385
|
+
return { enabled: entry.enabled, optionValue: entry.value, scope: "global" };
|
|
408
386
|
}
|
|
409
387
|
// The option hasn't been set at any scope, return our default value.
|
|
410
|
-
return {
|
|
388
|
+
return { enabled: this.defaultValue(option), scope: "none" };
|
|
411
389
|
}
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// Get the option value, if we have one.
|
|
417
|
-
for (const entry of this.configuredOptions) {
|
|
418
|
-
const regexMatch = regex.exec(entry);
|
|
419
|
-
if (regexMatch) {
|
|
420
|
-
return regexMatch[1].toLowerCase() === "enable";
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return undefined;
|
|
424
|
-
}
|
|
425
|
-
// Regular expression test for feature options.
|
|
426
|
-
optionRegex(option, id) {
|
|
427
|
-
// This regular expression is a bit more intricate than you might think it should be due to the need to ensure we capture values at the very end of the option. We
|
|
428
|
-
// also need to escape out our option to ensure we have no inadvertent issues in matching the regular expression.
|
|
429
|
-
return new RegExp("^(Enable|Disable)\\." + option.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + (!id ? "" : "\\." + id) + "$", "gi");
|
|
390
|
+
// Thin wrapper over resolveScope() that returns the OptionInfoEntry shape expected by test() and scope().
|
|
391
|
+
optionInfo(option, device, controller) {
|
|
392
|
+
const resolved = this.resolveScope(option, device, controller);
|
|
393
|
+
return { scope: resolved.scope, value: resolved.enabled };
|
|
430
394
|
}
|
|
431
395
|
// Utility function to parse and return a numeric configuration parameter.
|
|
432
396
|
parseOptionNumeric(option, convert) {
|
|
@@ -443,13 +407,74 @@ export class FeatureOptions {
|
|
|
443
407
|
// Return the value.
|
|
444
408
|
return convertedValue;
|
|
445
409
|
}
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
410
|
+
// Build a lookup index from the configured option strings. Each entry is keyed by its normalized lookup path (option name, or option name + scope id) and stores
|
|
411
|
+
// whether the option is enabled along with the extracted value for value-centric options. The index is built once when configured options or option definitions change,
|
|
412
|
+
// and all subsequent lookups are O(1).
|
|
413
|
+
buildConfigIndex() {
|
|
414
|
+
this.configLookup = new Map();
|
|
415
|
+
// Collect known value option names, sorted longest first for greedy prefix matching. This ensures that when option names overlap (e.g., a category "audio" and an
|
|
416
|
+
// option "audio.volume"), the more specific name matches first.
|
|
417
|
+
const valueOptionNames = Object.keys(this.valueOptions).sort((a, b) => b.length - a.length);
|
|
418
|
+
for (const rawEntry of this._configuredOptions) {
|
|
419
|
+
// Parse the action prefix (Enable or Disable).
|
|
420
|
+
const dotIndex = rawEntry.indexOf(".");
|
|
421
|
+
if (dotIndex === -1) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
const action = rawEntry.slice(0, dotIndex).toLowerCase();
|
|
425
|
+
if ((action !== "enable") && (action !== "disable")) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const enabled = action === "enable";
|
|
429
|
+
const tailOriginal = rawEntry.slice(dotIndex + 1);
|
|
430
|
+
const tail = tailOriginal.toLowerCase();
|
|
431
|
+
// Register the exact tail as a lookup key. First-write-wins...if the same option appears multiple times, the earliest entry in the array takes precedence.
|
|
432
|
+
if (!this.configLookup.has(tail)) {
|
|
433
|
+
this.configLookup.set(tail, { enabled });
|
|
434
|
+
}
|
|
435
|
+
// For Enable entries on value-centric options, extract the trailing value segment and register under the base key (option or option.id) so that value lookups
|
|
436
|
+
// resolve in O(1) instead of requiring regex matching and array scanning.
|
|
437
|
+
if (!enabled) {
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
for (const optName of valueOptionNames) {
|
|
441
|
+
if (!tail.startsWith(optName)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const remainder = tail.slice(optName.length);
|
|
445
|
+
// Exact match on the option name with no trailing segments...there's no value to extract.
|
|
446
|
+
if (!remainder.length) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
// The next character must be a dot separator. If not, this option name is merely a prefix of a longer unrelated token.
|
|
450
|
+
if (!remainder.startsWith(".")) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const extra = remainder.slice(1);
|
|
454
|
+
const extraOriginal = tailOriginal.slice(optName.length + 1);
|
|
455
|
+
const separatorIndex = extra.indexOf(".");
|
|
456
|
+
if (separatorIndex === -1) {
|
|
457
|
+
// Single trailing segment after the option name. At global scope this is the value; at scoped scope it's the id. Register under the option name as the
|
|
458
|
+
// base key so that global value lookups find it.
|
|
459
|
+
if (!this.configLookup.has(optName)) {
|
|
460
|
+
this.configLookup.set(optName, { enabled: true, value: extraOriginal });
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// Two trailing segments: the first is the scope id and the second is the value.
|
|
465
|
+
const idLower = extra.slice(0, separatorIndex);
|
|
466
|
+
const valueOriginal = extraOriginal.slice(separatorIndex + 1);
|
|
467
|
+
// Only register if the value portion is a single segment (no additional dots).
|
|
468
|
+
if (!valueOriginal.includes(".")) {
|
|
469
|
+
const baseKey = optName + "." + idLower;
|
|
470
|
+
if (!this.configLookup.has(baseKey)) {
|
|
471
|
+
this.configLookup.set(baseKey, { enabled: true, value: valueOriginal });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
453
478
|
}
|
|
454
479
|
}
|
|
455
480
|
//# sourceMappingURL=featureoptions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"featureoptions.js","sourceRoot":"","sources":["../src/featureoptions.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"featureoptions.js","sourceRoot":"","sources":["../src/featureoptions.ts"],"names":[],"mappings":"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"}
|