@project-chip/matter.js 0.11.0-alpha.0-20240928-08865c2ce → 0.11.0-alpha.0-20241002-e7b377c34

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 (46) hide show
  1. package/dist/cjs/CommissioningController.d.ts +7 -35
  2. package/dist/cjs/CommissioningController.d.ts.map +1 -1
  3. package/dist/cjs/CommissioningController.js +2 -1
  4. package/dist/cjs/CommissioningController.js.map +1 -1
  5. package/dist/cjs/CommissioningServer.d.ts.map +1 -1
  6. package/dist/cjs/CommissioningServer.js +2 -2
  7. package/dist/cjs/CommissioningServer.js.map +1 -1
  8. package/dist/cjs/MatterController.d.ts +21 -49
  9. package/dist/cjs/MatterController.d.ts.map +1 -1
  10. package/dist/cjs/MatterController.js +102 -502
  11. package/dist/cjs/MatterController.js.map +2 -2
  12. package/dist/cjs/compat/protocol.d.ts +1 -1
  13. package/dist/cjs/compat/protocol.d.ts.map +1 -1
  14. package/dist/cjs/compat/protocol.js +1 -2
  15. package/dist/cjs/compat/protocol.js.map +1 -1
  16. package/dist/cjs/device/LegacyInteractionServer.d.ts.map +1 -1
  17. package/dist/cjs/device/PairedNode.d.ts +1 -2
  18. package/dist/cjs/device/PairedNode.d.ts.map +1 -1
  19. package/dist/cjs/device/PairedNode.js +2 -3
  20. package/dist/cjs/device/PairedNode.js.map +1 -1
  21. package/dist/esm/CommissioningController.d.ts +7 -35
  22. package/dist/esm/CommissioningController.d.ts.map +1 -1
  23. package/dist/esm/CommissioningController.js +2 -1
  24. package/dist/esm/CommissioningController.js.map +1 -1
  25. package/dist/esm/CommissioningServer.d.ts.map +1 -1
  26. package/dist/esm/CommissioningServer.js +2 -2
  27. package/dist/esm/CommissioningServer.js.map +1 -1
  28. package/dist/esm/MatterController.d.ts +21 -49
  29. package/dist/esm/MatterController.d.ts.map +1 -1
  30. package/dist/esm/MatterController.js +105 -519
  31. package/dist/esm/MatterController.js.map +2 -2
  32. package/dist/esm/compat/protocol.d.ts +1 -1
  33. package/dist/esm/compat/protocol.d.ts.map +1 -1
  34. package/dist/esm/compat/protocol.js +2 -4
  35. package/dist/esm/compat/protocol.js.map +1 -1
  36. package/dist/esm/device/LegacyInteractionServer.d.ts.map +1 -1
  37. package/dist/esm/device/PairedNode.d.ts +1 -2
  38. package/dist/esm/device/PairedNode.d.ts.map +1 -1
  39. package/dist/esm/device/PairedNode.js +1 -1
  40. package/dist/esm/device/PairedNode.js.map +1 -1
  41. package/package.json +8 -8
  42. package/src/CommissioningController.ts +7 -42
  43. package/src/CommissioningServer.ts +2 -2
  44. package/src/MatterController.ts +126 -713
  45. package/src/compat/protocol.ts +2 -3
  46. package/src/device/PairedNode.ts +1 -1
@@ -18,8 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var MatterController_exports = {};
20
20
  __export(MatterController_exports, {
21
- MatterController: () => MatterController,
22
- NodeDiscoveryType: () => NodeDiscoveryType
21
+ MatterController: () => MatterController
23
22
  });
24
23
  module.exports = __toCommonJS(MatterController_exports);
25
24
  var import_clusters = require("#clusters");
@@ -37,21 +36,12 @@ const TlvCommissioningSuccessFailureResponse = (0, import_types.TlvObject)({
37
36
  /** Should help developers in troubleshooting errors. The value MAY go into logs or crash reports, not User UIs. */
38
37
  debugText: (0, import_types.TlvField)(1, import_types.TlvString.bound({ maxLength: 128 }))
39
38
  });
39
+ const DEFAULT_ADMIN_VENDOR_ID = (0, import_types.VendorId)(65521);
40
40
  const DEFAULT_FABRIC_INDEX = (0, import_types.FabricIndex)(1);
41
41
  const DEFAULT_FABRIC_ID = (0, import_types.FabricId)(1);
42
- const DEFAULT_ADMIN_VENDOR_ID = (0, import_types.VendorId)(65521);
43
- const RECONNECTION_POLLING_INTERVAL_MS = 6e5;
44
- const RETRANSMISSION_DISCOVERY_TIMEOUT_MS = 5e3;
45
42
  const CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE = 3;
46
43
  const CONTROLLER_MAX_PATHS_PER_INVOKE = 10;
47
44
  const logger = import_general.Logger.get("MatterController");
