homebridge-http-advanced-platform 2.0.0-alpha.6

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.
Files changed (56) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +175 -0
  3. package/config.schema.json +449 -0
  4. package/dist/accessory.d.ts +27 -0
  5. package/dist/accessory.js +164 -0
  6. package/dist/accessory.js.map +1 -0
  7. package/dist/actions.d.ts +8 -0
  8. package/dist/actions.js +47 -0
  9. package/dist/actions.js.map +1 -0
  10. package/dist/compatibility.d.ts +3 -0
  11. package/dist/compatibility.js +26 -0
  12. package/dist/compatibility.js.map +1 -0
  13. package/dist/config.d.ts +2 -0
  14. package/dist/config.js +81 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/coordinator.d.ts +51 -0
  17. package/dist/coordinator.js +145 -0
  18. package/dist/coordinator.js.map +1 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +8 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/mappers.d.ts +3 -0
  23. package/dist/mappers.js +116 -0
  24. package/dist/mappers.js.map +1 -0
  25. package/dist/metadata.d.ts +5 -0
  26. package/dist/metadata.js +8 -0
  27. package/dist/metadata.js.map +1 -0
  28. package/dist/platform.d.ts +18 -0
  29. package/dist/platform.js +129 -0
  30. package/dist/platform.js.map +1 -0
  31. package/dist/runtime.d.ts +54 -0
  32. package/dist/runtime.js +376 -0
  33. package/dist/runtime.js.map +1 -0
  34. package/dist/settings.d.ts +21 -0
  35. package/dist/settings.js +51 -0
  36. package/dist/settings.js.map +1 -0
  37. package/dist/transport.d.ts +25 -0
  38. package/dist/transport.js +139 -0
  39. package/dist/transport.js.map +1 -0
  40. package/dist/types.d.ts +106 -0
  41. package/dist/types.js +10 -0
  42. package/dist/types.js.map +1 -0
  43. package/dist/ui-config.d.ts +27 -0
  44. package/dist/ui-config.js +208 -0
  45. package/dist/ui-config.js.map +1 -0
  46. package/docs/alpha-release-notes.md +19 -0
  47. package/docs/legacy-reference.md +678 -0
  48. package/docs/migration.md +43 -0
  49. package/docs/modernization.md +211 -0
  50. package/docs/performance.md +37 -0
  51. package/docs/service-support.md +47 -0
  52. package/homebridge-ui/public/index.html +74 -0
  53. package/homebridge-ui/public/settings.js +132 -0
  54. package/homebridge-ui/server.js +17 -0
  55. package/package.json +83 -0
  56. package/sample-config.json +39 -0
