@matter/node 0.16.0-alpha.0-20250820-24939dd26 → 0.16.0-alpha.0-20250821-dd03e1003

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 (141) hide show
  1. package/dist/cjs/behavior/Events.d.ts +5 -0
  2. package/dist/cjs/behavior/Events.d.ts.map +1 -1
  3. package/dist/cjs/behavior/Events.js +7 -4
  4. package/dist/cjs/behavior/Events.js.map +1 -1
  5. package/dist/cjs/behavior/Transitions.d.ts.map +1 -1
  6. package/dist/cjs/behavior/Transitions.js +2 -2
  7. package/dist/cjs/behavior/Transitions.js.map +1 -1
  8. package/dist/cjs/behavior/context/ActionContext.d.ts +0 -7
  9. package/dist/cjs/behavior/context/ActionContext.d.ts.map +1 -1
  10. package/dist/cjs/behavior/context/server/ContextAgents.d.ts +4 -5
  11. package/dist/cjs/behavior/context/server/ContextAgents.d.ts.map +1 -1
  12. package/dist/cjs/behavior/context/server/ContextAgents.js +9 -0
  13. package/dist/cjs/behavior/context/server/ContextAgents.js.map +1 -1
  14. package/dist/cjs/behavior/context/server/OfflineContext.d.ts.map +1 -1
  15. package/dist/cjs/behavior/context/server/OfflineContext.js +0 -8
  16. package/dist/cjs/behavior/context/server/OfflineContext.js.map +1 -1
  17. package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
  18. package/dist/cjs/behavior/context/server/OnlineContext.js +0 -8
  19. package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
  20. package/dist/cjs/behavior/internal/Reactors.js +1 -1
  21. package/dist/cjs/behavior/internal/Reactors.js.map +1 -1
  22. package/dist/cjs/behavior/system/network/NetworkClient.d.ts.map +1 -1
  23. package/dist/cjs/behavior/system/network/NetworkClient.js +1 -0
  24. package/dist/cjs/behavior/system/network/NetworkClient.js.map +1 -1
  25. package/dist/cjs/behavior/system/parts/PartsBehavior.js +1 -1
  26. package/dist/cjs/behavior/system/parts/PartsBehavior.js.map +1 -1
  27. package/dist/cjs/behavior/system/product-description/ProductDescriptionServer.js +1 -1
  28. package/dist/cjs/behavior/system/product-description/ProductDescriptionServer.js.map +1 -1
  29. package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.js +1 -1
  30. package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.js.map +1 -1
  31. package/dist/cjs/behaviors/groups/GroupsServer.js +4 -4
  32. package/dist/cjs/behaviors/groups/GroupsServer.js.map +1 -1
  33. package/dist/cjs/endpoint/Agent.js +1 -1
  34. package/dist/cjs/endpoint/Agent.js.map +1 -1
  35. package/dist/cjs/endpoint/Endpoint.d.ts +9 -0
  36. package/dist/cjs/endpoint/Endpoint.d.ts.map +1 -1
  37. package/dist/cjs/endpoint/Endpoint.js +12 -1
  38. package/dist/cjs/endpoint/Endpoint.js.map +1 -1
  39. package/dist/cjs/endpoint/properties/Behaviors.js +3 -3
  40. package/dist/cjs/endpoint/properties/Behaviors.js.map +1 -1
  41. package/dist/cjs/endpoint/properties/Commands.js +1 -1
  42. package/dist/cjs/endpoint/properties/Commands.js.map +1 -1
  43. package/dist/cjs/node/ClientNode.d.ts.map +1 -1
  44. package/dist/cjs/node/ClientNode.js +2 -0
  45. package/dist/cjs/node/ClientNode.js.map +1 -1
  46. package/dist/cjs/node/client/ClientBehavior.js +11 -4
  47. package/dist/cjs/node/client/ClientBehavior.js.map +1 -1
  48. package/dist/cjs/node/client/ClientEventEmitter.d.ts +19 -0
  49. package/dist/cjs/node/client/ClientEventEmitter.d.ts.map +1 -0
  50. package/dist/cjs/node/client/ClientEventEmitter.js +97 -0
  51. package/dist/cjs/node/client/ClientEventEmitter.js.map +6 -0
  52. package/dist/cjs/node/client/ClientStructure.d.ts +7 -3
  53. package/dist/cjs/node/client/ClientStructure.d.ts.map +1 -1
  54. package/dist/cjs/node/client/ClientStructure.js +42 -22
  55. package/dist/cjs/node/client/ClientStructure.js.map +1 -1
  56. package/dist/cjs/node/server/IdentityService.js +1 -1
  57. package/dist/cjs/node/server/IdentityService.js.map +1 -1
  58. package/dist/cjs/node/server/ProtocolService.js +1 -1
  59. package/dist/cjs/node/server/ProtocolService.js.map +1 -1
  60. package/dist/esm/behavior/Events.d.ts +5 -0
  61. package/dist/esm/behavior/Events.d.ts.map +1 -1
  62. package/dist/esm/behavior/Events.js +8 -4
  63. package/dist/esm/behavior/Events.js.map +1 -1
  64. package/dist/esm/behavior/Transitions.d.ts.map +1 -1
  65. package/dist/esm/behavior/Transitions.js +2 -2
  66. package/dist/esm/behavior/Transitions.js.map +1 -1
  67. package/dist/esm/behavior/context/ActionContext.d.ts +0 -7
  68. package/dist/esm/behavior/context/ActionContext.d.ts.map +1 -1
  69. package/dist/esm/behavior/context/server/ContextAgents.d.ts +4 -5
  70. package/dist/esm/behavior/context/server/ContextAgents.d.ts.map +1 -1
  71. package/dist/esm/behavior/context/server/ContextAgents.js +9 -0
  72. package/dist/esm/behavior/context/server/ContextAgents.js.map +1 -1
  73. package/dist/esm/behavior/context/server/OfflineContext.d.ts.map +1 -1
  74. package/dist/esm/behavior/context/server/OfflineContext.js +0 -8
  75. package/dist/esm/behavior/context/server/OfflineContext.js.map +1 -1
  76. package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
  77. package/dist/esm/behavior/context/server/OnlineContext.js +0 -8
  78. package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
  79. package/dist/esm/behavior/internal/Reactors.js +1 -1
  80. package/dist/esm/behavior/internal/Reactors.js.map +1 -1
  81. package/dist/esm/behavior/system/network/NetworkClient.d.ts.map +1 -1
  82. package/dist/esm/behavior/system/network/NetworkClient.js +1 -0
  83. package/dist/esm/behavior/system/network/NetworkClient.js.map +1 -1
  84. package/dist/esm/behavior/system/parts/PartsBehavior.js +1 -1
  85. package/dist/esm/behavior/system/parts/PartsBehavior.js.map +1 -1
  86. package/dist/esm/behavior/system/product-description/ProductDescriptionServer.js +1 -1
  87. package/dist/esm/behavior/system/product-description/ProductDescriptionServer.js.map +1 -1
  88. package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.js +1 -1
  89. package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.js.map +1 -1
  90. package/dist/esm/behaviors/groups/GroupsServer.js +4 -4
  91. package/dist/esm/behaviors/groups/GroupsServer.js.map +1 -1
  92. package/dist/esm/endpoint/Agent.js +1 -1
  93. package/dist/esm/endpoint/Agent.js.map +1 -1
  94. package/dist/esm/endpoint/Endpoint.d.ts +9 -0
  95. package/dist/esm/endpoint/Endpoint.d.ts.map +1 -1
  96. package/dist/esm/endpoint/Endpoint.js +12 -1
  97. package/dist/esm/endpoint/Endpoint.js.map +1 -1
  98. package/dist/esm/endpoint/properties/Behaviors.js +3 -3
  99. package/dist/esm/endpoint/properties/Behaviors.js.map +1 -1
  100. package/dist/esm/endpoint/properties/Commands.js +1 -1
  101. package/dist/esm/endpoint/properties/Commands.js.map +1 -1
  102. package/dist/esm/node/ClientNode.d.ts.map +1 -1
  103. package/dist/esm/node/ClientNode.js +2 -0
  104. package/dist/esm/node/ClientNode.js.map +1 -1
  105. package/dist/esm/node/client/ClientBehavior.js +11 -4
  106. package/dist/esm/node/client/ClientBehavior.js.map +1 -1
  107. package/dist/esm/node/client/ClientEventEmitter.d.ts +19 -0
  108. package/dist/esm/node/client/ClientEventEmitter.d.ts.map +1 -0
  109. package/dist/esm/node/client/ClientEventEmitter.js +77 -0
  110. package/dist/esm/node/client/ClientEventEmitter.js.map +6 -0
  111. package/dist/esm/node/client/ClientStructure.d.ts +7 -3
  112. package/dist/esm/node/client/ClientStructure.d.ts.map +1 -1
  113. package/dist/esm/node/client/ClientStructure.js +42 -22
  114. package/dist/esm/node/client/ClientStructure.js.map +1 -1
  115. package/dist/esm/node/server/IdentityService.js +1 -1
  116. package/dist/esm/node/server/IdentityService.js.map +1 -1
  117. package/dist/esm/node/server/ProtocolService.js +1 -1
  118. package/dist/esm/node/server/ProtocolService.js.map +1 -1
  119. package/package.json +7 -7
  120. package/src/behavior/Events.ts +19 -6
  121. package/src/behavior/Transitions.ts +7 -6
  122. package/src/behavior/context/ActionContext.ts +0 -6
  123. package/src/behavior/context/server/ContextAgents.ts +17 -1
  124. package/src/behavior/context/server/OfflineContext.ts +0 -11
  125. package/src/behavior/context/server/OnlineContext.ts +0 -12
  126. package/src/behavior/internal/Reactors.ts +1 -1
  127. package/src/behavior/system/network/NetworkClient.ts +1 -0
  128. package/src/behavior/system/parts/PartsBehavior.ts +1 -1
  129. package/src/behavior/system/product-description/ProductDescriptionServer.ts +1 -1
  130. package/src/behaviors/general-commissioning/ServerNodeFailsafeContext.ts +1 -1
  131. package/src/behaviors/groups/GroupsServer.ts +4 -4
  132. package/src/endpoint/Agent.ts +1 -1
  133. package/src/endpoint/Endpoint.ts +14 -1
  134. package/src/endpoint/properties/Behaviors.ts +3 -3
  135. package/src/endpoint/properties/Commands.ts +1 -1
  136. package/src/node/ClientNode.ts +4 -1
  137. package/src/node/client/ClientBehavior.ts +12 -5
  138. package/src/node/client/ClientEventEmitter.ts +115 -0
  139. package/src/node/client/ClientStructure.ts +58 -34
  140. package/src/node/server/IdentityService.ts +1 -1
  141. package/src/node/server/ProtocolService.ts +1 -1
