@matter/node 0.13.0 → 0.13.1-alpha.0-20250501-80c86b03e

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 (60) hide show
  1. package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
  2. package/dist/cjs/behavior/context/server/OnlineContext.js +6 -3
  3. package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
  4. package/dist/cjs/behavior/system/controller/ControllerBehavior.js +2 -1
  5. package/dist/cjs/behavior/system/controller/ControllerBehavior.js.map +1 -1
  6. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  7. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js +7 -7
  8. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  9. package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.d.ts +2 -3
  10. package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.d.ts.map +1 -1
  11. package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.js.map +1 -1
  12. package/dist/cjs/node/server/InteractionServer.d.ts +84 -0
  13. package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -0
  14. package/dist/cjs/node/server/InteractionServer.js +1326 -0
  15. package/dist/cjs/node/server/InteractionServer.js.map +6 -0
  16. package/dist/cjs/node/server/ProtocolService.d.ts.map +1 -1
  17. package/dist/cjs/node/server/ProtocolService.js +9 -8
  18. package/dist/cjs/node/server/ProtocolService.js.map +2 -2
  19. package/dist/cjs/node/server/index.d.ts +1 -1
  20. package/dist/cjs/node/server/index.d.ts.map +1 -1
  21. package/dist/cjs/node/server/index.js +1 -1
  22. package/dist/cjs/node/server/index.js.map +1 -1
  23. package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
  24. package/dist/esm/behavior/context/server/OnlineContext.js +6 -3
  25. package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
  26. package/dist/esm/behavior/system/controller/ControllerBehavior.js +1 -1
  27. package/dist/esm/behavior/system/controller/ControllerBehavior.js.map +1 -1
  28. package/dist/esm/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  29. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js +2 -3
  30. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  31. package/dist/esm/behavior/system/subscription/SubscriptionBehavior.d.ts +2 -3
  32. package/dist/esm/behavior/system/subscription/SubscriptionBehavior.d.ts.map +1 -1
  33. package/dist/esm/behavior/system/subscription/SubscriptionBehavior.js.map +1 -1
  34. package/dist/esm/node/server/InteractionServer.d.ts +84 -0
  35. package/dist/esm/node/server/InteractionServer.d.ts.map +1 -0
  36. package/dist/esm/node/server/InteractionServer.js +1348 -0
  37. package/dist/esm/node/server/InteractionServer.js.map +6 -0
  38. package/dist/esm/node/server/ProtocolService.d.ts.map +1 -1
  39. package/dist/esm/node/server/ProtocolService.js +9 -8
  40. package/dist/esm/node/server/ProtocolService.js.map +1 -1
  41. package/dist/esm/node/server/index.d.ts +1 -1
  42. package/dist/esm/node/server/index.d.ts.map +1 -1
  43. package/dist/esm/node/server/index.js +1 -1
  44. package/package.json +7 -7
  45. package/src/behavior/context/server/OnlineContext.ts +9 -4
  46. package/src/behavior/system/controller/ControllerBehavior.ts +1 -1
  47. package/src/behavior/system/network/ServerNetworkRuntime.ts +4 -7
  48. package/src/behavior/system/subscription/SubscriptionBehavior.ts +2 -3
  49. package/src/node/server/InteractionServer.ts +1757 -0
  50. package/src/node/server/ProtocolService.ts +10 -8
  51. package/src/node/server/index.ts +1 -1
  52. package/dist/cjs/node/server/TransactionalInteractionServer.d.ts +0 -57
  53. package/dist/cjs/node/server/TransactionalInteractionServer.d.ts.map +0 -1
  54. package/dist/cjs/node/server/TransactionalInteractionServer.js +0 -334
  55. package/dist/cjs/node/server/TransactionalInteractionServer.js.map +0 -6
  56. package/dist/esm/node/server/TransactionalInteractionServer.d.ts +0 -57
  57. package/dist/esm/node/server/TransactionalInteractionServer.d.ts.map +0 -1
  58. package/dist/esm/node/server/TransactionalInteractionServer.js +0 -322
  59. package/dist/esm/node/server/TransactionalInteractionServer.js.map +0 -6
  60. package/src/node/server/TransactionalInteractionServer.ts +0 -413