@@ -0,0 +1,47 @@
1
+ import { mapResponse, mapValue } from './mappers.js';
2
+ import { interpolateLegacy } from './compatibility.js';
3
+ import { ActionError } from './types.js';
4
+ export class Actions {
5
+ transport;
6
+ constructor(transport) {
7
+ this.transport = transport;
8
+ }
9
+ async get(action, config, owner, state, seen = new Set(), onFailure) {
10
+ if (seen.has(action) || seen.size >= 32)
11
+ throw new ActionError('inconclusive');
12
+ seen.add(action);
13
+ let body;
14
+ try {
15
+ body = (await this.transport.request(action, config, owner)).body;
16
+ }
17
+ catch (error) {
18
+ if (error instanceof ActionError && !['aborted', 'deferred'].includes(error.category) && action.resultOnError != null) {
19
+ onFailure?.(error.category, error.retryAfter);
20
+ return action.resultOnError;
21
+ }
22
+ throw error;
23
+ }
24
+ const value = mapResponse(action.mappers, body, state, action.requireResponseMatch);
25
+ if (value === 'inconclusive') {
26
+ if (action.inconclusive)
27
+ return this.get(action.inconclusive, config, owner, state, seen, onFailure);
28
+ // published 1.3.0 returned this sentinel; callers validate it for the HAP format
29
+ return value;
30
+ }
31
+ return value;
32
+ }
33
+ async set(action, config, owner, state, value) {
34
+ const mapped = mapValue(action.mappers, value, state);
35
+ let url;
36
+ let body;
37
+ try {
38
+ url = interpolateLegacy(action.url, value, mapped, state);
39
+ body = action.body ? interpolateLegacy(action.body, value, mapped, state) : '';
40
+ }
41
+ catch {
42
+ throw new ActionError('mapper');
43
+ }
44
+ await this.transport.request({ ...action, url, body }, config, owner, true);
45
+ }
46
+ }
47
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAwE,MAAM,YAAY,CAAC;AAG/G,MAAM,OAAO,OAAO;IACG;IAArB,YAAqB,SAAoB;QAApB,cAAS,GAAT,SAAS,CAAW;IAAG,CAAC;IAC7C,KAAK,CAAC,GAAG,CAAC,MAAoB,EAAE,MAAoB,EAAE,KAAa,EAAE,KAAY,EAAE,OAAO,IAAI,GAAG,EAAgB,EAAE,SAAkE;QACnL,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE;YAAE,MAAM,IAAI,WAAW,CAAC,cAAc,CAAC,CAAC;QAC/E,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YAAC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;QAAC,CAAC;QAC1E,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,KAAK,YAAY,WAAW,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;gBACtH,SAAS,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC9C,OAAO,MAAM,CAAC,aAAa,CAAC;YAC9B,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QACD,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,oBAAoB,CAAC,CAAC;QACpF,IAAI,KAAK,KAAK,cAAc,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,YAAY;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;YACrG,iFAAiF;YACjF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAoB,EAAE,MAAoB,EAAE,KAAa,EAAE,KAAY,EAAE,KAAc;QAC/F,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,GAAW,CAAC;QAAC,IAAI,IAAY,CAAC;QAClC,IAAI,CAAC;YACH,GAAG,GAAG,iBAAiB,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YAC1D,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjF,CAAC;QAAC,MAAM,CAAC;YAAC,MAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC;QAAC,CAAC;QAC5C,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IAC9E,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import type { State } from './types.js';
2
+ export declare function evaluateLegacy(expression: string, value: unknown, state: State): unknown;
3
+ export declare function interpolateLegacy(template: string, value: unknown, mappedValue: unknown, state: State): string;
@@ -0,0 +1,26 @@
1
+ import { createRequire } from 'node:module';
2
+ // trusted legacy configuration is executable JavaScript, not a security sandbox
3
+ const require = createRequire(new URL('../package.json', import.meta.url));
4
+ export function evaluateLegacy(expression, value, state) {
5
+ const self = { exp: expression, state };
6
+ // direct eval deliberately retains value, self.state, this.state and CommonJS require
7
+ return function () {
8
+ void value;
9
+ void state;
10
+ void require;
11
+ return eval(self.exp);
12
+ }.call(self);
13
+ }
14
+ export function interpolateLegacy(template, value, mappedValue, state) {
15
+ // ordinary placeholders do not need JavaScript evaluation
16
+ let result = template;
17
+ if (template.includes('${') || template.includes('`') || template.includes('\\')) {
18
+ void state;
19
+ void value;
20
+ void require;
21
+ result = eval('`' + template + '`');
22
+ }
23
+ // string replacement preserves legacy replacement-token semantics as well
24
+ return result.replace(/{value}/gi, String(mappedValue));
25
+ }
26
+ //# sourceMappingURL=compatibility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,gFAAgF;AAChF,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3E,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,KAAc,EAAE,KAAY;IAC7E,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;IACxC,sFAAsF;IACtF,OAAO;QACL,KAAK,KAAK,CAAC;QAAC,KAAK,KAAK,CAAC;QAAC,KAAK,OAAO,CAAC;QACrC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,KAAc,EAAE,WAAoB,EAAE,KAAY;IACpG,0DAA0D;IAC1D,IAAI,MAAM,GAAG,QAAQ,CAAC;IACtB,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACjF,KAAK,KAAK,CAAC;QAAC,KAAK,KAAK,CAAC;QAAC,KAAK,OAAO,CAAC;QACrC,MAAM,GAAG,IAAI,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,CAAW,CAAC;IAChD,CAAC;IACD,0EAA0E;IAC1E,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { type DeviceConfig } from './types.js';
2
+ export declare function validateDevice(config: DeviceConfig): void;
package/dist/config.js ADDED
@@ -0,0 +1,81 @@
1
+ import { ActionError } from './types.js';
2
+ export function validateDevice(config) {
3
+ if (!config || typeof config.name !== 'string' || !config.name.trim() || typeof config.service !== 'string') {
4
+ throw new ActionError('config');
5
+ }
6
+ for (const value of [config.forceRefreshDelay, config.setterDelay, config.uriCallsDelay, config.writeConfirmationTimeout]) {
7
+ if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value) || value < 0))
8
+ throw new ActionError('config');
9
+ }
10
+ for (const value of Object.values(config.refresh ?? {})) {
11
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
12
+ throw new ActionError('config');
13
+ }
14
+ if (config.optionCharacteristic && (!Array.isArray(config.optionCharacteristic) || config.optionCharacteristic.some(v => typeof v !== 'string'))) {
15
+ throw new ActionError('config');
16
+ }
17
+ if (config.urls && (typeof config.urls !== 'object' || Array.isArray(config.urls)))
18
+ throw new ActionError('config');
19
+ for (const action of Object.values(config.urls ?? {}))
20
+ validateAction(action, new Set());
21
+ }
22
+ function validateAction(action, seen) {
23
+ if (!action || seen.has(action) || seen.size >= 32 || typeof action.url !== 'string' || !/^https?:\/\//i.test(action.url)) {
24
+ throw new ActionError('config');
25
+ }
26
+ seen.add(action);
27
+ if (!/^[A-Za-z]+$/.test(action.httpMethod || 'GET'))
28
+ throw new ActionError('config');
29
+ if (action.body !== undefined && typeof action.body !== 'string')
30
+ throw new ActionError('config');
31
+ if (action.timeout !== undefined && (!Number.isFinite(action.timeout) || action.timeout <= 0))
32
+ throw new ActionError('config');
33
+ if (action.requireResponseMatch !== undefined && typeof action.requireResponseMatch !== 'boolean')
34
+ throw new ActionError('config');
35
+ if (action.responsePattern !== undefined) {
36
+ if (typeof action.responsePattern !== 'string')
37
+ throw new ActionError('config');
38
+ try {
39
+ new RegExp(action.responsePattern);
40
+ }
41
+ catch {
42
+ throw new ActionError('config');
43
+ }
44
+ }
45
+ if (action.mappers !== undefined && !Array.isArray(action.mappers))
46
+ throw new ActionError('config');
47
+ for (const mapper of action.mappers ?? []) {
48
+ if (!mapper || !mapper.parameters)
49
+ throw new ActionError('config');
50
+ switch (mapper.type) {
51
+ case 'regex':
52
+ try {
53
+ new RegExp(mapper.parameters.regexp);
54
+ }
55
+ catch {
56
+ throw new ActionError('config');
57
+ }
58
+ break;
59
+ case 'static':
60
+ if (!mapper.parameters.mapping || typeof mapper.parameters.mapping !== 'object')
61
+ throw new ActionError('config');
62
+ break;
63
+ case 'eval':
64
+ if (typeof mapper.parameters.expression !== 'string')
65
+ throw new ActionError('config');
66
+ break;
67
+ case 'xpath':
68
+ if (typeof mapper.parameters.xpath !== 'string')
69
+ throw new ActionError('config');
70
+ break;
71
+ case 'jpath':
72
+ if (typeof mapper.parameters.jpath !== 'string')
73
+ throw new ActionError('config');
74
+ break;
75
+ default: throw new ActionError('config');
76
+ }
77
+ }
78
+ if (action.inconclusive)
79
+ validateAction(action.inconclusive, seen);
80
+ }
81
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { type CoordinatorConfig } from './types.js';
2
+ interface Job {
3
+ origin: string;
4
+ owner: string;
5
+ spacing: number;
6
+ priority: boolean;
7
+ run: (signal: AbortSignal) => Promise<unknown>;
8
+ resolve: (value: unknown) => void;
9
+ reject: (error: unknown) => void;
10
+ deadline?: number;
11
+ timer?: NodeJS.Timeout;
12
+ }
13
+ export declare class Coordinator {
14
+ limits: {
15
+ concurrency: number;
16
+ perOrigin: number;
17
+ maxQueue: number;
18
+ };
19
+ readonly queue: Job[];
20
+ readonly active: Set<AbortController>;
21
+ readonly origins: Map<string, number>;
22
+ readonly originStats: Map<string, {
23
+ started: number;
24
+ completed: number;
25
+ failed: number;
26
+ maxInFlight: number;
27
+ }>;
28
+ private readonly nextStart;
29
+ private readonly recovering;
30
+ private timer?;
31
+ private lastOrigin;
32
+ private stopped;
33
+ readonly stats: {
34
+ completed: number;
35
+ failed: number;
36
+ highWater: number;
37
+ maxInFlight: number;
38
+ durations: number[];
39
+ started: number;
40
+ };
41
+ constructor(config?: CoordinatorConfig);
42
+ configure(config: CoordinatorConfig): void;
43
+ get available(): boolean;
44
+ pauseOrigin(origin: string, until: number): void;
45
+ backgroundReady(origin: string, now?: number): boolean;
46
+ resumeOrigin(origin: string): void;
47
+ submit<T>(origin: string, owner: string, spacing: number, priority: boolean, run: (signal: AbortSignal) => Promise<T>, deadline?: number): Promise<T>;
48
+ private pump;
49
+ shutdown(): void;
50
+ }
51
+ export {};
@@ -0,0 +1,145 @@
1
+ import { ActionError } from './types.js';
2
+ export class Coordinator {
3
+ limits = { concurrency: 4, perOrigin: 2, maxQueue: 256 };
4
+ queue = [];
5
+ active = new Set();
6
+ origins = new Map();
7
+ originStats = new Map();
8
+ nextStart = new Map();
9
+ recovering = new Map();
10
+ timer;
11
+ lastOrigin = '';
12
+ stopped = false;
13
+ stats = { completed: 0, failed: 0, highWater: 0, maxInFlight: 0, durations: [], started: 0 };
14
+ constructor(config = {}) { this.configure(config); }
15
+ configure(config) {
16
+ for (const [key, value] of Object.entries(config)) {
17
+ if (!Object.hasOwn(this.limits, key) || !Number.isInteger(value) || value < 1)
18
+ throw new ActionError('config');
19
+ }
20
+ Object.assign(this.limits, config);
21
+ }
22
+ get available() { return !this.stopped && this.queue.length < this.limits.maxQueue; }
23
+ pauseOrigin(origin, until) {
24
+ this.recovering.set(origin, until);
25
+ // parked reads belong in the cache scheduler, not in slots needed by healthy endpoints
26
+ for (const job of this.queue.slice()) {
27
+ if (job.origin !== origin || job.priority)
28
+ continue;
29
+ this.queue.splice(this.queue.indexOf(job), 1);
30
+ clearTimeout(job.timer);
31
+ job.reject(new ActionError('deferred', Math.max(0, until - Date.now())));
32
+ }
33
+ }
34
+ backgroundReady(origin, now = Date.now()) {
35
+ const probeAt = this.recovering.get(origin);
36
+ return probeAt === undefined || (now >= probeAt && (this.origins.get(origin) ?? 0) === 0);
37
+ }
38
+ resumeOrigin(origin) {
39
+ this.recovering.delete(origin);
40
+ }
41
+ submit(origin, owner, spacing, priority, run, deadline) {
42
+ if (this.stopped)
43
+ return Promise.reject(new ActionError('aborted'));
44
+ if (!priority && !this.backgroundReady(origin))
45
+ return Promise.reject(new ActionError('deferred', Math.max(0, (this.recovering.get(origin) ?? 0) - Date.now())));
46
+ if (!this.available)
47
+ return Promise.reject(new ActionError('queue'));
48
+ return new Promise((resolve, reject) => {
49
+ const job = { origin, owner, spacing, priority, run, resolve: value => resolve(value), reject, deadline };
50
+ this.queue.push(job);
51
+ if (deadline !== undefined) {
52
+ const expire = () => {
53
+ if (!this.queue.includes(job))
54
+ return;
55
+ const remaining = deadline - Date.now();
56
+ if (remaining > 0) {
57
+ job.timer = setTimeout(expire, Math.min(remaining, 2147483647));
58
+ return;
59
+ }
60
+ this.queue.splice(this.queue.indexOf(job), 1);
61
+ job.reject(new ActionError('timeout'));
62
+ this.pump();
63
+ };
64
+ job.timer = setTimeout(expire, Math.max(0, Math.min(deadline - Date.now(), 2147483647)));
65
+ }
66
+ this.stats.highWater = Math.max(this.stats.highWater, this.queue.length);
67
+ this.pump();
68
+ });
69
+ }
70
+ pump() {
71
+ if (this.stopped)
72
+ return;
73
+ if (this.timer) {
74
+ clearTimeout(this.timer);
75
+ this.timer = undefined;
76
+ }
77
+ while (this.active.size < this.limits.concurrency) {
78
+ const now = Date.now();
79
+ // expired commands must never run later, even if admission beats their timer callback
80
+ for (const job of this.queue.slice()) {
81
+ if (job.deadline !== undefined && now >= job.deadline) {
82
+ this.queue.splice(this.queue.indexOf(job), 1);
83
+ clearTimeout(job.timer);
84
+ job.reject(new ActionError('timeout'));
85
+ }
86
+ }
87
+ const eligible = this.queue.filter(job => {
88
+ const active = this.origins.get(job.origin) ?? 0;
89
+ // writes retain priority; a recovering origin gets only one background probe at a time
90
+ return active < this.limits.perOrigin && (this.nextStart.get(job.owner) ?? 0) <= now
91
+ && (job.priority || this.backgroundReady(job.origin, now));
92
+ });
93
+ // prefer control writes, then rotate eligible origins to prevent a slow fleet starving others
94
+ const candidates = eligible.some(job => job.priority) ? eligible.filter(job => job.priority) : eligible;
95
+ const job = candidates.find(job => job.origin !== this.lastOrigin) ?? candidates[0];
96
+ if (!job)
97
+ break;
98
+ this.queue.splice(this.queue.indexOf(job), 1);
99
+ clearTimeout(job.timer);
100
+ this.lastOrigin = job.origin;
101
+ this.nextStart.set(job.owner, now + job.spacing);
102
+ this.origins.set(job.origin, (this.origins.get(job.origin) ?? 0) + 1);
103
+ const originStats = this.originStats.get(job.origin) ?? { started: 0, completed: 0, failed: 0, maxInFlight: 0 };
104
+ this.originStats.set(job.origin, originStats);
105
+ originStats.started++;
106
+ originStats.maxInFlight = Math.max(originStats.maxInFlight, this.origins.get(job.origin));
107
+ const controller = new AbortController();
108
+ this.active.add(controller);
109
+ this.stats.started++;
110
+ this.stats.maxInFlight = Math.max(this.stats.maxInFlight, this.active.size);
111
+ Promise.resolve().then(() => {
112
+ // admission can precede execution during event-loop load; pace from actual execution
113
+ this.nextStart.set(job.owner, Date.now() + job.spacing);
114
+ return job.run(controller.signal);
115
+ }).then(value => {
116
+ this.stats.completed++;
117
+ originStats.completed++;
118
+ job.resolve(value);
119
+ }, error => { this.stats.failed++; originStats.failed++; job.reject(error); }).finally(() => {
120
+ this.active.delete(controller);
121
+ this.origins.set(job.origin, (this.origins.get(job.origin) ?? 1) - 1);
122
+ this.stats.durations.push(Date.now() - now);
123
+ if (this.stats.durations.length > 2048)
124
+ this.stats.durations.shift();
125
+ // allow response mapping and recovery feedback to settle before admitting the next job
126
+ setImmediate(() => this.pump());
127
+ });
128
+ }
129
+ if (this.queue.length && this.active.size < this.limits.concurrency) {
130
+ this.timer = setTimeout(() => this.pump(), 20);
131
+ this.timer.unref();
132
+ }
133
+ }
134
+ shutdown() {
135
+ this.stopped = true;
136
+ clearTimeout(this.timer);
137
+ for (const job of this.queue.splice(0)) {
138
+ clearTimeout(job.timer);
139
+ job.reject(new ActionError('aborted'));
140
+ }
141
+ for (const controller of this.active)
142
+ controller.abort();
143
+ }
144
+ }
145
+ //# sourceMappingURL=coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coordinator.js","sourceRoot":"","sources":["../src/coordinator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { API } from 'homebridge';
2
+ export default function initialize(api: API): void;
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ import { LegacyAccessory } from './accessory.js';
2
+ import { HTTPPlatform } from './platform.js';
3
+ import { accessoryName, platformName, pluginName } from './metadata.js';
4
+ export default function initialize(api) {
5
+ api.registerAccessory(pluginName, accessoryName, LegacyAccessory);
6
+ api.registerPlatform(pluginName, platformName, HTTPPlatform);
7
+ }
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,GAAQ;IACzC,GAAG,CAAC,iBAAiB,CAAC,UAAU,EAAE,aAAa,EAAE,eAAe,CAAC,CAAC;IAClE,GAAG,CAAC,gBAAgB,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type MapperConfig, type State } from './types.js';
2
+ export declare function mapValue(mappers: MapperConfig[] | undefined, input: unknown, state?: State): unknown;
3
+ export declare function mapResponse(mappers: MapperConfig[] | undefined, input: unknown, state?: State, requireMatch?: boolean): unknown;
@@ -0,0 +1,116 @@
1
+ import { DOMParser } from '@xmldom/xmldom';
2
+ import xpath from 'xpath';
3
+ import { JSONPath } from 'jsonpath-plus';
4
+ import { evaluateLegacy } from './compatibility.js';
5
+ import { ActionError } from './types.js';
6
+ export function mapValue(mappers = [], input, state = {}) {
7
+ return mapPipeline(mappers, input, state, false);
8
+ }
9
+ export function mapResponse(mappers = [], input, state = {}, requireMatch = false) {
10
+ return mapPipeline(mappers, input, state, true, requireMatch);
11
+ }
12
+ function mapPipeline(mappers, input, state, response, requireMatch = false) {
13
+ let unmatched = false;
14
+ let invalidDocument = false;
15
+ try {
16
+ const result = mappers.reduce((value, mapper) => {
17
+ const p = mapper.parameters;
18
+ switch (mapper.type) {
19
+ case 'static': {
20
+ const mapping = mapper.parameters.mapping;
21
+ // preserve 1.3.0 falsey mapping pass-through; changing it requires an opt-in
22
+ if (Object.hasOwn(mapping, String(value))) {
23
+ unmatched = false;
24
+ invalidDocument = false;
25
+ return mapping[String(value)] || value;
26
+ }
27
+ return value;
28
+ }
29
+ case 'regex': {
30
+ const { regexp, capture } = mapper.parameters;
31
+ const matches = new RegExp(regexp).exec(String(value));
32
+ const index = String(capture || '1');
33
+ if (matches && index in matches) {
34
+ unmatched = false;
35
+ invalidDocument = false;
36
+ return matches[Number(index)];
37
+ }
38
+ unmatched = true;
39
+ return value;
40
+ }
41
+ case 'xpath': {
42
+ const { xpath: expression, index = 0 } = mapper.parameters;
43
+ let document;
44
+ try {
45
+ document = new DOMParser({ onError: () => { throw new ActionError('mapper'); } })
46
+ .parseFromString(String(value), 'text/xml');
47
+ }
48
+ catch (error) {
49
+ if (!response)
50
+ throw error;
51
+ invalidDocument = true;
52
+ unmatched = true;
53
+ return value;
54
+ }
55
+ const result = xpath.select(expression, document);
56
+ if (typeof result === 'string') {
57
+ unmatched = false;
58
+ invalidDocument = false;
59
+ return result;
60
+ }
61
+ if (Array.isArray(result) && result.length > index) {
62
+ unmatched = false;
63
+ invalidDocument = false;
64
+ return result[index].data;
65
+ }
66
+ unmatched = true;
67
+ return value;
68
+ }
69
+ case 'jpath': {
70
+ let json;
71
+ try {
72
+ json = JSON.parse(String(value));
73
+ }
74
+ catch {
75
+ unmatched = true;
76
+ return 'inconclusive';
77
+ }
78
+ if (typeof json !== 'object') {
79
+ unmatched = true;
80
+ return 'inconclusive';
81
+ }
82
+ const { jpath, index = 0 } = mapper.parameters;
83
+ // safe filter evaluation retains JSONPath filters without native eval
84
+ let result = JSONPath({ path: jpath, json: json, eval: 'safe' });
85
+ if (Array.isArray(result) && result.length > index) {
86
+ unmatched = false;
87
+ invalidDocument = false;
88
+ result = result[index];
89
+ }
90
+ else
91
+ unmatched = true;
92
+ return result instanceof Object ? JSON.stringify(result) : result;
93
+ }
94
+ case 'eval': {
95
+ const result = evaluateLegacy(mapper.parameters.expression, value, state);
96
+ // explicit user code may deliberately handle an earlier extraction miss
97
+ unmatched = false;
98
+ invalidDocument = false;
99
+ return result;
100
+ }
101
+ default:
102
+ void p;
103
+ throw new ActionError('config');
104
+ }
105
+ }, input);
106
+ // preserve intermediate legacy values so a later mapper can explicitly handle a miss
107
+ return response && unmatched && (requireMatch || invalidDocument) ? 'inconclusive' : result;
108
+ }
109
+ catch (error) {
110
+ if (error instanceof ActionError)
111
+ throw error;
112
+ // remote content or expressions may contain credentials; never expose the original error
113
+ throw new ActionError('mapper');
114
+ }
115
+ }
116
+ //# sourceMappingURL=mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mappers.js","sourceRoot":"","sources":["../src/mappers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare const pluginVersion: string;
2
+ export declare const pluginName = "homebridge-http-advanced-platform";
3
+ export declare const platformUUIDNamespace = "homebridge-http-advanced-accessory";
4
+ export declare const accessoryName = "HttpAdvancedAccessory";
5
+ export declare const platformName = "HttpAdvanced";
@@ -0,0 +1,8 @@
1
+ import { createRequire } from 'node:module';
2
+ export const pluginVersion = createRequire(import.meta.url)('../package.json').version;
3
+ export const pluginName = 'homebridge-http-advanced-platform';
4
+ // retain the Alpha.5 UUID namespace independently of the public package name
5
+ export const platformUUIDNamespace = 'homebridge-http-advanced-accessory';
6
+ export const accessoryName = 'HttpAdvancedAccessory';
7
+ export const platformName = 'HttpAdvanced';
8
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,aAAa,GAAW,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC;AAE/F,MAAM,CAAC,MAAM,UAAU,GAAG,mCAAmC,CAAC;AAC9D,6EAA6E;AAC7E,MAAM,CAAC,MAAM,qBAAqB,GAAG,oCAAoC,CAAC;AAC1E,MAAM,CAAC,MAAM,aAAa,GAAG,uBAAuB,CAAC;AACrD,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { API, DynamicPlatformPlugin, Logging, PlatformAccessory, PlatformConfig } from 'homebridge';
2
+ import type { CoordinatorConfig, DeviceConfig } from './types.js';
3
+ export { pluginName, platformName } from './metadata.js';
4
+ export interface HTTPPlatformConfig extends PlatformConfig {
5
+ enabled?: boolean;
6
+ devices?: DeviceConfig[];
7
+ coordinator?: CoordinatorConfig;
8
+ }
9
+ export declare class HTTPPlatform implements DynamicPlatformPlugin {
10
+ readonly log: Logging;
11
+ readonly config: HTTPPlatformConfig;
12
+ readonly api: API;
13
+ private readonly cached;
14
+ constructor(log: Logging, config: HTTPPlatformConfig, api: API);
15
+ configureAccessory(accessory: PlatformAccessory): void;
16
+ private unavailable;
17
+ discover(): void;
18
+ }