@matter/node 0.16.0-alpha.0-20251108-514b3f69e → 0.16.0-alpha.0-20251110-c4c70a41b

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 (212) hide show
  1. package/dist/cjs/behavior/cluster/ClientBehavior.d.ts +0 -7
  2. package/dist/cjs/behavior/cluster/ClientBehavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/cluster/ClientBehavior.js +2 -8
  4. package/dist/cjs/behavior/cluster/ClientBehavior.js.map +2 -2
  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/ClusterBehavior.js +9 -4
  8. package/dist/cjs/behavior/cluster/ClusterBehavior.js.map +1 -1
  9. package/dist/cjs/behavior/cluster/ClusterBehaviorCache.d.ts +7 -1
  10. package/dist/cjs/behavior/cluster/ClusterBehaviorCache.d.ts.map +1 -1
  11. package/dist/cjs/behavior/cluster/ClusterBehaviorCache.js +7 -5
  12. package/dist/cjs/behavior/cluster/ClusterBehaviorCache.js.map +1 -1
  13. package/dist/cjs/behavior/cluster/ClusterBehaviorType.d.ts +57 -0
  14. package/dist/cjs/behavior/cluster/ClusterBehaviorType.d.ts.map +1 -0
  15. package/dist/cjs/behavior/cluster/{ClusterBehaviorUtil.js → ClusterBehaviorType.js} +70 -39
  16. package/dist/cjs/behavior/cluster/ClusterBehaviorType.js.map +6 -0
  17. package/dist/cjs/behavior/cluster/ClusterEvents.d.ts +1 -1
  18. package/dist/cjs/behavior/cluster/ClusterEvents.d.ts.map +1 -1
  19. package/dist/cjs/behavior/cluster/ClusterState.d.ts +1 -1
  20. package/dist/cjs/behavior/cluster/ClusterState.d.ts.map +1 -1
  21. package/dist/cjs/behavior/cluster/ValidatedElements.js +2 -2
  22. package/dist/cjs/behavior/cluster/ValidatedElements.js.map +1 -1
  23. package/dist/cjs/behavior/cluster/{ClusterBehaviorUtil.d.ts → cluster-behavior-utils.d.ts} +10 -8
  24. package/dist/cjs/behavior/cluster/cluster-behavior-utils.d.ts.map +1 -0
  25. package/dist/cjs/behavior/cluster/cluster-behavior-utils.js +41 -0
  26. package/dist/cjs/behavior/cluster/cluster-behavior-utils.js.map +6 -0
  27. package/dist/cjs/behavior/cluster/index.d.ts +2 -1
  28. package/dist/cjs/behavior/cluster/index.d.ts.map +1 -1
  29. package/dist/cjs/behavior/cluster/index.js +2 -1
  30. package/dist/cjs/behavior/cluster/index.js.map +1 -1
  31. package/dist/cjs/behavior/context/server/LocalActorContext.d.ts +4 -0
  32. package/dist/cjs/behavior/context/server/LocalActorContext.d.ts.map +1 -1
  33. package/dist/cjs/behavior/context/server/LocalActorContext.js +2 -3
  34. package/dist/cjs/behavior/context/server/LocalActorContext.js.map +1 -1
  35. package/dist/cjs/behavior/context/server/RemoteActorContext.d.ts +4 -0
  36. package/dist/cjs/behavior/context/server/RemoteActorContext.d.ts.map +1 -1
  37. package/dist/cjs/behavior/context/server/RemoteActorContext.js +1 -0
  38. package/dist/cjs/behavior/context/server/RemoteActorContext.js.map +1 -1
  39. package/dist/cjs/behavior/internal/BehaviorBacking.js +2 -2
  40. package/dist/cjs/behavior/internal/BehaviorBacking.js.map +1 -1
  41. package/dist/cjs/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  42. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js +3 -3
  43. package/dist/cjs/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  44. package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js +1 -1
  45. package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
  46. package/dist/cjs/behaviors/operational-credentials/VendorIdVerification.d.ts +6 -1
  47. package/dist/cjs/behaviors/operational-credentials/VendorIdVerification.d.ts.map +1 -1
  48. package/dist/cjs/behaviors/operational-credentials/VendorIdVerification.js +2 -2
  49. package/dist/cjs/behaviors/operational-credentials/VendorIdVerification.js.map +1 -1
  50. package/dist/cjs/endpoint/Endpoint.d.ts +8 -0
  51. package/dist/cjs/endpoint/Endpoint.d.ts.map +1 -1
  52. package/dist/cjs/endpoint/Endpoint.js +11 -3
  53. package/dist/cjs/endpoint/Endpoint.js.map +1 -1
  54. package/dist/cjs/endpoint/properties/Behaviors.d.ts +1 -1
  55. package/dist/cjs/endpoint/properties/Behaviors.d.ts.map +1 -1
  56. package/dist/cjs/endpoint/properties/Behaviors.js +6 -3
  57. package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
  58. package/dist/cjs/endpoint/properties/EndpointInitializer.d.ts +0 -4
  59. package/dist/cjs/endpoint/properties/EndpointInitializer.d.ts.map +1 -1
  60. package/dist/cjs/endpoint/properties/EndpointInitializer.js +0 -6
  61. package/dist/cjs/endpoint/properties/EndpointInitializer.js.map +1 -1
  62. package/dist/cjs/node/Node.d.ts.map +1 -1
  63. package/dist/cjs/node/Node.js +2 -1
  64. package/dist/cjs/node/Node.js.map +1 -1
  65. package/dist/cjs/node/client/ClientCommandMethod.d.ts +11 -0
  66. package/dist/cjs/node/client/ClientCommandMethod.d.ts.map +1 -0
  67. package/dist/cjs/node/client/ClientCommandMethod.js +69 -0
  68. package/dist/cjs/node/client/ClientCommandMethod.js.map +6 -0
  69. package/dist/cjs/node/client/ClientEndpointInitializer.d.ts +0 -2
  70. package/dist/cjs/node/client/ClientEndpointInitializer.d.ts.map +1 -1
  71. package/dist/cjs/node/client/ClientEndpointInitializer.js +3 -34
  72. package/dist/cjs/node/client/ClientEndpointInitializer.js.map +1 -1
  73. package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
  74. package/dist/cjs/node/client/ClientStructure.js +164 -32
  75. package/dist/cjs/node/client/ClientStructure.js.map +2 -2
  76. package/dist/cjs/node/client/ClientStructureEvents.d.ts +22 -0
  77. package/dist/cjs/node/client/ClientStructureEvents.d.ts.map +1 -0
  78. package/dist/cjs/node/client/ClientStructureEvents.js +92 -0
  79. package/dist/cjs/node/client/ClientStructureEvents.js.map +6 -0
  80. package/dist/cjs/node/client/PeerBehavior.d.ts +14 -1
  81. package/dist/cjs/node/client/PeerBehavior.d.ts.map +1 -1
  82. package/dist/cjs/node/client/PeerBehavior.js +111 -71
  83. package/dist/cjs/node/client/PeerBehavior.js.map +1 -1
  84. package/dist/cjs/node/client/Peers.d.ts +17 -0
  85. package/dist/cjs/node/client/Peers.d.ts.map +1 -1
  86. package/dist/cjs/node/client/Peers.js +96 -42
  87. package/dist/cjs/node/client/Peers.js.map +1 -1
  88. package/dist/cjs/node/server/ServerSubscription.d.ts +1 -1
  89. package/dist/cjs/node/server/ServerSubscription.d.ts.map +1 -1
  90. package/dist/cjs/node/server/ServerSubscription.js +2 -3
  91. package/dist/cjs/node/server/ServerSubscription.js.map +1 -1
  92. package/dist/esm/behavior/cluster/ClientBehavior.d.ts +0 -7
  93. package/dist/esm/behavior/cluster/ClientBehavior.d.ts.map +1 -1
  94. package/dist/esm/behavior/cluster/ClientBehavior.js +2 -8
  95. package/dist/esm/behavior/cluster/ClientBehavior.js.map +2 -2
  96. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts +1 -1
  97. package/dist/esm/behavior/cluster/ClusterBehavior.d.ts.map +1 -1
  98. package/dist/esm/behavior/cluster/ClusterBehavior.js +9 -4
  99. package/dist/esm/behavior/cluster/ClusterBehavior.js.map +1 -1
  100. package/dist/esm/behavior/cluster/ClusterBehaviorCache.d.ts +7 -1
  101. package/dist/esm/behavior/cluster/ClusterBehaviorCache.d.ts.map +1 -1
  102. package/dist/esm/behavior/cluster/ClusterBehaviorCache.js +7 -5
  103. package/dist/esm/behavior/cluster/ClusterBehaviorCache.js.map +1 -1
  104. package/dist/esm/behavior/cluster/ClusterBehaviorType.d.ts +57 -0
  105. package/dist/esm/behavior/cluster/ClusterBehaviorType.d.ts.map +1 -0
  106. package/dist/esm/behavior/cluster/{ClusterBehaviorUtil.js → ClusterBehaviorType.js} +66 -35
  107. package/dist/esm/behavior/cluster/ClusterBehaviorType.js.map +6 -0
  108. package/dist/esm/behavior/cluster/ClusterEvents.d.ts +1 -1
  109. package/dist/esm/behavior/cluster/ClusterEvents.d.ts.map +1 -1
  110. package/dist/esm/behavior/cluster/ClusterState.d.ts +1 -1
  111. package/dist/esm/behavior/cluster/ClusterState.d.ts.map +1 -1
  112. package/dist/esm/behavior/cluster/ValidatedElements.js +1 -1
  113. package/dist/esm/behavior/cluster/{ClusterBehaviorUtil.d.ts → cluster-behavior-utils.d.ts} +10 -8
  114. package/dist/esm/behavior/cluster/cluster-behavior-utils.d.ts.map +1 -0
  115. package/dist/esm/behavior/cluster/cluster-behavior-utils.js +21 -0
  116. package/dist/esm/behavior/cluster/cluster-behavior-utils.js.map +6 -0
  117. package/dist/esm/behavior/cluster/index.d.ts +2 -1
  118. package/dist/esm/behavior/cluster/index.d.ts.map +1 -1
  119. package/dist/esm/behavior/cluster/index.js +2 -1
  120. package/dist/esm/behavior/cluster/index.js.map +1 -1
  121. package/dist/esm/behavior/context/server/LocalActorContext.d.ts +4 -0
  122. package/dist/esm/behavior/context/server/LocalActorContext.d.ts.map +1 -1
  123. package/dist/esm/behavior/context/server/LocalActorContext.js +2 -3
  124. package/dist/esm/behavior/context/server/LocalActorContext.js.map +1 -1
  125. package/dist/esm/behavior/context/server/RemoteActorContext.d.ts +4 -0
  126. package/dist/esm/behavior/context/server/RemoteActorContext.d.ts.map +1 -1
  127. package/dist/esm/behavior/context/server/RemoteActorContext.js +1 -0
  128. package/dist/esm/behavior/context/server/RemoteActorContext.js.map +1 -1
  129. package/dist/esm/behavior/internal/BehaviorBacking.js +2 -2
  130. package/dist/esm/behavior/internal/BehaviorBacking.js.map +1 -1
  131. package/dist/esm/behavior/system/commissioning/CommissioningServer.d.ts.map +1 -1
  132. package/dist/esm/behavior/system/commissioning/CommissioningServer.js +4 -3
  133. package/dist/esm/behavior/system/commissioning/CommissioningServer.js.map +1 -1
  134. package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js +1 -1
  135. package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
  136. package/dist/esm/behaviors/operational-credentials/VendorIdVerification.d.ts +6 -1
  137. package/dist/esm/behaviors/operational-credentials/VendorIdVerification.d.ts.map +1 -1
  138. package/dist/esm/behaviors/operational-credentials/VendorIdVerification.js +1 -1
  139. package/dist/esm/behaviors/operational-credentials/VendorIdVerification.js.map +1 -1
  140. package/dist/esm/endpoint/Endpoint.d.ts +8 -0
  141. package/dist/esm/endpoint/Endpoint.d.ts.map +1 -1
  142. package/dist/esm/endpoint/Endpoint.js +11 -3
  143. package/dist/esm/endpoint/Endpoint.js.map +1 -1
  144. package/dist/esm/endpoint/properties/Behaviors.d.ts +1 -1
  145. package/dist/esm/endpoint/properties/Behaviors.d.ts.map +1 -1
  146. package/dist/esm/endpoint/properties/Behaviors.js +6 -3
  147. package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
  148. package/dist/esm/endpoint/properties/EndpointInitializer.d.ts +0 -4
  149. package/dist/esm/endpoint/properties/EndpointInitializer.d.ts.map +1 -1
  150. package/dist/esm/endpoint/properties/EndpointInitializer.js +0 -6
  151. package/dist/esm/endpoint/properties/EndpointInitializer.js.map +1 -1
  152. package/dist/esm/node/Node.d.ts.map +1 -1
  153. package/dist/esm/node/Node.js +2 -1
  154. package/dist/esm/node/Node.js.map +1 -1
  155. package/dist/esm/node/client/ClientCommandMethod.d.ts +11 -0
  156. package/dist/esm/node/client/ClientCommandMethod.d.ts.map +1 -0
  157. package/dist/esm/node/client/ClientCommandMethod.js +49 -0
  158. package/dist/esm/node/client/ClientCommandMethod.js.map +6 -0
  159. package/dist/esm/node/client/ClientEndpointInitializer.d.ts +0 -2
  160. package/dist/esm/node/client/ClientEndpointInitializer.d.ts.map +1 -1
  161. package/dist/esm/node/client/ClientEndpointInitializer.js +3 -34
  162. package/dist/esm/node/client/ClientEndpointInitializer.js.map +1 -1
  163. package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
  164. package/dist/esm/node/client/ClientStructure.js +164 -32
  165. package/dist/esm/node/client/ClientStructure.js.map +2 -2
  166. package/dist/esm/node/client/ClientStructureEvents.d.ts +22 -0
  167. package/dist/esm/node/client/ClientStructureEvents.d.ts.map +1 -0
  168. package/dist/esm/node/client/ClientStructureEvents.js +72 -0
  169. package/dist/esm/node/client/ClientStructureEvents.js.map +6 -0
  170. package/dist/esm/node/client/PeerBehavior.d.ts +14 -1
  171. package/dist/esm/node/client/PeerBehavior.d.ts.map +1 -1
  172. package/dist/esm/node/client/PeerBehavior.js +119 -74
  173. package/dist/esm/node/client/PeerBehavior.js.map +1 -1
  174. package/dist/esm/node/client/Peers.d.ts +17 -0
  175. package/dist/esm/node/client/Peers.d.ts.map +1 -1
  176. package/dist/esm/node/client/Peers.js +106 -43
  177. package/dist/esm/node/client/Peers.js.map +1 -1
  178. package/dist/esm/node/server/ServerSubscription.d.ts +1 -1
  179. package/dist/esm/node/server/ServerSubscription.d.ts.map +1 -1
  180. package/dist/esm/node/server/ServerSubscription.js +2 -3
  181. package/dist/esm/node/server/ServerSubscription.js.map +1 -1
  182. package/package.json +7 -7
  183. package/src/behavior/cluster/ClientBehavior.ts +2 -16
  184. package/src/behavior/cluster/ClusterBehavior.ts +9 -4
  185. package/src/behavior/cluster/ClusterBehaviorCache.ts +16 -9
  186. package/src/behavior/cluster/{ClusterBehaviorUtil.ts → ClusterBehaviorType.ts} +160 -64
  187. package/src/behavior/cluster/ClusterEvents.ts +1 -1
  188. package/src/behavior/cluster/ClusterState.ts +1 -1
  189. package/src/behavior/cluster/ValidatedElements.ts +1 -1
  190. package/src/behavior/cluster/cluster-behavior-utils.ts +48 -0
  191. package/src/behavior/cluster/index.ts +2 -1
  192. package/src/behavior/context/server/LocalActorContext.ts +8 -4
  193. package/src/behavior/context/server/RemoteActorContext.ts +6 -0
  194. package/src/behavior/internal/BehaviorBacking.ts +2 -2
  195. package/src/behavior/system/commissioning/CommissioningServer.ts +4 -3
  196. package/src/behaviors/operational-credentials/OperationalCredentialsServer.ts +1 -1
  197. package/src/behaviors/operational-credentials/VendorIdVerification.ts +3 -2
  198. package/src/endpoint/Endpoint.ts +23 -3
  199. package/src/endpoint/properties/Behaviors.ts +12 -6
  200. package/src/endpoint/properties/EndpointInitializer.ts +0 -7
  201. package/src/node/Node.ts +2 -1
  202. package/src/node/client/ClientCommandMethod.ts +63 -0
  203. package/src/node/client/ClientEndpointInitializer.ts +6 -39
  204. package/src/node/client/ClientStructure.ts +133 -27
  205. package/src/node/client/ClientStructureEvents.ts +97 -0
  206. package/src/node/client/PeerBehavior.ts +185 -104
  207. package/src/node/client/Peers.ts +131 -51
  208. package/src/node/server/ServerSubscription.ts +3 -3
  209. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.d.ts.map +0 -1
  210. package/dist/cjs/behavior/cluster/ClusterBehaviorUtil.js.map +0 -6
  211. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.d.ts.map +0 -1
  212. package/dist/esm/behavior/cluster/ClusterBehaviorUtil.js.map +0 -6
