node-red-contrib-homekit-bridged 1.7.0-dev.8 → 1.7.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.
@@ -22,7 +22,7 @@ const HostType_1 = __importDefault(require("./types/HostType"));
22
22
  module.exports = (RED, hostType) => {
23
23
  const MdnsUtils = require('./utils/MdnsUtils')();
24
24
  const init = function (config) {
25
- var _a, _b, _c, _d, _e;
25
+ var _a, _b, _c, _d, _e, _f;
26
26
  const self = this;
27
27
  const log = (0, logger_1.logger)('NRCHKB', 'HAPHostNode', config.bridgeName, self);
28
28
  self.hostType = hostType;
@@ -36,7 +36,8 @@ module.exports = (RED, hostType) => {
36
36
  if (preload_1.default.parse(config.firmwareRev) == null) {
37
37
  config.firmwareRev = new semver_1.SemVer('0.0.0');
38
38
  }
39
- if (config.customMdnsConfig) {
39
+ if (!((_a = config.bind) === null || _a === void 0 ? void 0 : _a.length) && config.customMdnsConfig) {
40
+ log.error('Custom mdns config is deprecated, use bind instead!');
40
41
  self.mdnsConfig = {};
41
42
  if (MdnsUtils.checkMulticast(config.mdnsMulticast)) {
42
43
  self.mdnsConfig.multicast = config.mdnsMulticast;
@@ -45,13 +46,13 @@ module.exports = (RED, hostType) => {
45
46
  self.mdnsConfig.interface = config.mdnsInterface;
46
47
  }
47
48
  if (MdnsUtils.checkPort(config.mdnsPort)) {
48
- self.mdnsConfig.port = parseInt((_a = config.mdnsPort) === null || _a === void 0 ? void 0 : _a.toString());
49
+ self.mdnsConfig.port = parseInt((_b = config.mdnsPort) === null || _b === void 0 ? void 0 : _b.toString());
49
50
  }
50
51
  if (MdnsUtils.checkIp(config.mdnsIp)) {
51
52
  self.mdnsConfig.ip = config.mdnsIp;
52
53
  }
53
54
  if (MdnsUtils.checkTtl(config.mdnsTtl)) {
54
- self.mdnsConfig.ttl = parseInt((_b = config.mdnsTtl) === null || _b === void 0 ? void 0 : _b.toString());
55
+ self.mdnsConfig.ttl = parseInt((_c = config.mdnsTtl) === null || _c === void 0 ? void 0 : _c.toString());
55
56
  }
56
57
  if (MdnsUtils.checkLoopback(config.mdnsLoopback)) {
57
58
  self.mdnsConfig.loopback = config.mdnsLoopback;
@@ -81,7 +82,7 @@ module.exports = (RED, hostType) => {
81
82
  self.host = new hap_nodejs_1.Accessory(self.name, hostUUID);
82
83
  }
83
84
  self.publish = function () {
84
- var _a, _b, _c;
85
+ var _a, _b, _c, _d;
85
86
  if (self.hostType == HostType_1.default.BRIDGE) {
86
87
  log.debug(`Publishing ${hostTypeName} with pin code ${self.config.pinCode} and ${self.host.bridgedAccessories.length} accessories`);
87
88
  }
@@ -99,6 +100,15 @@ module.exports = (RED, hostType) => {
99
100
  oldPinCode = oldPinCode.replace(/-/g, '');
100
101
  oldPinCode = `${oldPinCode.slice(0, 3)}-${oldPinCode.slice(3, 5)}-${oldPinCode.slice(5, 8)}`;
101
102
  }
103
+ let bind;
104
+ if (((_c = self.config.bind) === null || _c === void 0 ? void 0 : _c.length) && self.config.bindType) {
105
+ if (self.config.bindType == 'str') {
106
+ bind = self.config.bind;
107
+ }
108
+ else if (self.config.bindType == 'json') {
109
+ bind = JSON.parse(self.config.bind);
110
+ }
111
+ }
102
112
  self.host.publish({
103
113
  username: self.bridgeUsername,
104
114
  port: self.config.port && !isNaN(self.config.port)
@@ -107,7 +117,8 @@ module.exports = (RED, hostType) => {
107
117
  pincode: oldPinCode,
108
118
  category: self.accessoryCategory,
109
119
  mdns: self.mdnsConfig,
110
- advertiser: (_c = self.config.advertiser) !== null && _c !== void 0 ? _c : "bonjour-hap",
120
+ bind: bind,
121
+ advertiser: (_d = self.config.advertiser) !== null && _d !== void 0 ? _d : "bonjour-hap",
111
122
  }, self.config.allowInsecureRequest);
112
123
  self.published = true;
113
124
  return true;
@@ -141,9 +152,9 @@ module.exports = (RED, hostType) => {
141
152
  .setCharacteristic(hap_nodejs_1.Characteristic.Manufacturer, self.config.manufacturer)
142
153
  .setCharacteristic(hap_nodejs_1.Characteristic.SerialNumber, self.config.serialNo)
143
154
  .setCharacteristic(hap_nodejs_1.Characteristic.Model, self.config.model)
144
- .setCharacteristic(hap_nodejs_1.Characteristic.FirmwareRevision, (_c = self.config.firmwareRev) === null || _c === void 0 ? void 0 : _c.toString())
145
- .setCharacteristic(hap_nodejs_1.Characteristic.HardwareRevision, (_d = self.config.hardwareRev) === null || _d === void 0 ? void 0 : _d.toString())
146
- .setCharacteristic(hap_nodejs_1.Characteristic.SoftwareRevision, (_e = self.config.softwareRev) === null || _e === void 0 ? void 0 : _e.toString());
155
+ .setCharacteristic(hap_nodejs_1.Characteristic.FirmwareRevision, (_d = self.config.firmwareRev) === null || _d === void 0 ? void 0 : _d.toString())
156
+ .setCharacteristic(hap_nodejs_1.Characteristic.HardwareRevision, (_e = self.config.hardwareRev) === null || _e === void 0 ? void 0 : _e.toString())
157
+ .setCharacteristic(hap_nodejs_1.Characteristic.SoftwareRevision, (_f = self.config.softwareRev) === null || _f === void 0 ? void 0 : _f.toString());
147
158
  };
148
159
  const macify = (nodeId) => {
149
160
  if (nodeId) {
@@ -95,8 +95,9 @@ module.exports = (RED) => {
95
95
  : self.config.accessoryId;
96
96
  self.hostNode = RED.nodes.getNode(hostId);
97
97
  if (!self.hostNode) {
98
- log.error('Host Node not found', false);
99
- throw new NRCHKBError_1.default('Host Node not found');
98
+ const message = `Host node ${self.config.hostType == HostType_1.default.BRIDGE ? 'Bridge' : 'Standalone Accessory'} ${hostId} not found`;
99
+ log.error(message, false);
100
+ throw new NRCHKBError_1.default(message);
100
101
  }
101
102
  self.childNodes = [];
102
103
  self.childNodes.push(self);
@@ -95,8 +95,9 @@ module.exports = (RED) => {
95
95
  : self.config.accessoryId;
96
96
  self.hostNode = RED.nodes.getNode(hostId);
97
97
  if (!self.hostNode) {
98
- log.error('Host Node not found', false);
99
- throw new NRCHKBError_1.default('Host Node not found');
98
+ const message = `Host node ${self.config.hostType == HostType_1.default.BRIDGE ? 'Bridge' : 'Standalone Accessory'} ${hostId} not found`;
99
+ log.error(message, false);
100
+ throw new NRCHKBError_1.default(message);
100
101
  }
101
102
  self.childNodes = [];
102
103
  self.childNodes.push(self);
@@ -13,6 +13,8 @@ type HAPHostConfigType = NodeDef & {
13
13
  firmwareRev: SemVer;
14
14
  hardwareRev: SemVer;
15
15
  softwareRev: SemVer;
16
+ bind?: string;
17
+ bindType?: 'json' | 'str';
16
18
  customMdnsConfig: boolean;
17
19
  mdnsMulticast: boolean;
18
20
  mdnsInterface: string;
@@ -1,11 +1,11 @@
1
- import { MulticastOptions } from 'bonjour-hap';
2
1
  import { Accessory, Categories } from 'hap-nodejs';
2
+ import BonjourMulticastOptions from './hap-nodejs/BonjourMulticastOptions';
3
3
  import HAPHostConfigType from './HAPHostConfigType';
4
4
  import HostType from './HostType';
5
5
  import NodeType from './NodeType';
6
6
  type HAPHostNodeType = NodeType & {
7
7
  config: HAPHostConfigType;
8
- mdnsConfig: MulticastOptions;
8
+ mdnsConfig: BonjourMulticastOptions;
9
9
  accessoryCategory: Categories;
10
10
  published: boolean;
11
11
  bridgeUsername: string;
@@ -0,0 +1,10 @@
1
+ type BonjourMulticastOptions = {
2
+ multicast?: boolean;
3
+ interface?: string;
4
+ port?: number;
5
+ ip?: string;
6
+ ttl?: number;
7
+ loopback?: boolean;
8
+ reuseAddr?: boolean;
9
+ };
10
+ export default BonjourMulticastOptions;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -55,9 +55,14 @@
55
55
  <label for="node-config-input-allowMessagePassthrough" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-step-forward"></i> Allow Message Passthrough</label>
56
56
  </div>
57
57
  <hr>
58
+ <div class="form-row">
59
+ <label for="node-config-input-bind"><i class="fa fa-filter"></i> Bind</label>
60
+ <input type="text" id="node-config-input-bind" placeholder="::">
61
+ <input type="hidden" id="node-config-input-bindType">
62
+ </div>
58
63
  <div class="form-row">
59
64
  <input type="checkbox" id="node-config-input-customMdnsConfig" style="display: inline-block; width: auto; vertical-align: top;">
60
- <label for="node-config-input-customMdnsConfig" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-filter"></i> Custom MDNS Configuration</label>
65
+ <label for="node-config-input-customMdnsConfig" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-filter"></i> Custom MDNS Configuration (DEPRECATED, use Bind)</label>
61
66
  </div>
62
67
  <div id="mdns-configuration" style="display: none;">
63
68
  <div class="form-row">
@@ -108,7 +113,60 @@
108
113
  <li><strong>Software Revision</strong>: Should be a version number string in the form of <em>MAJOR.MINOR.REVISION</em> e.g. <em>1.2.0</em>. Other types of strings are ignored and won't be displayed.</li>
109
114
  <li><strong>Name</strong>: If you intend to simulate a rocket, then why don&#39;t you call it <em>Rocket</em>. Name should be maximum 64 chars long and not contain <pre>.</pre></li>
110
115
  <li><strong>Allow Message Passthrough</strong>: If you allow then message from node input will be sent to node output.</li>
111
- <li><strong>Custom MDNS Configuration</strong>: Check if you would like to use custom mdns configuration.</li>
116
+ <li><strong>Bind</strong>: Defines the host where the HAP server will be bound to. </li>
117
+ <ul>
118
+ <li>
119
+ Defines the host where the HAP server will be bound to.
120
+ When undefined the HAP server will bind to all available interfaces
121
+ (see https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback).
122
+
123
+ This property accepts a mixture of IPAddresses and network interface names.
124
+ Depending on the mixture of supplied addresses/names hap-nodejs will bind differently.
125
+
126
+ It is advised to not just bind to a specific address, but specifying the interface name
127
+ in oder to bind on all address records (and ip version) available.
128
+
129
+ HAP-NodeJS (or the underlying ciao library) will not report about misspelled interface names,
130
+ as it could be that the interface is currently just down and will come up later.
131
+
132
+ Here are a few examples:
133
+ - bind: "::"
134
+ Pretty much identical to not specifying anything, as most systems (with ipv6 support)
135
+ will default to the unspecified ipv6 address (with dual stack support).
136
+
137
+ - bind: "0.0.0.0"
138
+ Binding TCP socket to the unspecified ipv4 address.
139
+ The mdns advertisement will exclude any ipv6 address records.
140
+
141
+ - bind: ["en0", "lo0"]
142
+ The mdns advertising will advertise all records of the en0 and loopback interface (if available) and
143
+ will also react to address changes on those interfaces.
144
+ In order for the HAP server to accept all those address records (which may contain ipv6 records)
145
+ it will bind on the unspecified ipv6 address "::" (assuming dual stack is supported).
146
+
147
+ - bind: ["en0", "lo0", "0.0.0.0"]
148
+ Same as above, only that the HAP server will bind on the unspecified ipv4 address "0.0.0.0".
149
+ The mdns advertisement will not advertise any ipv6 records.
150
+
151
+ - bind: "169.254.104.90"
152
+ This will bind the HAP server to the address 0.0.0.0.
153
+ The mdns advertisement will only advertise the A record 169.254.104.90.
154
+ If the given network interface of that address encounters an ip address change (to a different address),
155
+ the mdns advertisement will result in not advertising an address at all.
156
+ So it is advised to specify an interface name instead of a specific address.
157
+ This is identical with ipv6 addresses.
158
+
159
+ - bind: ["169.254.104.90", "192.168.1.4"]
160
+ As the HAP TCP socket can only bind to a single address, when specifying multiple ip addresses
161
+ the HAP server will bind to the unspecified ip address (0.0.0.0 if only ipv4 addresses are supplied,
162
+ :: if a mixture or only ipv6 addresses are supplied).
163
+ The mdns advertisement will only advertise the specified ip addresses.
164
+ If the given network interface of that address encounters an ip address change (to different addresses),
165
+ the mdns advertisement will result in not advertising an address at all.
166
+ So it is advised to specify an interface name instead of a specific address.
167
+ </li>
168
+ </ul>
169
+ <li><strong>Custom MDNS Configuration (DEPRECATED, use Bind)</strong>: Check if you would like to use custom mdns configuration.</li>
112
170
  <ul>
113
171
  <li><strong>Multicast</strong>: Use udp multicasting. Optional. Default true.</li>
114
172
  <li><strong>Multicast Interface IP:</strong>: Explicitly specify a network interface. Optional. Defaults to all.</li>
@@ -185,37 +243,42 @@
185
243
  required: false,
186
244
  validate: versionValidator,
187
245
  },
246
+ bind: {
247
+ value: undefined,
248
+ required: false,
249
+ },
250
+ bindType: {
251
+ value: undefined,
252
+ required: false,
253
+ },
188
254
  customMdnsConfig: {
189
255
  value: false,
190
256
  required: false,
257
+ validate: (value) => !value
191
258
  },
192
259
  mdnsMulticast: {
193
260
  value: true,
194
- required: false,
261
+ required: false
195
262
  },
196
263
  mdnsInterface: {
197
- required: false,
264
+ required: false
198
265
  },
199
266
  mdnsPort: {
200
- required: false,
201
- validate: function (value) {
202
- if (value) return RED.validators.port(value)
203
- else return true
204
- },
267
+ required: false
205
268
  },
206
269
  mdnsIp: {
207
- required: false,
270
+ required: false
208
271
  },
209
272
  mdnsTtl: {
210
- required: false,
273
+ required: false
211
274
  },
212
275
  mdnsLoopback: {
213
276
  value: true,
214
- required: false,
277
+ required: false
215
278
  },
216
279
  mdnsReuseAddr: {
217
280
  value: true,
218
- required: false,
281
+ required: false
219
282
  },
220
283
  allowMessagePassthrough: {
221
284
  value: true,
@@ -231,7 +294,7 @@
231
294
  oneditprepare: function () {
232
295
  if (!validatePinCode(this.pinCode)) {
233
296
  this.pinCode = generatePinCode()
234
- $("#node-config-input-pinCode").val(this.pinCode)
297
+ $('#node-config-input-pinCode').val(this.pinCode)
235
298
  }
236
299
 
237
300
  if (typeof this.allowMessagePassthrough == 'undefined') {
@@ -256,6 +319,12 @@
256
319
  }
257
320
  })
258
321
  .change()
322
+
323
+ $('#node-config-input-bind').typedInput({
324
+ typeField: '#node-config-input-bindType',
325
+ default: 'str',
326
+ types: ['json', 'str']
327
+ })
259
328
  },
260
329
  })
261
330
  </script>
@@ -7,6 +7,7 @@
7
7
  border-radius: .25rem;
8
8
  margin-right: 60px;
9
9
  }
10
+
10
11
  .alert-warning {
11
12
  color: #856404;
12
13
  background-color: #fff3cd;
@@ -56,55 +57,55 @@
56
57
 
57
58
  $('#node-input-customCharacteristics-container').css('min-height', '150px').css('min-width', '550px').editableList({
58
59
  addItem: function (container, i, opt) {
59
- $(".properties-accordion").accordion("option", "active", false);
60
+ $('.properties-accordion').accordion('option', 'active', false)
60
61
 
61
62
  const {name, UUID, ...props} = opt
62
63
 
63
64
  container.css({
64
65
  overflow: 'hidden',
65
66
  whiteSpace: 'nowrap'
66
- });
67
+ })
67
68
 
68
- const fragment = document.createDocumentFragment();
69
- const row1 = $('<div/>', {style: "display:flex; align-items: center;"}).appendTo(fragment);
70
- const row2 = $('<div/>', {style: "display:flex; margin-top:8px; align-items: center;"}).appendTo(fragment);
71
- const row3 = $('<div/>', {style: "margin-top:8px; align-items: center;"}).appendTo(fragment);
69
+ const fragment = document.createDocumentFragment()
70
+ const row1 = $('<div/>', {style: 'display:flex; align-items: center;'}).appendTo(fragment)
71
+ const row2 = $('<div/>', {style: 'display:flex; margin-top:8px; align-items: center;'}).appendTo(fragment)
72
+ const row3 = $('<div/>', {style: 'margin-top:8px; align-items: center;'}).appendTo(fragment)
72
73
 
73
74
  $('<div/>', {
74
- style: "display:inline-block;text-align:left; width:120px; padding-left:10px; box-sizing:border-box;",
75
- required: "required"
75
+ style: 'display:inline-block;text-align:left; width:120px; padding-left:10px; box-sizing:border-box;',
76
+ required: 'required'
76
77
  })
77
78
  .text('UUID ')
78
- .appendTo(row1);
79
- $('<input/>', {class: "property-uuid", type: "text"})
79
+ .appendTo(row1)
80
+ $('<input/>', {class: 'property-uuid', type: 'text'})
80
81
  .val(UUID ? UUID : uuidv4)
81
82
  .appendTo(row1)
82
83
 
83
84
  $('<div/>', {
84
- style: "display:inline-block;text-align:left; width:120px; padding-left:10px; box-sizing:border-box;",
85
- required: "required"
85
+ style: 'display:inline-block;text-align:left; width:120px; padding-left:10px; box-sizing:border-box;',
86
+ required: 'required'
86
87
  })
87
88
  .text('Name ')
88
- .appendTo(row2);
89
- $('<input/>', {class: "property-name", type: "text"})
89
+ .appendTo(row2)
90
+ $('<input/>', {class: 'property-name', type: 'text'})
90
91
  .val(name)
91
92
  .appendTo(row2)
92
93
 
93
- const row3_properties_accordion = $('<div/>', {class: "properties-accordion"})
94
- .appendTo(row3);
94
+ const row3_properties_accordion = $('<div/>', {class: 'properties-accordion'})
95
+ .appendTo(row3)
95
96
 
96
- $('<h3/>').text('Properties').appendTo(row3_properties_accordion);
97
+ $('<h3/>').text('Properties').appendTo(row3_properties_accordion)
97
98
 
98
- const row3_properties = $('<div/>', {class: "properties"}).appendTo(row3_properties_accordion);
99
+ const row3_properties = $('<div/>', {class: 'properties'}).appendTo(row3_properties_accordion)
99
100
 
100
- const formatRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
101
+ const formatRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
101
102
  $('<label/>', {
102
- class: "form-row",
103
- htmlFor: "property-format"
104
- }).text('Format *').appendTo(formatRow);
103
+ class: 'form-row',
104
+ htmlFor: 'property-format'
105
+ }).text('Format *').appendTo(formatRow)
105
106
  const formatInput = $('<select/>', {
106
- class: "property-format",
107
- required: "required"
107
+ class: 'property-format',
108
+ required: 'required'
108
109
  }).appendTo(formatRow)
109
110
  $('<option/>').val(undefined).text('Choose...').appendTo(formatInput)
110
111
  $('<option/>').val('bool').text('BOOL').appendTo(formatInput)
@@ -121,9 +122,9 @@
121
122
  $('<option/>').val('dict').text('DICTIONARY').appendTo(formatInput)
122
123
  formatInput.val(props.format)
123
124
 
124
- const unitRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
125
- $('<label/>', {class: "form-row", htmlFor: "property-unit"}).text('Unit').appendTo(unitRow)
126
- const unitSelect = $('<select/>', {class: "property-unit"}).appendTo(unitRow)
125
+ const unitRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
126
+ $('<label/>', {class: 'form-row', htmlFor: 'property-unit'}).text('Unit').appendTo(unitRow)
127
+ const unitSelect = $('<select/>', {class: 'property-unit'}).appendTo(unitRow)
127
128
  $('<option/>').val(undefined).text('Choose...').appendTo(unitSelect)
128
129
  $('<option/>').val('celsius').text('CELSIUS').appendTo(unitSelect)
129
130
  $('<option/>').val('percentage').text('PERCENTAGE').appendTo(unitSelect)
@@ -132,14 +133,14 @@
132
133
  $('<option/>').val('seconds').text('SECONDS').appendTo(unitSelect)
133
134
  unitSelect.val(props.unit)
134
135
 
135
- const permsRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
136
+ const permsRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
136
137
  $('<label/>', {
137
- class: "form-row",
138
- htmlFor: "property-perms"
138
+ class: 'form-row',
139
+ htmlFor: 'property-perms'
139
140
  }).text('Permissions').appendTo(permsRow)
140
141
  const permsSelect = $('<select/>', {
141
- class: "property-perms",
142
- multiple: "multiple"
142
+ class: 'property-perms',
143
+ multiple: 'multiple'
143
144
  }).appendTo(permsRow)
144
145
  $('<option/>').val('pr').text('PAIRED_READ / READ').appendTo(permsSelect)
145
146
  $('<option/>').val('pw').text('PAIRED_WRITE / WRITE').appendTo(permsSelect)
@@ -150,94 +151,94 @@
150
151
  $('<option/>').val('wr').text('WRITE_RESPONSE').appendTo(permsSelect)
151
152
  permsSelect.val(props.perms)
152
153
 
153
- const evRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
154
+ const evRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
154
155
  $('<label/>', {
155
- class: "form-row",
156
- htmlFor: "property-ev"
156
+ class: 'form-row',
157
+ htmlFor: 'property-ev'
157
158
  }).text('Event Notifications').appendTo(evRow)
158
159
  $('<input/>', {
159
- class: "property-ev",
160
- type: "checkbox",
160
+ class: 'property-ev',
161
+ type: 'checkbox',
161
162
  checked: props.ev
162
163
  }).appendTo(evRow)
163
164
 
164
- const descriptionRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
165
+ const descriptionRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
165
166
  $('<label/>', {
166
- class: "form-row",
167
- htmlFor: "property-description"
167
+ class: 'form-row',
168
+ htmlFor: 'property-description'
168
169
  }).text('Description').appendTo(descriptionRow)
169
170
  $('<input/>', {
170
- class: "property-description",
171
+ class: 'property-description',
171
172
  type: 'text'
172
173
  }).appendTo(descriptionRow).val(props.description)
173
174
 
174
- const minValueRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
175
+ const minValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
175
176
  $('<label/>', {
176
- class: "form-row",
177
- htmlFor: "property-minValue"
177
+ class: 'form-row',
178
+ htmlFor: 'property-minValue'
178
179
  }).text('Minimum Value').appendTo(minValueRow)
179
180
  $('<input/>', {
180
- class: "property-minValue",
181
+ class: 'property-minValue',
181
182
  type: 'number'
182
183
  }).appendTo(minValueRow).val(props.minValue)
183
184
 
184
- const maxValueRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
185
+ const maxValueRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
185
186
  $('<label/>', {
186
- class: "form-row",
187
- htmlFor: "property-maxValue"
187
+ class: 'form-row',
188
+ htmlFor: 'property-maxValue'
188
189
  }).text('Maximum Value').appendTo(maxValueRow)
189
190
  $('<input/>', {
190
- class: "property-maxValue",
191
+ class: 'property-maxValue',
191
192
  type: 'number'
192
193
  }).appendTo(maxValueRow).val(props.maxValue)
193
194
 
194
- const minStepRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
195
+ const minStepRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
195
196
  $('<label/>', {
196
- class: "form-row",
197
- htmlFor: "property-minStep"
197
+ class: 'form-row',
198
+ htmlFor: 'property-minStep'
198
199
  }).text('Minimum Step').appendTo(minStepRow)
199
200
  $('<input/>', {
200
- class: "property-minStep",
201
+ class: 'property-minStep',
201
202
  type: 'number'
202
203
  }).appendTo(minStepRow).val(props.minStep)
203
204
 
204
- const maxLenRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
205
+ const maxLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
205
206
  $('<label/>', {
206
- class: "form-row",
207
- htmlFor: "property-maxLen"
207
+ class: 'form-row',
208
+ htmlFor: 'property-maxLen'
208
209
  }).text('Maximum Length').appendTo(maxLenRow)
209
210
  $('<input/>', {
210
- class: "property-maxLen",
211
+ class: 'property-maxLen',
211
212
  type: 'number'
212
213
  }).appendTo(maxLenRow).val(props.maxLen)
213
214
 
214
- const maxDataLenRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
215
+ const maxDataLenRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
215
216
  $('<label/>', {
216
- class: "form-row",
217
- htmlFor: "property-maxDataLen"
217
+ class: 'form-row',
218
+ htmlFor: 'property-maxDataLen'
218
219
  }).text('Maximum Data Length').appendTo(maxDataLenRow)
219
220
  $('<input/>', {
220
- class: "property-maxDataLen",
221
+ class: 'property-maxDataLen',
221
222
  type: 'number'
222
223
  }).appendTo(maxDataLenRow).val(props.maxDataLen)
223
224
 
224
- const validValuesRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
225
+ const validValuesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
225
226
  $('<label/>', {
226
- class: "form-row",
227
- htmlFor: "property-validValues"
227
+ class: 'form-row',
228
+ htmlFor: 'property-validValues'
228
229
  }).text('Valid Values').appendTo(validValuesRow)
229
230
  $('<input/>', {
230
- class: "property-validValues",
231
+ class: 'property-validValues',
231
232
  type: 'text'
232
233
  }).appendTo(validValuesRow).val(props.validValues)
233
234
 
234
- const validValueRangesRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
235
+ const validValueRangesRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
235
236
  const validValueRangesLabel = $('<label/>', {
236
- class: "form-row property-validValueRanges-label",
237
- htmlFor: "property-validValueRanges"
237
+ class: 'form-row property-validValueRanges-label',
238
+ htmlFor: 'property-validValueRanges'
238
239
  }).text('Valid Value Ranges: ').appendTo(validValueRangesRow)
239
240
  const validValueRangesSlider = $('<div/>', {
240
- class: "property-validValueRanges",
241
+ class: 'property-validValueRanges',
241
242
  type: 'text'
242
243
  }).appendTo(validValueRangesRow)
243
244
 
@@ -247,19 +248,19 @@
247
248
  max: 500,
248
249
  values: props.validValueRanges,
249
250
  slide: function (event, ui) {
250
- validValueRangesLabel.text("Valid Value Ranges: [" + ui.values[0] + ", " + ui.values[1] + "]");
251
+ validValueRangesLabel.text('Valid Value Ranges: [' + ui.values[0] + ', ' + ui.values[1] + ']')
251
252
  }
252
- });
253
- validValueRangesLabel.text("Valid Value Ranges: [" + validValueRangesSlider.slider("values", 0) + ", " + validValueRangesSlider.slider("values", 1) + "]");
253
+ })
254
+ validValueRangesLabel.text('Valid Value Ranges: [' + validValueRangesSlider.slider('values', 0) + ', ' + validValueRangesSlider.slider('values', 1) + ']')
254
255
 
255
- const adminOnlyAccessRow = $('<div/>', {class: "form-row"}).appendTo(row3_properties);
256
+ const adminOnlyAccessRow = $('<div/>', {class: 'form-row'}).appendTo(row3_properties)
256
257
  $('<label/>', {
257
- class: "form-row",
258
- htmlFor: "property-adminOnlyAccess"
258
+ class: 'form-row',
259
+ htmlFor: 'property-adminOnlyAccess'
259
260
  }).text('Admin Only Access').appendTo(adminOnlyAccessRow)
260
261
  const adminOnlyAccessSelect = $('<select/>', {
261
- class: "property-adminOnlyAccess",
262
- multiple: "multiple"
262
+ class: 'property-adminOnlyAccess',
263
+ multiple: 'multiple'
263
264
  }).appendTo(adminOnlyAccessRow)
264
265
  $('<option/>').val(0).text('READ').appendTo(adminOnlyAccessSelect)
265
266
  $('<option/>').val(1).text('WRITE').appendTo(adminOnlyAccessSelect)
@@ -269,30 +270,30 @@
269
270
  row3_properties_accordion.accordion({
270
271
  collapsible: true,
271
272
  active: 'UUID' in opt ? false : 0,
272
- heightStyle: "content"
273
- });
273
+ heightStyle: 'content'
274
+ })
274
275
 
275
- container[0].appendChild(fragment);
276
+ container[0].appendChild(fragment)
276
277
  },
277
278
  removable: true,
278
279
  sortable: true
279
- });
280
+ })
280
281
 
