node-red-contrib-homekit-bridged 2.0.0-dev.5 → 2.0.0-dev.8

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.
Files changed (128) hide show
  1. package/build/lib/HAPHostNode.js +183 -141
  2. package/build/lib/HAPServiceNode.js +199 -172
  3. package/build/lib/HAPServiceNode2.js +207 -172
  4. package/build/lib/NRCHKBError.js +23 -2
  5. package/build/lib/PairingQRCode.js +62 -0
  6. package/build/lib/Storage.js +152 -90
  7. package/build/lib/api.js +654 -288
  8. package/build/lib/camera/CameraControl.js +119 -84
  9. package/build/lib/camera/CameraDelegate.js +481 -404
  10. package/build/lib/camera/MP4StreamingServer.js +148 -139
  11. package/build/lib/hap/HAPCharacteristic.js +25 -4
  12. package/build/lib/hap/HAPService.js +25 -4
  13. package/build/lib/hap/eve-app/EveCharacteristics.js +124 -81
  14. package/build/lib/hap/eve-app/EveServices.js +50 -17
  15. package/build/lib/hap/hap-nodejs.js +32 -0
  16. package/build/lib/migration/HomeKitService2Migration.js +34 -0
  17. package/build/lib/migration/NodeMigration.js +75 -0
  18. package/build/lib/types/AccessoryInformationType.js +15 -1
  19. package/build/lib/types/CameraConfigType.js +15 -1
  20. package/build/lib/types/CustomCharacteristicType.js +15 -1
  21. package/build/lib/types/HAPHostConfigType.js +15 -1
  22. package/build/lib/types/HAPHostNodeType.js +15 -1
  23. package/build/lib/types/HAPService2ConfigType.js +15 -1
  24. package/build/lib/types/HAPService2NodeType.js +15 -1
  25. package/build/lib/types/HAPServiceConfigType.js +15 -1
  26. package/build/lib/types/HAPServiceNodeType.js +15 -1
  27. package/build/lib/types/HAPStatusConfigType.js +15 -1
  28. package/build/lib/types/HAPStatusNodeType.js +15 -1
  29. package/build/lib/types/HostType.js +28 -7
  30. package/build/lib/types/NodeType.js +15 -1
  31. package/build/lib/types/PublishTimersType.js +15 -1
  32. package/build/lib/types/UniFiControllerConfigType.js +16 -0
  33. package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.js +28 -7
  34. package/build/lib/types/hap-nodejs/HapCategories.js +64 -43
  35. package/build/lib/types/storage/SerializedHostType.js +15 -1
  36. package/build/lib/types/storage/StorageType.js +34 -10
  37. package/build/lib/unifi/ProtectDiscovery.js +80 -0
  38. package/build/lib/utils/AccessoryUtils.js +152 -110
  39. package/build/lib/utils/BridgeUtils.js +82 -39
  40. package/build/lib/utils/CharacteristicUtils.js +5 -49
  41. package/build/lib/utils/CharacteristicUtils2.js +5 -49
  42. package/build/lib/utils/CharacteristicUtilsBase.js +81 -0
  43. package/build/lib/utils/NodeStatusUtils.js +89 -40
  44. package/build/lib/utils/ServiceUtils.js +433 -375
  45. package/build/lib/utils/ServiceUtils2.js +519 -309
  46. package/build/lib/utils/index.js +10 -11
  47. package/build/nodes/bridge.html +206 -168
  48. package/build/nodes/bridge.js +27 -9
  49. package/build/nodes/locales/en-US/node-red-contrib-homekit-bridged.json +22 -0
  50. package/build/nodes/nrchkb.html +1753 -117
  51. package/build/nodes/nrchkb.js +66 -88
  52. package/build/nodes/plugin-instance.html +509 -0
  53. package/build/nodes/plugin-instance.js +46 -0
  54. package/build/nodes/service.html +544 -307
  55. package/build/nodes/service.js +5 -6
  56. package/build/nodes/service2.html +1721 -455
  57. package/build/nodes/service2.js +5 -8
  58. package/build/nodes/standalone.html +208 -176
  59. package/build/nodes/standalone.js +27 -9
  60. package/build/nodes/status.html +51 -18
  61. package/build/nodes/status.js +47 -40
  62. package/build/nodes/unifi-controller.html +92 -0
  63. package/build/nodes/unifi-controller.js +20 -0
  64. package/build/plugins/embedded/homebridge-camera-ffmpeg/index.js +479 -0
  65. package/build/plugins/embedded/homebridge-unifi-protect/index.js +521 -0
  66. package/build/plugins/embedded/index.js +58 -0
  67. package/build/plugins/nrchkb-homekit-plugins.js +17 -0
  68. package/build/plugins/registry/index.js +203 -0
  69. package/build/plugins/registry/types.js +16 -0
  70. package/build/scripts/migrate-homekit-service-flows.js +47 -0
  71. package/examples/demo/01 - ALL Demos single import.json +1885 -1885
  72. package/examples/demo/02 - Air Purifier.json +279 -279
  73. package/examples/demo/03 - Air Quality sensor with Battery.json +254 -254
  74. package/examples/demo/04 - Dimmable Bulb.json +172 -172
  75. package/examples/demo/05 - Color Bulb (HSV).json +195 -195
  76. package/examples/demo/06 - Fan (simple, 3 speeds).json +240 -240
  77. package/examples/demo/07 - Fan (with speed, oscillate, rotation direction).json +175 -175
  78. package/examples/demo/08 - CO2 detector.json +224 -224
  79. package/examples/demo/09 - CO (carbon monoxide) example.json +255 -255
  80. package/examples/demo/10 - Door window contact sensor.json +234 -234
  81. package/examples/demos (advanced)/01 - Television with inputs and speaker.json +541 -541
  82. package/examples/switch/01 - Plain Switch.json +178 -178
  83. package/package.json +95 -84
  84. package/build/lib/HAPHostNode.d.ts +0 -1
  85. package/build/lib/HAPServiceNode.d.ts +0 -1
  86. package/build/lib/HAPServiceNode2.d.ts +0 -1
  87. package/build/lib/NRCHKBError.d.ts +0 -3
  88. package/build/lib/Storage.d.ts +0 -30
  89. package/build/lib/api.d.ts +0 -1
  90. package/build/lib/camera/CameraControl.d.ts +0 -3
  91. package/build/lib/camera/CameraDelegate.d.ts +0 -38
  92. package/build/lib/camera/MP4StreamingServer.d.ts +0 -26
  93. package/build/lib/hap/HAPCharacteristic.d.ts +0 -9
  94. package/build/lib/hap/HAPService.d.ts +0 -6
  95. package/build/lib/hap/eve-app/EveCharacteristics.d.ts +0 -20
  96. package/build/lib/hap/eve-app/EveServices.d.ts +0 -5
  97. package/build/lib/types/AccessoryInformationType.d.ts +0 -11
  98. package/build/lib/types/CameraConfigType.d.ts +0 -24
  99. package/build/lib/types/CustomCharacteristicType.d.ts +0 -6
  100. package/build/lib/types/HAPHostConfigType.d.ts +0 -22
  101. package/build/lib/types/HAPHostNodeType.d.ts +0 -14
  102. package/build/lib/types/HAPService2ConfigType.d.ts +0 -6
  103. package/build/lib/types/HAPService2NodeType.d.ts +0 -7
  104. package/build/lib/types/HAPServiceConfigType.d.ts +0 -26
  105. package/build/lib/types/HAPServiceNodeType.d.ts +0 -38
  106. package/build/lib/types/HAPStatusConfigType.d.ts +0 -5
  107. package/build/lib/types/HAPStatusNodeType.d.ts +0 -12
  108. package/build/lib/types/HostType.d.ts +0 -5
  109. package/build/lib/types/NodeType.d.ts +0 -3
  110. package/build/lib/types/PublishTimersType.d.ts +0 -4
  111. package/build/lib/types/hap-nodejs/HapAdaptiveLightingControllerMode.d.ts +0 -5
  112. package/build/lib/types/hap-nodejs/HapCategories.d.ts +0 -41
  113. package/build/lib/types/storage/SerializedHostType.d.ts +0 -5
  114. package/build/lib/types/storage/StorageType.d.ts +0 -8
  115. package/build/lib/utils/AccessoryUtils.d.ts +0 -1
  116. package/build/lib/utils/BridgeUtils.d.ts +0 -1
  117. package/build/lib/utils/CharacteristicUtils.d.ts +0 -1
  118. package/build/lib/utils/CharacteristicUtils2.d.ts +0 -1
  119. package/build/lib/utils/NodeStatusUtils.d.ts +0 -17
  120. package/build/lib/utils/ServiceUtils.d.ts +0 -1
  121. package/build/lib/utils/ServiceUtils2.d.ts +0 -1
  122. package/build/lib/utils/index.d.ts +0 -1
  123. package/build/nodes/bridge.d.ts +0 -1
  124. package/build/nodes/nrchkb.d.ts +0 -1
  125. package/build/nodes/service.d.ts +0 -1
  126. package/build/nodes/service2.d.ts +0 -1
  127. package/build/nodes/standalone.d.ts +0 -1
  128. package/build/nodes/status.d.ts +0 -1