48
- var NodeDiscoveryType = /* @__PURE__ */ ((NodeDiscoveryType2) => {
49
- NodeDiscoveryType2[NodeDiscoveryType2["None"] = 0] = "None";
50
- NodeDiscoveryType2[NodeDiscoveryType2["RetransmissionDiscovery"] = 1] = "RetransmissionDiscovery";
51
- NodeDiscoveryType2[NodeDiscoveryType2["TimedDiscovery"] = 2] = "TimedDiscovery";
52
- NodeDiscoveryType2[NodeDiscoveryType2["FullDiscovery"] = 3] = "FullDiscovery";
53
- return NodeDiscoveryType2;
54
- })(NodeDiscoveryType || {});
55
45
  class MatterController {
56
46
  static async create(options) {
57
47
  const {
@@ -62,7 +52,7 @@ class MatterController {
62
52
  scanners,
63
53
  netInterfaces,
64
54
  sessionClosedCallback,
65
- adminVendorId = (0, import_types.VendorId)(DEFAULT_ADMIN_VENDOR_ID),
55
+ adminVendorId,
66
56
  adminFabricId = (0, import_types.FabricId)(DEFAULT_FABRIC_ID),
67
57
  adminFabricIndex = (0, import_types.FabricIndex)(DEFAULT_FABRIC_INDEX),
68
58
  caseAuthenticatedTags
@@ -79,13 +69,12 @@ class MatterController {
79
69
  netInterfaces,
80
70
  certificateManager,
81
71
  fabric,
82
- adminVendorId: fabric.rootVendorId,
83
72
  sessionClosedCallback
84
73
  });
85
74
  } else {
86
75
  const rootNodeId = import_types.NodeId.randomOperationalNodeId();
87
76
  const ipkValue = import_general.Crypto.getRandomData(import_general.CRYPTO_SYMMETRIC_KEY_LENGTH);
88
- const fabricBuilder = new import_protocol.FabricBuilder().setRootCert(certificateManager.rootCert).setRootNodeId(rootNodeId).setIdentityProtectionKey(ipkValue).setRootVendorId(adminVendorId);
77
+ const fabricBuilder = new import_protocol.FabricBuilder().setRootCert(certificateManager.rootCert).setRootNodeId(rootNodeId).setIdentityProtectionKey(ipkValue).setRootVendorId(adminVendorId ?? DEFAULT_ADMIN_VENDOR_ID);
89
78
  fabricBuilder.setOperationalCert(
90
79
  certificateManager.generateNoc(
91
80
  fabricBuilder.publicKey,
@@ -103,7 +92,6 @@ class MatterController {
103
92
  netInterfaces,
104
93
  certificateManager,
105
94
  fabric,
106
- adminVendorId,
107
95
  sessionClosedCallback
108
96
  });
109
97
  }
@@ -133,7 +121,6 @@ class MatterController {
133
121
  netInterfaces,
134
122
  certificateManager,
135
123
  fabric,
136
- adminVendorId: fabric.rootVendorId,
137
124
  sessionClosedCallback
138
125
  });
139
126
  await controller.construction;
@@ -143,19 +130,16 @@ class MatterController {
143
130
  netInterfaces = new import_general.NetInterfaceSet();
144
131
  channelManager = new import_protocol.ChannelManager(CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE);
145
132
  exchangeManager;
146
- paseClient;
147
- caseClient;
148
- commissionedNodes = /* @__PURE__ */ new Map();
133
+ peers;
134
+ commissioner;
149
135
  #construction;
150
136
  sessionStorage;
151
137
  fabricStorage;
152
- nodesStorage;
138
+ nodesStore;
153
139
  scanners;
154
140
  certificateManager;
155
141
  fabric;
156
- adminVendorId;
157
142
  sessionClosedCallback;
158
- #runningNodeDiscoveries = /* @__PURE__ */ new Map();
159
143
  get construction() {
160
144
  return this.#construction;
161
145
  }
@@ -168,18 +152,15 @@ class MatterController {
168
152
  netInterfaces,
169
153
  certificateManager,
170
154
  fabric,
171
- sessionClosedCallback,
172
- adminVendorId
155
+ sessionClosedCallback
173
156
  } = options;
174
157
  this.sessionStorage = sessionStorage;
175
158
  this.fabricStorage = fabricStorage;
176
- this.nodesStorage = nodesStorage;
177
159
  this.scanners = scanners;
178
160
  this.netInterfaces = netInterfaces;
179
161
  this.certificateManager = certificateManager;
180
162
  this.fabric = fabric;
181
163
  this.sessionClosedCallback = sessionClosedCallback;
182
- this.adminVendorId = adminVendorId;
183
164
  const fabricManager = new import_protocol.FabricManager();
184
165
  fabricManager.addFabric(fabric);
185
166
  this.sessionManager = new import_protocol.SessionManager({
@@ -189,26 +170,34 @@ class MatterController {
189
170
  maxPathsPerInvoke: CONTROLLER_MAX_PATHS_PER_INVOKE
190
171
  }
191
172
  });
192
- this.paseClient = new import_protocol.PaseClient(this.sessionManager);
193
- this.caseClient = new import_protocol.CaseClient(this.sessionManager);
194
173
  this.sessionManager.sessions.deleted.on(async (session) => {
195
174
  this.sessionClosedCallback?.(session.peerNodeId);
196
175
  });
197
- this.sessionManager.resubmissionStarted.on(this.#handleResubmissionStarted.bind(this));
198
176
  this.exchangeManager = new import_protocol.ExchangeManager({
199
177
  sessionManager: this.sessionManager,
200
178
  channelManager: this.channelManager,
201
179
  transportInterfaces: this.netInterfaces
202
180
  });
203
181
  this.exchangeManager.addProtocolHandler(new import_protocol.StatusReportOnlySecureChannelProtocol());
182
+ this.nodesStore = new CommissionedNodeStore(nodesStorage, fabric);
183
+ this.nodesStore.peers = this.peers = new import_protocol.PeerSet({
184
+ sessions: this.sessionManager,
185
+ channels: this.channelManager,
186
+ exchanges: this.exchangeManager,
187
+ scanners: this.scanners,
188
+ netInterfaces: this.netInterfaces,
189
+ store: this.nodesStore
190
+ });
191
+ this.commissioner = new import_protocol.ControllerCommissioner({
192
+ peers: this.peers,
193
+ scanners: this.scanners,
194
+ netInterfaces: this.netInterfaces,
195
+ exchanges: this.exchangeManager,
196
+ sessions: this.sessionManager,
197
+ certificates: this.certificateManager
198
+ });
204
199
  this.#construction = (0, import_general.Construction)(this, async () => {
205
- if (await this.nodesStorage.has("commissionedNodes")) {
206
- const commissionedNodes = await this.nodesStorage.get("commissionedNodes");
207
- this.commissionedNodes.clear();
208
- for (const [nodeId, details] of commissionedNodes) {
209
- this.commissionedNodes.set(nodeId, details);
210
- }
211
- }
200
+ await this.peers.construction.ready;
212
201
  await this.sessionManager.construction.ready;
213
202
  });
214
203
  }
@@ -241,191 +230,36 @@ class MatterController {
241
230
  * Return true when the commissioning process is completed successfully, false on error.
242
231
  */
243
232
  async commission(options, completeCommissioningCallback) {
244
- const {
245
- commissioning: commissioningOptions = {
246
- regulatoryLocation: import_clusters.GeneralCommissioning.RegulatoryLocationType.Outdoor,
247
- // Set to the most restrictive if relevant
248
- regulatoryCountryCode: "XX"
249
- },
250
- discovery: { timeoutSeconds = 30 },
251
- passcode
252
- } = options;
253
- const commissionableDevice = "commissionableDevice" in options.discovery ? options.discovery.commissionableDevice : void 0;
254
- let {
255
- discovery: { discoveryCapabilities = {}, knownAddress }
256
- } = options;
257
- let identifierData = "identifierData" in options.discovery ? options.discovery.identifierData : {};
258
- if (this.scanners.hasScannerFor(import_general.ChannelType.UDP) && this.netInterfaces.hasInterfaceFor(import_general.ChannelType.UDP, "::") !== void 0) {
259
- discoveryCapabilities.onIpNetwork = true;
260
- }
261
- if (commissionableDevice !== void 0) {
262
- let { addresses } = commissionableDevice;
263
- if (discoveryCapabilities.ble === true) {
264
- discoveryCapabilities = { onIpNetwork: true, ble: addresses.some((address) => address.type === "ble") };
265
- } else if (discoveryCapabilities.onIpNetwork === true) {
266
- addresses = addresses.filter((address) => address.type !== "ble");
267
- }
268
- addresses.sort((a) => a.type === "udp" ? -1 : 1);
269
- knownAddress = addresses[0];
270
- if ("instanceId" in commissionableDevice && commissionableDevice.instanceId !== void 0) {
271
- identifierData = { instanceId: commissionableDevice.instanceId };
272
- } else {
273
- identifierData = { longDiscriminator: commissionableDevice.D };
274
- }
275
- }
276
- const scannersToUse = this.collectScanners(discoveryCapabilities);
277
- logger.info(
278
- `Commissioning device with identifier ${import_general.Logger.toJSON(identifierData)} and ${scannersToUse.length} scanners and knownAddress ${import_general.Logger.toJSON(knownAddress)}`
279
- );
280
- let paseSecureChannel;
281
- let discoveryData;
282
- if (knownAddress !== void 0) {
283
- try {
284
- paseSecureChannel = await this.initializePaseSecureChannel(knownAddress, passcode);
285
- } catch (error) {
286
- import_general.NoResponseTimeoutError.accept(error);
287
- }
288
- }
289
- if (paseSecureChannel === void 0) {
290
- const discoveredDevices = await import_protocol.ControllerDiscovery.discoverDeviceAddressesByIdentifier(
291
- scannersToUse,
292
- identifierData,
293
- timeoutSeconds
294
- );
295
- const { result } = await import_protocol.ControllerDiscovery.iterateServerAddresses(
296
- discoveredDevices,
297
- import_general.NoResponseTimeoutError,
298
- async () => scannersToUse.flatMap((scanner) => scanner.getDiscoveredCommissionableDevices(identifierData)),
299
- async (address, device) => {
300
- const channel = await this.initializePaseSecureChannel(address, passcode, device);
301
- discoveryData = device;
302
- return channel;
233
+ const commissioningOptions = {
234
+ ...options.commissioning,
235
+ fabric: this.fabric,
236
+ discovery: options.discovery,
237
+ passcode: options.passcode
238
+ };
239
+ if (completeCommissioningCallback) {
240
+ commissioningOptions.performCaseCommissioning = async (peerAddress, discoveryData) => {
241
+ const result = await completeCommissioningCallback(peerAddress.nodeId, discoveryData);
242
+ if (!result) {
243
+ throw new import_protocol.RetransmissionLimitReachedError("Device could not be discovered");
303
244
  }
304
- );
305
- paseSecureChannel = result;
245
+ };
306
246
  }
307
- return await this.commissionDevice(
308
- paseSecureChannel,
309
- commissioningOptions,
310
- discoveryData,
311
- completeCommissioningCallback
312
- );
247
+ const address = await this.commissioner.commission(commissioningOptions);
248
+ await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
249
+ return address.nodeId;
313
250
  }
314
251
  async disconnect(nodeId) {
315
- await this.sessionManager.removeAllSessionsForNode(nodeId, true);
316
- await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
252
+ return this.peers.disconnect(this.fabric.addressOf(nodeId));
317
253
  }
318
254
  async removeNode(nodeId) {
319
- logger.info(`Removing commissioned node ${nodeId} from controller.`);
320
- await this.sessionManager.removeAllSessionsForNode(nodeId);
321
- await this.sessionManager.removeResumptionRecord(nodeId);
322
- await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
323
- this.commissionedNodes.delete(nodeId);
324
- await this.storeCommissionedNodes();
325
- }
326
- /**
327
- * Method to start commission process with a PASE pairing.
328
- * If this not successful and throws an RetransmissionLimitReachedError the address is invalid or the passcode
329
- * is wrong.
330
- */
331
- async initializePaseSecureChannel(address, passcode, device) {
332
- let paseChannel;
333
- if (device !== void 0) {
334
- logger.info(`Commissioning device`, import_protocol.MdnsScanner.discoveryDataDiagnostics(device));
335
- }
336
- if (address.type === "udp") {
337
- const { ip } = address;
338
- const isIpv6Address = (0, import_general.isIPv6)(ip);
339
- const paseInterface = this.netInterfaces.interfaceFor(import_general.ChannelType.UDP, isIpv6Address ? "::" : "0.0.0.0");
340
- if (paseInterface === void 0) {
341
- throw new import_protocol.PairRetransmissionLimitReachedError(
342
- `IPv${isIpv6Address ? "6" : "4"} interface not initialized. Cannot use ${ip} for commissioning.`
343
- );
344
- }
345
- paseChannel = await paseInterface.openChannel(address);
346
- } else {
347
- const ble = this.netInterfaces.interfaceFor(import_general.ChannelType.BLE);
348
- if (!ble) {
349
- throw new import_protocol.PairRetransmissionLimitReachedError(
350
- `BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`
351
- );
352
- }
353
- paseChannel = await ble.openChannel(address);
354
- }
355
- const unsecureSession = this.sessionManager.createInsecureSession({
356
- // Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
357
- sessionParameters: {
358
- idleIntervalMs: device?.SII,
359
- activeIntervalMs: device?.SAI,
360
- activeThresholdMs: device?.SAT
361
- },
362
- isInitiator: true
363
- });
364
- const paseUnsecureMessageChannel = new import_protocol.MessageChannel(paseChannel, unsecureSession);
365
- const paseExchange = this.exchangeManager.initiateExchangeWithChannel(
366
- paseUnsecureMessageChannel,
367
- import_types.SECURE_CHANNEL_PROTOCOL_ID
368
- );
369
- let paseSecureSession;
370
- try {
371
- paseSecureSession = await this.paseClient.pair(
372
- this.sessionManager.sessionParameters,
373
- paseExchange,
374
- passcode
375
- );
376
- } catch (e) {
377
- await paseExchange.close();
378
- throw e;
379
- }
380
- await unsecureSession.destroy();
381
- return new import_protocol.MessageChannel(paseChannel, paseSecureSession);
382
- }
383
- /**
384
- * Method to commission a device with a PASE secure channel. It returns the NodeId of the commissioned device on
385
- * success.
386
- */
387
- async commissionDevice(paseSecureMessageChannel, commissioningOptions, discoveryData, completeCommissioningCallback) {
388
- const peerNodeId = commissioningOptions.nodeId ?? import_types.NodeId.randomOperationalNodeId();
389
- const commissioningManager = new import_protocol.ControllerCommissioner(
390
- // Use the created secure session to do the commissioning
391
- new import_protocol.InteractionClient(new import_protocol.ExchangeProvider(this.exchangeManager, paseSecureMessageChannel), peerNodeId),
392
- this.certificateManager,
393
- this.fabric,
394
- commissioningOptions,
395
- peerNodeId,
396
- this.adminVendorId,
397
- async () => {
398
- await paseSecureMessageChannel.close();
399
- if (completeCommissioningCallback !== void 0) {
400
- if (!await completeCommissioningCallback(peerNodeId, discoveryData)) {
401
- throw new import_protocol.RetransmissionLimitReachedError("Device could not be discovered");
402
- }
403
- throw new import_protocol.CommissioningSuccessfullyFinished();
404
- }
405
- return await this.connect(peerNodeId, {
406
- discoveryType: 2 /* TimedDiscovery */,
407
- timeoutSeconds: 120,
408
- discoveryData
409
- });
410
- }
411
- );
412
- try {
413
- await commissioningManager.executeCommissioning();
414
- } catch (error) {
415
- if (this.commissionedNodes.has(peerNodeId)) {
416
- this.commissionedNodes.delete(peerNodeId);
417
- }
418
- throw error;
419
- }
420
- await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
421
- return peerNodeId;
255
+ return this.peers.delete(this.fabric.addressOf(nodeId));
422
256
  }
423
257
  /**
424
258
  * Method to complete the commissioning process to a node which was initialized with a PASE secure channel.
425
259
  */
426
260
  async completeCommissioning(peerNodeId, discoveryData) {
427
261
  const interactionClient = await this.connect(peerNodeId, {
428
- discoveryType: 2 /* TimedDiscovery */,
262
+ discoveryType: import_protocol.NodeDiscoveryType.TimedDiscovery,
429
263
  timeoutSeconds: 120,
430
264
  discoveryData
431
265
  });
@@ -438,318 +272,44 @@ class MatterController {
438
272
  useExtendedFailSafeMessageResponseTimeout: true
439
273
  });
440
274
  if (errorCode !== import_clusters.GeneralCommissioning.CommissioningError.Ok) {
441
- if (this.commissionedNodes.has(peerNodeId)) {
442
- this.commissionedNodes.delete(peerNodeId);
443
- }
275
+ await this.peers.delete(this.fabric.addressOf(peerNodeId));
444
276
  throw new import_protocol.CommissioningError(`Commission error on commissioningComplete: ${errorCode}, ${debugText}`);
445
277
  }
446
278
  await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
447
279
  }
448
- #handleResubmissionStarted(peerNodeId) {
449
- if (peerNodeId === void 0) {
450
- return;
451
- }
452
- if (this.#runningNodeDiscoveries.has(peerNodeId)) {
453
- return;
454
- }
455
- this.#runningNodeDiscoveries.set(peerNodeId, { type: 1 /* RetransmissionDiscovery */ });
456
- this.scanners.scannerFor(import_general.ChannelType.UDP)?.findOperationalDevice(this.fabric, peerNodeId, RETRANSMISSION_DISCOVERY_TIMEOUT_MS, true).catch((error) => {
457
- logger.error(`Failed to discover device ${peerNodeId} after resubmission started.`, error);
458
- }).finally(() => {
459
- if (this.#runningNodeDiscoveries.get(peerNodeId)?.type === 1 /* RetransmissionDiscovery */) {
460
- this.#runningNodeDiscoveries.delete(peerNodeId);
461
- }
462
- });
463
- }
464
- async reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData, expectedProcessingTimeMs) {
465
- const { ip, port } = operationalAddress;
466
- try {
467
- logger.debug(
468
- `Resume device connection to configured server at ${ip}:${port}${expectedProcessingTimeMs !== void 0 ? ` with expected processing time of ${expectedProcessingTimeMs}ms` : ""} ...`
469
- );
470
- const channel = await this.pair(peerNodeId, operationalAddress, discoveryData, expectedProcessingTimeMs);
471
- await this.setOperationalDeviceData(peerNodeId, operationalAddress);
472
- return channel;
473
- } catch (error) {
474
- if (error instanceof import_general.NoResponseTimeoutError) {
475
- logger.debug(
476
- `Failed to resume connection to node ${peerNodeId} connection with ${ip}:${port}, discover the device ...`,
477
- error
478
- );
479
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
480
- return void 0;
481
- } else {
482
- throw error;
483
- }
484
- }
485
- }
486
- async connectOrDiscoverNode(peerNodeId, operationalAddress, discoveryOptions = {}) {
487
- const {
488
- discoveryType: requestedDiscoveryType = 3 /* FullDiscovery */,
489
- timeoutSeconds,
490
- discoveryData = this.commissionedNodes.get(peerNodeId)?.discoveryData
491
- } = discoveryOptions;
492
- if (timeoutSeconds !== void 0 && requestedDiscoveryType !== 2 /* TimedDiscovery */) {
493
- throw new import_general.ImplementationError("Cannot set timeout without timed discovery.");
494
- }
495
- if (requestedDiscoveryType === 1 /* RetransmissionDiscovery */) {
496
- throw new import_general.ImplementationError("Cannot set retransmission discovery type.");
497
- }
498
- const mdnsScanner = this.scanners.scannerFor(import_general.ChannelType.UDP);
499
- if (!mdnsScanner) {
500
- throw new import_general.ImplementationError("Cannot discover device without mDNS scanner.");
501
- }
502
- const existingDiscoveryDetails = this.#runningNodeDiscoveries.get(peerNodeId) ?? {
503
- type: 0 /* None */
504
- };
505
- if (existingDiscoveryDetails.type !== 0 /* None */ && existingDiscoveryDetails.type < requestedDiscoveryType) {
506
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
507
- this.#runningNodeDiscoveries.delete(peerNodeId);
508
- existingDiscoveryDetails.type = 0 /* None */;
509
- }
510
- const { type: runningDiscoveryType, promises } = existingDiscoveryDetails;
511
- if (operationalAddress !== void 0 && (runningDiscoveryType === 0 /* None */ || requestedDiscoveryType === 0 /* None */)) {
512
- const directReconnection = await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData);
513
- if (directReconnection !== void 0) {
514
- return directReconnection;
515
- }
516
- if (requestedDiscoveryType === 0 /* None */) {
517
- throw new import_protocol.DiscoveryError(`Node ${peerNodeId} is not reachable right now.`);
518
- }
519
- }
520
- if (promises !== void 0) {
521
- if (runningDiscoveryType > requestedDiscoveryType) {
522
- throw new import_protocol.DiscoveryError(
523
- `Node ${peerNodeId} is not reachable right now and discovery already running.`
524
- );
525
- } else {
526
- return await (0, import_general.anyPromise)(promises);
527
- }
528
- }
529
- const discoveryPromises = new Array();
530
- let reconnectionPollingTimer;
531
- if (operationalAddress !== void 0) {
532
- if (requestedDiscoveryType === 3 /* FullDiscovery */) {
533
- const { promise, resolver, rejecter } = (0, import_general.createPromise)();
534
- reconnectionPollingTimer = import_general.Time.getPeriodicTimer(
535
- "Controller reconnect",
536
- RECONNECTION_POLLING_INTERVAL_MS,
537
- async () => {
538
- try {
539
- logger.debug(`Polling for device at ${(0, import_general.serverAddressToString)(operationalAddress)} ...`);
540
- const result = await this.reconnectKnownAddress(
541
- peerNodeId,
542
- operationalAddress,
543
- discoveryData
544
- );
545
- if (result !== void 0 && reconnectionPollingTimer?.isRunning) {
546
- reconnectionPollingTimer?.stop();
547
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
548
- this.#runningNodeDiscoveries.delete(peerNodeId);
549
- resolver(result);
550
- }
551
- } catch (error) {
552
- if (reconnectionPollingTimer?.isRunning) {
553
- reconnectionPollingTimer?.stop();
554
- mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
555
- this.#runningNodeDiscoveries.delete(peerNodeId);
556
- rejecter(error);
557
- }
558
- }
559
- }
560
- ).start();
561
- discoveryPromises.push(() => promise);
562
- }
563
- }
564
- discoveryPromises.push(async () => {
565
- const scanResult = await import_protocol.ControllerDiscovery.discoverOperationalDevice(
566
- this.fabric,
567
- peerNodeId,
568
- mdnsScanner,
569
- timeoutSeconds,
570
- timeoutSeconds === void 0
571
- );
572
- const { timer } = this.#runningNodeDiscoveries.get(peerNodeId) ?? {};
573
- timer?.stop();
574
- this.#runningNodeDiscoveries.delete(peerNodeId);
575
- const { result } = await import_protocol.ControllerDiscovery.iterateServerAddresses(
576
- [scanResult],
577
- import_general.NoResponseTimeoutError,
578
- async () => {
579
- const device = mdnsScanner.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
580
- return device !== void 0 ? [device] : [];
581
- },
582
- async (address, device) => {
583
- const result2 = await this.pair(peerNodeId, address, device);
584
- await this.setOperationalDeviceData(peerNodeId, address, {
585
- ...discoveryData,
586
- ...device
587
- });
588
- return result2;
589
- }
590
- );
591
- return result;
592
- });
593
- this.#runningNodeDiscoveries.set(peerNodeId, {
594
- type: requestedDiscoveryType,
595
- promises: discoveryPromises,
596
- timer: reconnectionPollingTimer
597
- });
598
- return await (0, import_general.anyPromise)(discoveryPromises).finally(() => this.#runningNodeDiscoveries.delete(peerNodeId));
599
- }
600
- /**
601
- * Resume a device connection and establish a CASE session that was previously paired with the controller. This
602
- * method will try to connect to the device using the previously used server address (if set). If that fails, the
603
- * device is discovered again using its operational instance details.
604
- * It returns the operational MessageChannel on success.
605
- */
606
- async resume(peerNodeId, discoveryOptions) {
607
- const operationalAddress = this.getLastOperationalAddress(peerNodeId);
608
- try {
609
- return await this.connectOrDiscoverNode(peerNodeId, operationalAddress, discoveryOptions);
610
- } catch (error) {
611
- if ((error instanceof import_protocol.DiscoveryError || error instanceof import_general.NoResponseTimeoutError) && this.commissionedNodes.has(peerNodeId)) {
612
- logger.info(`Resume failed, remove all sessions for node ${peerNodeId}`);
613
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
614
- }
615
- throw error;
616
- }
617
- }
618
- /** Pair with an operational device (already commissioned) and establish a CASE session. */
619
- async pair(peerNodeId, operationalServerAddress, discoveryData, expectedProcessingTimeMs) {
620
- const { ip, port } = operationalServerAddress;
621
- const isIpv6Address = (0, import_general.isIPv6)(ip);
622
- const operationalInterface = this.netInterfaces.interfaceFor(import_general.ChannelType.UDP, isIpv6Address ? "::" : "0.0.0.0");
623
- if (operationalInterface === void 0) {
624
- throw new import_protocol.PairRetransmissionLimitReachedError(
625
- `IPv${isIpv6Address ? "6" : "4"} interface not initialized for port ${port}. Cannot use ${ip} for pairing.`
626
- );
627
- }
628
- const operationalChannel = await operationalInterface.openChannel(operationalServerAddress);
629
- const { sessionParameters } = this.findResumptionRecordByNodeId(peerNodeId) ?? {};
630
- const unsecureSession = this.sessionManager.createInsecureSession({
631
- // Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
632
- sessionParameters: {
633
- idleIntervalMs: discoveryData?.SII ?? sessionParameters?.idleIntervalMs,
634
- activeIntervalMs: discoveryData?.SAI ?? sessionParameters?.activeIntervalMs,
635
- activeThresholdMs: discoveryData?.SAT ?? sessionParameters?.activeThresholdMs
636
- },
637
- isInitiator: true
638
- });
639
- const operationalUnsecureMessageExchange = new import_protocol.MessageChannel(operationalChannel, unsecureSession);
640
- let operationalSecureSession;
641
- try {
642
- const exchange = this.exchangeManager.initiateExchangeWithChannel(
643
- operationalUnsecureMessageExchange,
644
- import_types.SECURE_CHANNEL_PROTOCOL_ID
645
- );
646
- try {
647
- operationalSecureSession = await this.caseClient.pair(
648
- exchange,
649
- this.fabric,
650
- peerNodeId,
651
- expectedProcessingTimeMs
652
- );
653
- } catch (e) {
654
- await exchange.close();
655
- throw e;
656
- }
657
- } catch (e) {
658
- import_general.NoResponseTimeoutError.accept(e);
659
- throw new import_protocol.PairRetransmissionLimitReachedError(e.message);
660
- }
661
- await unsecureSession.destroy();
662
- const channel = new import_protocol.MessageChannel(operationalChannel, operationalSecureSession);
663
- await this.channelManager.setChannel(this.fabric, peerNodeId, channel);
664
- return channel;
665
- }
666
280
  isCommissioned() {
667
- return this.commissionedNodes.size > 0;
281
+ return this.peers.size > 0;
668
282
  }
