@matter/protocol 0.13.1-alpha.0-20250508-047aa0277 → 0.13.1-alpha.0-20250511-74ef153aa

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 (118) hide show
  1. package/dist/cjs/action/protocols.d.ts +59 -2
  2. package/dist/cjs/action/protocols.d.ts.map +1 -1
  3. package/dist/cjs/action/request/Read.d.ts +2 -2
  4. package/dist/cjs/action/request/Read.d.ts.map +1 -1
  5. package/dist/cjs/action/request/Read.js +4 -4
  6. package/dist/cjs/action/request/Read.js.map +1 -1
  7. package/dist/cjs/action/response/ReadResult.d.ts +6 -2
  8. package/dist/cjs/action/response/ReadResult.d.ts.map +1 -1
  9. package/dist/cjs/action/server/AttributeResponse.d.ts +46 -17
  10. package/dist/cjs/action/server/AttributeResponse.d.ts.map +1 -1
  11. package/dist/cjs/action/server/AttributeResponse.js +128 -110
  12. package/dist/cjs/action/server/AttributeResponse.js.map +2 -2
  13. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts +36 -0
  14. package/dist/cjs/action/server/AttributeSubscriptionResponse.d.ts.map +1 -0
  15. package/dist/cjs/action/server/AttributeSubscriptionResponse.js +86 -0
  16. package/dist/cjs/action/server/AttributeSubscriptionResponse.js.map +6 -0
  17. package/dist/cjs/action/server/DataResponse.d.ts +45 -0
  18. package/dist/cjs/action/server/DataResponse.d.ts.map +1 -0
  19. package/dist/cjs/action/server/DataResponse.js +69 -0
  20. package/dist/cjs/action/server/DataResponse.js.map +6 -0
  21. package/dist/cjs/action/server/EventResponse.d.ts +28 -0
  22. package/dist/cjs/action/server/EventResponse.d.ts.map +1 -0
  23. package/dist/cjs/action/server/EventResponse.js +318 -0
  24. package/dist/cjs/action/server/EventResponse.js.map +6 -0
  25. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -1
  26. package/dist/cjs/action/server/ServerInteraction.js +15 -2
  27. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  28. package/dist/cjs/action/server/index.d.ts +3 -0
  29. package/dist/cjs/action/server/index.d.ts.map +1 -1
  30. package/dist/cjs/action/server/index.js +3 -0
  31. package/dist/cjs/action/server/index.js.map +1 -1
  32. package/dist/cjs/events/OccurrenceManager.d.ts +20 -11
  33. package/dist/cjs/events/OccurrenceManager.d.ts.map +1 -1
  34. package/dist/cjs/events/OccurrenceManager.js +113 -74
  35. package/dist/cjs/events/OccurrenceManager.js.map +1 -1
  36. package/dist/cjs/interaction/InteractionMessenger.d.ts +14 -2
  37. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  38. package/dist/cjs/interaction/InteractionMessenger.js +87 -3
  39. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  40. package/dist/cjs/interaction/index.d.ts +0 -1
  41. package/dist/cjs/interaction/index.d.ts.map +1 -1
  42. package/dist/cjs/interaction/index.js +0 -1
  43. package/dist/cjs/interaction/index.js.map +1 -1
  44. package/dist/cjs/peer/ControllerCommissioningFlow.js +1 -1
  45. package/dist/cjs/protocol/MessageExchange.d.ts.map +1 -1
  46. package/dist/cjs/protocol/MessageExchange.js +11 -1
  47. package/dist/cjs/protocol/MessageExchange.js.map +1 -1
  48. package/dist/esm/action/protocols.d.ts +59 -2
  49. package/dist/esm/action/protocols.d.ts.map +1 -1
  50. package/dist/esm/action/request/Read.d.ts +2 -2
  51. package/dist/esm/action/request/Read.d.ts.map +1 -1
  52. package/dist/esm/action/request/Read.js +4 -4
  53. package/dist/esm/action/request/Read.js.map +1 -1
  54. package/dist/esm/action/response/ReadResult.d.ts +6 -2
  55. package/dist/esm/action/response/ReadResult.d.ts.map +1 -1
  56. package/dist/esm/action/server/AttributeResponse.d.ts +46 -17
  57. package/dist/esm/action/server/AttributeResponse.d.ts.map +1 -1
  58. package/dist/esm/action/server/AttributeResponse.js +129 -113
  59. package/dist/esm/action/server/AttributeResponse.js.map +1 -1
  60. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts +36 -0
  61. package/dist/esm/action/server/AttributeSubscriptionResponse.d.ts.map +1 -0
  62. package/dist/esm/action/server/AttributeSubscriptionResponse.js +66 -0
  63. package/dist/esm/action/server/AttributeSubscriptionResponse.js.map +6 -0
  64. package/dist/esm/action/server/DataResponse.d.ts +45 -0
  65. package/dist/esm/action/server/DataResponse.d.ts.map +1 -0
  66. package/dist/esm/action/server/DataResponse.js +49 -0
  67. package/dist/esm/action/server/DataResponse.js.map +6 -0
  68. package/dist/esm/action/server/EventResponse.d.ts +28 -0
  69. package/dist/esm/action/server/EventResponse.d.ts.map +1 -0
  70. package/dist/esm/action/server/EventResponse.js +305 -0
  71. package/dist/esm/action/server/EventResponse.js.map +6 -0
  72. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -1
  73. package/dist/esm/action/server/ServerInteraction.js +16 -3
  74. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  75. package/dist/esm/action/server/index.d.ts +3 -0
  76. package/dist/esm/action/server/index.d.ts.map +1 -1
  77. package/dist/esm/action/server/index.js +3 -0
  78. package/dist/esm/action/server/index.js.map +1 -1
  79. package/dist/esm/events/OccurrenceManager.d.ts +20 -11
  80. package/dist/esm/events/OccurrenceManager.d.ts.map +1 -1
  81. package/dist/esm/events/OccurrenceManager.js +117 -80
  82. package/dist/esm/events/OccurrenceManager.js.map +1 -1
  83. package/dist/esm/interaction/InteractionMessenger.d.ts +14 -2
  84. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  85. package/dist/esm/interaction/InteractionMessenger.js +87 -3
  86. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  87. package/dist/esm/interaction/index.d.ts +0 -1
  88. package/dist/esm/interaction/index.d.ts.map +1 -1
  89. package/dist/esm/interaction/index.js +0 -1
  90. package/dist/esm/interaction/index.js.map +1 -1
  91. package/dist/esm/peer/ControllerCommissioningFlow.js +1 -1
  92. package/dist/esm/protocol/MessageExchange.d.ts.map +1 -1
  93. package/dist/esm/protocol/MessageExchange.js +11 -1
  94. package/dist/esm/protocol/MessageExchange.js.map +1 -1
  95. package/package.json +6 -6
  96. package/src/action/protocols.ts +68 -2
  97. package/src/action/request/Read.ts +2 -2
  98. package/src/action/response/ReadResult.ts +8 -1
  99. package/src/action/server/AttributeResponse.ts +145 -118
  100. package/src/action/server/AttributeSubscriptionResponse.ts +90 -0
  101. package/src/action/server/DataResponse.ts +70 -0
  102. package/src/action/server/EventResponse.ts +381 -0
  103. package/src/action/server/ServerInteraction.ts +18 -4
  104. package/src/action/server/index.ts +3 -0
  105. package/src/events/OccurrenceManager.ts +126 -100
  106. package/src/interaction/InteractionMessenger.ts +93 -8
  107. package/src/interaction/index.ts +0 -1
  108. package/src/peer/ControllerCommissioningFlow.ts +1 -1
  109. package/src/protocol/MessageExchange.ts +13 -1
  110. package/dist/cjs/interaction/ServerSubscription.d.ts +0 -116
  111. package/dist/cjs/interaction/ServerSubscription.d.ts.map +0 -1
  112. package/dist/cjs/interaction/ServerSubscription.js +0 -778
  113. package/dist/cjs/interaction/ServerSubscription.js.map +0 -6
  114. package/dist/esm/interaction/ServerSubscription.d.ts +0 -116
  115. package/dist/esm/interaction/ServerSubscription.d.ts.map +0 -1
  116. package/dist/esm/interaction/ServerSubscription.js +0 -778
  117. package/dist/esm/interaction/ServerSubscription.js.map +0 -6
  118. package/src/interaction/ServerSubscription.ts +0 -1038
