homebridge-plugin-utils 1.35.0 → 2.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/README.md +130 -2
- package/build/eslint-plugin/README.md +164 -0
- package/build/eslint-plugin/config.mjs +308 -0
- package/build/eslint-plugin/index.mjs +7 -0
- package/build/eslint-plugin/plugin.mjs +46 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
- package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
- package/build/eslint-plugin/rules/comment-style.mjs +190 -0
- package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
- package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
- package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
- package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
- package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
- package/build/eslint-plugin/test-setup.mjs +21 -0
- package/build/fs-ops.mjs +184 -0
- package/build/tsconfig.json +18 -3
- package/dist/backpressure.d.ts +94 -45
- package/dist/backpressure.js +229 -93
- package/dist/backpressure.js.map +1 -1
- package/dist/cli/index.d.ts +166 -0
- package/dist/cli/index.js +551 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/clock-double.d.ts +93 -0
- package/dist/clock-double.js +141 -0
- package/dist/clock-double.js.map +1 -0
- package/dist/clock.d.ts +39 -0
- package/dist/clock.js +34 -0
- package/dist/clock.js.map +1 -0
- package/dist/disposable-stack.d.ts +59 -0
- package/dist/disposable-stack.js +155 -0
- package/dist/disposable-stack.js.map +1 -0
- package/dist/docChrome.d.ts +260 -0
- package/dist/docChrome.js +361 -0
- package/dist/docChrome.js.map +1 -0
- package/dist/eslint-plugin/config.d.mts +193 -0
- package/dist/eslint-plugin/index.d.mts +2 -0
- package/dist/eslint-plugin/plugin.d.mts +87 -0
- package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
- package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
- package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
- package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
- package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
- package/dist/featureOptions-docs.d.ts +96 -0
- package/dist/featureOptions-docs.js +193 -0
- package/dist/featureOptions-docs.js.map +1 -0
- package/dist/featureOptions.d.ts +674 -0
- package/dist/featureOptions.js +870 -0
- package/dist/featureOptions.js.map +1 -0
- package/dist/ffmpeg/codecs.d.ts +256 -72
- package/dist/ffmpeg/codecs.js +477 -262
- package/dist/ffmpeg/codecs.js.map +1 -1
- package/dist/ffmpeg/dgram-util.d.ts +46 -0
- package/dist/ffmpeg/dgram-util.js +38 -0
- package/dist/ffmpeg/dgram-util.js.map +1 -0
- package/dist/ffmpeg/exec.d.ts +83 -64
- package/dist/ffmpeg/exec.js +77 -86
- package/dist/ffmpeg/exec.js.map +1 -1
- package/dist/ffmpeg/fmp4.d.ts +49 -2
- package/dist/ffmpeg/fmp4.js +47 -11
- package/dist/ffmpeg/fmp4.js.map +1 -1
- package/dist/ffmpeg/hap-enums.d.ts +214 -0
- package/dist/ffmpeg/hap-enums.js +92 -0
- package/dist/ffmpeg/hap-enums.js.map +1 -0
- package/dist/ffmpeg/index.d.ts +16 -9
- package/dist/ffmpeg/index.js +6 -0
- package/dist/ffmpeg/index.js.map +1 -1
- package/dist/ffmpeg/mp4-assembler.d.ts +161 -0
- package/dist/ffmpeg/mp4-assembler.js +424 -0
- package/dist/ffmpeg/mp4-assembler.js.map +1 -0
- package/dist/ffmpeg/mp4-parser.d.ts +94 -0
- package/dist/ffmpeg/mp4-parser.js +130 -0
- package/dist/ffmpeg/mp4-parser.js.map +1 -0
- package/dist/ffmpeg/options.d.ts +62 -149
- package/dist/ffmpeg/options.js +608 -499
- package/dist/ffmpeg/options.js.map +1 -1
- package/dist/ffmpeg/process.d.ts +142 -96
- package/dist/ffmpeg/process.js +406 -278
- package/dist/ffmpeg/process.js.map +1 -1
- package/dist/ffmpeg/record.d.ts +325 -186
- package/dist/ffmpeg/record.js +418 -565
- package/dist/ffmpeg/record.js.map +1 -1
- package/dist/ffmpeg/recording-process-double.d.ts +157 -0
- package/dist/ffmpeg/recording-process-double.js +190 -0
- package/dist/ffmpeg/recording-process-double.js.map +1 -0
- package/dist/ffmpeg/rtp-parser.d.ts +70 -0
- package/dist/ffmpeg/rtp-parser.js +77 -0
- package/dist/ffmpeg/rtp-parser.js.map +1 -0
- package/dist/ffmpeg/rtp.d.ts +198 -141
- package/dist/ffmpeg/rtp.js +474 -251
- package/dist/ffmpeg/rtp.js.map +1 -1
- package/dist/ffmpeg/settings.d.ts +5 -2
- package/dist/ffmpeg/settings.js +20 -5
- package/dist/ffmpeg/settings.js.map +1 -1
- package/dist/ffmpeg/stream.d.ts +57 -107
- package/dist/ffmpeg/stream.js +121 -150
- package/dist/ffmpeg/stream.js.map +1 -1
- package/dist/formatters.d.ts +106 -0
- package/dist/formatters.js +174 -0
- package/dist/formatters.js.map +1 -0
- package/dist/homebridge-enums.d.ts +30 -0
- package/dist/homebridge-enums.js +17 -0
- package/dist/homebridge-enums.js.map +1 -0
- package/dist/index.d.ts +13 -6
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/logclient/auth.d.ts +114 -0
- package/dist/logclient/auth.js +199 -0
- package/dist/logclient/auth.js.map +1 -0
- package/dist/logclient/cli-run.d.ts +76 -0
- package/dist/logclient/cli-run.js +639 -0
- package/dist/logclient/cli-run.js.map +1 -0
- package/dist/logclient/cli.d.ts +3 -0
- package/dist/logclient/cli.js +97 -0
- package/dist/logclient/cli.js.map +1 -0
- package/dist/logclient/client.d.ts +145 -0
- package/dist/logclient/client.js +600 -0
- package/dist/logclient/client.js.map +1 -0
- package/dist/logclient/config.d.ts +173 -0
- package/dist/logclient/config.js +199 -0
- package/dist/logclient/config.js.map +1 -0
- package/dist/logclient/endpoints.d.ts +54 -0
- package/dist/logclient/endpoints.js +73 -0
- package/dist/logclient/endpoints.js.map +1 -0
- package/dist/logclient/filter.d.ts +45 -0
- package/dist/logclient/filter.js +51 -0
- package/dist/logclient/filter.js.map +1 -0
- package/dist/logclient/frame.d.ts +93 -0
- package/dist/logclient/frame.js +203 -0
- package/dist/logclient/frame.js.map +1 -0
- package/dist/logclient/index.d.ts +31 -0
- package/dist/logclient/index.js +12 -0
- package/dist/logclient/index.js.map +1 -0
- package/dist/logclient/parser.d.ts +211 -0
- package/dist/logclient/parser.js +393 -0
- package/dist/logclient/parser.js.map +1 -0
- package/dist/logclient/rest.d.ts +41 -0
- package/dist/logclient/rest.js +111 -0
- package/dist/logclient/rest.js.map +1 -0
- package/dist/logclient/settings.d.ts +15 -0
- package/dist/logclient/settings.js +64 -0
- package/dist/logclient/settings.js.map +1 -0
- package/dist/logclient/socket-double.d.ts +201 -0
- package/dist/logclient/socket-double.js +384 -0
- package/dist/logclient/socket-double.js.map +1 -0
- package/dist/logclient/socket.d.ts +257 -0
- package/dist/logclient/socket.js +620 -0
- package/dist/logclient/socket.js.map +1 -0
- package/dist/logclient/stitch.d.ts +83 -0
- package/dist/logclient/stitch.js +146 -0
- package/dist/logclient/stitch.js.map +1 -0
- package/dist/logclient/time-expression.d.ts +42 -0
- package/dist/logclient/time-expression.js +181 -0
- package/dist/logclient/time-expression.js.map +1 -0
- package/dist/logclient/time-window.d.ts +38 -0
- package/dist/logclient/time-window.js +53 -0
- package/dist/logclient/time-window.js.map +1 -0
- package/dist/logclient/types.d.ts +107 -0
- package/dist/logclient/types.js +6 -0
- package/dist/logclient/types.js.map +1 -0
- package/dist/mqttClient.d.ts +287 -0
- package/dist/mqttClient.js +433 -0
- package/dist/mqttClient.js.map +1 -0
- package/dist/service.d.ts +64 -15
- package/dist/service.js +93 -66
- package/dist/service.js.map +1 -1
- package/dist/ui/featureOptions.js +870 -0
- package/dist/ui/featureOptions.js.map +1 -0
- package/dist/ui/formatters.js +174 -0
- package/dist/ui/formatters.js.map +1 -0
- package/dist/ui/pluginConfigSession.mjs +141 -0
- package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
- package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
- package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
- package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
- package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
- package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
- package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
- package/dist/ui/webUi-featureOptions/state.mjs +319 -0
- package/dist/ui/webUi-featureOptions/store.mjs +181 -0
- package/dist/ui/webUi-featureOptions/utils.mjs +213 -0
- package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
- package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
- package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
- package/dist/ui/webUi-featureOptions/views/nav.mjs +341 -0
- package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
- package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
- package/dist/ui/webUi-featureOptions.mjs +702 -0
- package/dist/ui/webUi.mjs +225 -88
- package/dist/util.d.ts +679 -45
- package/dist/util.js +830 -77
- package/dist/util.js.map +1 -1
- package/package.json +33 -15
- package/build/eslint-rules.mjs +0 -511
- package/dist/featureoptions.d.ts +0 -264
- package/dist/featureoptions.js +0 -480
- package/dist/featureoptions.js.map +0 -1
- package/dist/mqttclient.d.ts +0 -178
- package/dist/mqttclient.js +0 -310
- package/dist/mqttclient.js.map +0 -1
- package/dist/ui/featureoptions.js +0 -480
- package/dist/ui/featureoptions.js.map +0 -1
- package/dist/ui/webUi-featureoptions.mjs +0 -3765
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* mqttClient.ts: Signal-driven MQTT client for Homebridge plugins.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* AsyncDisposable MQTT client whose connection lifetime is a composed {@link AbortSignal}.
|
|
7
|
+
*
|
|
8
|
+
* The client wraps the underlying MQTT.js connection in the same lifetime shape every other long-lived resource class in this library uses: a composed
|
|
9
|
+
* `AbortSignal`, a single `abort()` verb, and `Symbol.asyncDispose` for scope-bound ownership. Per-subscription and per-publish signals compose into the connection-
|
|
10
|
+
* level signal so that tearing down a specific handler or cancelling a single publish unwinds cleanly without touching the rest of the client.
|
|
11
|
+
*
|
|
12
|
+
* Non-abort transient disconnects continue to trigger MQTT.js's own auto-reconnect - `abort()` is specifically "this client is done for good," not "pause until
|
|
13
|
+
* further notice." Calling `abort()` (or letting a parent signal fire) ends the connection permanently via `mqtt.end(true)`, rejects any pending publishes with the
|
|
14
|
+
* signal's reason, clears all subscription state, and makes every subsequent call a no-op.
|
|
15
|
+
*
|
|
16
|
+
* Construction fails loudly on invalid broker URLs: the constructor throws with the underlying mqtt.js error attached as `cause`, so a misconfigured plugin cannot
|
|
17
|
+
* silently sit in a zombie state where every call either pretends to succeed or throws an unrelated abort error. Callers who want graceful degradation wrap the
|
|
18
|
+
* `new MqttClient(...)` call in their own try/catch.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { HbpuAbortError, composeSignals, formatErrorMessage, markHandled, onAbort, runWithAbort, waitWithSignal } from "./util.js";
|
|
23
|
+
import { connect } from "mqtt";
|
|
24
|
+
import util from "node:util";
|
|
25
|
+
// Default reconnect interval, in seconds. A one-minute cadence gives a typical LAN broker (a mosquitto instance restarting or a brief network blip) time to come
|
|
26
|
+
// back without hammering it with reconnect attempts, while still recovering well within the timescale a user would notice as "the plugin is broken."
|
|
27
|
+
const MQTT_DEFAULT_RECONNECT_INTERVAL = 60;
|
|
28
|
+
// Module-scope success sentinel for `subscribeSet` handler invocations. `runWithAbort` returns `null` when its composed signal fires, so we use a sentinel to
|
|
29
|
+
// distinguish "user setter ran to completion" from "the invocation was cancelled." Hoisted once at module load rather than allocated per message, and declared as
|
|
30
|
+
// `unique symbol` so the typed comparison below keeps its narrowing precision.
|
|
31
|
+
const SUBSCRIBE_SET_OK = Symbol("mqtt:subscribeSet:ok");
|
|
32
|
+
/**
|
|
33
|
+
* Route a transport-level MQTT error to the appropriate log line and signal whether the underlying transport should be ended. Pure function: no class state, no
|
|
34
|
+
* mqtt.js handles, no closure over the live client. The wiring layer in {@link MqttClient} forwards every `client.on("error", ...)` invocation through here and acts
|
|
35
|
+
* on the returned `endTransport` flag.
|
|
36
|
+
*
|
|
37
|
+
* The routing paths mirror the transport-error categories HBPU distinguishes:
|
|
38
|
+
*
|
|
39
|
+
* - `ECONNREFUSED` - the broker host is up but no listener accepts the connection. Recoverable; auto-reconnect handles it.
|
|
40
|
+
* - `ECONNRESET` - the broker accepted then dropped the connection. Recoverable; auto-reconnect handles it.
|
|
41
|
+
* - `ENOTFOUND` - DNS could not resolve the broker hostname. Non-recoverable - retrying the same hostname will keep failing - so we end the transport permanently
|
|
42
|
+
* and emit a standalone log line without the retry-cadence suffix.
|
|
43
|
+
* - default - any other error code (or none). Logged through the retry-cadence formatter with `util.inspect` of the error, so a future mqtt.js error code we
|
|
44
|
+
* did not anticipate still surfaces in the log stream rather than being silently swallowed.
|
|
45
|
+
*
|
|
46
|
+
* @param error - The error event payload from the underlying mqtt.js client.
|
|
47
|
+
* @param log - Logger used to emit the routed message.
|
|
48
|
+
* @param reconnectInterval - Configured reconnect interval (in seconds) used to format the retry-cadence suffix.
|
|
49
|
+
*
|
|
50
|
+
* @returns A {@link MqttBrokerErrorResult} indicating whether the wiring layer should end the transport.
|
|
51
|
+
*
|
|
52
|
+
* @category Utilities
|
|
53
|
+
*/
|
|
54
|
+
export function routeMqttBrokerError(error, log, reconnectInterval) {
|
|
55
|
+
const logError = (message) => {
|
|
56
|
+
log.error("MQTT Broker: %s. Will retry again in %s second%s.", message, reconnectInterval, (reconnectInterval === 1) ? "" : "s");
|
|
57
|
+
};
|
|
58
|
+
switch (error.code) {
|
|
59
|
+
case "ECONNREFUSED":
|
|
60
|
+
logError("Connection refused");
|
|
61
|
+
return { endTransport: false };
|
|
62
|
+
case "ECONNRESET":
|
|
63
|
+
logError("Connection reset");
|
|
64
|
+
return { endTransport: false };
|
|
65
|
+
case "ENOTFOUND":
|
|
66
|
+
log.error("MQTT Broker: Hostname or IP address not found.");
|
|
67
|
+
return { endTransport: true };
|
|
68
|
+
default:
|
|
69
|
+
logError(util.inspect(error, { sorted: true }));
|
|
70
|
+
return { endTransport: false };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Route the outcome of a `subscribeGet` response publish to the appropriate log line. Pure function: no class state, no mqtt.js handles, no closure over the live
|
|
75
|
+
* client. The wiring in {@link MqttClient.subscribeGet} forwards each `.then` / `.catch` settlement here, and tests cover both branches by calling the function
|
|
76
|
+
* directly with synthetic `{ ok: true }` and `{ ok: false, error: ... }` outcomes - bypassing the real-broker substrate where a forced QoS-0 publish failure would
|
|
77
|
+
* require contrived socket-level setup that is not worth the test-architecture complexity.
|
|
78
|
+
*
|
|
79
|
+
* @param log - Logger that receives the routed message.
|
|
80
|
+
* @param type - Human-readable label (the `type` argument the caller passed to `subscribeGet`).
|
|
81
|
+
* @param outcome - The publish outcome. See {@link GetterPublishOutcome}.
|
|
82
|
+
*
|
|
83
|
+
* @category Utilities
|
|
84
|
+
*/
|
|
85
|
+
export function logGetterPublishOutcome(log, type, outcome) {
|
|
86
|
+
if (outcome.ok) {
|
|
87
|
+
log.info("MQTT: %s status published.", type);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
log.error("MQTT: failed to publish %s status: %s.", type, formatErrorMessage(outcome.error));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Signal-driven MQTT client with automatic topic-prefix management, composed connection lifetime, and per-operation signal support.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* import { MqttClient } from "homebridge-plugin-utils";
|
|
99
|
+
*
|
|
100
|
+
* await using mqtt = new MqttClient({ brokerUrl: "mqtt://localhost:1883", log, topicPrefix: "homebridge" }, { signal: platform.signal });
|
|
101
|
+
*
|
|
102
|
+
* // A subscription that auto-unsubscribes on the per-feature signal.
|
|
103
|
+
* const feature = new AbortController();
|
|
104
|
+
*
|
|
105
|
+
* mqtt.subscribe("device1/status", (payload) => log.info("Status: %s.", payload.toString()), { signal: feature.signal });
|
|
106
|
+
*
|
|
107
|
+
* // Abort-aware publish.
|
|
108
|
+
* await mqtt.publish("device1/status", "on");
|
|
109
|
+
* ```
|
|
110
|
+
*
|
|
111
|
+
* @category Utilities
|
|
112
|
+
*/
|
|
113
|
+
export class MqttClient {
|
|
114
|
+
/**
|
|
115
|
+
* The composed abort signal representing this client's lifetime. Aborts exactly once when {@link MqttClient.abort} is called or when the parent signal fires.
|
|
116
|
+
*/
|
|
117
|
+
signal;
|
|
118
|
+
#brokerUrl;
|
|
119
|
+
#controller;
|
|
120
|
+
#log;
|
|
121
|
+
#mqtt;
|
|
122
|
+
#reconnectInterval;
|
|
123
|
+
#subscriptions;
|
|
124
|
+
#topicPrefix;
|
|
125
|
+
#isConnected;
|
|
126
|
+
/**
|
|
127
|
+
* Construct and start a new MQTT client.
|
|
128
|
+
*
|
|
129
|
+
* Connection is initiated synchronously as part of construction; there is no separate `connect()` step. A synchronous failure from mqtt.js (typically an invalid
|
|
130
|
+
* broker URL) surfaces as an `Error` wrapping the underlying cause, so a misconfigured plugin fails loudly instead of living in a zombie state. Network-level
|
|
131
|
+
* failures (an unreachable broker reachable by a valid URL) do not throw - they surface asynchronously through the client's `error` event, are logged, and trigger
|
|
132
|
+
* mqtt.js's built-in auto-reconnect until {@link MqttClient.abort} or a parent signal ends the client for good. A pre-aborted parent signal still constructs
|
|
133
|
+
* a client (so `#mqtt` stays non-null) and then immediately runs the regular teardown path.
|
|
134
|
+
*
|
|
135
|
+
* @param config - Static broker / topic configuration. See {@link MqttConfig}.
|
|
136
|
+
* @param init - Optional init options. See {@link MqttClientInit}.
|
|
137
|
+
*
|
|
138
|
+
* @throws `Error` (with the underlying mqtt.js error attached as `cause`) when mqtt.js's `connect()` fails synchronously.
|
|
139
|
+
*/
|
|
140
|
+
constructor(config, init = {}) {
|
|
141
|
+
this.#brokerUrl = config.brokerUrl;
|
|
142
|
+
this.#isConnected = false;
|
|
143
|
+
this.#log = config.log;
|
|
144
|
+
this.#reconnectInterval = config.reconnectInterval ?? MQTT_DEFAULT_RECONNECT_INTERVAL;
|
|
145
|
+
this.#subscriptions = new Map();
|
|
146
|
+
this.#topicPrefix = config.topicPrefix;
|
|
147
|
+
this.#controller = new AbortController();
|
|
148
|
+
this.signal = composeSignals(init.signal, this.#controller.signal);
|
|
149
|
+
// Establish the underlying MQTT.js connection unconditionally so `#mqtt` is always a live reference by the time the constructor returns. A synchronous failure
|
|
150
|
+
// (typically URL parsing) is wrapped in an Error whose `cause` carries the original error, so callers distinguish via `error.cause` rather than matching on
|
|
151
|
+
// mqtt.js's internal message text.
|
|
152
|
+
try {
|
|
153
|
+
// We connect with `rejectUnauthorized: false` deliberately. The broker URL is user-supplied and, in the typical Homebridge deployment, points at a local
|
|
154
|
+
// LAN broker (a mosquitto instance on the same network) that is frequently fronted by a self-signed certificate with no chain to a public CA. Enforcing strict
|
|
155
|
+
// certificate-chain verification would break those common self-signed setups and force users to hand-wire a CA bundle, so we accept the broker's certificate as
|
|
156
|
+
// presented. The trust boundary here is the user's own network and their own broker, not a public endpoint, which makes this the correct posture rather than a
|
|
157
|
+
// weakening of transport security.
|
|
158
|
+
this.#mqtt = connect(this.#brokerUrl, { reconnectPeriod: this.#reconnectInterval * 1000, rejectUnauthorized: false });
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
throw new Error("MqttClient: connection setup failed.", { cause: error });
|
|
162
|
+
}
|
|
163
|
+
this.#wireMqttEvents(this.#mqtt);
|
|
164
|
+
// Teardown convergence point. `onAbort` registers the one-shot teardown handler AND runs it synchronously when the signal is already aborted at construction
|
|
165
|
+
// time (the AbortSignal spec does not re-dispatch historical events, so bare `addEventListener` would silently skip the handler for a pre-aborted parent).
|
|
166
|
+
// Running the handler in both paths means pre-aborted construction unwinds identically to a mid-session abort - the brief mqtt.js `connect()` call above is
|
|
167
|
+
// cancelled by `end(true)` before any network work proceeds.
|
|
168
|
+
onAbort(this.signal, () => this.#teardown());
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Publish `payload` to `topic`, returning a promise that resolves when the broker acknowledges the publish, or rejects on failure or abort.
|
|
172
|
+
*
|
|
173
|
+
* The topic is prefixed with the configured {@link MqttConfig.topicPrefix} before being sent; callers supply the topic tail (for example, `"device1/status"`).
|
|
174
|
+
*
|
|
175
|
+
* @param topic - The relative topic (tail) to publish to.
|
|
176
|
+
* @param payload - The payload to publish. Buffers and strings are passed through unchanged.
|
|
177
|
+
* @param init - Optional per-publish options. See {@link MqttPublishInit}.
|
|
178
|
+
*
|
|
179
|
+
* @returns A promise that resolves once the broker acknowledges, or rejects on error or abort.
|
|
180
|
+
*/
|
|
181
|
+
async publish(topic, payload, init = {}) {
|
|
182
|
+
const composed = composeSignals(this.signal, init.signal);
|
|
183
|
+
// Short-circuit pre-aborted signals before queueing anything into mqtt.js. Without this check, a publish issued after the client has already torn down would
|
|
184
|
+
// still enqueue inside mqtt.js's internal buffer (which is already being flushed by `end(true)`), producing a phantom write.
|
|
185
|
+
composed.throwIfAborted();
|
|
186
|
+
const full = this.#expandTopic(topic);
|
|
187
|
+
this.#log.debug("MQTT publish: %s.", full);
|
|
188
|
+
// Wrap mqtt.js's callback-style publish in a promise, then race it against the composed signal through `waitWithSignal` - the canonical primitive every other
|
|
189
|
+
// signal-aware wait in this library uses. `Promise.withResolvers` is the codebase-wide pattern for callback-bridged deferreds; using it here keeps the hop from
|
|
190
|
+
// mqtt.js's callback shape to a Promise on the same primitive every other wrap in HBPU uses.
|
|
191
|
+
const { promise: ackPromise, resolve, reject } = Promise.withResolvers();
|
|
192
|
+
this.#mqtt.publish(full, payload, (error) => {
|
|
193
|
+
if (error) {
|
|
194
|
+
reject(error);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
resolve();
|
|
198
|
+
});
|
|
199
|
+
return waitWithSignal(ackPromise, composed);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Subscribe to `topic` with the given handler. The topic is prefixed with the configured {@link MqttConfig.topicPrefix} before being registered with the broker.
|
|
203
|
+
* Multiple handlers may subscribe to the same topic; each gets independent delivery.
|
|
204
|
+
*
|
|
205
|
+
* @param topic - The relative topic (tail) to subscribe to.
|
|
206
|
+
* @param handler - Callback invoked with each received payload.
|
|
207
|
+
* @param init - Optional per-subscription options. See {@link MqttSubscribeInit}.
|
|
208
|
+
*/
|
|
209
|
+
subscribe(topic, handler, init = {}) {
|
|
210
|
+
// Ignore subscribes that cannot produce any effect: the client is already dead or the caller handed us a pre-aborted per-subscription signal. Early-return keeps
|
|
211
|
+
// the subscription map honest - nothing gets registered that cannot receive.
|
|
212
|
+
if (this.signal.aborted || init.signal?.aborted) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const full = this.#expandTopic(topic);
|
|
216
|
+
this.#log.debug("MQTT subscribe: %s.", full);
|
|
217
|
+
let handlers = this.#subscriptions.get(full);
|
|
218
|
+
if (!handlers) {
|
|
219
|
+
handlers = new Set();
|
|
220
|
+
this.#subscriptions.set(full, handlers);
|
|
221
|
+
// First handler on this topic: issue the wire-level subscribe. Subsequent handlers on the same topic reuse the existing broker subscription, matching the
|
|
222
|
+
// "pay once per topic" cost model the MQTT protocol naturally provides.
|
|
223
|
+
this.#mqtt.subscribe(full);
|
|
224
|
+
}
|
|
225
|
+
handlers.add(handler);
|
|
226
|
+
// Per-subscription cleanup: compose the caller's signal with the connection-level signal so either teardown path fires the listener exactly once. Attaching the
|
|
227
|
+
// listener to the composed signal (rather than to `init.signal` directly) ensures the listener auto-releases when the client aborts even if the caller never
|
|
228
|
+
// aborts their per-subscription controller - the composed signal is what prevents the closure from pinning the handler past the connection's lifetime.
|
|
229
|
+
if (init.signal) {
|
|
230
|
+
const composed = composeSignals(this.signal, init.signal);
|
|
231
|
+
onAbort(composed, () => this.#removeHandler(full, handler));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Subscribe to the `/get` child of `topic`. When a `"true"` message arrives on the get topic, the provided `getValue` callback runs and its return value is
|
|
236
|
+
* published back on the parent topic. The classic HomeKit "get" pattern, wrapped once.
|
|
237
|
+
*
|
|
238
|
+
* @param topic - The relative topic (tail); the `/get` suffix is appended automatically.
|
|
239
|
+
* @param type - Human-readable label used in log messages (for example, `"Temperature"`).
|
|
240
|
+
* @param getValue - Callback returning the current value as a string, invoked on each incoming `"true"` message.
|
|
241
|
+
* @param init - Optional per-subscription options. See {@link MqttSubscribeInit}.
|
|
242
|
+
*/
|
|
243
|
+
subscribeGet(topic, type, getValue, init = {}) {
|
|
244
|
+
this.subscribe(topic + "/get", (message) => {
|
|
245
|
+
const value = message.toString().toLowerCase();
|
|
246
|
+
// The get-pattern contract: only `"true"` triggers a republish. Other values (empty, `"false"`, noise) are ignored silently so the broker-side fan-out can
|
|
247
|
+
// request status without every listener responding.
|
|
248
|
+
if (value !== "true") {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Sequence both log lines through {@link logGetterPublishOutcome} so the routing between success and failure is expressible as a pure function and testable in
|
|
252
|
+
// isolation. The publish promise settles exactly once - either `.then` (success) or `.catch` (failure) runs - and each path forwards its outcome through the
|
|
253
|
+
// single routing point, keeping the log surface honest about what actually happened on the wire.
|
|
254
|
+
void this.publish(topic, getValue())
|
|
255
|
+
.then(() => logGetterPublishOutcome(this.#log, type, { ok: true }))
|
|
256
|
+
.catch((error) => logGetterPublishOutcome(this.#log, type, { error, ok: false }));
|
|
257
|
+
}, init);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Subscribe to the `/set` child of `topic`. Each incoming message invokes `setValue` with the lowercased normalized value, the raw message string, and an
|
|
261
|
+
* {@link AbortSignal} that composes the connection-level signal with the optional per-invocation `timeout`. Signal-aware setters forward that signal to cancellation-
|
|
262
|
+
* capable APIs so the setter's work actually stops when the timeout elapses or the client aborts; signal-unaware setters continue to run but the subscription slot
|
|
263
|
+
* is released either way, so a hanging setter cannot tie up the slot indefinitely.
|
|
264
|
+
*
|
|
265
|
+
* @param topic - The relative topic (tail); the `/set` suffix is appended automatically.
|
|
266
|
+
* @param type - Human-readable label used in log messages.
|
|
267
|
+
* @param setValue - Callback invoked with each received value. Receives three arguments: `(value, rawValue, signal)`. See {@link MqttSetHandler}.
|
|
268
|
+
* @param init - Optional per-subscription options including a handler-invocation `timeout`. See {@link MqttSubscribeSetInit}.
|
|
269
|
+
*/
|
|
270
|
+
subscribeSet(topic, type, setValue, init = {}) {
|
|
271
|
+
const timeout = init.timeout;
|
|
272
|
+
this.subscribe(topic + "/set", async (message) => {
|
|
273
|
+
const rawValue = message.toString();
|
|
274
|
+
const value = rawValue.toLowerCase();
|
|
275
|
+
try {
|
|
276
|
+
// `runWithAbort` composes the connection-level signal with the optional per-invocation timeout and passes the composed signal to its factory. We forward that
|
|
277
|
+
// signal into the user's setter so signal-aware setters can actually cancel in-flight work on timeout or teardown. A module-level sentinel distinguishes "user
|
|
278
|
+
// setter ran to completion" from "runWithAbort returned null because the signal aborted," keeping cancellation on the `warn` log path rather than the error
|
|
279
|
+
// path. The `{ signal, timeout?: number }` option form accepts `timeout: undefined`, so a single call shape covers both the with-timeout and without-timeout
|
|
280
|
+
// cases.
|
|
281
|
+
const result = await runWithAbort(async (setterSignal) => {
|
|
282
|
+
await setValue(value, rawValue, setterSignal);
|
|
283
|
+
return SUBSCRIBE_SET_OK;
|
|
284
|
+
}, { signal: this.signal, timeout });
|
|
285
|
+
if (result !== SUBSCRIBE_SET_OK) {
|
|
286
|
+
this.#log.warn("MQTT: set handler for %s was cancelled before completion.", type);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.#log.info("MQTT: set message received for %s: %s.", type, value);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
this.#log.error("MQTT: error setting %s to %s: %s.", type, value, formatErrorMessage(error));
|
|
293
|
+
}
|
|
294
|
+
}, init);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Unsubscribe all handlers for the specified `(id, topic)` tuple. Reconstructs the topic using the configured {@link MqttConfig.topicPrefix}, removes the
|
|
298
|
+
* subscription from the internal map, and issues the wire-level unsubscribe. Preserved as a separate imperative verb for the mid-session feature-toggle pattern
|
|
299
|
+
* where the caller has the `(id, topic)` tuple but never retained a dedicated controller.
|
|
300
|
+
*
|
|
301
|
+
* Deliberately does not accept a `{ signal }` option: unsubscribe is synchronous and has nothing to cancel, and exposing a vestigial signal would suggest a
|
|
302
|
+
* cancellation semantic the method cannot deliver. Callers composing teardown through a signal remove handlers by aborting the per-subscription signal they passed
|
|
303
|
+
* to `subscribe*` instead.
|
|
304
|
+
*
|
|
305
|
+
* @param id - The device or accessory identifier portion of the topic. An empty string short-circuits the whole call.
|
|
306
|
+
* @param topic - The topic tail relative to the id.
|
|
307
|
+
*/
|
|
308
|
+
unsubscribe(id, topic) {
|
|
309
|
+
// No-op on an aborted client: the wire-level subscribe has already been released by `#teardown`, and the subscription map is empty. Checking the signal up front
|
|
310
|
+
// lets subsequent `this.#mqtt` accesses run unguarded.
|
|
311
|
+
if (this.signal.aborted || !id) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const full = this.#expandTopic(id + "/" + topic);
|
|
315
|
+
this.#subscriptions.delete(full);
|
|
316
|
+
this.#mqtt.unsubscribe(full);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Abort the client and tear the connection down. Defaults to `HbpuAbortError("shutdown")` when no reason is supplied; explicit reasons pass through unchanged.
|
|
320
|
+
*
|
|
321
|
+
* Safe to call more than once. After this runs, every subsequent `publish`, `subscribe*`, or `unsubscribe` call is a no-op.
|
|
322
|
+
*
|
|
323
|
+
* @param reason - Optional abort reason. Typically an {@link HbpuAbortError}; platform errors also interoperate by convention.
|
|
324
|
+
*/
|
|
325
|
+
abort(reason) {
|
|
326
|
+
if (this.aborted) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
this.#controller.abort(reason ?? new HbpuAbortError("shutdown"));
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* `AsyncDisposable` implementation. Aborts the client (defaulting to `"shutdown"`), which tears the MQTT connection down and rejects any pending publishes through
|
|
333
|
+
* the regular teardown path.
|
|
334
|
+
*
|
|
335
|
+
* @returns A promise that resolves once teardown has been scheduled. MQTT.js's `end(true)` completes synchronously for userland purposes, so the awaited microtask
|
|
336
|
+
* is all the ordering the caller needs.
|
|
337
|
+
*/
|
|
338
|
+
async [Symbol.asyncDispose]() {
|
|
339
|
+
this.abort();
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* `true` once `this.signal` has aborted. Derived from the signal; no independent state.
|
|
343
|
+
*/
|
|
344
|
+
get aborted() {
|
|
345
|
+
return this.signal.aborted;
|
|
346
|
+
}
|
|
347
|
+
// Wire up the MQTT.js event handlers that drive message dispatch, connection lifecycle logging, and error escalation. Extracted into a helper so the constructor
|
|
348
|
+
// reads as a linear flow (set up state -> compose signal -> connect -> wire events).
|
|
349
|
+
#wireMqttEvents(client) {
|
|
350
|
+
client.on("connect", () => {
|
|
351
|
+
this.#isConnected = true;
|
|
352
|
+
// The replace strips the password from the broker URL's `user:password@host` userinfo before logging, so credentials never reach the log stream. The capture
|
|
353
|
+
// groups preserve the scheme-plus-username prefix and the `@host` suffix while swapping only the password segment for REDACTED.
|
|
354
|
+
this.#log.info("MQTT Broker: Connected to %s (topic: %s).", this.#brokerUrl.replace(/^(.*:\/\/.*:)(.*)(@.*)$/, "$1REDACTED$3"), this.#topicPrefix);
|
|
355
|
+
});
|
|
356
|
+
client.on("close", () => {
|
|
357
|
+
// Only log a disconnect when we had previously connected - otherwise we would spam the log on every failed connect attempt during a retry loop. Tracking
|
|
358
|
+
// `#isConnected` as an edge flag (true on connect, false on close) captures the transition cleanly; mqtt.js's own `.connected` property is already `false` by
|
|
359
|
+
// the time this listener fires, so it cannot answer the "were we previously connected?" question on its own.
|
|
360
|
+
if (!this.#isConnected) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.#isConnected = false;
|
|
364
|
+
this.#log.info("MQTT Broker: Connection closed.");
|
|
365
|
+
});
|
|
366
|
+
client.on("message", (topic, message) => {
|
|
367
|
+
const handlers = this.#subscriptions.get(topic);
|
|
368
|
+
if (!handlers) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
// Snapshot the handler set before iterating so a handler that removes itself (or a sibling) mid-dispatch cannot alter which handlers this dispatch pass
|
|
372
|
+
// invokes. Iterating the live Set directly would still be spec-correct - Set iteration order and hole-skipping on delete are well-defined - but it would make
|
|
373
|
+
// the invocation set implicit and mutation-order-dependent. The snapshot cost is O(n) in handlers per topic, same as the dispatch itself, so the overhead is
|
|
374
|
+
// negligible. Sync handlers run inline so their effects are observable on the calling turn (matching
|
|
375
|
+
// EventEmitter's dispatch model and the public contract these tests assert); only the per-handler error handling is split between the sync and async legs,
|
|
376
|
+
// both of which route through the same `logHandlerError` so the log surface is single-source-of-truth. One bad handler logs but does not destabilize the
|
|
377
|
+
// connection or skip its siblings.
|
|
378
|
+
const logHandlerError = (err) => {
|
|
379
|
+
this.#log.error("MQTT: handler for %s threw: %s.", topic, formatErrorMessage(err));
|
|
380
|
+
};
|
|
381
|
+
for (const handler of [...handlers]) {
|
|
382
|
+
try {
|
|
383
|
+
const result = handler(message);
|
|
384
|
+
if (result instanceof Promise) {
|
|
385
|
+
void markHandled(result.catch(logHandlerError));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
logHandlerError(err);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
client.on("error", (error) => {
|
|
394
|
+
// Route the error through the pure {@link routeMqttBrokerError} helper, which selects the right log line for each known errno code (and the default branch for
|
|
395
|
+
// unknown codes). The wiring layer's only side-effect responsibility is acting on the returned `endTransport` flag - the routing logic is testable in isolation
|
|
396
|
+
// through direct invocation against synthetic errors, so this `client.on("error", ...)` callback stays a thin adapter from the mqtt.js event surface to the
|
|
397
|
+
// pure routing function.
|
|
398
|
+
const result = routeMqttBrokerError(error, this.#log, this.#reconnectInterval);
|
|
399
|
+
if (result.endTransport) {
|
|
400
|
+
this.#mqtt.end(true);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
// Remove a single handler from a topic's subscription set. When the last handler leaves, the underlying MQTT subscription is dropped so idle topics do not continue
|
|
405
|
+
// consuming broker bandwidth. Safe to call for handlers that were never registered (silently no-ops) and safe to call after the connection has aborted (the
|
|
406
|
+
// subscription map is cleared by `#teardown`, so the initial `!handlers` check short-circuits before any `#mqtt` interaction).
|
|
407
|
+
#removeHandler(full, handler) {
|
|
408
|
+
const handlers = this.#subscriptions.get(full);
|
|
409
|
+
if (!handlers) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
handlers.delete(handler);
|
|
413
|
+
if (handlers.size === 0) {
|
|
414
|
+
this.#subscriptions.delete(full);
|
|
415
|
+
this.#mqtt.unsubscribe(full);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Expand a relative topic tail into the full broker-facing topic. Always concatenates with the configured prefix; the caller is responsible for any id structure
|
|
419
|
+
// inside the tail. Keeping this as a single small helper means every publish / subscribe site goes through identical expansion logic, avoiding drift.
|
|
420
|
+
#expandTopic(topic) {
|
|
421
|
+
return this.#topicPrefix + "/" + topic;
|
|
422
|
+
}
|
|
423
|
+
// Single teardown convergence point, fired exactly once when `this.signal` aborts. Clears the subscription map so multi-consumer fan-out stops delivering messages
|
|
424
|
+
// on the way out, then ends the MQTT.js connection with `force = true` so any in-flight publish / subscribe packets are dropped rather than awaited - the client is
|
|
425
|
+
// unambiguously done, and the underlying library's reconnect logic exits permanently. The `#mqtt` reference is not nulled: callers never reach `#mqtt` accesses
|
|
426
|
+
// when the signal is aborted (every public method short-circuits on `signal.aborted`), and keeping the reference preserves the `readonly #mqtt: MqttJsClient`
|
|
427
|
+
// guarantee that lets TypeScript drop every non-null assertion in the live paths.
|
|
428
|
+
#teardown() {
|
|
429
|
+
this.#subscriptions.clear();
|
|
430
|
+
this.#mqtt.end(true);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
//# sourceMappingURL=mqttClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mqttClient.js","sourceRoot":"","sources":["../src/mqttClient.ts"],"names":[],"mappings":"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"}
|
package/dist/service.d.ts
CHANGED
|
@@ -4,18 +4,38 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import type { PlatformAccessory, Service, WithUUID } from "homebridge";
|
|
7
|
-
import {
|
|
7
|
+
import type { Nullable } from "./util.ts";
|
|
8
|
+
/**
|
|
9
|
+
* The constructor shape {@link acquireService} expects for a Service subclass. Every HAP Service subclass (Lightbulb, Switch, Television, ...) satisfies both halves
|
|
10
|
+
* of this intersection naturally:
|
|
11
|
+
*
|
|
12
|
+
* - `WithUUID<typeof Service>` - provides the static `UUID` property AND assignability to HAP's `getService` / `getServiceById` lookup APIs that require this exact
|
|
13
|
+
* shape.
|
|
14
|
+
* - `new (displayName?: string, subtype?: string) => T` - the actual runtime constructor signature every Service subclass exposes; supersedes the BASE Service
|
|
15
|
+
* class's `(displayName, UUID, subtype?)` signature that the wider `WithUUID<typeof Service>` would otherwise surface.
|
|
16
|
+
*
|
|
17
|
+
* Intersecting both shapes lets the function invoke `new serviceType(sanitized, subtype)` against an honest type-checked signature without any cast or non-null
|
|
18
|
+
* assertion.
|
|
19
|
+
*
|
|
20
|
+
* @typeParam T - The concrete Service subclass produced by the constructor. Inferred from the call site so callers receive the specific subclass type back.
|
|
21
|
+
*
|
|
22
|
+
* @category Accessory
|
|
23
|
+
*/
|
|
24
|
+
export type AcquireServiceTarget<T extends Service = Service> = WithUUID<typeof Service> & (new (displayName?: string, subtype?: string) => T);
|
|
8
25
|
/**
|
|
9
26
|
* Utility method that either creates a new service on an accessory if needed, or returns an existing one. Optionally, it executes a callback to initialize a new
|
|
10
27
|
* service instance. Additionally, the various name characteristics of the service are set to the specified name, and optionally added if necessary.
|
|
11
28
|
*
|
|
29
|
+
* @typeParam T - The concrete Service subclass being acquired. Inferred from `serviceType` so callers receive the specific subclass type back rather than
|
|
30
|
+
* the wider `Service` type.
|
|
12
31
|
* @param accessory - The Homebridge accessory to check or modify.
|
|
13
|
-
* @param serviceType - The type of service to instantiate or retrieve.
|
|
32
|
+
* @param serviceType - The type of service to instantiate or retrieve. Must be a HAP Service subclass with the standard `(displayName?, subtype?)` constructor;
|
|
33
|
+
* see {@link AcquireServiceTarget}.
|
|
14
34
|
* @param name - Name to be displayed to the end user for this service.
|
|
15
35
|
* @param subtype - Optional service subtype to uniquely identify the service.
|
|
16
36
|
* @param onServiceCreate - Optional callback invoked only when a new service is created, receiving the new service as its argument.
|
|
17
37
|
*
|
|
18
|
-
* @returns Returns the created or retrieved service
|
|
38
|
+
* @returns Returns the created or retrieved service. Construction failures throw rather than returning `null`.
|
|
19
39
|
*
|
|
20
40
|
* @remarks
|
|
21
41
|
* This method ensures that the service's display name and available name characteristics are updated to the specified name. If `onServiceCreate` is provided,
|
|
@@ -25,10 +45,11 @@ import { type Nullable } from "./util.js";
|
|
|
25
45
|
*
|
|
26
46
|
* @example
|
|
27
47
|
* ```typescript
|
|
28
|
-
* // Example: Ensure a Lightbulb service exists with a user-friendly name, and initialize it if newly created.
|
|
29
|
-
*
|
|
48
|
+
* // Example: Ensure a Lightbulb service exists with a user-friendly name, and initialize it if newly created. The return type is narrowed to `Lightbulb | null`,
|
|
49
|
+
* // so callers can invoke subclass-specific behavior on the result without casts.
|
|
50
|
+
* const lightbulbService = acquireService(accessory, hap.Service.Lightbulb, "Living Room Lamp", undefined, (svc): void => {
|
|
30
51
|
*
|
|
31
|
-
* // Called only if the service is newly created.
|
|
52
|
+
* // Called only if the service is newly created. `svc` is typed as `Lightbulb` here.
|
|
32
53
|
* svc.setCharacteristic(hap.Characteristic.On, false);
|
|
33
54
|
* });
|
|
34
55
|
*
|
|
@@ -39,11 +60,11 @@ import { type Nullable } from "./util.js";
|
|
|
39
60
|
* }
|
|
40
61
|
* ```
|
|
41
62
|
*
|
|
42
|
-
* @see setServiceName
|
|
43
|
-
* @see validService
|
|
63
|
+
* @see setServiceName - updates the newly created (or existing) service's name-related characteristics.
|
|
64
|
+
* @see validService - validate or prune services after acquisition.
|
|
44
65
|
* @category Accessory
|
|
45
66
|
*/
|
|
46
|
-
export declare function acquireService(accessory: PlatformAccessory, serviceType:
|
|
67
|
+
export declare function acquireService<T extends Service>(accessory: PlatformAccessory, serviceType: AcquireServiceTarget<T>, name: string, subtype?: string, onServiceCreate?: (svc: T) => void): Nullable<T>;
|
|
47
68
|
/**
|
|
48
69
|
* Validates whether a specific service should exist on the given accessory, removing the service if it fails validation.
|
|
49
70
|
*
|
|
@@ -74,17 +95,44 @@ export declare function acquireService(accessory: PlatformAccessory, serviceType
|
|
|
74
95
|
* validService(accessory, Service.Switch, (hasService) => hasService || config.enableSwitch);
|
|
75
96
|
* ```
|
|
76
97
|
*
|
|
77
|
-
* @see acquireService
|
|
98
|
+
* @see acquireService - to add or retrieve services.
|
|
78
99
|
* @category Accessory
|
|
79
100
|
*/
|
|
80
101
|
export declare function validService(accessory: PlatformAccessory, serviceType: WithUUID<typeof Service>, validate: boolean | ((hasService: boolean) => boolean), subtype?: string): boolean;
|
|
81
102
|
/**
|
|
82
|
-
*
|
|
103
|
+
* Build a `validService` predicate for a service gated on a hardware capability and a user toggle, applying an additive-eager / subtractive-conservative asymmetry
|
|
104
|
+
* between the two: the user `toggle` is absolute - when false, the service is removed - while the hardware `capability` is conservative - an existing service is kept
|
|
105
|
+
* through a transient capability-false, and a new service is created only when the capability reports.
|
|
106
|
+
*
|
|
107
|
+
* @param options - The `capability` and `toggle` inputs for the gate.
|
|
108
|
+
*
|
|
109
|
+
* @returns A `validService` function-form predicate, `(hasService) => toggle && (hasService || capability)`.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* Pass the result as `validService`'s `validate` argument. The asymmetry keeps a capability-gated service from being removed during a transient window in which the
|
|
113
|
+
* device under-reports its capability, while still honoring a user who disables the service. A service with no user toggle should gate on its capability directly.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* // Keep the service while its user toggle is on, add it when the capability reports, and keep an existing one through a transient capability-false.
|
|
118
|
+
* validService(accessory, Service.Switch, capabilityGate({ capability: deviceReportsFeature, toggle: config.enableSwitch }));
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @see validService - consumes the returned predicate.
|
|
122
|
+
* @category Accessory
|
|
123
|
+
*/
|
|
124
|
+
export declare function capabilityGate({ capability, toggle }: {
|
|
125
|
+
capability: boolean;
|
|
126
|
+
toggle: boolean;
|
|
127
|
+
}): (hasService: boolean) => boolean;
|
|
128
|
+
/**
|
|
129
|
+
* Retrieves the primary name of a service, preferring the ConfiguredName characteristic over the Name characteristic. This is a pure read - it never mutates the
|
|
130
|
+
* service.
|
|
83
131
|
*
|
|
84
132
|
* @param service - The service from which to retrieve the name.
|
|
85
|
-
* @returns The configured or display name of the service, or `undefined` if neither is set.
|
|
133
|
+
* @returns The configured or display name of the service, or `undefined` if neither characteristic is present or set.
|
|
86
134
|
*
|
|
87
|
-
* @see setServiceName
|
|
135
|
+
* @see setServiceName - to update the current name on a service.
|
|
88
136
|
* @category Accessory
|
|
89
137
|
*/
|
|
90
138
|
export declare function getServiceName(service?: Service): string | undefined;
|
|
@@ -98,8 +146,9 @@ export declare function getServiceName(service?: Service): string | undefined;
|
|
|
98
146
|
* This function ensures the name is validated, updates the service's `displayName`, and sets the `ConfiguredName` and `Name`
|
|
99
147
|
* characteristics when supported by the service type.
|
|
100
148
|
*
|
|
101
|
-
* @see acquireService
|
|
102
|
-
* @see getServiceName
|
|
149
|
+
* @see acquireService - to add or retrieve services.
|
|
150
|
+
* @see getServiceName - to retrieve the current name set on a service.
|
|
103
151
|
* @category Accessory
|
|
104
152
|
*/
|
|
105
153
|
export declare function setServiceName(service: Service, name: string): void;
|
|
154
|
+
//# sourceMappingURL=service.d.ts.map
|