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
package/dist/ui/webUi.mjs
CHANGED
|
@@ -4,181 +4,286 @@
|
|
|
4
4
|
*/
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { PluginConfigSession } from "./pluginConfigSession.mjs";
|
|
8
|
+
import { webUiFeatureOptions } from "./webUi-featureOptions.mjs";
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} FirstRunContext
|
|
12
|
+
* @property {(patch: Object) => Promise<void>} [commit] - Persist a patch to the primary platform-config entry. Supplied only to `onSubmit` (the one write hook).
|
|
13
|
+
* @property {Object} config - The primary platform-config entry, injected so the hook is a pure function of its input rather than reaching for the config itself.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} FirstRunHandlers
|
|
18
|
+
* @property {(context: FirstRunContext) => boolean | Promise<boolean>} [isRequired] - Returns truthy when the first-run flow must run before the main UI is shown.
|
|
19
|
+
* @property {(context: FirstRunContext) => boolean | Promise<boolean>} [onStart] - Initialization for the first-run UI; populates forms and runs any startup tasks.
|
|
20
|
+
* @property {(context: FirstRunContext) => boolean | Promise<boolean>} [onSubmit] - Executes the first-run workflow, typically a login or configuration validation.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} WebUiConfig
|
|
25
|
+
* @property {Object} [featureOptions] - Parameters forwarded to {@link webUiFeatureOptions}.
|
|
26
|
+
* @property {FirstRunHandlers} [firstRun] - First-run lifecycle hooks.
|
|
27
|
+
* @property {string} [name] - Plugin name used to seed a fresh configuration.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* webUi - Top-level plugin webUI orchestrator.
|
|
32
|
+
*
|
|
33
|
+
* Owns the page-level menu state, the first-run flow, and the {@link webUiFeatureOptions} instance that renders the feature options page. The orchestrator is the
|
|
34
|
+
* single entry point Homebridge invokes to render the configuration UI; everything else - feature option discovery, theming, sidebar navigation, search - lives in
|
|
35
|
+
* the composed {@link webUiFeatureOptions} instance and its sub-components.
|
|
36
|
+
*/
|
|
9
37
|
export class webUi {
|
|
10
38
|
|
|
11
|
-
// Feature options class instance.
|
|
12
39
|
featureOptions;
|
|
13
40
|
|
|
14
|
-
// First run webUI callback endpoints for customization.
|
|
15
41
|
#firstRun;
|
|
16
|
-
|
|
17
|
-
// Plugin name.
|
|
18
42
|
#name;
|
|
43
|
+
#session;
|
|
19
44
|
|
|
20
45
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
46
|
+
* Initialize the plugin webUI orchestrator.
|
|
47
|
+
*
|
|
48
|
+
* Constructs the composed {@link webUiFeatureOptions} instance immediately so the feature-options page is ready to render the moment the user navigates to it.
|
|
49
|
+
* Caller-supplied first-run hooks are merged in a single spread over the default no-op handlers, so partial overrides work naturally - a caller can supply only
|
|
50
|
+
* `onSubmit` and the unspecified slots stay at the defaults that keep the flow driveable.
|
|
51
|
+
*
|
|
52
|
+
* @param {WebUiConfig} [options] - Configuration options for the webUI. All fields are optional; firstRun's hooks fall back to no-op handlers, and
|
|
53
|
+
* featureOptions/name simply default to undefined.
|
|
27
54
|
*/
|
|
28
55
|
constructor({ featureOptions, firstRun = {}, name } = {}) {
|
|
29
56
|
|
|
30
|
-
//
|
|
31
|
-
|
|
57
|
+
// First-run handlers default to no-ops; caller-supplied entries override per-key. The single-statement spread lands `#firstRun` in its final shape on first
|
|
58
|
+
// assignment, so there is no intermediate object that gets discarded a line later.
|
|
59
|
+
this.#firstRun = { isRequired: () => false, onStart: () => true, onSubmit: () => true, ...firstRun };
|
|
32
60
|
|
|
33
|
-
// Figure out the options passed in to us.
|
|
34
61
|
this.featureOptions = new webUiFeatureOptions(featureOptions);
|
|
35
|
-
this.#firstRun = Object.assign({}, this.#firstRun, firstRun);
|
|
36
62
|
this.#name = name;
|
|
37
63
|
}
|
|
38
64
|
|
|
39
65
|
/**
|
|
40
66
|
* Render the webUI.
|
|
67
|
+
*
|
|
68
|
+
* Public entry point Homebridge invokes when the configuration UI is opened. Delegates the actual rendering to {@link #launchWebUI}; this wrapper exists to
|
|
69
|
+
* standardize error handling (a launch failure becomes a user-facing toast rather than a silent broken UI) and to guarantee the spinner is hidden no matter how
|
|
70
|
+
* the launch settles. The `finally` runs after the awaited launch resolves or rejects, so the spinner stays visible for the full duration of the async setup
|
|
71
|
+
* rather than disappearing the moment the synchronous portion of the call returns.
|
|
72
|
+
*
|
|
73
|
+
* @returns {Promise<void>}
|
|
74
|
+
* @public
|
|
41
75
|
*/
|
|
42
|
-
|
|
43
|
-
show() {
|
|
76
|
+
async show() {
|
|
44
77
|
|
|
45
|
-
// Fire off our UI, catching errors along the way.
|
|
46
78
|
try {
|
|
47
79
|
|
|
48
|
-
this.#launchWebUI();
|
|
80
|
+
await this.#launchWebUI();
|
|
49
81
|
} catch(err) {
|
|
50
82
|
|
|
51
|
-
//
|
|
52
|
-
|
|
83
|
+
// Outermost user-facing diagnostic seam in the webUI. Caller-supplied first-run handlers and other extension points can throw any shape (strings, plain
|
|
84
|
+
// objects, primitives), so normalize before reaching the toast: `err?.message` extracts the message field when present, the nullish coalesce falls back
|
|
85
|
+
// to a string coercion of the whole value otherwise. This keeps the toast text useful regardless of what bubbled out of `#launchWebUI`.
|
|
86
|
+
homebridge.toast.error(err?.message ?? String(err), "Error");
|
|
53
87
|
} finally {
|
|
54
88
|
|
|
55
|
-
// Always leave the UI in a usable place for the end user.
|
|
56
89
|
homebridge.hideSpinner();
|
|
57
90
|
}
|
|
58
91
|
}
|
|
59
92
|
|
|
60
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Show the first-run user experience.
|
|
95
|
+
*
|
|
96
|
+
* Wires the submit button to run the caller-supplied submit handler, swap the page from first-run to feature-options, and hand off to the feature-options view.
|
|
97
|
+
* The save button stays disabled until the user completes the first-run flow so a partially-configured plugin cannot be written back to disk.
|
|
98
|
+
*
|
|
99
|
+
* @returns {Promise<void>}
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
61
102
|
async #showFirstRun() {
|
|
62
103
|
|
|
63
104
|
const buttonFirstRun = document.getElementById("firstRun");
|
|
64
105
|
|
|
65
|
-
//
|
|
66
|
-
|
|
106
|
+
// Inject the primary platform-config entry so the hook reads its config from its argument rather than reaching for the session or the host. onStart only reads
|
|
107
|
+
// (it pre-populates the form), so it receives config without the writer.
|
|
108
|
+
if(!(await this.#processHandler(this.#firstRun.onStart, { config: this.#session.platform }))) {
|
|
67
109
|
|
|
68
110
|
return;
|
|
69
111
|
}
|
|
70
112
|
|
|
71
|
-
// We disable saving any settings until we configure the plugin.
|
|
72
113
|
homebridge.disableSaveButton();
|
|
73
114
|
|
|
74
|
-
// First run user experience.
|
|
75
115
|
buttonFirstRun.addEventListener("click", async () => {
|
|
76
116
|
|
|
77
|
-
// Show the beachball while we setup.
|
|
78
117
|
homebridge.showSpinner();
|
|
79
118
|
|
|
80
|
-
|
|
81
|
-
if(!(await this.#processHandler(this.#firstRun.onSubmit))) {
|
|
119
|
+
try {
|
|
82
120
|
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
// onSubmit is the one write hook: it validates credentials and persists them. It receives both the current config and a `commit` bound to the session's
|
|
122
|
+
// single write seam, so the hook owns the shape of the write (it knows credentials live under the controllers array) while the session owns persistence.
|
|
123
|
+
if(!(await this.#processHandler(this.#firstRun.onSubmit, { commit: (patch) => this.#session.commit(patch), config: this.#session.platform }))) {
|
|
85
124
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Swap from the first-run page to the main configuration UI and hand off to the feature-options view. The feature-options surface manages its own
|
|
129
|
+
// progressive disclosure - page-shell visible immediately, regions populating as their I/O resolves - so the click handler's spinner is the only one that
|
|
130
|
+
// brackets this transition. The `try/finally` ensures it comes down on every exit path, including the early bail above.
|
|
131
|
+
document.getElementById("pageFirstRun").style.display = "none";
|
|
132
|
+
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
89
133
|
|
|
90
|
-
|
|
134
|
+
await this.featureOptions.show(this.#session);
|
|
91
135
|
|
|
92
|
-
|
|
136
|
+
homebridge.enableSaveButton();
|
|
137
|
+
} finally {
|
|
93
138
|
|
|
94
|
-
|
|
95
|
-
|
|
139
|
+
homebridge.hideSpinner();
|
|
140
|
+
}
|
|
96
141
|
});
|
|
97
142
|
|
|
98
143
|
document.getElementById("pageFirstRun").style.display = "block";
|
|
99
144
|
}
|
|
100
145
|
|
|
101
|
-
|
|
102
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Show the main plugin configuration tab.
|
|
148
|
+
*
|
|
149
|
+
* Hides the feature-options view, swaps the menu button states (home and feature-options become primary; settings becomes elegant to indicate the active tab),
|
|
150
|
+
* and asks Homebridge to render its built-in schema-driven settings form. The spinner brackets the swap so transient layout shifts are not visible to the user.
|
|
151
|
+
*
|
|
152
|
+
* Awaits `featureOptions.hide()` BEFORE revealing the schema form so any debounced-but-unwritten option edit is flushed into Homebridge's in-memory config model
|
|
153
|
+
* first - the Settings form then renders against the flushed config rather than a stale snapshot. The try/finally guarantees the spinner comes down and the tab
|
|
154
|
+
* reveals even if the drain rejects (the drain's own failure path already toasts via `persist:failed`), so a persistence error never strands the user on a spinner.
|
|
155
|
+
*
|
|
156
|
+
* @returns {Promise<void>}
|
|
157
|
+
* @private
|
|
158
|
+
*/
|
|
159
|
+
async #showSettings() {
|
|
103
160
|
|
|
104
|
-
// Show the beachball while we setup.
|
|
105
161
|
homebridge.showSpinner();
|
|
106
|
-
this.featureOptions.hide();
|
|
107
162
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
163
|
+
try {
|
|
164
|
+
|
|
165
|
+
await this.featureOptions.hide();
|
|
166
|
+
} finally {
|
|
167
|
+
|
|
168
|
+
this.#toggleClasses("menuHome", "btn-elegant", "btn-primary");
|
|
169
|
+
this.#toggleClasses("menuFeatureOptions", "btn-elegant", "btn-primary");
|
|
170
|
+
this.#toggleClasses("menuSettings", "btn-primary", "btn-elegant");
|
|
112
171
|
|
|
113
|
-
|
|
114
|
-
|
|
172
|
+
document.getElementById("pageSupport").style.display = "none";
|
|
173
|
+
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
115
174
|
|
|
116
|
-
|
|
175
|
+
homebridge.showSchemaForm();
|
|
117
176
|
|
|
118
|
-
|
|
119
|
-
|
|
177
|
+
homebridge.hideSpinner();
|
|
178
|
+
}
|
|
120
179
|
}
|
|
121
180
|
|
|
122
|
-
|
|
123
|
-
|
|
181
|
+
/**
|
|
182
|
+
* Show the support tab.
|
|
183
|
+
*
|
|
184
|
+
* Hides the feature-options view and the schema form, swaps the menu button states (home becomes elegant as the active tab; feature-options and settings revert
|
|
185
|
+
* to primary), and reveals the static support page. Spinner brackets the swap to mask transient layout shifts.
|
|
186
|
+
*
|
|
187
|
+
* Awaits `featureOptions.hide()` BEFORE revealing the support page so any debounced-but-unwritten option edit is flushed first, matching the Settings path. The
|
|
188
|
+
* try/finally guarantees the spinner comes down and the tab reveals even if the drain rejects (the drain's own failure path already toasts via `persist:failed`).
|
|
189
|
+
*
|
|
190
|
+
* @returns {Promise<void>}
|
|
191
|
+
* @private
|
|
192
|
+
*/
|
|
193
|
+
async #showSupport() {
|
|
124
194
|
|
|
125
|
-
// Show the beachball while we setup.
|
|
126
195
|
homebridge.showSpinner();
|
|
127
196
|
homebridge.hideSchemaForm();
|
|
128
|
-
this.featureOptions.hide();
|
|
129
197
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
198
|
+
try {
|
|
199
|
+
|
|
200
|
+
await this.featureOptions.hide();
|
|
201
|
+
} finally {
|
|
202
|
+
|
|
203
|
+
this.#toggleClasses("menuHome", "btn-primary", "btn-elegant");
|
|
204
|
+
this.#toggleClasses("menuFeatureOptions", "btn-elegant", "btn-primary");
|
|
205
|
+
this.#toggleClasses("menuSettings", "btn-elegant", "btn-primary");
|
|
134
206
|
|
|
135
|
-
|
|
136
|
-
|
|
207
|
+
document.getElementById("pageSupport").style.display = "block";
|
|
208
|
+
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
137
209
|
|
|
138
|
-
|
|
139
|
-
|
|
210
|
+
homebridge.hideSpinner();
|
|
211
|
+
}
|
|
140
212
|
}
|
|
141
213
|
|
|
142
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Launch the webUI.
|
|
216
|
+
*
|
|
217
|
+
* Opens the configuration session, wires the menu event listeners, and routes the user to either the feature-options view (when the caller's first-run gate says
|
|
218
|
+
* no) or the first-run flow (when it says yes). The session loads the host config once and seeds the minimum shape, so routing and every downstream reader share
|
|
219
|
+
* one config owner rather than fetching it independently.
|
|
220
|
+
*
|
|
221
|
+
* @returns {Promise<void>}
|
|
222
|
+
* @private
|
|
223
|
+
*/
|
|
143
224
|
async #launchWebUI() {
|
|
144
225
|
|
|
145
|
-
//
|
|
146
|
-
this
|
|
226
|
+
// Open the configuration session: one host read, seeded to the minimum shape. Routing, the first-run flow, and the feature-options page all read their config
|
|
227
|
+
// from this single owner rather than re-fetching it independently - so the routing decision lands before any UI work begins and against the same data every
|
|
228
|
+
// later reader sees.
|
|
229
|
+
this.#session = await PluginConfigSession.open({ host: homebridge, name: this.#name });
|
|
147
230
|
|
|
148
|
-
//
|
|
231
|
+
// Menu click listeners use a uniform shape: an arrow expression that calls the handler and returns its result. addEventListener discards the return value, so an
|
|
232
|
+
// async handler's promise is dropped either way; wrapping `featureOptions.show()` in `async () => await ...` would add a microtask hop and break the visual
|
|
233
|
+
// symmetry across the three sibling registrations for no behavioral benefit. All three handlers are async (show() syncs; #showSettings / #showSupport await
|
|
234
|
+
// the navigate-away flush before revealing the next tab), and the dropped promise is a deliberate fire-and-forget: each handler's own try/finally reveals the tab
|
|
235
|
+
// and drains the spinner on every path, and the flush drain's failure surfaces internally via persist:failed's toast - so there is no unobserved rejection here.
|
|
149
236
|
document.getElementById("menuHome").addEventListener("click", () => this.#showSupport());
|
|
150
|
-
document.getElementById("menuFeatureOptions").addEventListener("click",
|
|
237
|
+
document.getElementById("menuFeatureOptions").addEventListener("click", () => this.featureOptions.show(this.#session));
|
|
151
238
|
document.getElementById("menuSettings").addEventListener("click", () => this.#showSettings());
|
|
152
239
|
|
|
153
|
-
//
|
|
154
|
-
|
|
240
|
+
// The caller's first-run gate decides routing against the injected platform config. No separate "is there any config?" test is needed: a plugin with a first-run
|
|
241
|
+
// flow returns true on a fresh config (no valid credentials yet), and a plugin without one keeps the default `() => false` gate and lands straight on feature
|
|
242
|
+
// options - the right destination for a device-discovery plugin even on a brand-new install. The session has already seeded the minimum shape, so the first-run
|
|
243
|
+
// flow can persist credentials on submit without a separate eager write here.
|
|
244
|
+
if(!(await this.#processHandler(this.#firstRun.isRequired, { config: this.#session.platform }))) {
|
|
155
245
|
|
|
156
246
|
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
157
|
-
await this.featureOptions.show();
|
|
247
|
+
await this.featureOptions.show(this.#session);
|
|
158
248
|
|
|
159
249
|
return;
|
|
160
250
|
}
|
|
161
251
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
await homebridge.updatePluginConfig(this.featureOptions.currentConfig);
|
|
167
|
-
this.#showFirstRun();
|
|
252
|
+
// Await first-run setup so the spinner-bracketed window in `show()` only closes after the first-run page is fully wired up - the onStart handler has resolved,
|
|
253
|
+
// the save button is disabled, the click listener is registered, and the page is visible. Returning before this would let `show()`'s `finally` hide the spinner
|
|
254
|
+
// while initialization is still in flight, leaving the user looking at a half-rendered first-run UI.
|
|
255
|
+
await this.#showFirstRun();
|
|
168
256
|
}
|
|
169
257
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Resolve a caller-supplied handler whose shape may be a function or a plain truthy/falsy value.
|
|
260
|
+
*
|
|
261
|
+
* The first-run hooks accept either a function (synchronous or asynchronous - both forms are awaited via the `await handler()` call below) or a literal truthy
|
|
262
|
+
* value (e.g., a caller that always wants the flow to continue can pass `true`). This helper unifies both shapes into a single `Promise<boolean>` answer so the
|
|
263
|
+
* call sites stay flat. The context object is forwarded to the function form so each hook is a pure function of its injected config (and, for `onSubmit`, the
|
|
264
|
+
* write seam) rather than reaching for the session or the host itself.
|
|
265
|
+
*
|
|
266
|
+
* @param {Function|*} handler - Caller-supplied handler. When a function, it is awaited; otherwise it is treated as a truthy/falsy continuation flag.
|
|
267
|
+
* @param {FirstRunContext} [context] - The injected context forwarded to the function form of the handler.
|
|
268
|
+
* @returns {Promise<boolean>} `true` when the workflow should continue, `false` when it should be aborted.
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
async #processHandler(handler, context) {
|
|
177
272
|
|
|
178
|
-
return
|
|
273
|
+
return Boolean((typeof handler === "function") ? await handler(context) : handler);
|
|
179
274
|
}
|
|
180
275
|
|
|
181
|
-
|
|
276
|
+
/**
|
|
277
|
+
* Swap one Bootstrap button class for another on a DOM element.
|
|
278
|
+
*
|
|
279
|
+
* The menu uses the Bootstrap (Material Design for Bootstrap) `btn-primary` / `btn-elegant` pair to encode active vs inactive tabs. Tab-switch handlers call this
|
|
280
|
+
* helper once per menu button, so the exact class swap each tab needs lives at the call site rather than embedded in this helper.
|
|
281
|
+
*
|
|
282
|
+
* @param {string} id - The element ID to update.
|
|
283
|
+
* @param {string} removeClass - The class to remove.
|
|
284
|
+
* @param {string} addClass - The class to add.
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
182
287
|
#toggleClasses(id, removeClass, addClass) {
|
|
183
288
|
|
|
184
289
|
const element = document.getElementById(id);
|