669
283
  getCommissionedNodes() {
670
- return Array.from(this.commissionedNodes.keys());
284
+ return this.peers.map((peer) => peer.address.nodeId);
671
285
  }
672
286
  getCommissionedNodesDetails() {
673
- return Array.from(this.commissionedNodes.entries()).map(
674
- ([nodeId, { operationalServerAddress, discoveryData, basicInformationData }]) => ({
675
- nodeId,
676
- operationalAddress: operationalServerAddress ? (0, import_general.serverAddressToString)(operationalServerAddress) : void 0,
287
+ return this.peers.map((peer) => {
288
+ const { address, operationalAddress, discoveryData, basicInformationData } = peer;
289
+ return {
290
+ nodeId: address.nodeId,
291
+ operationalAddress: operationalAddress ? (0, import_general.serverAddressToString)(operationalAddress) : void 0,
677
292
  advertisedName: discoveryData?.DN,
678
293
  discoveryData,
679
294
  basicInformationData
680
- })
681
- );
682
- }
683
- async setOperationalDeviceData(nodeId, operationalServerAddress, discoveryData) {
684
- const nodeDetails = this.commissionedNodes.get(nodeId) ?? {};
685
- nodeDetails.operationalServerAddress = operationalServerAddress;
686
- if (discoveryData !== void 0) {
687
- nodeDetails.discoveryData = {
688
- ...nodeDetails.discoveryData,
689
- ...discoveryData
690
295
  };
691
- }
692
- this.commissionedNodes.set(nodeId, nodeDetails);
693
- await this.storeCommissionedNodes();
296
+ });
694
297
  }
695
298
  async enhanceCommissionedNodeDetails(nodeId, data) {
696
- const nodeDetails = this.commissionedNodes.get(nodeId);
299
+ const nodeDetails = this.peers.get(this.fabric.addressOf(nodeId));
697
300
  if (nodeDetails === void 0) {
698
301
  throw new Error(`Node ${nodeId} is not commissioned.`);
699
302
  }
700
303
  const { basicInformationData } = data;
701
304
  nodeDetails.basicInformationData = basicInformationData;
702
- this.commissionedNodes.set(nodeId, nodeDetails);
703
- await this.storeCommissionedNodes();
704
- }
705
- getLastOperationalAddress(nodeId) {
706
- return this.commissionedNodes.get(nodeId)?.operationalServerAddress;
707
- }
708
- async storeCommissionedNodes() {
709
- await this.nodesStorage.set("commissionedNodes", Array.from(this.commissionedNodes.entries()));
305
+ await this.nodesStore.save();
710
306
  }
711
307
  /**
712
308
  * Connect to the device by opening a channel and creating a new CASE session if necessary.
713
309
  * Returns a InteractionClient on success.
714
310
  */
715
311
  async connect(peerNodeId, discoveryOptions) {
716
- const { discoveryData } = discoveryOptions;
717
- let channel;
718
- try {
719
- channel = this.channelManager.getChannel(this.fabric, peerNodeId);
720
- } catch (error) {
721
- import_protocol.NoChannelError.accept(error);
722
- channel = await this.resume(peerNodeId, discoveryOptions);
723
- }
724
- return new import_protocol.InteractionClient(
725
- new import_protocol.ExchangeProvider(this.exchangeManager, channel, async () => {
726
- if (!this.channelManager.hasChannel(this.fabric, peerNodeId)) {
727
- throw new import_protocol.RetransmissionLimitReachedError(`Device ${peerNodeId} is currently not reachable.`);
728
- }
729
- await this.channelManager.removeAllNodeChannels(this.fabric, peerNodeId);
730
- const mdnsScanner = this.scanners.scannerFor(import_general.ChannelType.UDP);
731
- const discoveredAddresses = mdnsScanner?.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
732
- const lastKnownAddress = this.getLastOperationalAddress(peerNodeId);
733
- if (lastKnownAddress !== void 0 && discoveredAddresses !== void 0 && discoveredAddresses.addresses.some(
734
- ({ ip, port }) => ip === lastKnownAddress.ip && port === lastKnownAddress.port
735
- )) {
736
- discoveredAddresses.addresses.length = 0;
737
- }
738
- const operationalAddress = discoveredAddresses?.addresses[0];
739
- if (operationalAddress === void 0) {
740
- logger.info(
741
- `Re-Discovering device failed (no address found), remove all sessions for node ${peerNodeId}`
742
- );
743
- await this.sessionManager.removeAllSessionsForNode(peerNodeId);
744
- throw new import_protocol.RetransmissionLimitReachedError(`No operational address found for node ${peerNodeId}`);
745
- }
746
- if (await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData, 2e3) === void 0) {
747
- throw new import_protocol.RetransmissionLimitReachedError(`Device ${peerNodeId} is not reachable.`);
748
- }
749
- return this.channelManager.getChannel(this.fabric, peerNodeId);
750
- }),
751
- peerNodeId
752
- );
312
+ return this.peers.connect(this.fabric.addressOf(peerNodeId), discoveryOptions);
753
313
  }