@@ -27,6 +27,7 @@ export class NetworkClient extends NetworkBehavior {
27
27
  minIntervalFloor: DEFAULT_MIN_INTERVAL_FLOOR,
28
28
  maxIntervalCeiling: 0,
29
29
  attributes: [{}],
30
+ events: [{ isUrgent: true }],
30
31
  ...startupSubscription,
31
32
  });
32
33
 
@@ -37,7 +37,7 @@ export class PartsBehavior extends Behavior implements MutableSet<Endpoint, Endp
37
37
 
38
38
  *[Symbol.iterator]() {
39
39
  for (const part of this.endpoint.parts) {
40
- yield this.context.agentFor(part);
40
+ yield part.agentFor(this.context);
41
41
  }
42
42
  }
43
43
  }
@@ -116,7 +116,7 @@ function inferDeviceType(agent: Agent): DeviceTypeId | undefined {
116
116
  }
117
117
 
118
118
  for (const child of agent.endpoint.parts) {
119
- const deviceType = inferDeviceType(agent.context.agentFor(child));
119
+ const deviceType = inferDeviceType(child.agentFor(agent.context));
120
120
  if (deviceType !== undefined) {
121
121
  return deviceType;
122
122
  }
@@ -81,7 +81,7 @@ export class ServerNodeFailsafeContext extends FailsafeContext {
81
81
  await this.#node.visit(async endpoint => {
82
82
  const networks = this.#storedState?.networks.get(endpoint);
83
83
  if (networks) {
84
- context.agentFor(endpoint).get(NetworkCommissioningBehavior).state.networks = [...networks];
84
+ endpoint.agentFor(context).get(NetworkCommissioningBehavior).state.networks = [...networks];
85
85
  }
86
86
  });
