@matter/node 0.16.0-alpha.0-20251018-dd1ea6a8a → 0.16.0-alpha.0-20251021-cca02fb0e

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 (160) hide show
  1. package/dist/cjs/behavior/Behavior.d.ts +1 -1
  2. package/dist/cjs/behavior/Behavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/Behavior.js +29 -3
  4. package/dist/cjs/behavior/Behavior.js.map +1 -1
  5. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts +1 -1
  6. package/dist/cjs/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  7. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.d.ts.map +1 -1
  8. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js +2 -4
  9. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  10. package/dist/cjs/behavior/supervision/RootSupervisor.d.ts.map +1 -1
  11. package/dist/cjs/behavior/supervision/RootSupervisor.js +2 -1
  12. package/dist/cjs/behavior/supervision/RootSupervisor.js.map +1 -1
  13. package/dist/cjs/behavior/system/commissioning/CommissioningClient.d.ts +35 -11
  14. package/dist/cjs/behavior/system/commissioning/CommissioningClient.d.ts.map +1 -1
  15. package/dist/cjs/behavior/system/commissioning/CommissioningClient.js +155 -140
  16. package/dist/cjs/behavior/system/commissioning/CommissioningClient.js.map +2 -2
  17. package/dist/cjs/behavior/system/commissioning/CommissioningServer.d.ts +0 -7
  18. package/dist/cjs/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  19. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js +23 -27
  20. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  21. package/dist/cjs/behavior/system/commissioning/RemoteDescriptor.js +4 -4
  22. package/dist/cjs/behavior/system/commissioning/RemoteDescriptor.js.map +1 -1
  23. package/dist/cjs/behavior/system/controller/ControllerBehavior.d.ts +13 -2
  24. package/dist/cjs/behavior/system/controller/ControllerBehavior.d.ts.map +1 -1
  25. package/dist/cjs/behavior/system/controller/ControllerBehavior.js +37 -23
  26. package/dist/cjs/behavior/system/controller/ControllerBehavior.js.map +1 -1
  27. package/dist/cjs/behavior/system/network/NetworkClient.d.ts.map +1 -1
  28. package/dist/cjs/behavior/system/network/NetworkClient.js +7 -2
  29. package/dist/cjs/behavior/system/network/NetworkClient.js.map +1 -1
  30. package/dist/cjs/behavior/system/network/NetworkServer.js +1 -1
  31. package/dist/cjs/behavior/system/network/NetworkServer.js.map +1 -1
  32. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  33. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js +1 -2
  34. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  35. package/dist/cjs/behavior/system/subscriptions/SubscriptionsServer.js +3 -3
  36. package/dist/cjs/behavior/system/subscriptions/SubscriptionsServer.js.map +1 -1
  37. package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  38. package/dist/cjs/behaviors/access-control/AccessControlServer.js +2 -9
  39. package/dist/cjs/behaviors/access-control/AccessControlServer.js.map +1 -1
  40. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js +1 -1
  41. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  42. package/dist/cjs/endpoint/properties/Endpoints.d.ts +1 -1
  43. package/dist/cjs/endpoint/properties/Endpoints.d.ts.map +1 -1
  44. package/dist/cjs/node/ClientGroup.d.ts +18 -0
  45. package/dist/cjs/node/ClientGroup.d.ts.map +1 -0
  46. package/dist/cjs/node/ClientGroup.js +53 -0
  47. package/dist/cjs/node/ClientGroup.js.map +6 -0
  48. package/dist/cjs/node/ClientNode.d.ts +3 -0
  49. package/dist/cjs/node/ClientNode.d.ts.map +1 -1
  50. package/dist/cjs/node/ClientNode.js +8 -2
  51. package/dist/cjs/node/ClientNode.js.map +1 -1
  52. package/dist/cjs/node/client/ClientGroupInteraction.d.ts +22 -0
  53. package/dist/cjs/node/client/ClientGroupInteraction.d.ts.map +1 -0
  54. package/dist/cjs/node/client/ClientGroupInteraction.js +67 -0
  55. package/dist/cjs/node/client/ClientGroupInteraction.js.map +6 -0
  56. package/dist/cjs/node/client/ClientNodeFactory.d.ts +2 -1
  57. package/dist/cjs/node/client/ClientNodeFactory.d.ts.map +1 -1
  58. package/dist/cjs/node/client/ClientNodeFactory.js.map +1 -1
  59. package/dist/cjs/node/client/Peers.d.ts.map +1 -1
  60. package/dist/cjs/node/client/Peers.js +28 -18
  61. package/dist/cjs/node/client/Peers.js.map +2 -2
  62. package/dist/cjs/node/server/ServerEnvironment.d.ts.map +1 -1
  63. package/dist/cjs/node/server/ServerEnvironment.js +8 -2
  64. package/dist/cjs/node/server/ServerEnvironment.js.map +1 -1
  65. package/dist/cjs/storage/client/ClientNodeStores.d.ts +2 -0
  66. package/dist/cjs/storage/client/ClientNodeStores.d.ts.map +1 -1
  67. package/dist/cjs/storage/client/ClientNodeStores.js +19 -0
  68. package/dist/cjs/storage/client/ClientNodeStores.js.map +1 -1
  69. package/dist/esm/behavior/Behavior.d.ts +1 -1
  70. package/dist/esm/behavior/Behavior.d.ts.map +1 -1
  71. package/dist/esm/behavior/Behavior.js +29 -3
  72. package/dist/esm/behavior/Behavior.js.map +1 -1
  73. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts +1 -1
  74. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  75. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.d.ts.map +1 -1
  76. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js +3 -4
  77. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js.map +1 -1
  78. package/dist/esm/behavior/supervision/RootSupervisor.d.ts.map +1 -1
  79. package/dist/esm/behavior/supervision/RootSupervisor.js +3 -1
  80. package/dist/esm/behavior/supervision/RootSupervisor.js.map +1 -1
  81. package/dist/esm/behavior/system/commissioning/CommissioningClient.d.ts +35 -11
  82. package/dist/esm/behavior/system/commissioning/CommissioningClient.d.ts.map +1 -1
  83. package/dist/esm/behavior/system/commissioning/CommissioningClient.js +175 -143
  84. package/dist/esm/behavior/system/commissioning/CommissioningClient.js.map +2 -2
  85. package/dist/esm/behavior/system/commissioning/CommissioningServer.d.ts +0 -7
  86. package/dist/esm/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  87. package/dist/esm/behavior/system/commissioning/CommissioningServer.js +23 -27
  88. package/dist/esm/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  89. package/dist/esm/behavior/system/commissioning/RemoteDescriptor.js +4 -4
  90. package/dist/esm/behavior/system/commissioning/RemoteDescriptor.js.map +1 -1
  91. package/dist/esm/behavior/system/controller/ControllerBehavior.d.ts +13 -2
  92. package/dist/esm/behavior/system/controller/ControllerBehavior.d.ts.map +1 -1
  93. package/dist/esm/behavior/system/controller/ControllerBehavior.js +38 -25
  94. package/dist/esm/behavior/system/controller/ControllerBehavior.js.map +1 -1
  95. package/dist/esm/behavior/system/network/NetworkClient.d.ts.map +1 -1
  96. package/dist/esm/behavior/system/network/NetworkClient.js +7 -2
  97. package/dist/esm/behavior/system/network/NetworkClient.js.map +1 -1
  98. package/dist/esm/behavior/system/network/NetworkServer.js +1 -1
  99. package/dist/esm/behavior/system/network/NetworkServer.js.map +1 -1
  100. package/dist/esm/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  101. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js +1 -3
  102. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  103. package/dist/esm/behavior/system/subscriptions/SubscriptionsServer.js +3 -3
  104. package/dist/esm/behavior/system/subscriptions/SubscriptionsServer.js.map +1 -1
  105. package/dist/esm/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  106. package/dist/esm/behaviors/access-control/AccessControlServer.js +2 -9
  107. package/dist/esm/behaviors/access-control/AccessControlServer.js.map +1 -1
  108. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js +1 -1
  109. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  110. package/dist/esm/endpoint/properties/Endpoints.d.ts +1 -1
  111. package/dist/esm/endpoint/properties/Endpoints.d.ts.map +1 -1
  112. package/dist/esm/node/ClientGroup.d.ts +18 -0
  113. package/dist/esm/node/ClientGroup.d.ts.map +1 -0
  114. package/dist/esm/node/ClientGroup.js +33 -0
  115. package/dist/esm/node/ClientGroup.js.map +6 -0
  116. package/dist/esm/node/ClientNode.d.ts +3 -0
  117. package/dist/esm/node/ClientNode.d.ts.map +1 -1
  118. package/dist/esm/node/ClientNode.js +8 -2
  119. package/dist/esm/node/ClientNode.js.map +1 -1
  120. package/dist/esm/node/client/ClientGroupInteraction.d.ts +22 -0
  121. package/dist/esm/node/client/ClientGroupInteraction.d.ts.map +1 -0
  122. package/dist/esm/node/client/ClientGroupInteraction.js +47 -0
  123. package/dist/esm/node/client/ClientGroupInteraction.js.map +6 -0
  124. package/dist/esm/node/client/ClientNodeFactory.d.ts +2 -1
  125. package/dist/esm/node/client/ClientNodeFactory.d.ts.map +1 -1
  126. package/dist/esm/node/client/ClientNodeFactory.js.map +1 -1
  127. package/dist/esm/node/client/Peers.d.ts.map +1 -1
  128. package/dist/esm/node/client/Peers.js +23 -13
  129. package/dist/esm/node/client/Peers.js.map +1 -1
  130. package/dist/esm/node/server/ServerEnvironment.d.ts.map +1 -1
  131. package/dist/esm/node/server/ServerEnvironment.js +9 -3
  132. package/dist/esm/node/server/ServerEnvironment.js.map +1 -1
  133. package/dist/esm/storage/client/ClientNodeStores.d.ts +2 -0
  134. package/dist/esm/storage/client/ClientNodeStores.d.ts.map +1 -1
  135. package/dist/esm/storage/client/ClientNodeStores.js +20 -1
  136. package/dist/esm/storage/client/ClientNodeStores.js.map +1 -1
  137. package/package.json +7 -7
  138. package/src/behavior/Behavior.ts +42 -4
  139. package/src/behavior/cluster/ClusterBehavior.ts +1 -1
  140. package/src/behavior/cluster/ClusterBehaviorUtil.ts +7 -7
  141. package/src/behavior/supervision/RootSupervisor.ts +4 -2
  142. package/src/behavior/system/commissioning/CommissioningClient.ts +121 -75
  143. package/src/behavior/system/commissioning/CommissioningServer.ts +25 -30
  144. package/src/behavior/system/commissioning/RemoteDescriptor.ts +4 -4
  145. package/src/behavior/system/controller/ControllerBehavior.ts +49 -32
  146. package/src/behavior/system/network/NetworkClient.ts +8 -2
  147. package/src/behavior/system/network/NetworkServer.ts +1 -1
  148. package/src/behavior/system/network/ServerNetworkRuntime.ts +1 -3
  149. package/src/behavior/system/subscriptions/SubscriptionsServer.ts +3 -3
  150. package/src/behaviors/access-control/AccessControlServer.ts +1 -8
  151. package/src/behaviors/general-commissioning/GeneralCommissioningServer.ts +1 -1
  152. package/src/endpoint/properties/Endpoints.ts +1 -1
  153. package/src/node/ClientGroup.ts +36 -0
  154. package/src/node/ClientNode.ts +10 -2
  155. package/src/node/client/ClientEndpointInitializer.ts +1 -1
  156. package/src/node/client/ClientGroupInteraction.ts +70 -0
  157. package/src/node/client/ClientNodeFactory.ts +2 -1
  158. package/src/node/client/Peers.ts +25 -14
  159. package/src/node/server/ServerEnvironment.ts +11 -3
  160. package/src/storage/client/ClientNodeStores.ts +25 -1
