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.
- package/LICENSE +176 -0
- package/README.md +175 -0
- package/config.schema.json +449 -0
- package/dist/accessory.d.ts +27 -0
- package/dist/accessory.js +164 -0
- package/dist/accessory.js.map +1 -0
- package/dist/actions.d.ts +8 -0
- package/dist/actions.js +47 -0
- package/dist/actions.js.map +1 -0
- package/dist/compatibility.d.ts +3 -0
- package/dist/compatibility.js +26 -0
- package/dist/compatibility.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/coordinator.d.ts +51 -0
- package/dist/coordinator.js +145 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/mappers.d.ts +3 -0
- package/dist/mappers.js +116 -0
- package/dist/mappers.js.map +1 -0
- package/dist/metadata.d.ts +5 -0
- package/dist/metadata.js +8 -0
- package/dist/metadata.js.map +1 -0
- package/dist/platform.d.ts +18 -0
- package/dist/platform.js +129 -0
- package/dist/platform.js.map +1 -0
- package/dist/runtime.d.ts +54 -0
- package/dist/runtime.js +376 -0
- package/dist/runtime.js.map +1 -0
- package/dist/settings.d.ts +21 -0
- package/dist/settings.js +51 -0
- package/dist/settings.js.map +1 -0
- package/dist/transport.d.ts +25 -0
- package/dist/transport.js +139 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/ui-config.d.ts +27 -0
- package/dist/ui-config.js +208 -0
- package/dist/ui-config.js.map +1 -0
- package/docs/alpha-release-notes.md +19 -0
- package/docs/legacy-reference.md +678 -0
- package/docs/migration.md +43 -0
- package/docs/modernization.md +211 -0
- package/docs/performance.md +37 -0
- package/docs/service-support.md +47 -0
- package/homebridge-ui/public/index.html +74 -0
- package/homebridge-ui/public/settings.js +132 -0
- package/homebridge-ui/server.js +17 -0
- package/package.json +83 -0
- package/sample-config.json +39 -0
package/dist/settings.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { ActionError } from './types.js';
|
|
3
|
+
export class SettingsValidationError extends ActionError {
|
|
4
|
+
constructor(field, requirement) {
|
|
5
|
+
super('config');
|
|
6
|
+
this.message = `Shared settings: ${field} ${requirement}`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function validateSettings(value) {
|
|
10
|
+
const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
11
|
+
if (!object(value))
|
|
12
|
+
throw new SettingsValidationError('httpAdvanced', 'must be an object');
|
|
13
|
+
const number = (field, v, zero = false, integer = false) => {
|
|
14
|
+
if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || (zero ? v < 0 : v <= 0) || (integer && !Number.isInteger(v)))) {
|
|
15
|
+
throw new SettingsValidationError(field, `must be a ${integer ? 'whole ' : ''}number ${zero ? 'of zero or greater' : 'greater than zero'}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
number('requestTimeout', value.requestTimeout);
|
|
19
|
+
for (const key of ['uriCallsDelay', 'setterDelay', 'writeConfirmationTimeout'])
|
|
20
|
+
number(key, value[key], true);
|
|
21
|
+
for (const section of ['refresh', 'coordinator', 'recovery']) {
|
|
22
|
+
if (value[section] !== undefined && !object(value[section]))
|
|
23
|
+
throw new SettingsValidationError(section, 'must be an object');
|
|
24
|
+
}
|
|
25
|
+
const settings = value;
|
|
26
|
+
for (const key of ['activeInterval', 'idleInterval', 'idleAfter'])
|
|
27
|
+
number(`refresh.${key}`, settings.refresh?.[key]);
|
|
28
|
+
for (const key of ['concurrency', 'perOrigin', 'maxQueue'])
|
|
29
|
+
number(`coordinator.${key}`, settings.coordinator?.[key], false, true);
|
|
30
|
+
if (Object.keys(settings.coordinator ?? {}).some(key => !['concurrency', 'perOrigin', 'maxQueue'].includes(key)))
|
|
31
|
+
throw new SettingsValidationError('coordinator', 'supports only concurrency, perOrigin and maxQueue');
|
|
32
|
+
for (const key of ['retryInterval', 'maxRetryInterval', 'reminderInterval'])
|
|
33
|
+
number(`recovery.${key}`, settings.recovery?.[key]);
|
|
34
|
+
number('recovery.quietPeriod', settings.recovery?.quietPeriod, true);
|
|
35
|
+
if ((settings.recovery?.maxRetryInterval ?? 30) < (settings.recovery?.retryInterval ?? 5))
|
|
36
|
+
throw new SettingsValidationError('recovery.maxRetryInterval', 'must be at least recovery.retryInterval (defaults: 30 and 5 seconds)');
|
|
37
|
+
}
|
|
38
|
+
export function readGlobalSettings(api, log) {
|
|
39
|
+
try {
|
|
40
|
+
// child bridges use the same config path; defaults do not require a platform instance
|
|
41
|
+
const settings = JSON.parse(readFileSync(api.user.configPath(), 'utf8')).httpAdvanced ?? {};
|
|
42
|
+
validateSettings(settings);
|
|
43
|
+
return settings;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof ActionError)
|
|
47
|
+
log.warn('HTTP Advanced shared settings invalid; using built-in defaults');
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ActionConfig, type DeviceConfig } from './types.js';
|
|
2
|
+
import { Coordinator } from './coordinator.js';
|
|
3
|
+
import type { GlobalSettings } from './settings.js';
|
|
4
|
+
export interface HTTPResult {
|
|
5
|
+
body: string;
|
|
6
|
+
status: number;
|
|
7
|
+
location?: string;
|
|
8
|
+
retryAfter?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare class Transport {
|
|
11
|
+
readonly coordinator: Coordinator;
|
|
12
|
+
readonly defaults: GlobalSettings;
|
|
13
|
+
private readonly httpAgent;
|
|
14
|
+
private readonly httpsAgent;
|
|
15
|
+
readonly stats: {
|
|
16
|
+
timeouts: number;
|
|
17
|
+
httpErrors: number;
|
|
18
|
+
aborted: number;
|
|
19
|
+
bytes: number;
|
|
20
|
+
};
|
|
21
|
+
constructor(coordinator: Coordinator, defaults?: GlobalSettings);
|
|
22
|
+
request(action: ActionConfig, config: DeviceConfig, owner: string, priority?: boolean, depth?: number, deadline?: number | undefined): Promise<HTTPResult>;
|
|
23
|
+
private follow;
|
|
24
|
+
shutdown(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { ActionError } from './types.js';
|
|
4
|
+
export class Transport {
|
|
5
|
+
coordinator;
|
|
6
|
+
defaults;
|
|
7
|
+
httpAgent = new http.Agent({ keepAlive: true, maxSockets: 4, maxFreeSockets: 2 });
|
|
8
|
+
httpsAgent = new https.Agent({ keepAlive: true, maxSockets: 4, maxFreeSockets: 2 });
|
|
9
|
+
stats = { timeouts: 0, httpErrors: 0, aborted: 0, bytes: 0 };
|
|
10
|
+
constructor(coordinator, defaults = {}) {
|
|
11
|
+
this.coordinator = coordinator;
|
|
12
|
+
this.defaults = defaults;
|
|
13
|
+
}
|
|
14
|
+
async request(action, config, owner, priority = false, depth = 0, deadline = priority ? Date.now() + (action.timeout ?? this.defaults.requestTimeout ?? 10000) : undefined) {
|
|
15
|
+
if (depth > 10)
|
|
16
|
+
throw new ActionError('http');
|
|
17
|
+
let url;
|
|
18
|
+
try {
|
|
19
|
+
url = new URL(action.url);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return Promise.reject(new ActionError('config'));
|
|
23
|
+
}
|
|
24
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
25
|
+
return Promise.reject(new ActionError('config'));
|
|
26
|
+
const result = await this.coordinator.submit(url.origin, owner, config.uriCallsDelay ?? this.defaults.uriCallsDelay ?? 0, priority, async (signal) => {
|
|
27
|
+
// background work gets its request budget when admitted, including subsequent redirects
|
|
28
|
+
deadline ??= Date.now() + (action.timeout ?? this.defaults.requestTimeout ?? 10000);
|
|
29
|
+
const remaining = deadline - Date.now();
|
|
30
|
+
if (remaining <= 0)
|
|
31
|
+
throw new ActionError('timeout');
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const abort = () => controller.abort(new ActionError('aborted'));
|
|
34
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
35
|
+
if (signal.aborted)
|
|
36
|
+
abort();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(new ActionError('timeout')), remaining);
|
|
38
|
+
const headers = { ...action.headers };
|
|
39
|
+
// 1.3.0 always sent supplied credentials immediately, even with immediately:false
|
|
40
|
+
if ((config.username || config.password) && !Object.keys(headers).some(k => k.toLowerCase() === 'authorization')) {
|
|
41
|
+
headers.Authorization = `Basic ${Buffer.from(`${config.username || ''}:${config.password || ''}`).toString('base64')}`;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const result = await this.follow(url, action.httpMethod || 'GET', action.body || '', headers, controller.signal);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (controller.signal.aborted) {
|
|
49
|
+
const reason = controller.signal.reason;
|
|
50
|
+
if (reason.category !== 'timeout')
|
|
51
|
+
this.stats.aborted++;
|
|
52
|
+
throw reason;
|
|
53
|
+
}
|
|
54
|
+
throw error instanceof ActionError ? error : new ActionError('network');
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
signal.removeEventListener('abort', abort);
|
|
59
|
+
}
|
|
60
|
+
}, deadline).catch(error => {
|
|
61
|
+
if (error instanceof ActionError && error.category === 'timeout')
|
|
62
|
+
this.stats.timeouts++;
|
|
63
|
+
throw error;
|
|
64
|
+
});
|
|
65
|
+
const method = (action.httpMethod || 'GET').toUpperCase();
|
|
66
|
+
if ([301, 302, 303, 307, 308].includes(result.status) && result.location && ['GET', 'HEAD'].includes(method)) {
|
|
67
|
+
let next;
|
|
68
|
+
try {
|
|
69
|
+
next = new URL(result.location, url);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
throw new ActionError('http');
|
|
73
|
+
}
|
|
74
|
+
const headers = { ...action.headers };
|
|
75
|
+
let nextConfig = config;
|
|
76
|
+
if (next.origin !== url.origin) {
|
|
77
|
+
for (const key of Object.keys(headers))
|
|
78
|
+
if (['authorization', 'cookie', 'host'].includes(key.toLowerCase()))
|
|
79
|
+
delete headers[key];
|
|
80
|
+
nextConfig = { ...config, username: '', password: '' };
|
|
81
|
+
}
|
|
82
|
+
return this.request({ ...action, url: next.href, headers }, nextConfig, owner, priority, depth + 1, deadline);
|
|
83
|
+
}
|
|
84
|
+
if (result.status < 200 || result.status >= 300) {
|
|
85
|
+
this.stats.httpErrors++;
|
|
86
|
+
// an explicit retry signal is an outage, never a device value or a successful write
|
|
87
|
+
if ([429, 503].includes(result.status) && result.retryAfter !== undefined) {
|
|
88
|
+
const seconds = Number(result.retryAfter);
|
|
89
|
+
const delay = result.retryAfter.trim() && Number.isFinite(seconds) ? seconds * 1000 : Date.parse(result.retryAfter) - Date.now();
|
|
90
|
+
throw new ActionError('unavailable', Number.isFinite(delay) ? Math.max(0, Math.min(300000, delay)) : undefined);
|
|
91
|
+
}
|
|
92
|
+
if (action.strictHTTP)
|
|
93
|
+
throw new ActionError([408, 429, 500, 502, 503, 504].includes(result.status) ? 'unavailable' : 'http');
|
|
94
|
+
}
|
|
95
|
+
if (action.responsePattern !== undefined) {
|
|
96
|
+
let pattern;
|
|
97
|
+
try {
|
|
98
|
+
pattern = new RegExp(action.responsePattern);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
throw new ActionError('config');
|
|
102
|
+
}
|
|
103
|
+
// fixed legacy protocols can identify valid replies without changing their server
|
|
104
|
+
if (!pattern.test(result.body))
|
|
105
|
+
throw new ActionError('inconclusive');
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
async follow(url, method, body, headers, signal) {
|
|
110
|
+
const result = await new Promise((resolve, reject) => {
|
|
111
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
112
|
+
const requestHeaders = { ...headers };
|
|
113
|
+
if (body && !Object.keys(headers).some(k => k.toLowerCase() === 'content-length'))
|
|
114
|
+
requestHeaders['Content-Length'] = String(Buffer.byteLength(body));
|
|
115
|
+
const request = client.request(url, { method, headers: requestHeaders, signal, agent: url.protocol === 'https:' ? this.httpsAgent : this.httpAgent }, response => {
|
|
116
|
+
const chunks = [];
|
|
117
|
+
let size = 0;
|
|
118
|
+
response.on('data', (chunk) => {
|
|
119
|
+
size += chunk.length;
|
|
120
|
+
if (size > 8 * 1024 * 1024) {
|
|
121
|
+
request.destroy(new ActionError('http'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
chunks.push(chunk);
|
|
125
|
+
});
|
|
126
|
+
response.on('error', reject);
|
|
127
|
+
response.on('end', () => {
|
|
128
|
+
this.stats.bytes += size;
|
|
129
|
+
resolve({ body: Buffer.concat(chunks).toString('utf8'), status: response.statusCode ?? 0, location: response.headers.location, retryAfter: response.headers['retry-after'] });
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
request.on('error', reject);
|
|
133
|
+
request.end(body);
|
|
134
|
+
});
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
shutdown() { this.coordinator.shutdown(); this.httpAgent.destroy(); this.httpsAgent.destroy(); }
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { CharacteristicProps, CharacteristicValue } from 'homebridge';
|
|
2
|
+
export type Value = string | number | boolean;
|
|
3
|
+
export type State = Record<string, unknown>;
|
|
4
|
+
export type MapperConfig = {
|
|
5
|
+
type: 'static';
|
|
6
|
+
parameters: {
|
|
7
|
+
mapping: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
} | {
|
|
10
|
+
type: 'regex';
|
|
11
|
+
parameters: {
|
|
12
|
+
regexp: string;
|
|
13
|
+
capture?: number | string;
|
|
14
|
+
};
|
|
15
|
+
} | {
|
|
16
|
+
type: 'xpath';
|
|
17
|
+
parameters: {
|
|
18
|
+
xpath: string;
|
|
19
|
+
index?: number;
|
|
20
|
+
};
|
|
21
|
+
} | {
|
|
22
|
+
type: 'jpath';
|
|
23
|
+
parameters: {
|
|
24
|
+
jpath: string;
|
|
25
|
+
index?: number;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
type: 'eval';
|
|
29
|
+
parameters: {
|
|
30
|
+
expression: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export interface ActionConfig {
|
|
34
|
+
url: string;
|
|
35
|
+
httpMethod?: string;
|
|
36
|
+
body?: string;
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
mappers?: MapperConfig[];
|
|
39
|
+
resultOnError?: Value;
|
|
40
|
+
inconclusive?: ActionConfig;
|
|
41
|
+
timeout?: number;
|
|
42
|
+
strictHTTP?: boolean;
|
|
43
|
+
responsePattern?: string;
|
|
44
|
+
requireResponseMatch?: boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface RefreshConfig {
|
|
47
|
+
activeInterval?: number;
|
|
48
|
+
idleInterval?: number;
|
|
49
|
+
idleAfter?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface DeviceConfig {
|
|
52
|
+
accessory?: string;
|
|
53
|
+
id?: string;
|
|
54
|
+
name: string;
|
|
55
|
+
service: string;
|
|
56
|
+
manufacturer?: string;
|
|
57
|
+
model?: string;
|
|
58
|
+
username?: string;
|
|
59
|
+
password?: string;
|
|
60
|
+
immediately?: boolean;
|
|
61
|
+
debug?: boolean;
|
|
62
|
+
optionCharacteristic?: string[];
|
|
63
|
+
props?: Record<string, Partial<CharacteristicProps>>;
|
|
64
|
+
forceRefreshDelay?: number;
|
|
65
|
+
setterDelay?: number;
|
|
66
|
+
writeConfirmationTimeout?: number;
|
|
67
|
+
uriCallsDelay?: number;
|
|
68
|
+
refresh?: RefreshConfig;
|
|
69
|
+
urls?: Record<string, ActionConfig>;
|
|
70
|
+
}
|
|
71
|
+
export interface CoordinatorConfig {
|
|
72
|
+
concurrency?: number;
|
|
73
|
+
perOrigin?: number;
|
|
74
|
+
maxQueue?: number;
|
|
75
|
+
}
|
|
76
|
+
export type ErrorCategory = 'config' | 'network' | 'timeout' | 'aborted' | 'deferred' | 'http' | 'unavailable' | 'mapper' | 'inconclusive' | 'queue';
|
|
77
|
+
export declare class ActionError extends Error {
|
|
78
|
+
readonly category: ErrorCategory;
|
|
79
|
+
readonly retryAfter?: number | undefined;
|
|
80
|
+
constructor(category: ErrorCategory, retryAfter?: number | undefined);
|
|
81
|
+
}
|
|
82
|
+
export interface CacheEntry {
|
|
83
|
+
key: string;
|
|
84
|
+
actionName: string;
|
|
85
|
+
config: DeviceConfig;
|
|
86
|
+
state: State;
|
|
87
|
+
value?: CharacteristicValue;
|
|
88
|
+
stateValue?: unknown;
|
|
89
|
+
known: boolean;
|
|
90
|
+
lastSuccess: number;
|
|
91
|
+
lastAttempt: number;
|
|
92
|
+
lastChange: number;
|
|
93
|
+
lastDemand: number;
|
|
94
|
+
inFlight: boolean;
|
|
95
|
+
failures: number;
|
|
96
|
+
lastError?: ErrorCategory;
|
|
97
|
+
nextEligible: number;
|
|
98
|
+
generation: number;
|
|
99
|
+
pendingWrite?: PendingWrite;
|
|
100
|
+
convert: (value: unknown) => CharacteristicValue;
|
|
101
|
+
update: (value: CharacteristicValue | Error) => void;
|
|
102
|
+
}
|
|
103
|
+
export interface PendingWrite {
|
|
104
|
+
value: CharacteristicValue;
|
|
105
|
+
expires?: number;
|
|
106
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAsDA,MAAM,OAAO,WAAY,SAAQ,KAAK;IACR;IAAyC;IAArE,YAA4B,QAAuB,EAAkB,UAAmB;QACtF,KAAK,CAAC,iBAAiB,QAAQ,UAAU,CAAC,CAAC;QADjB,aAAQ,GAAR,QAAQ,CAAe;QAAkB,eAAU,GAAV,UAAU,CAAS;IAExF,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type GlobalSettings } from './settings.js';
|
|
2
|
+
type Block = Record<string, unknown>;
|
|
3
|
+
export interface PluginSettings {
|
|
4
|
+
accessories: Block[];
|
|
5
|
+
platforms: Block[];
|
|
6
|
+
settings: GlobalSettings;
|
|
7
|
+
}
|
|
8
|
+
export declare class SettingsError extends Error {
|
|
9
|
+
}
|
|
10
|
+
export declare function selectSettings(config: unknown): PluginSettings;
|
|
11
|
+
export declare function validatePluginSettings(value: unknown): asserts value is PluginSettings;
|
|
12
|
+
export declare function mergeSettings(config: unknown, draft: PluginSettings): Block;
|
|
13
|
+
export declare class ConfigEditor {
|
|
14
|
+
private readonly configPath;
|
|
15
|
+
constructor(configPath: string);
|
|
16
|
+
private read;
|
|
17
|
+
load(): {
|
|
18
|
+
draft: PluginSettings;
|
|
19
|
+
revision: string;
|
|
20
|
+
};
|
|
21
|
+
save(input: unknown): {
|
|
22
|
+
draft: PluginSettings;
|
|
23
|
+
revision: string;
|
|
24
|
+
changed: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { closeSync, fchmodSync, fchownSync, fstatSync, fsyncSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { validateDevice } from './config.js';
|
|
5
|
+
import { validateSettings, SettingsValidationError } from './settings.js';
|
|
6
|
+
import { pluginName, accessoryName, platformName } from './metadata.js';
|
|
7
|
+
const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
8
|
+
const owns = (block, type) => {
|
|
9
|
+
const alias = type === 'accessory' ? accessoryName : platformName;
|
|
10
|
+
return isObject(block) && (block[type] === alias || block[type] === `${pluginName}.${alias}`);
|
|
11
|
+
};
|
|
12
|
+
export class SettingsError extends Error {
|
|
13
|
+
}
|
|
14
|
+
function configObject(value) {
|
|
15
|
+
if (!isObject(value) || ['accessories', 'platforms'].some(key => value[key] !== undefined && !Array.isArray(value[key]))) {
|
|
16
|
+
throw new SettingsError('Homebridge configuration is invalid; no changes saved');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function selectSettings(config) {
|
|
20
|
+
configObject(config);
|
|
21
|
+
const settings = config.httpAdvanced ?? {};
|
|
22
|
+
validateSettings(settings);
|
|
23
|
+
return { accessories: (config.accessories ?? []).filter(b => owns(b, 'accessory')),
|
|
24
|
+
platforms: (config.platforms ?? []).filter(b => owns(b, 'platform')), settings };
|
|
25
|
+
}
|
|
26
|
+
export function validatePluginSettings(value) {
|
|
27
|
+
if (!isObject(value) || !Array.isArray(value.accessories) || !Array.isArray(value.platforms))
|
|
28
|
+
throw new SettingsError('Accessory and platform configuration must be arrays');
|
|
29
|
+
let location = 'Shared settings';
|
|
30
|
+
try {
|
|
31
|
+
validateSettings(value.settings);
|
|
32
|
+
const legacyNames = new Set();
|
|
33
|
+
for (const [index, block] of value.accessories.entries()) {
|
|
34
|
+
location = `Legacy accessory ${index + 1} in JSON Config`;
|
|
35
|
+
if (!owns(block, 'accessory'))
|
|
36
|
+
throw new Error();
|
|
37
|
+
validateDevice(block);
|
|
38
|
+
if (legacyNames.has(block.name))
|
|
39
|
+
throw new Error();
|
|
40
|
+
legacyNames.add(block.name);
|
|
41
|
+
}
|
|
42
|
+
const platformNames = new Set();
|
|
43
|
+
for (const [index, block] of value.platforms.entries()) {
|
|
44
|
+
location = `Platform ${index + 1}`;
|
|
45
|
+
if (!owns(block, 'platform') || typeof block.name !== 'string' || !block.name.trim() || !Array.isArray(block.devices)
|
|
46
|
+
|| (block.enabled !== undefined && typeof block.enabled !== 'boolean'))
|
|
47
|
+
throw new Error();
|
|
48
|
+
if (platformNames.has(block.name))
|
|
49
|
+
throw new Error();
|
|
50
|
+
platformNames.add(block.name);
|
|
51
|
+
if (block.coordinator !== undefined)
|
|
52
|
+
validateSettings({ coordinator: block.coordinator });
|
|
53
|
+
const ids = new Set();
|
|
54
|
+
for (const [deviceIndex, device] of block.devices.entries()) {
|
|
55
|
+
location = `Platform ${index + 1}, device ${deviceIndex + 1}`;
|
|
56
|
+
validateDevice(device);
|
|
57
|
+
const id = device.id ?? device.name;
|
|
58
|
+
if (typeof id !== 'string' || !id.trim() || ids.has(id))
|
|
59
|
+
throw new Error();
|
|
60
|
+
ids.add(id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const detail = error instanceof SettingsValidationError ? error.message.replace(/^Shared settings: /, '') : 'check names, aliases, device actions and timing values';
|
|
66
|
+
throw new SettingsError(`${location}: ${detail}; no changes saved`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function replaceBlocks(current, type, replacements) {
|
|
70
|
+
let index = 0;
|
|
71
|
+
const result = current.flatMap(block => !owns(block, type) ? [block] : index < replacements.length ? [replacements[index++]] : []);
|
|
72
|
+
return result.concat(replacements.slice(index));
|
|
73
|
+
}
|
|
74
|
+
export function mergeSettings(config, draft) {
|
|
75
|
+
configObject(config);
|
|
76
|
+
validatePluginSettings(draft);
|
|
77
|
+
const result = { ...config };
|
|
78
|
+
if (config.accessories !== undefined || draft.accessories.length)
|
|
79
|
+
result.accessories = replaceBlocks((config.accessories ?? []), 'accessory', draft.accessories);
|
|
80
|
+
if (config.platforms !== undefined || draft.platforms.length)
|
|
81
|
+
result.platforms = replaceBlocks((config.platforms ?? []), 'platform', draft.platforms);
|
|
82
|
+
if (config.httpAdvanced !== undefined || Object.keys(draft.settings).length)
|
|
83
|
+
result.httpAdvanced = draft.settings;
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
const revision = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
87
|
+
function acquireLock(path) {
|
|
88
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
89
|
+
try {
|
|
90
|
+
const descriptor = openSync(path, 'wx', 0o600);
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(descriptor, JSON.stringify({ pid: process.pid }));
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
closeSync(descriptor);
|
|
96
|
+
unlinkSync(path);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
return descriptor;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// reclaim only a lock whose recorded process no longer exists
|
|
103
|
+
try {
|
|
104
|
+
const before = statSync(path);
|
|
105
|
+
const { pid } = JSON.parse(readFileSync(path, 'utf8'));
|
|
106
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
107
|
+
break;
|
|
108
|
+
try {
|
|
109
|
+
process.kill(pid, 0);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error.code !== 'ESRCH')
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (statSync(path).ino !== before.ino)
|
|
117
|
+
break;
|
|
118
|
+
unlinkSync(path);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
throw new SettingsError('Another settings save is in progress or its lock could not be acquired; try again after it completes');
|
|
126
|
+
}
|
|
127
|
+
export class ConfigEditor {
|
|
128
|
+
configPath;
|
|
129
|
+
constructor(configPath) {
|
|
130
|
+
this.configPath = configPath;
|
|
131
|
+
}
|
|
132
|
+
read() {
|
|
133
|
+
try {
|
|
134
|
+
const path = realpathSync(this.configPath);
|
|
135
|
+
const raw = readFileSync(path, 'utf8');
|
|
136
|
+
const config = JSON.parse(raw);
|
|
137
|
+
configObject(config);
|
|
138
|
+
return { path, raw, config, draft: selectSettings(config) };
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (error instanceof SettingsValidationError)
|
|
142
|
+
throw new SettingsError(`${error.message}; correct httpAdvanced in Homebridge's full configuration editor, then reopen Plugin Config`);
|
|
143
|
+
throw new SettingsError('Could not read Homebridge configuration; no changes saved');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
load() {
|
|
147
|
+
const { draft } = this.read();
|
|
148
|
+
return { draft, revision: revision(draft) };
|
|
149
|
+
}
|
|
150
|
+
save(input) {
|
|
151
|
+
if (!isObject(input) || typeof input.revision !== 'string')
|
|
152
|
+
throw new SettingsError('Reload settings before saving');
|
|
153
|
+
validatePluginSettings(input.draft);
|
|
154
|
+
const initial = this.read();
|
|
155
|
+
const lockPath = initial.path + '.http-advanced.lock';
|
|
156
|
+
const lock = acquireLock(lockPath);
|
|
157
|
+
const temporary = initial.path + `.http-advanced-${randomUUID()}.tmp`;
|
|
158
|
+
try {
|
|
159
|
+
const current = this.read();
|
|
160
|
+
if (current.path !== initial.path || revision(current.draft) !== input.revision)
|
|
161
|
+
throw new SettingsError('HTTP Advanced settings changed in another editor; reload before saving');
|
|
162
|
+
const merged = mergeSettings(current.config, input.draft);
|
|
163
|
+
if (isDeepStrictEqual(merged, current.config))
|
|
164
|
+
return { ...this.load(), changed: false };
|
|
165
|
+
const descriptor = openSync(temporary, 'wx', 0o600);
|
|
166
|
+
try {
|
|
167
|
+
writeFileSync(descriptor, JSON.stringify(merged, null, 4) + '\n');
|
|
168
|
+
// preserve ownership as well as mode, including when the UI runs as root
|
|
169
|
+
const original = statSync(current.path), replacement = fstatSync(descriptor);
|
|
170
|
+
if (replacement.uid !== original.uid || replacement.gid !== original.gid)
|
|
171
|
+
fchownSync(descriptor, original.uid, original.gid);
|
|
172
|
+
fchmodSync(descriptor, original.mode & 0o777);
|
|
173
|
+
fsyncSync(descriptor);
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
closeSync(descriptor);
|
|
177
|
+
}
|
|
178
|
+
// preserve an exact private backup; recheck for writes from other Homebridge editors
|
|
179
|
+
const backup = current.path + `.http-advanced-backup-${Date.now()}-${randomUUID()}`;
|
|
180
|
+
const backupDescriptor = openSync(backup, 'wx', 0o600);
|
|
181
|
+
try {
|
|
182
|
+
writeFileSync(backupDescriptor, current.raw);
|
|
183
|
+
fsyncSync(backupDescriptor);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
closeSync(backupDescriptor);
|
|
187
|
+
}
|
|
188
|
+
if (readFileSync(current.path, 'utf8') !== current.raw)
|
|
189
|
+
throw new SettingsError('Homebridge configuration changed during save; reload before saving');
|
|
190
|
+
renameSync(temporary, current.path);
|
|
191
|
+
return { ...this.load(), changed: true };
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
if (error instanceof SettingsError)
|
|
195
|
+
throw error;
|
|
196
|
+
throw new SettingsError('Could not save Homebridge configuration; check file permissions');
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
try {
|
|
200
|
+
unlinkSync(temporary);
|
|
201
|
+
}
|
|
202
|
+
catch { /* no temporary file after a successful rename */ }
|
|
203
|
+
closeSync(lock);
|
|
204
|
+
unlinkSync(lockPath);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=ui-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-config.js","sourceRoot":"","sources":["../src/ui-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzK,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAuB,MAAM,eAAe,CAAC;AAK/F,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACxE,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAkB,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AACnH,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,IAA8B,EAAW,EAAE;IACvE,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC;IAClE,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,UAAU,IAAI,KAAK,EAAE,CAAC,CAAC;AAChG,CAAC,CAAC;AACF,MAAM,OAAO,aAAc,SAAQ,KAAK;CAAG;AAE3C,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACzH,MAAM,IAAI,aAAa,CAAC,uDAAuD,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,YAAY,CAAC,MAAM,CAAC,CAAC;IACrB,MAAM,QAAQ,GAAG,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC;IAC3C,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,OAAO,EAAE,WAAW,EAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAC7F,SAAS,EAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;AAClG,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,MAAM,IAAI,aAAa,CAAC,qDAAqD,CAAC,CAAC;IAC7K,IAAI,QAAQ,GAAG,iBAAiB,CAAC;IACjC,IAAI,CAAC;QACH,gBAAgB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACjC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;QACtC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,CAAC;YACzD,QAAQ,GAAG,oBAAoB,KAAK,GAAG,CAAC,iBAAiB,CAAC;YAC1D,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,CAAC;gBAAE,MAAM,IAAI,KAAK,EAAE,CAAC;YACjD,cAAc,CAAC,KAAqB,CAAC,CAAC;YACtC,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,EAAE,CAAC;YACnD,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;QACxC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;YACvD,QAAQ,GAAG,YAAY,KAAK,GAAG,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;mBAChH,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC;gBAAE,MAAM,IAAI,KAAK,EAAE,CAAC;YAC5F,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,EAAE,CAAC;YACrD,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;gBAAE,gBAAgB,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;YAC9B,KAAK,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC5D,QAAQ,GAAG,YAAY,KAAK,GAAG,CAAC,YAAY,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC9D,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,IAAI,CAAC;gBACpC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,IAAI,KAAK,EAAE,CAAC;gBAC3E,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,wDAAwD,CAAC;QACrK,MAAM,IAAI,aAAa,CAAC,GAAG,QAAQ,KAAK,MAAM,oBAAoB,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAkB,EAAE,IAA8B,EAAE,YAAqB;IAC9F,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnI,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,KAAqB;IAClE,YAAY,CAAC,MAAM,CAAC,CAAC;IAAC,sBAAsB,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC7B,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM;QAAE,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAc,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC9K,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM;QAAE,MAAM,CAAC,SAAS,GAAG,aAAa,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAc,EAAE,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACnK,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM;QAAE,MAAM,CAAC,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC;IAClH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAE9G,SAAS,WAAW,CAAC,IAAY;IAC/B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC/C,IAAI,CAAC;gBAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAAC,CAAC;YACxE,OAAO,KAAK,EAAE,CAAC;gBAAC,SAAS,CAAC,UAAU,CAAC,CAAC;gBAAC,UAAU,CAAC,IAAI,CAAC,CAAC;gBAAC,MAAM,KAAK,CAAC;YAAC,CAAC;YACvE,OAAO,UAAU,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;YAC9D,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;gBAC9B,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAoB,CAAC;gBAC1E,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;oBAAE,MAAM;gBAClD,IAAI,CAAC;oBAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBAAC,MAAM;gBAAC,CAAC;gBACpC,OAAO,KAAK,EAAE,CAAC;oBAAC,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;wBAAE,MAAM;gBAAC,CAAC;gBAC/E,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;oBAAE,MAAM;gBAC7C,UAAU,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBAAC,MAAM;YAAC,CAAC;QACpB,CAAC;IACH,CAAC;IACD,MAAM,IAAI,aAAa,CAAC,sGAAsG,CAAC,CAAC;AAClI,CAAC;AAED,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,UAAkB;QAAlB,eAAU,GAAV,UAAU,CAAQ;IAAG,CAAC;IAE3C,IAAI;QACV,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC3C,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACvC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,YAAY,CAAC,MAAM,CAAC,CAAC;YACrB,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,uBAAuB;gBAAE,MAAM,IAAI,aAAa,CAAC,GAAG,KAAK,CAAC,OAAO,6FAA6F,CAAC,CAAC;YACrL,MAAM,IAAI,aAAa,CAAC,2DAA2D,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,IAAI;QACF,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9C,CAAC;IAED,IAAI,CAAC,KAAc;QACjB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;YAAE,MAAM,IAAI,aAAa,CAAC,+BAA+B,CAAC,CAAC;QACrH,sBAAsB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,GAAG,qBAAqB,CAAC;QACtD,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,GAAG,kBAAkB,UAAU,EAAE,MAAM,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,QAAQ;gBAAE,MAAM,IAAI,aAAa,CAAC,wEAAwE,CAAC,CAAC;YACnL,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,IAAI,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YACzF,MAAM,UAAU,GAAG,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACpD,IAAI,CAAC;gBACH,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;gBAClE,yEAAyE;gBACzE,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;gBAC7E,IAAI,WAAW,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG,IAAI,WAAW,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG;oBAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;gBAC7H,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;gBAC9C,SAAS,CAAC,UAAU,CAAC,CAAC;YACxB,CAAC;oBAAS,CAAC;gBAAC,SAAS,CAAC,UAAU,CAAC,CAAC;YAAC,CAAC;YACpC,qFAAqF;YACrF,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,yBAAyB,IAAI,CAAC,GAAG,EAAE,IAAI,UAAU,EAAE,EAAE,CAAC;YACpF,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YACvD,IAAI,CAAC;gBAAC,aAAa,CAAC,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;gBAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;YAAC,CAAC;oBAC1E,CAAC;gBAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;YAAC,CAAC;YACxC,IAAI,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,OAAO,CAAC,GAAG;gBAAE,MAAM,IAAI,aAAa,CAAC,oEAAoE,CAAC,CAAC;YACtJ,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,aAAa;gBAAE,MAAM,KAAK,CAAC;YAChD,MAAM,IAAI,aAAa,CAAC,iEAAiE,CAAC,CAAC;QAC7F,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBAAC,UAAU,CAAC,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,iDAAiD,CAAC,CAAC;YAC1F,SAAS,CAAC,IAAI,CAAC,CAAC;YAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Homebridge HTTP Advanced Platform 2.0.0-alpha.6
|
|
2
|
+
|
|
3
|
+
Opt-in Alpha based on preserved `2.0.0-alpha.5`. Confirm availability in the
|
|
4
|
+
matching [GitHub prerelease](https://github.com/shanemcw/homebridge-http-advanced-platform/releases)
|
|
5
|
+
and [npm version list](https://www.npmjs.com/package/homebridge-http-advanced-platform?activeTab=versions).
|
|
6
|
+
|
|
7
|
+
This is a platform conversion of [staromeste/homebridge-http-advanced-accessory](https://github.com/staromeste/homebridge-http-advanced-accessory). It retains the original Apache-2.0 license, contributor credit and Git history.
|
|
8
|
+
|
|
9
|
+
The release changes the public package to `homebridge-http-advanced-platform`, updates branding and search metadata, and retains Alpha.5 device behavior: shared cached reads, bounded background HTTP requests, recovery, write confirmation, legacy accessories and optional platform devices. No parent issue/PR fixes or dependency updates are included in this packaging milestone.
|
|
10
|
+
|
|
11
|
+
Configuration aliases remain `HttpAdvancedAccessory` and `HttpAdvanced`. The platform UUID namespace remains the Alpha.5 namespace. Package-qualified configuration and plugin lists must use the new package name; do not load both old and new packages. Follow the [replacement and rollback guide](migration.md), preserving configuration, storage and bridge identity.
|
|
12
|
+
|
|
13
|
+
Moving a legacy accessory into a platform remains an explicit manual conversion with a new HomeKit identity. No automatic conversion tool is included.
|
|
14
|
+
|
|
15
|
+
Requirements remain Node `^22.13.0 || ^24.0.0` and Homebridge `^1.11.4 || ^2.4.0`. Source checks and installed loader/adapters/custom UI IPC passed across Node 22/24 and Homebridge 1/2. Rendered Homebridge UI 5.29.0, native editing, managed child bridges and backed-up replacement/rollback passed in an isolated Node 24/Homebridge 2 fixture. HAP was disabled in that fixture; paired Apple Home, physical controls, rooms/scenes/automations and live soak remain separate acceptance.
|
|
16
|
+
|
|
17
|
+
The publication channel is the explicit `alpha` npm tag with a matching GitHub prerelease; this package has no stable release or `latest` tag. Once published, select **alpha / v2.0.0-alpha.6** in Homebridge UI 5.29.0's install version chooser, or install `homebridge-http-advanced-platform@alpha` (exact version: `@2.0.0-alpha.6`). The Alpha-only chooser and metadata matching were rehearsed using isolated registry responses and actual UI logic; public indexing and registry installation still require launch checks. Its tags do not modify the original accessory package's tags. Homebridge verification will be pursued separately after publication; this Alpha is not verified.
|
|
18
|
+
|
|
19
|
+
Report versions and sanitized diagnostics. Do not include credentials, pairing PINs or unredacted configuration.
|