754
314
  async getNextAvailableSessionId() {
755
315
  return this.sessionManager.getNextAvailableSessionId();
@@ -758,7 +318,7 @@ class MatterController {
758
318
  return this.sessionManager.findResumptionRecordById(resumptionId);
759
319
  }
760
320
  findResumptionRecordByNodeId(nodeId) {
761
- return this.sessionManager.findResumptionRecordByNodeId(nodeId);
321
+ return this.sessionManager.findResumptionRecordByAddress(this.fabric.addressOf(nodeId));
762
322
  }
763
323
  async saveResumptionRecord(resumptionRecord) {
764
324
  return this.sessionManager.saveResumptionRecord(resumptionRecord);
@@ -766,11 +326,7 @@ class MatterController {
766
326
  announce() {
767
327
  }
768
328
  async close() {
769
- const mdnsScanner = this.scanners.scannerFor(import_general.ChannelType.UDP);
770
- for (const [nodeId, { timer }] of this.#runningNodeDiscoveries.entries()) {
771
- timer?.stop();
772
- mdnsScanner?.cancelOperationalDeviceDiscovery(this.fabric, nodeId, false);
773
- }
329
+ await this.peers.close();
774
330
  await this.exchangeManager.close();
775
331
  await this.sessionManager.close();
776
332
  await this.channelManager.close();
@@ -780,4 +336,48 @@ class MatterController {
780
336
  return this.sessionManager.getActiveSessionInformation();
781
337
  }
782
338
  }
339
+ class CommissionedNodeStore extends import_protocol.PeerStore {
340
+ constructor(nodesStorage, fabric) {
341
+ super();
342
+ this.nodesStorage = nodesStorage;
343
+ this.fabric = fabric;
344
+ }
345
+ async loadPeers() {
346
+ if (!await this.nodesStorage.has("commissionedNodes")) {
347
+ return [];
348
+ }
349
+ const commissionedNodes = await this.nodesStorage.get("commissionedNodes");
350
+ return commissionedNodes.map(
351
+ ([nodeId, { operationalServerAddress, discoveryData, basicInformationData }]) => ({
352
+ address: this.fabric.addressOf(nodeId),
353
+ operationalAddress: operationalServerAddress,
354
+ discoveryData,
355
+ basicInformationData
356
+ })
357
+ );
358
+ }
359
+ async updatePeer() {
360
+ return this.save();
361
+ }
362
+ async deletePeer() {
363
+ return this.save();
364
+ }
365
+ async save() {
366
+ await this.nodesStorage.set(
367
+ "commissionedNodes",
368
+ this.peers.map((peer) => {
369
+ const {
370
+ address,
371
+ operationalAddress: operationalServerAddress,
372
+ basicInformationData,
373
+ discoveryData
374
+ } = peer;
375
+ return [
376
+ address.nodeId,
377
+ { operationalServerAddress, basicInformationData, discoveryData }
378
+ ];
379
+ })
380
+ );
381
+ }
382
+ }
783
383
  //# sourceMappingURL=MatterController.js.map