@@ -15,7 +15,12 @@ export let nextInternalId = 1;
15
15
 
16
16
  let ReadOnly: LocalActorContext | undefined;
17
17
 
18
- export interface LocalActorContext extends ValueSupervisor.LocalActorSession {}
18
+ export interface LocalActorContext extends ValueSupervisor.LocalActorSession {
19
+ /**
20
+ * @deprecated use `context.fabric === undefined` or `hasLocalActor(context)` to detect a local actor
21
+ */
22
+ offline: true;
23
+ }
19
24
 
20
25
  /**
21
26
  * The context for operations triggered locally, either for in-process node implementations or remote nodes that are
@@ -80,9 +85,6 @@ export const LocalActorContext = {
80
85
  const context = Object.freeze({
81
86
  ...options,
82
87
 
83
- // Disable access level enforcement
84
- offline: true,
85
-
86
88
  transaction,
87
89
  activity: frame,
88
90
 
@@ -101,6 +103,8 @@ export const LocalActorContext = {
101
103
 
102
104
  resolve: transaction.resolve.bind(transaction),
103
105
  reject: transaction.reject.bind(transaction),
106
+
107
+ offline: true,
104
108
  });
105
109
 
106
110
  return context;
@@ -45,6 +45,11 @@ export interface RemoteActorContext extends ValueSupervisor.RemoteActorSession {
45
45
  * The priority of actions in this context.
46
46
  */