281
282
  for (let i = 0; i < config.customCharacteristics.length; i++) {
282
- const customCharacteristic = config.customCharacteristics[i];
283
- $("#node-input-customCharacteristics-container").editableList('addItem', customCharacteristic);
283
+ const customCharacteristic = config.customCharacteristics[i]
284
+ $('#node-input-customCharacteristics-container').editableList('addItem', customCharacteristic)
284
285
  }
285
286
  },
286
287
  oneditresize: function (size) {
287
- const rows = $("#dialog-form>div:not(.node-input-customCharacteristics-container-row)");
288
- let height = size.height;
288
+ const rows = $('#dialog-form>div:not(.node-input-customCharacteristics-container-row)')
289
+ let height = size.height
289
290
  for (let i = 0; i < rows.length; i++) {
290
- height -= $(rows[i]).outerHeight(true);
291
+ height -= $(rows[i]).outerHeight(true)
291
292
  }
292
- const editorRow = $("#dialog-form>div.node-input-customCharacteristics-container-row");
293
- height -= (parseInt(editorRow.css("marginTop")) + parseInt(editorRow.css("marginBottom")));
294
- height += 16;
295
- $("#node-input-customCharacteristics-container").editableList('height', height);
293
+ const editorRow = $('#dialog-form>div.node-input-customCharacteristics-container-row')
294
+ height -= (parseInt(editorRow.css('marginTop')) + parseInt(editorRow.css('marginBottom')))
295
+ height += 16
296
+ $('#node-input-customCharacteristics-container').editableList('height', height)
296
297
  }
