homebridge-plugin-utils 1.0.0 → 1.2.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/build/eslint-rules.mjs +172 -20
- package/dist/featureoptions.d.ts +24 -11
- package/dist/featureoptions.js +32 -22
- package/dist/featureoptions.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mqttclient.js +2 -8
- package/dist/mqttclient.js.map +1 -1
- package/dist/rtp.d.ts +25 -0
- package/dist/rtp.js +178 -0
- package/dist/rtp.js.map +1 -0
- package/dist/ui/featureoptions.js +32 -22
- package/dist/ui/featureoptions.js.map +1 -1
- package/dist/ui/{webui-featureoptions.mjs → webUi-featureoptions.mjs} +353 -117
- package/dist/ui/webUi.mjs +85 -56
- package/dist/utils.d.ts +22 -0
- package/dist/utils.js.map +1 -1
- package/package.json +4 -4
package/dist/ui/webUi.mjs
CHANGED
|
@@ -4,22 +4,43 @@
|
|
|
4
4
|
*/
|
|
5
5
|
"use strict";
|
|
6
6
|
|
|
7
|
+
import { webUiFeatureOptions } from "./webUi-featureoptions.mjs";
|
|
8
|
+
|
|
7
9
|
export class webUi {
|
|
8
10
|
|
|
9
11
|
// Feature options class instance.
|
|
10
|
-
|
|
12
|
+
featureOptions;
|
|
11
13
|
|
|
12
|
-
//
|
|
13
|
-
#
|
|
14
|
+
// First run webUI callback endpoints for customization.
|
|
15
|
+
#firstRun;
|
|
14
16
|
|
|
15
17
|
// Plugin name.
|
|
16
18
|
#name;
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* featureOptions - parameters to webUiFeatureOptions.
|
|
22
|
+
* firstRun - first run handlers:
|
|
23
|
+
* isRequired - do we need to run the first run UI workflow?
|
|
24
|
+
* onStart - initialization for the first run webUI to populate forms and other startup tasks.
|
|
25
|
+
* onSubmit - execute the first run workflow, typically a login or configuration validation of some sort.
|
|
26
|
+
* name - plugin name.
|
|
27
|
+
*/
|
|
28
|
+
constructor({ featureOptions, firstRun = {}, name } = {}) {
|
|
29
|
+
|
|
30
|
+
// Defaults for our first run handlers.
|
|
31
|
+
this.firstRun = { isRequired: () => false, onStart: () => true, onSubmit: () => true };
|
|
32
|
+
|
|
33
|
+
// Figure out the options passed in to us.
|
|
34
|
+
this.featureOptions = new webUiFeatureOptions(featureOptions);
|
|
35
|
+
this.firstRun = Object.assign({}, this.firstRun, firstRun);
|
|
22
36
|
this.name = name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Render the webUI.
|
|
41
|
+
*/
|
|
42
|
+
// Render the UI.
|
|
43
|
+
show() {
|
|
23
44
|
|
|
24
45
|
// Fire off our UI, catching errors along the way.
|
|
25
46
|
try {
|
|
@@ -28,11 +49,11 @@ export class webUi {
|
|
|
28
49
|
} catch(err) {
|
|
29
50
|
|
|
30
51
|
// If we had an error instantiating or updating the UI, notify the user.
|
|
31
|
-
|
|
52
|
+
homebridge.toast.error(err.message, "Error");
|
|
32
53
|
} finally {
|
|
33
54
|
|
|
34
55
|
// Always leave the UI in a usable place for the end user.
|
|
35
|
-
|
|
56
|
+
homebridge.hideSpinner();
|
|
36
57
|
}
|
|
37
58
|
}
|
|
38
59
|
|
|
@@ -41,31 +62,31 @@ export class webUi {
|
|
|
41
62
|
|
|
42
63
|
const buttonFirstRun = document.getElementById("firstRun");
|
|
43
64
|
|
|
65
|
+
// Run a custom initialization handler the user may have provided.
|
|
66
|
+
if(!(await this.#processHandler(this.firstRun.onStart))) {
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
// First run user experience.
|
|
45
72
|
buttonFirstRun.addEventListener("click", async () => {
|
|
46
73
|
|
|
47
74
|
// Show the beachball while we setup.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Get the list of devices the plugin knows about.
|
|
51
|
-
const devices = await this.homebridge.getCachedAccessories();
|
|
75
|
+
homebridge.showSpinner();
|
|
52
76
|
|
|
53
|
-
//
|
|
54
|
-
|
|
77
|
+
// Run a custom submit handler the user may have provided.
|
|
78
|
+
if(!(await this.#processHandler(this.firstRun.onSubmit))) {
|
|
55
79
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
60
|
-
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
61
82
|
|
|
62
83
|
// Create our UI.
|
|
63
84
|
document.getElementById("pageFirstRun").style.display = "none";
|
|
64
85
|
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
65
|
-
this.featureOptions.
|
|
86
|
+
this.featureOptions.show();
|
|
66
87
|
|
|
67
88
|
// All done. Let the user interact with us, although in practice, we shouldn't get here.
|
|
68
|
-
//
|
|
89
|
+
// homebridge.hideSpinner();
|
|
69
90
|
});
|
|
70
91
|
|
|
71
92
|
document.getElementById("pageFirstRun").style.display = "block";
|
|
@@ -75,81 +96,89 @@ export class webUi {
|
|
|
75
96
|
#showSettings() {
|
|
76
97
|
|
|
77
98
|
// Show the beachball while we setup.
|
|
78
|
-
|
|
99
|
+
homebridge.showSpinner();
|
|
79
100
|
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
document.getElementById("menuFeatureOptions").classList.add("btn-primary");
|
|
85
|
-
document.getElementById("menuSettings").classList.add("btn-elegant");
|
|
86
|
-
document.getElementById("menuSettings").classList.remove("btn-primary");
|
|
101
|
+
// Highlight the tab in our UI.
|
|
102
|
+
this.#toggleClasses("menuHome", "btn-elegant", "btn-primary");
|
|
103
|
+
this.#toggleClasses("menuFeatureOptions", "btn-elegant", "btn-primary");
|
|
104
|
+
this.#toggleClasses("menuSettings", "btn-primary", "btn-elegant");
|
|
87
105
|
|
|
88
106
|
document.getElementById("pageSupport").style.display = "none";
|
|
89
107
|
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
90
108
|
|
|
91
|
-
|
|
109
|
+
homebridge.showSchemaForm();
|
|
92
110
|
|
|
93
111
|
// All done. Let the user interact with us.
|
|
94
|
-
|
|
112
|
+
homebridge.hideSpinner();
|
|
95
113
|
}
|
|
96
114
|
|
|
97
115
|
// Show the support tab.
|
|
98
116
|
#showSupport() {
|
|
99
117
|
|
|
100
118
|
// Show the beachball while we setup.
|
|
101
|
-
|
|
102
|
-
|
|
119
|
+
homebridge.showSpinner();
|
|
120
|
+
homebridge.hideSchemaForm();
|
|
103
121
|
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
document.getElementById("menuFeatureOptions").classList.add("btn-primary");
|
|
109
|
-
document.getElementById("menuSettings").classList.remove("btn-elegant");
|
|
110
|
-
document.getElementById("menuSettings").classList.add("btn-primary");
|
|
122
|
+
// Highlight the tab in our UI.
|
|
123
|
+
this.#toggleClasses("menuHome", "btn-primary", "btn-elegant");
|
|
124
|
+
this.#toggleClasses("menuFeatureOptions", "btn-elegant", "btn-primary");
|
|
125
|
+
this.#toggleClasses("menuSettings", "btn-elegant", "btn-primary");
|
|
111
126
|
|
|
112
127
|
document.getElementById("pageSupport").style.display = "block";
|
|
113
128
|
document.getElementById("pageFeatureOptions").style.display = "none";
|
|
114
129
|
|
|
115
130
|
// All done. Let the user interact with us.
|
|
116
|
-
|
|
131
|
+
homebridge.hideSpinner();
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
// Launch our webUI.
|
|
120
135
|
async #launchWebUI() {
|
|
121
136
|
|
|
122
137
|
// Retrieve the current plugin configuration.
|
|
123
|
-
this.featureOptions.currentConfig = await
|
|
138
|
+
this.featureOptions.currentConfig = await homebridge.getPluginConfig();
|
|
124
139
|
|
|
125
140
|
// Add our event listeners to animate the UI.
|
|
126
141
|
document.getElementById("menuHome").addEventListener("click", () => this.#showSupport());
|
|
127
|
-
document.getElementById("menuFeatureOptions").addEventListener("click", () => this.featureOptions.
|
|
142
|
+
document.getElementById("menuFeatureOptions").addEventListener("click", () => this.featureOptions.show());
|
|
128
143
|
document.getElementById("menuSettings").addEventListener("click", () => this.#showSettings());
|
|
129
144
|
|
|
130
145
|
// Get the list of devices the plugin knows about.
|
|
131
|
-
const devices = await
|
|
146
|
+
const devices = await homebridge.getCachedAccessories();
|
|
132
147
|
|
|
133
148
|
// If we've got devices detected, we launch our feature option UI. Otherwise, we launch our first run UI.
|
|
134
|
-
if(this.featureOptions.currentConfig.length && devices?.length) {
|
|
149
|
+
if(this.featureOptions.currentConfig.length && devices?.length && !(await this.#processHandler(this.firstRun.isRequired))) {
|
|
135
150
|
|
|
136
151
|
document.getElementById("menuWrapper").style.display = "inline-flex";
|
|
137
|
-
this.featureOptions.
|
|
152
|
+
this.featureOptions.show();
|
|
153
|
+
|
|
138
154
|
return;
|
|
139
155
|
}
|
|
140
156
|
|
|
141
|
-
// If we have
|
|
142
|
-
|
|
157
|
+
// If we have the name property set for the plugin configuration yet, let's do so now. If we don't have a configuration, let's initialize it as well.
|
|
158
|
+
(this.featureOptions.currentConfig[0] ??= { name: this.name }).name ??= this.name;
|
|
143
159
|
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
// Update the plugin configuration and launch the first run UI.
|
|
161
|
+
await homebridge.updatePluginConfig(this.featureOptions.currentConfig);
|
|
162
|
+
this.#showFirstRun();
|
|
163
|
+
}
|
|
146
164
|
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
// Utility to process user-provided custom handlers that can handle both synchronous and asynchronous handlers.
|
|
166
|
+
async #processHandler(handler) {
|
|
167
|
+
|
|
168
|
+
if(((typeof handler === "function") && !(await handler())) || ((typeof handler !== "function") && !handler)) {
|
|
169
|
+
|
|
170
|
+
return false;
|
|
149
171
|
}
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Utility to toggle our classes.
|
|
177
|
+
#toggleClasses(id, removeClass, addClass) {
|
|
178
|
+
|
|
179
|
+
const element = document.getElementById(id);
|
|
180
|
+
|
|
181
|
+
element.classList.remove(removeClass);
|
|
182
|
+
element.classList.add(addClass);
|
|
154
183
|
}
|
|
155
184
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* A utility type that recursively makes all properties of an object, including nested objects, optional. This should only be used on JSON objects only. Otherwise,
|
|
6
|
+
* you're going to end up with class methods marked as optional as well. Credit for this belongs to: https://github.com/joonhocho/tsdef.
|
|
7
|
+
*
|
|
8
|
+
* @template T - The type to make recursively partial.
|
|
9
|
+
*/
|
|
10
|
+
export type DeepPartial<T> = {
|
|
11
|
+
[P in keyof T]?: T[P] extends Array<infer I> ? Array<DeepPartial<I>> : DeepPartial<T[P]>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* @internal
|
|
15
|
+
*
|
|
16
|
+
* A utility type that recursively makes all properties of an object, including nested objects, optional. This should only be used on JSON objects only. Otherwise,
|
|
17
|
+
* you're going to end up with class methods marked as optional as well. Credit for this belongs to: https://github.com/joonhocho/tsdef.
|
|
18
|
+
*
|
|
19
|
+
* @template T - The type to make recursively partial.
|
|
20
|
+
*/
|
|
21
|
+
export type DeepReadonly<T> = {
|
|
22
|
+
readonly [P in keyof T]: T[P] extends Array<infer I> ? Array<DeepReadonly<I>> : DeepReadonly<T[P]>;
|
|
23
|
+
};
|
|
2
24
|
export interface HomebridgePluginLogging {
|
|
3
25
|
debug: (message: string, ...parameters: unknown[]) => void;
|
|
4
26
|
error: (message: string, ...parameters: unknown[]) => void;
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA4CH,4BAA4B;AAC5B,MAAM,UAAU,KAAK,CAAC,UAAkB;IAEtC,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,SAAiC,EAAE,aAAqB;IAElF,wCAAwC;IACxC,IAAG,CAAC,CAAC,MAAM,SAAS,EAAE,CAAC,EAAE,CAAC;QAExB,4FAA4F;QAC5F,MAAM,KAAK,CAAC,aAAa,CAAC,CAAC;QAE3B,OAAO,KAAK,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IACzC,CAAC;IAED,mCAAmC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-plugin-utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"displayName": "Homebridge Plugin Utilities",
|
|
5
5
|
"description": "Opinionated utilities to provide common capabilities and create rich configuration webUI experiences for Homebridge plugins.",
|
|
6
6
|
"author": {
|
|
@@ -40,13 +40,13 @@
|
|
|
40
40
|
"main": "dist/index.js",
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@stylistic/eslint-plugin": "2.1.0",
|
|
43
|
-
"@types/node": "20.
|
|
43
|
+
"@types/node": "20.13.0",
|
|
44
44
|
"eslint": "8.57.0",
|
|
45
45
|
"shx": "^0.3.4",
|
|
46
46
|
"typescript": "5.4.5",
|
|
47
|
-
"typescript-eslint": "^7.
|
|
47
|
+
"typescript-eslint": "^7.11.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"mqtt": "^5.
|
|
50
|
+
"mqtt": "^5.7.0"
|
|
51
51
|
}
|
|
52
52
|
}
|