homebridge-plugin-utils 1.35.0 → 2.0.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.
Files changed (198) hide show
  1. package/README.md +130 -2
  2. package/build/eslint-plugin/README.md +164 -0
  3. package/build/eslint-plugin/config.mjs +308 -0
  4. package/build/eslint-plugin/index.mjs +7 -0
  5. package/build/eslint-plugin/plugin.mjs +46 -0
  6. package/build/eslint-plugin/rules/blank-line-after-open-brace.mjs +137 -0
  7. package/build/eslint-plugin/rules/blank-line-after-open-brace.test.mjs +112 -0
  8. package/build/eslint-plugin/rules/comment-style.mjs +190 -0
  9. package/build/eslint-plugin/rules/comment-style.test.mjs +190 -0
  10. package/build/eslint-plugin/rules/enforce-node-protocol.mjs +114 -0
  11. package/build/eslint-plugin/rules/enforce-node-protocol.test.mjs +116 -0
  12. package/build/eslint-plugin/rules/paren-comparisons-in-logical.mjs +94 -0
  13. package/build/eslint-plugin/rules/paren-comparisons-in-logical.test.mjs +107 -0
  14. package/build/eslint-plugin/rules/split-type-imports.mjs +354 -0
  15. package/build/eslint-plugin/rules/split-type-imports.test.mjs +268 -0
  16. package/build/eslint-plugin/test-setup.mjs +21 -0
  17. package/build/fs-ops.mjs +184 -0
  18. package/build/tsconfig.json +18 -3
  19. package/dist/backpressure.d.ts +94 -45
  20. package/dist/backpressure.js +229 -93
  21. package/dist/backpressure.js.map +1 -1
  22. package/dist/cli/index.d.ts +95 -0
  23. package/dist/cli/index.js +355 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/clock-double.d.ts +93 -0
  26. package/dist/clock-double.js +141 -0
  27. package/dist/clock-double.js.map +1 -0
  28. package/dist/clock.d.ts +39 -0
  29. package/dist/clock.js +34 -0
  30. package/dist/clock.js.map +1 -0
  31. package/dist/eslint-plugin/config.d.mts +193 -0
  32. package/dist/eslint-plugin/index.d.mts +2 -0
  33. package/dist/eslint-plugin/plugin.d.mts +87 -0
  34. package/dist/eslint-plugin/rules/blank-line-after-open-brace.d.mts +19 -0
  35. package/dist/eslint-plugin/rules/comment-style.d.mts +15 -0
  36. package/dist/eslint-plugin/rules/enforce-node-protocol.d.mts +18 -0
  37. package/dist/eslint-plugin/rules/paren-comparisons-in-logical.d.mts +15 -0
  38. package/dist/eslint-plugin/rules/split-type-imports.d.mts +16 -0
  39. package/dist/featureOptions-docs.d.ts +96 -0
  40. package/dist/featureOptions-docs.js +193 -0
  41. package/dist/featureOptions-docs.js.map +1 -0
  42. package/dist/featureOptions.d.ts +674 -0
  43. package/dist/featureOptions.js +870 -0
  44. package/dist/featureOptions.js.map +1 -0
  45. package/dist/ffmpeg/codecs.d.ts +256 -72
  46. package/dist/ffmpeg/codecs.js +477 -262
  47. package/dist/ffmpeg/codecs.js.map +1 -1
  48. package/dist/ffmpeg/dgram-util.d.ts +46 -0
  49. package/dist/ffmpeg/dgram-util.js +38 -0
  50. package/dist/ffmpeg/dgram-util.js.map +1 -0
  51. package/dist/ffmpeg/exec.d.ts +83 -64
  52. package/dist/ffmpeg/exec.js +77 -86
  53. package/dist/ffmpeg/exec.js.map +1 -1
  54. package/dist/ffmpeg/fmp4.d.ts +49 -2
  55. package/dist/ffmpeg/fmp4.js +47 -11
  56. package/dist/ffmpeg/fmp4.js.map +1 -1
  57. package/dist/ffmpeg/hap-enums.d.ts +214 -0
  58. package/dist/ffmpeg/hap-enums.js +92 -0
  59. package/dist/ffmpeg/hap-enums.js.map +1 -0
  60. package/dist/ffmpeg/index.d.ts +16 -9
  61. package/dist/ffmpeg/index.js +6 -0
  62. package/dist/ffmpeg/index.js.map +1 -1
  63. package/dist/ffmpeg/mp4-assembler.d.ts +120 -0
  64. package/dist/ffmpeg/mp4-assembler.js +387 -0
  65. package/dist/ffmpeg/mp4-assembler.js.map +1 -0
  66. package/dist/ffmpeg/mp4-parser.d.ts +94 -0
  67. package/dist/ffmpeg/mp4-parser.js +130 -0
  68. package/dist/ffmpeg/mp4-parser.js.map +1 -0
  69. package/dist/ffmpeg/options.d.ts +62 -149
  70. package/dist/ffmpeg/options.js +608 -499
  71. package/dist/ffmpeg/options.js.map +1 -1
  72. package/dist/ffmpeg/process.d.ts +142 -96
  73. package/dist/ffmpeg/process.js +406 -278
  74. package/dist/ffmpeg/process.js.map +1 -1
  75. package/dist/ffmpeg/record.d.ts +302 -188
  76. package/dist/ffmpeg/record.js +408 -567
  77. package/dist/ffmpeg/record.js.map +1 -1
  78. package/dist/ffmpeg/recording-process-double.d.ts +144 -0
  79. package/dist/ffmpeg/recording-process-double.js +168 -0
  80. package/dist/ffmpeg/recording-process-double.js.map +1 -0
  81. package/dist/ffmpeg/rtp-parser.d.ts +70 -0
  82. package/dist/ffmpeg/rtp-parser.js +77 -0
  83. package/dist/ffmpeg/rtp-parser.js.map +1 -0
  84. package/dist/ffmpeg/rtp.d.ts +198 -141
  85. package/dist/ffmpeg/rtp.js +473 -251
  86. package/dist/ffmpeg/rtp.js.map +1 -1
  87. package/dist/ffmpeg/settings.d.ts +5 -2
  88. package/dist/ffmpeg/settings.js +20 -5
  89. package/dist/ffmpeg/settings.js.map +1 -1
  90. package/dist/ffmpeg/stream.d.ts +57 -107
  91. package/dist/ffmpeg/stream.js +121 -150
  92. package/dist/ffmpeg/stream.js.map +1 -1
  93. package/dist/formatters.d.ts +106 -0
  94. package/dist/formatters.js +174 -0
  95. package/dist/formatters.js.map +1 -0
  96. package/dist/homebridge-enums.d.ts +30 -0
  97. package/dist/homebridge-enums.js +17 -0
  98. package/dist/homebridge-enums.js.map +1 -0
  99. package/dist/index.d.ts +12 -6
  100. package/dist/index.js +7 -2
  101. package/dist/index.js.map +1 -1
  102. package/dist/logclient/auth.d.ts +114 -0
  103. package/dist/logclient/auth.js +199 -0
  104. package/dist/logclient/auth.js.map +1 -0
  105. package/dist/logclient/cli-run.d.ts +76 -0
  106. package/dist/logclient/cli-run.js +639 -0
  107. package/dist/logclient/cli-run.js.map +1 -0
  108. package/dist/logclient/cli.d.ts +3 -0
  109. package/dist/logclient/cli.js +97 -0
  110. package/dist/logclient/cli.js.map +1 -0
  111. package/dist/logclient/client.d.ts +145 -0
  112. package/dist/logclient/client.js +600 -0
  113. package/dist/logclient/client.js.map +1 -0
  114. package/dist/logclient/config.d.ts +173 -0
  115. package/dist/logclient/config.js +199 -0
  116. package/dist/logclient/config.js.map +1 -0
  117. package/dist/logclient/endpoints.d.ts +54 -0
  118. package/dist/logclient/endpoints.js +73 -0
  119. package/dist/logclient/endpoints.js.map +1 -0
  120. package/dist/logclient/filter.d.ts +45 -0
  121. package/dist/logclient/filter.js +51 -0
  122. package/dist/logclient/filter.js.map +1 -0
  123. package/dist/logclient/frame.d.ts +93 -0
  124. package/dist/logclient/frame.js +203 -0
  125. package/dist/logclient/frame.js.map +1 -0
  126. package/dist/logclient/index.d.ts +31 -0
  127. package/dist/logclient/index.js +12 -0
  128. package/dist/logclient/index.js.map +1 -0
  129. package/dist/logclient/parser.d.ts +211 -0
  130. package/dist/logclient/parser.js +393 -0
  131. package/dist/logclient/parser.js.map +1 -0
  132. package/dist/logclient/rest.d.ts +41 -0
  133. package/dist/logclient/rest.js +111 -0
  134. package/dist/logclient/rest.js.map +1 -0
  135. package/dist/logclient/settings.d.ts +15 -0
  136. package/dist/logclient/settings.js +64 -0
  137. package/dist/logclient/settings.js.map +1 -0
  138. package/dist/logclient/socket-double.d.ts +201 -0
  139. package/dist/logclient/socket-double.js +384 -0
  140. package/dist/logclient/socket-double.js.map +1 -0
  141. package/dist/logclient/socket.d.ts +257 -0
  142. package/dist/logclient/socket.js +620 -0
  143. package/dist/logclient/socket.js.map +1 -0
  144. package/dist/logclient/stitch.d.ts +83 -0
  145. package/dist/logclient/stitch.js +146 -0
  146. package/dist/logclient/stitch.js.map +1 -0
  147. package/dist/logclient/time-expression.d.ts +42 -0
  148. package/dist/logclient/time-expression.js +181 -0
  149. package/dist/logclient/time-expression.js.map +1 -0
  150. package/dist/logclient/time-window.d.ts +38 -0
  151. package/dist/logclient/time-window.js +53 -0
  152. package/dist/logclient/time-window.js.map +1 -0
  153. package/dist/logclient/types.d.ts +107 -0
  154. package/dist/logclient/types.js +6 -0
  155. package/dist/logclient/types.js.map +1 -0
  156. package/dist/mqttClient.d.ts +287 -0
  157. package/dist/mqttClient.js +433 -0
  158. package/dist/mqttClient.js.map +1 -0
  159. package/dist/service.d.ts +64 -15
  160. package/dist/service.js +93 -66
  161. package/dist/service.js.map +1 -1
  162. package/dist/ui/featureOptions.js +870 -0
  163. package/dist/ui/featureOptions.js.map +1 -0
  164. package/dist/ui/formatters.js +174 -0
  165. package/dist/ui/formatters.js.map +1 -0
  166. package/dist/ui/pluginConfigSession.mjs +141 -0
  167. package/dist/ui/webUi-featureOptions/categoryState.mjs +135 -0
  168. package/dist/ui/webUi-featureOptions/effects/keyboard.mjs +61 -0
  169. package/dist/ui/webUi-featureOptions/effects/persist.mjs +226 -0
  170. package/dist/ui/webUi-featureOptions/effects/theme.mjs +398 -0
  171. package/dist/ui/webUi-featureOptions/effects/tokens.mjs +152 -0
  172. package/dist/ui/webUi-featureOptions/rendering.mjs +431 -0
  173. package/dist/ui/webUi-featureOptions/selectors.mjs +360 -0
  174. package/dist/ui/webUi-featureOptions/state.mjs +319 -0
  175. package/dist/ui/webUi-featureOptions/store.mjs +181 -0
  176. package/dist/ui/webUi-featureOptions/utils.mjs +200 -0
  177. package/dist/ui/webUi-featureOptions/views/connectionError.mjs +152 -0
  178. package/dist/ui/webUi-featureOptions/views/deviceInfo.mjs +80 -0
  179. package/dist/ui/webUi-featureOptions/views/header.mjs +77 -0
  180. package/dist/ui/webUi-featureOptions/views/nav.mjs +327 -0
  181. package/dist/ui/webUi-featureOptions/views/options.mjs +521 -0
  182. package/dist/ui/webUi-featureOptions/views/search.mjs +395 -0
  183. package/dist/ui/webUi-featureOptions.mjs +677 -0
  184. package/dist/ui/webUi.mjs +192 -87
  185. package/dist/util.d.ts +602 -45
  186. package/dist/util.js +783 -78
  187. package/dist/util.js.map +1 -1
  188. package/package.json +33 -15
  189. package/build/eslint-rules.mjs +0 -511
  190. package/dist/featureoptions.d.ts +0 -264
  191. package/dist/featureoptions.js +0 -480
  192. package/dist/featureoptions.js.map +0 -1
  193. package/dist/mqttclient.d.ts +0 -178
  194. package/dist/mqttclient.js +0 -310
  195. package/dist/mqttclient.js.map +0 -1
  196. package/dist/ui/featureoptions.js +0 -480
  197. package/dist/ui/featureoptions.js.map +0 -1
  198. package/dist/ui/webUi-featureoptions.mjs +0 -3765