297
298
  })
298
299
  }
@@ -352,27 +353,27 @@
352
353
 
353
354
  const saveCustomCharacteristics = function (self) {
354
355
  const customCharacteristics = []
355
- const items = $("#node-input-customCharacteristics-container").editableList('items');
356
+ const items = $('#node-input-customCharacteristics-container').editableList('items')
356
357
 
357
358
  items.each(function () {
358
- const item = $(this);
359
- const name = item.find(".property-name").val();
360
- const UUID = item.find(".property-uuid").val();
361
- const format = item.find(".property-format").val();
362
- const unit = item.find(".property-unit").val();
363
- const perms = item.find(".property-perms").val();
364
- const ev = item.find(".property-ev").prop('checked');
365
- const description = item.find(".property-description").val();
366
- const minValue = item.find(".property-minValue").val();
367
- const maxValue = item.find(".property-maxValue").val();
368
- const minStep = item.find(".property-minStep").val();
369
- const maxLen = item.find(".property-maxLen").val();
370
- const maxDataLen = item.find(".property-maxDataLen").val();
371
- const validValues = item.find(".property-validValues").val();
372
- const validValueRangesMin = item.find(".property-validValueRanges").slider("values", 0);
373
- const validValueRangesMax = item.find(".property-validValueRanges").slider("values", 1);
359
+ const item = $(this)
360
+ const name = item.find('.property-name').val()
361
+ const UUID = item.find('.property-uuid').val()
362
+ const format = item.find('.property-format').val()
363
+ const unit = item.find('.property-unit').val()
364
+ const perms = item.find('.property-perms').val()
365
+ const ev = item.find('.property-ev').prop('checked')
366
+ const description = item.find('.property-description').val()
367
+ const minValue = item.find('.property-minValue').val()
368
+ const maxValue = item.find('.property-maxValue').val()
369
+ const minStep = item.find('.property-minStep').val()
370
+ const maxLen = item.find('.property-maxLen').val()
371
+ const maxDataLen = item.find('.property-maxDataLen').val()
372
+ const validValues = item.find('.property-validValues').val()
373
+ const validValueRangesMin = item.find('.property-validValueRanges').slider('values', 0)
374
+ const validValueRangesMax = item.find('.property-validValueRanges').slider('values', 1)
374
375
  const validValueRanges = validValueRangesMin && validValueRangesMax ? [validValueRangesMin, validValueRangesMax] : undefined
375
- const adminOnlyAccess = item.find(".property-adminOnlyAccess").val();
376
+ const adminOnlyAccess = item.find('.property-adminOnlyAccess').val()
376
377
  customCharacteristics.push({
377
378
  name,
378
379
  UUID,
@@ -389,8 +390,8 @@
389
390
  validValues,
390
391
  validValueRanges,
391
392
  adminOnlyAccess
392
- });
393
- });
393
+ })
394
+ })
394
395
 