@@ -1,778 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2022-2025 Matter.js Authors
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
- import {
7
- Diagnostic,
8
- InternalError,
9
- Logger,
10
- MatterAggregateError,
11
- MatterError,
12
- MaybePromise,
13
- NetworkError,
14
- NoResponseTimeoutError,
15
- Time,
16
- isObject
17
- } from "#general";
18
- import { Specification } from "#model";
19
- import {
20
- EventNumber,
21
- INTERACTION_PROTOCOL_ID,
22
- StatusCode,
23
- StatusResponseError
24
- } from "#types";
25
- import { FabricScopedAttributeServer } from "../cluster/server/AttributeServer.js";
26
- import { FabricSensitiveEventServer } from "../cluster/server/EventServer.js";
27
- import { NoChannelError } from "../protocol/ChannelManager.js";
28
- import {
29
- attributePathToId,
30
- clusterPathToId,
31
- eventPathToId
32
- } from "./InteractionEndpointStructure.js";
33
- import { InteractionServerMessenger } from "./InteractionMessenger.js";
34
- import { Subscription } from "./Subscription.js";
35
- const logger = Logger.get("ServerSubscription");
36
- const MAX_INTERVAL_PUBLISHER_LIMIT_S = 60 * 60;
37
- const INTERNAL_INTERVAL_PUBLISHER_LIMIT_S = 3 * 60;
38
- const MIN_INTERVAL_S = 2;
39
- const DEFAULT_RANDOMIZATION_WINDOW_S = 10;
40
- var ServerSubscriptionConfig;
41
- ((ServerSubscriptionConfig2) => {
42
- function of(options) {
43
- return {
44
- maxIntervalSeconds: options?.maxIntervalSeconds ?? INTERNAL_INTERVAL_PUBLISHER_LIMIT_S,
45
- minIntervalSeconds: Math.max(options?.minIntervalSeconds ?? MIN_INTERVAL_S, MIN_INTERVAL_S),
46
- randomizationWindowSeconds: options?.randomizationWindowSeconds ?? DEFAULT_RANDOMIZATION_WINDOW_S
47
- };
48
- }
49
- ServerSubscriptionConfig2.of = of;
50
- })(ServerSubscriptionConfig || (ServerSubscriptionConfig = {}));
51
- class ServerSubscription extends Subscription {
52
- #context;
53
- #structure;
54
- #lastUpdateTimeMs = 0;
55
- #updateTimer;
56
- #sendDelayTimer = Time.getTimer(
57
- `Subscription ${this.id} delay`,
58
- 50,
59
- () => this.#triggerSendUpdate()
60
- );
61
- #outstandingAttributeUpdates = /* @__PURE__ */ new Map();
62
- #outstandingEventUpdates = /* @__PURE__ */ new Set();
63
- #attributeListeners = /* @__PURE__ */ new Map();
64
- #eventListeners = /* @__PURE__ */ new Map();
65
- #sendUpdatesActivated = false;
66
- #sendIntervalMs;
67
- #minIntervalFloorMs;
68
- #maxIntervalCeilingMs;
69
- #peerAddress;
70
- #sendNextUpdateImmediately = false;
71
- #sendUpdateErrorCounter = 0;
72
- #attributeUpdatePromises = /* @__PURE__ */ new Set();
73
- #currentUpdatePromise;
74
- constructor(options) {
75
- const {
76
- id,
77
- context,
78
- criteria,
79
- minIntervalFloorSeconds,
80
- maxIntervalCeilingSeconds,
81
- subscriptionOptions,
82
- useAsMaxInterval,
83
- useAsSendInterval
84
- } = options;
85
- super(context.session, id, criteria);
86
- this.#context = context;
87
- this.#structure = context.structure;
88
- this.#peerAddress = this.session.peerAddress;
89
- this.#minIntervalFloorMs = minIntervalFloorSeconds * 1e3;
90
- this.#maxIntervalCeilingMs = maxIntervalCeilingSeconds * 1e3;
91
- let maxInterval;
92
- let sendInterval;
93
- if (useAsMaxInterval !== void 0 && useAsSendInterval !== void 0) {
94
- maxInterval = useAsMaxInterval * 1e3;
95
- sendInterval = useAsSendInterval * 1e3;
96
- } else {
97
- ({ maxInterval, sendInterval } = this.#determineSendingIntervals(
98
- subscriptionOptions.minIntervalSeconds * 1e3,
99
- subscriptionOptions.maxIntervalSeconds * 1e3,
100
- subscriptionOptions.randomizationWindowSeconds * 1e3
101
- ));
102
- }
103
- this.maxIntervalMs = maxInterval;
104
- this.#sendIntervalMs = sendInterval;
105
- this.#updateTimer = Time.getTimer(
106
- `Subscription ${this.id} update`,
107
- this.#sendIntervalMs,
108
- () => this.#prepareDataUpdate()
109
- );
110
- }
111
- #determineSendingIntervals(subscriptionMinIntervalMs, subscriptionMaxIntervalMs, subscriptionRandomizationWindowMs) {
112
- const maxInterval = Math.min(
113
- Math.max(
114
- subscriptionMinIntervalMs,
115
- Math.max(this.#minIntervalFloorMs, Math.min(subscriptionMaxIntervalMs, this.#maxIntervalCeilingMs))
116
- ) + Math.floor(subscriptionRandomizationWindowMs * Math.random()),
117
- MAX_INTERVAL_PUBLISHER_LIMIT_S * 1e3
118
- );
119
- let sendInterval = Math.floor(maxInterval / 2);
120
- if (sendInterval < 6e4) {
121
- sendInterval = Math.max(this.#minIntervalFloorMs, Math.floor(maxInterval * 0.8));
122
- }
123
- if (sendInterval < subscriptionMinIntervalMs) {
124
- logger.warn(
125
- `Determined subscription send interval of ${sendInterval}ms is too low. Using maxInterval (${maxInterval}ms) instead.`
126
- );
127
- sendInterval = subscriptionMinIntervalMs;
128
- }
129
- return { maxInterval, sendInterval };
130
- }
131
- #registerNewAttributes() {
132
- const newAttributes = new Array();
133
- const attributeErrors = new Array();
134
- const formerAttributes = new Set(this.#attributeListeners.keys());
135
- if (this.criteria.attributeRequests !== void 0) {
136
- this.criteria.attributeRequests.forEach((path) => {
137
- const attributes = this.#structure.getAttributes([path]);
138
- if (attributes.length === 0) {
139
- const { endpointId, clusterId, attributeId } = path;
140
- if (endpointId === void 0 || clusterId === void 0 || attributeId === void 0) {
141
- logger.debug(
142
- `Subscription attribute ${this.#structure.resolveAttributeName(
143
- path
144
- )}: ignore non-existing attribute`
145
- );
146
- } else {
147
- try {
148
- this.#structure.validateConcreteAttributePath(endpointId, clusterId, attributeId);
149
- throw new InternalError(
150
- "validateConcreteAttributePath check should throw StatusResponseError but did not."
151
- );
152
- } catch (e) {
153
- StatusResponseError.accept(e);
154
- logger.debug(
155
- `Subscription attribute ${this.#structure.resolveAttributeName(
156
- path
157
- )}: unsupported path: Status=${e.code}`
158
- );
159
- attributeErrors.push({ path, status: { status: e.code } });
160
- }
161
- }
162
- return;
163
- }
164
- attributes.forEach(({ path: path2, attribute }) => {
165
- formerAttributes.delete(attributePathToId(path2));
166
- const existingAttributeListener = this.#attributeListeners.get(attributePathToId(path2));
167
- if (existingAttributeListener !== void 0) {
168
- const { attribute: existingAttribute, listener: existingListener } = existingAttributeListener;
169
- if (existingAttribute !== attribute) {
170
- if (existingListener !== void 0) {
171
- existingAttribute.removeValueChangeListener(existingListener);
172
- }
173
- this.#attributeListeners.delete(attributePathToId(path2));
174
- } else {
175
- return;
176
- }
177
- }
178
- if (attribute.isSubscribable) {
179
- const listener = (value, version) => this.attributeChangeListener(path2, attribute.schema, version, value);
180
- attribute.addValueChangeListener(listener);
181
- this.#attributeListeners.set(attributePathToId(path2), { attribute, listener });
182
- } else {
183
- this.#attributeListeners.set(attributePathToId(path2), { attribute });
184
- }
185
- newAttributes.push({ path: path2, attribute });
186
- });
187
- });
188
- }
189
- this.unregisterAttributeListeners(Array.from(formerAttributes.values()));
190
- return { newAttributes, attributeErrors };
191
- }
192
- unregisterAttributeListeners(list) {
193
- for (const pathId of list) {
194
- const existingAttributeListener = this.#attributeListeners.get(pathId);
195
- if (existingAttributeListener !== void 0) {
196
- const { attribute, listener } = existingAttributeListener;
197
- if (listener !== void 0) {
198
- attribute.removeValueChangeListener(listener);
199
- }
200
- this.#attributeListeners.delete(pathId);
201
- }
202
- }
203
- }
204
- #registerNewEvents() {
205
- const newEvents = new Array();
206
- const eventErrors = new Array();
207
- const formerEvents = new Set(this.#eventListeners.keys());
208
- if (this.criteria.eventRequests !== void 0) {
209
- this.criteria.eventRequests.forEach((path) => {
210
- const events = this.#structure.getEvents([path]);
211
- if (events.length === 0) {
212
- const { endpointId, clusterId, eventId } = path;
213
- if (endpointId === void 0 || clusterId === void 0 || eventId === void 0) {
214
- logger.debug(
215
- `Subscription event ${this.#structure.resolveEventName(path)}: ignore non-existing event`
216
- );
217
- } else {
218
- try {
219
- this.#structure.validateConcreteEventPath(endpointId, clusterId, eventId);
220
- throw new InternalError(
221
- "validateConcreteEventPath should throw StatusResponseError but did not."
222
- );
223
- } catch (e) {
224
- StatusResponseError.accept(e);
225
- logger.debug(
226
- `Subscription event ${this.#structure.resolveEventName(
227
- path
228
- )}: unsupported path: Status=${e.code}`
229
- );
230
- eventErrors.push({ path, status: { status: e.code } });
231
- }
232
- }
233
- return;
234
- }
235
- events.forEach(({ path: path2, event }) => {
236
- formerEvents.delete(eventPathToId(path2));
237
- const existingEventListener = this.#eventListeners.get(eventPathToId(path2));
238
- if (existingEventListener !== void 0) {
239
- const { event: existingEvent, listener: existingListener } = existingEventListener;
240
- if (existingEvent !== event) {
241
- if (existingListener !== void 0) {
242
- existingEvent.removeListener(existingListener);
243
- }
244
- this.#eventListeners.delete(eventPathToId(path2));
245
- } else {
246
- return;
247
- }
248
- }
249
- const listener = (newEvent) => this.eventChangeListener(path2, event.schema, newEvent);
250
- event.addListener(listener);
251
- newEvents.push({ path: path2, event });
252
- this.#eventListeners.set(eventPathToId(path2), { event, listener });
253
- });
254
- });
255
- }
256
- this.unregisterEventListeners(Array.from(formerEvents.values()));
257
- return { newEvents, eventErrors };
258
- }
259
- unregisterEventListeners(list) {
260
- for (const pathId of list) {
261
- const existingEventListener = this.#eventListeners.get(pathId);
262
- if (existingEventListener !== void 0) {
263
- const { event, listener } = existingEventListener;
264
- if (listener !== void 0) {
265
- event.removeListener(listener);
266
- }
267
- this.#eventListeners.delete(pathId);
268
- }
269
- }
270
- }
271
- /**
272
- * Update the session after an endpoint structure change. The method will initialize all missing new attributes and
273
- * events and will remove listeners no longer needed.
274
- * Newly added attributes are then treated as "changed values" and will be sent as subscription data update to the
275
- * controller. The data of newly added events are not sent automatically.
276
- */
277
- async updateSubscription() {
278
- const { newAttributes } = this.#registerNewAttributes();
279
- for (const { path, attribute } of newAttributes) {
280
- const { version, value } = this.#context.readAttribute(path, attribute);
281
- this.#outstandingAttributeUpdates.set(attributePathToId(path), {
282
- attribute,
283
- path,
284
- schema: attribute.schema,
285
- version,
286
- value
287
- });
288
- }
289
- const { newEvents } = this.#registerNewEvents();
290
- const occurrences = Array();
291
- for (const { path, event } of newEvents) {
292
- const { schema } = event;
293
- let eventOccurrences = event.get(
294
- this.session,
295
- this.criteria.isFabricFiltered,
296
- void 0,
297
- this.criteria.eventFilters
298
- );
299
- if (MaybePromise.is(eventOccurrences)) {
300
- eventOccurrences = await eventOccurrences;
301
- }
302
- occurrences.push(
303
- ...eventOccurrences.map((data) => ({
304
- event,
305
- schema,
306
- path,
307
- data
308
- }))
309
- );
310
- }
311
- occurrences.sort((a, b) => {
312
- const eventNumberA = a.data?.number ?? EventNumber(0);
313
- const eventNumberB = b.data?.number ?? EventNumber(0);
314
- if (eventNumberA > eventNumberB) {
315
- return 1;
316
- } else if (eventNumberA < eventNumberB) {
317
- return -1;
318
- } else {
319
- return 0;
320
- }
321
- });
322
- for (const occurrence of occurrences) {
323
- this.#outstandingEventUpdates.add(occurrence);
324
- }
325
- this.#prepareDataUpdate();
326
- }
327
- get sendInterval() {
328
- return Math.ceil(this.#sendIntervalMs / 1e3);
329
- }
330
- get minIntervalFloorSeconds() {
331
- return Math.ceil(this.#minIntervalFloorMs / 1e3);
332
- }
333
- get maxIntervalCeilingSeconds() {
334
- return Math.ceil(this.#maxIntervalCeilingMs / 1e3);
335
- }
336
- activate() {
337
- super.activate();
338
- if (this.criteria.eventFilters !== void 0) this.criteria.eventFilters.length = 0;
339
- if (this.criteria.dataVersionFilters !== void 0) this.criteria.dataVersionFilters.length = 0;
340
- this.#sendUpdatesActivated = true;
341
- if (this.#outstandingAttributeUpdates.size > 0 || this.#outstandingEventUpdates.size > 0) {
342
- this.#triggerSendUpdate();
343
- }
344
- this.#updateTimer = Time.getTimer(
345
- "Subscription update",
346
- this.#sendIntervalMs,
347
- () => this.#prepareDataUpdate()
348
- ).start();
349
- this.#structure.change.on(() => {
350
- if (this.isClosed) {
351
- return;
352
- }
353
- this.updateSubscription().catch(
354
- (error) => logger.error("Error updating subscription after structure change:", error)
355
- );
356
- });
357
- }
358
- /**
359
- * Check if data should be sent straight away or delayed because the minimum interval is not reached. Delay real
360
- * sending by 50ms in any case to mke sure to catch all updates.
361
- */
362
- #prepareDataUpdate() {
363
- if (this.#sendDelayTimer.isRunning || this.isClosed) {
364
- return;
365
- }
366
- if (!this.#sendUpdatesActivated) {
367
- return;
368
- }
369
- this.#updateTimer.stop();
370
- const now = Time.nowMs();
371
- const timeSinceLastUpdateMs = now - this.#lastUpdateTimeMs;
372
- if (timeSinceLastUpdateMs < this.#minIntervalFloorMs) {
373
- this.#updateTimer = Time.getTimer(
374
- "Subscription update",
375
- this.#minIntervalFloorMs - timeSinceLastUpdateMs,
376
- () => this.#prepareDataUpdate()
377
- ).start();
378
- return;
379
- }
380
- this.#sendDelayTimer.start();
381
- this.#updateTimer = Time.getTimer(
382
- `Subscription update ${this.id}`,
383
- this.#sendIntervalMs,
384
- () => this.#prepareDataUpdate()
385
- ).start();
386
- }
387
- #triggerSendUpdate() {
388
- if (this.#currentUpdatePromise !== void 0) {
389
- logger.debug("Sending update already in progress, delaying update ...");
390
- this.#sendNextUpdateImmediately = true;
391
- return;
392
- }
393
- this.#currentUpdatePromise = this.#sendUpdate().catch((error) => logger.warn("Sending subscription update failed:", error)).finally(() => this.#currentUpdatePromise = void 0);
394
- }
395
- /**
396
- * Determine all attributes that have changed since the last update and send them tout to the subscriber.
397
- * Important: This method MUST NOT be called directly. Use triggerSendUpdate() instead!
398
- */
399
- async #sendUpdate(onlyWithData = false) {
400
- const attributeUpdatesToSend = new Array();
401
- const attributeUpdates = {};
402
- Array.from(this.#outstandingAttributeUpdates.values()).forEach((entry) => {
403
- const {
404
- path: { nodeId, endpointId, clusterId }
405
- } = entry;
406
- const pathId = `${nodeId}-${endpointId}-${clusterId}`;
407
- attributeUpdates[pathId] = attributeUpdates[pathId] ?? [];
408
- attributeUpdates[pathId].push(entry);
409
- });
410
- this.#outstandingAttributeUpdates.clear();
411
- Object.values(attributeUpdates).forEach(
412
- (data) => attributeUpdatesToSend.push(
413
- ...data.sort(({ version: versionA }, { version: versionB }) => versionA - versionB)
414
- )
415
- );
416
- const eventUpdatesToSend = Array.from(this.#outstandingEventUpdates.values());
417
- this.#outstandingEventUpdates.clear();
418
- if (onlyWithData && attributeUpdatesToSend.length === 0 && eventUpdatesToSend.length === 0) {
419
- return;
420
- }
421
- this.#lastUpdateTimeMs = Time.nowMs();
422
- try {
423
- await this.#sendUpdateMessage(attributeUpdatesToSend, eventUpdatesToSend);
424
- this.#sendUpdateErrorCounter = 0;
425
- } catch (error) {
426
- if (this.isClosed) {
427
- return;
428
- }
429
- this.#sendUpdateErrorCounter++;
430
- logger.info(
431
- `Error sending subscription update message (error count=${this.#sendUpdateErrorCounter}):`,
432
- error instanceof MatterError && error.message || error
433
- );
434
- if (this.#sendUpdateErrorCounter <= 2) {
435
- const newAttributeUpdatesToSend = Array.from(this.#outstandingAttributeUpdates.values());
436
- this.#outstandingAttributeUpdates.clear();
437
- const newEventUpdatesToSend = Array.from(this.#outstandingEventUpdates.values());
438
- this.#outstandingEventUpdates.clear();
439
- [...attributeUpdatesToSend, ...newAttributeUpdatesToSend].forEach(
440
- (update) => this.#outstandingAttributeUpdates.set(attributePathToId(update.path), update)
441
- );
442
- [...eventUpdatesToSend, ...newEventUpdatesToSend].forEach(
443
- (update) => this.#outstandingEventUpdates.add(update)
444
- );
445
- } else {
446
- logger.info(
447
- `Sending update failed 3 times in a row, canceling subscription ${this.id} and let controller subscribe again.`
448
- );
449
- this.#sendNextUpdateImmediately = false;
450
- if (error instanceof NoResponseTimeoutError || error instanceof NetworkError || error instanceof NoChannelError) {
451
- this.isCanceledByPeer = true;
452
- await this.destroy();
453
- return;
454
- } else {
455
- throw error;
456
- }
457
- }
458
- }
459
- if (this.#sendNextUpdateImmediately) {
460
- logger.debug("Sending delayed update immediately after last one was sent.");
461
- this.#sendNextUpdateImmediately = false;
462
- await this.#sendUpdate(true);
463
- }
464
- }
465
- async #collectInitialEventReportPayloads(newEvents) {
466
- let eventsFiltered = false;
467
- const eventReportsPayload = new Array();
468
- for (const { path, event } of newEvents) {
469
- const { schema } = event;
470
- try {
471
- const matchingEvents = await this.#context.readEvent(path, event, this.criteria.eventFilters);
472
- if (matchingEvents.length === 0) {
473
- eventsFiltered = true;
474
- } else {
475
- matchingEvents.forEach(({ number, priority, epochTimestamp, payload }) => {
476
- eventReportsPayload.push({
477
- hasFabricSensitiveData: event.hasFabricSensitiveData,
478
- eventData: {
479
- path,
480
- eventNumber: number,
481
- priority,
482
- epochTimestamp,
483
- payload,
484
- schema
485
- }
486
- });
487
- });
488
- }
489
- } catch (error) {
490
- if (StatusResponseError.is(error, StatusCode.UnsupportedAccess)) {
491
- logger.warn(`Permission denied reading event ${this.#structure.resolveEventName(path)}`);
492
- } else {
493
- logger.warn(`Error reading event ${this.#structure.resolveEventName(path)}:`, error);
494
- }
495
- }
496
- }
497
- eventReportsPayload.sort((a, b) => {
498
- const eventNumberA = a.eventData?.eventNumber ?? 0;
499
- const eventNumberB = b.eventData?.eventNumber ?? 0;
500
- if (eventNumberA > eventNumberB) {
501
- return 1;
502
- } else if (eventNumberA < eventNumberB) {
503
- return -1;
504
- } else {
505
- return 0;
506
- }
507
- });
508
- return { eventReportsPayload, eventsFiltered };
509
- }
510
- /**
511
- * Returns an iterator that yields the initial subscription data to be sent to the controller.
512
- * The iterator will yield all attributes and events that match the subscription criteria.
513
- * A thrown exception will cancel the sending process immediately.
514
- * TODO: Streamline all this with the normal Read flow to also handle Concrete Path subscriptions with errors correctly
515
- */
516
- async *#iterateInitialSubscriptionData(attributesToSend, eventsToSend) {
517
- const dataVersionFilterMap = new Map(
518
- this.criteria.dataVersionFilters?.map(({ path, dataVersion }) => [clusterPathToId(path), dataVersion]) ?? []
519
- );
520
- const { newAttributes, attributeErrors } = attributesToSend;
521
- const { eventReportsPayload, eventsFiltered, eventErrors } = eventsToSend;
522
- logger.debug(
523
- `Initializes Subscription with ${newAttributes.length} attributes and ${eventReportsPayload.length} events.`
524
- );
525
- let attributesFilteredWithVersion = false;
526
- const attributesPerCluster = /* @__PURE__ */ new Map();
527
- for (const { path, attribute } of newAttributes) {
528
- const { endpointId } = path;
529
- const endpointAttributes = attributesPerCluster.get(endpointId) ?? new Array();
530
- endpointAttributes.push({ path, attribute });
531
- attributesPerCluster.set(endpointId, endpointAttributes);
532
- }
533
- let attributesCounter = 0;
534
- for (const endpointId of attributesPerCluster.keys()) {
535
- const endpointAttributes = attributesPerCluster.get(endpointId);
536
- attributesPerCluster.delete(endpointId);
537
- for (const { path, attribute, value, version } of this.#context.readEndpointAttributesForSubscription(
538
- endpointAttributes
539
- )) {
540
- if (value === void 0) continue;
541
- const { nodeId, endpointId: endpointId2, clusterId } = path;
542
- const versionFilterValue = endpointId2 !== void 0 && clusterId !== void 0 ? dataVersionFilterMap.get(clusterPathToId({ nodeId, endpointId: endpointId2, clusterId })) : void 0;
543
- if (versionFilterValue !== void 0 && versionFilterValue === version) {
544
- attributesFilteredWithVersion = true;
545
- continue;
546
- }
547
- attributesCounter++;
548
- yield {
549
- hasFabricSensitiveData: attribute.hasFabricSensitiveData,
550
- attributeData: {
551
- path,
552
- dataVersion: version,
553
- payload: value,
554
- schema: attribute.schema
555
- }
556
- };
557
- }
558
- }
559
- for (const attributeStatus of attributeErrors) {
560
- yield {
561
- hasFabricSensitiveData: false,
562
- attributeStatus
563
- };
564
- }
565
- if (attributesCounter === 0 && !attributesFilteredWithVersion && eventReportsPayload.length === 0 && !eventsFiltered) {
566
- throw new StatusResponseError(
567
- "Subscription failed because no attributes or events are matching the query",
568
- StatusCode.InvalidAction
569
- );
570
- }
571
- for (const eventReport of eventReportsPayload) {
572
- yield eventReport;
573
- }
574
- for (const eventStatus of eventErrors) {
575
- yield {
576
- hasFabricSensitiveData: false,
577
- eventStatus
578
- };
579
- }
580
- this.#lastUpdateTimeMs = Time.nowMs();
581
- }
582
- async sendInitialReport(messenger) {
583
- this.#updateTimer.stop();
584
- const { newAttributes, attributeErrors } = this.#registerNewAttributes();
585
- const { newEvents, eventErrors } = this.#registerNewEvents();
586
- const { eventReportsPayload, eventsFiltered } = await this.#collectInitialEventReportPayloads(newEvents);
587
- await messenger.sendDataReport(
588
- {
589
- suppressResponse: false,
590
- // we always need proper response for initial report
591
- subscriptionId: this.id,
592
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION
593
- },
594
- this.criteria.isFabricFiltered,
595
- this.#iterateInitialSubscriptionData(
596
- { newAttributes, attributeErrors },
597
- { eventReportsPayload, eventsFiltered, eventErrors }
598
- )
599
- );
600
- }
601
- attributeChangeListener(path, schema, version, value) {
602
- const changeResult = this.attributeChangeHandler(path, schema, version, value);
603
- if (MaybePromise.is(changeResult)) {
604
- const resolver = Promise.resolve(changeResult).catch((error) => logger.error(`Error handling attribute change:`, error)).finally(() => this.#attributeUpdatePromises.delete(resolver));
605
- this.#attributeUpdatePromises.add(resolver);
606
- }
607
- }
608
- attributeChangeHandler(path, schema, version, value) {
609
- const attributeListenerData = this.#attributeListeners.get(attributePathToId(path));
610
- if (attributeListenerData === void 0) return;
611
- const { attribute } = attributeListenerData;
612
- if (attribute instanceof FabricScopedAttributeServer) {
613
- const { value: value2 } = this.#context.readAttribute(path, attribute, true);
614
- this.#outstandingAttributeUpdates.set(attributePathToId(path), {
615
- attribute,
616
- path,
617
- schema,
618
- version,
619
- value: value2
620
- });
621
- this.#prepareDataUpdate();
622
- }
623
- this.#outstandingAttributeUpdates.set(attributePathToId(path), { attribute, path, schema, version, value });
624
- this.#prepareDataUpdate();
625
- }
626
- eventChangeListener(path, schema, newEvent) {
627
- const eventListenerData = this.#eventListeners.get(eventPathToId(path));
628
- if (eventListenerData === void 0) return;
629
- const { event } = eventListenerData;
630
- if (event instanceof FabricSensitiveEventServer) {
631
- const { payload } = newEvent;
632
- if (isObject(payload) && "fabricIndex" in payload && payload.fabricIndex !== this.session.fabric?.fabricIndex) {
633
- return;
634
- }
635
- }
636
- this.#outstandingEventUpdates.add({ event, path, schema, data: newEvent });
637
- if (path.isUrgent) {
638
- this.#prepareDataUpdate();
639
- }
640
- }
641
- async #flush() {
642
- this.#sendDelayTimer.stop();
643
- if (this.#outstandingAttributeUpdates.size > 0 || this.#outstandingEventUpdates.size > 0) {
644
- logger.debug(
645
- `Flushing subscription ${this.id} with ${this.#outstandingAttributeUpdates.size} attributes and ${this.#outstandingEventUpdates.size} events${this.isClosed ? " (for closing)" : ""}`
646
- );
647
- this.#triggerSendUpdate();
648
- if (this.#currentUpdatePromise) {
649
- await this.#currentUpdatePromise;
650
- }
651
- }
652
- }
653
- async destroy() {
654
- this.#sendUpdatesActivated = false;
655
- this.unregisterAttributeListeners(Array.from(this.#attributeListeners.keys()));
656
- this.unregisterEventListeners(Array.from(this.#eventListeners.keys()));
657
- if (this.#attributeUpdatePromises.size) {
658
- const resolvers = [...this.#attributeUpdatePromises.values()];
659
- this.#attributeUpdatePromises.clear();
660
- await MatterAggregateError.allSettled(resolvers, "Error receiving all outstanding attribute values").catch(
661
- (error) => logger.error(error)
662
- );
663
- }
664
- this.#updateTimer.stop();
665
- this.#sendDelayTimer.stop();
666
- await super.destroy();
667
- }
668
- /**
669
- * Closes the subscription and flushes all outstanding data updates if requested.
670
- */
671
- async close(graceful = false, cancelledByPeer = false) {
672
- if (this.isClosed) {
673
- return;
674
- }
675
- if (cancelledByPeer) {
676
- this.isCanceledByPeer = true;
677
- }
678
- await this.destroy();
679
- if (graceful) {
680
- await this.#flush();
681
- }
682
- if (this.#currentUpdatePromise) {
683
- await this.#currentUpdatePromise;
684
- }
685
- }
686
- /**
687
- * Iterates over all attributes and events that have changed since the last update and sends them to
688
- * the controller.
689
- * A thrown exception will cancel the sending process immediately.
690
- */
691
- async *#iterateDataUpdate(attributes, events) {
692
- for (const {
693
- path,
694
- schema,
695
- value: payload,
696
- version: dataVersion,
697
- attribute: { hasFabricSensitiveData }
698
- } of attributes) {
699
- yield {
700
- hasFabricSensitiveData,
701
- attributeData: { path, dataVersion, schema, payload }
702
- };
703
- }
704
- for (const {
705
- path,
706
- schema,
707
- event,
708
- data: { number: eventNumber, priority, epochTimestamp, payload }
709
- } of events) {
710
- yield {
711
- hasFabricSensitiveData: event.hasFabricSensitiveData,
712
- eventData: { path, eventNumber, priority, epochTimestamp, schema, payload }
713
- };
714
- }
715
- }
716
- async #sendUpdateMessage(attributes, events) {
717
- const exchange = this.#context.initiateExchange(this.#peerAddress, INTERACTION_PROTOCOL_ID);
718
- if (exchange === void 0) return;
719
- if (attributes.length) {
720
- logger.debug(
721
- `Subscription attribute changes for ID ${this.id}: ${attributes.map(
722
- ({ path, value, version }) => `${this.#structure.resolveAttributeName(path)}=${Diagnostic.json(value)} (${version})`
723
- ).join(", ")}`
724
- );
725
- }
726
- const messenger = new InteractionServerMessenger(exchange);
727
- try {
728
- if (attributes.length === 0 && events.length === 0) {
729
- await messenger.sendDataReport(
730
- {
731
- suppressResponse: true,
732
- // suppressResponse true for empty DataReports
733
- subscriptionId: this.id,
734
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION
735
- },
736
- this.criteria.isFabricFiltered,
737
- void 0,
738
- !this.isClosed
739
- // Do not wait for ack when closed
740
- );
741
- } else {
742
- await messenger.sendDataReport(
743
- {
744
- suppressResponse: false,
745
- // Non-empty data reports always need to send response
746
- subscriptionId: this.id,
747
- interactionModelRevision: Specification.INTERACTION_MODEL_REVISION
748
- },
749
- this.criteria.isFabricFiltered,
750
- this.#iterateDataUpdate(attributes, events),
751
- !this.isClosed
752
- // Do not wait for ack when closed
753
- );
754
- }
755
- } catch (error) {
756
- if (StatusResponseError.is(error, StatusCode.InvalidSubscription, StatusCode.Failure)) {
757
- logger.info(`Subscription ${this.id} cancelled by peer.`);
758
- this.isCanceledByPeer = true;
759
- await this.close(false);
760
- } else {
761
- StatusResponseError.accept(error);
762
- logger.info(`Subscription ${this.id} update failed:`, error);
763
- await this.close(false);
764
- }
765
- } finally {
766
- await messenger.close();
767
- }
768
- }
769
- }
770
- export {
771
- DEFAULT_RANDOMIZATION_WINDOW_S,
772
- INTERNAL_INTERVAL_PUBLISHER_LIMIT_S,
773
- MAX_INTERVAL_PUBLISHER_LIMIT_S,
774
- MIN_INTERVAL_S,
775
- ServerSubscription,
776
- ServerSubscriptionConfig
777
- };
778
- //# sourceMappingURL=ServerSubscription.js.map