@matter/protocol 0.13.1-alpha.0-20250509-28e1567e1 → 0.13.1-alpha.0-20250515-a4c61c546

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