@matter/protocol 0.12.4-alpha.0-20250223-1e0341a1a → 0.12.4-alpha.0-20250224-e0964a795

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 (189) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +38 -0
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -0
  3. package/dist/cjs/action/client/ClientInteraction.js +91 -0
  4. package/dist/cjs/action/client/ClientInteraction.js.map +6 -0
  5. package/dist/cjs/action/client/index.d.ts +7 -0
  6. package/dist/cjs/action/client/index.d.ts.map +1 -0
  7. package/dist/cjs/action/client/index.js +24 -0
  8. package/dist/cjs/action/client/index.js.map +6 -0
  9. package/dist/cjs/action/index.d.ts +1 -0
  10. package/dist/cjs/action/index.d.ts.map +1 -1
  11. package/dist/cjs/action/index.js +1 -0
  12. package/dist/cjs/action/index.js.map +1 -1
  13. package/dist/cjs/interaction/DecodedDataReport.d.ts +15 -0
  14. package/dist/cjs/interaction/DecodedDataReport.d.ts.map +1 -0
  15. package/dist/cjs/interaction/DecodedDataReport.js +42 -0
  16. package/dist/cjs/interaction/DecodedDataReport.js.map +6 -0
  17. package/dist/cjs/interaction/InteractionClient.d.ts +13 -23
  18. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  19. package/dist/cjs/interaction/InteractionClient.js +99 -127
  20. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  21. package/dist/cjs/interaction/InteractionMessenger.d.ts +94 -1
  22. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  23. package/dist/cjs/interaction/InteractionMessenger.js +56 -37
  24. package/dist/cjs/interaction/InteractionMessenger.js.map +1 -1
  25. package/dist/cjs/interaction/InteractionServer.d.ts +4 -2
  26. package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
  27. package/dist/cjs/interaction/InteractionServer.js +12 -4
  28. package/dist/cjs/interaction/InteractionServer.js.map +1 -1
  29. package/dist/cjs/interaction/SubscriptionClient.d.ts +38 -0
  30. package/dist/cjs/interaction/SubscriptionClient.d.ts.map +1 -0
  31. package/dist/cjs/interaction/SubscriptionClient.js +98 -0
  32. package/dist/cjs/interaction/SubscriptionClient.js.map +6 -0
  33. package/dist/cjs/interaction/index.d.ts +1 -0
  34. package/dist/cjs/interaction/index.d.ts.map +1 -1
  35. package/dist/cjs/interaction/index.js +1 -0
  36. package/dist/cjs/interaction/index.js.map +1 -1
  37. package/dist/cjs/peer/ControllerCommissioner.d.ts +2 -2
  38. package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
  39. package/dist/cjs/peer/ControllerCommissioner.js +4 -3
  40. package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
  41. package/dist/cjs/peer/InteractionQueue.d.ts +11 -0
  42. package/dist/cjs/peer/InteractionQueue.d.ts.map +1 -0
  43. package/dist/cjs/peer/InteractionQueue.js +42 -0
  44. package/dist/cjs/peer/InteractionQueue.js.map +6 -0
  45. package/dist/cjs/peer/PeerAddressStore.d.ts +1 -1
  46. package/dist/cjs/peer/PeerAddressStore.d.ts.map +1 -1
  47. package/dist/cjs/peer/PeerSet.d.ts +16 -7
  48. package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
  49. package/dist/cjs/peer/PeerSet.js +58 -61
  50. package/dist/cjs/peer/PeerSet.js.map +1 -1
  51. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts +26 -0
  52. package/dist/cjs/peer/PhysicalDeviceProperties.d.ts.map +1 -0
  53. package/dist/cjs/peer/PhysicalDeviceProperties.js +74 -0
  54. package/dist/cjs/peer/PhysicalDeviceProperties.js.map +6 -0
  55. package/dist/cjs/peer/index.d.ts +1 -0
  56. package/dist/cjs/peer/index.d.ts.map +1 -1
  57. package/dist/cjs/peer/index.js +1 -0
  58. package/dist/cjs/peer/index.js.map +1 -1
  59. package/dist/cjs/protocol/ExchangeManager.d.ts +1 -0
  60. package/dist/cjs/protocol/ExchangeManager.d.ts.map +1 -1
  61. package/dist/cjs/protocol/ExchangeManager.js +6 -3
  62. package/dist/cjs/protocol/ExchangeManager.js.map +1 -1
  63. package/dist/cjs/protocol/ExchangeProvider.d.ts +13 -1
  64. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  65. package/dist/cjs/protocol/ExchangeProvider.js +2 -0
  66. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  67. package/dist/cjs/protocol/ProtocolHandler.d.ts +1 -1
  68. package/dist/cjs/protocol/ProtocolHandler.d.ts.map +1 -1
  69. package/dist/cjs/securechannel/SecureChannelProtocol.d.ts +1 -1
  70. package/dist/cjs/securechannel/SecureChannelProtocol.d.ts.map +1 -1
  71. package/dist/cjs/securechannel/SecureChannelProtocol.js +1 -3
  72. package/dist/cjs/securechannel/SecureChannelProtocol.js.map +1 -1
  73. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  74. package/dist/cjs/session/SessionManager.js +1 -0
  75. package/dist/cjs/session/SessionManager.js.map +1 -1
  76. package/dist/cjs/session/case/CaseServer.d.ts +1 -1
  77. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  78. package/dist/cjs/session/case/CaseServer.js +1 -3
  79. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  80. package/dist/cjs/session/pase/PaseServer.d.ts +2 -3
  81. package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
  82. package/dist/cjs/session/pase/PaseServer.js +12 -14
  83. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  84. package/dist/esm/action/client/ClientInteraction.d.ts +38 -0
  85. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -0
  86. package/dist/esm/action/client/ClientInteraction.js +71 -0
  87. package/dist/esm/action/client/ClientInteraction.js.map +6 -0
  88. package/dist/esm/action/client/index.d.ts +7 -0
  89. package/dist/esm/action/client/index.d.ts.map +1 -0
  90. package/dist/esm/action/client/index.js +7 -0
  91. package/dist/esm/action/client/index.js.map +6 -0
  92. package/dist/esm/action/index.d.ts +1 -0
  93. package/dist/esm/action/index.d.ts.map +1 -1
  94. package/dist/esm/action/index.js +1 -0
  95. package/dist/esm/action/index.js.map +1 -1
  96. package/dist/esm/interaction/DecodedDataReport.d.ts +15 -0
  97. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -0
  98. package/dist/esm/interaction/DecodedDataReport.js +22 -0
  99. package/dist/esm/interaction/DecodedDataReport.js.map +6 -0
  100. package/dist/esm/interaction/InteractionClient.d.ts +13 -23
  101. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  102. package/dist/esm/interaction/InteractionClient.js +101 -134
  103. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  104. package/dist/esm/interaction/InteractionMessenger.d.ts +94 -1
  105. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  106. package/dist/esm/interaction/InteractionMessenger.js +56 -37
  107. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  108. package/dist/esm/interaction/InteractionServer.d.ts +4 -2
  109. package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
  110. package/dist/esm/interaction/InteractionServer.js +12 -4
  111. package/dist/esm/interaction/InteractionServer.js.map +1 -1
  112. package/dist/esm/interaction/SubscriptionClient.d.ts +38 -0
  113. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -0
  114. package/dist/esm/interaction/SubscriptionClient.js +78 -0
  115. package/dist/esm/interaction/SubscriptionClient.js.map +6 -0
  116. package/dist/esm/interaction/index.d.ts +1 -0
  117. package/dist/esm/interaction/index.d.ts.map +1 -1
  118. package/dist/esm/interaction/index.js +1 -0
  119. package/dist/esm/interaction/index.js.map +1 -1
  120. package/dist/esm/peer/ControllerCommissioner.d.ts +2 -2
  121. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  122. package/dist/esm/peer/ControllerCommissioner.js +6 -5
  123. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  124. package/dist/esm/peer/InteractionQueue.d.ts +11 -0
  125. package/dist/esm/peer/InteractionQueue.d.ts.map +1 -0
  126. package/dist/esm/peer/InteractionQueue.js +22 -0
  127. package/dist/esm/peer/InteractionQueue.js.map +6 -0
  128. package/dist/esm/peer/PeerAddressStore.d.ts +1 -1
  129. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  130. package/dist/esm/peer/PeerSet.d.ts +16 -7
  131. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  132. package/dist/esm/peer/PeerSet.js +59 -62
  133. package/dist/esm/peer/PeerSet.js.map +1 -1
  134. package/dist/esm/peer/PhysicalDeviceProperties.d.ts +26 -0
  135. package/dist/esm/peer/PhysicalDeviceProperties.d.ts.map +1 -0
  136. package/dist/esm/peer/PhysicalDeviceProperties.js +54 -0
  137. package/dist/esm/peer/PhysicalDeviceProperties.js.map +6 -0
  138. package/dist/esm/peer/index.d.ts +1 -0
  139. package/dist/esm/peer/index.d.ts.map +1 -1
  140. package/dist/esm/peer/index.js +1 -0
  141. package/dist/esm/peer/index.js.map +1 -1
  142. package/dist/esm/protocol/ExchangeManager.d.ts +1 -0
  143. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  144. package/dist/esm/protocol/ExchangeManager.js +6 -3
  145. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  146. package/dist/esm/protocol/ExchangeProvider.d.ts +13 -1
  147. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  148. package/dist/esm/protocol/ExchangeProvider.js +2 -0
  149. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  150. package/dist/esm/protocol/ProtocolHandler.d.ts +1 -1
  151. package/dist/esm/protocol/ProtocolHandler.d.ts.map +1 -1
  152. package/dist/esm/securechannel/SecureChannelProtocol.d.ts +1 -1
  153. package/dist/esm/securechannel/SecureChannelProtocol.d.ts.map +1 -1
  154. package/dist/esm/securechannel/SecureChannelProtocol.js +1 -3
  155. package/dist/esm/securechannel/SecureChannelProtocol.js.map +1 -1
  156. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  157. package/dist/esm/session/SessionManager.js +1 -0
  158. package/dist/esm/session/SessionManager.js.map +1 -1
  159. package/dist/esm/session/case/CaseServer.d.ts +1 -1
  160. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  161. package/dist/esm/session/case/CaseServer.js +1 -3
  162. package/dist/esm/session/case/CaseServer.js.map +1 -1
  163. package/dist/esm/session/pase/PaseServer.d.ts +2 -3
  164. package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
  165. package/dist/esm/session/pase/PaseServer.js +12 -14
  166. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  167. package/package.json +6 -6
  168. package/src/action/client/ClientInteraction.ts +110 -0
  169. package/src/action/client/index.ts +7 -0
  170. package/src/action/index.ts +1 -0
  171. package/src/interaction/DecodedDataReport.ts +29 -0
  172. package/src/interaction/InteractionClient.ts +112 -164
  173. package/src/interaction/InteractionMessenger.ts +63 -43
  174. package/src/interaction/InteractionServer.ts +18 -5
  175. package/src/interaction/SubscriptionClient.ts +107 -0
  176. package/src/interaction/index.ts +1 -0
  177. package/src/peer/ControllerCommissioner.ts +7 -6
  178. package/src/peer/InteractionQueue.ts +22 -0
  179. package/src/peer/PeerAddressStore.ts +1 -1
  180. package/src/peer/PeerSet.ts +69 -76
  181. package/src/peer/PhysicalDeviceProperties.ts +80 -0
  182. package/src/peer/index.ts +1 -0
  183. package/src/protocol/ExchangeManager.ts +7 -3
  184. package/src/protocol/ExchangeProvider.ts +14 -1
  185. package/src/protocol/ProtocolHandler.ts +1 -1
  186. package/src/securechannel/SecureChannelProtocol.ts +1 -3
  187. package/src/session/SessionManager.ts +1 -0
  188. package/src/session/case/CaseServer.ts +2 -4
  189. package/src/session/pase/PaseServer.ts +13 -15
