homebridge-bluos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/DEVELOPMENT.md +65 -0
- package/LICENSE +202 -0
- package/README.md +251 -0
- package/SECURITY.md +43 -0
- package/config.schema.json +135 -0
- package/dist/api/client.d.ts +131 -0
- package/dist/api/client.js +226 -0
- package/dist/api/discovery.d.ts +136 -0
- package/dist/api/discovery.js +402 -0
- package/dist/api/http.d.ts +52 -0
- package/dist/api/http.js +136 -0
- package/dist/api/identity.d.ts +73 -0
- package/dist/api/identity.js +120 -0
- package/dist/api/index.d.ts +14 -0
- package/dist/api/index.js +30 -0
- package/dist/api/sync-status.d.ts +50 -0
- package/dist/api/sync-status.js +191 -0
- package/dist/api/xml.d.ts +76 -0
- package/dist/api/xml.js +365 -0
- package/dist/devices/base-accessory.d.ts +131 -0
- package/dist/devices/base-accessory.js +236 -0
- package/dist/devices/battery-accessory.d.ts +28 -0
- package/dist/devices/battery-accessory.js +85 -0
- package/dist/devices/host.d.ts +45 -0
- package/dist/devices/host.js +14 -0
- package/dist/devices/index.d.ts +14 -0
- package/dist/devices/index.js +30 -0
- package/dist/devices/mute-accessory.d.ts +35 -0
- package/dist/devices/mute-accessory.js +71 -0
- package/dist/devices/volume-accessory.d.ts +66 -0
- package/dist/devices/volume-accessory.js +218 -0
- package/dist/devices/volume-preset-accessory.d.ts +32 -0
- package/dist/devices/volume-preset-accessory.js +89 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +19 -0
- package/dist/platform.d.ts +124 -0
- package/dist/platform.js +489 -0
- package/dist/poller.d.ts +109 -0
- package/dist/poller.js +300 -0
- package/dist/settings.d.ts +184 -0
- package/dist/settings.js +210 -0
- package/dist/types/index.d.ts +218 -0
- package/dist/types/index.js +38 -0
- package/dist/ui-api.d.ts +20 -0
- package/dist/ui-api.js +32 -0
- package/dist/utils/context.d.ts +18 -0
- package/dist/utils/context.js +56 -0
- package/dist/utils/errors.d.ts +37 -0
- package/dist/utils/errors.js +92 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +29 -0
- package/dist/utils/serial.d.ts +22 -0
- package/dist/utils/serial.js +37 -0
- package/dist/utils/timing.d.ts +52 -0
- package/dist/utils/timing.js +74 -0
- package/dist/utils/validators.d.ts +99 -0
- package/dist/utils/validators.js +461 -0
- package/docs/FEATURES.md +91 -0
- package/docs/PROTOCOL.md +194 -0
- package/homebridge-ui/public/index.html +87 -0
- package/homebridge-ui/public/index.js +475 -0
- package/homebridge-ui/server.js +189 -0
- package/package.json +91 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Error description helpers.
|
|
9
|
+
*
|
|
10
|
+
* Node wraps low-level network failures in `cause` chains, so a bare
|
|
11
|
+
* `error.message` frequently reads "fetch failed" while the useful detail
|
|
12
|
+
* (ECONNREFUSED, ETIMEDOUT) sits one level down.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ConnectionError = exports.ProtocolError = exports.ConfigValidationError = void 0;
|
|
16
|
+
exports.describeError = describeError;
|
|
17
|
+
exports.describeErrorStack = describeErrorStack;
|
|
18
|
+
/** Longest description produced, so a hostile endpoint cannot flood the log. */
|
|
19
|
+
const MAX_DESCRIPTION_LENGTH = 300;
|
|
20
|
+
function messageOf(error) {
|
|
21
|
+
if (error instanceof Error) {
|
|
22
|
+
const code = error.code;
|
|
23
|
+
return typeof code === 'string' && code.length > 0
|
|
24
|
+
? `${error.message} (${code})`
|
|
25
|
+
: error.message;
|
|
26
|
+
}
|
|
27
|
+
if (typeof error === 'string') {
|
|
28
|
+
return error;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.stringify(error) ?? String(error);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return String(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Describe an error, including any `cause` chain, for a single log line.
|
|
39
|
+
*
|
|
40
|
+
* Control characters are stripped: an error message can contain remote input,
|
|
41
|
+
* and a newline inside a log line lets an attacker forge log entries.
|
|
42
|
+
*/
|
|
43
|
+
function describeError(error) {
|
|
44
|
+
const parts = [];
|
|
45
|
+
let current = error;
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
while (current !== undefined && current !== null && !seen.has(current)) {
|
|
48
|
+
seen.add(current);
|
|
49
|
+
const text = messageOf(current);
|
|
50
|
+
if (text.length > 0 && !parts.includes(text)) {
|
|
51
|
+
parts.push(text);
|
|
52
|
+
}
|
|
53
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
54
|
+
}
|
|
55
|
+
const joined = parts.length > 0 ? parts.join(': ') : 'unknown error';
|
|
56
|
+
const sanitized = joined.replace(/[\u0000-\u001F\u007F]/g, '\uFFFD');
|
|
57
|
+
return sanitized.length > MAX_DESCRIPTION_LENGTH
|
|
58
|
+
? `${sanitized.slice(0, MAX_DESCRIPTION_LENGTH)}\u2026`
|
|
59
|
+
: sanitized;
|
|
60
|
+
}
|
|
61
|
+
/** Describe an error and append its stack, for `log.debug` only. */
|
|
62
|
+
function describeErrorStack(error) {
|
|
63
|
+
const description = describeError(error);
|
|
64
|
+
if (error instanceof Error && typeof error.stack === 'string') {
|
|
65
|
+
return `${description}\n${error.stack}`;
|
|
66
|
+
}
|
|
67
|
+
return description;
|
|
68
|
+
}
|
|
69
|
+
/** Raised when configuration cannot produce a usable accessory set. */
|
|
70
|
+
class ConfigValidationError extends Error {
|
|
71
|
+
constructor(message) {
|
|
72
|
+
super(message);
|
|
73
|
+
this.name = 'ConfigValidationError';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.ConfigValidationError = ConfigValidationError;
|
|
77
|
+
/** Raised when a player answers, but not with something we can parse. */
|
|
78
|
+
class ProtocolError extends Error {
|
|
79
|
+
constructor(message, options) {
|
|
80
|
+
super(message, options);
|
|
81
|
+
this.name = 'ProtocolError';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.ProtocolError = ProtocolError;
|
|
85
|
+
/** Raised when a player cannot be reached at all. */
|
|
86
|
+
class ConnectionError extends Error {
|
|
87
|
+
constructor(message, options) {
|
|
88
|
+
super(message, options);
|
|
89
|
+
this.name = 'ConnectionError';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.ConnectionError = ConnectionError;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Utility barrel.
|
|
8
|
+
*/
|
|
9
|
+
export * from './context';
|
|
10
|
+
export * from './errors';
|
|
11
|
+
export * from './serial';
|
|
12
|
+
export * from './timing';
|
|
13
|
+
export * from './validators';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Utility barrel.
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
22
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
23
|
+
};
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
__exportStar(require("./context"), exports);
|
|
26
|
+
__exportStar(require("./errors"), exports);
|
|
27
|
+
__exportStar(require("./serial"), exports);
|
|
28
|
+
__exportStar(require("./timing"), exports);
|
|
29
|
+
__exportStar(require("./validators"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Opaque HomeKit serial numbers.
|
|
8
|
+
*
|
|
9
|
+
* The player's MAC address is the obvious candidate and the wrong one. HomeKit
|
|
10
|
+
* shows SerialNumber in the Home app and it ends up in screenshots and bug
|
|
11
|
+
* reports, and a MAC is both identifying and, on a multi-zone chassis, shared
|
|
12
|
+
* between zones. A random value generated once and persisted in accessory
|
|
13
|
+
* context is stable across restarts without disclosing anything.
|
|
14
|
+
*/
|
|
15
|
+
import type { PlatformAccessory } from 'homebridge';
|
|
16
|
+
/** Generate a fresh opaque serial number. */
|
|
17
|
+
export declare function newAccessorySerialNumber(): string;
|
|
18
|
+
/**
|
|
19
|
+
* Return this accessory's serial number, generating and persisting one if the
|
|
20
|
+
* cached accessory predates the field.
|
|
21
|
+
*/
|
|
22
|
+
export declare function ensureAccessorySerialNumber(accessory: PlatformAccessory): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Opaque HomeKit serial numbers.
|
|
9
|
+
*
|
|
10
|
+
* The player's MAC address is the obvious candidate and the wrong one. HomeKit
|
|
11
|
+
* shows SerialNumber in the Home app and it ends up in screenshots and bug
|
|
12
|
+
* reports, and a MAC is both identifying and, on a multi-zone chassis, shared
|
|
13
|
+
* between zones. A random value generated once and persisted in accessory
|
|
14
|
+
* context is stable across restarts without disclosing anything.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.newAccessorySerialNumber = newAccessorySerialNumber;
|
|
18
|
+
exports.ensureAccessorySerialNumber = ensureAccessorySerialNumber;
|
|
19
|
+
const node_crypto_1 = require("node:crypto");
|
|
20
|
+
/** Generate a fresh opaque serial number. */
|
|
21
|
+
function newAccessorySerialNumber() {
|
|
22
|
+
return (0, node_crypto_1.randomUUID)();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Return this accessory's serial number, generating and persisting one if the
|
|
26
|
+
* cached accessory predates the field.
|
|
27
|
+
*/
|
|
28
|
+
function ensureAccessorySerialNumber(accessory) {
|
|
29
|
+
const context = accessory.context;
|
|
30
|
+
const existing = context.serialNumber;
|
|
31
|
+
if (typeof existing === 'string' && existing.length > 0) {
|
|
32
|
+
return existing;
|
|
33
|
+
}
|
|
34
|
+
const generated = newAccessorySerialNumber();
|
|
35
|
+
context.serialNumber = generated;
|
|
36
|
+
return generated;
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Waiting primitives, in one place so the reference semantics are
|
|
8
|
+
* decided once.
|
|
9
|
+
*
|
|
10
|
+
* The distinction matters and is easy to get wrong. A timer inside an operation
|
|
11
|
+
* somebody is awaiting must keep the event loop alive: an unreferenced timer
|
|
12
|
+
* there lets Node decide the process has nothing left to do and exit while a
|
|
13
|
+
* caller is still waiting for an answer. That was a real defect in this plugin —
|
|
14
|
+
* the API's one-second minimum gap between requests for the same resource was
|
|
15
|
+
* implemented with an unreferenced timer, so a script that awaited a read could
|
|
16
|
+
* exit silently mid-request instead of returning a value.
|
|
17
|
+
*
|
|
18
|
+
* The opposite applies to a timer nothing is waiting on, such as a retry backoff
|
|
19
|
+
* inside a loop that can be cancelled. Those are always cleared rather than
|
|
20
|
+
* merely unreferenced, so shutdown is immediate instead of waiting out a delay
|
|
21
|
+
* that has been rendered pointless.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Wait, keeping the process alive for the duration.
|
|
25
|
+
*
|
|
26
|
+
* For use inside an operation a caller is awaiting.
|
|
27
|
+
*/
|
|
28
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
29
|
+
/** A wait that can be abandoned before it elapses. */
|
|
30
|
+
export interface InterruptibleSleep {
|
|
31
|
+
/** Resolves when the delay elapses or {@link interrupt} is called. */
|
|
32
|
+
readonly promise: Promise<void>;
|
|
33
|
+
/** Resolve now and cancel the underlying timer. */
|
|
34
|
+
interrupt(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Wait, but allow the wait to be cut short.
|
|
38
|
+
*
|
|
39
|
+
* The timer is cleared on interruption, so nothing is left holding the event
|
|
40
|
+
* loop open once the delay is no longer wanted.
|
|
41
|
+
*/
|
|
42
|
+
export declare function interruptibleSleep(ms: number): InterruptibleSleep;
|
|
43
|
+
/** Returned by {@link raceTimeout} when the deadline came first. */
|
|
44
|
+
export declare const TIMED_OUT: unique symbol;
|
|
45
|
+
/**
|
|
46
|
+
* Race work against a deadline, without leaving the timer behind.
|
|
47
|
+
*
|
|
48
|
+
* The work is not cancelled when the deadline wins — that is the caller's
|
|
49
|
+
* decision — but the timer is always cleared, so a fast result does not leave a
|
|
50
|
+
* pending timer holding the process open.
|
|
51
|
+
*/
|
|
52
|
+
export declare function raceTimeout<T>(work: Promise<T>, ms: number): Promise<T | typeof TIMED_OUT>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 tbaur
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0
|
|
6
|
+
* See LICENSE file for full license text
|
|
7
|
+
*
|
|
8
|
+
* @fileoverview Waiting primitives, in one place so the reference semantics are
|
|
9
|
+
* decided once.
|
|
10
|
+
*
|
|
11
|
+
* The distinction matters and is easy to get wrong. A timer inside an operation
|
|
12
|
+
* somebody is awaiting must keep the event loop alive: an unreferenced timer
|
|
13
|
+
* there lets Node decide the process has nothing left to do and exit while a
|
|
14
|
+
* caller is still waiting for an answer. That was a real defect in this plugin —
|
|
15
|
+
* the API's one-second minimum gap between requests for the same resource was
|
|
16
|
+
* implemented with an unreferenced timer, so a script that awaited a read could
|
|
17
|
+
* exit silently mid-request instead of returning a value.
|
|
18
|
+
*
|
|
19
|
+
* The opposite applies to a timer nothing is waiting on, such as a retry backoff
|
|
20
|
+
* inside a loop that can be cancelled. Those are always cleared rather than
|
|
21
|
+
* merely unreferenced, so shutdown is immediate instead of waiting out a delay
|
|
22
|
+
* that has been rendered pointless.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.TIMED_OUT = void 0;
|
|
26
|
+
exports.sleep = sleep;
|
|
27
|
+
exports.interruptibleSleep = interruptibleSleep;
|
|
28
|
+
exports.raceTimeout = raceTimeout;
|
|
29
|
+
/**
|
|
30
|
+
* Wait, keeping the process alive for the duration.
|
|
31
|
+
*
|
|
32
|
+
* For use inside an operation a caller is awaiting.
|
|
33
|
+
*/
|
|
34
|
+
function sleep(ms) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
setTimeout(resolve, Math.max(0, ms));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Wait, but allow the wait to be cut short.
|
|
41
|
+
*
|
|
42
|
+
* The timer is cleared on interruption, so nothing is left holding the event
|
|
43
|
+
* loop open once the delay is no longer wanted.
|
|
44
|
+
*/
|
|
45
|
+
function interruptibleSleep(ms) {
|
|
46
|
+
let cancel = () => { };
|
|
47
|
+
const promise = new Promise((resolve) => {
|
|
48
|
+
const timer = setTimeout(resolve, Math.max(0, ms));
|
|
49
|
+
cancel = () => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
resolve();
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
return { promise, interrupt: () => cancel() };
|
|
55
|
+
}
|
|
56
|
+
/** Returned by {@link raceTimeout} when the deadline came first. */
|
|
57
|
+
exports.TIMED_OUT = Symbol('timed out');
|
|
58
|
+
/**
|
|
59
|
+
* Race work against a deadline, without leaving the timer behind.
|
|
60
|
+
*
|
|
61
|
+
* The work is not cancelled when the deadline wins — that is the caller's
|
|
62
|
+
* decision — but the timer is always cleared, so a fast result does not leave a
|
|
63
|
+
* pending timer holding the process open.
|
|
64
|
+
*/
|
|
65
|
+
async function raceTimeout(work, ms) {
|
|
66
|
+
const deadline = interruptibleSleep(ms);
|
|
67
|
+
const expired = deadline.promise.then(() => exports.TIMED_OUT);
|
|
68
|
+
try {
|
|
69
|
+
return await Promise.race([work, expired]);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
deadline.interrupt();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 tbaur
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0
|
|
5
|
+
* See LICENSE file for full license text
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Configuration validation.
|
|
8
|
+
*
|
|
9
|
+
* The split between fatal and non-fatal is deliberate. A structural problem —
|
|
10
|
+
* `devices` present but not an array — means the file does not describe anything
|
|
11
|
+
* we can act on, so the platform disables itself while leaving cached
|
|
12
|
+
* accessories registered, and HomeKit shows them as No Response rather than
|
|
13
|
+
* losing the rooms and automations built on them.
|
|
14
|
+
*
|
|
15
|
+
* A problem with one device is different: rejecting the whole fleet because one
|
|
16
|
+
* entry is malformed would be a worse outcome than skipping that entry. Skipped
|
|
17
|
+
* devices are warned about by name and reason, because a device that silently
|
|
18
|
+
* fails to appear is the hardest kind of bug for a user to report.
|
|
19
|
+
*/
|
|
20
|
+
import { type ResolvedAccessory, type ResolvedDevice, type SliderService } from '../types';
|
|
21
|
+
/** Outcome of validating a platform configuration block. */
|
|
22
|
+
export interface ConfigValidationResult {
|
|
23
|
+
/** Fatal problems. Any entry means the platform must not start polling. */
|
|
24
|
+
errors: string[];
|
|
25
|
+
/** Problems worth reporting that do not prevent operation. */
|
|
26
|
+
warnings: string[];
|
|
27
|
+
/** Devices that survived validation, in configuration order. */
|
|
28
|
+
devices: ResolvedDevice[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Make an untrusted string safe to interpolate into a log line.
|
|
32
|
+
*
|
|
33
|
+
* Device names come from configuration and from the players themselves, so they
|
|
34
|
+
* are attacker-influenced in the threat model where someone can write to either.
|
|
35
|
+
* A newline in a log line lets them forge entries; truncation stops one long
|
|
36
|
+
* name from burying everything else.
|
|
37
|
+
*/
|
|
38
|
+
export declare function forLog(value: unknown): string;
|
|
39
|
+
/**
|
|
40
|
+
* Make an untrusted string safe to publish to HomeKit.
|
|
41
|
+
*
|
|
42
|
+
* Same sanitising as {@link forLog} but capped at HomeKit's name budget rather
|
|
43
|
+
* than the log field budget, for values that become characteristic values and
|
|
44
|
+
* are written into the accessory cache. An empty result becomes undefined, so a
|
|
45
|
+
* caller keeps the value it already had instead of publishing a blank.
|
|
46
|
+
*/
|
|
47
|
+
export declare function forDisplay(value: string): string | undefined;
|
|
48
|
+
/** True for an IPv4 literal whose octets are all in range. */
|
|
49
|
+
export declare function isIpv4(value: string): boolean;
|
|
50
|
+
/** True for something usable as an HTTP host: an IPv4 literal or a hostname. */
|
|
51
|
+
export declare function isValidHost(value: unknown): value is string;
|
|
52
|
+
/**
|
|
53
|
+
* True for an address outside the ranges treated as local.
|
|
54
|
+
*
|
|
55
|
+
* Local here is RFC 1918, RFC 6598 shared address space (CGNAT / Tailscale),
|
|
56
|
+
* loopback, and link-local. Not blocked in configuration, only warned about:
|
|
57
|
+
* the BluOS API is unauthenticated and intended for a local network, and a
|
|
58
|
+
* routable address in the configuration is much more likely to be a typo than
|
|
59
|
+
* an intention. Hostnames cannot be classified this way and are left alone
|
|
60
|
+
* in configuration; the settings-page probe uses {@link isProbeableHost}.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isNonPrivateIpv4(value: string): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* True for a host the settings-page probe is allowed to dial.
|
|
65
|
+
*
|
|
66
|
+
* Narrower than {@link isValidHost}: configuration may name a split-DNS
|
|
67
|
+
* hostname that happens to look public, but the probe is an authenticated
|
|
68
|
+
* Homebridge administrator asking this host to open a connection, so it must
|
|
69
|
+
* not become a scanner. Public IPv4 literals and multi-label public names
|
|
70
|
+
* (`example.com`) are refused. Private IPv4 (including CGNAT) and local
|
|
71
|
+
* hostnames are accepted.
|
|
72
|
+
*/
|
|
73
|
+
export declare function isProbeableHost(value: unknown): value is string;
|
|
74
|
+
/** Clamp the discovery window into the supported range. */
|
|
75
|
+
export declare function resolveDiscoveryTimeoutSec(value: unknown, warnings?: string[]): number;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a slider service, falling back to the platform default then `fan`.
|
|
78
|
+
*
|
|
79
|
+
* An empty string counts as absent, because that is what the Homebridge form
|
|
80
|
+
* writes for the per-device "use the platform setting" option; treating it as a
|
|
81
|
+
* bad value would make choosing that option override the platform setting.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveSliderService(deviceValue: unknown, platformValue: unknown, warnings?: string[], label?: string): SliderService;
|
|
84
|
+
/**
|
|
85
|
+
* Validate a platform configuration block.
|
|
86
|
+
*
|
|
87
|
+
* Never throws: the platform needs the errors and warnings in order to report
|
|
88
|
+
* them, and a configuration problem should produce a diagnosable log rather than
|
|
89
|
+
* an exception during Homebridge startup.
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateConfig(config: unknown): ConfigValidationResult;
|
|
92
|
+
/**
|
|
93
|
+
* Expand validated devices into the accessories to expose.
|
|
94
|
+
*
|
|
95
|
+
* Names are derived here rather than at use time so that duplicates can be
|
|
96
|
+
* detected once: two accessories sharing a name still work, but they make Siri
|
|
97
|
+
* ambiguous, which is worth a warning.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveAccessories(devices: readonly ResolvedDevice[], warnings?: string[]): ResolvedAccessory[];
|