395
396
  config.customCharacteristics = customCharacteristics
396
397
 
@@ -399,19 +400,19 @@
399
400
  url: 'nrchkb/config',
400
401
  dataType: 'json',
401
402
  data: {customCharacteristics}
402
- });
403
+ })
403
404
  }
404
405
 
405
406
  const uuidv4 = function () {
406
407
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
407
408
  (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
408
- );
409
+ )
409
410
  }
410
411
 
411
412
  const getRandomIntInclusive = function (min, max) {
412
- min = Math.ceil(min);
413
- max = Math.floor(max);
414
- return Math.floor(Math.random() * (max - min + 1) + min);
413
+ min = Math.ceil(min)
414
+ max = Math.floor(max)
415
+ return Math.floor(Math.random() * (max - min + 1) + min)
415
416
  }
416
417
 
417
418
  const forbiddenPinCodes = [
@@ -430,7 +431,7 @@
430
431
  ]
431
432
 
432
433
  const generatePinCode = function () {
433
- const [a, b, c, d, e, f, g, h] = Array.from({length: 9}, () => getRandomIntInclusive(0, 9));
434
+ const [a, b, c, d, e, f, g, h] = Array.from({length: 9}, () => getRandomIntInclusive(0, 9))
434
435
 
435
436
  if (forbiddenPinCodes.includes(`${a}${b}${c}${d}${e}${f}${g}${h}`)) {
436
437
  return generatePinCode()
@@ -440,11 +441,15 @@
440
441
  }
441
442
 
442
443
  const validatePinCode = function (value) {
443
- if (!RED.validators.regex(/([0-9]{3}-[0-9]{2}-[0-9]{3}|[0-9]{4}-[0-9]{4})/)) {
444
+ if (!value) {
445
+ return false
446
+ }
447
+
448
+ if (!RED.validators.regex(/([0-9]{3}-[0-9]{2}-[0-9]{3}|[0-9]{4}-[0-9]{4})/)(value)) {
444
449
  return false
445
450
  }
446
451
 
447
- return !forbiddenPinCodes.includes(value.replaceAll("-", ""));
452
+ return !forbiddenPinCodes.includes(value.replaceAll('-', ''))
448
453
  }
449
454
  </script>
450
455
 
@@ -63,9 +63,14 @@
63
63
  <label for="node-config-input-allowMessagePassthrough" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-step-forward"></i> Allow Message Passthrough</label>
64
64
  </div>
65
65
  <hr>
66
+ <div class="form-row">
67
+ <label for="node-config-input-bind"><i class="fa fa-filter"></i> Bind</label>
68
+ <input type="text" id="node-config-input-bind" placeholder="::">
69
+ <input type="hidden" id="node-config-input-bindType">
70
+ </div>
66
71
  <div class="form-row">
67
72
  <input type="checkbox" id="node-config-input-customMdnsConfig" style="display: inline-block; width: auto; vertical-align: top;">
68
- <label for="node-config-input-customMdnsConfig" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-filter"></i> Custom MDNS Configuration</label>
73
+ <label for="node-config-input-customMdnsConfig" style="width: 70%;">&nbsp;&nbsp;<i class="fa fa-filter"></i> Custom MDNS Configuration (DEPRECATED, use Bind)</label>
69
74
  </div>
70
75
  <div id="mdns-configuration" style="display: none;">
71
76
  <div class="form-row">
@@ -115,7 +120,60 @@
115
120
  <li><strong>Software Revision</strong>: Should be a version number string in the form of <em>MAJOR.MINOR.REVISION</em> e.g. <em>1.2.0</em>. Other types of strings are ignored and won't be displayed.</li>
116
121
  <li><strong>Name</strong>: If you intend to simulate a rocket, then why don&#39;t you call it <em>Rocket</em>. Name should be maximum 64 chars long and not contain <pre>.</pre></li>
117
122
  <li><strong>Allow Message Passthrough</strong>: If you allow then message from node input will be sent to node output.</li>
118
- <li><strong>Custom MDNS Configuration</strong>: Check if you would like to use custom mdns configuration.</li>
123
+ <li><strong>Bind</strong>: Defines the host where the HAP server will be bound to. </li>
124
+ <ul>
125
+ <li>
126
+ Defines the host where the HAP server will be bound to.
127
+ When undefined the HAP server will bind to all available interfaces
128
+ (see https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback).
129
+
130
+ This property accepts a mixture of IPAddresses and network interface names.
131
+ Depending on the mixture of supplied addresses/names hap-nodejs will bind differently.
132
+
133
+ It is advised to not just bind to a specific address, but specifying the interface name
134
+ in oder to bind on all address records (and ip version) available.
135
+
136
+ HAP-NodeJS (or the underlying ciao library) will not report about misspelled interface names,
137
+ as it could be that the interface is currently just down and will come up later.
138
+
139
+ Here are a few examples:
140
+ - bind: "::"
141
+ Pretty much identical to not specifying anything, as most systems (with ipv6 support)
142
+ will default to the unspecified ipv6 address (with dual stack support).
143
+
144
+ - bind: "0.0.0.0"
145
+ Binding TCP socket to the unspecified ipv4 address.
146
+ The mdns advertisement will exclude any ipv6 address records.
147
+
148
+ - bind: ["en0", "lo0"]
149
+ The mdns advertising will advertise all records of the en0 and loopback interface (if available) and
150
+ will also react to address changes on those interfaces.
151
+ In order for the HAP server to accept all those address records (which may contain ipv6 records)
152
+ it will bind on the unspecified ipv6 address "::" (assuming dual stack is supported).
153
+
154
+ - bind: ["en0", "lo0", "0.0.0.0"]
155
+ Same as above, only that the HAP server will bind on the unspecified ipv4 address "0.0.0.0".
156
+ The mdns advertisement will not advertise any ipv6 records.
157
+
158
+ - bind: "169.254.104.90"
159
+ This will bind the HAP server to the address 0.0.0.0.
160
+ The mdns advertisement will only advertise the A record 169.254.104.90.
161
+ If the given network interface of that address encounters an ip address change (to a different address),
162
+ the mdns advertisement will result in not advertising an address at all.
163
+ So it is advised to specify an interface name instead of a specific address.
164
+ This is identical with ipv6 addresses.
165
+
166
+ - bind: ["169.254.104.90", "192.168.1.4"]
167
+ As the HAP TCP socket can only bind to a single address, when specifying multiple ip addresses
168
+ the HAP server will bind to the unspecified ip address (0.0.0.0 if only ipv4 addresses are supplied,
169
+ :: if a mixture or only ipv6 addresses are supplied).
170
+ The mdns advertisement will only advertise the specified ip addresses.
171
+ If the given network interface of that address encounters an ip address change (to different addresses),
172
+ the mdns advertisement will result in not advertising an address at all.
173
+ So it is advised to specify an interface name instead of a specific address.
174
+ </li>
175
+ </ul>
176
+ <li><strong>Custom MDNS Configuration (DEPRECATED, use Bind)</strong>: Check if you would like to use custom mdns configuration.</li>
119
177
  <ul>
120
178
  <li><strong>Multicast</strong>: Use udp multicasting. Optional. Default true.</li>
121
179
  <li><strong>Multicast Interface IP:</strong>: Explicitly specify a network interface. Optional. Defaults to all.</li>
@@ -194,41 +252,46 @@
194
252
  required: false,
195
253
  validate: versionValidator,
196
254
  },
255
+ bind: {
256
+ value: undefined,
257
+ required: false,
258
+ },
259
+ bindType: {
260
+ value: undefined,
261
+ required: false,
262
+ },
197
263
  customMdnsConfig: {
198
264
  value: false,
199
265
  required: false,
266
+ validate: (value) => !value
200
267
  },
201
268
  mdnsMulticast: {
202
269
  value: true,
203
- required: false,
270
+ required: false
204
271
  },
205
272
  mdnsInterface: {
206
- required: false,
273
+ required: false
207
274
  },
208
275
  mdnsPort: {
209
- required: false,
210
- validate: function (value) {
211
- if (value) return RED.validators.port(value)
212
- else return true
213
- },
276
+ required: false
214
277
  },
215
278
  mdnsIp: {
216
- required: false,
279
+ required: false
217
280
  },
218
281
  mdnsTtl: {
219
- required: false,
282
+ required: false
220
283
  },
221
284
  mdnsLoopback: {
222
285
  value: true,
223
- required: false,
286
+ required: false
224
287
  },
225
288
  mdnsReuseAddr: {
226
289
  value: true,
227
- required: false,
290
+ required: false
228
291
  },
229
292
  allowMessagePassthrough: {
230
293
  value: true,
231
- required: true,
294
+ required: true
232
295
  },
233
296
  },