@@ -18,11 +18,9 @@ import {
18
18
  StatusCode,
19
19
  StatusResponseError,
20
20
  TlvAny,
21
- TlvAttributeReport,
22
21
  TlvDataReport,
23
22
  TlvDataReportForSend,
24
23
  TlvDataVersionFilter,
25
- TlvEventReport,
26
24
  TlvInvokeRequest,
27
25
  TlvInvokeResponse,
28
26
  TlvReadRequest,
@@ -693,15 +691,10 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
693
691
  return message;
694
692
  }
695
693
 
696
- // TODO: Adjust to use callbacks or events to push put received data to allow parallel processing
697
- async readDataReports(expectedSubscriptionIds?: number[]): Promise<DataReport> {
698
- let subscriptionId: number | undefined;
699
- const attributeValues: TypeFromSchema<typeof TlvAttributeReport>[] = [];
700
- const eventValues: TypeFromSchema<typeof TlvEventReport>[] = [];
694
+ async readAggregateDataReport(expectedSubscriptionIds?: number[]): Promise<DataReport> {
695
+ let result: DataReport | undefined;
701
696
 
702
- while (true) {
703
- const dataReportMessage = await this.waitFor("DataReport", MessageType.ReportData);
704
- const report = TlvDataReport.decode(dataReportMessage.payload);
697
+ for await (const report of this.readDataReports()) {
705
698
  if (expectedSubscriptionIds !== undefined) {
706
699
  if (report.subscriptionId === undefined || !expectedSubscriptionIds.includes(report.subscriptionId)) {
707
700
  await this.sendStatus(StatusCode.InvalidSubscription, {
@@ -718,56 +711,82 @@ export class IncomingInteractionClientMessenger extends InteractionMessenger {
718
711
  }
719
712
  }
720
713
 
721
- if (subscriptionId === undefined && report.subscriptionId !== undefined) {
722
- subscriptionId = report.subscriptionId;
723
- } else if (
724
- (subscriptionId !== undefined || report.subscriptionId !== undefined) &&
725
- report.subscriptionId !== subscriptionId
726
- ) {
714
+ if (result?.subscriptionId !== undefined && report.subscriptionId !== result.subscriptionId) {
727
715
  throw new UnexpectedDataError(`Invalid subscription ID ${report.subscriptionId} received`);
728
716
  }
729
717
 
730
- const logContext = {
731
- subId: report.subscriptionId,
732
- dataReportFlags: Diagnostic.asFlags({
733
- empty: !report.attributeReports?.length && !report.eventReports?.length,
734
- suppressResponse: report.suppressResponse,
735
- moreChunkedMessages: report.moreChunkedMessages,
736
- }),
737
- attr: report.attributeReports?.length,
738
- ev: report.eventReports?.length,
739
- };
740
-
741
- if (Array.isArray(report.attributeReports) && report.attributeReports.length > 0) {
742
- attributeValues.push(...report.attributeReports);
743
- }
744
- if (Array.isArray(report.eventReports) && report.eventReports.length > 0) {
745
- eventValues.push(...report.eventReports);
718
+ if (!result) {
719
+ result = report;
720
+ } else {
721
+ if (Array.isArray(report.attributeReports)) {
722
+ if (!result.attributeReports) {
723
+ result.attributeReports = report.attributeReports;
724
+ } else {
725
+ result.attributeReports.push(...report.attributeReports);
726
+ }
727
+ }
728
+ if (Array.isArray(report.eventReports)) {
729
+ if (!result.eventReports) {
730
+ result.eventReports = report.eventReports;
731
+ } else {
732
+ result.eventReports.push(...report.eventReports);
733
+ }
734
+ }
746
735
  }
736
+ }
737
+
738
+ if (result === undefined) {
739
+ // readDataReports should have thrown
740
+ throw new InternalError("No data reports loaded during read");
741
+ }
742
+
743
+ return result;
744
+ }
745
+
746
+ /**
747
+ * Read data reports as they come in on the wire.
748
+ *
749
+ * Data reports payloads are decoded but list attributes may be split across messages; these will require reassembly.
750
+ */
751
+ async *readDataReports() {
752
+ while (true) {
753
+ const dataReportMessage = await this.waitFor("DataReport", MessageType.ReportData);
754
+ const report = TlvDataReport.decode(dataReportMessage.payload);
755
+
756
+ yield report;
747
757
 
748
758
  if (report.moreChunkedMessages) {
749
759
  await this.sendStatus(StatusCode.Success, {
750
760
  multipleMessageInteraction: true,
751
- logContext,
761
+ logContext: this.#logContextOf(report),
752
762
  });
753
763
  } else if (!report.suppressResponse) {
754
- // We received the last message and need to send a final Success, but we do not need to wait for it and
764
+ // We received the last message and need to send a final success, but we do not need to wait for it and
755
765
  // also don't care if it fails
756
766
  this.sendStatus(StatusCode.Success, {
757
767
  multipleMessageInteraction: true,
758
- logContext,
759
- }).catch(error =>
760
- logger.info("Error while sending final Success after receiving all DataReport chunks", error),
761
- );
768
+ logContext: this.#logContextOf(report),
769
+ }).catch(error => logger.info("Error sending success after final data report chunk", error));
762
770
  }
763
771
 
764
772
  if (!report.moreChunkedMessages) {
765
- report.attributeReports = attributeValues;
766
- report.eventReports = eventValues;
767
- return report;
773
+ break;
768
774
  }
769
775
  }
770
776
  }
777
+
778
+ #logContextOf(report: DataReport) {
779
+ return {
780
+ subId: report.subscriptionId,
781
+ dataReportFlags: Diagnostic.asFlags({
782
+ empty: !report.attributeReports?.length && !report.eventReports?.length,
783
+ suppressResponse: report.suppressResponse,
784
+ moreChunkedMessages: report.moreChunkedMessages,
785
+ }),
786
+ attr: report.attributeReports?.length,
787
+ ev: report.eventReports?.length,
788
+ };
789
+ }
771
790
  }
772
791
 
773
792
  export class InteractionClientMessenger extends IncomingInteractionClientMessenger {
@@ -793,6 +812,7 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
793
812
  return await this.exchange.send(messageType, payload, options);
794
813
  } catch (error) {
795
814
  if (
815
+ this.exchangeProvider.supportsReconnect &&
796
816
  (error instanceof RetransmissionLimitReachedError || error instanceof ChannelNotConnectedError) &&
797
817
  !options?.multipleMessageInteraction
798
818
  ) {
@@ -815,7 +835,7 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
815
835
  async sendReadRequest(readRequest: ReadRequest) {
816
836
  await this.send(MessageType.ReadRequest, this.#encodeReadingRequest(TlvReadRequest, readRequest));
817
837
 
818
- return this.readDataReports();
838
+ return this.readAggregateDataReport();
819
839
  }
820
840
 
821
841
  #encodeReadingRequest<T extends TlvSchema<any>>(schema: T, request: TypeFromSchema<T>) {
@@ -876,7 +896,7 @@ export class InteractionClientMessenger extends IncomingInteractionClientMesseng
876
896
  const request = this.#encodeReadingRequest(TlvSubscribeRequest, subscribeRequest);
877
897
  await this.send(MessageType.SubscribeRequest, request);
878
898
 
879
- const report = await this.readDataReports();
899
+ const report = await this.readAggregateDataReport();
880
900
  const { subscriptionId } = report;
881
901
 
882
902
  if (subscriptionId === undefined) {
@@ -227,9 +227,11 @@ export interface InteractionContext {
227
227
  * Translates interactions from the Matter protocol to matter.js APIs.
228
228
  */
229
229
  export class InteractionServer implements ProtocolHandler, InteractionRecipient {
230
+ readonly id = INTERACTION_PROTOCOL_ID;
230
231
  #context: InteractionContext;
231
232
  #nextSubscriptionId = Crypto.getRandomUInt32();
232
233
  #isClosing = false;
234
+ #clientHandler?: ProtocolHandler;
233
235
  readonly #subscriptionConfig: ServerSubscriptionConfig;
234
236
  readonly #maxPathsPerInvoke;
235
237
 
@@ -244,10 +246,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
244
246
  });
245
247
  }
246
248
 
247
- getId() {
248
- return INTERACTION_PROTOCOL_ID;
249
- }
250
-
251
249
  protected get isClosing() {
252
250
  return this.#isClosing;
253
251
  }
@@ -256,13 +254,28 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
256
254
  return this.#maxPathsPerInvoke;
257
255
  }
258
256
 
259
- async onNewExchange(exchange: MessageExchange) {
257
+ async onNewExchange(exchange: MessageExchange, message: Message) {
260
258
  // Note - changes here must be copied to TransactionalInteractionServer as it does not call super() to avoid
261
259
  // the stack frame
262
260
  if (this.#isClosing) return; // We are closing, ignore anything newly incoming
261
+
262
+ // An incoming data report as the first message is not a valid server operation. We instead delegate to a
263
+ // client implementation if available
264
+ if (message.payloadHeader.messageType === MessageType.SubscribeRequest && this.#clientHandler) {
265
+ return this.#clientHandler.onNewExchange(exchange, message);
266
+ }
267
+
263
268
  await new InteractionServerMessenger(exchange).handleRequest(this);
264
269
  }
265
270
 
271
+ get clientHandler(): ProtocolHandler | undefined {
272
+ return this.#clientHandler;
273
+ }
274
+
275
+ set clientHandler(clientHandler: ProtocolHandler) {
276
+ this.#clientHandler = clientHandler;
277
+ }
278
+
266
279
  async #collectEventDataForRead(
267
280
  { eventRequests, eventFilters, isFabricFiltered }: ReadRequest,
268
281
  exchange: MessageExchange,
@@ -0,0 +1,107 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { MessageExchange } from "#protocol/MessageExchange.js";
8
+ import { ProtocolHandler } from "#protocol/ProtocolHandler.js";
9
+ import { Environment, Environmental, Logger, MaybePromise, Time, Timer } from "@matter/general";
10
+ import { INTERACTION_PROTOCOL_ID } from "@matter/types";
11
+ import { DataReport, IncomingInteractionClientMessenger } from "./InteractionMessenger.js";
12
+
13
+ const logger = Logger.get("SubscriptionClient");
14
+
15
+ export interface RegisteredSubscription {
16
+ id: number;
17
+ maximumPeerResponseTime: number;
18
+ maxIntervalS: number;
19
+ onData: (dataReport: DataReport) => MaybePromise<void>;
20
+ onTimeout?: () => void;
21
+ }
22
+
23
+ /**
24
+ * A simple protocol handler that handles exchanges starting with data reports.
25
+ *
26
+ * Incoming data reports must match to a subscription registered with {@link add} or the exchange is invalid.
27
+ */
28
+ export class SubscriptionClient implements ProtocolHandler {
29
+ readonly #listeners = new Map<number, (dataReport: DataReport) => MaybePromise<void>>();
30
+ readonly #timeouts = new Map<number, Timer>();
31
+
32
+ constructor() {}
33
+
34
+ static [Environmental.create](env: Environment) {
35
+ const client = new SubscriptionClient();
36
+ env.set(SubscriptionClient, client);
37
+ return client;
38
+ }
39
+
40
+ readonly id = INTERACTION_PROTOCOL_ID;
41
+
42
+ /**
43
+ * Register a subscription.
44
+ */
45
+ add(subscription: RegisteredSubscription) {
46
+ const { id, onData, onTimeout } = subscription;
47
+
48
+ this.#listeners.set(id, onData);
49
+ if (onTimeout) {
50
+ let timer = this.#timeouts.get(id);
51
+ if (timer !== undefined) {
52
+ timer.stop();
53
+ this.#timeouts.delete(id);
54
+ }
55
+
56
+ const maxIntervalMs = subscription.maxIntervalS * 1000 + subscription.maximumPeerResponseTime;
57
+
58
+ timer = Time.getTimer("Subscription timeout", maxIntervalMs, () => {
59
+ logger.info(`Subscription ${id} timed out after ${maxIntervalMs}ms`);
60
+ this.delete(id);
61
+ onTimeout();
62
+ }).start();
63
+
64
+ this.#timeouts.set(id, timer);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Unregister a subscription.
70
+ */
71
+ delete(id: number) {
72
+ this.#listeners.delete(id);
73
+ const timer = this.#timeouts.get(id);
74
+ if (timer !== undefined) {
75
+ timer.stop();
76
+ this.#timeouts.delete(id);
77
+ }
78
+ }
79
+
80
+ async onNewExchange(exchange: MessageExchange) {
81
+ const messenger = new IncomingInteractionClientMessenger(exchange);
82
+
83
+ let dataReport: DataReport;
84
+ try {
85
+ // TODO Adjust this to getting packages as callback when received to handle error cases and checks outside
86
+ dataReport = await messenger.readAggregateDataReport([...this.#listeners.keys()]);
87
+ } finally {
88
+ messenger.close().catch(error => logger.info("Error closing client messenger", error));
89
+ }
90
+ const subscriptionId = dataReport.subscriptionId as number; // this is checked in the messenger already because we hand over allowed list
91
+
92
+ const listener = this.#listeners.get(subscriptionId);
93
+ const timer = this.#timeouts.get(subscriptionId);
94
+
95
+ if (timer !== undefined) {
96
+ timer.stop().start(); // Restart timer because we received data
97
+ }
98
+
99
+ await listener?.(dataReport);
100
+ }
101
+
102
+ async close() {
103
+ this.#listeners.clear();
104
+ this.#timeouts.forEach(timer => timer.stop());
105
+ this.#timeouts.clear();
106
+ }
107
+ }
@@ -14,3 +14,4 @@ export * from "./InteractionMessenger.js";
14
14
  export * from "./InteractionServer.js";
15
15
  export * from "./ServerSubscription.js";
16
16
  export * from "./Subscription.js";
17
+ export * from "./SubscriptionClient.js";
@@ -27,11 +27,11 @@ import { ChannelStatusResponseError } from "#securechannel/index.js";
27
27
  import { PaseClient } from "#session/index.js";
28
28
  import { SessionManager } from "#session/SessionManager.js";
29
29
  import { DiscoveryCapabilitiesBitmap, NodeId, SECURE_CHANNEL_PROTOCOL_ID, TypeFromPartialBitSchema } from "#types";
30
- import { InteractionClient } from "../interaction/InteractionClient.js";
30
+ import { InteractionClient, InteractionClientProvider } from "../interaction/InteractionClient.js";
31
31
  import { ExchangeManager, MessageChannel } from "../protocol/ExchangeManager.js";
32
32
  import { DedicatedChannelExchangeProvider } from "../protocol/ExchangeProvider.js";
33
33
  import { PeerAddress } from "./PeerAddress.js";
34
- import { NodeDiscoveryType, PeerSet } from "./PeerSet.js";
34
+ import { NodeDiscoveryType } from "./PeerSet.js";
35
35
 
36
36
  const logger = Logger.get("PeerCommissioner");
37
37
 
@@ -108,7 +108,7 @@ export interface DiscoveryAndCommissioningOptions extends CommissioningOptions {
108
108
  * Interfaces {@link ControllerCommissioner} with other components.
109
109
  */
110
110
  export interface ControllerCommissionerContext {
111
- peers: PeerSet;
111
+ clients: InteractionClientProvider;
112
112
  scanners: ScannerSet;
113
113
  netInterfaces: NetInterfaceSet;
114
114
  sessions: SessionManager;
@@ -130,7 +130,7 @@ export class ControllerCommissioner {
130
130
 
131
131
  static [Environmental.create](env: Environment) {
132
132
  const instance = new ControllerCommissioner({
133
- peers: env.get(PeerSet),
133
+ clients: env.get(InteractionClientProvider),
134
134
  scanners: env.get(ScannerSet),
135
135
  netInterfaces: env.get(NetInterfaceSet),
136
136
  sessions: env.get(SessionManager),
@@ -385,6 +385,7 @@ export class ControllerCommissioner {
385
385
  // Use the created secure session to do the commissioning
386
386
  new InteractionClient(
387
387
  new DedicatedChannelExchangeProvider(this.#context.exchanges, paseSecureMessageChannel),
388
+ this.#context.clients.peers.subscriptionClient,
388
389
  address,
389
390
  ),
390
391
  this.#context.ca,
@@ -406,7 +407,7 @@ export class ControllerCommissioner {
406
407
  }
407
408
 
408
409
  // Look for the device broadcast over MDNS and do CASE pairing
409
- return await this.#context.peers.connect(
410
+ return await this.#context.clients.connect(
410
411
  address,
411
412
  {
412
413
  discoveryType: NodeDiscoveryType.TimedDiscovery,
@@ -422,7 +423,7 @@ export class ControllerCommissioner {
422
423
  await commissioningManager.executeCommissioning();
423
424
  } catch (error) {
424
425
  // We might have added data for an operational address that we need to cleanup
425
- await this.#context.peers.delete(address);
426
+ await this.#context.clients.peers.delete(address);
426
427
  throw error;
427
428
  } finally {
428
429
  if (!paseSecureMessageChannel.closed) {
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Environment, Environmental, PromiseQueue } from "@matter/general";
8
+
9
+ const CONCURRENT_QUEUED_INTERACTIONS = 4;
10
+ const INTERACTION_QUEUE_DELAY_MS = 100;
11
+
12
+ export class InteractionQueue extends PromiseQueue {
13
+ constructor() {
14
+ super(CONCURRENT_QUEUED_INTERACTIONS, INTERACTION_QUEUE_DELAY_MS);
15
+ }
16
+
17
+ static [Environmental.create](env: Environment) {
18
+ const instance = new InteractionQueue();
19
+ env.set(InteractionQueue, instance);
20
+ return instance;
21
+ }
22
+ }
@@ -18,7 +18,7 @@ export abstract class PeerAddressStore {
18
18
  abstract loadPeers(): MaybePromise<Iterable<OperationalPeer>>;
19
19
  abstract updatePeer(peer: OperationalPeer): MaybePromise<void>;
20
20
  abstract deletePeer(address: PeerAddress): MaybePromise<void>;
21
- abstract createNodeStore(address: PeerAddress): Promise<PeerDataStore>;
21
+ abstract createNodeStore(address: PeerAddress): MaybePromise<PeerDataStore | undefined>;
22
22
  }
23
23
 
24
24
  export abstract class PeerDataStore {