47
47
  priority?: Priority;
48
+
49
+ /**
50
+ * @deprecated use `context.fabric !== undefined` or `hasRemoteActor(context)` to detect a remote actor
51
+ */
52
+ offline?: false;
48
53
  }
49
54
 
50
55
  /**
@@ -185,6 +190,7 @@ export function RemoteActorContext(options: RemoteActorContext.Options) {
185
190
  exchange,
186
191
  subject,
187
192
  largeMessage: exchange?.channel.supportsLargeMessages,
193
+ offline: false,
188
194
 
189
195
  fabric: fabric?.fabricIndex ?? FabricIndex.NO_FABRIC,
190
196
  transaction,
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
- import { ClientBehavior } from "#behavior/cluster/ClientBehavior.js";
7
+ import { isClientBehavior } from "#behavior/cluster/cluster-behavior-utils.js";
8
8
  import { OnlineEvent } from "#behavior/Events.js";
9
9
  import { Migration } from "#behavior/state/migrations/Migration.js";
10
10
  import type { Agent } from "#endpoint/Agent.js";
@@ -185,7 +185,7 @@ export abstract class BehaviorBacking {
185
185
  */
186
186
  createBehavior(agent: Agent, type: Behavior.Type) {
187
187
  const behavior = new this.#type(agent, this);
188
- if (behavior instanceof type || ClientBehavior.is(type)) {
188
+ if (behavior instanceof type || isClientBehavior(type)) {
189
189
  return behavior;
190
190
  }
191
191
 
@@ -18,6 +18,7 @@ import {
18
18
  Logger,
19
19
  MatterFlowError,
20
20
  Mutex,
21
+ MutexClosedError,
21
22
  Observable,
22
23
  } from "#general";
23
24
  import { DatatypeModel, FieldElement } from "#model";
@@ -159,14 +160,14 @@ export class CommissioningServer extends Behavior {
159
160
  const sessions = this.agent.get(SessionsBehavior);
160
161
  if (Object.keys(sessions.state.sessions).length > 0) {
161
162
  // We have still open sessions, wait for them to close
162
- this.reactTo(sessions.events.closed, this.#handleSessionClosed);
163
+ this.reactTo(sessions.events.closed, this.#resetAfterSessionsClear);
163
164
  } else {
164
165
  this.#triggerFactoryReset();
165
166
  }
166
167
  }
167
168
  }
168
169
 
169
- #handleSessionClosed() {
170
+ #resetAfterSessionsClear() {
170
171
  const sessions = this.agent.get(SessionsBehavior);
171
172
  if (Object.keys(sessions.state.sessions).length === 0) {
172
173
  this.#triggerFactoryReset();
@@ -174,7 +175,7 @@ export class CommissioningServer extends Behavior {
174
175
  }
175
176
 
176
177
  #triggerFactoryReset() {
177
- this.env.runtime.add((this.endpoint as ServerNode).erase());
178
+ this.env.runtime.add((this.endpoint as ServerNode).erase().catch(e => MutexClosedError.accept(e)));
178
179
  }
179
180
 
180
181
  #monitorFailsafe(failsafe: FailsafeContext) {
@@ -405,7 +405,7 @@ export class OperationalCredentialsServer extends OperationalCredentialsBehavior
405
405
  };
406
406
  }
407
407
 
408
- await fabric.remove(this.context.session.id);
408
+ await fabric.leave(this.context.session.id);
409
409
  // The state is updated on removal via commissionedFabricChanged event, see constructor
410
410
 
411
411
  return {
@@ -3,8 +3,8 @@
3
3
  * Copyright 2022-2025 Matter.js Authors
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { OperationalCredentialsClient } from "#behaviors/operational-credentials";
7
- import { OperationalCredentials } from "#clusters/operational-credentials";
6
+
7
+ import type { OperationalCredentials } from "#clusters/operational-credentials";
8
8
  import {
9
9
  Bytes,
10
10
  Crypto,
@@ -22,6 +22,7 @@ import { ClientNodeInteraction } from "#node/client/ClientNodeInteraction.js";
22
22
  import type { ClientNode } from "#node/ClientNode.js";
23
23
  import { Icac, Noc, NodeSession, Rcac, Vvsc } from "#protocol";
24
24
  import { FabricId, FabricIndex, ReceivedStatusResponseError, StatusResponse, VendorId } from "#types";
25
+ import { OperationalCredentialsClient } from "../operational-credentials/OperationalCredentialsClient.js";
25
26
 
26
27
  const logger = Logger.get("VendorIdVerification");
27
28
 
@@ -191,13 +191,33 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
191
191
  stateOf<T extends Behavior.Type>(type: T): Immutable<Behavior.StateOf<T>>;
192
192
 
193
193
  stateOf(type: Behavior.Type | string) {
194
+ const state = this.maybeStateOf(type as any);
195
+ if (state) {
196
+ return state;
197
+ }
198
+
199
+ const id = typeof type === "string" ? type : type.id;
200
+ throw new ImplementationError(`Behavior ${id} is not supported by ${this}`);
201
+ }
202
+
203
+ /**
204
+ * Version of {@link stateOf} that returns undefined instead of throwing if the requested behavior unsupported.
205
+ */
206
+ maybeStateOf(type: string): Immutable<Val.Struct>;
207
+
208
+ /**
209
+ * Version of {@link stateOf} that returns undefined instead of throwing if the requested behavior unsupported.
210
+ */
211
+ maybeStateOf<T extends Behavior.Type>(type: T): Immutable<Behavior.StateOf<T>>;
212
+
213
+ maybeStateOf(type: Behavior.Type | string) {
194
214
  if (typeof type === "string") {
195
215
  if (!(type in this.#stateView)) {
196
- throw new ImplementationError(`Behavior ${type} is not supported by ${this}`);
216
+ return undefined;
197
217
  }
198
218
  } else {
199
219
  if (!this.behaviors.has(type)) {
200
- throw new ImplementationError(`Behavior ${type.id} is not supported by ${this}`);
220
+ return undefined;
201
221
  }
202
222
  type = type.id;
203
223
  }
@@ -371,7 +391,7 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
371
391
  }
372
392
  } else {
373
393
  if (!this.behaviors.has(type)) {
374
- throw new ImplementationError(`Behavior ${type.id} is not supported by this endpoint`);
394
+ throw new ImplementationError(`Behavior ${type.id} is not supported by ${this}`);
375
395
  }
376
396
  type = type.id;
377
397
  }
@@ -393,7 +393,11 @@ export class Behaviors {
393
393
  /**
394
394
  * Determine if a specified behavior is supported and active.
395
395
  */
396
- isActive(type: Behavior.Type) {
396
+ isActive(type: Behavior.Type | string) {
397
+ if (typeof type === "string") {
398
+ return this.#backings[type] !== undefined;
399
+ }
400
+
397
401
  const backing = this.#backings[type.id];
398
402
  return !!backing && backing.type.supports(type);
399
403
  }
@@ -463,11 +467,6 @@ export class Behaviors {
463
467
  this.#supported = { ...this.#supported };
464
468
  }
465
469
 
466
- // TODO how to better solve that?
467
- if (this.#endpoint.env.has(EndpointInitializer)) {
468
- type = this.#endpoint.env.get(EndpointInitializer).finalizeType(type);
469
- }
470
-
471
470
  this.#supported[type.id] = type;
472
471
 
473
472
  this.#augmentEndpoint(type);
@@ -672,6 +671,13 @@ export class Behaviors {
672
671
 
673
672
  const backing = this.#endpoint.env.get(EndpointInitializer).createBacking(this.#endpoint, myType);
674
673
  this.#backings[backing.type.id] = backing;
674
+
675
+ // The EndpointInitializer may choose to replace the behavior implementation. If so the replacement should be
676
+ // compatible, but update our support map to designate the specific implementation
677
+ if (backing.type !== myType) {
678
+ this.#supported[backing.type.id] = backing.type;
679
+ }
680
+
675
681
  if (!this.#protocol) {
676
682
  this.#protocol = this.#endpoint.env.get(ProtocolService);
677
683
  }
@@ -41,11 +41,4 @@ export abstract class EndpointInitializer {
41
41
  * Invoked after behaviors are initialized but before the initialization transaction commits.
42
42
  */
43
43
  behaviorsInitialized(_agent: Agent) {}
44
-
45
- /**
46
- * Allows Initializer to manipulate the type before it is used to create a backing or behavior.
47
- */
48
- finalizeType(type: Behavior.Type): Behavior.Type {
49
- return type;
50
- }
51
44
  }
package/src/node/Node.ts CHANGED
@@ -177,7 +177,8 @@ export abstract class Node<T extends Node.CommonRootEndpoint = Node.CommonRootEn
177
177
 
178
178
  override async close() {
179
179
  this.lifecycle.targetState = "offline";
180
- await this.lifecycle.mutex.produce(this.closeWithMutex.bind(this));
180
+ await this.lifecycle.mutex.close();
181
+ await this.closeWithMutex();
181
182
  }
182
183
 
183
184
  protected async closeWithMutex() {
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ClusterBehavior } from "#behavior/cluster/ClusterBehavior.js";
8
+ import type { ClientNode } from "#node/ClientNode.js";
9
+ import { Node } from "#node/Node.js";
10
+ import { ClientInteraction, Invoke } from "#protocol";
11
+ import { Status, StatusResponseError } from "#types";
12
+
13
+ /**
14
+ * Create the command method for a client behavior.
15
+ */
16
+ export function ClientCommandMethod(name: string) {
17
+ // This is our usual hack to give a function a proper name in stack traces
18
+ const temp = {
19
+ // The actual implementation
20
+ [name](this: ClusterBehavior, fields?: {}) {
21
+ return invokeOnPeer(this, name, fields);
22
+ },
23
+ };
24
+
25
+ return temp[name];
26
+ }
27
+
28
+ /**
29
+ * Invokes a command remotely on behalf of client behaviors.
30
+ */
31
+ async function invokeOnPeer(behavior: ClusterBehavior, command: string, fields?: {}) {
32
+ const node = behavior.env.get(Node) as ClientNode;
33
+
34
+ // TODO when implementing TCP add needed logic for Large messages
35
+ const chunks = (node.interaction as ClientInteraction).invoke(
36
+ Invoke({
37
+ commands: [
38
+ Invoke.ConcreteCommandRequest<any>({
39
+ endpoint: behavior.endpoint,
40
+ cluster: behavior.cluster,
41
+ command,
42
+ fields,
43
+ }),
44
+ ],
45
+ }),
46
+ );
47
+
48
+ for await (const chunk of chunks) {
49
+ for (const entry of chunk) {
50
+ // We send only one command, so we only get one response back
51
+ switch (entry.kind) {
52
+ case "cmd-status":
53
+ if (entry.status !== Status.Success) {
54
+ throw StatusResponseError.create(entry.status, undefined, entry.clusterStatus);
55
+ }
56
+ return;
57
+
58
+ case "cmd-response":
59
+ return entry.data;
60
+ }
61
+ }
62
+ }
63
+ }
@@ -11,12 +11,10 @@ import { ClientBehaviorBacking } from "#behavior/internal/ClientBehaviorBacking.
11
11
  import { ServerBehaviorBacking } from "#behavior/internal/ServerBehaviorBacking.js";
12
12
  import { Endpoint } from "#endpoint/Endpoint.js";
13
13
  import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
14
- import { FeatureBitmap } from "#model";
15
14
  import { PeerBehavior } from "#node/client/PeerBehavior.js";
16
15
  import type { ClientNode } from "#node/ClientNode.js";
17
16
  import { ClientNodeStore } from "#storage/client/ClientNodeStore.js";
18
17
  import { NodeStore } from "#storage/NodeStore.js";
19
- import { AttributeId, CommandId } from "#types";
20
18
  import { ClientStructure } from "./ClientStructure.js";
21
19
 
22
20
  export class ClientEndpointInitializer extends EndpointInitializer {
@@ -55,44 +53,13 @@ export class ClientEndpointInitializer extends EndpointInitializer {
55
53
  return new ServerBehaviorBacking(endpoint, type, store, endpoint.behaviors.optionsFor(type));
56
54
  }
57
55
 
58
- // Cluster behaviors are clients to a remote cluster
59
- const store = this.structure.storeForRemote(endpoint, type as ClusterBehavior.Type);
60
- return new ClientBehaviorBacking(endpoint, type, store, endpoint.behaviors.optionsFor(type));
61
- }
62
-
63
- /** Convert the Cluster type to a ClientBehavior */
64
- override finalizeType(type: Behavior.Type): Behavior.Type {
65
- const cluster = (type as ClusterBehavior.Type).cluster;
66
- if (cluster === undefined) {
67
- return type;
68
- }
69
-
70
- const features: FeatureBitmap = {};
71
- for (const f in cluster.features) {
72
- features[f] = true;
73
- }
74
- const attributeNames = new Array<string>();
75
- const attributes = new Array<AttributeId>();
76
- for (const [name, attr] of Object.entries(cluster.attributes)) {
77
- attributeNames.push(name);
78
- attributes.push(attr.id);
79
- }
80
- const commandNames = new Array<string>();
81
- const commands = new Array<CommandId>();
82
- for (const [name, cmd] of Object.entries(cluster.commands)) {
83
- commandNames.push(name);
84
- commands.push(cmd.requestId);
85
- }
56
+ // Cluster behaviors must be instrumented for access to a remote cluster. If already instrumented this is a
57
+ // no-op, otherwise it creates a new type extension
58
+ const peerType = PeerBehavior({ kind: "known", behavior: type as ClusterBehavior.Type });
86
59
 
87
- return PeerBehavior({
88
- id: cluster.id,
89
- revision: cluster.revision,
90
- features,
91
- attributes,
92
- commands,
93
- attributeNames,
94
- commandNames,
95
- });
60
+ // Activate remote behavior
61
+ const store = this.structure.storeForRemote(endpoint, peerType);
62
+ return new ClientBehaviorBacking(endpoint, type, store, endpoint.behaviors.optionsFor(peerType));
96
63
  }
97
64
 
98
65
  get structure() {
@@ -11,6 +11,7 @@ import { Descriptor } from "#clusters/descriptor";
11
11
  import { Endpoint } from "#endpoint/Endpoint.js";
12
12
  import { EndpointType } from "#endpoint/type/EndpointType.js";
13
13
  import { RootEndpoint } from "#endpoints/root";
14
+ import { InternalError, Logger } from "#general";
14
15
  import {
15
16
  AcceptedCommandList,
16
17
  AttributeList,
@@ -22,13 +23,17 @@ import {
22
23
  type FeatureBitmap,
23
24
  } from "#model";
24
25
  import type { ClientNode } from "#node/ClientNode.js";
26
+ import type { Node } from "#node/Node.js";
25
27
  import { ReadScope, type Read, type ReadResult } from "#protocol";
26
28
  import { DatasourceCache } from "#storage/client/DatasourceCache.js";
27
29
  import { ClientNodeStore } from "#storage/index.js";
28
30
  import type { AttributeId, ClusterId, ClusterType, CommandId, EndpointNumber } from "#types";
29
31
  import { ClientEventEmitter } from "./ClientEventEmitter.js";
32
+ import { ClientStructureEvents } from "./ClientStructureEvents.js";
30
33
  import { PeerBehavior } from "./PeerBehavior.js";
31
34
 
35
+ const logger = Logger.get("ClientStructure");
36
+
32
37
  const DEVICE_TYPE_LIST_ATTR_ID = Descriptor.Cluster.attributes.deviceTypeList.id;
33
38
  const SERVER_LIST_ATTR_ID = Descriptor.Cluster.attributes.serverList.id;
34
39
  const PARTS_LIST_ATTR_ID = Descriptor.Cluster.attributes.partsList.id;
@@ -42,6 +47,8 @@ export class ClientStructure {
42
47
  #emitEvent: ClientEventEmitter;
43
48
  #node: ClientNode;
44
49
  #subscribedFabricFiltered?: boolean;
50
+ #pending = new Map<EndpointStructure, "erase" | "reparent">();
51
+ #events: ClientStructureEvents;
45
52
 
46
53
  constructor(node: ClientNode) {
47
54
  this.#node = node;
@@ -51,6 +58,7 @@ export class ClientStructure {
51
58
  clusters: {},
52
59
  };
53
60
  this.#emitEvent = ClientEventEmitter(node, this);
61
+ this.#events = this.#node.env.get(ClientStructureEvents);
54
62
  }
55
63
 
56
64
  /**
@@ -76,7 +84,20 @@ export class ClientStructure {
76
84
  }
77
85
 
78
86
  const cluster = this.#clusterFor(endpoint, id);
79
- this.#initializeCluster(endpoint, cluster);
87
+ this.#synchronizeCluster(endpoint, cluster);
88
+ }
89
+ }
90
+
91
+ for (const [endpoint, opcode] of this.#pending.entries()) {
92
+ this.#pending.delete(endpoint);
93
+
94
+ switch (opcode) {
95
+ case "reparent":
96
+ this.#install(endpoint);
97
+ break;
98
+
99
+ default:
100
+ throw new InternalError(`Unexpected ${opcode} operation in initial hierarchy load`);
80
101
  }
81
102
  }
82
103
  }
@@ -142,10 +163,14 @@ export class ClientStructure {
142
163
  * Update the node structure by applying attribute changes.
143
164
  */
144
165
  async *mutate(request: Read, changes: ReadResult) {
145
- const scope = ReadScope(request);
166
+ // Ensure mutations run serially and integrate properly with node lifecycle
167
+ using _lock = await this.#node.lifecycle.mutex.lock();
146
168
 
169
+ // We collect updates and only apply when we transition clusters
147
170
  let currentUpdates: AttributeUpdates | undefined;
148
171
 
172
+ // Apply changes
173
+ const scope = ReadScope(request);
149
174
  for await (const chunk of changes) {
150
175
  for (const change of chunk) {
151
176
  switch (change.kind) {
@@ -164,9 +189,32 @@ export class ClientStructure {
164
189
  yield chunk;
165
190
  }
166
191
 
192
+ // The last cluster still needs its changes applied
167
193
  if (currentUpdates) {
168
194
  await this.#updateCluster(currentUpdates);
169
195
  }
196
+
197
+ // We don't apply structural changes until we've processed all attribute data if a.) listeners might otherwise
198
+ // see partially initialized endpoints, or b.) the change requires an async operation
199
+ for (const [endpoint, opcode] of this.#pending.entries()) {
200
+ this.#pending.delete(endpoint);
201
+
202
+ switch (opcode) {
203
+ case "reparent":
204
+ this.#install(endpoint);
205
+ break;
206
+
207
+ case "erase":
208
+ logger.debug(`Removing endpoint ${endpoint.endpoint} because it is no longer present on the peer`);
209
+ delete this.#endpoints[endpoint.endpoint.number];
210
+ try {
211
+ await endpoint.endpoint.delete();
212
+ } catch (e) {
213
+ logger.error(`Error erasing peer endpoint ${endpoint.endpoint}:`, e);
214
+ }
215
+ break;
216
+ }
217
+ }
170
218
  }
171
219
 
172
220
  /** Reference to the default subscription used when the node was started. */
@@ -246,10 +294,11 @@ export class ClientStructure {
246
294
  * This is invoked in a batch when we've collected all sequential values for the current endpoint/cluster.
247
295
  */
248
296
  async #updateCluster(attrs: AttributeUpdates) {
297
+ // TODO: Detect changes in revision/features/attributes/commands and update behavior if needed
249
298
  const endpoint = this.#endpointFor(attrs.endpointId);
250
299
  const cluster = this.#clusterFor(endpoint, attrs.clusterId);
251
300
  await cluster.store.externalSet(attrs.values);
252
- this.#initializeCluster(endpoint, cluster);
301
+ this.#synchronizeCluster(endpoint, cluster);
253
302
  }
254
303
 
255
304
  /**
@@ -260,18 +309,15 @@ export class ClientStructure {
260
309
  *
261
310
  * Invoked once we've loaded all attributes in an interaction.
262
311
  */
263
- #initializeCluster(endpoint: EndpointStructure, cluster: ClusterStructure) {
264
- const attrs = cluster.store.initialValues ?? {};
265
-
312
+ #synchronizeCluster(endpoint: EndpointStructure, cluster: ClusterStructure) {
266
313
  // Generate a behavior if enough information is available
267
- // TODO: Detect changes in revision/features/attributes/commands and update behavior if needed
268
- if (cluster.behavior === undefined) {
314
+ if (cluster.behavior === undefined && cluster.store.initialValues) {
269
315
  const {
270
316
  [ClusterRevision.id]: clusterRevision,
271
317
  [FeatureMap.id]: features,
272
318
  [AttributeList.id]: attributeList,
273
319
  [AcceptedCommandList.id]: commandList,
274
- } = attrs;
320
+ } = cluster.store.initialValues;
275
321
 
276
322
  if (typeof clusterRevision === "number") {
277
323
  cluster.revision = clusterRevision;
@@ -297,11 +343,20 @@ export class ClientStructure {
297
343
  ) {
298
344
  cluster.behavior = PeerBehavior(cluster as PeerBehavior.ClusterShape);
299
345
  endpoint.endpoint.behaviors.require(cluster.behavior);
346
+ if (endpoint.endpoint.lifecycle.isInstalled) {
347
+ this.#events.emitCluster(endpoint.endpoint, cluster.behavior);
348
+ }
300
349
  }
301
350
  }
302
351
 
303
352
  // Special handling for descriptor cluster
304
353
  if (cluster.id === Descriptor.Cluster.id) {
354
+ let attrs;
355
+ if (cluster.behavior && endpoint.endpoint.behaviors.isActive(cluster.behavior.id)) {
356
+ attrs = endpoint.endpoint.stateOf(cluster.behavior);
357
+ } else {
358
+ attrs = cluster.store.initialValues ?? {};
359
+ }
305
360
  this.#synchronizeDescriptor(endpoint, attrs);
306
361
  }
307
362
  }
@@ -356,31 +411,52 @@ export class ClientStructure {
356
411
  }
357
412
  }
358
413
 
414
+ // The remaining logic deals with the parts list
359
415
  const partsList = attrs[PARTS_LIST_ATTR_ID];
360
- if (Array.isArray(partsList)) {
361
- for (const partNo of partsList) {
362
- if (typeof partNo !== "number") {
363
- continue;
364
- }
416
+ if (!Array.isArray(partsList)) {
417
+ return;
418
+ }
365
419
 
366
- const part = this.#endpointFor(partNo as EndpointNumber);
420
+ // Ensure an endpoint is present and installed for each part in the partsList
421
+ for (const partNo of partsList) {
422
+ if (typeof partNo !== "number") {
423
+ continue;
424
+ }
367
425
 
368
- // TODO - remove endpoints/parts that are no longer present
369
- // Including events vis parts/endpoints on node (per endpoint and generic "changed")?
370
- // Including data cleanup
371
- let isAlreadyDescendant = false;
372
- for (let owner = part.endpoint.owner; owner; owner = owner.owner) {
373
- if (owner === endpoint.endpoint) {
374
- isAlreadyDescendant = true;
375
- break;
376
- }
426
+ const part = this.#endpointFor(partNo as EndpointNumber);
427
+
428
+ let isAlreadyDescendant = false;
429
+ for (let owner = this.#ownerOf(part); owner; owner = this.#ownerOf(owner)) {
430
+ if (owner === endpoint) {
431
+ isAlreadyDescendant = true;
432
+ break;
377
433
  }
434
+ }
378
435
 
379
- if (isAlreadyDescendant) {
436
+ if (isAlreadyDescendant) {
437
+ continue;
438
+ }
439
+
440
+ part.pendingOwner = endpoint;
441
+ this.#pending.set(part, "reparent");
442
+ }
443
+
444
+ // For the root partsList specifically, if an endpoint is no longer present then it has been removd from the
445
+ // node. Schedule for erase
446
+ if (endpoint.endpoint.maybeNumber === 0) {
447
+ const numbersUsed = new Set(partsList);
448
+ for (const descendent of (endpoint.endpoint as Node).endpoints) {
449
+ // Skip root endpoint and uninitialized numbers (though latter shouldn't be possible)
450
+ if (!descendent.maybeNumber) {
380
451
  continue;
381
452
  }
382
453
 
383
- part.endpoint.owner = endpoint.endpoint;
454
+ if (!numbersUsed.has(descendent.number)) {
455
+ const endpoint = this.#endpoints[descendent.number];
456
+ if (endpoint) {
457
+ this.#pending.set(endpoint, "erase");
458
+ }
459
+ }
384
460
  }
385
461
  }
386
462
  }
@@ -415,6 +491,7 @@ export class ClientStructure {
415
491
  }
416
492
 
417
493
  cluster = {
494
+ kind: "discovered",
418
495
  id,
419
496
  store: this.#nodeStore.storeForEndpoint(endpoint.endpoint).createStoreForBehavior(id.toString()),
420
497
  };
@@ -422,6 +499,33 @@ export class ClientStructure {
422
499
 
423
500
  return cluster;
424
501
  }
502
+
503
+ #ownerOf(endpoint: EndpointStructure) {
504
+ if (endpoint.pendingOwner) {
505
+ return endpoint.pendingOwner;
506
+ }
507
+
508
+ // Do not return the ServerNode if this is the ClientNode
509
+ if (endpoint.endpoint.number === 0) {
510
+ return;
511
+ }
512
+
513
+ const ownerNumber = endpoint.endpoint.owner?.maybeNumber;
514
+ if (ownerNumber !== undefined) {
515
+ return this.#endpointFor(ownerNumber);
516
+ }
517
+ }
518
+
519
+ #install(endpoint: EndpointStructure) {
520
+ const { pendingOwner } = endpoint;
521
+ if (!pendingOwner) {
522
+ return;
523
+ }
524
+
525
+ endpoint.endpoint.owner = pendingOwner.endpoint;
526
+ endpoint.pendingOwner = undefined;
527
+ this.#events.emitEndpoint(endpoint.endpoint);
528
+ }
425
529
  }
426
530
 
427
531
  interface AttributeUpdates {
@@ -433,11 +537,13 @@ interface AttributeUpdates {
433
537
  }
434
538
 
435
539
  interface EndpointStructure {
540
+ pendingOwner?: EndpointStructure;
436
541
  endpoint: Endpoint;
437
542
  clusters: Record<ClusterId, ClusterStructure>;
438
543
  }
439
544
 
440
- interface ClusterStructure extends Partial<PeerBehavior.ClusterShape> {
545
+ interface ClusterStructure extends Partial<PeerBehavior.DiscoveredClusterShape> {
546
+ kind: "discovered";
441
547
  id: ClusterId;
442
548
  behavior?: ClusterBehavior.Type;
443
549
  store: Datasource.ExternallyMutableStore;