node-red-contrib-homekit-bridged 2.0.0-dev.5 → 2.0.0-dev.7
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/lib/HAPHostNode.js +183 -141
- package/build/lib/HAPServiceNode.js +199 -172
- package/build/lib/HAPServiceNode2.js +207 -172
- package/build/lib/NRCHKBError.js +23 -2
- package/build/lib/PairingQRCode.js +62 -0
- package/build/lib/Storage.js +157 -92
- package/build/lib/api.js +654 -288
- package/build/lib/camera/CameraControl.js +119 -84
- package/build/lib/camera/CameraDelegate.js +481 -404
- package/build/lib/camera/MP4StreamingServer.js +148 -139
- package/build/lib/hap/HAPCharacteristic.js +25 -4
- package/build/lib/hap/HAPService.js +25 -4
- package/build/lib/hap/eve-app/EveCharacteristics.js +124 -81
- package/build/lib/hap/eve-app/EveServices.js +50 -17
- package/build/lib/hap/hap-nodejs.js +32 -0
- package/build/lib/migration/HomeKitService2Migration.js +34 -0
- package/build/lib/migration/NodeMigration.js +75 -0
- package/build/lib/types/AccessoryInformationType.js +15 -1
- package/build/lib/types/CameraConfigType.js +15 -1
- package/build/lib/types/CustomCharacteristicType.js +15 -1
- package/build/lib/types/HAPHostConfigType.js +15 -1
- package/build/lib/types/HAPHostNodeType.js +15 -1
- package/build/lib/types/HAPService2ConfigType.js +15 -1
- package/build/lib/types/HAPService2NodeType.js +15 -1
- package/build/lib/types/HAPServiceConfigType.js +15 -1
- package/build/lib/types/HAPServiceNodeType.js +15 -1
- package/build/lib/types/HAPStatusConfigType.js +15 -1
- package/build/lib/types/HAPStatusNodeType.js +15 -1
- package/build/lib/types/HostType.js +28 -7
- package/build/lib/types/NodeType.js +15 -1
- package/build/lib/types/PublishTimersType.js +15 -1
- package/build/lib/types/UniFiControllerConfigType.js +16 -0
- package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.js +28 -7
- package/build/lib/types/hap-nodejs/HapCategories.js +64 -43
- package/build/lib/types/storage/SerializedHostType.js +15 -1
- package/build/lib/types/storage/StorageType.js +34 -10
- package/build/lib/unifi/ProtectDiscovery.js +80 -0
- package/build/lib/utils/AccessoryUtils.js +152 -110
- package/build/lib/utils/BridgeUtils.js +82 -39
- package/build/lib/utils/CharacteristicUtils.js +5 -49
- package/build/lib/utils/CharacteristicUtils2.js +5 -49
- package/build/lib/utils/CharacteristicUtilsBase.js +81 -0
- package/build/lib/utils/NodeStatusUtils.js +89 -40
- package/build/lib/utils/ServiceUtils.js +434 -375
- package/build/lib/utils/ServiceUtils2.js +514 -309
- package/build/lib/utils/index.js +10 -11
- package/build/nodes/bridge.html +184 -166
- package/build/nodes/bridge.js +27 -9
- package/build/nodes/locales/en-US/node-red-contrib-homekit-bridged.json +22 -0
- package/build/nodes/nrchkb.html +1601 -88
- package/build/nodes/nrchkb.js +66 -88
- package/build/nodes/plugin-instance.html +499 -0
- package/build/nodes/plugin-instance.js +46 -0
- package/build/nodes/service.html +517 -299
- package/build/nodes/service.js +5 -6
- package/build/nodes/service2.html +1683 -460
- package/build/nodes/service2.js +5 -8
- package/build/nodes/standalone.html +187 -174
- package/build/nodes/standalone.js +27 -9
- package/build/nodes/status.html +51 -18
- package/build/nodes/status.js +47 -40
- package/build/nodes/unifi-controller.html +92 -0
- package/build/nodes/unifi-controller.js +20 -0
- package/build/plugins/embedded/homebridge-camera-ffmpeg/index.js +479 -0
- package/build/plugins/embedded/homebridge-unifi-protect/index.js +521 -0
- package/build/plugins/embedded/index.js +58 -0
- package/build/plugins/nrchkb-homekit-plugins.js +17 -0
- package/build/plugins/registry/index.js +203 -0
- package/build/plugins/registry/types.js +16 -0
- package/build/scripts/migrate-homekit-service-flows.js +47 -0
- package/examples/demo/01 - ALL Demos single import.json +1885 -1885
- package/examples/demo/02 - Air Purifier.json +279 -279
- package/examples/demo/03 - Air Quality sensor with Battery.json +254 -254
- package/examples/demo/04 - Dimmable Bulb.json +172 -172
- package/examples/demo/05 - Color Bulb (HSV).json +195 -195
- package/examples/demo/06 - Fan (simple, 3 speeds).json +240 -240
- package/examples/demo/07 - Fan (with speed, oscillate, rotation direction).json +175 -175
- package/examples/demo/08 - CO2 detector.json +224 -224
- package/examples/demo/09 - CO (carbon monoxide) example.json +255 -255
- package/examples/demo/10 - Door window contact sensor.json +234 -234
- package/examples/demos (advanced)/01 - Television with inputs and speaker.json +541 -541
- package/examples/switch/01 - Plain Switch.json +178 -178
- package/package.json +95 -84
- package/build/lib/HAPHostNode.d.ts +0 -1
- package/build/lib/HAPServiceNode.d.ts +0 -1
- package/build/lib/HAPServiceNode2.d.ts +0 -1
- package/build/lib/NRCHKBError.d.ts +0 -3
- package/build/lib/Storage.d.ts +0 -30
- package/build/lib/api.d.ts +0 -1
- package/build/lib/camera/CameraControl.d.ts +0 -3
- package/build/lib/camera/CameraDelegate.d.ts +0 -38
- package/build/lib/camera/MP4StreamingServer.d.ts +0 -26
- package/build/lib/hap/HAPCharacteristic.d.ts +0 -9
- package/build/lib/hap/HAPService.d.ts +0 -6
- package/build/lib/hap/eve-app/EveCharacteristics.d.ts +0 -20
- package/build/lib/hap/eve-app/EveServices.d.ts +0 -5
- package/build/lib/types/AccessoryInformationType.d.ts +0 -11
- package/build/lib/types/CameraConfigType.d.ts +0 -24
- package/build/lib/types/CustomCharacteristicType.d.ts +0 -6
- package/build/lib/types/HAPHostConfigType.d.ts +0 -22
- package/build/lib/types/HAPHostNodeType.d.ts +0 -14
- package/build/lib/types/HAPService2ConfigType.d.ts +0 -6
- package/build/lib/types/HAPService2NodeType.d.ts +0 -7
- package/build/lib/types/HAPServiceConfigType.d.ts +0 -26
- package/build/lib/types/HAPServiceNodeType.d.ts +0 -38
- package/build/lib/types/HAPStatusConfigType.d.ts +0 -5
- package/build/lib/types/HAPStatusNodeType.d.ts +0 -12
- package/build/lib/types/HostType.d.ts +0 -5
- package/build/lib/types/NodeType.d.ts +0 -3
- package/build/lib/types/PublishTimersType.d.ts +0 -4
- package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.d.ts +0 -5
- package/build/lib/types/hap-nodejs/HapCategories.d.ts +0 -41
- package/build/lib/types/storage/SerializedHostType.d.ts +0 -5
- package/build/lib/types/storage/StorageType.d.ts +0 -8
- package/build/lib/utils/AccessoryUtils.d.ts +0 -1
- package/build/lib/utils/BridgeUtils.d.ts +0 -1
- package/build/lib/utils/CharacteristicUtils.d.ts +0 -1
- package/build/lib/utils/CharacteristicUtils2.d.ts +0 -1
- package/build/lib/utils/NodeStatusUtils.d.ts +0 -17
- package/build/lib/utils/ServiceUtils.d.ts +0 -1
- package/build/lib/utils/ServiceUtils2.d.ts +0 -1
- package/build/lib/utils/index.d.ts +0 -1
- package/build/nodes/bridge.d.ts +0 -1
- package/build/nodes/nrchkb.d.ts +0 -1
- package/build/nodes/service.d.ts +0 -1
- package/build/nodes/service2.d.ts +0 -1
- package/build/nodes/standalone.d.ts +0 -1
- package/build/nodes/status.d.ts +0 -1
package/build/nodes/nrchkb.js
CHANGED
|
@@ -1,93 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
37
15
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var path = __toESM(require("node:path"));
|
|
25
|
+
var import_hap_nodejs = require("@homebridge/hap-nodejs");
|
|
26
|
+
var import_logger = require("@nrchkb/logger");
|
|
27
|
+
var import_semver = __toESM(require("semver"));
|
|
28
|
+
var import_Storage = require("../lib/Storage");
|
|
29
|
+
(0, import_logger.loggerSetup)({ timestampEnabled: "NRCHKB" });
|
|
30
|
+
const log = (0, import_logger.logger)("NRCHKB");
|
|
31
|
+
if (process.env.NRCHKB_EXPERIMENTAL === "true") {
|
|
32
|
+
log.error("Experimental features enabled");
|
|
48
33
|
}
|
|
49
34
|
module.exports = (RED) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
if (process.env.NRCHKB_EXPERIMENTAL === 'true') {
|
|
88
|
-
log.debug('Registering nrchkb type');
|
|
89
|
-
RED.nodes.registerType('nrchkb', function (config) {
|
|
90
|
-
RED.nodes.createNode(this, config);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
35
|
+
const minimalNodeVersion = "22.9.0";
|
|
36
|
+
const nodeVersion = process.version;
|
|
37
|
+
if (!import_semver.default.gte(nodeVersion, minimalNodeVersion)) {
|
|
38
|
+
throw RangeError(
|
|
39
|
+
`Node.js version requirement not met. Required >=${minimalNodeVersion}. Installed ${nodeVersion}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
log.debug(
|
|
43
|
+
`Node.js version requirement met. Required >=${minimalNodeVersion}. Installed ${nodeVersion}`
|
|
44
|
+
);
|
|
45
|
+
let rootFolder;
|
|
46
|
+
if (RED.settings.available() && RED.settings.userDir) {
|
|
47
|
+
log.debug("RED settings available");
|
|
48
|
+
rootFolder = RED.settings.userDir;
|
|
49
|
+
} else {
|
|
50
|
+
log.error("RED settings not available");
|
|
51
|
+
rootFolder = path.join(require("node:os").homedir(), ".node-red");
|
|
52
|
+
}
|
|
53
|
+
const hapStoragePath = path.resolve(rootFolder, "homekit-persist");
|
|
54
|
+
try {
|
|
55
|
+
import_hap_nodejs.HAPStorage.setCustomStoragePath(hapStoragePath);
|
|
56
|
+
log.debug(`HAPStorage path set to ${hapStoragePath}`);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
log.debug("HAPStorage already initialized");
|
|
59
|
+
log.error("node-red restart highly recommended");
|
|
60
|
+
log.trace(error);
|
|
61
|
+
}
|
|
62
|
+
const API = require("../lib/api")(RED);
|
|
63
|
+
import_Storage.Storage.init(rootFolder, "nrchkb").then(() => {
|
|
64
|
+
log.debug(`nrchkb storage path set to ${import_Storage.Storage.storagePath()}`);
|
|
65
|
+
API.init();
|
|
66
|
+
});
|
|
67
|
+
log.debug("Registering nrchkb type");
|
|
68
|
+
RED.nodes.registerType("nrchkb", function(config) {
|
|
69
|
+
RED.nodes.createNode(this, config);
|
|
70
|
+
});
|
|
93
71
|
};
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.nrchkb-editor .nrchkb-plugin-instance-about {
|
|
3
|
+
margin-bottom: 10px;
|
|
4
|
+
padding: 7px 9px;
|
|
5
|
+
border: 1px solid var(--red-ui-secondary-border-color, #d8d8d8);
|
|
6
|
+
border-radius: 6px;
|
|
7
|
+
background: var(--red-ui-tertiary-background, #f7f7f7);
|
|
8
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
9
|
+
font-size: 12px;
|
|
10
|
+
line-height: 1.35;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.nrchkb-editor .nrchkb-plugin-instance-fields {
|
|
14
|
+
display: grid;
|
|
15
|
+
gap: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.nrchkb-editor .nrchkb-plugin-instance-section {
|
|
19
|
+
margin-top: 8px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.nrchkb-editor .nrchkb-plugin-instance-section:first-of-type {
|
|
23
|
+
margin-top: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.nrchkb-editor .nrchkb-plugin-instance-section summary {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 6px;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.nrchkb-editor .nrchkb-plugin-instance-section-title {
|
|
34
|
+
min-width: 0;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
text-overflow: ellipsis;
|
|
37
|
+
white-space: nowrap;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.nrchkb-editor .nrchkb-plugin-instance-check {
|
|
41
|
+
min-width: 0;
|
|
42
|
+
max-width: 100%;
|
|
43
|
+
color: var(--red-ui-primary-text-color, #333);
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.nrchkb-editor .nrchkb-plugin-instance-check input {
|
|
48
|
+
flex: 0 0 auto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.nrchkb-editor .nrchkb-plugin-instance-check span {
|
|
52
|
+
min-width: 0;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
text-overflow: ellipsis;
|
|
55
|
+
white-space: nowrap;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.nrchkb-editor .nrchkb-plugin-instance-check-row {
|
|
59
|
+
margin-bottom: 5px;
|
|
60
|
+
min-height: 24px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.nrchkb-editor .nrchkb-plugin-instance-textarea,
|
|
64
|
+
.nrchkb-editor .nrchkb-plugin-instance-select {
|
|
65
|
+
width: 100%;
|
|
66
|
+
box-sizing: border-box;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.nrchkb-editor .form-row > .nrchkb-plugin-instance-textarea {
|
|
70
|
+
flex: 1 1 auto;
|
|
71
|
+
width: auto !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.nrchkb-editor .nrchkb-plugin-instance-textarea-row {
|
|
75
|
+
align-items: flex-start;
|
|
76
|
+
flex-wrap: wrap;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.nrchkb-editor .nrchkb-plugin-instance-textarea {
|
|
80
|
+
min-height: 92px;
|
|
81
|
+
padding: 6px 8px;
|
|
82
|
+
resize: vertical;
|
|
83
|
+
font-family: var(--red-ui-monospace-font, monospace);
|
|
84
|
+
line-height: 1.35;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.nrchkb-editor .nrchkb-plugin-instance-help {
|
|
88
|
+
flex: 1 1 100%;
|
|
89
|
+
margin: -3px 0 3px 160px;
|
|
90
|
+
color: var(--red-ui-secondary-text-color, #666);
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
line-height: 1.35;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@media (max-width: 420px) {
|
|
96
|
+
.nrchkb-editor .nrchkb-plugin-instance-help {
|
|
97
|
+
margin-left: 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
101
|
+
|
|
102
|
+
<script data-template-name="homekit-plugin-instance" type="text/x-red">
|
|
103
|
+
<div class="nrchkb-editor">
|
|
104
|
+
<input type="hidden" id="node-config-input-pluginId">
|
|
105
|
+
<input type="hidden" id="node-config-input-pluginConfig">
|
|
106
|
+
|
|
107
|
+
<div id="node-config-input-plugin-instance-editor"></div>
|
|
108
|
+
</div>
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<script data-help-name="homekit-plugin-instance" type="text/markdown">
|
|
112
|
+
# HomeKit Plugin Instance
|
|
113
|
+
|
|
114
|
+
Generic NRCHKB plugin instance used by service nodes to track plugin dependencies.
|
|
115
|
+
|
|
116
|
+
> [!NOTE]
|
|
117
|
+
> Plugin instances are created and managed from plugin-aware service editors.
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<script type="text/javascript">
|
|
121
|
+
RED.nodes.registerType('homekit-plugin-instance', {
|
|
122
|
+
category: 'config',
|
|
123
|
+
defaults: {
|
|
124
|
+
pluginId: {value: ''},
|
|
125
|
+
pluginConfig: {value: '{}'},
|
|
126
|
+
controller: {
|
|
127
|
+
value: '',
|
|
128
|
+
type: 'homekit-unifi-controller',
|
|
129
|
+
required: false,
|
|
130
|
+
validate: function () { return true }
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
label: function () {
|
|
134
|
+
const plugin = (window.NRCHKBAvailablePlugins || []).find(function (entry) {
|
|
135
|
+
return entry.id === this.pluginId
|
|
136
|
+
}, this)
|
|
137
|
+
return plugin ? plugin.displayName : 'HomeKit Plugin Instance'
|
|
138
|
+
},
|
|
139
|
+
onadd: function () {
|
|
140
|
+
applyDefaultNodeDocumentation(this, 'homekit-plugin-instance')
|
|
141
|
+
},
|
|
142
|
+
oneditprepare: function () {
|
|
143
|
+
const node = this
|
|
144
|
+
const draft = window.NRCHKBPluginInstanceDraft || {}
|
|
145
|
+
const pluginId = node.pluginId || draft.pluginId || ''
|
|
146
|
+
const pluginConfig = $.extend(true, {}, draft.pluginConfig || {}, parsePluginConfig(node.pluginConfig))
|
|
147
|
+
if (node.controller && !pluginConfig.controller) {
|
|
148
|
+
pluginConfig.controller = node.controller
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
$('#node-config-input-pluginId').val(pluginId)
|
|
152
|
+
$('#node-config-input-pluginConfig').val(JSON.stringify(pluginConfig))
|
|
153
|
+
|
|
154
|
+
$.getJSON('nrchkb/plugins', function (plugins) {
|
|
155
|
+
window.NRCHKBAvailablePlugins = plugins || []
|
|
156
|
+
const plugin = (plugins || []).find(function (entry) {
|
|
157
|
+
return entry.id === pluginId
|
|
158
|
+
})
|
|
159
|
+
renderPluginEditor(plugin, pluginConfig)
|
|
160
|
+
}).fail(function () {
|
|
161
|
+
RED.notify('Unable to load NRCHKB plugin metadata.', 'warning')
|
|
162
|
+
renderPluginEditor(undefined, pluginConfig)
|
|
163
|
+
})
|
|
164
|
+
},
|
|
165
|
+
oneditsave: function () {
|
|
166
|
+
const config = {}
|
|
167
|
+
|
|
168
|
+
$('[data-plugin-config-path]').each(function () {
|
|
169
|
+
const input = $(this)
|
|
170
|
+
const field = input.data('pluginField') || {}
|
|
171
|
+
const path = input.attr('data-plugin-config-path')
|
|
172
|
+
|
|
173
|
+
if (!path) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (field.type === 'checkbox') {
|
|
178
|
+
setConfigValue(config, path, input.is(':checked'))
|
|
179
|
+
} else if (field.type === 'number') {
|
|
180
|
+
const value = input.val()
|
|
181
|
+
setConfigValue(config, path, value === '' || value === undefined ? undefined : Number(value))
|
|
182
|
+
} else if (field.type === 'dynamic-select') {
|
|
183
|
+
setConfigValue(config, path, input.val() || input.data('pluginDynamicValue') || '')
|
|
184
|
+
} else {
|
|
185
|
+
setConfigValue(config, path, input.val())
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const pluginId = $('#node-config-input-pluginId').val()
|
|
190
|
+
const controller = config.controller || $('#node-config-input-controller').val() || ''
|
|
191
|
+
if (controller) {
|
|
192
|
+
setConfigValue(config, 'controller', controller)
|
|
193
|
+
}
|
|
194
|
+
const pluginConfig = JSON.stringify(config)
|
|
195
|
+
|
|
196
|
+
$('#node-config-input-pluginId').val(pluginId)
|
|
197
|
+
$('#node-config-input-pluginConfig').val(pluginConfig)
|
|
198
|
+
$('#node-config-input-controller').val(controller)
|
|
199
|
+
|
|
200
|
+
this.pluginId = pluginId
|
|
201
|
+
this.pluginConfig = pluginConfig
|
|
202
|
+
this.controller = controller
|
|
203
|
+
delete window.NRCHKBPluginInstanceDraft
|
|
204
|
+
},
|
|
205
|
+
oneditcancel: function () {
|
|
206
|
+
delete window.NRCHKBPluginInstanceDraft
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
function parsePluginConfig(value) {
|
|
211
|
+
if (!value) {
|
|
212
|
+
return {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(value)
|
|
217
|
+
} catch (_) {
|
|
218
|
+
return {}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getPathParts(path) {
|
|
223
|
+
return typeof path === 'string' && path.length
|
|
224
|
+
? path.split('.').filter(function (part) {
|
|
225
|
+
return part.length > 0
|
|
226
|
+
})
|
|
227
|
+
: []
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getConfigValue(config, path, defaultValue) {
|
|
231
|
+
const parts = getPathParts(path)
|
|
232
|
+
let current = config
|
|
233
|
+
|
|
234
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
235
|
+
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, parts[index])) {
|
|
236
|
+
return defaultValue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
current = current[parts[index]]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return current === undefined ? defaultValue : current
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function setConfigValue(config, path, value) {
|
|
246
|
+
const parts = getPathParts(path)
|
|
247
|
+
let current = config
|
|
248
|
+
|
|
249
|
+
parts.forEach(function (part, index) {
|
|
250
|
+
if (index === parts.length - 1) {
|
|
251
|
+
current[part] = value
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!current[part] || typeof current[part] !== 'object') {
|
|
256
|
+
current[part] = {}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
current = current[part]
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function createFieldId(field) {
|
|
264
|
+
if (field.type === 'config-node' && field.path === 'controller' && field.configNodeType === 'homekit-unifi-controller') {
|
|
265
|
+
return 'node-config-input-controller'
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return 'node-config-input-plugin-' + (field.path || 'field').replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function renderPluginEditor(plugin, config) {
|
|
272
|
+
const editorContainer = $('#node-config-input-plugin-instance-editor').empty()
|
|
273
|
+
|
|
274
|
+
if (!plugin) {
|
|
275
|
+
$('<div/>', {class: 'nrchkb-plugin-instance-about'})
|
|
276
|
+
.text('Plugin metadata is not available.')
|
|
277
|
+
.appendTo(editorContainer)
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
$('<div/>', {class: 'nrchkb-plugin-instance-about'})
|
|
282
|
+
.text(plugin.displayName + ' v' + plugin.version + '. ' + plugin.description)
|
|
283
|
+
.appendTo(editorContainer)
|
|
284
|
+
|
|
285
|
+
const sections = plugin.editor && Array.isArray(plugin.editor.sections)
|
|
286
|
+
? plugin.editor.sections
|
|
287
|
+
: []
|
|
288
|
+
|
|
289
|
+
if (!sections.length) {
|
|
290
|
+
$('<div/>', {class: 'nrchkb-plugin-instance-about'})
|
|
291
|
+
.text('This plugin does not provide editor fields.')
|
|
292
|
+
.appendTo(editorContainer)
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
sections.forEach(function (section, index) {
|
|
297
|
+
const details = $('<details/>', {
|
|
298
|
+
class: 'nrchkb-section nrchkb-plugin-instance-section'
|
|
299
|
+
}).appendTo(editorContainer)
|
|
300
|
+
|
|
301
|
+
if (section.collapsed !== true) {
|
|
302
|
+
details.prop('open', true)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const summary = $('<summary/>').appendTo(details)
|
|
306
|
+
$('<i/>', {class: 'fa ' + (section.icon || 'fa-sliders')}).appendTo(summary)
|
|
307
|
+
$('<span/>', {class: 'nrchkb-plugin-instance-section-title'})
|
|
308
|
+
.text(section.title || 'Settings ' + (index + 1))
|
|
309
|
+
.appendTo(summary)
|
|
310
|
+
|
|
311
|
+
const body = $('<div/>', {class: 'nrchkb-section-body'}).appendTo(details)
|
|
312
|
+
|
|
313
|
+
if (section.description) {
|
|
314
|
+
$('<div/>', {class: 'nrchkb-plugin-instance-about'})
|
|
315
|
+
.text(section.description)
|
|
316
|
+
.appendTo(body)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const fields = $('<div/>', {class: 'nrchkb-plugin-instance-fields'}).appendTo(body)
|
|
320
|
+
;(section.fields || []).forEach(function (field) {
|
|
321
|
+
renderPluginField(fields, field, config)
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
editorContainer.find('[data-plugin-dynamic-select="true"]').each(function () {
|
|
326
|
+
loadDynamicPluginSelect(editorContainer, $(this))
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
editorContainer.on('change', '[data-plugin-config-path]', function (event) {
|
|
330
|
+
const changedInput = $(event.target)
|
|
331
|
+
const changedPath = $(event.target).attr('data-plugin-config-path') || ''
|
|
332
|
+
const changedField = changedInput.data('pluginField') || {}
|
|
333
|
+
|
|
334
|
+
if (changedField.type === 'dynamic-select') {
|
|
335
|
+
changedInput.data('pluginDynamicValue', changedInput.val() || '')
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
editorContainer.find('[data-plugin-dynamic-select="true"]').each(function () {
|
|
339
|
+
const dynamicInput = $(this)
|
|
340
|
+
const dynamicField = dynamicInput.data('pluginField') || {}
|
|
341
|
+
|
|
342
|
+
if (dynamicField.optionsDependsOn === changedPath) {
|
|
343
|
+
dynamicInput.data(
|
|
344
|
+
'pluginDynamicValue',
|
|
345
|
+
dynamicInput.val() || dynamicInput.data('pluginDynamicValue') || ''
|
|
346
|
+
)
|
|
347
|
+
loadDynamicPluginSelect(editorContainer, dynamicInput)
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
})
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function renderPluginField(container, field, config) {
|
|
354
|
+
const fieldId = createFieldId(field)
|
|
355
|
+
const value = getConfigValue(config, field.path, field.default)
|
|
356
|
+
if (fieldId === 'node-config-input-controller') {
|
|
357
|
+
$('#' + fieldId).remove()
|
|
358
|
+
}
|
|
359
|
+
const row = $('<div/>', {
|
|
360
|
+
class: 'form-row' + (field.type === 'checkbox'
|
|
361
|
+
? ' nrchkb-checkbox-row nrchkb-plugin-instance-check-row'
|
|
362
|
+
: field.type === 'textarea'
|
|
363
|
+
? ' nrchkb-plugin-instance-textarea-row'
|
|
364
|
+
: '')
|
|
365
|
+
}).appendTo(container)
|
|
366
|
+
|
|
367
|
+
if (field.type === 'checkbox') {
|
|
368
|
+
const label = $('<label/>', {
|
|
369
|
+
class: 'nrchkb-checkbox-label nrchkb-plugin-instance-check',
|
|
370
|
+
for: fieldId
|
|
371
|
+
}).appendTo(row)
|
|
372
|
+
const input = $('<input/>', {type: 'checkbox', id: fieldId}).appendTo(label)
|
|
373
|
+
$('<span/>')
|
|
374
|
+
.append(field.icon ? $('<i/>', {class: 'fa ' + field.icon}) : $())
|
|
375
|
+
.append(field.icon ? ' ' : '')
|
|
376
|
+
.append(document.createTextNode(field.label || field.path))
|
|
377
|
+
.appendTo(label)
|
|
378
|
+
input.prop('checked', !!value)
|
|
379
|
+
attachFieldMetadata(input, field)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const label = $('<label/>', {for: fieldId}).appendTo(row)
|
|
384
|
+
if (field.icon) {
|
|
385
|
+
$('<i/>', {class: 'fa ' + field.icon}).appendTo(label)
|
|
386
|
+
label.append(' ')
|
|
387
|
+
}
|
|
388
|
+
label.append(document.createTextNode(field.label || field.path))
|
|
389
|
+
|
|
390
|
+
let input
|
|
391
|
+
if (field.type === 'select' || field.type === 'dynamic-select') {
|
|
392
|
+
input = $('<select/>', {id: fieldId, class: 'nrchkb-plugin-instance-select'}).appendTo(row)
|
|
393
|
+
;(field.options || []).forEach(function (option) {
|
|
394
|
+
$('<option/>').val(option.value).text(option.label).appendTo(input)
|
|
395
|
+
})
|
|
396
|
+
input.val(value === undefined ? '' : value)
|
|
397
|
+
} else if (field.type === 'textarea') {
|
|
398
|
+
input = $('<textarea/>', {
|
|
399
|
+
id: fieldId,
|
|
400
|
+
class: 'nrchkb-plugin-instance-textarea',
|
|
401
|
+
rows: 4
|
|
402
|
+
}).appendTo(row)
|
|
403
|
+
input.val(value === undefined ? '' : value)
|
|
404
|
+
if (field.placeholder) {
|
|
405
|
+
const helpId = fieldId + '-help'
|
|
406
|
+
input.attr('aria-describedby', helpId)
|
|
407
|
+
$('<div/>', {
|
|
408
|
+
id: helpId,
|
|
409
|
+
class: 'nrchkb-plugin-instance-help'
|
|
410
|
+
}).text(field.placeholder).appendTo(row)
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
input = $('<input/>', {
|
|
414
|
+
type: field.type === 'number' ? 'number' : field.type === 'password' ? 'password' : 'text',
|
|
415
|
+
id: fieldId,
|
|
416
|
+
placeholder: field.placeholder || ''
|
|
417
|
+
}).appendTo(row)
|
|
418
|
+
input.val(value === undefined ? '' : value)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
attachFieldMetadata(input, field)
|
|
422
|
+
|
|
423
|
+
if (field.type === 'config-node' && field.configNodeType && RED.editor && RED.editor.prepareConfigNodeSelect) {
|
|
424
|
+
const property = fieldId.replace(/^node-config-input-/, '')
|
|
425
|
+
const configNode = {}
|
|
426
|
+
configNode[property] = value === undefined ? '' : value
|
|
427
|
+
|
|
428
|
+
RED.editor.prepareConfigNodeSelect(
|
|
429
|
+
configNode,
|
|
430
|
+
property,
|
|
431
|
+
field.configNodeType,
|
|
432
|
+
'node-config-input'
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
attachFieldMetadata($('#' + fieldId), field)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (field.type === 'dynamic-select') {
|
|
439
|
+
input.attr('data-plugin-dynamic-select', 'true')
|
|
440
|
+
input.data('pluginDynamicValue', value === undefined ? '' : value)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function attachFieldMetadata(input, field) {
|
|
445
|
+
input
|
|
446
|
+
.attr('data-plugin-config-path', field.path)
|
|
447
|
+
.data('pluginField', field)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function loadDynamicPluginSelect(container, input) {
|
|
451
|
+
const field = input.data('pluginField') || {}
|
|
452
|
+
const selectedValue = input.val() || input.data('pluginDynamicValue') || ''
|
|
453
|
+
|
|
454
|
+
if (!field.optionsUrl) {
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let url = field.optionsUrl
|
|
459
|
+
if (field.optionsDependsOn) {
|
|
460
|
+
const dependsInput = container.find('[data-plugin-config-path="' + field.optionsDependsOn + '"]')
|
|
461
|
+
const dependsValue = dependsInput.val()
|
|
462
|
+
|
|
463
|
+
if (!dependsValue) {
|
|
464
|
+
input.empty()
|
|
465
|
+
$('<option/>').val('').text('Choose a dependency first').appendTo(input)
|
|
466
|
+
input.val('')
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
url = url.replace('{' + field.optionsDependsOn + '}', encodeURIComponent(dependsValue))
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
input.empty()
|
|
474
|
+
$('<option/>').val('').text('Loading...').appendTo(input)
|
|
475
|
+
|
|
476
|
+
$.getJSON(url, function (options) {
|
|
477
|
+
input.empty()
|
|
478
|
+
$('<option/>').val('').text('Choose...').appendTo(input)
|
|
479
|
+
;(options || []).forEach(function (option) {
|
|
480
|
+
$('<option/>').val(option.value).text(option.label).appendTo(input)
|
|
481
|
+
})
|
|
482
|
+
if (selectedValue && input.find('option').filter(function () {
|
|
483
|
+
return $(this).val() === selectedValue
|
|
484
|
+
}).length === 0) {
|
|
485
|
+
$('<option/>').val(selectedValue).text('Saved value (' + selectedValue + ')').appendTo(input)
|
|
486
|
+
}
|
|
487
|
+
input.val(selectedValue)
|
|
488
|
+
input.data('pluginDynamicValue', input.val() || selectedValue)
|
|
489
|
+
}).fail(function () {
|
|
490
|
+
input.empty()
|
|
491
|
+
$('<option/>').val('').text('Unable to load options').appendTo(input)
|
|
492
|
+
if (selectedValue) {
|
|
493
|
+
$('<option/>').val(selectedValue).text('Saved value (' + selectedValue + ')').appendTo(input)
|
|
494
|
+
input.val(selectedValue)
|
|
495
|
+
}
|
|
496
|
+
input.data('pluginDynamicValue', input.val() || selectedValue)
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
</script>
|