package/build/lib/api.js CHANGED
@@ -1,306 +1,672 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __rest = (this && this.__rest) || function (s, e) {
12
- var t = {};
13
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
- t[p] = s[p];
15
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
- t[p[i]] = s[p[i]];
19
- }
20
- return t;
21
- };
22
- var __importDefault = (this && this.__importDefault) || function (mod) {
23
- 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;
24
15
  };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- const hap_nodejs_1 = require("@homebridge/hap-nodejs");
27
- const logger_1 = require("@nrchkb/logger");
28
- const EveCharacteristics_1 = __importDefault(require("./hap/eve-app/EveCharacteristics"));
29
- const Storage_1 = require("./Storage");
30
- const HapCategories_1 = __importDefault(require("./types/hap-nodejs/HapCategories"));
31
- const version = require('../../package.json').version.trim();
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 fs = __toESM(require("node:fs"));
25
+ var os = __toESM(require("node:os"));
26
+ var import_hap_nodejs = require("@homebridge/hap-nodejs");
27
+ var import_logger = require("@nrchkb/logger");
28
+ var import_embedded = require("../plugins/embedded");
29
+ var import_registry = require("../plugins/registry");
30
+ var import_EveCharacteristics = __toESM(require("./hap/eve-app/EveCharacteristics"));
31
+ var import_NodeMigration = require("./migration/NodeMigration");
32
+ var import_PairingQRCode = require("./PairingQRCode");
33
+ var import_Storage = require("./Storage");
34
+ var import_HapCategories = __toESM(require("./types/hap-nodejs/HapCategories"));
35
+ var import_ProtectDiscovery = require("./unifi/ProtectDiscovery");
36
+ const version = require("../../package.json").version.trim();
32
37
  module.exports = (RED) => {
33
- const log = (0, logger_1.logger)('NRCHKB', 'API');
34
- const _initServiceAPI = () => {
35
- log.debug('Initialize Service API');
36
- const serviceData = {
37
- BatteryService: {
38
- nrchkbDisabledText: 'BatteryService (deprecated, replaced by Battery)'
39
- },
40
- BridgeConfiguration: {
41
- nrchkbDisabledText: 'BridgeConfiguration (deprecated, unused)'
42
- },
43
- BridgingState: {
44
- nrchkbDisabledText: 'BridgingState (deprecated, unused)'
45
- },
46
- CameraControl: {
47
- nrchkbDisabledText: 'CameraControl (deprecated, replaced by)'
48
- },
49
- CameraEventRecordingManagement: {
50
- nrchkbDisabledText: 'CameraEventRecordingManagement (deprecated, replaced by CameraRecordingManagement)'
51
- },
52
- Relay: {
53
- nrchkbDisabledText: 'Relay (deprecated, replaced by CloudRelay)'
54
- },
55
- Slat: {
56
- nrchkbDisabledText: 'Slat (deprecated, replaced by Slats)'
57
- },
58
- TimeInformation: {
59
- nrchkbDisabledText: 'TimeInformation (deprecated, unused)'
60
- },
61
- TunneledBTLEAccessoryService: {
62
- nrchkbDisabledText: 'TunneledBTLEAccessoryService (deprecated, replaced by Tunnel)'
63
- }
38
+ const log = (0, import_logger.logger)("NRCHKB", "API");
39
+ const registeredCustomCharacteristicKeys = /* @__PURE__ */ new Set();
40
+ const pathExists = (path) => {
41
+ try {
42
+ return fs.existsSync(path);
43
+ } catch (_error) {
44
+ return false;
45
+ }
46
+ };
47
+ const detectAdvertiserRecommendation = () => {
48
+ const platform = os.platform();
49
+ const container = pathExists("/.dockerenv") || pathExists("/run/.containerenv") || process.env.container !== void 0 || process.env.KUBERNETES_SERVICE_HOST !== void 0;
50
+ const dbusAvailable = pathExists("/run/dbus/system_bus_socket") || pathExists("/var/run/dbus/system_bus_socket");
51
+ const avahiAvailable = dbusAvailable && (pathExists("/run/avahi-daemon/socket") || pathExists("/var/run/avahi-daemon/socket"));
52
+ const resolvedAvailable = dbusAvailable && (pathExists("/run/systemd/resolve") || pathExists("/run/systemd/resolve/io.systemd.Resolve"));
53
+ const caveats = [];
54
+ if (platform === "linux") {
55
+ if (avahiAvailable) {
56
+ if (container) {
57
+ caveats.push(
58
+ "Container detected. HomeKit discovery still depends on host networking and multicast visibility."
59
+ );
60
+ }
61
+ return {
62
+ caveats,
63
+ detected: {
64
+ avahiAvailable,
65
+ container,
66
+ dbusAvailable,
67
+ platform,
68
+ resolvedAvailable
69
+ },
70
+ reason: "Linux host with Avahi and D-Bus available.",
71
+ recommended: "avahi",
72
+ title: "AVAHI recommended"
64
73
  };
65
- Object.values(hap_nodejs_1.Service)
66
- .filter((service) => service.prototype instanceof hap_nodejs_1.Service)
67
- .map((service) => {
68
- const newService = hap_nodejs_1.Service.serialize(new service());
69
- newService.displayName = service.name;
70
- return newService;
71
- })
72
- .forEach((serialized) => {
73
- serviceData[serialized.displayName] = Object.assign(Object.assign({}, serviceData === null || serviceData === void 0 ? void 0 : serviceData[serialized.displayName]), serialized);
74
- });
75
- RED.httpAdmin.get('/nrchkb/service/types', RED.auth.needsPermission('nrchkb.read'), (_req, res) => {
76
- res.setHeader('Content-Type', 'application/json');
77
- res.json(serviceData);
78
- });
74
+ }
75
+ if (container) {
76
+ caveats.push(
77
+ "Container detected without usable host Avahi. Docker bridge networking can prevent mDNS discovery even with the recommended advertiser."
78
+ );
79
+ } else if (resolvedAvailable) {
80
+ caveats.push(
81
+ "systemd-resolved mDNS appears available, but RESOLVED is an advanced experimental choice. CIAO is safer unless you intentionally manage mDNS through systemd-resolved."
82
+ );
83
+ } else {
84
+ caveats.push(
85
+ "Install and run avahi-daemon with D-Bus if you prefer the Linux-native advertiser."
86
+ );
87
+ }
88
+ return {
89
+ caveats,
90
+ detected: {
91
+ avahiAvailable,
92
+ container,
93
+ dbusAvailable,
94
+ platform,
95
+ resolvedAvailable
96
+ },
97
+ reason: container ? "Linux container without usable host Avahi detected." : "Linux host without usable Avahi detected.",
98
+ recommended: "ciao",
99
+ title: "CIAO recommended"
100
+ };
101
+ }
102
+ if (platform === "darwin" || platform === "win32") {
103
+ return {
104
+ caveats,
105
+ detected: {
106
+ avahiAvailable,
107
+ container,
108
+ dbusAvailable,
109
+ platform,
110
+ resolvedAvailable
111
+ },
112
+ reason: platform === "darwin" ? "macOS host detected." : "Windows host detected.",
113
+ recommended: "ciao",
114
+ title: "CIAO recommended"
115
+ };
116
+ }
117
+ caveats.push(
118
+ "Host platform is uncommon for HomeKit. Verify multicast and mDNS behavior on this system."
119
+ );
120
+ return {
121
+ caveats,
122
+ detected: {
123
+ avahiAvailable,
124
+ container,
125
+ dbusAvailable,
126
+ platform,
127
+ resolvedAvailable
128
+ },
129
+ reason: `Unsupported or uncommon host platform: ${platform}.`,
130
+ recommended: "ciao",
131
+ title: "CIAO recommended"
79
132
  };
80
- const stringifyVersion = (version) => {
81
- const releaseVersionRegex = /(\d+)\.(\d+)\.(\d+)/;
82
- const devVersionRegex = /(\d+)\.(\d+)\.(\d+)-dev\.(\d+)/;
83
- const releaseVersionFound = releaseVersionRegex.test(version);
84
- const devVersionFound = devVersionRegex.test(version);
85
- let xyzVersion = '0.0.0';
86
- if (devVersionFound) {
87
- try {
88
- const match = devVersionRegex.exec(version);
89
- if (match) {
90
- xyzVersion = `0.${match[1]}${match[2]}${match[3]}.${match[4]}`;
91
- }
92
- else {
93
- log.debug('Could not match dev version');
94
- }
95
- }
96
- catch (e) {
97
- log.error(e);
98
- }
133
+ };
134
+ const _initServiceAPI = () => {
135
+ log.debug("Initialize Service API");
136
+ const serviceData = {
137
+ Camera: {
138
+ displayName: "Camera",
139
+ constructorName: "Camera"
140
+ },
141
+ BatteryService: {
142
+ nrchkbDisabledText: "BatteryService (deprecated, replaced by Battery)"
143
+ },
144
+ BridgeConfiguration: {
145
+ nrchkbDisabledText: "BridgeConfiguration (deprecated, unused)"
146
+ },
147
+ BridgingState: {
148
+ nrchkbDisabledText: "BridgingState (deprecated, unused)"
149
+ },
150
+ CameraControl: {
151
+ nrchkbDisabledText: "CameraControl (deprecated, replaced by Camera)",
152
+ nrchkbHiddenInService2: true
153
+ },
154
+ CameraEventRecordingManagement: {
155
+ nrchkbDisabledText: "CameraEventRecordingManagement (deprecated, replaced by CameraRecordingManagement)"
156
+ },
157
+ Relay: {
158
+ nrchkbDisabledText: "Relay (deprecated, replaced by CloudRelay)"
159
+ },
160
+ Slat: {
161
+ nrchkbDisabledText: "Slat (deprecated, replaced by Slats)"
162
+ },
163
+ TimeInformation: {
164
+ nrchkbDisabledText: "TimeInformation (deprecated, unused)"
165
+ },
166
+ TunneledBTLEAccessoryService: {
167
+ nrchkbDisabledText: "TunneledBTLEAccessoryService (deprecated, replaced by Tunnel)"
168
+ }
169
+ };
170
+ Object.values(import_hap_nodejs.Service).filter((service) => service.prototype instanceof import_hap_nodejs.Service).map((service) => {
171
+ const newService = import_hap_nodejs.Service.serialize(new service());
172
+ newService.displayName = service.name;
173
+ return newService;
174
+ }).forEach((serialized) => {
175
+ serviceData[serialized.displayName] = {
176
+ ...serviceData?.[serialized.displayName],
177
+ ...serialized
178
+ };
179
+ });
180
+ RED.httpAdmin.get(
181
+ "/nrchkb/service/types",
182
+ RED.auth.needsPermission("nrchkb.read"),
183
+ (_req, res) => {
184
+ res.setHeader("Content-Type", "application/json");
185
+ res.json(serviceData);
186
+ }
187
+ );
188
+ RED.httpAdmin.get(
189
+ "/nrchkb/plugins",
190
+ RED.auth.needsPermission("nrchkb.read"),
191
+ (_req, res) => {
192
+ (0, import_registry.registerNodeRedPlugins)(RED);
193
+ (0, import_embedded.registerEmbeddedPlugins)();
194
+ res.setHeader("Content-Type", "application/json");
195
+ res.json((0, import_registry.listPlugins)().map((plugin) => plugin.metadata));
196
+ }
197
+ );
198
+ RED.httpAdmin.get(
199
+ "/nrchkb/unifi/controllers/:id/test",
200
+ RED.auth.needsPermission("nrchkb.read"),
201
+ async (req, res) => {
202
+ const controller = RED.nodes.getNode(
203
+ req.params.id
204
+ );
205
+ if (!controller) {
206
+ res.status(404).json({
207
+ error: "UniFi controller config node was not found."
208
+ });
209
+ return;
99
210
  }
100
- else if (releaseVersionFound) {
101
- try {
102
- const match = releaseVersionRegex.exec(version);
103
- if (match) {
104
- xyzVersion = match[0];
105
- }
106
- else {
107
- log.debug('Could not match release version');
108
- }
109
- }
110
- catch (e) {
111
- log.error(e);
112
- }
211
+ try {
212
+ (0, import_ProtectDiscovery.assertProtectControllerConfig)(
213
+ controller,
214
+ controller.credentials ?? {}
215
+ );
216
+ await (0, import_ProtectDiscovery.discoverProtectCameras)(
217
+ controller,
218
+ controller.credentials ?? {}
219
+ );
220
+ res.json({ ok: true });
221
+ } catch (error) {
222
+ res.status(400).json({
223
+ error: error instanceof Error ? error.message : "Unable to connect to UniFi Protect."
224
+ });
113
225
  }
114
- else {
115
- log.debug('Bad version format');
116
- xyzVersion = '0.0.0';
226
+ }
227
+ );
228
+ RED.httpAdmin.get(
229
+ "/nrchkb/unifi/controllers/:id/protect/cameras",
230
+ RED.auth.needsPermission("nrchkb.read"),
231
+ async (req, res) => {
232
+ const controller = RED.nodes.getNode(
233
+ req.params.id
234
+ );
235
+ if (!controller) {
236
+ res.status(404).json({
237
+ error: "UniFi controller config node was not found."
238
+ });
239
+ return;
117
240
  }
118
- return xyzVersion;
119
- };
120
- const _initNRCHKBInfoAPI = () => {
121
- log.debug('Initialize NRCHKB Info API');
122
- log.debug(`Running version: ${version}`);
123
- const xyzVersion = stringifyVersion(version);
124
- log.debug(`Evaluated as: ${xyzVersion}`);
125
- const experimental = process.env.NRCHKB_EXPERIMENTAL === 'true';
126
- log.debug(`Running experimental: ${experimental}`);
127
- RED.httpAdmin.get('/nrchkb/info', RED.auth.needsPermission('nrchkb.read'), (_req, res) => {
128
- res.setHeader('Content-Type', 'application/json');
129
- res.json({
130
- version: xyzVersion,
131
- experimental
132
- });
241
+ try {
242
+ const cameras = await (0, import_ProtectDiscovery.discoverProtectCameras)(
243
+ controller,
244
+ controller.credentials ?? {}
245
+ );
246
+ res.json(
247
+ cameras.map((camera) => ({
248
+ value: camera.mac,
249
+ label: `${camera.name ?? camera.marketName ?? camera.mac} (${camera.mac})`,
250
+ id: camera.id,
251
+ mac: camera.mac,
252
+ state: camera.state
253
+ }))
254
+ );
255
+ } catch (error) {
256
+ res.status(400).json({
257
+ error: error instanceof Error ? error.message : "Unable to discover UniFi Protect cameras."
258
+ });
259
+ }
260
+ }
261
+ );
262
+ RED.httpAdmin.post(
263
+ "/nrchkb/migration/node",
264
+ RED.auth.needsPermission("nrchkb.write"),
265
+ (req, res) => {
266
+ if (!req.body || typeof req.body !== "object") {
267
+ res.status(400).json({
268
+ error: "Request body must be a Node-RED node object."
269
+ });
270
+ return;
271
+ }
272
+ res.setHeader("Content-Type", "application/json");
273
+ res.json((0, import_NodeMigration.migrateNode)(req.body));
274
+ }
275
+ );
276
+ RED.httpAdmin.post(
277
+ "/nrchkb/migration/flow",
278
+ RED.auth.needsPermission("nrchkb.write"),
279
+ (req, res) => {
280
+ if (!Array.isArray(req.body)) {
281
+ res.status(400).json({
282
+ error: "Request body must be an array of Node-RED nodes."
283
+ });
284
+ return;
285
+ }
286
+ res.setHeader("Content-Type", "application/json");
287
+ res.json((0, import_NodeMigration.migrateFlow)(req.body));
288
+ }
289
+ );
290
+ };
291
+ const stringifyVersion = (version2) => {
292
+ const releaseVersionRegex = /(\d+)\.(\d+)\.(\d+)/;
293
+ const devVersionRegex = /(\d+)\.(\d+)\.(\d+)-dev\.(\d+)/;
294
+ const releaseVersionFound = releaseVersionRegex.test(version2);
295
+ const devVersionFound = devVersionRegex.test(version2);
296
+ let xyzVersion = "0.0.0";
297
+ if (devVersionFound) {
298
+ try {
299
+ const match = devVersionRegex.exec(version2);
300
+ if (match) {
301
+ xyzVersion = `0.${match[1]}${match[2]}${match[3]}.${match[4]}`;
302
+ } else {
303
+ log.debug("Could not match dev version");
304
+ }
305
+ } catch (e) {
306
+ log.error(e);
307
+ }
308
+ } else if (releaseVersionFound) {
309
+ try {
310
+ const match = releaseVersionRegex.exec(version2);
311
+ if (match) {
312
+ xyzVersion = match[0];
313
+ } else {
314
+ log.debug("Could not match release version");
315
+ }
316
+ } catch (e) {
317
+ log.error(e);
318
+ }
319
+ } else {
320
+ log.debug("Bad version format");
321
+ xyzVersion = "0.0.0";
322
+ }
323
+ return xyzVersion;
324
+ };
325
+ const _initNRCHKBInfoAPI = () => {
326
+ log.debug("Initialize NRCHKB Info API");
327
+ log.debug(`Running version: ${version}`);
328
+ const xyzVersion = stringifyVersion(version);
329
+ log.debug(`Evaluated as: ${xyzVersion}`);
330
+ const experimental = process.env.NRCHKB_EXPERIMENTAL === "true";
331
+ log.debug(`Running experimental: ${experimental}`);
332
+ RED.httpAdmin.get(
333
+ "/nrchkb/info",
334
+ RED.auth.needsPermission("nrchkb.read"),
335
+ (_req, res) => {
336
+ res.setHeader("Content-Type", "application/json");
337
+ res.json({
338
+ version: xyzVersion,
339
+ experimental
133
340
  });
341
+ }
342
+ );
343
+ };
344
+ const _initAdvertiserRecommendationAPI = () => {
345
+ log.debug("Initialize Advertiser Recommendation API");
346
+ RED.httpAdmin.get(
347
+ "/nrchkb/advertiser/recommendation",
348
+ RED.auth.needsPermission("nrchkb.read"),
349
+ (_req, res) => {
350
+ res.setHeader("Content-Type", "application/json");
351
+ res.json(detectAdvertiserRecommendation());
352
+ }
353
+ );
354
+ };
355
+ const getHostPairedState = (hostNode) => {
356
+ const accessoryInfo = hostNode.host._accessoryInfo;
357
+ if (accessoryInfo) {
358
+ hostNode.paired = accessoryInfo.paired();
359
+ }
360
+ return !!hostNode.paired;
361
+ };
362
+ const _initBridgePairingAPI = () => {
363
+ log.debug("Initialize Bridge Pairing API");
364
+ RED.httpAdmin.get(
365
+ "/nrchkb/bridge/:id/pairing",
366
+ RED.auth.needsPermission("nrchkb.read"),
367
+ async (req, res) => {
368
+ const hostId = req.params.id;
369
+ const node = RED.nodes.getNode(hostId);
370
+ res.setHeader("Content-Type", "application/json");
371
+ if (!node || node.type !== "homekit-bridge" && node.type !== "homekit-standalone") {
372
+ res.status(404).json({
373
+ error: "Pairing host not found.",
374
+ paired: false,
375
+ published: false
376
+ });
377
+ return;
378
+ }
379
+ const bridgeName = node.config.bridgeName;
380
+ const pinCode = node.config.pinCode;
381
+ const formattedPinCode = (0, import_PairingQRCode.formatPinCodeForPairing)(pinCode);
382
+ const paired = getHostPairedState(node);
383
+ if (!node.published) {
384
+ res.status(409).json({
385
+ bridgeName,
386
+ formattedPinCode,
387
+ paired,
388
+ pinCode,
389
+ published: false,
390
+ status: "unpublished"
391
+ });
392
+ return;
393
+ }
394
+ if (paired) {
395
+ res.json({
396
+ bridgeName,
397
+ formattedPinCode,
398
+ paired: true,
399
+ pinCode,
400
+ published: true,
401
+ status: "paired"
402
+ });
403
+ return;
404
+ }
405
+ try {
406
+ const setupUri = node.host.setupURI();
407
+ const qrCodeDataUrl = await (0, import_PairingQRCode.createPairingQRCodeDataURL)(
408
+ setupUri,
409
+ pinCode
410
+ );
411
+ res.json({
412
+ bridgeName,
413
+ formattedPinCode,
414
+ paired: false,
415
+ pinCode,
416
+ published: true,
417
+ qrCodeDataUrl,
418
+ setupUri,
419
+ status: "unpaired"
420
+ });
421
+ } catch (error) {
422
+ log.error(
423
+ `Failed to generate pairing QR code for host ${hostId}: ${error}`
424
+ );
425
+ res.status(500).json({
426
+ bridgeName,
427
+ error: "Failed to generate pairing QR code.",
428
+ formattedPinCode,
429
+ paired: false,
430
+ pinCode,
431
+ published: true
432
+ });
433
+ }
434
+ }
435
+ );
436
+ };
437
+ const _initNRCHKBCustomCharacteristicsAPI = async () => {
438
+ const getCustomCharacteristics = async () => {
439
+ try {
440
+ const value = await import_Storage.Storage.loadCustomCharacteristics();
441
+ log.trace("loadCustomCharacteristics()");
442
+ log.trace(value);
443
+ if (Array.isArray(value)) {
444
+ return value;
445
+ } else {
446
+ log.debug(
447
+ "customCharacteristics is not Array, returning empty value"
448
+ );
449
+ return import_EveCharacteristics.default;
450
+ }
451
+ } catch (error) {
452
+ log.error(
453
+ `Failed to get customCharacteristics in nrchkbStorage due to ${error}`
454
+ );
455
+ return import_EveCharacteristics.default;
456
+ }
134
457
  };
135
- const _initNRCHKBCustomCharacteristicsAPI = () => __awaiter(void 0, void 0, void 0, function* () {
136
- const getCustomCharacteristics = () => __awaiter(void 0, void 0, void 0, function* () {
137
- try {
138
- const value = yield Storage_1.Storage.loadCustomCharacteristics();
139
- log.trace('loadCustomCharacteristics()');
140
- log.trace(value);
141
- if (Array.isArray(value)) {
142
- return value;
143
- }
144
- else {
145
- log.debug('customCharacteristics is not Array, returning empty value');
146
- return EveCharacteristics_1.default;
147
- }
458
+ const characteristicNameToKey = (name) => {
459
+ return name.replace(/\s+/g, "");
460
+ };
461
+ const toNumber = (value, optional = void 0) => {
462
+ const num = Number(value);
463
+ if (Number.isNaN(num)) {
464
+ return optional;
465
+ } else return num;
466
+ };
467
+ const rebindCharacteristicListeners = (characteristic, serviceNode) => {
468
+ characteristic.removeListener(
469
+ "get",
470
+ serviceNode.onCharacteristicGet
471
+ );
472
+ characteristic.removeListener(
473
+ "set",
474
+ serviceNode.onCharacteristicSet
475
+ );
476
+ characteristic.removeListener(
477
+ "change",
478
+ serviceNode.onCharacteristicChange
479
+ );
480
+ characteristic.on("get", serviceNode.onCharacteristicGet);
481
+ characteristic.on("set", serviceNode.onCharacteristicSet);
482
+ characteristic.on("change", serviceNode.onCharacteristicChange);
483
+ };
484
+ const refreshCustomCharacteristics = (customCharacteristics) => {
485
+ log.debug("Refreshing Custom Characteristics");
486
+ const customCharacteristicKeys = /* @__PURE__ */ new Set();
487
+ customCharacteristics.forEach(({ name, UUID, ...props }) => {
488
+ if (UUID && name) {
489
+ const key = characteristicNameToKey(name);
490
+ log.debug(
491
+ `Adding Custom Characteristic ${name} using key ${key}`
492
+ );
493
+ if (customCharacteristicKeys.has(key)) {
494
+ log.error(
495
+ `Cannot add ${name}. Another Custom Characteristic already defined using key ${key}`
496
+ );
497
+ return;
498
+ }
499
+ const validatedProps = { ...props };
500
+ if (validatedProps.validValues?.length === 0) {
501
+ validatedProps.validValues = void 0;
502
+ }
503
+ if (validatedProps.validValueRanges?.length) {
504
+ const [minRange, maxRange] = validatedProps.validValueRanges;
505
+ if (minRange === void 0 || maxRange === void 0) {
506
+ validatedProps.validValueRanges = void 0;
507
+ } else {
508
+ const minRangeNumber = Number(minRange);
509
+ const maxRangeNumber = Number(maxRange);
510
+ if (Number.isNaN(minRangeNumber) || Number.isNaN(maxRangeNumber)) {
511
+ validatedProps.validValueRanges = void 0;
512
+ } else {
513
+ validatedProps.validValueRanges = [
514
+ minRangeNumber,
515
+ maxRangeNumber
516
+ ];
517
+ }
148
518
  }
149
- catch (error) {
150
- log.error(`Failed to get customCharacteristics in nrchkbStorage due to ${error}`);
151
- return EveCharacteristics_1.default;
519
+ }
520
+ if (validatedProps.adminOnlyAccess?.length === 0) {
521
+ validatedProps.adminOnlyAccess = void 0;
522
+ }
523
+ if (validatedProps.minValue !== void 0) {
524
+ validatedProps.minValue = toNumber(
525
+ validatedProps.minValue
526
+ );
527
+ }
528
+ if (validatedProps.maxValue !== void 0) {
529
+ validatedProps.maxValue = toNumber(
530
+ validatedProps.maxValue
531
+ );
532
+ }
533
+ if (validatedProps.minStep !== void 0) {
534
+ validatedProps.minStep = toNumber(
535
+ validatedProps.minStep
536
+ );
537
+ }
538
+ class CustomCharacteristic extends import_hap_nodejs.Characteristic {
539
+ static {
540
+ this.UUID = UUID;
152
541
  }
153
- });
154
- const characteristicNameToKey = (name) => {
155
- return name.replace(' ', '');
156
- };
157
- const toNumber = (value, optional = undefined) => {
158
- const num = Number(value);
159
- if (Number.isNaN(num)) {
160
- return optional;
542
+ constructor() {
543
+ super(name, CustomCharacteristic.UUID, {
544
+ ...validatedProps,
545
+ perms: validatedProps.perms ?? [
546
+ import_hap_nodejs.Perms.PAIRED_READ,
547
+ import_hap_nodejs.Perms.PAIRED_WRITE,
548
+ import_hap_nodejs.Perms.NOTIFY
549
+ ]
550
+ });
551
+ this.value = this.getDefaultValue();
161
552
  }
162
- else
163
- return num;
164
- };
165
- const refreshCustomCharacteristics = (customCharacteristics) => {
166
- log.debug('Refreshing Custom Characteristics');
167
- const customCharacteristicKeys = [];
168
- customCharacteristics.forEach((_a) => {
169
- var _b, _c, _d, _e;
170
- var { name, UUID } = _a, props = __rest(_a, ["name", "UUID"]);
171
- if (!!UUID && !!name) {
172
- const key = characteristicNameToKey(name);
173
- log.debug(`Adding Custom Characteristic ${name} using key ${key}`);
174
- if (customCharacteristicKeys.includes(key)) {
175
- log.error(`Cannot add ${name}. Another Custom Characteristic already defined using key ${key}`);
176
- return;
177
- }
178
- const validatedProps = props;
179
- if (((_b = validatedProps.validValues) === null || _b === void 0 ? void 0 : _b.length) === 0) {
180
- validatedProps.validValues = undefined;
181
- }
182
- if (!((_c = validatedProps.validValueRanges) === null || _c === void 0 ? void 0 : _c[0]) ||
183
- !((_d = validatedProps.validValueRanges) === null || _d === void 0 ? void 0 : _d[1])) {
184
- validatedProps.validValueRanges = undefined;
185
- }
186
- if (((_e = validatedProps.adminOnlyAccess) === null || _e === void 0 ? void 0 : _e.length) === 0) {
187
- validatedProps.adminOnlyAccess = undefined;
188
- }
189
- if (validatedProps.minValue) {
190
- validatedProps.minValue = toNumber(validatedProps.minValue);
191
- }
192
- if (validatedProps.maxValue) {
193
- validatedProps.maxValue = toNumber(validatedProps.maxValue);
194
- }
195
- if (validatedProps.minStep) {
196
- validatedProps.minStep = toNumber(validatedProps.minStep);
197
- }
198
- class CustomCharacteristic extends hap_nodejs_1.Characteristic {
199
- constructor() {
200
- var _a;
201
- super(name, CustomCharacteristic.UUID, Object.assign(Object.assign({}, validatedProps), { perms: (_a = validatedProps.perms) !== null && _a !== void 0 ? _a : [
202
- "pr",
203
- "pw",
204
- "ev"
205
- ] }));
206
- this.value = this.getDefaultValue();
207
- }
208
- }
209
- CustomCharacteristic.UUID = UUID;
210
- Object.defineProperty(CustomCharacteristic, 'name', {
211
- value: key,
212
- configurable: true
213
- });
214
- Object.defineProperty(hap_nodejs_1.Characteristic, key, {
215
- value: CustomCharacteristic,
216
- configurable: true
217
- });
218
- customCharacteristicKeys.push(key);
219
- }
220
- });
221
- new Promise((resolve) => {
222
- const isRedInitialized = () => {
223
- try {
224
- RED.nodes.eachNode(() => {
225
- return;
226
- });
227
- resolve(true);
228
- }
229
- catch (_) {
230
- log.debug('Waiting for RED to be initialized');
231
- setTimeout(isRedInitialized, 1000);
232
- }
233
- };
234
- isRedInitialized();
235
- }).then(() => {
236
- RED.nodes.eachNode((node) => {
237
- if (node.type === 'homekit-service' ||
238
- node.type === 'homekit-service2') {
239
- const serviceNodeConfig = node;
240
- const serviceNode = RED.nodes.getNode(serviceNodeConfig.id);
241
- if ((serviceNode === null || serviceNode === void 0 ? void 0 : serviceNode.characteristicProperties) && serviceNode.service) {
242
- for (const key in serviceNode.characteristicProperties) {
243
- if (customCharacteristicKeys.includes(key)) {
244
- const characteristic = serviceNode.service
245
- .getCharacteristic(hap_nodejs_1.Characteristic[key])
246
- .setProps(serviceNode.characteristicProperties[key]);
247
- serviceNode.supported.push(key);
248
- characteristic.on('get', serviceNode.onCharacteristicGet);
249
- characteristic.on('set', serviceNode.onCharacteristicSet);
250
- characteristic.on('change', serviceNode.onCharacteristicChange);
251
- }
252
- }
253
- }
254
- }
255
- });
553
+ }
554
+ Object.defineProperty(CustomCharacteristic, "name", {
555
+ value: key,
556
+ configurable: true
557
+ });
558
+ Object.defineProperty(import_hap_nodejs.Characteristic, key, {
559
+ value: CustomCharacteristic,
560
+ configurable: true
561
+ });
562
+ customCharacteristicKeys.add(key);
563
+ }
564
+ });
565
+ registeredCustomCharacteristicKeys.forEach((key) => {
566
+ if (customCharacteristicKeys.has(key)) {
567
+ return;
568
+ }
569
+ log.debug(`Removing stale Custom Characteristic ${key}`);
570
+ delete import_hap_nodejs.Characteristic[key];
571
+ registeredCustomCharacteristicKeys.delete(key);
572
+ });
573
+ customCharacteristicKeys.forEach((key) => {
574
+ registeredCustomCharacteristicKeys.add(key);
575
+ });
576
+ new Promise((resolve) => {
577
+ const isRedInitialized = () => {
578
+ try {
579
+ RED.nodes.eachNode(() => {
580
+ return;
256
581
  });
582
+ resolve(true);
583
+ } catch (_) {
584
+ log.debug("Waiting for RED to be initialized");
585
+ setTimeout(isRedInitialized, 1e3);
586
+ }
257
587
  };
258
- log.debug('Initialize NRCHKBCustomCharacteristicsAPI');
259
- getCustomCharacteristics().then((value) => refreshCustomCharacteristics(value));
260
- RED.httpAdmin.get('/nrchkb/config', RED.auth.needsPermission('nrchkb.read'), (_req, res) => __awaiter(void 0, void 0, void 0, function* () {
261
- res.setHeader('Content-Type', 'application/json');
262
- res.json({
263
- customCharacteristics: yield getCustomCharacteristics()
264
- });
265
- }));
266
- RED.httpAdmin.post('/nrchkb/config', RED.auth.needsPermission('nrchkb.write'), (req, res) => __awaiter(void 0, void 0, void 0, function* () {
267
- const customCharacteristics = req.body.customCharacteristics || EveCharacteristics_1.default;
268
- Storage_1.Storage.saveCustomCharacteristics(customCharacteristics)
269
- .then(() => {
270
- res.sendStatus(200);
271
- refreshCustomCharacteristics(customCharacteristics);
272
- })
273
- .catch((error) => {
274
- log.error(error);
275
- res.sendStatus(500);
276
- });
277
- }));
278
- });
279
- const _initAccessoryAPI = () => {
280
- log.debug('Initialize Accessory API');
281
- const accessoryCategoriesData = {};
282
- Object.keys(HapCategories_1.default)
283
- .sort()
284
- .filter((x) => parseInt(x, 10) >= 0)
285
- .forEach((key) => {
286
- const keyNumber = key;
287
- accessoryCategoriesData[keyNumber] = HapCategories_1.default[keyNumber];
288
- });
289
- RED.httpAdmin.get('/nrchkb/accessory/categories', RED.auth.needsPermission('nrchkb.read'), (_req, res) => {
290
- res.setHeader('Content-Type', 'application/json');
291
- res.json(accessoryCategoriesData);
588
+ isRedInitialized();
589
+ }).then(() => {
590
+ RED.nodes.eachNode((node) => {
591
+ if (node.type === "homekit-service" || node.type === "homekit-service2") {
592
+ const serviceNodeConfig = node;
593
+ const serviceNode = RED.nodes.getNode(
594
+ serviceNodeConfig.id
595
+ );
596
+ if (serviceNode?.characteristicProperties && serviceNode.service) {
597
+ for (const key in serviceNode.characteristicProperties) {
598
+ if (customCharacteristicKeys.has(key)) {
599
+ const characteristic = serviceNode.service.getCharacteristic(import_hap_nodejs.Characteristic[key]).setProps(
600
+ serviceNode.characteristicProperties[key]
601
+ );
602
+ serviceNode.supported.add(key);
603
+ rebindCharacteristicListeners(
604
+ characteristic,
605
+ serviceNode
606
+ );
607
+ }
608
+ }
609
+ }
610
+ }
292
611
  });
612
+ });
293
613
  };
294
- const init = () => {
295
- _initServiceAPI();
296
- _initNRCHKBInfoAPI();
297
- _initAccessoryAPI();
298
- if (process.env.NRCHKB_EXPERIMENTAL === 'true') {
299
- _initNRCHKBCustomCharacteristicsAPI().then();
300
- }
301
- };
302
- return {
303
- init,
304
- stringifyVersion
305
- };
614
+ log.debug("Initialize NRCHKBCustomCharacteristicsAPI");
615
+ getCustomCharacteristics().then(
616
+ (value) => refreshCustomCharacteristics(value)
617
+ );
618
+ RED.httpAdmin.get(
619
+ "/nrchkb/config",
620
+ RED.auth.needsPermission("nrchkb.read"),
621
+ async (_req, res) => {
622
+ res.setHeader("Content-Type", "application/json");
623
+ res.json({
624
+ customCharacteristics: await getCustomCharacteristics()
625
+ });
626
+ }
627
+ );
628
+ RED.httpAdmin.post(
629
+ "/nrchkb/config",
630
+ RED.auth.needsPermission("nrchkb.write"),
631
+ async (req, res) => {
632
+ const customCharacteristics = req.body.customCharacteristics || import_EveCharacteristics.default;
633
+ import_Storage.Storage.saveCustomCharacteristics(customCharacteristics).then(() => {
634
+ res.sendStatus(200);
635
+ refreshCustomCharacteristics(customCharacteristics);
636
+ }).catch((error) => {
637
+ log.error(error);
638
+ res.sendStatus(500);
639
+ });
640
+ }
641
+ );
642
+ };
643
+ const _initAccessoryAPI = () => {
644
+ log.debug("Initialize Accessory API");
645
+ const accessoryCategoriesData = {};
646
+ Object.keys(import_HapCategories.default).sort().filter((x) => parseInt(x, 10) >= 0).forEach((key) => {
647
+ const keyNumber = key;
648
+ accessoryCategoriesData[keyNumber] = import_HapCategories.default[keyNumber];
649
+ });
650
+ RED.httpAdmin.get(
651
+ "/nrchkb/accessory/categories",
652
+ RED.auth.needsPermission("nrchkb.read"),
653
+ (_req, res) => {
654
+ res.setHeader("Content-Type", "application/json");
655
+ res.json(accessoryCategoriesData);
656
+ }
657
+ );
658
+ };
659
+ const init = () => {
660
+ _initServiceAPI();
661
+ _initNRCHKBInfoAPI();
662
+ _initAdvertiserRecommendationAPI();
663
+ _initBridgePairingAPI();
664
+ _initAccessoryAPI();
665
+ _initNRCHKBCustomCharacteristicsAPI().then();
666
+ };
667
+ return {
668
+ detectAdvertiserRecommendation,
669
+ init,
670
+ stringifyVersion
671
+ };
306
672
  };