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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A decoded protocol event, discriminated on `kind`.
|
|
3
|
+
*
|
|
4
|
+
* The union collapses the two-layer Engine.IO/Socket.IO wire format into the handful of events the log client actually reacts to: the Engine.IO handshake (`open`) and
|
|
5
|
+
* heartbeat (`ping`/`pong`), and the Socket.IO namespace lifecycle (`namespaceConnect`/`namespaceError`) and payload delivery (`message`). Any unrecognized string
|
|
6
|
+
* frame the codec does not model - an empty frame, an unknown Engine.IO digit, or an unhandled Socket.IO packet type - decodes to `unknown` carrying the original text,
|
|
7
|
+
* so a consumer can log it without the codec having to model every Engine.IO/Socket.IO packet type that the log stream never exercises.
|
|
8
|
+
*
|
|
9
|
+
* @category Log Client
|
|
10
|
+
*/
|
|
11
|
+
export type ProtocolEvent = {
|
|
12
|
+
readonly kind: "message";
|
|
13
|
+
readonly event: string;
|
|
14
|
+
readonly namespace: string;
|
|
15
|
+
readonly payload: unknown;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "namespaceConnect";
|
|
18
|
+
readonly namespace: string;
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "namespaceError";
|
|
21
|
+
readonly namespace: string;
|
|
22
|
+
readonly reason: unknown;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "open";
|
|
25
|
+
readonly pingInterval: number;
|
|
26
|
+
readonly pingTimeout: number;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "ping";
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "pong";
|
|
31
|
+
} | {
|
|
32
|
+
readonly kind: "unknown";
|
|
33
|
+
readonly raw: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* An outbound protocol event to serialize, discriminated on `kind`.
|
|
37
|
+
*
|
|
38
|
+
* The OutboundEvent union models three frame shapes: a namespace connect (`connect`), a namespace event with a JSON payload (`event` - this is how `tail-log` is
|
|
39
|
+
* requested), and a heartbeat pong (`pong`). A fourth outbound frame, the namespace DISCONNECT, is hand-assembled separately (see {@link LOG_NAMESPACE_PATH}) because its
|
|
40
|
+
* fixed, argument-free shape needs no serialization through this codec. Modeling the outbound set as its own narrow union keeps {@link encodeFrame} total over exactly
|
|
41
|
+
* what it models, rather than re-using the wider inbound {@link ProtocolEvent} and leaving unserializable arms.
|
|
42
|
+
*
|
|
43
|
+
* @category Log Client
|
|
44
|
+
*/
|
|
45
|
+
export type OutboundEvent = {
|
|
46
|
+
readonly args: unknown;
|
|
47
|
+
readonly event: string;
|
|
48
|
+
readonly kind: "event";
|
|
49
|
+
readonly namespace: string;
|
|
50
|
+
} | {
|
|
51
|
+
readonly kind: "connect";
|
|
52
|
+
readonly namespace: string;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "pong";
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Serialize an outbound protocol event to its Engine.IO/Socket.IO wire text.
|
|
58
|
+
*
|
|
59
|
+
* Always emits EIO4-shaped frames: a namespace connect is `40<nsPrefix>`, an event is `42<nsPrefix>[event,args]`, and a pong is the bare Engine.IO `"3"`. The event
|
|
60
|
+
* payload is `JSON.stringify`d as the `[eventName, args]` array the Socket.IO EVENT format requires.
|
|
61
|
+
*
|
|
62
|
+
* @param event - The outbound event to serialize. See {@link OutboundEvent}.
|
|
63
|
+
*
|
|
64
|
+
* @returns The wire text to send as a single WebSocket message.
|
|
65
|
+
*
|
|
66
|
+
* @category Log Client
|
|
67
|
+
*/
|
|
68
|
+
export declare function encodeFrame(event: OutboundEvent): string;
|
|
69
|
+
/**
|
|
70
|
+
* Decode a single inbound WebSocket frame into a {@link ProtocolEvent}.
|
|
71
|
+
*
|
|
72
|
+
* The decoder peels the leading Engine.IO packet-type digit and dispatches: `0` is the handshake open (whose JSON carries the ping interval/timeout), `2`/`3` are the
|
|
73
|
+
* heartbeat, and `4` is a message whose remaining text is a Socket.IO packet decoded by the inner namespace/event parser. Its input is typed `string` - the composing
|
|
74
|
+
* socket drops binary frames before they reach the decoder - so any unrecognized STRING frame (an empty frame, an unknown Engine.IO digit, or an unhandled Socket.IO
|
|
75
|
+
* packet type) decodes to `unknown` carrying the original text.
|
|
76
|
+
*
|
|
77
|
+
* @param frame - The raw WebSocket message text.
|
|
78
|
+
*
|
|
79
|
+
* @returns The decoded protocol event.
|
|
80
|
+
*
|
|
81
|
+
* @category Log Client
|
|
82
|
+
*/
|
|
83
|
+
export declare function decodeFrame(frame: string): ProtocolEvent;
|
|
84
|
+
/**
|
|
85
|
+
* The Socket.IO namespace, with its leading slash, that the log stream is served on. The wire format addresses namespaces with a leading slash (`/log`), whereas the
|
|
86
|
+
* `LOG_NAMESPACE` setting holds the bare name (`log`). This constant supplies the slash-prefixed namespace token used to hand-assemble the raw Socket.IO disconnect frame
|
|
87
|
+
* (`"41/log,"`), where {@link encodeFrame} is bypassed. It must NOT be passed to `encodeFrame`: that codec's connect and event arms take the bare `LOG_NAMESPACE` and
|
|
88
|
+
* derive the leading slash themselves, so handing them `LOG_NAMESPACE_PATH` would prepend a second slash and emit a malformed `"//log,"` prefix.
|
|
89
|
+
*
|
|
90
|
+
* @category Log Client
|
|
91
|
+
*/
|
|
92
|
+
export declare const LOG_NAMESPACE_PATH: string;
|
|
93
|
+
//# sourceMappingURL=frame.d.ts.map
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* logclient/frame.ts: Pure Engine.IO / Socket.IO wire codec for the log namespace.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Pure Engine.IO / Socket.IO v4 wire codec.
|
|
7
|
+
*
|
|
8
|
+
* The log client speaks raw Engine.IO over a single WebSocket: each WebSocket message is exactly one Engine.IO packet (no `\x1e` payload batching, since batching is an
|
|
9
|
+
* HTTP-long-polling concern, not a WebSocket one), and a Socket.IO packet is layered inside an Engine.IO `message` packet. This module is the single source of truth for
|
|
10
|
+
* that two-layer framing - the Engine.IO packet-type digits (`0` open, `2` ping, `3` pong, `4` message), the Socket.IO packet-type digits inside a message (`0` CONNECT,
|
|
11
|
+
* `2` EVENT, `4` CONNECT_ERROR), and the `/log,` namespace prefix. It is deliberately pure and stateless, mirroring the `mp4-parser.ts` / `rtp-parser.ts` pure-parser
|
|
12
|
+
* precedent: it turns wire text into a {@link ProtocolEvent} discriminated union and back, and leaves all I/O, liveness, and lifecycle to the composing socket.
|
|
13
|
+
*
|
|
14
|
+
* `allowEIO3` interop: the server is started with `allowEIO3: true`, so a downgraded EIO3 client would see the Socket.IO CONNECT acknowledgement as a bare `40` with no
|
|
15
|
+
* namespace and no trailing payload, where EIO4 sends `40/log,{"sid":"..."}`. {@link decodeFrame} treats both shapes as a namespace-connect for whichever namespace the
|
|
16
|
+
* frame names (defaulting to the root namespace when none is present), so a consumer reading the decoded `kind` does not have to branch on the negotiated protocol
|
|
17
|
+
* version. We always emit EIO4-shaped frames from {@link encodeFrame}; the interop only matters on the decode side.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { LOG_NAMESPACE } from "./settings.js";
|
|
22
|
+
// The Engine.IO packet-type digit for an `open` packet: the server's first frame, carrying the session handshake JSON (ping interval/timeout, sid).
|
|
23
|
+
const ENGINE_OPEN = "0";
|
|
24
|
+
// The Engine.IO packet-type digit for a `ping`. Under the WebSocket transport in EIO4 the server is the pinger, so an inbound `"2"` is a server ping the client must
|
|
25
|
+
// answer with a `"3"` pong.
|
|
26
|
+
const ENGINE_PING = "2";
|
|
27
|
+
// The Engine.IO packet-type digit for a `pong`. Surfaced for completeness on decode; the client emits one via {@link encodeFrame} in response to each server ping.
|
|
28
|
+
const ENGINE_PONG = "3";
|
|
29
|
+
// The Engine.IO packet-type digit for a `message`: the envelope that carries a Socket.IO packet as its remaining text.
|
|
30
|
+
const ENGINE_MESSAGE = "4";
|
|
31
|
+
// The Socket.IO packet-type digit (inside an Engine.IO message) for a namespace CONNECT acknowledgement.
|
|
32
|
+
const SOCKET_CONNECT = "0";
|
|
33
|
+
// The Socket.IO packet-type digit for an EVENT - the packet that carries `[eventName, ...args]` JSON, which is how the server delivers `stdout` log chunks.
|
|
34
|
+
const SOCKET_EVENT = "2";
|
|
35
|
+
// The Socket.IO packet-type digit for a CONNECT_ERROR - the server's rejection of a namespace connect (e.g., an auth failure surfaced at the namespace layer).
|
|
36
|
+
const SOCKET_CONNECT_ERROR = "4";
|
|
37
|
+
// Build the Socket.IO namespace prefix that addresses a frame at a namespace. The root namespace ("/") is implicit on the wire and carries no prefix; any other
|
|
38
|
+
// namespace is emitted as `/<name>,` - note the leading slash the server expects and the trailing comma that terminates the namespace token. Centralized here so both
|
|
39
|
+
// the connect and event encoders address namespaces identically.
|
|
40
|
+
function namespacePrefix(namespace) {
|
|
41
|
+
if (namespace === "/") {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
return "/" + namespace + ",";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Serialize an outbound protocol event to its Engine.IO/Socket.IO wire text.
|
|
48
|
+
*
|
|
49
|
+
* Always emits EIO4-shaped frames: a namespace connect is `40<nsPrefix>`, an event is `42<nsPrefix>[event,args]`, and a pong is the bare Engine.IO `"3"`. The event
|
|
50
|
+
* payload is `JSON.stringify`d as the `[eventName, args]` array the Socket.IO EVENT format requires.
|
|
51
|
+
*
|
|
52
|
+
* @param event - The outbound event to serialize. See {@link OutboundEvent}.
|
|
53
|
+
*
|
|
54
|
+
* @returns The wire text to send as a single WebSocket message.
|
|
55
|
+
*
|
|
56
|
+
* @category Log Client
|
|
57
|
+
*/
|
|
58
|
+
export function encodeFrame(event) {
|
|
59
|
+
switch (event.kind) {
|
|
60
|
+
case "connect": {
|
|
61
|
+
// A namespace CONNECT is an Engine.IO message (`4`) wrapping a Socket.IO CONNECT (`0`), addressed at the namespace.
|
|
62
|
+
return ENGINE_MESSAGE + SOCKET_CONNECT + namespacePrefix(event.namespace);
|
|
63
|
+
}
|
|
64
|
+
case "event": {
|
|
65
|
+
// A namespace EVENT is an Engine.IO message (`4`) wrapping a Socket.IO EVENT (`2`), addressed at the namespace, followed by the `[eventName, args]` JSON array.
|
|
66
|
+
return ENGINE_MESSAGE + SOCKET_EVENT + namespacePrefix(event.namespace) + JSON.stringify([event.event, event.args]);
|
|
67
|
+
}
|
|
68
|
+
case "pong": {
|
|
69
|
+
// A pong is a bare Engine.IO packet with no Socket.IO layer.
|
|
70
|
+
return ENGINE_PONG;
|
|
71
|
+
}
|
|
72
|
+
default: {
|
|
73
|
+
// Every OutboundEvent arm above returns, so this `default` is an unreachable fallback that yields an empty string; it provides no compile-time exhaustiveness
|
|
74
|
+
// guard, so a future arm added without its own case would silently fall through to this empty frame rather than failing to compile.
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Parse the Socket.IO body that follows the Engine.IO message digit. The body is `<socketDigit>[<nsPrefix>][<ackId>][<json>]`; we peel each segment in order and return
|
|
80
|
+
// the resulting protocol event. Kept as a focused helper so {@link decodeFrame} stays a readable Engine.IO-layer switch.
|
|
81
|
+
function decodeSocketMessage(body) {
|
|
82
|
+
const socketDigit = body.charAt(0);
|
|
83
|
+
let rest = body.slice(1);
|
|
84
|
+
// Peel an optional namespace prefix. A namespaced frame begins with `/`; the namespace token runs up to (and is terminated by) the first comma. When no leading slash
|
|
85
|
+
// is present the frame addresses the root namespace, which carries no prefix on the wire.
|
|
86
|
+
let namespace = "/";
|
|
87
|
+
if (rest.startsWith("/")) {
|
|
88
|
+
const comma = rest.indexOf(",");
|
|
89
|
+
// A namespace prefix without its terminating comma is malformed; treat the whole remainder as the namespace and leave nothing for the payload, which the JSON peel
|
|
90
|
+
// below tolerates as an empty body.
|
|
91
|
+
if (comma === -1) {
|
|
92
|
+
namespace = rest.slice(1);
|
|
93
|
+
rest = "";
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
namespace = rest.slice(1, comma);
|
|
97
|
+
rest = rest.slice(comma + 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
switch (socketDigit) {
|
|
101
|
+
case SOCKET_CONNECT: {
|
|
102
|
+
// A namespace CONNECT acknowledgement. EIO4 carries a `{"sid":"..."}` payload we do not need; EIO3 (under `allowEIO3`) carries nothing. Either way the event is a
|
|
103
|
+
// namespace connect for the named namespace - the payload is intentionally discarded.
|
|
104
|
+
return { kind: "namespaceConnect", namespace };
|
|
105
|
+
}
|
|
106
|
+
case SOCKET_CONNECT_ERROR: {
|
|
107
|
+
// A namespace CONNECT_ERROR. The trailing JSON, when present, is the server's rejection reason (often `{ message }`); we surface it verbatim as `reason` for the
|
|
108
|
+
// consumer to render. A malformed or absent reason decodes to `null`.
|
|
109
|
+
return { kind: "namespaceError", namespace, reason: safeJsonParse(rest) };
|
|
110
|
+
}
|
|
111
|
+
case SOCKET_EVENT: {
|
|
112
|
+
// A namespace EVENT carrying `[eventName, ...args]`. Peel an optional numeric ack id that may precede the JSON payload (the server does not request acks on the
|
|
113
|
+
// log stream, but the format permits one, so we skip leading digits before the first JSON-value start character: `[`, `{`, or `"`). The first array element is
|
|
114
|
+
// the event name; the second is the payload we forward.
|
|
115
|
+
const jsonStart = rest.search(/[[{"]/);
|
|
116
|
+
const json = jsonStart === -1 ? rest : rest.slice(jsonStart);
|
|
117
|
+
const parsed = safeJsonParse(json);
|
|
118
|
+
if (!Array.isArray(parsed) || (parsed.length === 0) || (typeof parsed[0] !== "string")) {
|
|
119
|
+
// An EVENT whose body is not a `[stringEventName, ...]` array is not something we can route; surface it as unknown rather than fabricating an event name.
|
|
120
|
+
return { kind: "unknown", raw: ENGINE_MESSAGE + body };
|
|
121
|
+
}
|
|
122
|
+
return { event: parsed[0], kind: "message", namespace, payload: parsed.length > 1 ? parsed[1] : undefined };
|
|
123
|
+
}
|
|
124
|
+
default: {
|
|
125
|
+
// Any other Socket.IO packet type (ACK, BINARY_EVENT, BINARY_ACK, DISCONNECT) is not part of the log stream's vocabulary; surface the original message frame as
|
|
126
|
+
// unknown so the consumer can log it without us modeling packet types the stream never produces.
|
|
127
|
+
return { kind: "unknown", raw: ENGINE_MESSAGE + body };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Parse JSON, returning `null` rather than throwing on malformed input. The wire is a trusted server, but a partial or non-JSON body must not crash the decoder; the
|
|
132
|
+
// caller treats a `null` result as "no structured payload."
|
|
133
|
+
function safeJsonParse(text) {
|
|
134
|
+
if (text.length === 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(text);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Decode a single inbound WebSocket frame into a {@link ProtocolEvent}.
|
|
146
|
+
*
|
|
147
|
+
* The decoder peels the leading Engine.IO packet-type digit and dispatches: `0` is the handshake open (whose JSON carries the ping interval/timeout), `2`/`3` are the
|
|
148
|
+
* heartbeat, and `4` is a message whose remaining text is a Socket.IO packet decoded by the inner namespace/event parser. Its input is typed `string` - the composing
|
|
149
|
+
* socket drops binary frames before they reach the decoder - so any unrecognized STRING frame (an empty frame, an unknown Engine.IO digit, or an unhandled Socket.IO
|
|
150
|
+
* packet type) decodes to `unknown` carrying the original text.
|
|
151
|
+
*
|
|
152
|
+
* @param frame - The raw WebSocket message text.
|
|
153
|
+
*
|
|
154
|
+
* @returns The decoded protocol event.
|
|
155
|
+
*
|
|
156
|
+
* @category Log Client
|
|
157
|
+
*/
|
|
158
|
+
export function decodeFrame(frame) {
|
|
159
|
+
// An empty frame carries no packet-type digit; classify it as unknown rather than indexing into nothing.
|
|
160
|
+
if (frame.length === 0) {
|
|
161
|
+
return { kind: "unknown", raw: frame };
|
|
162
|
+
}
|
|
163
|
+
const engineDigit = frame.charAt(0);
|
|
164
|
+
const body = frame.slice(1);
|
|
165
|
+
switch (engineDigit) {
|
|
166
|
+
case ENGINE_OPEN: {
|
|
167
|
+
// The Engine.IO open handshake. Its JSON body carries `pingInterval` and `pingTimeout` (milliseconds), which the socket uses to size its liveness watchdog. A
|
|
168
|
+
// malformed or absent handshake decodes to zero intervals, which the consumer treats as "fall back to defaults."
|
|
169
|
+
const handshake = safeJsonParse(body);
|
|
170
|
+
const intervalValue = isRecord(handshake) ? handshake["pingInterval"] : undefined;
|
|
171
|
+
const timeoutValue = isRecord(handshake) ? handshake["pingTimeout"] : undefined;
|
|
172
|
+
const pingInterval = (typeof intervalValue === "number") ? intervalValue : 0;
|
|
173
|
+
const pingTimeout = (typeof timeoutValue === "number") ? timeoutValue : 0;
|
|
174
|
+
return { kind: "open", pingInterval, pingTimeout };
|
|
175
|
+
}
|
|
176
|
+
case ENGINE_PING: {
|
|
177
|
+
return { kind: "ping" };
|
|
178
|
+
}
|
|
179
|
+
case ENGINE_PONG: {
|
|
180
|
+
return { kind: "pong" };
|
|
181
|
+
}
|
|
182
|
+
case ENGINE_MESSAGE: {
|
|
183
|
+
return decodeSocketMessage(body);
|
|
184
|
+
}
|
|
185
|
+
default: {
|
|
186
|
+
return { kind: "unknown", raw: frame };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Narrow an unknown value to a plain record so its properties can be read without unsafe member access. Used to read the handshake JSON's numeric fields defensively.
|
|
191
|
+
function isRecord(value) {
|
|
192
|
+
return (typeof value === "object") && (value !== null);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The Socket.IO namespace, with its leading slash, that the log stream is served on. The wire format addresses namespaces with a leading slash (`/log`), whereas the
|
|
196
|
+
* `LOG_NAMESPACE` setting holds the bare name (`log`). This constant supplies the slash-prefixed namespace token used to hand-assemble the raw Socket.IO disconnect frame
|
|
197
|
+
* (`"41/log,"`), where {@link encodeFrame} is bypassed. It must NOT be passed to `encodeFrame`: that codec's connect and event arms take the bare `LOG_NAMESPACE` and
|
|
198
|
+
* derive the leading slash themselves, so handing them `LOG_NAMESPACE_PATH` would prepend a second slash and emit a malformed `"//log,"` prefix.
|
|
199
|
+
*
|
|
200
|
+
* @category Log Client
|
|
201
|
+
*/
|
|
202
|
+
export const LOG_NAMESPACE_PATH = "/" + LOG_NAMESPACE;
|
|
203
|
+
//# sourceMappingURL=frame.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frame.js","sourceRoot":"","sources":["../../src/logclient/frame.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The log client's public surface, re-exported for consumption through the package barrel (`import { HomebridgeLogClient } from "homebridge-plugin-utils"`).
|
|
3
|
+
*
|
|
4
|
+
* This is a curated barrel, not an `export *` of every module: the subsystem has a deliberate public/internal split. The consumer-facing engine (the client, its options
|
|
5
|
+
* and stream type, the domain vocabulary, the line parser/splitter, the filter), the dependency-inversion seams power users need to substitute their own transport (the
|
|
6
|
+
* socket and WebSocket factories plus their production defaults), the lower-level transports that power users may compose directly (`acquireToken`, `downloadLog`,
|
|
7
|
+
* `LogSocket`), and the reusable test doubles are surfaced here. The purely-internal machinery - the Engine.IO/Socket.IO frame codec, the history-plus-live stitch, the
|
|
8
|
+
* URL builders, the scalar settings, the time-range internals (`parseTimeExpression`, `timeWindow`, `normalizeClock`), and the CLI layer (`config.ts`, `cli-run.ts`,
|
|
9
|
+
* `cli.ts`) - is intentionally NOT re-exported, so those internals can be refactored without a breaking change to the published surface. Every symbol below carries an
|
|
10
|
+
* `@category Log Client` tag (or `@category Testing` for the doubles).
|
|
11
|
+
*
|
|
12
|
+
* The CLI bin (`cli.ts`) does not ride this barrel: a bin must reach its engine through a realpath-canonicalized dynamic import rather than a package specifier, so it is
|
|
13
|
+
* deliberately absent here. See `cli.ts` for that rationale.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
export type { LogClientCredentials, LogLevel, LogQuantity, LogRecord, TailRequest } from "./types.ts";
|
|
18
|
+
export type { HomebridgeLogClientOptions, LogStream } from "./client.ts";
|
|
19
|
+
export type { LogSocketFactory, LogSocketInit, LogSocketLike, TokenProvider, WebSocketFactory, WebSocketLike } from "./socket.ts";
|
|
20
|
+
export type { AcquireTokenOptions, LogAuthErrorKind, LogAuthErrorOptions } from "./auth.ts";
|
|
21
|
+
export type { DownloadLogOptions } from "./rest.ts";
|
|
22
|
+
export type { LogFilterCriteria } from "./filter.ts";
|
|
23
|
+
export type { TestLogSocketInit } from "./socket-double.ts";
|
|
24
|
+
export { LogSocket, WEBSOCKET_OPEN, logSocketFactory, reconnectBackoff, webSocketFactory } from "./socket.ts";
|
|
25
|
+
export { LogAuthError, acquireToken, isPermanentAuthError } from "./auth.ts";
|
|
26
|
+
export { LogLineSplitter, parseLogLine, parseLogTimestamp } from "./parser.ts";
|
|
27
|
+
export { TestLogSocket, TestLogSocketFactory, TestWebSocket, TestWebSocketFactory } from "./socket-double.ts";
|
|
28
|
+
export { HomebridgeLogClient } from "./client.ts";
|
|
29
|
+
export { createLogFilter } from "./filter.ts";
|
|
30
|
+
export { downloadLog } from "./rest.ts";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* logclient/index.ts: Public barrel for the Homebridge UI log client (the hblog engine).
|
|
4
|
+
*/
|
|
5
|
+
export { LogSocket, WEBSOCKET_OPEN, logSocketFactory, reconnectBackoff, webSocketFactory } from "./socket.js";
|
|
6
|
+
export { LogAuthError, acquireToken, isPermanentAuthError } from "./auth.js";
|
|
7
|
+
export { LogLineSplitter, parseLogLine, parseLogTimestamp } from "./parser.js";
|
|
8
|
+
export { TestLogSocket, TestLogSocketFactory, TestWebSocket, TestWebSocketFactory } from "./socket-double.js";
|
|
9
|
+
export { HomebridgeLogClient } from "./client.js";
|
|
10
|
+
export { createLogFilter } from "./filter.js";
|
|
11
|
+
export { downloadLog } from "./rest.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/logclient/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyBH,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9G,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, incremental text-to-{@link LogRecord} parsing for the Homebridge UI log stream.
|
|
3
|
+
*
|
|
4
|
+
* The pure pieces that live here, shared by the socket and REST transports, are:
|
|
5
|
+
*
|
|
6
|
+
* - {@link LogLineSplitter} - a cursor-based incremental splitter that turns an unbounded stream of text chunks (from a WebSocket `stdout` event or a streamed REST
|
|
7
|
+
* download) into discrete raw lines, transparently handling lines split across chunk boundaries and the four newline conventions the stream mixes.
|
|
8
|
+
* - {@link parseLogLine} - a per-line parser that extracts the timestamp/plugin brackets and the ANSI-color-derived severity level, producing a {@link LogRecord} whose
|
|
9
|
+
* `message` is ANSI-stripped and whose `raw` preserves the original escapes.
|
|
10
|
+
*
|
|
11
|
+
* A third pure function, {@link parseLogTimestamp} (with its shared {@link normalizeClock} clock-rule helper), interprets a {@link LogRecord.timestamp} string as an
|
|
12
|
+
* epoch instant on demand. It is deliberately separate from {@link parseLogLine}, which leaves the timestamp as text: the epoch is a cold, cheaply-derivable value that
|
|
13
|
+
* only the time-range query path needs, so it is computed by this one shared function when required rather than eagerly materialized on every parsed line.
|
|
14
|
+
*
|
|
15
|
+
* This module is the single owner of the ANSI escape regex and the SGR-code-to-{@link LogLevel} map - they live beside the parser that uses them, not in `settings.ts`
|
|
16
|
+
* (which holds only scalars). The design mirrors the library's per-consumer incremental-assembly pattern (`process.ts`, `mp4-assembler.ts`): rather than sharing a
|
|
17
|
+
* splitter core with `process.ts`, this splitter is purpose-built, because the two have opposite control-character requirements - `process.ts` strips non-printable
|
|
18
|
+
* control characters to `os.EOL`, whereas this splitter must PRESERVE control characters because the ANSI color sequence IS the severity data.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import type { LogRecord } from "./types.ts";
|
|
23
|
+
import type { Nullable } from "../util.ts";
|
|
24
|
+
/**
|
|
25
|
+
* Cursor-based incremental line splitter for the log text stream.
|
|
26
|
+
*
|
|
27
|
+
* Feed each text chunk through {@link LogLineSplitter.consume} and iterate the raw lines it yields. The splitter carries the bytes of an incomplete trailing line across
|
|
28
|
+
* calls, so a line split across two chunks is reassembled transparently, and it recognizes all four newline conventions the PTY-driven stream mixes (`\r\n`, `\n\r`,
|
|
29
|
+
* `\r`, `\n`) as single line breaks. Crucially, a chunk that ends with a lone `\r` or `\n` holds that terminator in the carry rather than yielding immediately: the next
|
|
30
|
+
* chunk may begin with the matching second half of a `\n\r`/`\r\n` pair, and emitting eagerly would split one line break into two and inject a phantom blank line.
|
|
31
|
+
*
|
|
32
|
+
* The scan is a single integer cursor over the carried buffer; the buffer is rebuilt once per `consume` (the residual tail becomes the next carry), not once per line,
|
|
33
|
+
* so per-line work is a single `slice` of the line's content with no quadratic buffer churn. Because the splitter holds at most one partial line plus the current chunk,
|
|
34
|
+
* its memory is bounded by chunk size regardless of total stream length.
|
|
35
|
+
*
|
|
36
|
+
* The class is intentionally signal-free and event-free. Resource lifecycle and async consumption belong to the composing transport, exactly as `Mp4BoxParser` leaves
|
|
37
|
+
* those concerns to `Mp4SegmentAssembler`.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* const splitter = new LogLineSplitter();
|
|
43
|
+
*
|
|
44
|
+
* for(const line of splitter.consume(chunk)) {
|
|
45
|
+
*
|
|
46
|
+
* handle(parseLogLine(line));
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @category Log Client
|
|
51
|
+
*/
|
|
52
|
+
export declare class LogLineSplitter {
|
|
53
|
+
#private;
|
|
54
|
+
/**
|
|
55
|
+
* Feed the splitter a text chunk and yield every complete raw line now available.
|
|
56
|
+
*
|
|
57
|
+
* Each yielded line is the content between line breaks with the terminator removed and ANSI escapes preserved. A line that is still incomplete (no terminator yet, or
|
|
58
|
+
* only a held lone terminator that may pair with the next chunk) is carried internally and surfaces on a later call once its break is unambiguous.
|
|
59
|
+
*
|
|
60
|
+
* @param chunk - A contiguous slice of log text from the transport.
|
|
61
|
+
*
|
|
62
|
+
* @returns An iterable of every complete raw line contained in (or completed by) this chunk, in stream order.
|
|
63
|
+
*/
|
|
64
|
+
consume(chunk: string): Iterable<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Flush any buffered final line that never received a terminator.
|
|
67
|
+
*
|
|
68
|
+
* A log stream may end without a trailing newline, leaving the last line held in the carry. Call this once after the source has ended to surface that final line. It
|
|
69
|
+
* yields at most one line and clears the carry; calling it again yields nothing. A held lone terminator with no content (a trailing bare `\r`/`\n`) flushes as an
|
|
70
|
+
* empty final line, matching the convention that a terminator delimits a line that exists.
|
|
71
|
+
*
|
|
72
|
+
* @returns An iterable yielding the final unterminated line, or nothing if the carry is empty.
|
|
73
|
+
*/
|
|
74
|
+
flush(): Iterable<string>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse a single raw log line into a {@link LogRecord}.
|
|
78
|
+
*
|
|
79
|
+
* The `[timestamp] [Plugin Name] ` prefix is extracted from the ANSI-stripped text, and the remainder becomes the human-readable `message`. The severity level is read
|
|
80
|
+
* from the SGR color sequence that the server wraps around the MESSAGE - which means it appears AFTER the timestamp/plugin brackets, not at the start of the line: the
|
|
81
|
+
* timestamp (white) and plugin (cyan) carry their own colors, so reading the line's first color would always misclassify them as severity. We therefore scan for the
|
|
82
|
+
* severity color starting just past the plugin bracket. The original line is preserved verbatim in `raw`. A colored line that carries no severity color resolves to
|
|
83
|
+
* `level: "info"` (Homebridge's info convention), while a fully color-stripped line resolves to `level: null` because severity is genuinely unknown; a line with no
|
|
84
|
+
* bracketed prefix resolves to `timestamp: null` / `plugin: null`, a `message` equal to the full stripped line, and a level read from any severity color present
|
|
85
|
+
* anywhere in the (prefix-less) line.
|
|
86
|
+
*
|
|
87
|
+
* @param raw - A single raw log line (escapes intact, terminator already removed by {@link LogLineSplitter}).
|
|
88
|
+
*
|
|
89
|
+
* @returns The parsed {@link LogRecord}.
|
|
90
|
+
*
|
|
91
|
+
* @category Log Client
|
|
92
|
+
*/
|
|
93
|
+
export declare function parseLogLine(raw: string): LogRecord;
|
|
94
|
+
/**
|
|
95
|
+
* Parse a Homebridge log timestamp into epoch milliseconds, or `null` when the text is not a recognized timestamp.
|
|
96
|
+
*
|
|
97
|
+
* The Homebridge UI renders the first bracketed field as the host's locale/clock string. This recognizes the en-US default in both its renderings - the 12-hour
|
|
98
|
+
* `M/D/YYYY, h:mm:ss AM` and the 24-hour `M/D/YYYY, HH:mm:ss` - via a single regex, constructs the instant in LOCAL time (matching how the server formats it in the
|
|
99
|
+
* host's own timezone), and returns its epoch milliseconds. The shared {@link normalizeClock} helper owns the meridiem-to-24-hour conversion and the clock-component
|
|
100
|
+
* range check, so that rule lives in exactly one place.
|
|
101
|
+
*
|
|
102
|
+
* Deliberate limitations, in the same register as the rest of the client: only the en-US 12h/24h default is recognized, so a server running another locale yields
|
|
103
|
+
* `null` (the time-window stage carries forward the most recent parsed instant, so a null-epoch line is kept iff its parent record is in-window, never dropped merely for
|
|
104
|
+
* lacking a parseable epoch); and the interpretation is LOCAL time, so a client whose timezone differs from the server's skews the absolute values. A well-formed but
|
|
105
|
+
* impossible calendar date (for example `2/30`) returns `null`; a wall-clock time that falls in the client's DST spring-forward gap is best-effort accepted rather than
|
|
106
|
+
* rejected, because only the calendar fields are round-trip-validated.
|
|
107
|
+
*
|
|
108
|
+
* @param text - The raw timestamp text from a {@link LogRecord.timestamp} (the first bracketed field), for example `"6/29/2026, 7:00:00 AM"`.
|
|
109
|
+
*
|
|
110
|
+
* @returns The instant as epoch milliseconds, or `null` when the text is not a recognized en-US timestamp or names an impossible calendar date.
|
|
111
|
+
*
|
|
112
|
+
* @category Log Client
|
|
113
|
+
*/
|
|
114
|
+
export declare function parseLogTimestamp(text: string): Nullable<number>;
|
|
115
|
+
/**
|
|
116
|
+
* Normalize a raw clock reading - the captured hour/minute/second plus an optional meridiem token - into a 24-hour `{ hour, minute, second }`, or `null` when any
|
|
117
|
+
* component is out of range. This is the SINGLE source of truth for the meridiem-to-24-hour conversion and the clock-component range check, shared by
|
|
118
|
+
* {@link parseLogTimestamp} and the CLI-layer time-expression parser so neither re-implements the rule. It is a named export for those consumers and its own test, but it
|
|
119
|
+
* is intentionally not part of the package barrel.
|
|
120
|
+
*
|
|
121
|
+
* Range rules: minute and second are 0-59 in every rendering; when a meridiem is present the hour is a 12-hour value (1-12), and without one it is a 24-hour value
|
|
122
|
+
* (0-23). An out-of-range component yields `null`. The conversion maps `12am` to 0 and `12pm` to 12, adds 12 to any other PM hour, and leaves any other AM hour and every
|
|
123
|
+
* 24-hour reading unchanged.
|
|
124
|
+
*
|
|
125
|
+
* @param parts - The raw clock components.
|
|
126
|
+
* @param parts.hour - The hour as written (1-12 with a meridiem, 0-23 without).
|
|
127
|
+
* @param parts.meridiem - The matched `AM`/`PM` token (any case) when the source carried one, or `undefined` for a 24-hour rendering.
|
|
128
|
+
* @param parts.minute - The minute (0-59).
|
|
129
|
+
* @param parts.second - The second (0-59).
|
|
130
|
+
*
|
|
131
|
+
* @returns The normalized 24-hour `{ hour, minute, second }`, or `null` when any component is out of range.
|
|
132
|
+
*
|
|
133
|
+
* @category Log Client
|
|
134
|
+
*/
|
|
135
|
+
export declare function normalizeClock(parts: {
|
|
136
|
+
hour: number;
|
|
137
|
+
meridiem?: string;
|
|
138
|
+
minute: number;
|
|
139
|
+
second: number;
|
|
140
|
+
}): Nullable<{
|
|
141
|
+
hour: number;
|
|
142
|
+
minute: number;
|
|
143
|
+
second: number;
|
|
144
|
+
}>;
|
|
145
|
+
/**
|
|
146
|
+
* Determine whether a raw log line begins a new Homebridge log entry - that is, whether it opens with a parseable bracketed timestamp.
|
|
147
|
+
*
|
|
148
|
+
* Every genuine entry the server emits starts with a `[timestamp]` field; a `[plugin]` bracket may or may not follow, because Homebridge's own core lines (for example
|
|
149
|
+
* `[7/3/2026, 4:31:46 PM] Homebridge v1.11.3 ... is running`) carry a timestamp but no plugin prefix. Only the FIRST bracket is inspected: requiring a second `[plugin]`
|
|
150
|
+
* bracket would wrongly reject those core lines. A line that is not an entry start - the server's `Loading logs...` / `File: ...` seed preamble, a byte-truncated seed
|
|
151
|
+
* fragment, or a continuation line such as a stack frame or a wrapped object dump - has no leading parseable timestamp and returns `false`. ANSI is stripped first
|
|
152
|
+
* because the server wraps the timestamp in an SGR color, so the raw line begins with an escape sequence rather than the `[`.
|
|
153
|
+
*
|
|
154
|
+
* @param raw - A single raw log line (escapes intact, terminator already removed by {@link LogLineSplitter}).
|
|
155
|
+
*
|
|
156
|
+
* @returns `true` when the line opens with a parseable en-US timestamp bracket, `false` otherwise.
|
|
157
|
+
*
|
|
158
|
+
* @category Log Client
|
|
159
|
+
*/
|
|
160
|
+
export declare function isLogLineStart(raw: string): boolean;
|
|
161
|
+
/**
|
|
162
|
+
* A one-way admission latch that suppresses the unusable leading lines of a live socket log seed until the first genuine log entry.
|
|
163
|
+
*
|
|
164
|
+
* The homebridge-config-ui-x native/file log method seeds a live tail by streaming the tail of the log file from a BYTE offset rather than a line boundary, so the first
|
|
165
|
+
* line of file content is a fragment - the tail end of whatever line the offset fell inside - preceded by the server's own `Loading logs...` / `File: ...` preamble. None
|
|
166
|
+
* of that is a showable entry. This gate drops every line until the first that begins a real entry - a parseable leading `[timestamp]`, per {@link isLogLineStart} - then
|
|
167
|
+
* latches OPEN and admits everything thereafter, so the continuation lines and plugin-less core lines that legitimately lack a full prefix flow through untouched.
|
|
168
|
+
*
|
|
169
|
+
* It is composed per stream, exactly as {@link LogLineSplitter} is: the consumer owns WHEN to apply it (only on the byte-seeded socket seed - the REST whole-file
|
|
170
|
+
* download starts at byte 0 and needs no gate), while this owns the latch policy. Once open it never closes, so steady-state admission is a single boolean read and the
|
|
171
|
+
* recognition predicate runs only across the short leading prefix, never the live stream.
|
|
172
|
+
*
|
|
173
|
+
* A bounded safety valve guards the pathological case of a stream whose timestamps are not the recognized en-US rendering (a non-default server locale): if no entry
|
|
174
|
+
* start is seen within `maxSkip` lines, the gate concludes the format is unrecognized, latches open, and admits the rest rather than suppressing the stream forever.
|
|
175
|
+
* `maxSkip` is sized above any plausible leading-noise run yet well below the ~500-line seed, so it never fires for a normal log and bounds worst-case loss when it does.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
*
|
|
179
|
+
* ```ts
|
|
180
|
+
* const gate = new SeedGate(SEED_GATE_MAX_SKIP);
|
|
181
|
+
*
|
|
182
|
+
* for(const line of splitter.consume(chunk)) {
|
|
183
|
+
*
|
|
184
|
+
* if(gate.admit(line)) {
|
|
185
|
+
*
|
|
186
|
+
* emit(line);
|
|
187
|
+
* }
|
|
188
|
+
* }
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* @category Log Client
|
|
192
|
+
*/
|
|
193
|
+
export declare class SeedGate {
|
|
194
|
+
#private;
|
|
195
|
+
/**
|
|
196
|
+
* @param maxSkip - The safety-valve bound: the most leading non-entry lines to drop before opening the latch unconditionally. The consumer injects it (production
|
|
197
|
+
* passes `SEED_GATE_MAX_SKIP`) so a test can drive the bounded-open path with a small value.
|
|
198
|
+
*/
|
|
199
|
+
constructor(maxSkip: number);
|
|
200
|
+
/**
|
|
201
|
+
* Decide whether a raw line is admitted, advancing the latch. Once the latch has opened every line is admitted; before then, only the first line that begins a genuine
|
|
202
|
+
* entry (which opens the latch) or the line at which the skip bound is reached (the unrecognized-format safety valve) is admitted, and every leading non-entry line is
|
|
203
|
+
* dropped.
|
|
204
|
+
*
|
|
205
|
+
* @param line - A single raw log line from the seed stream.
|
|
206
|
+
*
|
|
207
|
+
* @returns `true` if the line should be emitted, `false` if it is leading seed noise to drop.
|
|
208
|
+
*/
|
|
209
|
+
admit(line: string): boolean;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=parser.d.ts.map
|