@@ -0,0 +1,620 @@
1
+ /* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
2
+ *
3
+ * logclient/socket.ts: AsyncDisposable Socket.IO log-stream client with reconnect, ping-liveness, and a bounded push-to-pull line stream.
4
+ */
5
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
6
+ if (value !== null && value !== void 0) {
7
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
8
+ var dispose, inner;
9
+ if (async) {
10
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
11
+ dispose = value[Symbol.asyncDispose];
12
+ }
13
+ if (dispose === void 0) {
14
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
15
+ dispose = value[Symbol.dispose];
16
+ if (async) inner = dispose;
17
+ }
18
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
19
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
20
+ env.stack.push({ value: value, dispose: dispose, async: async });
21
+ }
22
+ else if (async) {
23
+ env.stack.push({ async: true });
24
+ }
25
+ return value;
26
+ };
27
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
28
+ return function (env) {
29
+ function fail(e) {
30
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
31
+ env.hasError = true;
32
+ }
33
+ var r, s = 0;
34
+ function next() {
35
+ while (r = env.stack.pop()) {
36
+ try {
37
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
38
+ if (r.dispose) {
39
+ var result = r.dispose.call(r.value);
40
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
41
+ }
42
+ else s |= 1;
43
+ }
44
+ catch (e) {
45
+ fail(e);
46
+ }
47
+ }
48
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
49
+ if (env.hasError) throw env.error;
50
+ }
51
+ return next();
52
+ };
53
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
54
+ var e = new Error(message);
55
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
56
+ });
57
+ /**
58
+ * AsyncDisposable live-log socket for the Homebridge UI log stream.
59
+ *
60
+ * {@link LogSocket} owns a single WebSocket connection to the homebridge-config-ui-x Socket.IO log namespace and surfaces the raw `stdout` text the server streams as a
61
+ * bounded push-to-pull `AsyncIterable<string>`. It composes the library's own primitives rather than reinventing them: {@link composeSignals} / {@link onAbort} for
62
+ * lifetime, {@link Watchdog} for ping liveness, {@link retry} (driving the connect phase with the log client's own dev-tuned backoff curve), and {@link HbpuAbortError}
63
+ * for the abort taxonomy. The wire framing is delegated entirely to the pure {@link decodeFrame} / {@link encodeFrame} codec.
64
+ *
65
+ * Lifecycle, in one pass:
66
+ *
67
+ * - A background reconnect loop runs for the socket's lifetime: `while(!signal.aborted)`, each iteration `retry()`s the CONNECT PHASE ONLY (re-acquire the token, open
68
+ * the WebSocket, await the Engine.IO open handshake, join the `/log` namespace, emit `tail-log`). Wrapping only the connect phase in `retry` means the exponential
69
+ * backoff curve RESETS after every healthy session - a socket that stays connected for hours and then drops reconnects briskly rather than at the climbed-up delay a
70
+ * single long-lived `retry` would have reached.
71
+ * - The connect-phase backoff is the log client's own jittered exponential curve: a 500 ms base (`RECONNECT_BASE_MS`) doubling each attempt and plateauing at a 5-second
72
+ * ceiling (`RECONNECT_CAP_MS`), deliberately snappier than `defaultRetryBackoff`'s 30-second ceiling so the tail resumes promptly after a Homebridge restart. `random`
73
+ * is injectable (default `Math.random`) so the jitter is deterministic in tests.
74
+ * - `shouldRetry` vetoes a retry the instant a permanent authentication failure surfaces (wrong password, missing OTP, noauth disabled), so a credential problem fails
75
+ * the socket fast with an actionable error rather than looping forever.
76
+ * - Once connected, each server ping (`"2"`) is answered with a pong (`"3"`) and re-arms the liveness {@link Watchdog}; if no ping arrives within
77
+ * `pingInterval + pingTimeout + MARGIN_MS`, the watchdog aborts the session and the loop reconnects.
78
+ * - A Socket.IO CONNECT_ERROR (`44/log,`) on the namespace is surfaced as a connect-phase failure - transient and retried for a refreshable credential (password/noauth),
79
+ * permanent and made terminal by the `shouldRetry` veto for a static token that cannot be refreshed.
80
+ *
81
+ * Teardown is idempotent and state-gated: it sends a namespace DISCONNECT (`41/log,`) only when the socket is still OPEN, ALWAYS issues `close(1000)`, clears the
82
+ * watchdog, and settles the parked stdout waiter exactly once. The class introduces NO `Clock` dependency - reconnect timing is exercised in tests by injecting a
83
+ * near-zero `backoff`, and the watchdog by `node:test` `mock.timers`.
84
+ *
85
+ * @module
86
+ */
87
+ import { DEFAULT_PORT, JITTER_FRACTION, LOG_NAMESPACE, MARGIN_MS, PTY_COLUMNS, PTY_ROWS, RECONNECT_BASE_MS, RECONNECT_CAP_MS } from "./settings.js";
88
+ import { HbpuAbortError, Watchdog, composeSignals, formatErrorMessage, onAbort, retry } from "../util.js";
89
+ import { LOG_NAMESPACE_PATH, decodeFrame, encodeFrame } from "./frame.js";
90
+ import { LogAuthError, isPermanentAuthError } from "./auth.js";
91
+ import { LogLineSplitter } from "./parser.js";
92
+ import { socketUrl } from "./endpoints.js";
93
+ // The default high-water mark for the stdout queue: the maximum number of lines buffered between the WebSocket producer and a slow consumer before the oldest lines are
94
+ // dropped. A live consumer (a terminal, a grep) keeps up in practice, so this bound is a safety valve against an unbounded backlog rather than a routine limit; it is
95
+ // generous enough to absorb the server's ~500-line seed plus normal jitter, and overflow is logged once so a chronically-slow consumer is not silently lossy.
96
+ const DEFAULT_STDOUT_HIGH_WATER = 10000;
97
+ /**
98
+ * The numeric `readyState` value denoting an open WebSocket. The DOM `WebSocket.OPEN` constant is `1`; we name it as a module constant so the seam interface does not
99
+ * have to carry the static constant and teardown can gate on it without depending on the concrete class.
100
+ *
101
+ * @category Log Client
102
+ */
103
+ export const WEBSOCKET_OPEN = 1;
104
+ /**
105
+ * The production {@link WebSocketFactory}: constructs the platform global `WebSocket`. A consumer holds the factory typed as the seam abstraction; a test substitutes a
106
+ * double. The platform `WebSocket` begins connecting on construction, which is exactly the factory contract.
107
+ *
108
+ * @param url - The connect URL.
109
+ *
110
+ * @returns A live platform `WebSocket`, typed as the {@link WebSocketLike} seam.
111
+ *
112
+ * @category Log Client
113
+ */
114
+ export const webSocketFactory = (url) => new WebSocket(url);
115
+ // Add up-to-`JITTER_FRACTION` upward random jitter to a base delay. Jitter spreads reconnect attempts so a fleet of clients does not stampede the server in lockstep
116
+ // after a shared outage. The `random` source is injected so the jittered delay is deterministic in tests; with `random` pinned near zero the delay is effectively the
117
+ // base, which is how the reconnect tests drive the loop without real waits.
118
+ function withJitter(base, random) {
119
+ return Math.round(base + (base * JITTER_FRACTION * random()));
120
+ }
121
+ /**
122
+ * The log client's connect-phase reconnect backoff policy: a dev-tuned jittered exponential curve with a low ceiling.
123
+ *
124
+ * This is the single source of truth for the socket's default reconnect timing. The base delay is `RECONNECT_BASE_MS` (500 ms) and it doubles with each successive
125
+ * connect attempt, plateauing at the `RECONNECT_CAP_MS` ceiling (5 s) - deliberately snappier than `defaultRetryBackoff`'s 30-second ceiling, because a log-tailing
126
+ * dev tool should resume the tail promptly after the frequent Homebridge restarts a plugin developer does rather than back off to a half-minute lag. Up to
127
+ * `JITTER_FRACTION` of the computed base is added as upward jitter so a fleet of clients does not reconnect in lockstep after a shared outage.
128
+ *
129
+ * It is exported (rather than left inline in the constructor) so the bare schedule is a directly unit-testable function: with `random` pinned to `0` the curve yields the
130
+ * exact, deterministic 500, 1000, 2000, 4000, 5000, 5000, ... sequence. `retry` invokes the socket's backoff 1-indexed with the attempt about to run and never with
131
+ * `attempt === 1` (the first attempt runs immediately), so `attempt - 2` is the zero-based exponent for the second-and-later attempts.
132
+ *
133
+ * @param attempt - The 1-indexed connect attempt about to run. Called only for the second and later attempts (the first runs with no wait).
134
+ * @param random - Source of `[0, 1)` randomness for the jitter. Defaults to `Math.random`; pinned in tests for a deterministic schedule.
135
+ *
136
+ * @returns The delay, in milliseconds, to wait before running `attempt`.
137
+ *
138
+ * @category Log Client
139
+ */
140
+ export function reconnectBackoff(attempt, random = Math.random) {
141
+ return withJitter(Math.min(RECONNECT_CAP_MS, RECONNECT_BASE_MS * (2 ** (attempt - 2))), random);
142
+ }
143
+ /**
144
+ * AsyncDisposable client for the live Homebridge log stream over a single Socket.IO WebSocket, with automatic reconnect, ping liveness, and a bounded push-to-pull line
145
+ * stream.
146
+ *
147
+ * @example
148
+ *
149
+ * ```ts
150
+ * await using socket = new LogSocket({
151
+ *
152
+ * host: "localhost",
153
+ * log,
154
+ * refreshable: true,
155
+ * tokenProvider: (signal) => acquireToken(credentials, { host: "localhost", port: 8581, signal }),
156
+ * signal: session.signal
157
+ * });
158
+ *
159
+ * for await (const line of socket.stdout()) {
160
+ *
161
+ * process.stdout.write(line + "\n");
162
+ * }
163
+ * ```
164
+ *
165
+ * @category Log Client
166
+ */
167
+ export class LogSocket {
168
+ /**
169
+ * The composed abort signal representing this socket's lifetime. Aborts exactly once - when {@link LogSocket.abort} is called, the parent signal fires, or the
170
+ * reconnect loop gives up on a permanent failure - and `signal.reason` names the cause.
171
+ */
172
+ signal;
173
+ // The private controller whose signal is composed into `this.signal`. Owned internally so the only abort verb is `abort()`.
174
+ #controller;
175
+ #backoff;
176
+ #host;
177
+ #log;
178
+ #port;
179
+ #random;
180
+ #refreshable;
181
+ #stdoutHighWater;
182
+ #tls;
183
+ #tokenProvider;
184
+ #webSocketFactory;
185
+ // The bounded queue of raw stdout lines staged for the `stdout()` consumer, plus the single parked waiter the consumer blocks on when the queue is empty. The class is
186
+ // single-consumer by design, mirroring `Mp4SegmentAssembler`: one parked-waiter slot is sufficient.
187
+ #stdoutQueue = [];
188
+ #stdoutWaiter;
189
+ // The number of lines dropped because the stdout queue hit its high-water mark, and a one-time flag so a chronically-slow consumer is warned exactly once rather than
190
+ // on every drop. Tracking the count lets the single warning report the magnitude of the loss.
191
+ #droppedLines = 0;
192
+ #overflowLogged = false;
193
+ // The background reconnect loop's promise, retained so `[Symbol.asyncDispose]` can await its completion before returning.
194
+ #loopTask;
195
+ // The current live session, or `undefined` between sessions / after teardown. Held so teardown can close the live WebSocket and so the message handler can address the
196
+ // correct connection's `send`.
197
+ #session;
198
+ /**
199
+ * Construct and start a new live-log socket. The reconnect loop starts synchronously as part of construction: by the time the constructor returns, the first connect
200
+ * attempt is already in flight (unless the signal was pre-aborted, in which case the socket tears down immediately).
201
+ *
202
+ * @param init - Required init options. See {@link LogSocketInit}.
203
+ */
204
+ constructor(init) {
205
+ this.#host = init.host;
206
+ this.#log = init.log;
207
+ this.#port = init.port ?? DEFAULT_PORT;
208
+ this.#random = init.random ?? Math.random;
209
+ this.#refreshable = init.refreshable;
210
+ this.#stdoutHighWater = init.stdoutHighWater ?? DEFAULT_STDOUT_HIGH_WATER;
211
+ this.#tls = init.tls ?? false;
212
+ this.#tokenProvider = init.tokenProvider;
213
+ this.#webSocketFactory = init.webSocketFactory ?? webSocketFactory;
214
+ // The connect-phase backoff. Defaults to the log client's own jittered exponential curve via {@link reconnectBackoff} - a 500 ms base doubling each attempt and
215
+ // plateauing at a 5-second ceiling, deliberately snappier than `defaultRetryBackoff`'s 30-second ceiling because a dev tool should resume the tail promptly after the
216
+ // frequent Homebridge restarts a plugin developer does. A caller (a test) may inject a near-zero policy to drive the reconnect loop without real waits. Resolved once
217
+ // here, closing over the socket's injected `random`, so the hot reconnect path reads a single field rather than re-deriving the default each attempt.
218
+ this.#backoff = init.backoff ?? ((attempt) => reconnectBackoff(attempt, this.#random));
219
+ this.#controller = new AbortController();
220
+ this.signal = composeSignals(init.signal, this.#controller.signal);
221
+ // Single teardown convergence point. `onAbort` registers the one-shot teardown for the normal abort path AND runs it inline when the signal is already aborted at
222
+ // construction time (a pre-aborted parent), so the stdout waiter is settled and the live connection closed regardless of which path fired.
223
+ onAbort(this.signal, () => this.#teardown());
224
+ if (this.signal.aborted) {
225
+ return;
226
+ }
227
+ // Start the background reconnect loop. The returned promise is retained so disposal can await its completion; the loop is written to always resolve (it classifies
228
+ // its own faults), so no `markHandled` wrapper is needed.
229
+ this.#loopTask = this.#runLoop();
230
+ }
231
+ /**
232
+ * Abort the socket and tear it down. Defaults to `HbpuAbortError("shutdown")` when no reason is supplied; explicit reasons pass through unchanged.
233
+ *
234
+ * Safe to call more than once: the underlying signal aborts only once, so subsequent calls are no-ops.
235
+ *
236
+ * @param reason - Optional abort reason. Typically an {@link HbpuAbortError}.
237
+ */
238
+ abort(reason) {
239
+ if (this.aborted) {
240
+ return;
241
+ }
242
+ this.#controller.abort(reason ?? new HbpuAbortError("shutdown"));
243
+ }
244
+ /**
245
+ * `AsyncDisposable` implementation. Aborts the socket (defaulting to `"shutdown"`) and awaits the reconnect loop's completion before returning, so callers using
246
+ * `await using` are guaranteed the connection is closed and the loop has unwound by the time the block exits.
247
+ *
248
+ * @returns A promise that resolves once the reconnect loop has fully exited.
249
+ */
250
+ async [Symbol.asyncDispose]() {
251
+ this.abort();
252
+ if (this.#loopTask) {
253
+ // The loop is written to always resolve; the catch is belt-and-suspenders so disposal never surfaces a cleanup-side failure the caller cannot react to.
254
+ await this.#loopTask.catch(() => { });
255
+ }
256
+ }
257
+ /**
258
+ * `true` once `this.signal` has aborted. Derived from the signal; no independent state.
259
+ */
260
+ get aborted() {
261
+ return this.signal.aborted;
262
+ }
263
+ /**
264
+ * The number of stdout lines dropped so far because the consumer fell behind the high-water mark. Zero in steady state; non-zero only after the bounded queue
265
+ * overflowed, which is also logged once.
266
+ */
267
+ get droppedLines() {
268
+ return this.#droppedLines;
269
+ }
270
+ /**
271
+ * The bounded push-to-pull stream of raw log lines (ANSI intact, terminators removed) the server streams over the log namespace's `stdout` events.
272
+ *
273
+ * The server delivers `stdout` as raw text chunks whose boundaries do not align with log lines; the socket runs each chunk through a per-session
274
+ * {@link LogLineSplitter}, yields complete lines here, and flushes it on each session's close so the final line is never stranded. Mirroring
275
+ * `Mp4SegmentAssembler.segments`, a bounded queue decouples the WebSocket producer from this consumer, and a single parked waiter blocks the consumer when the queue is
276
+ * empty until a line is pushed or the socket aborts. The queue survives reconnects - the same iterable keeps yielding across a drop-and-reconnect - so a consumer
277
+ * iterates it once for the whole socket lifetime. The stream terminates (returns) when the socket aborts; the queue is drained before it returns, so a line already
278
+ * staged before teardown is never lost.
279
+ *
280
+ * **Single-consumer only.** The parked-waiter slot is single-writer; iterating `stdout()` concurrently from two consumers is unsupported.
281
+ *
282
+ * @returns An async generator yielding raw log lines in stream order.
283
+ */
284
+ async *stdout() {
285
+ for (;;) {
286
+ const env_1 = { stack: [], error: void 0, hasError: false };
287
+ try {
288
+ // Swap-drain the queue: take whatever the producer has staged, leave a fresh empty array for it to push into, then yield the snapshot. Draining unconditionally
289
+ // before the abort check preserves the "no staged line lost on teardown" invariant.
290
+ while (this.#stdoutQueue.length > 0) {
291
+ const drained = this.#stdoutQueue;
292
+ this.#stdoutQueue = [];
293
+ for (const line of drained) {
294
+ yield line;
295
+ }
296
+ }
297
+ if (this.signal.aborted) {
298
+ return;
299
+ }
300
+ // Park until a line is pushed (producer resolves the waiter) or the socket aborts (teardown resolves it). A per-iteration resolver keeps the waiter always fresh.
301
+ const waiter = Promise.withResolvers();
302
+ this.#stdoutWaiter = waiter;
303
+ // `onAbort` unifies registration, the pre-aborted-signal pitfall, and one-shot `{ once: true }` semantics; the disposer is handed to `using` so the listener is
304
+ // removed on every scope-exit path. Matches the parked-wait shape `Mp4SegmentAssembler` uses.
305
+ const _abortRegistration = __addDisposableResource(env_1, onAbort(this.signal, () => waiter.resolve()), false);
306
+ try {
307
+ // eslint-disable-next-line no-await-in-loop
308
+ await waiter.promise;
309
+ }
310
+ finally {
311
+ this.#stdoutWaiter = undefined;
312
+ }
313
+ }
314
+ catch (e_1) {
315
+ env_1.error = e_1;
316
+ env_1.hasError = true;
317
+ }
318
+ finally {
319
+ __disposeResources(env_1);
320
+ }
321
+ }
322
+ }
323
+ // The background reconnect loop. Runs for the socket's lifetime: each iteration `retry()`s the connect phase to obtain a live session, then streams until the session
324
+ // ends. A clean socket-level abort exits the loop; any other session end loops again, and because only the connect phase is wrapped in `retry`, the next attempt
325
+ // starts from a fresh (reset) backoff curve.
326
+ async #runLoop() {
327
+ while (!this.signal.aborted) {
328
+ let session;
329
+ try {
330
+ // Connect phase only. `retry` owns the backoff and the permanent-failure veto; `Infinity` attempts means the loop reconnects indefinitely until the socket is
331
+ // aborted or a permanent failure is vetoed. The default backoff is the log client's own dev-tuned exponential curve (a 500 ms base doubling each attempt and
332
+ // capped at a 5-second ceiling, snappier than `defaultRetryBackoff`'s 30-second ceiling) with up-to-`JITTER_FRACTION` upward jitter layered on, so a fleet of
333
+ // clients does not stampede the server in lockstep after a shared outage. Because only the connect phase is wrapped, the backoff resets every time this returns -
334
+ // a socket that stayed healthy for hours reconnects briskly rather than at a climbed-up delay.
335
+ // eslint-disable-next-line no-await-in-loop
336
+ session = await retry((attemptSignal) => this.#connect(attemptSignal), {
337
+ attempts: Infinity,
338
+ backoff: this.#backoff,
339
+ shouldRetry: (error) => !isPermanentAuthError(error),
340
+ signal: this.signal
341
+ });
342
+ }
343
+ catch (error) {
344
+ // The connect phase exhausted its retries or hit a permanent failure (a vetoed auth error), or the socket was aborted mid-connect. When the socket is already
345
+ // aborted, this is orderly teardown - stay quiet and exit. Otherwise classify the connection as permanently failed, abort the socket so `stdout()` terminates,
346
+ // and surface an actionable error. The signal's aborted state changes asynchronously while `await retry(...)` is in flight, so the loop-condition narrowing the
347
+ // compiler applies here does not reflect the runtime value.
348
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
349
+ if (this.signal.aborted) {
350
+ return;
351
+ }
352
+ this.#log.error("Unable to connect to the Homebridge log stream: %s.", formatErrorMessage(error));
353
+ this.#controller.abort(new HbpuAbortError("failed", { cause: error }));
354
+ return;
355
+ }
356
+ // Stream until the session ends (peer close, error, watchdog timeout, or socket abort). When it returns, loop to reconnect unless the socket itself was aborted.
357
+ // eslint-disable-next-line no-await-in-loop
358
+ await this.#stream(session);
359
+ }
360
+ }
361
+ // Connect phase. Re-acquires a fresh token, opens the WebSocket, awaits the Engine.IO open handshake (capturing its ping cadence), joins the `/log` namespace, and
362
+ // emits `tail-log`. Resolves with the live session once `tail-log` has been emitted; rejects on any handshake fault (WebSocket error, CONNECT_ERROR, peer close, or
363
+ // attempt abort), so `retry` governs the backoff. A per-attempt controller composed under the attempt signal ensures a failed attempt's WebSocket and listeners are
364
+ // torn down before the next attempt opens a new one; on success that same controller becomes the session controller, so the streaming phase shares the teardown path.
365
+ async #connect(attemptSignal) {
366
+ const env_2 = { stack: [], error: void 0, hasError: false };
367
+ try {
368
+ const token = await this.#tokenProvider(attemptSignal);
369
+ const url = socketUrl({ host: this.#host, port: this.#port, tls: this.#tls, token });
370
+ const controller = new AbortController();
371
+ const composed = composeSignals(attemptSignal, controller.signal);
372
+ const ws = this.#webSocketFactory(url);
373
+ // Capture the Engine.IO open handshake's ping cadence so the streaming phase can size the liveness watchdog from the server's real cadence rather than a guess.
374
+ let pingInterval = 0;
375
+ let pingTimeout = 0;
376
+ // The connect handshake is a one-shot promise resolved by the message handler once `tail-log` has been emitted, or rejected by an error / close / CONNECT_ERROR /
377
+ // abort.
378
+ const { promise: connected, resolve, reject } = Promise.withResolvers();
379
+ // Tear down this attempt's WebSocket when the composed signal aborts (attempt abort or our own controller abort), and reject the connect promise so a pending
380
+ // handshake unwinds. The `using` disposer removes the listener when this method's scope exits.
381
+ const _abortRegistration = __addDisposableResource(env_2, onAbort(composed, () => {
382
+ reject(composed.reason);
383
+ this.#closeWebSocket(ws);
384
+ }), false);
385
+ ws.addEventListener("error", (event) => {
386
+ reject(new HbpuAbortError("failed", { cause: this.#describeWsError(event) }));
387
+ });
388
+ ws.addEventListener("close", (event) => {
389
+ // A close during the connect handshake is a connect-phase failure (the server hung up before we finished joining the namespace); reject so `retry` backs off.
390
+ reject(new HbpuAbortError("closed", { cause: { code: event.code } }));
391
+ });
392
+ ws.addEventListener("message", (event) => {
393
+ if (typeof event.data !== "string") {
394
+ return;
395
+ }
396
+ const decoded = decodeFrame(event.data);
397
+ switch (decoded.kind) {
398
+ case "open": {
399
+ // The Engine.IO handshake. Record the ping cadence and join the `/log` namespace; the server replies with a namespace CONNECT acknowledgement (or a
400
+ // CONNECT_ERROR).
401
+ pingInterval = decoded.pingInterval;
402
+ pingTimeout = decoded.pingTimeout;
403
+ ws.send(encodeFrame({ kind: "connect", namespace: LOG_NAMESPACE }));
404
+ return;
405
+ }
406
+ case "namespaceConnect": {
407
+ // The namespace is joined. Request the tail with the advertised PTY geometry, then consider the handshake complete - from here the server streams `stdout`.
408
+ ws.send(encodeFrame({ args: { cols: PTY_COLUMNS, rows: PTY_ROWS }, event: "tail-log", kind: "event", namespace: LOG_NAMESPACE }));
409
+ resolve();
410
+ return;
411
+ }
412
+ case "namespaceError": {
413
+ // The server rejected the namespace join (a Socket.IO CONNECT_ERROR, commonly an auth failure on a stale or invalid token). How we classify it depends on
414
+ // whether the backing credential can mint a fresh token on the next attempt. With a refreshable credential (password/noauth) the next reconnect re-auths from
415
+ // the stored credentials, so this is a transient connect-phase failure and we surface it as a plain `HbpuAbortError("failed")` that `retry` governs. With
416
+ // a static `token` credential there is nothing to refresh, so retrying the same rejected token would loop forever; we raise a PERMANENT `LogAuthError` instead,
417
+ // so the connect-phase `shouldRetry` veto (`!isPermanentAuthError`) makes the socket fail fast with an actionable error rather than spinning on a doomed token.
418
+ if (this.#refreshable) {
419
+ reject(new HbpuAbortError("failed", { cause: { reason: decoded.reason } }));
420
+ return;
421
+ }
422
+ reject(new LogAuthError("Authentication rejected; the token cannot be refreshed - provide a fresh --token or use --user/--pass.", { cause: { reason: decoded.reason }, kind: "permanent" }));
423
+ return;
424
+ }
425
+ default: {
426
+ // ping/pong/message/unknown during the handshake are not part of the connect sequence; ignore them. The server does not stream `stdout` until `tail-log` is
427
+ // emitted, so a `message` before that is not expected, but ignoring it is harmless.
428
+ return;
429
+ }
430
+ }
431
+ });
432
+ // Await the handshake. On any failure path - a WebSocket error, a CONNECT_ERROR, a peer close mid-handshake, or an attempt abort - close this attempt's WebSocket so
433
+ // a failed attempt never leaks its connection before `retry` opens the next one. The `using` abort registration handles the abort path's close; this catch covers
434
+ // the listener-driven rejections (error / close / CONNECT_ERROR) where the composed signal did not abort.
435
+ try {
436
+ await connected;
437
+ }
438
+ catch (error) {
439
+ this.#closeWebSocket(ws);
440
+ throw error;
441
+ }
442
+ return { closed: false, controller, pingInterval, pingTimeout, ws };
443
+ }
444
+ catch (e_2) {
445
+ env_2.error = e_2;
446
+ env_2.hasError = true;
447
+ }
448
+ finally {
449
+ __disposeResources(env_2);
450
+ }
451
+ }
452
+ // Streaming phase. The session is connected; split `stdout` chunks into lines and route them into the bounded queue, answer pings with pongs and re-arm the liveness
453
+ // watchdog, and end the session on close / error / watchdog timeout. Resolves when the session's signal aborts - which the outer loop reads to decide whether to
454
+ // reconnect. The watchdog window is sized from the server's advertised ping cadence plus a margin; the watchdog uses real timers (no `Clock` seam), so tests drive it
455
+ // with `mock.timers`.
456
+ async #stream(session) {
457
+ const env_3 = { stack: [], error: void 0, hasError: false };
458
+ try {
459
+ this.#session = session;
460
+ const composed = composeSignals(this.signal, session.controller.signal);
461
+ // The per-session line splitter. The server delivers `stdout` events as raw text chunks whose boundaries do not align with log lines; the splitter reassembles lines
462
+ // across chunk boundaries and normalizes the mixed newline conventions. It is per-session (a fresh connection starts with a clean carry) and is flushed on session
463
+ // close so the final line - which the splitter withholds when a chunk ends on a lone line-feed pending a possible cross-chunk pair - surfaces rather than stranding.
464
+ const splitter = new LogLineSplitter();
465
+ // The liveness watchdog, sized from the server's advertised ping cadence captured during the connect handshake: one ping interval plus the ping timeout plus a fixed
466
+ // margin, so a single late ping does not trip it. A zero/absent cadence (malformed handshake) falls back to the margin alone. Each inbound ping re-arms it; if the
467
+ // window lapses with no ping, it aborts the SESSION (not the whole socket), so the outer loop reconnects. The watchdog uses real timers (no `Clock` seam), so tests
468
+ // drive its firing with `node:test` `mock.timers`.
469
+ const windowMs = ((session.pingInterval > 0) ? session.pingInterval : 0) + ((session.pingTimeout > 0) ? session.pingTimeout : 0) + MARGIN_MS;
470
+ const watchdog = new Watchdog({ onFire: () => session.controller.abort(new HbpuAbortError("timeout")), signal: composed, timeoutMs: windowMs });
471
+ // A single resolver settled when the session ends so this method awaits the session lifetime without a busy loop. `onAbort` settles it on session abort; the message
472
+ // and close handlers drive the session controller, which aborts the composed signal, which fires this.
473
+ const { promise: ended, resolve: endSession } = Promise.withResolvers();
474
+ const _watchdog = __addDisposableResource(env_3, watchdog, false);
475
+ const _abortRegistration = __addDisposableResource(env_3, onAbort(composed, () => endSession()), false);
476
+ // Arm the watchdog immediately so a session that never receives a single ping (a silently-wedged connection) still trips the liveness timer; each inbound ping
477
+ // re-arms it from here.
478
+ watchdog.arm();
479
+ session.ws.addEventListener("message", (event) => {
480
+ this.#handleStreamMessage(session, watchdog, splitter, event.data);
481
+ });
482
+ session.ws.addEventListener("close", (event) => {
483
+ // The peer closed the connection. End the session with `"closed"` so the outer loop reconnects (the socket-level signal is untouched, so this is a session end,
484
+ // not a socket end).
485
+ if (!session.controller.signal.aborted) {
486
+ session.controller.abort(new HbpuAbortError("closed", { cause: { code: event.code } }));
487
+ }
488
+ });
489
+ session.ws.addEventListener("error", (event) => {
490
+ if (!session.controller.signal.aborted) {
491
+ session.controller.abort(new HbpuAbortError("failed", { cause: this.#describeWsError(event) }));
492
+ }
493
+ });
494
+ await ended;
495
+ // Flush the splitter on session end so the final line - withheld by the splitter when the last chunk ended on a lone line-feed pending a possible cross-chunk pair -
496
+ // surfaces rather than stranding. This is the load-bearing flush the wiring note calls for; mid-stream the next chunk drains the carry, but at session close no next
497
+ // chunk arrives.
498
+ for (const line of splitter.flush()) {
499
+ this.#enqueueStdout(line);
500
+ }
501
+ // Tear down the session's WebSocket on the way out: send the namespace DISCONNECT only when still OPEN, then always close(1000). Clearing `#session` prevents a stale
502
+ // reference from leaking into the next iteration's teardown.
503
+ this.#closeSession(session);
504
+ if (this.#session === session) {
505
+ this.#session = undefined;
506
+ }
507
+ }
508
+ catch (e_3) {
509
+ env_3.error = e_3;
510
+ env_3.hasError = true;
511
+ }
512
+ finally {
513
+ __disposeResources(env_3);
514
+ }
515
+ }
516
+ // Handle a message in the streaming phase. A ping is answered with a pong and re-arms the liveness watchdog (each ping is the server's heartbeat). A `stdout` message's
517
+ // payload is a raw log-text chunk; it is fed through the per-session splitter and every complete line it yields is enqueued for the consumer (the server's chunk
518
+ // boundaries do not align with log lines, so splitting must happen here, before the bounded queue).
519
+ #handleStreamMessage(session, watchdog, splitter, data) {
520
+ if (typeof data !== "string") {
521
+ return;
522
+ }
523
+ const event = decodeFrame(data);
524
+ switch (event.kind) {
525
+ case "ping": {
526
+ // Answer the heartbeat and re-arm the liveness watchdog. Re-arming on each ping is the liveness contract: as long as pings keep arriving, the watchdog never
527
+ // fires.
528
+ session.ws.send(encodeFrame({ kind: "pong" }));
529
+ watchdog.arm();
530
+ return;
531
+ }
532
+ case "message": {
533
+ // A namespace event. The log stream's only event is `stdout`, whose payload is a raw text chunk; split it into lines and enqueue each for the consumer. Any other
534
+ // event name is ignored.
535
+ if ((event.event === "stdout") && (typeof event.payload === "string")) {
536
+ for (const line of splitter.consume(event.payload)) {
537
+ this.#enqueueStdout(line);
538
+ }
539
+ }
540
+ return;
541
+ }
542
+ default: {
543
+ // open / pong / namespaceConnect / namespaceError / unknown in the streaming phase are not actionable here; ignore them.
544
+ return;
545
+ }
546
+ }
547
+ }
548
+ // Enqueue a complete log line for the `stdout()` consumer, enforcing the high-water bound. When the queue is at capacity the OLDEST line is dropped (a live tail cares
549
+ // about recent output, not stale backlog), the drop counter is incremented, and a single warning is logged so a chronically-slow consumer learns it is lossy without
550
+ // the log being flooded. Resolving the parked waiter wakes a blocked consumer.
551
+ #enqueueStdout(line) {
552
+ if (this.#stdoutQueue.length >= this.#stdoutHighWater) {
553
+ this.#stdoutQueue.shift();
554
+ this.#droppedLines++;
555
+ if (!this.#overflowLogged) {
556
+ this.#overflowLogged = true;
557
+ this.#log.warn("The Homebridge log stream is producing output faster than it is being consumed; the oldest buffered lines are being dropped.");
558
+ }
559
+ }
560
+ this.#stdoutQueue.push(line);
561
+ this.#stdoutWaiter?.resolve();
562
+ }
563
+ // Render a WebSocket `"error"` event for diagnostics. The DOM error event carries no structured detail; a Node `ws` error event may carry an `error` field. We surface
564
+ // whichever is present, falling back to a generic label, so the abort cause is at least minimally informative.
565
+ #describeWsError(event) {
566
+ if ((typeof event === "object") && (event !== null) && ("error" in event)) {
567
+ return event.error;
568
+ }
569
+ return new Error("WebSocket connection error.");
570
+ }
571
+ // Close a session's WebSocket cleanly: send the namespace DISCONNECT (`41/log,`) only when the connection is still OPEN, then ALWAYS issue `close(1000)`. Idempotent
572
+ // via the session's one-shot `closed` latch - both the streaming phase's post-end cleanup and the socket-level teardown can reach the same live session, and the latch
573
+ // ensures the DISCONNECT-and-close sequence runs exactly once. The readyState gate avoids a send on a half-closed connection; the unconditional close is the teardown
574
+ // invariant.
575
+ #closeSession(session) {
576
+ if (session.closed) {
577
+ return;
578
+ }
579
+ session.closed = true;
580
+ if (session.ws.readyState === WEBSOCKET_OPEN) {
581
+ try {
582
+ session.ws.send("41" + LOG_NAMESPACE_PATH + ",");
583
+ }
584
+ catch {
585
+ // A send on a connection that closed between the readyState check and here is harmless; the close below is what matters.
586
+ }
587
+ }
588
+ this.#closeWebSocket(session.ws);
589
+ }
590
+ // Issue the orderly WebSocket close, swallowing any throw (closing an already-closed or still-connecting socket can throw on some platforms). `close(1000)` is the
591
+ // normal-closure code; teardown always sends it so the server sees a clean disconnect.
592
+ #closeWebSocket(ws) {
593
+ try {
594
+ ws.close(1000);
595
+ }
596
+ catch {
597
+ // Closing a socket that is already closed (or in a state where close throws) is a no-op for our purposes.
598
+ }
599
+ }
600
+ // Single teardown convergence point, fired exactly once when `this.signal` aborts. Closes the live session's WebSocket (sending the namespace DISCONNECT if OPEN, then
601
+ // always close(1000)) and settles the parked stdout waiter so the consumer's `stdout()` generator wakes, drains the queue, and returns. The reconnect loop observes the
602
+ // aborted signal and exits on its next iteration boundary; the watchdog self-cleans through its own composed-signal listener.
603
+ #teardown() {
604
+ if (this.#session !== undefined) {
605
+ this.#closeSession(this.#session);
606
+ this.#session = undefined;
607
+ }
608
+ this.#stdoutWaiter?.resolve();
609
+ }
610
+ }
611
+ /**
612
+ * The production {@link LogSocketFactory}: builds the concrete WebSocket-backed {@link LogSocket}. A client holds the factory typed as the seam abstraction; a test
613
+ * substitutes a fake factory. `create` is exactly the {@link LogSocket} constructor call, so wiring construction through this seam is behavior-neutral.
614
+ *
615
+ * @category Log Client
616
+ */
617
+ export const logSocketFactory = {
618
+ create: (init) => new LogSocket(init)
619
+ };
620
+ //# sourceMappingURL=socket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.js","sourceRoot":"","sources":["../../src/logclient/socket.ts"],"names":[],"mappings":"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"}