234
297
  label: function () {
@@ -242,7 +305,7 @@
242
305
 
243
306
  if (!validatePinCode(node.pinCode)) {
244
307
  node.pinCode = generatePinCode()
245
- $("#node-config-input-pinCode").val(node.pinCode)
308
+ $('#node-config-input-pinCode').val(this.pinCode)
246
309
  }
247
310
 
248
311
  $('#node-config-input-allowMessagePassthrough').prop(
@@ -265,22 +328,28 @@
265
328
  })
266
329
  .change()
267
330
 
268
- const selectCategoryName = $("#node-config-input-accessoryCategory");
331
+ const selectCategoryName = $('#node-config-input-accessoryCategory')
269
332
 
270
333
  Object.entries(accessoryCategories).forEach(([key, value]) => {
271
334
  selectCategoryName.append(
272
- $("<option></option>")
335
+ $('<option></option>')
273
336
  .val(key)
274
337
  .text(value)
275
- );
338
+ )
276
339
  })
277
340
 
278
341
  selectCategoryName
279
- .find("option")
342
+ .find('option')
280
343
  .filter(function () {
281
- return $(this).val() === node.accessoryCategory;
344
+ return $(this).val() === node.accessoryCategory
282
345
  })
283
- .attr("selected", 'true');
346
+ .attr('selected', 'true')
347
+
348
+ $('#node-config-input-bind').typedInput({
349
+ typeField: '#node-config-input-bindType',
350
+ default: 'str',
351
+ types: ['json', 'str']
352
+ })
284
353
  },