@@ -296,35 +296,6 @@ export class CommissioningServer extends Behavior {
296
296
  );
297
297
  }
298
298
 
299
- /**
300
- * Obtain pairing codes for a node.
301
- */
302
- static pairingCodesFor(node: Endpoint) {
303
- const bi = node.stateOf(BasicInformationBehavior);
304
- const comm = node.stateOf(CommissioningServer);
305
- const net = node.stateOf(NetworkServer);
306
-
307
- const qrPairingCode = QrPairingCodeCodec.encode([
308
- {
309
- version: 0,
310
- vendorId: bi.vendorId,
311
- productId: bi.productId,
312
- flowType: comm.flowType,
313
- discriminator: comm.discriminator,
314
- passcode: comm.passcode,
315
- discoveryCapabilities: DiscoveryCapabilitiesSchema.encode(net.discoveryCapabilities),
316
- },
317
- ]);
318
-
319
- return {
320
- manualPairingCode: ManualPairingCodeCodec.encode({
321
- discriminator: comm.discriminator,
322
- passcode: comm.passcode,
323
- }),
324
- qrPairingCode,
325
- };
326
- }
327
-
328
299
  /**
329
300
  * Define logical schema to make passcode and discriminator persistent.
330
301
  */
@@ -384,9 +355,33 @@ export namespace CommissioningServer {
384
355
  ble?: BleAdvertiser.Options;
385
356
 
386
357
  [Val.properties](endpoint: Endpoint) {
358
+ const comm = this;
387
359
  return {
388
360
  get pairingCodes() {
389
- return CommissioningServer.pairingCodesFor(endpoint);
361
+ const bi = endpoint.stateOf(BasicInformationBehavior);
362
+ const net = endpoint.stateOf(NetworkServer);
363
+
364
+ const qrPairingCode = QrPairingCodeCodec.encode([
365
+ {
366
+ version: 0,
367
+ vendorId: bi.vendorId,
368
+ productId: bi.productId,
369
+ flowType: comm.flowType,
370
+ discriminator: comm.discriminator,
371
+ passcode: comm.passcode,
372
+ discoveryCapabilities: DiscoveryCapabilitiesSchema.encode(net.discoveryCapabilities),
373
+ },
374
+ ]);
375
+
376
+ return {
377
+ manualPairingCode: ManualPairingCodeCodec.encode({
378
+ // We use -1 to flag "need generated value" but this will crash the pairing code generator. So use 0
379
+ // so we don't throw an error during initialization
380
+ discriminator: comm.discriminator < 0 ? 0 : comm.discriminator,
381
+ passcode: comm.passcode < 0 ? 0 : comm.passcode,
382
+ }),
383
+ qrPairingCode,
384
+ };
390
385
  },
391
386
 
392
387
  get fabrics() {
@@ -57,7 +57,7 @@ export namespace RemoteDescriptor {
57
57
  rotatingIdentifier,
58
58
  pairingHint,
59
59
  pairingInstructions,
60
- sessionParameters,
60
+ sessionIntervals,
61
61
  tcpSupport,
62
62
  longIdleTimeOperatingMode,
63
63
  } = long;
@@ -102,8 +102,8 @@ export namespace RemoteDescriptor {
102
102
  result.PI = pairingInstructions;
103
103
  }
104
104
 
105
- if (sessionParameters !== undefined) {
106
- const { idleInterval, activeInterval, activeThreshold } = sessionParameters;
105
+ if (sessionIntervals !== undefined) {
106
+ const { idleInterval, activeInterval, activeThreshold } = sessionIntervals;
107
107
 
108
108
  if (idleInterval !== undefined) {
109
109
  result.SII = idleInterval;
@@ -189,7 +189,7 @@ export namespace RemoteDescriptor {
189
189
  if (SAT !== undefined) {
190
190
  (sessionParameters ??= {}).activeThreshold = SAT;
191
191
  }
192
- long.sessionParameters = sessionParameters;
192
+ long.sessionIntervals = sessionParameters;
193
193
 
194
194
  long.deviceType = DT === undefined ? undefined : DeviceTypeId(DT);
195
195
  long.deviceName = DN;
@@ -6,14 +6,14 @@
6
6
 
7
7
  import { Behavior } from "#behavior/Behavior.js";
8
8
  import { BasicInformationBehavior } from "#behaviors/basic-information";
9
- import { ConnectionlessTransportSet, ImplementationError } from "#general";
9
+ import { ConnectionlessTransportSet, ImplementationError, Logger } from "#general";
10
10
  import { Node } from "#node/Node.js";
11
11
  import {
12
12
  Ble,
13
13
  ClientSubscriptions,
14
14
  Fabric,
15
15
  FabricAuthority,
16
- FabricAuthorityConfigurationProvider,
16
+ FabricAuthorityConfiguration,
17
17
  FabricManager,
18
18
  MdnsClient,
19
19
  MdnsScannerTargetCriteria,
@@ -21,13 +21,15 @@ import {
21
21
  Scanner,
22
22
  ScannerSet,
23
23
  } from "#protocol";
24
- import { FabricId } from "@matter/types";
24
+ import { CaseAuthenticatedTag, FabricId, NodeId } from "#types";
25
25
  import type { CommissioningClient } from "../commissioning/CommissioningClient.js";
26
26
  import { CommissioningServer } from "../commissioning/CommissioningServer.js";
27
27
  import { NetworkServer } from "../network/NetworkServer.js";
28
28
  import { ActiveDiscoveries } from "./discovery/ActiveDiscoveries.js";
29
29
  import type { Discovery } from "./discovery/Discovery.js";
30
30
 
31
+ const logger = Logger.get("ControllerBehavior");
32
+
31
33
  /**
32
34
  * Node controller functionality.
33
35
  *
@@ -44,10 +46,8 @@ export class ControllerBehavior extends Behavior {
44
46
 
45
47
  override async initialize() {
46
48
  if (this.state.adminFabricLabel === undefined || this.state.adminFabricLabel === "") {
47
- throw new ImplementationError("adminFabricLabel must be set for ControllerBehavior.");
49
+ throw new ImplementationError("adminFabricLabel must be set for ControllerBehavior");
48
50
  }
49
- const adminFabricLabel = this.state.adminFabricLabel;
50
- const adminFabricId = this.state.adminFabricId;
51
51
 
52
52
  // Configure discovery transports
53
53
  if (this.state.ip === undefined) {
@@ -61,28 +61,12 @@ export class ControllerBehavior extends Behavior {
61
61
  this.state.ble = (await this.agent.load(NetworkServer)).state.ble;
62
62
  }
63
63
  if (this.state.ble !== false) {
64
- this.env.get(ScannerSet).add(this.env.get(Ble).scanner);
65
- }
66
-
67
- // Configure management of controlled fabrics
68
- if (!this.env.has(FabricAuthorityConfigurationProvider)) {
69
- const biState = this.endpoint.stateOf(BasicInformationBehavior);
70
- this.env.set(
71
- FabricAuthorityConfigurationProvider,
72
- new (class extends FabricAuthorityConfigurationProvider {
73
- get vendorId() {
74
- return biState.vendorId;
75
- }
76
-
77
- override get adminFabricLabel() {
78
- return adminFabricLabel;
79
- }
80
-
81
- get fabricId() {
82
- return adminFabricId;
83
- }
84
- })(),
85
- );
64
+ try {
65
+ this.env.get(ScannerSet).add(this.env.get(Ble).scanner);
66
+ } catch (error) {
67
+ logger.error("Disabling BLE due to initialization error:", error);
68
+ this.state.ble = false;
69
+ }
86
70
  }
87
71
 
88
72
  // Ensure the fabric authority is fully initialized
@@ -104,6 +88,7 @@ export class ControllerBehavior extends Behavior {
104
88
  if (node.lifecycle.isOnline) {
105
89
  this.#nodeOnline();
106
90
  }
91
+ this.reactTo(node.lifecycle.goingOffline, this.#nodeGoingOffline);
107
92
  }
108
93
 
109
94
  override async [Symbol.asyncDispose]() {
@@ -112,17 +97,22 @@ export class ControllerBehavior extends Behavior {
112
97
  this.env.delete(ScannerSet);
113
98
  }
114
99
 
100
+ get fabricAuthorityConfig(): FabricAuthorityConfiguration {
101
+ const biState = this.endpoint.stateOf(BasicInformationBehavior);
102
+ return {
103
+ adminVendorId: biState.vendorId,
104
+ ...this.state,
105
+ };
106
+ }
107
+
115
108
  #nodeOnline() {
116
109
  // Configure network connections
117
110
  const netTransports = this.env.get(ConnectionlessTransportSet);
118
111
  if (this.state.ble) {
112
+ // no try-catch needed because we already added the scanner in initialize()
119
113
  netTransports.add(this.env.get(Ble).centralInterface);
120
114
  }
121
115
 
122
- // Clean up as the node goes offline
123
- const node = Node.forEndpoint(this.endpoint);
124
- this.reactTo(node.lifecycle.goingOffline, this.#nodeGoingOffline);
125
-
126
116
  // Add each pre-existing fabric to discovery criteria
127
117
  const authority = this.env.get(FabricAuthority);
128
118
  for (const fabric of authority.fabrics) {
@@ -140,6 +130,21 @@ export class ControllerBehavior extends Behavior {
140
130
 
141
131
  async #nodeGoingOffline() {
142
132
  await this.env.close(ClientSubscriptions);
133
+
134
+ // Configure each MDNS scanner with criteria
135
+ const scanners = this.env.get(ScannerSet);
136
+ for (const scanner of scanners) {
137
+ if (scanner instanceof MdnsClient) {
138
+ scanner.targetCriteriaProviders.delete(this.internal.mdnsTargetCriteria);
139
+ }
140
+ }
141
+ // Clear operational targets
142
+ this.internal.mdnsTargetCriteria.operationalTargets.length = 0;
143
+
144
+ const netTransports = this.env.get(ConnectionlessTransportSet);
145
+ if (this.state.ble) {
146
+ netTransports.delete(this.env.get(Ble).centralInterface);
147
+ }
143
148
  }
144
149
 
145
150
  #enableScanningForFabric(fabric: Fabric) {
@@ -191,5 +196,17 @@ export namespace ControllerBehavior {
191
196
  * If not provided, a random FabricId will be generated.
192
197
  */
193
198
  adminFabricId?: FabricId = undefined;
199
+
200
+ /**
201
+ * Contains the NodeId of the admin node when a defined number needs to be used because special Certificates
202
+ * are used.
203
+ * If not provided, a random NodeId will be generated.
204
+ */
205
+ adminNodeId?: NodeId = undefined;
206
+
207
+ /**
208
+ * Case Authenticated Tags to be used to commission and connect to devices.
209
+ */
210
+ caseAuthenticatedTags?: CaseAuthenticatedTag[] = undefined;
194
211
  }
195
212
  }
@@ -20,8 +20,14 @@ export class NetworkClient extends NetworkBehavior {
20
20
  declare events: NetworkClient.Events;
21
21
 
22
22
  override initialize() {
23
- this.reactTo(this.events.autoSubscribe$Changed, this.#handleSubscription);
24
- this.reactTo(this.events.defaultSubscription$Changed, this.#handleChangedDefaultSubscription);
23
+ if (this.#node.isGroup) {
24
+ // Groups can never subscribe
25
+ this.state.autoSubscribe = false;
26
+ this.state.defaultSubscription = undefined;
27
+ } else {
28
+ this.reactTo(this.events.autoSubscribe$Changed, this.#handleSubscription, { offline: true });
29
+ this.reactTo(this.events.defaultSubscription$Changed, this.#handleChangedDefaultSubscription);
30
+ }
25
31
  }
26
32
 
27
33
  override async startup() {
@@ -49,7 +49,7 @@ export class NetworkServer extends NetworkBehavior {
49
49
  discoveryCaps.onIpNetwork = true;
50
50
  }
51
51
 
52
- this.reactTo(this.agent.get(CommissioningServer).events.commissioned, this.#endUncommissionedMode);
52
+ this.reactTo(this.endpoint.eventsOf(CommissioningServer).commissioned, this.#endUncommissionedMode);
53
53
 
54
54
  return super.initialize();
55
55
  }
@@ -18,7 +18,6 @@ import {
18
18
  UdpInterface,
19
19
  } from "#general";
20
20
  import type { ServerNode } from "#node/ServerNode.js";
21
- import { NodePeerAddressStore } from "#node/client/NodePeerAddressStore.js";
22
21
  import { InteractionServer } from "#node/server/InteractionServer.js";
23
22
  import {
24
23
  Advertiser,
@@ -30,7 +29,6 @@ import {
30
29
  ExchangeManager,
31
30
  MdnsAdvertiser,
32
31
  MdnsService,
33
- PeerAddressStore,
34
32
  PeerSet,
35
33
  ScannerSet,
36
34
  SecureChannelProtocol,
@@ -294,7 +292,6 @@ export class ServerNetworkRuntime extends NetworkRuntime {
294
292
  // Initialize ScannerSet
295
293
  this.owner.env.get(ScannerSet).add(mdns.client);
296
294
 
297
- env.set(PeerAddressStore, new NodePeerAddressStore(owner));
298
295
  await env.load(PeerSet);
299
296
 
300
297
  await this.owner.act(agent => this.owner.lifecycle.online.emit(agent.context));
@@ -327,6 +324,7 @@ export class ServerNetworkRuntime extends NetworkRuntime {
327
324
  await env.close(SecureChannelProtocol);
328
325
  await env.close(ConnectionlessTransportSet);
329
326
  await env.close(InteractionServer);
327
+ await env.close(PeerSet);
330
328
  }
331
329
 
332
330
  protected override blockNewActivity() {
@@ -227,10 +227,10 @@ export class SubscriptionsBehavior extends Behavior {
227
227
  const { peerAddress: peerAddressDetails, operationalAddress, subscriptionId } = subscription;
228
228
  const peerAddress = PeerAddress(peerAddressDetails);
229
229
  if (peerStopList.has(peerAddress)) {
230
- logger.debug(`Skip re-establishing former subscription to ${peerAddress}`);
230
+ logger.debug(`Skip reestablishing former subscription to ${peerAddress}`);
231
231
  continue;
232
232
  }
233
- logger.debug(`Try to re-establish former subscription ${subscriptionId} to ${peerAddress}`);
233
+ logger.debug(`Try to reestablish former subscription ${subscriptionId} to ${peerAddress}`);
234
234
  if (sessions.getSessionForNode(peerAddress) !== undefined) {
235
235
  logger.debug(`We already have and existing session for peer ${peerAddress}`);
236
236
  } else {
@@ -280,7 +280,7 @@ export class SubscriptionsBehavior extends Behavior {
280
280
  interactionServer.subscriptionEstablishmentStarted.off(blockHandler);
281
281
 
282
282
  logger.info(
283
- `Re-established ${successfullReEstablishments.length}${successfullReEstablishments.length ? ` (${successfullReEstablishments.join(",")})` : ""} of ${formerSubscriptions.length} former subscriptions successfully`,
283
+ `Reestablished ${successfullReEstablishments.length}${successfullReEstablishments.length ? ` (${successfullReEstablishments.join(",")})` : ""} of ${formerSubscriptions.length} former subscriptions successfully`,
284
284
  );
285
285
  }
286
286
  }
@@ -77,21 +77,14 @@ export class AccessControlServer extends AccessControlBehavior.with("Extension")
77
77
  // Handle Backward compatibility to Matter.js before 0.9.1 and add the missing ACL entry if no entry was set
78
78
  // so far by the controller
79
79
  const fallbackAcl: AccessControlTypes.AccessControlEntry = {
80
- fabricIndex: fabric.fabricIndex,
81
80
  privilege: AccessControlTypes.AccessControlEntryPrivilege.Administer,
82
81
  authMode: AccessControlTypes.AccessControlEntryAuthMode.Case,
83
82
  subjects: [fabric.rootNodeId],
84
83
  targets: null, // entire node
84
+ fabricIndex: fabric.fabricIndex,
85
85
  };
86
86
  this.state.acl.push(fallbackAcl);
87
87
  fabricAcls.push(fallbackAcl);
88
- logger.warn(
89
- "Added missing ACL entry for fabric",
90
- fabric.fabricIndex,
91
- "for Node ID",
92
- fabric.rootNodeId,
93
- ". This should only happen once after upgrading to matter.js 0.9.1",
94
- );
95
88
  }
96
89
  fabric.accessControl.aclList = fabricAcls;
97
90
  fabric.accessControl.extensionEntryAccessCheck = this.extensionEntryAccessCheck.bind(this);
@@ -99,7 +99,7 @@ export class GeneralCommissioningServer extends GeneralCommissioningBehavior {
99
99
  sessions: this.env.get(SessionManager),
100
100
  expiryLength: Seconds(expiryLengthSeconds),
101
101
  maxCumulativeFailsafe: Seconds(this.state.basicCommissioningInfo.maxCumulativeFailsafeSeconds),
102
- associatedFabric: session.fabric,
102
+ session,
103
103
  });
104
104
 
105
105
  // Note - this used to be async and wait for construction internally. However that leads to race
@@ -8,7 +8,7 @@ import { IndexBehavior } from "#behavior/system/index/IndexBehavior.js";
8
8
  import type { Endpoint } from "#endpoint/Endpoint.js";
9
9
  import { EndpointType } from "#endpoint/type/EndpointType.js";
10
10
  import type { ImmutableSet } from "#general";
11
- import { Node } from "#node/Node.js";
11
+ import type { Node } from "#node/Node.js";
12
12
  import { StatusResponse } from "#types";
13
13
 
14
14
  /**
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import type { ActionContext } from "#behavior/context/ActionContext.js";
7
+ import { Interactable } from "#protocol";
8
+ import { ServerNodeStore } from "#storage/index.js";
9
+ import { ClientNode } from "./ClientNode.js";
10
+ import { ClientGroupInteraction } from "./client/ClientGroupInteraction.js";
11
+
12
+ export class ClientGroup extends ClientNode {
13
+ #interaction?: ClientGroupInteraction;
14
+
15
+ override get isGroup() {
16
+ return true;
17
+ }
18
+
19
+ override get interaction(): Interactable<ActionContext> {
20
+ if (this.#interaction === undefined) {
21
+ this.#interaction = new ClientGroupInteraction(this);
22
+ }
23
+
24
+ return this.#interaction;
25
+ }
26
+
27
+ protected override get store() {
28
+ return this.env.get(ServerNodeStore).clientStores.storeForGroup(this);
29
+ }
30
+ }
31
+
32
+ export namespace ClientGroup {
33
+ export function is(value: unknown): value is ClientGroup {
34
+ return value instanceof ClientGroup;
35
+ }
36
+ }
@@ -53,6 +53,10 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
53
53
  this.#matter = options.matter ?? Matter;
54
54
  }
55
55
 
56
+ get isGroup() {
57
+ return false;
58
+ }
59
+
56
60
  /**
57
61
  * Model of Matter semantics understood by this node.
58
62
  *
@@ -66,8 +70,12 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
66
70
  return new ClientNodeEndpoints(this);
67
71
  }
68
72
 
73
+ protected get store() {
74
+ return this.env.get(ServerNodeStore).clientStores.storeForNode(this);
75
+ }
76
+
69
77
  override initialize() {
70
- const store = this.env.get(ServerNodeStore).clientStores.storeForNode(this);
78
+ const store = this.store;
71
79
 
72
80
  this.env.set(ClientNodeStore, store);
73
81
 
@@ -202,7 +210,7 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
202
210
 
203
211
  // During early initialization commissioning state may not be loaded, so check directly in storage too
204
212
  if (!address) {
205
- address = this.env.get(ClientNodeStore).storeForEndpoint(this).peerAddress as PeerAddress | undefined;
213
+ address = this.store.storeForEndpoint(this).peerAddress as PeerAddress | undefined;
206
214
  }
207
215
 
208
216
  // Use the peer address as a log identifier if present
@@ -49,7 +49,7 @@ export class ClientEndpointInitializer extends EndpointInitializer {
49
49
  }
50
50
 
51
51
  override createBacking(endpoint: Endpoint, type: Behavior.Type): BehaviorBacking {
52
- // Non-cluster behaviors are local operation the same server behaviors
52
+ // Non-cluster behaviors are local, operating the same server behaviors
53
53
  if ((type as ClusterBehavior.Type).cluster === undefined) {
54
54
  const store = this.structure.storeForLocal(endpoint, type);
55
55
  return new ServerBehaviorBacking(endpoint, type, store, endpoint.behaviors.optionsFor(type));
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type { ActionContext } from "#behavior/index.js";
8
+ import { ImplementationError } from "#general";
9
+ import {
10
+ ClientInvoke,
11
+ DecodedInvokeResult,
12
+ Read,
13
+ ReadResult,
14
+ Subscribe,
15
+ SubscribeResult,
16
+ Write,
17
+ WriteResult,
18
+ } from "#protocol";
19
+ import { ClientNodeInteraction } from "./ClientNodeInteraction.js";
20
+
21
+ export class InvalidGroupOperationError extends ImplementationError {}
22
+
23
+ export class ClientGroupInteraction extends ClientNodeInteraction {
24
+ /** Groups do not support reading or subscribing to attributes */
25
+ override read(_request: Read, _context?: ActionContext): ReadResult {
26
+ throw new InvalidGroupOperationError("Groups do not support reading attributes");
27
+ }
28
+
29
+ /** Groups do not support reading or subscribing to attributes */
30
+ override async subscribe(_request: Subscribe, _context?: ActionContext): SubscribeResult {
31
+ throw new InvalidGroupOperationError("Groups do not support subscribing to attributes");
32
+ }
33
+
34
+ override async write<T extends Write>(
35
+ request: T,
36
+ context?: ActionContext,
37
+ ): WriteResult<T & { suppressResponse: true }> {
38
+ if (request.timedRequest) {
39
+ throw new InvalidGroupOperationError("Timed requests are not supported for group address writes.");
40
+ }
41
+
42
+ if (request.suppressResponse === false) {
43
+ // If flag was explicitly set to false, we cannot comply
44
+ throw new InvalidGroupOperationError("Writing attributes on a group address can not return a response.");
45
+ }
46
+
47
+ if (
48
+ request.writeRequests.some(
49
+ ({ path: { endpointId, clusterId, attributeId } }) =>
50
+ endpointId !== undefined || clusterId === undefined || attributeId === undefined,
51
+ )
52
+ ) {
53
+ throw new InvalidGroupOperationError("Not all attribute write paths are valid for group address writes.");
54
+ }
55
+
56
+ // Writing to a group does not yield a response
57
+ return super.write({ ...request, suppressResponse: true }, context);
58
+ }
59
+
60
+ override invoke(request: ClientInvoke, context?: ActionContext): DecodedInvokeResult {
61
+ if (request.invokeRequests.some(({ commandPath: { endpointId } }) => endpointId !== undefined)) {
62
+ throw new InvalidGroupOperationError("Invoking a concrete command on a group address is not supported.");
63
+ }
64
+ if (request.timedRequest) {
65
+ throw new InvalidGroupOperationError("Timed requests are not supported for group address invokes.");
66
+ }
67
+
68
+ return super.invoke(request, context);
69
+ }
70
+ }
@@ -7,6 +7,7 @@
7
7
  import type { RemoteDescriptor } from "#behavior/system/commissioning/RemoteDescriptor.js";
8
8
  import type { ImmutableSet } from "#general";
9
9
  import type { ClientNode } from "#node/ClientNode.js";
10
+ import type { PeerAddress } from "#protocol";
10
11
 
11
12
  /**
12
13
  * Create a new client node.
@@ -16,6 +17,6 @@ import type { ClientNode } from "#node/ClientNode.js";
16
17
  */
17
18
  export abstract class ClientNodeFactory {
18
19
  abstract find(descriptor: RemoteDescriptor): ClientNode | undefined;
19
- abstract create(options: ClientNode.Options): ClientNode;
20
+ abstract create(options: ClientNode.Options, peerAddress?: PeerAddress): ClientNode;
20
21
  abstract nodes: ImmutableSet<ClientNode>;
21
22
  }
@@ -11,14 +11,13 @@ import { Discovery } from "#behavior/system/controller/discovery/Discovery.js";
11
11
  import { InstanceDiscovery } from "#behavior/system/controller/discovery/InstanceDiscovery.js";
12
12
  import { EndpointContainer } from "#endpoint/properties/EndpointContainer.js";
13
13
  import { CancelablePromise, Duration, ImplementationError, Logger, Minutes, Seconds, Time, Timestamp } from "#general";
14
- import { InteractionServer } from "#node/index.js";
15
- import { FabricManager, PeerAddress, PeerAddressStore } from "#protocol";
14
+ import { ClientGroup } from "#node/ClientGroup.js";
15
+ import { InteractionServer } from "#node/server/InteractionServer.js";
16
+ import { ClientSubscriptionHandler, ClientSubscriptions, FabricManager, PeerAddress } from "#protocol";
16
17
  import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
17
- import { ClientSubscriptionHandler, ClientSubscriptions } from "@matter/protocol";
18
18
  import { ClientNode } from "../ClientNode.js";
19
19
  import type { ServerNode } from "../ServerNode.js";
20
20
  import { ClientNodeFactory } from "./ClientNodeFactory.js";
21
- import { NodePeerAddressStore } from "./NodePeerAddressStore.js";
22
21
 
23
22
  const logger = Logger.get("ClientNodes");
24
23
 
@@ -43,8 +42,6 @@ export class Peers extends EndpointContainer<ClientNode> {
43
42
  owner.env.set(ClientNodeFactory, new Factory(this));
44
43
  }
45
44
 
46
- this.owner.env.set(PeerAddressStore, new NodePeerAddressStore(owner));
47
-
48
45
  owner.env.applyTo(InteractionServer, this.#configureInteractionServer.bind(this));
49
46
 
50
47
  this.added.on(this.#handlePeerAdded.bind(this));
@@ -58,6 +55,7 @@ export class Peers extends EndpointContainer<ClientNode> {
58
55
  const factory = this.owner.env.get(ClientNodeFactory);
59
56
 
60
57
  const clientStores = this.owner.env.get(ServerNodeStore).clientStores;
58
+ // Group nodes have an in-memory only store, so all nodes restored here are ClientNode
61
59
  for (const id of clientStores.knownIds) {
62
60
  this.add(
63
61
  factory.create({
@@ -131,7 +129,7 @@ export class Peers extends EndpointContainer<ClientNode> {
131
129
  if (!node) {
132
130
  // We do not have that node till now, also not persisted, so create it
133
131
  const factory = this.owner.env.get(ClientNodeFactory);
134
- node = factory.create(options);
132
+ node = factory.create(options, peerAddress);
135
133
  await node.construction;
136
134
  this.add(node);
137
135
 
@@ -278,20 +276,33 @@ export class Peers extends EndpointContainer<ClientNode> {
278
276
 
279
277
  class Factory extends ClientNodeFactory {
280
278
  #owner: Peers;
279
+ #groupIdCounter = 0;
281
280
 
282
281
  constructor(owner: Peers) {
283
282
  super();
284
283
  this.#owner = owner;
285
284
  }
286
285
 
287
- create(options: ClientNode.Options) {
288
- if (options.id === undefined) {
289
- options.id = this.#owner.owner.env.get(ServerNodeStore).clientStores.allocateId();
286
+ create(options: ClientNode.Options, peerAddress?: PeerAddress) {
287
+ let node: ClientNode;
288
+ if (peerAddress !== undefined && PeerAddress.isGroup(peerAddress)) {
289
+ if (options.id === undefined) {
290
+ options.id = `group${++this.#groupIdCounter}`;
291
+ }
292
+ node = new ClientGroup({
293
+ ...options,
294
+ owner: this.#owner.owner,
295
+ });
296
+ } else {
297
+ if (options.id === undefined) {
298
+ options.id = this.#owner.owner.env.get(ServerNodeStore).clientStores.allocateId();
299
+ }
300
+ node = new ClientNode({
301
+ ...options,
302
+ owner: this.#owner.owner,
303
+ });
290
304
  }
291
- const node = new ClientNode({
292
- ...options,
293
- owner: this.#owner.owner,
294
- });
305
+
295
306
  node.construction.start();
296
307
  return node;
297
308
  }
@@ -7,10 +7,11 @@
7
7
  import { limitNodeDataToAllowedFabrics } from "#behavior/cluster/FabricScopedDataHandler.js";
8
8
  import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
9
9
  import { Crypto, Observable } from "#general";
10
+ import { NodePeerAddressStore } from "#node/client/NodePeerAddressStore.js";
10
11
  import { ChangeNotificationService } from "#node/integration/ChangeNotificationService.js";
11
12
  import { ServerEndpointInitializer } from "#node/server/ServerEndpointInitializer.js";
12
13
  import type { ServerNode } from "#node/ServerNode.js";
13
- import { FabricManager, MdnsService, OccurrenceManager, SessionManager } from "#protocol";
14
+ import { FabricManager, MdnsService, OccurrenceManager, PeerAddressStore, SessionManager } from "#protocol";
14
15
  import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
15
16
  import { IdentityService } from "./IdentityService.js";
16
17
 
@@ -25,10 +26,17 @@ export namespace ServerEnvironment {
25
26
  const { env } = node;
26
27
 
27
28
  // Install support services
28
- const store = await ServerNodeStore.create(env, node.id);
29
- env.set(ServerNodeStore, store);
29
+ if (!env.has(ServerNodeStore)) {
30
+ // TODO Remove the "if" once the legacy controller is removed
31
+ const store = await ServerNodeStore.create(env, node.id);
32
+ env.set(ServerNodeStore, store);
33
+ }
30
34
  env.set(EndpointInitializer, new ServerEndpointInitializer(env));
31
35
  env.set(IdentityService, new IdentityService(node));
36
+ if (!env.has(PeerAddressStore)) {
37
+ // TODO Remove the "if" once the legacy controller is removed
38
+ env.set(PeerAddressStore, new NodePeerAddressStore(node));
39
+ }
32
40
  env.set(ChangeNotificationService, new ChangeNotificationService(node));
33
41
 
34
42
  // Ensure these are fully initialized