@@ -1,413 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2022-2025 Matter.js Authors
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- import { ActionContext } from "#behavior/context/ActionContext.js";
8
- import { ActionTracer } from "#behavior/context/ActionTracer.js";
9
- import { NodeActivity } from "#behavior/context/NodeActivity.js";
10
- import { OfflineContext } from "#behavior/context/server/OfflineContext.js";
11
- import { OnlineContext } from "#behavior/context/server/OnlineContext.js";
12
- import { AccessControlCluster } from "#clusters/access-control";
13
- import { Endpoint } from "#endpoint/Endpoint.js";
14
- import { EndpointLifecycle } from "#endpoint/properties/EndpointLifecycle.js";
15
- import { EndpointServer } from "#endpoint/server/EndpointServer.js";
16
- import { Diagnostic, InternalError, Logger, MaybePromise } from "#general";
17
- import {
18
- AccessControl,
19
- AccessDeniedError,
20
- AnyAttributeServer,
21
- AnyEventServer,
22
- AttributePath,
23
- AttributeServer,
24
- CommandPath,
25
- CommandServer,
26
- EndpointInterface,
27
- EventPath,
28
- ExchangeManager,
29
- InteractionContext,
30
- InteractionEndpointStructure,
31
- InteractionServer,
32
- InteractionServerMessenger,
33
- Message,
34
- MessageExchange,
35
- MessageType,
36
- SessionManager,
37
- WriteRequest,
38
- WriteResponse,
39
- } from "#protocol";
40
- import { StatusCode, StatusResponseError, TlvEventFilter, TypeFromSchema } from "#types";
41
- import { AccessControlServer } from "../../behaviors/access-control/AccessControlServer.js";
42
- import { ServerNode } from "../ServerNode.js";
43
-
44
- const logger = Logger.get("TransactionalInteractionServer");
45
-
46
- const activityKey = Symbol("activity");
47
-
48
- interface WithActivity {
49
- [activityKey]?: NodeActivity.Activity;
50
- }
51
-
52
- const AclClusterId = AccessControlCluster.id;
53
- const AclAttributeId = AccessControlCluster.attributes.acl.id;
54
-
55
- /**
56
- * Wire up an InteractionServer that initializes an InvocationContext earlier than the cluster API supports.
57
- *
58
- * This is necessary for attributes because the ClusterServer attribute APIs are synchronous while transaction
59
- * management is asynchronous.
60
- *
61
- * It's not necessary for command handling because that API is entirely async. We do it here, however, just for the
62
- * sake of consistency.
63
- *
64
- * This could be integrated directly into InteractionServer but this further refactoring is probably warranted there
65
- * regardless. This keeps the touch light for now.
66
- */
67
- export class TransactionalInteractionServer extends InteractionServer {
68
- #endpointStructure: InteractionEndpointStructure;
69
- #changeListener: (type: EndpointLifecycle.Change, endpoint: Endpoint) => void;
70
- #node: ServerNode;
71
- #activity: NodeActivity;
72
- #newActivityBlocked = false;
73
- #aclServer?: AccessControlServer;
74
- #aclUpdateIsDelayedInExchange = new Set<MessageExchange>();
75
-
76
- static async create(node: ServerNode, sessions: SessionManager) {
77
- const structure = new InteractionEndpointStructure();
78
-
79
- return new TransactionalInteractionServer(node, {
80
- sessions,
81
- structure,
82
- subscriptionOptions: node.state.network.subscriptionOptions,
83
- maxPathsPerInvoke: node.state.basicInformation.maxPathsPerInvoke,
84
- initiateExchange: (address, protocolId) =>
85
- node.env.get(ExchangeManager).initiateExchange(address, protocolId),
86
- });
87
- }
88
-
89
- constructor(node: ServerNode, context: InteractionContext) {
90
- super(context);
91
-
92
- const { structure } = context;
93
-
94
- this.#activity = node.env.get(NodeActivity);
95
-
96
- this.#node = node;
97
- this.#endpointStructure = structure;
98
-
99
- // TODO - rewrite element lookup so we don't need to build the secondary endpoint structure cache
100
- this.#updateStructure();
101
- this.#changeListener = (type, endpoint) => {
102
- switch (type) {
103
- case EndpointLifecycle.Change.ServersChanged:
104
- EndpointServer.forEndpoint(endpoint).updateServers();
105
- this.#updateStructure();
106
- break;
107
-
108
- case EndpointLifecycle.Change.PartsReady:
109
- case EndpointLifecycle.Change.ClientsChanged:
110
- case EndpointLifecycle.Change.Destroyed:
111
- this.#updateStructure();
112
- break;
113
- }
114
- };
115
-
116
- node.lifecycle.changed.on(this.#changeListener);
117
- }
118
-
119
- async [Symbol.asyncDispose]() {
120
- this.#node.lifecycle.changed.off(this.#changeListener);
121
- await this.close();
122
- this.#endpointStructure.close();
123
- await EndpointServer.forEndpoint(this.#node)[Symbol.asyncDispose]();
124
- }
125
-
126
- blockNewActivity() {
127
- this.#newActivityBlocked = true;
128
- }
129
-
130
- override async onNewExchange(exchange: MessageExchange, message: Message) {
131
- // When closing, ignore anything newly incoming
132
- if (this.#newActivityBlocked || this.isClosing) {
133
- return;
134
- }
135
-
136
- // An incoming data report as the first message is not a valid server operation. We instead delegate to a
137
- // client implementation if available
138
- if (message.payloadHeader.messageType === MessageType.ReportData && this.clientHandler) {
139
- return this.clientHandler.onNewExchange(exchange, message);
140
- }
141
-
142
- // Activity tracking. This provides diagnostic information and prevents the server from shutting down whilst
143
- // the exchange is active
144
- using activity = this.#activity.begin(`session#${exchange.session.id.toString(16)}`);
145
- (exchange as WithActivity)[activityKey] = activity;
146
-
147
- // Delegate to InteractionServerMessenger
148
- return new InteractionServerMessenger(exchange)
149
- .handleRequest(this)
150
- .finally(() => delete (exchange as WithActivity)[activityKey]);
151
- }
152
-
153
- get aclServer() {
154
- if (this.#aclServer !== undefined) {
155
- return this.#aclServer;
156
- }
157
- const aclServer = this.#node.act(agent => agent.get(AccessControlServer));
158
- if (MaybePromise.is(aclServer)) {
159
- throw new InternalError("AccessControlServer should already be initialized.");
160
- }
161
- return (this.#aclServer = aclServer);
162
- }
163
-
164
- protected override readAttribute(
165
- path: AttributePath,
166
- attribute: AnyAttributeServer<any>,
167
- exchange: MessageExchange,
168
- fabricFiltered: boolean,
169
- message: Message,
170
- offline = false,
171
- ) {
172
- const readAttribute = () => super.readAttribute(path, attribute, exchange, fabricFiltered, message, offline);
173
-
174
- const endpoint = this.#endpointStructure.getEndpoint(path.endpointId);
175
- if (!endpoint) {
176
- throw new InternalError("Endpoint not found for ACL check. This should never happen.");
177
- }
178
-
179
- const result = offline
180
- ? OfflineContext.act("offline-read", this.#activity, readAttribute)
181
- : OnlineContext({
182
- activity: (exchange as WithActivity)[activityKey],
183
- fabricFiltered,
184
- message,
185
- exchange,
186
- tracer: this.#tracer,
187
- actionType: ActionTracer.ActionType.Read,
188
- node: this.#node,
189
- }).act(readAttribute);
190
-
191
- if (MaybePromise.is(result)) {
192
- throw new InternalError("Reads should not return a promise.");
193
- }
194
- return result;
195
- }
196
-
197
- /**
198
- * Reads the attributes for the given endpoint.
199
- * This can currently only be used for subscriptions because errors are ignored!
200
- */
201
- protected override readEndpointAttributesForSubscription(
202
- attributes: { path: AttributePath; attribute: AnyAttributeServer<any> }[],
203
- exchange: MessageExchange,
204
- fabricFiltered: boolean,
205
- message: Message,
206
- offline = false,
207
- ) {
208
- const readAttributes = () => {
209
- const result = new Array<{
210
- path: AttributePath;
211
- attribute: AnyAttributeServer<unknown>;
212
- value: any;
213
- version: number;
214
- }>();
215
- for (const { path, attribute } of attributes) {
216
- try {
217
- const value = super.readAttribute(path, attribute, exchange, fabricFiltered, message, offline);
218
- result.push({ path, attribute, value: value.value, version: value.version });
219
- } catch (error) {
220
- if (StatusResponseError.is(error, StatusCode.UnsupportedAccess)) {
221
- logger.warn(
222
- `Permission denied reading attribute ${this.#endpointStructure.resolveAttributeName(path)}`,
223
- );
224
- } else {
225
- logger.warn(
226
- `Error reading attribute ${this.#endpointStructure.resolveAttributeName(path)}:`,
227
- error,
228
- );
229
- }
230
- }
231
- }
232
- return result;
233
- };
234
-
235
- const result = offline
236
- ? OfflineContext.act("offline-read", this.#activity, readAttributes)
237
- : OnlineContext({
238
- activity: (exchange as WithActivity)[activityKey],
239
- fabricFiltered,
240
- message,
241
- exchange,
242
- tracer: this.#tracer,
243
- actionType: ActionTracer.ActionType.Read,
244
- node: this.#node,
245
- }).act(readAttributes);
246
- if (MaybePromise.is(result)) {
247
- throw new InternalError("Online read should not return a promise.");
248
- }
249
- return result;
250
- }
251
-
252
- protected override async readEvent(
253
- path: EventPath,
254
- eventFilters: TypeFromSchema<typeof TlvEventFilter>[] | undefined,
255
- event: AnyEventServer<any, any>,
256
- exchange: MessageExchange,
257
- fabricFiltered: boolean,
258
- message: Message,
259
- ) {
260
- const readEvent = (context: ActionContext) => {
261
- if (
262
- context.authorityAt(event.readAcl, {
263
- endpoint: path.endpointId,
264
- cluster: path.clusterId,
265
- } as AccessControl.Location) !== AccessControl.Authority.Granted
266
- ) {
267
- throw new AccessDeniedError(
268
- `Access to ${path.endpointId}/${Diagnostic.hex(path.clusterId)} denied on ${exchange.session.name}.`,
269
- );
270
- }
271
- return super.readEvent(path, eventFilters, event, exchange, fabricFiltered, message);
272
- };
273
-
274
- return OnlineContext({
275
- activity: (exchange as WithActivity)[activityKey],
276
- fabricFiltered,
277
- message,
278
- exchange,
279
- tracer: this.#tracer,
280
- actionType: ActionTracer.ActionType.Read,
281
- node: this.#node,
282
- }).act(readEvent);
283
- }
284
-
285
- override async handleWriteRequest(
286
- exchange: MessageExchange,
287
- writeRequest: WriteRequest,
288
- message: Message,
289
- ): Promise<WriteResponse> {
290
- let result: WriteResponse;
291
- try {
292
- result = await super.handleWriteRequest(exchange, writeRequest, message);
293
- } catch (error) {
294
- if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
295
- // Unlikely to get there at all, but make sure we handle it best we can for now
296
- this.#aclUpdateIsDelayedInExchange.delete(exchange);
297
-
298
- if (this.#aclUpdateIsDelayedInExchange.size === 0) {
299
- // only that one ACl change in flight, so we can reset the delayed ACL
300
- this.aclServer.resetDelayedAccessControlList();
301
- } else {
302
- // TODO: we should restore the delayed data just for this errored fabric?
303
- logger.error("One of multiple concurrent ACL writes failed, unhandled case for now.");
304
- }
305
- }
306
- throw error;
307
- }
308
- // We delayed the ACL update during this write transaction, so we need to update it now that anything is written
309
- if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
310
- this.#aclUpdateIsDelayedInExchange.delete(exchange);
311
-
312
- if (this.#aclUpdateIsDelayedInExchange.size === 0) {
313
- // Committing the ACL changes in case of an unhandled exception might be dangerous, but we do it anyway
314
- this.aclServer.aclUpdateDelayed = false;
315
- } else {
316
- logger.info("Multiple concurrent ACL writes, waiting for all to finish.");
317
- }
318
- }
319
-
320
- return result;
321
- }
322
-
323
- protected override async writeAttribute(
324
- path: AttributePath,
325
- attribute: AttributeServer<any>,
326
- value: any,
327
- exchange: MessageExchange,
328
- message: Message,
329
- endpoint: EndpointInterface,
330
- timed = false,
331
- isListWrite?: boolean,
332
- ) {
333
- const writeAttribute = () =>
334
- super.writeAttribute(path, attribute, value, exchange, message, endpoint, timed, isListWrite);
335
-
336
- if (path.endpointId === 0 && path.clusterId === AclClusterId && path.attributeId === AclAttributeId) {
337
- // This is a hack to prevent the ACL from updating while we are in the middle of a write transaction
338
- // and is needed because Acl should not become effective during writing of the ACL itself.
339
- this.aclServer.aclUpdateDelayed = true;
340
- this.#aclUpdateIsDelayedInExchange.add(exchange);
341
- } else {
342
- // Ok it seems that acl was written, but we now write another path, so we can update Acl attribute now
343
- if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
344
- this.#aclUpdateIsDelayedInExchange.delete(exchange);
345
-
346
- if (this.#aclUpdateIsDelayedInExchange.size === 0) {
347
- this.aclServer.aclUpdateDelayed = false;
348
- } else {
349
- logger.info("Multiple concurrent ACL writes, waiting for all to finish.");
350
- }
351
- }
352
- }
353
-
354
- return OnlineContext({
355
- activity: (exchange as WithActivity)[activityKey],
356
- timed,
357
- message,
358
- exchange,
359
- fabricFiltered: true,
360
- tracer: this.#tracer,
361
- actionType: ActionTracer.ActionType.Write,
362
- node: this.#node,
363
- }).act(writeAttribute);
364
- }
365
-
366
- protected override async invokeCommand(
367
- path: CommandPath,
368
- command: CommandServer<any, any>,
369
- exchange: MessageExchange,
370
- commandFields: any,
371
- message: Message,
372
- endpoint: EndpointInterface,
373
- timed = false,
374
- ) {
375
- const invokeCommand = (context: ActionContext) => {
376
- if (
377
- context.authorityAt(command.invokeAcl, {
378
- endpoint: endpoint.number,
379
- cluster: path.clusterId,
380
- } as AccessControl.Location) !== AccessControl.Authority.Granted
381
- ) {
382
- throw new AccessDeniedError(
383
- `Access to ${endpoint.number}/${Diagnostic.hex(path.clusterId)} denied on ${exchange.session.name}.`,
384
- );
385
- }
386
- return super.invokeCommand(path, command, exchange, commandFields, message, endpoint, timed);
387
- };
388
-
389
- return OnlineContext({
390
- activity: (exchange as WithActivity)[activityKey],
391
- command: true,
392
- timed,
393
- message,
394
- exchange,
395
- tracer: this.#tracer,
396
- actionType: ActionTracer.ActionType.Invoke,
397
- node: this.#node,
398
- }).act(invokeCommand);
399
- }
400
-
401
- get #tracer() {
402
- if (this.#node.env.has(ActionTracer)) {
403
- return this.#node.env.get(ActionTracer);
404
- }
405
- }
406
-
407
- #updateStructure() {
408
- if (this.#node.lifecycle.isPartsReady) {
409
- const server = EndpointServer.forEndpoint(this.#node);
410
- this.#endpointStructure.initializeFromEndpoint(server);
411
- }
412
- }
413
- }