285
354
  })
286
355
  </script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-homekit-bridged",
3
- "version": "1.7.0-dev.8",
3
+ "version": "1.7.0",
4
4
  "description": "Node-RED nodes to simulate Apple HomeKit devices.",
5
5
  "main": "build/nodes/nrchkb.js",
6
6
  "scripts": {
@@ -42,15 +42,15 @@
42
42
  },
43
43
  "homepage": "https://github.com/NRCHKB/node-red-contrib-homekit-bridged#readme",
44
44
  "dependencies": {
45
- "@nrchkb/logger": "^3.0.0",
46
- "hap-nodejs": "0.12.2",
47
- "node-persist": "^4.0.1",
48
- "semver": "^7.6.2",
49
- "uuid": "^10.0.0"
45
+ "@nrchkb/logger": "~3.1.1",
46
+ "hap-nodejs": "0.12.3-beta.18",
47
+ "node-persist": "^3.1.3",
48
+ "semver": "~7.6.2",
49
+ "uuid": "~10.0.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@homebridge/ciao": "^1.2.0",
53
- "@node-red/registry": "^4.0.0",
52
+ "@homebridge/ciao": "~1.3.0",
53
+ "@node-red/registry": "^4.0.2",
54
54
  "@types/mocha": "^10.0.7",
55
55
  "@types/node": "^18",
56
56
  "@types/node-persist": "^3.1.8",
@@ -58,21 +58,21 @@
58
58
  "@types/node-red-node-test-helper": "^0.3.4",
59
59
  "@types/semver": "^7.5.8",
60
60
  "@types/uuid": "^10.0.0",
61
- "@typescript-eslint/eslint-plugin": "^7.14.1",
62
- "@typescript-eslint/parser": "^7.14.1",
61
+ "@typescript-eslint/eslint-plugin": "^7.16.0",
62
+ "@typescript-eslint/parser": "^7.16.0",
63
63
  "babel-eslint": "^10.1.0",
64
64
  "del-cli": "^5.1.0",
65
65
  "eslint": "^8",
66
66
  "eslint-config-prettier": "^9.1.0",
67
67
  "eslint-plugin-prettier": "^5.1.3",
68
- "eslint-plugin-simple-import-sort": "^12.1.0",
68
+ "eslint-plugin-simple-import-sort": "^12.1.1",
69
69
  "husky": "^9.0.11",
70
- "mocha": "^10.5.1",
71
- "node-red": "^4.0.0",
70
+ "mocha": "^10.6.0",
71
+ "node-red": "^4.0.2",
72
72
  "node-red-node-test-helper": "^0.3.4",
73
73
  "prettier": "^3.3.2",
74
74
  "ts-node": "^10.9.2",
75
- "typescript": "^5.5.2"
75
+ "typescript": "^5.5.3"
76
76
  },
77
77
  "engines": {
78
78
  "node": ">=18"