87
87
  });
@@ -67,7 +67,7 @@ export class GroupsServer extends GroupsBase {
67
67
 
68
68
  // We need to search the root here ourselves because we cannot include ServerNode because else we generate a
69
69
  // circular dependency
70
- #rootEndpoint(): Endpoint<RootEndpoint> {
70
+ get #rootEndpoint(): Endpoint<RootEndpoint> {
71
71
  const rootEndpoint = this.endpoint.ownerOfType(RootEndpoint);
72
72
  if (rootEndpoint === undefined) {
73
73
  throw new InternalError("RootEndpoint not found");
@@ -76,7 +76,7 @@ export class GroupsServer extends GroupsBase {
76
76
  }
77
77
 
78
78
  async #actOnGroupKeyManagement<T>(act: (groupKeyManagement: GroupKeyManagementServer) => T): Promise<T> {
79
- const agent = this.context.agentFor(this.#rootEndpoint());
79
+ const agent = this.#rootEndpoint.agentFor(this.context);
80
80
  const gkm = agent.get(GroupKeyManagementServer);
81
81
  await agent.context.transaction.addResources(gkm);
82
82
  await agent.context.transaction.begin();
@@ -122,7 +122,7 @@ export class GroupsServer extends GroupsBase {
122
122
  const fabricIndex = fabric.fabricIndex;
123
123
  const endpointNumber = this.endpoint.number;
124
124
 
125
- const { groupTable } = this.#rootEndpoint().stateOf(GroupKeyManagementServer);
125
+ const { groupTable } = this.#rootEndpoint.stateOf(GroupKeyManagementServer);
126
126
  const groupEntry = groupTable.find(entry => entry.groupId === groupId && entry.fabricIndex === fabricIndex);
127
127
  if (groupEntry === undefined || !groupEntry.endpoints.includes(endpointNumber)) {
128
128
  return { status: StatusCode.NotFound, groupId, groupName: "" };
@@ -137,7 +137,7 @@ export class GroupsServer extends GroupsBase {
137
137
  const fabricIndex = fabric.fabricIndex;
138
138
  const endpointNumber = this.endpoint.number;
139
139
 
140
- const { groupTable } = this.#rootEndpoint().stateOf(GroupKeyManagementServer);
140
+ const { groupTable } = this.#rootEndpoint.stateOf(GroupKeyManagementServer);
141
141
  const endpointGroups = groupTable.filter(
142
142
  entry => entry.endpoints.includes(endpointNumber) && entry.fabricIndex === fabricIndex,
143
143
  );
@@ -51,7 +51,7 @@ export class Agent {
51
51
  if (this.#endpoint.owner === undefined) {
52
52
  return undefined;
53
53
  }
54
- return this.context.agentFor(this.#endpoint.owner);
54
+ return this.#endpoint.owner.agentFor(this.context);
55
55
  }
56
56
 
57
57
  /**
@@ -5,7 +5,9 @@
5
5
  */
6
6
 
7
7
  import { Behavior } from "#behavior/Behavior.js";
8
+ import { ActionContext } from "#behavior/context/ActionContext.js";
8
9
  import { NodeActivity } from "#behavior/context/NodeActivity.js";
10
+ import { ContextAgents } from "#behavior/context/server/ContextAgents.js";
9
11
  import { OfflineContext } from "#behavior/context/server/OfflineContext.js";
10
12
  import {
11
13
  Construction,
@@ -112,6 +114,17 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
112
114
  return this.#owner;
113
115
  }
114
116
 
117
+ /**
118
+ * Access an {@link Agent} for this endpoint.
119
+ *
120
+ * An {@link Agent} allows you to interact directly with the behaviors supported by the endpoint. Normally you
121
+ * would use {@link act} to obtain an agent but {@link agentFor} is useful if you need to interact with multiple
122
+ * endpoints in the same context.
123
+ */
124
+ agentFor<T extends EndpointType>(this: Endpoint<T>, context: ActionContext) {
125
+ return ContextAgents(context).agentFor(this);
126
+ }
127
+
115
128
  get endpointProtocol() {
116
129
  if (this.#number === undefined || !this.env.has(ProtocolService)) {
117
130
  return undefined;
@@ -599,7 +612,7 @@ export class Endpoint<T extends EndpointType = EndpointType.Empty> {
599
612
  return OfflineContext.act(
600
613
  purpose,
601
614
  context => {
602
- return actor(context.agentFor(this));
615
+ return actor(this.agentFor(context));
603
616
  },
604
617
  { activity: this.#activity },
605
618
  );
@@ -186,7 +186,7 @@ export class Behaviors {
186
186
 
187
187
  // Initialization action. We initialize all behaviors in the same transaction
188
188
  const initializeBehaviors = (context: ActionContext): MaybePromise => {
189
- const agent = context.agentFor(this.#endpoint);
189
+ const agent = this.#endpoint.agentFor(context);
190
190
 
191
191
  // Activate behaviors
192
192
  //
@@ -377,7 +377,7 @@ export class Behaviors {
377
377
  */
378
378
  async close() {
379
379
  const dispose = async (context: ActionContext) => {
380
- const agent = context.agentFor(this.#endpoint);
380
+ const agent = this.#endpoint.agentFor(context);
381
381
 
382
382
  let destroyNow = new Set(Object.keys(this.#backings));
383
383
  while (destroyNow.size) {
@@ -565,7 +565,7 @@ export class Behaviors {
565
565
  const result = OfflineContext.act(
566
566
  "behavior-late-activation",
567
567
  context => {
568
- this.activate(type, context.agentFor(this.#endpoint));
568
+ this.activate(type, this.#endpoint.agentFor(context));
569
569
 
570
570
  // Agent must remain active until backing is initialized
571
571
  const backing = this.#backingFor(type);
@@ -67,7 +67,7 @@ function Implementation(endpoint: Endpoint, type: Behavior.Type, name: string):
67
67
 
68
68
  // Create function to perform invocation
69
69
  function invokerFor(context: ActionContext) {
70
- const agent = context.agentFor(endpoint);
70
+ const agent = endpoint.agentFor(context);
71
71
  const behavior = agent.get(type);
72
72
  const method = (behavior as unknown as Record<string, Commands.Command>)[name];
73
73
  if (typeof method !== "function") {
@@ -12,7 +12,7 @@ import { NetworkRuntime } from "#behavior/system/network/NetworkRuntime.js";
12
12
  import { Agent } from "#endpoint/Agent.js";
13
13
  import { EndpointInitializer } from "#endpoint/properties/EndpointInitializer.js";
14
14
  import { Identity, Lifecycle, MaybePromise } from "#general";
15
- import { Interactable } from "#protocol";
15
+ import { Interactable, OccurrenceManager } from "#protocol";
16
16
  import { ClientNodeStore } from "#storage/client/ClientNodeStore.js";
17
17
  import { RemoteWriter } from "#storage/client/RemoteWriter.js";
18
18
  import { ServerNodeStore } from "#storage/server/ServerNodeStore.js";
@@ -41,6 +41,9 @@ export class ClientNode extends Node<ClientNode.RootEndpoint> {
41
41
 
42
42
  super(opts);
43
43
 
44
+ // Block the OccurranceManager from parent environment so we don't attempt to record events from peers
45
+ this.env.block(OccurrenceManager);
46
+
44
47
  this.env.set(Node, this);
45
48
  this.env.set(ClientNode, this);
46
49
 
@@ -75,6 +75,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
75
75
  }
76
76
 
77
77
  let { schema } = analysis;
78
+ let isCloned = false;
78
79
  const { extraAttrs, extraCommands } = analysis;
79
80
 
80
81
  // Obtain a ClusterType. This provides TLV for known elements
@@ -99,8 +100,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
99
100
  .map(([k]) => k);
100
101
  if (featureNames.length) {
101
102
  // Update ClusterModel
102
- schema = schema.clone();
103
- schema.supportedFeatures = featureNames;
103
+ cloneSchema();
104
104
 
105
105
  // Update the cluster. Note that we do not validate feature combinations. What the device sends we work with
106
106
  cluster = new ClusterComposer(cluster, true).compose(featureNames.map(capitalize));
@@ -109,7 +109,7 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
109
109
  // If the schema does not match what the device actually returned, further augment both the ClusterModel and
110
110
  // ClusterType with unknown attributes and/or commands
111
111
  if (schema.revision !== analysis.shape.revision || extraAttrs.size || extraCommands.size) {
112
- schema = schema.clone();
112
+ cloneSchema();
113
113
 
114
114
  cluster = {
115
115
  ...cluster,
@@ -118,8 +118,6 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
118
118
  commands: { ...cluster.commands },
119
119
  };
120
120
 
121
- schema.supportedFeatures = supportedFeatures;
122
-
123
121
  for (const id of extraAttrs) {
124
122
  const name = createUnknownName("attr", id);
125
123
  cluster.attributes[camelize(name, false)] = Attribute(id, TlvAny);
@@ -145,6 +143,15 @@ function generateType(analysis: ShapeAnalysis, baseType: Behavior.Type): Cluster
145
143
 
146
144
  return type;
147
145
 
146
+ function cloneSchema() {
147
+ if (isCloned) {
148
+ return;
149
+ }
150
+ schema = schema.clone();
151
+ schema.supportedFeatures = featureNames;
152
+ isCloned = true;
153
+ }
154
+
148
155
  function implementCommand(command: ClusterType.Command) {
149
156
  return async function (this: ClusterBehavior, fields?: {}) {
150
157
  const node = this.env.get(Node) as ClientNode;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { ElementEvent, Events } from "#behavior/Events.js";
8
+ import { camelize, Logger } from "#general";
9
+ import { ClusterModel, EventModel, MatterModel } from "#model";
10
+ import { ClientNode } from "#node/ClientNode.js";
11
+ import { ReadResult } from "#protocol";
12
+ import { ClusterId, EndpointNumber, EventId } from "#types";
13
+ import { ClientStructure } from "./ClientStructure.js";
14
+
15
+ const logger = Logger.get("ClientEventEmitter");
16
+
17
+ /**
18
+ * Event handler for Matter events transmitted by a peer.
19
+ *
20
+ * TODO - set priority on context when split for server vs. client
21
+ * TODO - record latest event number for each subscription shape (or just wildcard?)
22
+ */
23
+ export interface ClientEventEmitter {
24
+ (event: ReadResult.EventValue): void;
25
+ }
26
+
27
+ /**
28
+ * Cache of MatterModel + cluster + event ID -> event name.
29
+ */
30
+ const nameCache = new WeakMap<
31
+ MatterModel,
32
+ Record<`${ClusterId}-${EventId}`, undefined | { cluster: string; event: string }>
33
+ >();
34
+
35
+ /**
36
+ * We warn for each cluster or cluster+event that we don't support.
37
+ */
38
+ const warnedForUnknown = new Set<ClusterId | `${ClusterId}-${EventId}`>();
39
+
40
+ export function ClientEventEmitter(node: ClientNode, structure: ClientStructure) {
41
+ return emitClientEvent;
42
+
43
+ function emitClientEvent(occurrence: ReadResult.EventValue) {
44
+ const names = getNames(node.matter, occurrence);
45
+ if (!names) {
46
+ return;
47
+ }
48
+
49
+ const event = getEvent(occurrence.path.endpointId, names.cluster, names.event);
50
+ if (event) {
51
+ node.act(agent => {
52
+ // Current ActionContext is not writable, could skip act() but meh, see TODO above
53
+ //agent.context.priority = occurrence.priority;
54
+ event.emit(occurrence.value, agent.context);
55
+ });
56
+ }
57
+ }
58
+
59
+ function getEvent(endpointId: EndpointNumber, clusterName: string, eventName: string) {
60
+ const endpoint = structure.endpointFor(endpointId);
61
+ if (endpoint === undefined) {
62
+ logger.error(`Received event for unsupported endpoint #${endpointId}`);
63
+ return;
64
+ }
65
+
66
+ const events = (endpoint.events as Events.Generic<ElementEvent>)[clusterName];
67
+ if (events === undefined) {
68
+ logger.error(`Received event ${eventName} for unsupported cluster ${clusterName} on ${endpoint}`);
69
+ return;
70
+ }
71
+
72
+ const event = events[eventName];
73
+ if (event === undefined) {
74
+ logger.error(`Received unsupported event ${eventName} for cluster ${clusterName} on ${endpoint}`);
75
+ return;
76
+ }
77
+
78
+ return event;
79
+ }
80
+ }
81
+
82
+ function getNames(matter: MatterModel, { path: { clusterId, eventId } }: ReadResult.EventValue) {
83
+ let matterCache = nameCache.get(matter);
84
+ if (matterCache === undefined) {
85
+ matterCache = {};
86
+ nameCache.set(matter, matterCache);
87
+ }
88
+
89
+ const key = `${clusterId}-${eventId}` as const;
90
+ if (key in matterCache) {
91
+ return matterCache[key];
92
+ }
93
+
94
+ const cluster = matter.get(ClusterModel, clusterId);
95
+ if (cluster === undefined) {
96
+ if (!warnedForUnknown.has(clusterId)) {
97
+ logger.warn(`Ignoring events for unknown cluster #${clusterId}`);
98
+ warnedForUnknown.add(clusterId);
99
+ matterCache[key] = undefined;
100
+ }
101
+ return;
102
+ }
103
+
104
+ const event = cluster.get(EventModel, eventId);
105
+ if (event === undefined) {
106
+ if (!warnedForUnknown.has(key)) {
107
+ logger.warn(`Ignoring unknown event #${eventId} for ${cluster.name} cluster`);
108
+ warnedForUnknown.add(key);
109
+ matterCache[key] = undefined;
110
+ }
111
+ return;
112
+ }
113
+
114
+ return (matterCache[key] = { cluster: camelize(cluster.name), event: camelize(event.name) });
115
+ }
@@ -17,6 +17,7 @@ import { DatasourceCache } from "#storage/client/DatasourceCache.js";
17
17
  import { ClientNodeStore } from "#storage/index.js";
18
18
  import type { AttributeId, ClusterId, ClusterType, CommandId, DeviceTypeId, EndpointNumber } from "#types";
19
19
  import { ClientBehavior } from "./ClientBehavior.js";
20
+ import { ClientEventEmitter } from "./ClientEventEmitter.js";
20
21
 
21
22
  const DEVICE_TYPE_LIST_ATTR_ID = DescriptorCluster.attributes.deviceTypeList.id;
22
23
  const SERVER_LIST_ATTR_ID = DescriptorCluster.attributes.serverList.id;
@@ -28,6 +29,7 @@ const PARTS_LIST_ATTR_ID = DescriptorCluster.attributes.partsList.id;
28
29
  export class ClientStructure {
29
30
  #nodeStore: ClientNodeStore;
30
31
  #endpoints: Record<EndpointNumber, EndpointStructure> = {};
32
+ #emitEvent: ClientEventEmitter;
31
33
 
32
34
  constructor(node: ClientNode) {
33
35
  this.#nodeStore = node.env.get(ClientNodeStore);
@@ -35,6 +37,7 @@ export class ClientStructure {
35
37
  endpoint: node,
36
38
  clusters: {},
37
39
  };
40
+ this.#emitEvent = ClientEventEmitter(node, this);
38
41
  }
39
42
 
40
43
  /**
@@ -76,9 +79,9 @@ export class ClientStructure {
76
79
  }
77
80
 
78
81
  /**
82
+ * Obtain the store for a non-cluster behavior.
79
83
  *
80
- * @param request
81
- * @returns
84
+ * The data for these behaviors is managed locally and not synced from the peer.
82
85
  */
83
86
  storeForLocal(endpoint: Endpoint, type: Behavior.Type) {
84
87
  return this.#nodeStore.storeForEndpoint(endpoint).createStoreForLocalBehavior(type.id);
@@ -132,39 +135,17 @@ export class ClientStructure {
132
135
 
133
136
  for await (const chunk of changes) {
134
137
  for (const change of chunk) {
135
- if (change.kind !== "attr-value") {
136
- continue;
137
- }
138
-
139
- const { endpointId, clusterId, attributeId } = change.path;
138
+ switch (change.kind) {
139
+ case "attr-value":
140
+ currentUpdates = await this.#mutateAttribute(change, scope, currentUpdates);
141
+ break;
140
142
 
141
- // If we are building updates to a cluster and the cluster/endpoint changes, apply the current update
142
- // set
143
- if (
144
- currentUpdates &&
145
- (currentUpdates.endpointId !== endpointId || currentUpdates.clusterId !== clusterId)
146
- ) {
147
- await this.#updateCluster(currentUpdates);
148
- currentUpdates = undefined;
143
+ case "event-value":
144
+ this.#emitEvent(change);
145
+ break;
149
146
  }
150
-
151
- if (currentUpdates === undefined) {
152
- // Updating a new endpoint/cluster
153
- currentUpdates = {
154
- endpointId,
155
- clusterId,
156
- values: {
157
- [attributeId]: change.value,
158
- },
159
- };
160
-
161
- // Update version but only if this was a wildcard read
162
- if (scope.isWildcard(endpointId, clusterId)) {
163
- currentUpdates.values[DatasourceCache.VERSION_KEY] = change.version;
164
- }
165
- } else {
166
- // Add value to change set for current endpoint/cluster
167
- currentUpdates.values[attributeId] = change.value;
147
+ if (change.kind !== "attr-value") {
148
+ continue;
168
149
  }
169
150
  }
170
151
 
@@ -176,8 +157,44 @@ export class ClientStructure {
176
157
  }
177
158
  }
178
159
 
160
+ async #mutateAttribute(
161
+ change: ReadResult.AttributeValue,
162
+ scope: ReadScope,
163
+ currentUpdates: undefined | AttributeUpdates,
164
+ ) {
165
+ const { endpointId, clusterId, attributeId } = change.path;
166
+
167
+ // If we are building updates to a cluster and the cluster/endpoint changes, apply the current update
168
+ // set
169
+ if (currentUpdates && (currentUpdates.endpointId !== endpointId || currentUpdates.clusterId !== clusterId)) {
170
+ await this.#updateCluster(currentUpdates);
171
+ currentUpdates = undefined;
172
+ }
173
+
174
+ if (currentUpdates === undefined) {
175
+ // Updating a new endpoint/cluster
176
+ currentUpdates = {
177
+ endpointId,
178
+ clusterId,
179
+ values: {
180
+ [attributeId]: change.value,
181
+ },
182
+ };
183
+
184
+ // Update version but only if this was a wildcard read
185
+ if (scope.isWildcard(endpointId, clusterId)) {
186
+ currentUpdates.values[DatasourceCache.VERSION_KEY] = change.version;
187
+ }
188
+ } else {
189
+ // Add value to change set for current endpoint/cluster
190
+ currentUpdates.values[attributeId] = change.value;
191
+ }
192
+
193
+ return currentUpdates;
194
+ }
195
+
179
196
  /**
180
- * Obtain the {@link ClusterType} for an endpoint number and cluster ID.
197
+ * Obtain the {@link ClusterType} for an {@link EndpointNumber} and {@link ClusterId}.
181
198
  */
182
199
  clusterFor(endpoint: EndpointNumber, cluster: ClusterId) {
183
200
  const ep = this.#endpointFor(endpoint);
@@ -188,6 +205,13 @@ export class ClientStructure {
188
205
  return this.#clusterFor(ep, cluster)?.behavior?.cluster;
189
206
  }
190
207
 
208
+ /**
209
+ * Obtain the {@link Endpoint} for a {@link EndpointNumber}.
210
+ */
211
+ endpointFor(endpoint: EndpointNumber): Endpoint | undefined {
212
+ return this.#endpoints[endpoint]?.endpoint;
213
+ }
214
+
191
215
  /**
192
216
  * Apply new attribute values for specific endpoint/cluster.
193
217
  *
@@ -44,7 +44,7 @@ export class IdentityService {
44
44
  other = this.#node;
45
45
  } else {
46
46
  if (this.#partsById === undefined) {
47
- this.#partsById = OfflineContext.ReadOnly.agentFor(this.#node).get(IndexBehavior).partsById;
47
+ this.#partsById = this.#node.agentFor(OfflineContext.ReadOnly).get(IndexBehavior).partsById;
48
48
  }
49
49
  other = this.#partsById?.[number];
50
50
  }
@@ -602,7 +602,7 @@ function invokeCommand(
602
602
  requestDiagnostic,
603
603
  );
604
604
 
605
- const agent = context.agentFor(endpoint);
605
+ const agent = endpoint.agentFor(context);
606
606
  const behavior = agent.get(backing.type);
607
607
 
608
608
  let isAsync = false;