homebridge-plugin-utils 1.34.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.
- 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 +128 -0
- package/dist/backpressure.js +273 -0
- package/dist/backpressure.js.map +1 -0
- package/dist/cli/index.d.ts +95 -0
- package/dist/cli/index.js +355 -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/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 -257
- 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 +70 -4
- package/dist/ffmpeg/fmp4.js +100 -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 +120 -0
- package/dist/ffmpeg/mp4-assembler.js +387 -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 +143 -91
- package/dist/ffmpeg/process.js +406 -265
- package/dist/ffmpeg/process.js.map +1 -1
- package/dist/ffmpeg/record.d.ts +302 -188
- package/dist/ffmpeg/record.js +408 -560
- package/dist/ffmpeg/record.js.map +1 -1
- package/dist/ffmpeg/recording-process-double.d.ts +144 -0
- package/dist/ffmpeg/recording-process-double.js +168 -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 +473 -244
- package/dist/ffmpeg/rtp.js.map +1 -1
- package/dist/ffmpeg/settings.d.ts +5 -1
- package/dist/ffmpeg/settings.js +20 -2
- package/dist/ffmpeg/settings.js.map +1 -1
- package/dist/ffmpeg/stream.d.ts +57 -107
- package/dist/ffmpeg/stream.js +121 -148
- 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 +12 -5
- 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 +200 -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 +327 -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 +677 -0
- package/dist/ui/webUi.mjs +192 -87
- package/dist/util.d.ts +597 -45
- package/dist/util.js +787 -68
- 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,393 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* logclient/parser.ts: Pure incremental line splitter and per-line parser for Homebridge UI log text.
|
|
4
|
+
*/
|
|
5
|
+
// The ANSI escape-sequence regex. Matches CSI sequences (ESC `[` ... final byte) - which covers the SGR color sequences the log stream uses - so they can be stripped
|
|
6
|
+
// when building the human-readable `message`. Compiled once at module scope since it runs on every parsed line on the parse hot path. Two character classes cover the
|
|
7
|
+
// parameter bytes and then the intermediate bytes; the trailing class is the final byte that terminates the sequence.
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
const ANSI_PATTERN = /\[[0-9;?]*[ -/]*[@-~]/g;
|
|
10
|
+
// The SGR (Select Graphic Rendition) foreground color codes homebridge-config-ui-x uses to convey severity, mapped to the corresponding {@link LogLevel}. The server
|
|
11
|
+
// colors the MESSAGE portion of a line - not the whole line - by wrapping it in a single SGR sequence: 31 (red) error, 33 (yellow) warn, 90 (bright black) debug, 32
|
|
12
|
+
// (green) success. An info line carries no message color, so the SGR code 39 is intentionally absent from this map (`readLevel` yields null for it); `parseLogLine` then
|
|
13
|
+
// promotes a colored line with no severity color to "info", reserving null for a color-stripped line. The timestamp (37/white) and plugin (36/cyan) colors are
|
|
14
|
+
// deliberately absent too - they prefix the message and must never be mistaken for severity. Frozen so the shared module-scope table cannot be mutated by a consumer.
|
|
15
|
+
const SGR_LEVEL = Object.freeze({
|
|
16
|
+
"31": "error",
|
|
17
|
+
"32": "success",
|
|
18
|
+
"33": "warn",
|
|
19
|
+
"90": "debug"
|
|
20
|
+
});
|
|
21
|
+
// Match every SGR sequence in a string globally, capturing each one's numeric parameter. The severity color is read by scanning these from the point AFTER the
|
|
22
|
+
// timestamp/plugin brackets (see {@link readLevel}), because the server colors the message body, not the line prefix; the timestamp's and plugin's own colors sit before
|
|
23
|
+
// that point and are excluded by where the scan starts. The capture group is the parameter digits between `ESC[` and the terminating `m`.
|
|
24
|
+
// eslint-disable-next-line no-control-regex
|
|
25
|
+
const SGR_PATTERN = /\[([0-9;]*)m/g;
|
|
26
|
+
// Match the conventional `[timestamp] [Plugin Name] ` prefix at the start of an ANSI-stripped line, capturing the timestamp and plugin text. Each bracketed field is
|
|
27
|
+
// guaranteed present when the match succeeds: the timestamp is rendered white and the plugin name is rendered cyan; the remainder after the closing plugin bracket and
|
|
28
|
+
// its trailing space is the message body. Both captures use a negated character class (`[^\]]*`) that cannot cross a `]`, so a message containing brackets is not
|
|
29
|
+
// absorbed into the prefix.
|
|
30
|
+
const BRACKET_PREFIX_PATTERN = /^\[([^\]]*)\] \[([^\]]*)\] /;
|
|
31
|
+
// Recognize the Homebridge en-US default timestamp in both renderings the server emits: the 12-hour `M/D/YYYY, h:mm:ss AM` and the 24-hour `M/D/YYYY, HH:mm:ss` (meridiem
|
|
32
|
+
// absent). One module-scope regex serves both - the meridiem group is optional - mirroring the other compiled-once patterns above. The capture groups are, in order:
|
|
33
|
+
// month, day, four-digit year, hour, minute, second, and the optional AM/PM token.
|
|
34
|
+
const LOG_TIMESTAMP_PATTERN = /^(\d{1,2})\/(\d{1,2})\/(\d{4}),\s+(\d{1,2}):(\d{2}):(\d{2})(?:\s*([AaPp][Mm]))?$/;
|
|
35
|
+
// Capture the contents of the FIRST bracket at the very start of an ANSI-stripped line - used by {@link isLogLineStart} to test whether a line opens with a timestamp.
|
|
36
|
+
// Only the first bracket is captured (the negated class cannot cross a `]`), because a genuine log entry begins with `[timestamp]` whether or not a `[plugin]` bracket
|
|
37
|
+
// follows; the timestamp's parseability, not the presence of a second bracket, is what marks an entry start.
|
|
38
|
+
const LOG_LINE_START_PATTERN = /^\[([^\]]*)\]/;
|
|
39
|
+
// Strip every ANSI escape sequence from a string, leaving only the printable text. Used to derive the human-readable `message` from a raw line; the raw line itself
|
|
40
|
+
// retains the escapes so a `--raw` consumer can reproduce the server's coloring.
|
|
41
|
+
function stripAnsi(text) {
|
|
42
|
+
return text.replace(ANSI_PATTERN, "");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Cursor-based incremental line splitter for the log text stream.
|
|
46
|
+
*
|
|
47
|
+
* 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
|
|
48
|
+
* 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`,
|
|
49
|
+
* `\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
|
|
50
|
+
* 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.
|
|
51
|
+
*
|
|
52
|
+
* 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,
|
|
53
|
+
* 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,
|
|
54
|
+
* its memory is bounded by chunk size regardless of total stream length.
|
|
55
|
+
*
|
|
56
|
+
* The class is intentionally signal-free and event-free. Resource lifecycle and async consumption belong to the composing transport, exactly as `Mp4BoxParser` leaves
|
|
57
|
+
* those concerns to `Mp4SegmentAssembler`.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* const splitter = new LogLineSplitter();
|
|
63
|
+
*
|
|
64
|
+
* for(const line of splitter.consume(chunk)) {
|
|
65
|
+
*
|
|
66
|
+
* handle(parseLogLine(line));
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @category Log Client
|
|
71
|
+
*/
|
|
72
|
+
export class LogLineSplitter {
|
|
73
|
+
// The residual carry: the bytes of an incomplete trailing line (and any held lone terminator) from the previous call, prepended to the next chunk. Empty when the
|
|
74
|
+
// previous chunk ended exactly on a line break.
|
|
75
|
+
#carry = "";
|
|
76
|
+
/**
|
|
77
|
+
* Feed the splitter a text chunk and yield every complete raw line now available.
|
|
78
|
+
*
|
|
79
|
+
* 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
|
|
80
|
+
* 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.
|
|
81
|
+
*
|
|
82
|
+
* @param chunk - A contiguous slice of log text from the transport.
|
|
83
|
+
*
|
|
84
|
+
* @returns An iterable of every complete raw line contained in (or completed by) this chunk, in stream order.
|
|
85
|
+
*/
|
|
86
|
+
*consume(chunk) {
|
|
87
|
+
// Prepend the carry only when there is one, keeping the common "chunk completes its own lines and nothing is pending" path free of an empty-string concatenation.
|
|
88
|
+
const buffer = this.#carry.length > 0 ? this.#carry + chunk : chunk;
|
|
89
|
+
// `start` marks the beginning of the current line; `cursor` scans forward looking for terminators. We slice each line's content from `[start, breakStart)` and never
|
|
90
|
+
// rebuild `buffer` mid-loop - the residual tail is computed once at the end.
|
|
91
|
+
let start = 0;
|
|
92
|
+
let cursor = 0;
|
|
93
|
+
while (cursor < buffer.length) {
|
|
94
|
+
const character = buffer.charAt(cursor);
|
|
95
|
+
// Only `\r` and `\n` can start a line break; skip everything else with a single cursor advance.
|
|
96
|
+
if ((character !== "\r") && (character !== "\n")) {
|
|
97
|
+
cursor++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// We found a terminator character. Determine whether it pairs with the following character to form a two-character break (`\r\n` or `\n\r`). The pair forms only
|
|
101
|
+
// when the partner is the OTHER terminator character - `\r\r` and `\n\n` are two separate breaks (two lines), not one.
|
|
102
|
+
const partner = character === "\r" ? "\n" : "\r";
|
|
103
|
+
const hasNext = (cursor + 1) < buffer.length;
|
|
104
|
+
// A lone terminator at the very end of the buffer is ambiguous: the next chunk might supply the partner that completes a two-character break. Hold the line (and
|
|
105
|
+
// this terminator) in the carry rather than yielding, so a `\n\r`/`\r\n` pair split across chunks is never seen as two breaks. We stop scanning here; the residual
|
|
106
|
+
// computed below preserves `[start, end)` including the held terminator.
|
|
107
|
+
if (!hasNext) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
// The break is unambiguous now that we can see the next character. Yield the line content up to the terminator, then advance past either one or two characters
|
|
111
|
+
// depending on whether the partner immediately follows.
|
|
112
|
+
yield buffer.slice(start, cursor);
|
|
113
|
+
const isPair = buffer.charAt(cursor + 1) === partner;
|
|
114
|
+
cursor += isPair ? 2 : 1;
|
|
115
|
+
start = cursor;
|
|
116
|
+
}
|
|
117
|
+
// The residual is everything from the last unfinished line's start to the end of the buffer, including any held lone terminator. It becomes the carry for the next
|
|
118
|
+
// call. When `start` reached the buffer end exactly (the chunk ended on a complete break), the carry is empty.
|
|
119
|
+
this.#carry = buffer.slice(start);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Flush any buffered final line that never received a terminator.
|
|
123
|
+
*
|
|
124
|
+
* 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
|
|
125
|
+
* 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
|
|
126
|
+
* empty final line, matching the convention that a terminator delimits a line that exists.
|
|
127
|
+
*
|
|
128
|
+
* @returns An iterable yielding the final unterminated line, or nothing if the carry is empty.
|
|
129
|
+
*/
|
|
130
|
+
*flush() {
|
|
131
|
+
if (this.#carry.length === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Strip a single trailing lone terminator that was being held for possible pairing; at flush time no partner will arrive, so it delimits the end of the final line.
|
|
135
|
+
const trailing = this.#carry.replace(/[\r\n]$/, "");
|
|
136
|
+
this.#carry = "";
|
|
137
|
+
yield trailing;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse a single raw log line into a {@link LogRecord}.
|
|
142
|
+
*
|
|
143
|
+
* 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
|
|
144
|
+
* 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
|
|
145
|
+
* 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
|
|
146
|
+
* 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
|
|
147
|
+
* `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
|
|
148
|
+
* 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
|
|
149
|
+
* anywhere in the (prefix-less) line.
|
|
150
|
+
*
|
|
151
|
+
* @param raw - A single raw log line (escapes intact, terminator already removed by {@link LogLineSplitter}).
|
|
152
|
+
*
|
|
153
|
+
* @returns The parsed {@link LogRecord}.
|
|
154
|
+
*
|
|
155
|
+
* @category Log Client
|
|
156
|
+
*/
|
|
157
|
+
export function parseLogLine(raw) {
|
|
158
|
+
// Strip ANSI to get the printable text, then peel the conventional `[timestamp] [Plugin Name] ` prefix. When the prefix is absent the whole stripped line is the
|
|
159
|
+
// message and both bracketed fields are null.
|
|
160
|
+
const stripped = stripAnsi(raw);
|
|
161
|
+
const match = BRACKET_PREFIX_PATTERN.exec(stripped);
|
|
162
|
+
if (match === null) {
|
|
163
|
+
// No bracketed prefix: this is a status or continuation line, not a formatted Homebridge log line, so the "uncolored = info" convention does not apply. The level is
|
|
164
|
+
// whatever severity color the line carries (read from the whole line, since there is no prefix to skip past), or null.
|
|
165
|
+
return { level: readLevel(raw, 0), message: stripped, plugin: null, raw, timestamp: null };
|
|
166
|
+
}
|
|
167
|
+
// A formatted log line. The severity SGR colors the message, so it sits after the timestamp/plugin brackets; we anchor the scan just past the plugin bracket's closing
|
|
168
|
+
// `]` in the raw line so the timestamp's (white) and plugin's (cyan) own colors are not read as severity. When the message has no severity color, the level depends
|
|
169
|
+
// on whether the line is colored at ALL: a colored line with an uncolored message is Homebridge's info convention ("info"), whereas a line with no ANSI whatsoever is
|
|
170
|
+
// color-stripped, leaving severity genuinely unknown (null). We read "is the line colored" for free from the strip already performed - stripping shortened the text if
|
|
171
|
+
// and only if the line carried ANSI - rather than scanning a second time.
|
|
172
|
+
const colored = stripped.length !== raw.length;
|
|
173
|
+
const level = readLevel(raw, messageColorStart(raw)) ?? (colored ? "info" : null);
|
|
174
|
+
// `match[1]` is the timestamp text and `match[2]` is the plugin text; each bracketed field is guaranteed present when the match succeeds. The message is the
|
|
175
|
+
// stripped text after the matched prefix.
|
|
176
|
+
return { level, message: stripped.slice(match[0].length), plugin: match[2] ?? null, raw, timestamp: match[1] ?? null };
|
|
177
|
+
}
|
|
178
|
+
// Locate, in the RAW (still-escaped) line, the index just past the second `]` - the plugin bracket's close - so the severity-color scan starts at the message portion
|
|
179
|
+
// and never sees the timestamp's or plugin's own color. The closing bracket `]` never appears inside the SGR sequences the server emits, so scanning the raw line for it
|
|
180
|
+
// is unambiguous. Returns 0 when fewer than two `]` are present (no recognizable prefix), letting the caller scan from the start.
|
|
181
|
+
function messageColorStart(raw) {
|
|
182
|
+
const firstClose = raw.indexOf("]");
|
|
183
|
+
if (firstClose === -1) {
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
const secondClose = raw.indexOf("]", firstClose + 1);
|
|
187
|
+
return secondClose === -1 ? 0 : secondClose + 1;
|
|
188
|
+
}
|
|
189
|
+
// Read the severity level from the first severity-mapping SGR color sequence at or after `from` in the raw line, or null when none is present. Scanning from `from`
|
|
190
|
+
// (past the bracket prefix) excludes the timestamp/plugin colors; the first SGR whose parameter maps to a severity wins. An SGR parameter may be a semicolon-separated
|
|
191
|
+
// list (e.g., `1;31` for bold red), so each component is consulted against the severity map. Resets and default-color codes (0, 39) never map, so they are skipped
|
|
192
|
+
// transparently and the genuine severity color is found.
|
|
193
|
+
function readLevel(raw, from) {
|
|
194
|
+
// Reset the shared global regex's cursor to the scan start. `SGR_PATTERN` carries the `g` flag so successive `.exec` calls walk every SGR sequence in the line; setting
|
|
195
|
+
// `lastIndex` bounds the walk to the message portion and keeps the module-scope regex stateless across `parseLogLine` calls.
|
|
196
|
+
SGR_PATTERN.lastIndex = from;
|
|
197
|
+
for (let match = SGR_PATTERN.exec(raw); match !== null; match = SGR_PATTERN.exec(raw)) {
|
|
198
|
+
// `match[1]` is the captured parameter string; split on semicolons so a compound SGR like `1;31m` is checked component-by-component against the severity map.
|
|
199
|
+
const parameters = (match[1] ?? "").split(";");
|
|
200
|
+
for (const parameter of parameters) {
|
|
201
|
+
const mapped = SGR_LEVEL[parameter];
|
|
202
|
+
if (mapped !== undefined) {
|
|
203
|
+
return mapped;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Parse a Homebridge log timestamp into epoch milliseconds, or `null` when the text is not a recognized timestamp.
|
|
211
|
+
*
|
|
212
|
+
* 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
|
|
213
|
+
* `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
|
|
214
|
+
* 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
|
|
215
|
+
* range check, so that rule lives in exactly one place.
|
|
216
|
+
*
|
|
217
|
+
* 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
|
|
218
|
+
* `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
|
|
219
|
+
* 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
|
|
220
|
+
* 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
|
|
221
|
+
* rejected, because only the calendar fields are round-trip-validated.
|
|
222
|
+
*
|
|
223
|
+
* @param text - The raw timestamp text from a {@link LogRecord.timestamp} (the first bracketed field), for example `"6/29/2026, 7:00:00 AM"`.
|
|
224
|
+
*
|
|
225
|
+
* @returns The instant as epoch milliseconds, or `null` when the text is not a recognized en-US timestamp or names an impossible calendar date.
|
|
226
|
+
*
|
|
227
|
+
* @category Log Client
|
|
228
|
+
*/
|
|
229
|
+
export function parseLogTimestamp(text) {
|
|
230
|
+
const match = LOG_TIMESTAMP_PATTERN.exec(text);
|
|
231
|
+
if (match === null) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
// The numeric groups are guaranteed present by a successful match; the optional meridiem group is undefined for a 24-hour rendering. We parse the calendar fields
|
|
235
|
+
// here and delegate the clock fields to `normalizeClock`, which owns the range check and the 12-hour-to-24-hour conversion.
|
|
236
|
+
const month = Number.parseInt(match[1] ?? "", 10);
|
|
237
|
+
const day = Number.parseInt(match[2] ?? "", 10);
|
|
238
|
+
const year = Number.parseInt(match[3] ?? "", 10);
|
|
239
|
+
const clock = normalizeClock({ hour: Number.parseInt(match[4] ?? "", 10), meridiem: match[7], minute: Number.parseInt(match[5] ?? "", 10),
|
|
240
|
+
second: Number.parseInt(match[6] ?? "", 10) });
|
|
241
|
+
// An out-of-range clock component (an impossible hour/minute/second) invalidates the whole timestamp.
|
|
242
|
+
if (clock === null) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
// Construct the instant in LOCAL time, then round-trip the CALENDAR fields only. The round-trip rejects a well-formed-but-impossible date (the Date constructor rolls
|
|
246
|
+
// `2/30` forward into March, so the day/month no longer match); we intentionally do NOT round-trip the hour/minute/second, so a wall-clock time in the DST spring-
|
|
247
|
+
// forward gap survives rather than being rejected. Because both the clock and the calendar are validated, the result is total and needs no `NaN` guard.
|
|
248
|
+
const date = new Date(year, month - 1, day, clock.hour, clock.minute, clock.second);
|
|
249
|
+
if ((date.getFullYear() !== year) || (date.getMonth() !== (month - 1)) || (date.getDate() !== day)) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return date.getTime();
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 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
|
|
256
|
+
* 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
|
|
257
|
+
* {@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
|
|
258
|
+
* is intentionally not part of the package barrel.
|
|
259
|
+
*
|
|
260
|
+
* 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
|
|
261
|
+
* (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
|
|
262
|
+
* 24-hour reading unchanged.
|
|
263
|
+
*
|
|
264
|
+
* @param parts - The raw clock components.
|
|
265
|
+
* @param parts.hour - The hour as written (1-12 with a meridiem, 0-23 without).
|
|
266
|
+
* @param parts.meridiem - The matched `AM`/`PM` token (any case) when the source carried one, or `undefined` for a 24-hour rendering.
|
|
267
|
+
* @param parts.minute - The minute (0-59).
|
|
268
|
+
* @param parts.second - The second (0-59).
|
|
269
|
+
*
|
|
270
|
+
* @returns The normalized 24-hour `{ hour, minute, second }`, or `null` when any component is out of range.
|
|
271
|
+
*
|
|
272
|
+
* @category Log Client
|
|
273
|
+
*/
|
|
274
|
+
export function normalizeClock(parts) {
|
|
275
|
+
const { hour, meridiem, minute, second } = parts;
|
|
276
|
+
// Minute and second are 0-59 regardless of rendering; an out-of-range component invalidates the reading.
|
|
277
|
+
if ((minute < 0) || (minute > 59) || (second < 0) || (second > 59)) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
// No meridiem means a 24-hour reading (0-23) that is already normalized; validate the range and pass it through verbatim.
|
|
281
|
+
if (meridiem === undefined) {
|
|
282
|
+
if ((hour < 0) || (hour > 23)) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
return { hour, minute, second };
|
|
286
|
+
}
|
|
287
|
+
// A meridiem is present, so the hour is a 12-hour value (1-12).
|
|
288
|
+
if ((hour < 1) || (hour > 12)) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
// Convert to 24-hour: `12am` is midnight (0) and `12pm` is noon (12); any other PM hour is `+12` and any other AM hour is unchanged.
|
|
292
|
+
const isPm = meridiem.charAt(0).toLowerCase() === "p";
|
|
293
|
+
if (hour === 12) {
|
|
294
|
+
return { hour: isPm ? 12 : 0, minute, second };
|
|
295
|
+
}
|
|
296
|
+
return { hour: isPm ? hour + 12 : hour, minute, second };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Determine whether a raw log line begins a new Homebridge log entry - that is, whether it opens with a parseable bracketed timestamp.
|
|
300
|
+
*
|
|
301
|
+
* 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
|
|
302
|
+
* `[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]`
|
|
303
|
+
* 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
|
|
304
|
+
* 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
|
|
305
|
+
* because the server wraps the timestamp in an SGR color, so the raw line begins with an escape sequence rather than the `[`.
|
|
306
|
+
*
|
|
307
|
+
* @param raw - A single raw log line (escapes intact, terminator already removed by {@link LogLineSplitter}).
|
|
308
|
+
*
|
|
309
|
+
* @returns `true` when the line opens with a parseable en-US timestamp bracket, `false` otherwise.
|
|
310
|
+
*
|
|
311
|
+
* @category Log Client
|
|
312
|
+
*/
|
|
313
|
+
export function isLogLineStart(raw) {
|
|
314
|
+
const match = LOG_LINE_START_PATTERN.exec(stripAnsi(raw));
|
|
315
|
+
return (match !== null) && (parseLogTimestamp(match[1] ?? "") !== null);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A one-way admission latch that suppresses the unusable leading lines of a live socket log seed until the first genuine log entry.
|
|
319
|
+
*
|
|
320
|
+
* 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
|
|
321
|
+
* 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
|
|
322
|
+
* 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
|
|
323
|
+
* latches OPEN and admits everything thereafter, so the continuation lines and plugin-less core lines that legitimately lack a full prefix flow through untouched.
|
|
324
|
+
*
|
|
325
|
+
* 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
|
|
326
|
+
* 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
|
|
327
|
+
* recognition predicate runs only across the short leading prefix, never the live stream.
|
|
328
|
+
*
|
|
329
|
+
* 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
|
|
330
|
+
* 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.
|
|
331
|
+
* `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.
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
*
|
|
335
|
+
* ```ts
|
|
336
|
+
* const gate = new SeedGate(SEED_GATE_MAX_SKIP);
|
|
337
|
+
*
|
|
338
|
+
* for(const line of splitter.consume(chunk)) {
|
|
339
|
+
*
|
|
340
|
+
* if(gate.admit(line)) {
|
|
341
|
+
*
|
|
342
|
+
* emit(line);
|
|
343
|
+
* }
|
|
344
|
+
* }
|
|
345
|
+
* ```
|
|
346
|
+
*
|
|
347
|
+
* @category Log Client
|
|
348
|
+
*/
|
|
349
|
+
export class SeedGate {
|
|
350
|
+
// Whether the latch has opened. Once true, every line is admitted with a single boolean read and the recognition predicate is never consulted again.
|
|
351
|
+
#open = false;
|
|
352
|
+
// The number of leading non-entry lines dropped so far while the latch is still closed. Bounded by `#maxSkip`, at which point the gate gives up and opens.
|
|
353
|
+
#skipped = 0;
|
|
354
|
+
// The safety-valve bound: the most leading non-entry lines to drop before concluding the stream carries no recognizable entry start and opening the latch regardless.
|
|
355
|
+
#maxSkip;
|
|
356
|
+
/**
|
|
357
|
+
* @param maxSkip - The safety-valve bound: the most leading non-entry lines to drop before opening the latch unconditionally. The consumer injects it (production
|
|
358
|
+
* passes `SEED_GATE_MAX_SKIP`) so a test can drive the bounded-open path with a small value.
|
|
359
|
+
*/
|
|
360
|
+
constructor(maxSkip) {
|
|
361
|
+
this.#maxSkip = maxSkip;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 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
|
|
365
|
+
* 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
|
|
366
|
+
* dropped.
|
|
367
|
+
*
|
|
368
|
+
* @param line - A single raw log line from the seed stream.
|
|
369
|
+
*
|
|
370
|
+
* @returns `true` if the line should be emitted, `false` if it is leading seed noise to drop.
|
|
371
|
+
*/
|
|
372
|
+
admit(line) {
|
|
373
|
+
// Once the first real entry has opened the latch, every subsequent line is admitted with a single boolean read - no re-parsing across the live stream.
|
|
374
|
+
if (this.#open) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
// The first line that begins a genuine entry opens the latch and is itself admitted.
|
|
378
|
+
if (isLogLineStart(line)) {
|
|
379
|
+
this.#open = true;
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
// Still leading seed noise (the byte-seek fragment, the server preamble, or an orphaned continuation of the truncated entry): drop it - unless we have already
|
|
383
|
+
// dropped `#maxSkip` lines without seeing an entry start, which means the stream carries no recognized timestamp (an unrecognized locale/format). In that case
|
|
384
|
+
// stop gating and admit the rest so the stream is never suppressed indefinitely.
|
|
385
|
+
if (this.#skipped >= this.#maxSkip) {
|
|
386
|
+
this.#open = true;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
this.#skipped++;
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/logclient/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { SocketTarget } from "./endpoints.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Options accepted by {@link downloadLog}: the connection target plus the raw token, an injectable `fetch` seam, and an optional abort signal.
|
|
4
|
+
*
|
|
5
|
+
* @property fetch - The fetch implementation to use. Defaults to the global `fetch`. Injected so the download flow is testable without a live server.
|
|
6
|
+
* @property host - The hostname or IP of the homebridge-config-ui-x server.
|
|
7
|
+
* @property port - The TCP port the server listens on.
|
|
8
|
+
* @property signal - Optional abort signal forwarded to the underlying `fetch`. When it aborts, the in-flight request and the body reader are both cancelled so the
|
|
9
|
+
* connection is released immediately rather than draining to completion in the background. A pre-aborted signal short-circuits the download before any
|
|
10
|
+
* request is made (the platform `fetch` rejects synchronously).
|
|
11
|
+
* @property tls - When `true`, use the secure (`https`) scheme; when `false` or omitted, plaintext (`http`).
|
|
12
|
+
* @property token - The raw bearer token, sent as `Authorization: Bearer <token>`.
|
|
13
|
+
*
|
|
14
|
+
* @category Log Client
|
|
15
|
+
*/
|
|
16
|
+
export interface DownloadLogOptions extends SocketTarget {
|
|
17
|
+
readonly fetch?: typeof fetch;
|
|
18
|
+
readonly signal?: AbortSignal;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Download the entire Homebridge log file over REST and yield it as raw lines.
|
|
22
|
+
*
|
|
23
|
+
* Streams `GET /api/platform-tools/hb-service/log/download?colour=yes` through the shared {@link LogLineSplitter}, yielding each raw line (ANSI intact) as it becomes
|
|
24
|
+
* available, and flushing the splitter at end-of-response so the file's final line is never stranded. The whole-file download is the deep-history channel: the server
|
|
25
|
+
* exposes no tail/range parameter, so a caller that only wants the most recent N lines drains this iterable and retains the tail (e.g., via `takeLast`).
|
|
26
|
+
*
|
|
27
|
+
* When `options.signal` is supplied, the download is abortable: aborting the signal cancels the in-flight `fetch` and the body reader so the connection is released
|
|
28
|
+
* immediately rather than draining in the background, and a pre-aborted signal short-circuits before any request is made. This is what lets the windowed hedge supersede
|
|
29
|
+
* a speculative deep-history download the moment the socket seed is shown to cover the window.
|
|
30
|
+
*
|
|
31
|
+
* @param options - The connection target, the raw token, the injectable `fetch` seam, and the optional abort signal. See {@link DownloadLogOptions}.
|
|
32
|
+
*
|
|
33
|
+
* @returns An async iterable of raw log lines (escapes intact, terminators removed), in file order.
|
|
34
|
+
*
|
|
35
|
+
* @throws `Error` when the server returns a 400 (the log method is not file-backed, so there is no file to download - the message advises `--follow`), or any other
|
|
36
|
+
* non-2xx status, or when the response carries no body. Propagates `options.signal`'s reason (typically an `HbpuAbortError`) when the signal aborts.
|
|
37
|
+
*
|
|
38
|
+
* @category Log Client
|
|
39
|
+
*/
|
|
40
|
+
export declare function downloadLog(options: DownloadLogOptions): AsyncIterable<string>;
|
|
41
|
+
//# sourceMappingURL=rest.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* Copyright(C) 2017-2026, HJD (https://github.com/hjdhjd). All rights reserved.
|
|
2
|
+
*
|
|
3
|
+
* logclient/rest.ts: Streamed whole-file log download from the homebridge-config-ui-x REST API.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Streamed REST log retrieval for the Homebridge UI log client.
|
|
7
|
+
*
|
|
8
|
+
* {@link downloadLog} fetches the entire log file from `GET /api/platform-tools/hb-service/log/download?colour=yes` and yields it line by line as an
|
|
9
|
+
* `AsyncIterable<string>` of raw lines (ANSI escapes intact). The server has no range/tail parameter - it always streams the whole file - so this is the deep-history
|
|
10
|
+
* channel paid only when the user explicitly asks for history beyond the socket's ~500-line seed (see the cost model on `TailRequest` in `types.ts`).
|
|
11
|
+
*
|
|
12
|
+
* Two details are load-bearing:
|
|
13
|
+
*
|
|
14
|
+
* - The response body is streamed, not buffered. We feed each chunk through the shared {@link LogLineSplitter} so a multi-MB log never has to be materialized in memory
|
|
15
|
+
* as one string, and the consumer can begin processing lines as they arrive. The splitter handles lines split across chunk boundaries and the mixed newline
|
|
16
|
+
* conventions transparently.
|
|
17
|
+
* - We MUST call `splitter.flush()` at end-of-response. The splitter withholds a chunk-final lone line-feed pending a possible split `\n\r`/`\r\n` pair in the next
|
|
18
|
+
* chunk; at end-of-response no next chunk arrives, so without a flush the final line of the file would be stranded in the carry and silently lost.
|
|
19
|
+
*
|
|
20
|
+
* The transport asks for `colour=yes` (the raw file with ANSI intact) rather than `colour=no`, because the server's color stripping shifts byte offsets and, more
|
|
21
|
+
* importantly, the ANSI color IS the severity-level signal the parser reads. When the log method is `systemd`/`custom` the endpoint returns 400; we map that to a clear
|
|
22
|
+
* "no log file on disk; use --follow" error rather than surfacing a bare HTTP status.
|
|
23
|
+
*
|
|
24
|
+
* The `fetch` implementation is injected (defaulting to the global `fetch`) so the whole flow is exercised in tests without a live server.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
import { LogLineSplitter } from "./parser.js";
|
|
29
|
+
import { httpBaseUrl } from "./endpoints.js";
|
|
30
|
+
// Decode a streamed byte chunk to text. We hold a single `TextDecoder` across the whole response with `{ stream: true }` so a multi-byte UTF-8 sequence split across two
|
|
31
|
+
// chunks is reassembled correctly rather than producing a replacement character at the boundary. The final `decode()` with no argument flushes any trailing partial
|
|
32
|
+
// sequence.
|
|
33
|
+
async function* readLines(body, signal) {
|
|
34
|
+
const splitter = new LogLineSplitter();
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
const reader = body.getReader();
|
|
37
|
+
try {
|
|
38
|
+
for (;;) {
|
|
39
|
+
// eslint-disable-next-line no-await-in-loop
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (done) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
// Decode this chunk (streaming, so a split multi-byte sequence carries into the next read) and feed it to the splitter, yielding every complete raw line the chunk
|
|
45
|
+
// contained or completed.
|
|
46
|
+
for (const line of splitter.consume(decoder.decode(value, { stream: true }))) {
|
|
47
|
+
yield line;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Flush the decoder's trailing partial sequence (if any) into the splitter, then flush the splitter's held final line. The splitter withholds a chunk-final lone
|
|
51
|
+
// line-feed pending a possible cross-chunk pair; at end-of-response no further chunk arrives, so this flush is what surfaces the file's last line rather than
|
|
52
|
+
// stranding it in the carry.
|
|
53
|
+
const tail = decoder.decode();
|
|
54
|
+
if (tail.length > 0) {
|
|
55
|
+
for (const line of splitter.consume(tail)) {
|
|
56
|
+
yield line;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const line of splitter.flush()) {
|
|
60
|
+
yield line;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
// When the download was aborted (the hedge superseded it, or the caller tore the stream down), actively cancel the body first so undici aborts the request and the
|
|
65
|
+
// connection is released immediately rather than draining to completion in the background as a pinned connection; the cancel's rejection on an already-aborted body
|
|
66
|
+
// is expected, so it is swallowed. In every case release the reader lock - normal completion, an early `break` by the consumer, or a thrown error - so the underlying
|
|
67
|
+
// stream is never left locked.
|
|
68
|
+
if (signal?.aborted) {
|
|
69
|
+
void reader.cancel().catch(() => { });
|
|
70
|
+
}
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Download the entire Homebridge log file over REST and yield it as raw lines.
|
|
76
|
+
*
|
|
77
|
+
* Streams `GET /api/platform-tools/hb-service/log/download?colour=yes` through the shared {@link LogLineSplitter}, yielding each raw line (ANSI intact) as it becomes
|
|
78
|
+
* available, and flushing the splitter at end-of-response so the file's final line is never stranded. The whole-file download is the deep-history channel: the server
|
|
79
|
+
* exposes no tail/range parameter, so a caller that only wants the most recent N lines drains this iterable and retains the tail (e.g., via `takeLast`).
|
|
80
|
+
*
|
|
81
|
+
* When `options.signal` is supplied, the download is abortable: aborting the signal cancels the in-flight `fetch` and the body reader so the connection is released
|
|
82
|
+
* immediately rather than draining in the background, and a pre-aborted signal short-circuits before any request is made. This is what lets the windowed hedge supersede
|
|
83
|
+
* a speculative deep-history download the moment the socket seed is shown to cover the window.
|
|
84
|
+
*
|
|
85
|
+
* @param options - The connection target, the raw token, the injectable `fetch` seam, and the optional abort signal. See {@link DownloadLogOptions}.
|
|
86
|
+
*
|
|
87
|
+
* @returns An async iterable of raw log lines (escapes intact, terminators removed), in file order.
|
|
88
|
+
*
|
|
89
|
+
* @throws `Error` when the server returns a 400 (the log method is not file-backed, so there is no file to download - the message advises `--follow`), or any other
|
|
90
|
+
* non-2xx status, or when the response carries no body. Propagates `options.signal`'s reason (typically an `HbpuAbortError`) when the signal aborts.
|
|
91
|
+
*
|
|
92
|
+
* @category Log Client
|
|
93
|
+
*/
|
|
94
|
+
export async function* downloadLog(options) {
|
|
95
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
96
|
+
const url = httpBaseUrl(options) + "/api/platform-tools/hb-service/log/download?colour=yes";
|
|
97
|
+
const response = await fetchImpl(url, { headers: { "Authorization": "Bearer " + options.token }, method: "GET", signal: options.signal });
|
|
98
|
+
// A 400 from this endpoint means the configured log method is not file-backed (systemd/custom), so there is no file on disk to download. Map it to an actionable
|
|
99
|
+
// message that points the user at the live-tail channel rather than surfacing a bare status code.
|
|
100
|
+
if (response.status === 400) {
|
|
101
|
+
throw new Error("No log file is available to download on disk (the Homebridge log method is not file-backed); use --follow to live-tail instead.");
|
|
102
|
+
}
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new Error("Log download failed - the server returned HTTP " + response.status.toString() + " (" + response.statusText + ").");
|
|
105
|
+
}
|
|
106
|
+
if (response.body === null) {
|
|
107
|
+
throw new Error("Log download failed - the server returned an empty response body.");
|
|
108
|
+
}
|
|
109
|
+
yield* readLines(response.body, options.signal);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=rest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.js","sourceRoot":"","sources":["../../src/logclient/rest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const DEFAULT_HOST = "localhost";
|
|
2
|
+
export declare const DEFAULT_PORT = 8581;
|
|
3
|
+
export declare const LOG_NAMESPACE = "log";
|
|
4
|
+
export declare const SOCKET_PATH = "/socket.io/";
|
|
5
|
+
export declare const PTY_COLUMNS = 80;
|
|
6
|
+
export declare const PTY_ROWS = 24;
|
|
7
|
+
export declare const RECONNECT_BASE_MS = 500;
|
|
8
|
+
export declare const RECONNECT_CAP_MS = 5000;
|
|
9
|
+
export declare const JITTER_FRACTION = 0.5;
|
|
10
|
+
export declare const MARGIN_MS = 5000;
|
|
11
|
+
export declare const SEED_QUIESCENCE_MS = 250;
|
|
12
|
+
export declare const SEED_SETTLE_MS = 1000;
|
|
13
|
+
export declare const SEED_WINDOW_MAX_MS = 5000;
|
|
14
|
+
export declare const SEED_GATE_MAX_SKIP = 100;
|
|
15
|
+
//# sourceMappingURL=settings.d.ts.map
|