@matter/protocol 0.12.4-alpha.0-20250217-b0bba5179 → 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 (219) 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 +18 -11
  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/InsecureSession.d.ts.map +1 -1
  74. package/dist/cjs/session/InsecureSession.js +1 -0
  75. package/dist/cjs/session/InsecureSession.js.map +1 -1
  76. package/dist/cjs/session/SecureSession.d.ts.map +1 -1
  77. package/dist/cjs/session/SecureSession.js +4 -0
  78. package/dist/cjs/session/SecureSession.js.map +1 -1
  79. package/dist/cjs/session/Session.d.ts +3 -1
  80. package/dist/cjs/session/Session.d.ts.map +1 -1
  81. package/dist/cjs/session/Session.js +5 -1
  82. package/dist/cjs/session/Session.js.map +1 -1
  83. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  84. package/dist/cjs/session/SessionManager.js +1 -0
  85. package/dist/cjs/session/SessionManager.js.map +1 -1
  86. package/dist/cjs/session/case/CaseClient.d.ts.map +1 -1
  87. package/dist/cjs/session/case/CaseClient.js +22 -15
  88. package/dist/cjs/session/case/CaseClient.js.map +1 -1
  89. package/dist/cjs/session/case/CaseServer.d.ts +1 -1
  90. package/dist/cjs/session/case/CaseServer.d.ts.map +1 -1
  91. package/dist/cjs/session/case/CaseServer.js +2 -4
  92. package/dist/cjs/session/case/CaseServer.js.map +1 -1
  93. package/dist/cjs/session/pase/PaseServer.d.ts +2 -3
  94. package/dist/cjs/session/pase/PaseServer.d.ts.map +1 -1
  95. package/dist/cjs/session/pase/PaseServer.js +12 -14
  96. package/dist/cjs/session/pase/PaseServer.js.map +1 -1
  97. package/dist/esm/action/client/ClientInteraction.d.ts +38 -0
  98. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -0
  99. package/dist/esm/action/client/ClientInteraction.js +71 -0
  100. package/dist/esm/action/client/ClientInteraction.js.map +6 -0
  101. package/dist/esm/action/client/index.d.ts +7 -0
  102. package/dist/esm/action/client/index.d.ts.map +1 -0
  103. package/dist/esm/action/client/index.js +7 -0
  104. package/dist/esm/action/client/index.js.map +6 -0
  105. package/dist/esm/action/index.d.ts +1 -0
  106. package/dist/esm/action/index.d.ts.map +1 -1
  107. package/dist/esm/action/index.js +1 -0
  108. package/dist/esm/action/index.js.map +1 -1
  109. package/dist/esm/interaction/DecodedDataReport.d.ts +15 -0
  110. package/dist/esm/interaction/DecodedDataReport.d.ts.map +1 -0
  111. package/dist/esm/interaction/DecodedDataReport.js +22 -0
  112. package/dist/esm/interaction/DecodedDataReport.js.map +6 -0
  113. package/dist/esm/interaction/InteractionClient.d.ts +13 -23
  114. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  115. package/dist/esm/interaction/InteractionClient.js +101 -134
  116. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  117. package/dist/esm/interaction/InteractionMessenger.d.ts +94 -1
  118. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  119. package/dist/esm/interaction/InteractionMessenger.js +56 -37
  120. package/dist/esm/interaction/InteractionMessenger.js.map +1 -1
  121. package/dist/esm/interaction/InteractionServer.d.ts +4 -2
  122. package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
  123. package/dist/esm/interaction/InteractionServer.js +12 -4
  124. package/dist/esm/interaction/InteractionServer.js.map +1 -1
  125. package/dist/esm/interaction/SubscriptionClient.d.ts +38 -0
  126. package/dist/esm/interaction/SubscriptionClient.d.ts.map +1 -0
  127. package/dist/esm/interaction/SubscriptionClient.js +78 -0
  128. package/dist/esm/interaction/SubscriptionClient.js.map +6 -0
  129. package/dist/esm/interaction/index.d.ts +1 -0
  130. package/dist/esm/interaction/index.d.ts.map +1 -1
  131. package/dist/esm/interaction/index.js +1 -0
  132. package/dist/esm/interaction/index.js.map +1 -1
  133. package/dist/esm/peer/ControllerCommissioner.d.ts +2 -2
  134. package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
  135. package/dist/esm/peer/ControllerCommissioner.js +6 -5
  136. package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
  137. package/dist/esm/peer/InteractionQueue.d.ts +11 -0
  138. package/dist/esm/peer/InteractionQueue.d.ts.map +1 -0
  139. package/dist/esm/peer/InteractionQueue.js +22 -0
  140. package/dist/esm/peer/InteractionQueue.js.map +6 -0
  141. package/dist/esm/peer/PeerAddressStore.d.ts +1 -1
  142. package/dist/esm/peer/PeerAddressStore.d.ts.map +1 -1
  143. package/dist/esm/peer/PeerSet.d.ts +16 -7
  144. package/dist/esm/peer/PeerSet.d.ts.map +1 -1
  145. package/dist/esm/peer/PeerSet.js +59 -62
  146. package/dist/esm/peer/PeerSet.js.map +1 -1
  147. package/dist/esm/peer/PhysicalDeviceProperties.d.ts +26 -0
  148. package/dist/esm/peer/PhysicalDeviceProperties.d.ts.map +1 -0
  149. package/dist/esm/peer/PhysicalDeviceProperties.js +54 -0
  150. package/dist/esm/peer/PhysicalDeviceProperties.js.map +6 -0
  151. package/dist/esm/peer/index.d.ts +1 -0
  152. package/dist/esm/peer/index.d.ts.map +1 -1
  153. package/dist/esm/peer/index.js +1 -0
  154. package/dist/esm/peer/index.js.map +1 -1
  155. package/dist/esm/protocol/ExchangeManager.d.ts +1 -0
  156. package/dist/esm/protocol/ExchangeManager.d.ts.map +1 -1
  157. package/dist/esm/protocol/ExchangeManager.js +18 -11
  158. package/dist/esm/protocol/ExchangeManager.js.map +1 -1
  159. package/dist/esm/protocol/ExchangeProvider.d.ts +13 -1
  160. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  161. package/dist/esm/protocol/ExchangeProvider.js +2 -0
  162. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  163. package/dist/esm/protocol/ProtocolHandler.d.ts +1 -1
  164. package/dist/esm/protocol/ProtocolHandler.d.ts.map +1 -1
  165. package/dist/esm/securechannel/SecureChannelProtocol.d.ts +1 -1
  166. package/dist/esm/securechannel/SecureChannelProtocol.d.ts.map +1 -1
  167. package/dist/esm/securechannel/SecureChannelProtocol.js +1 -3
  168. package/dist/esm/securechannel/SecureChannelProtocol.js.map +1 -1
  169. package/dist/esm/session/InsecureSession.d.ts.map +1 -1
  170. package/dist/esm/session/InsecureSession.js +1 -0
  171. package/dist/esm/session/InsecureSession.js.map +1 -1
  172. package/dist/esm/session/SecureSession.d.ts.map +1 -1
  173. package/dist/esm/session/SecureSession.js +4 -0
  174. package/dist/esm/session/SecureSession.js.map +1 -1
  175. package/dist/esm/session/Session.d.ts +3 -1
  176. package/dist/esm/session/Session.d.ts.map +1 -1
  177. package/dist/esm/session/Session.js +6 -2
  178. package/dist/esm/session/Session.js.map +1 -1
  179. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  180. package/dist/esm/session/SessionManager.js +1 -0
  181. package/dist/esm/session/SessionManager.js.map +1 -1
  182. package/dist/esm/session/case/CaseClient.d.ts.map +1 -1
  183. package/dist/esm/session/case/CaseClient.js +22 -15
  184. package/dist/esm/session/case/CaseClient.js.map +1 -1
  185. package/dist/esm/session/case/CaseServer.d.ts +1 -1
  186. package/dist/esm/session/case/CaseServer.d.ts.map +1 -1
  187. package/dist/esm/session/case/CaseServer.js +2 -4
  188. package/dist/esm/session/case/CaseServer.js.map +1 -1
  189. package/dist/esm/session/pase/PaseServer.d.ts +2 -3
  190. package/dist/esm/session/pase/PaseServer.d.ts.map +1 -1
  191. package/dist/esm/session/pase/PaseServer.js +12 -14
  192. package/dist/esm/session/pase/PaseServer.js.map +1 -1
  193. package/package.json +6 -6
  194. package/src/action/client/ClientInteraction.ts +110 -0
  195. package/src/action/client/index.ts +7 -0
  196. package/src/action/index.ts +1 -0
  197. package/src/interaction/DecodedDataReport.ts +29 -0
  198. package/src/interaction/InteractionClient.ts +112 -164
  199. package/src/interaction/InteractionMessenger.ts +63 -43
  200. package/src/interaction/InteractionServer.ts +18 -5
  201. package/src/interaction/SubscriptionClient.ts +107 -0
  202. package/src/interaction/index.ts +1 -0
  203. package/src/peer/ControllerCommissioner.ts +7 -6
  204. package/src/peer/InteractionQueue.ts +22 -0
  205. package/src/peer/PeerAddressStore.ts +1 -1
  206. package/src/peer/PeerSet.ts +69 -76
  207. package/src/peer/PhysicalDeviceProperties.ts +80 -0
  208. package/src/peer/index.ts +1 -0
  209. package/src/protocol/ExchangeManager.ts +19 -11
  210. package/src/protocol/ExchangeProvider.ts +14 -1
  211. package/src/protocol/ProtocolHandler.ts +1 -1
  212. package/src/securechannel/SecureChannelProtocol.ts +1 -3
  213. package/src/session/InsecureSession.ts +1 -0
  214. package/src/session/SecureSession.ts +4 -0
  215. package/src/session/Session.ts +7 -2
  216. package/src/session/SessionManager.ts +1 -0
  217. package/src/session/case/CaseClient.ts +18 -12
  218. package/src/session/case/CaseServer.ts +3 -5
  219. package/src/session/pase/PaseServer.ts +13 -15
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ export * from "./client/index.js";
7
8
  export * from "./errors.js";
8
9
  export * from "./Interactable.js";
9
10
  export * from "./protocols.js";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Matter.js Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { DataReport } from "#types";
8
+ import { DecodedAttributeReportValue, normalizeAndDecodeReadAttributeReport } from "./AttributeDataDecoder.js";
9
+ import { DecodedEventReportValue, normalizeAndDecodeReadEventReport } from "./EventDataDecoder.js";
10
+
11
+ export interface DecodedDataReport extends DataReport {
12
+ isNormalized: true;
13
+ attributeReports: DecodedAttributeReportValue<any>[];
14
+ eventReports: DecodedEventReportValue<any>[];
15
+ }
16
+
17
+ export function DecodedDataReport(report: DataReport): DecodedDataReport {
18
+ if ((report as DecodedDataReport).isNormalized) {
19
+ return report as DecodedDataReport;
20
+ }
21
+
22
+ return {
23
+ ...report,
24
+ isNormalized: true,
25
+ attributeReports:
26
+ report.attributeReports === undefined ? [] : normalizeAndDecodeReadAttributeReport(report.attributeReports),
27
+ eventReports: report.eventReports === undefined ? [] : normalizeAndDecodeReadEventReport(report.eventReports),
28
+ };
29
+ }
@@ -5,20 +5,20 @@
5
5
  */
6
6
 
7
7
  import {
8
+ Environment,
9
+ Environmental,
8
10
  ImplementationError,
9
- InternalError,
10
11
  Logger,
11
12
  MatterFlowError,
12
- MaybePromise,
13
13
  PromiseQueue,
14
- Time,
15
14
  Timer,
16
15
  UnexpectedDataError,
17
16
  isDeepEqual,
18
17
  } from "#general";
19
18
  import { Specification } from "#model";
20
- import { PeerAddress } from "#peer/PeerAddress.js";
19
+ import { PeerAddress, PeerAddressMap } from "#peer/PeerAddress.js";
21
20
  import { PeerDataStore } from "#peer/PeerAddressStore.js";
21
+ import { DiscoveryOptions, PeerSet } from "#peer/PeerSet.js";
22
22
  import {
23
23
  ArraySchema,
24
24
  Attribute,
@@ -30,7 +30,6 @@ import {
30
30
  Event,
31
31
  EventId,
32
32
  EventNumber,
33
- INTERACTION_PROTOCOL_ID,
34
33
  NodeId,
35
34
  RequestType,
36
35
  ResponseType,
@@ -47,16 +46,11 @@ import {
47
46
  resolveEventName,
48
47
  } from "#types";
49
48
  import { ExchangeProvider, ReconnectableExchangeProvider } from "../protocol/ExchangeProvider.js";
50
- import { MessageExchange } from "../protocol/MessageExchange.js";
51
- import { ProtocolHandler } from "../protocol/ProtocolHandler.js";
52
- import { DecodedAttributeReportValue, normalizeAndDecodeReadAttributeReport } from "./AttributeDataDecoder.js";
53
- import { DecodedEventData, DecodedEventReportValue, normalizeAndDecodeReadEventReport } from "./EventDataDecoder.js";
54
- import {
55
- DataReport,
56
- IncomingInteractionClientMessenger,
57
- InteractionClientMessenger,
58
- ReadRequest,
59
- } from "./InteractionMessenger.js";
49
+ import { DecodedAttributeReportValue } from "./AttributeDataDecoder.js";
50
+ import { DecodedDataReport } from "./DecodedDataReport.js";
51
+ import { DecodedEventData, DecodedEventReportValue } from "./EventDataDecoder.js";
52
+ import { DataReport, InteractionClientMessenger, ReadRequest } from "./InteractionMessenger.js";
53
+ import { RegisteredSubscription, SubscriptionClient } from "./SubscriptionClient.js";
60
54
 
61
55
  const logger = Logger.get("InteractionClient");
62
56
 
@@ -74,102 +68,98 @@ export interface AttributeStatus {
74
68
  status: StatusCode;
75
69
  }
76
70
 
77
- export class SubscriptionClient implements ProtocolHandler {
78
- private readonly subscriptionListeners = new Map<number, (dataReport: DataReport) => MaybePromise<void>>();
79
- private readonly subscriptionUpdateTimers = new Map<number, Timer>();
80
-
81
- constructor() {}
71
+ export class InteractionClientProvider {
72
+ readonly #peers: PeerSet;
73
+ readonly #clients = new PeerAddressMap<InteractionClient>();
82
74
 
83
- getId() {
84
- return INTERACTION_PROTOCOL_ID;
75
+ constructor(peers: PeerSet) {
76
+ this.#peers = peers;
77
+ this.#peers.deleted.on(peer => this.#onPeerLoss(peer.address));
78
+ this.#peers.disconnected.on(address => this.#onPeerLoss(address));
85
79
  }
86
80
 
87
- registerSubscriptionListener(subscriptionId: number, listener: (dataReport: DataReport) => MaybePromise<void>) {
88
- this.subscriptionListeners.set(subscriptionId, listener);
81
+ static [Environmental.create](env: Environment) {
82
+ const instance = new InteractionClientProvider(env.get(PeerSet));
83
+ env.set(InteractionClientProvider, instance);
84
+ return instance;
89
85
  }
90
86
 
91
- removeSubscriptionListener(subscriptionId: number) {
92
- this.subscriptionListeners.delete(subscriptionId);
87
+ get peers() {
88
+ return this.#peers;
93
89
  }
94
90
 
95
- registerSubscriptionUpdateTimer(subscriptionId: number, timer: Timer) {
96
- this.subscriptionUpdateTimers.set(subscriptionId, timer);
97
- }
91
+ async connect(
92
+ address: PeerAddress,
93
+ discoveryOptions: DiscoveryOptions,
94
+ allowUnknownPeer = false,
95
+ ): Promise<InteractionClient> {
96
+ await this.#peers.ensureConnection(address, discoveryOptions, allowUnknownPeer);
98
97
 
99
- removeSubscriptionUpdateTimer(subscriptionId: number) {
100
- this.subscriptionUpdateTimers.get(subscriptionId)?.stop();
101
- this.subscriptionUpdateTimers.delete(subscriptionId);
98
+ return this.getInteractionClient(address, discoveryOptions);
102
99
  }
103
100
 
104
- async onNewExchange(exchange: MessageExchange) {
105
- const messenger = new IncomingInteractionClientMessenger(exchange);
106
-
107
- let dataReport: DataReport;
108
- try {
109
- // TODO Adjust this to getting packages as callback when received to handle error cases and checks outside
110
- dataReport = await messenger.readDataReports([...this.subscriptionListeners.keys()]);
111
- } finally {
112
- messenger.close().catch(error => logger.info("Error closing client messenger", error));
101
+ async getInteractionClient(address: PeerAddress, discoveryOptions: DiscoveryOptions) {
102
+ let client = this.#clients.get(address);
103
+ if (client !== undefined) {
104
+ return client;
113
105
  }
114
- const subscriptionId = dataReport.subscriptionId as number; // this is checked in the messenger already because we hand over allowed list
115
106
 
116
- const listener = this.subscriptionListeners.get(subscriptionId);
117
- const timer = this.subscriptionUpdateTimers.get(subscriptionId);
107
+ const nodeStore = this.#peers.get(address)?.dataStore;
108
+ await nodeStore?.construction; // Lazy initialize the data if not already done
118
109
 
119
- if (timer !== undefined) {
120
- timer.stop().start(); // Restart timer because we received data
121
- }
110
+ const exchangeProvider = await this.#peers.exchangeProviderFor(address, discoveryOptions);
122
111
 
123
- await listener?.(dataReport);
112
+ client = new InteractionClient(
113
+ exchangeProvider,
114
+ this.#peers.subscriptionClient,
115
+ address,
116
+ this.#peers.interactionQueue,
117
+ nodeStore,
118
+ );
119
+ this.#clients.set(address, client);
120
+
121
+ return client;
124
122
  }
125
123
 
126
- async close() {
127
- this.subscriptionListeners.clear();
128
- this.subscriptionUpdateTimers.forEach(timer => timer.stop());
129
- this.subscriptionUpdateTimers.clear();
124
+ #onPeerLoss(address: PeerAddress) {
125
+ const client = this.#clients.get(address);
126
+ if (client !== undefined) {
127
+ client.close();
128
+ this.#clients.delete(address);
129
+ }
130
130
  }
131
131
  }
132
132
 
133
133
  export class InteractionClient {
134
+ readonly #exchangeProvider: ExchangeProvider;
134
135
  readonly #nodeStore?: PeerDataStore;
135
136
  readonly #ownSubscriptionIds = new Set<number>();
136
137
  readonly #subscriptionClient: SubscriptionClient;
137
138
  readonly #queue?: PromiseQueue;
138
139
 
139
140
  constructor(
140
- private readonly exchangeProvider: ExchangeProvider,
141
+ exchangeProvider: ExchangeProvider,
142
+ subscriptionClient: SubscriptionClient,
141
143
  readonly address: PeerAddress,
142
144
  queue?: PromiseQueue,
143
145
  nodeStore?: PeerDataStore,
144
146
  ) {
147
+ this.#exchangeProvider = exchangeProvider;
145
148
  this.#nodeStore = nodeStore;
149
+ this.#subscriptionClient = subscriptionClient;
146
150
  this.#queue = queue;
147
-
148
- const client = this.exchangeProvider.getProtocolHandler(INTERACTION_PROTOCOL_ID);
149
- if (client === undefined || !(client instanceof SubscriptionClient)) {
150
- throw new InternalError(
151
- `Subscription protocol handler ${INTERACTION_PROTOCOL_ID} missing or unexpected type.`,
152
- );
153
- }
154
- this.#subscriptionClient = client;
155
151
  }
156
152
 
157
153
  get channelUpdated() {
158
- if (this.exchangeProvider instanceof ReconnectableExchangeProvider) {
159
- return this.exchangeProvider.channelUpdated;
154
+ if (this.#exchangeProvider instanceof ReconnectableExchangeProvider) {
155
+ return this.#exchangeProvider.channelUpdated;
160
156
  }
161
157
  throw new ImplementationError("ExchangeProvider does not support channelUpdated");
162
158
  }
163
159
 
164
- registerSubscriptionListener(subscriptionId: number, listener: (dataReport: DataReport) => MaybePromise<void>) {
165
- this.#ownSubscriptionIds.add(subscriptionId);
166
- this.#subscriptionClient.registerSubscriptionListener(subscriptionId, listener);
167
- }
168
-
169
160
  removeSubscription(subscriptionId: number) {
170
161
  this.#ownSubscriptionIds.delete(subscriptionId);
171
- this.#subscriptionClient.removeSubscriptionListener(subscriptionId);
172
- this.#subscriptionClient.removeSubscriptionUpdateTimer(subscriptionId);
162
+ this.#subscriptionClient.delete(subscriptionId);
173
163
  }
174
164
 
175
165
  async getAllAttributes(
@@ -259,10 +249,7 @@ export class InteractionClient {
259
249
  isFabricFiltered?: boolean;
260
250
  executeQueued?: boolean;
261
251
  } = {},
262
- ): Promise<{
263
- attributeReports: DecodedAttributeReportValue<any>[];
264
- eventReports: DecodedEventReportValue<any>[];
265
- }> {
252
+ ): Promise<DecodedDataReport> {
266
253
  const {
267
254
  attributes: attributeRequests,
268
255
  dataVersionFilters,
@@ -302,10 +289,7 @@ export class InteractionClient {
302
289
  );
303
290
  }
304
291
 
305
- const result = await this.withMessenger<{
306
- attributeReports: DecodedAttributeReportValue<any>[];
307
- eventReports: DecodedEventReportValue<any>[];
308
- }>(async messenger => {
292
+ const result = await this.withMessenger(async messenger => {
309
293
  const { isFabricFiltered = true } = options;
310
294
  return await this.processReadRequest(messenger, {
311
295
  attributeRequests,
@@ -404,7 +388,7 @@ export class InteractionClient {
404
388
  private async processReadRequest(
405
389
  messenger: InteractionClientMessenger,
406
390
  request: ReadRequest,
407
- ): Promise<{ attributeReports: DecodedAttributeReportValue<any>[]; eventReports: DecodedEventReportValue<any>[] }> {
391
+ ): Promise<DecodedDataReport> {
408
392
  const { attributeRequests, eventRequests } = request;
409
393
  logger.debug(
410
394
  `Sending read request to ${messenger.getExchangeChannelName()} for attributes ${attributeRequests
@@ -415,10 +399,7 @@ export class InteractionClient {
415
399
  const response = await messenger.sendReadRequest(request);
416
400
 
417
401
  // Normalize and decode the response
418
- const normalizedResult = {
419
- attributeReports: normalizeAndDecodeReadAttributeReport(response.attributeReports ?? []),
420
- eventReports: normalizeAndDecodeReadEventReport(response.eventReports ?? []),
421
- };
402
+ const normalizedResult = DecodedDataReport(response);
422
403
  logger.debug(
423
404
  `Received read response with attributes ${normalizedResult.attributeReports
424
405
  .map(({ path, value }) => `${resolveAttributeName(path)} = ${Logger.toJSON(value)}`)
@@ -643,33 +624,39 @@ export class InteractionClient {
643
624
  return;
644
625
  }
645
626
 
646
- const data = normalizeAndDecodeReadAttributeReport(dataReport.attributeReports);
627
+ const { attributeReports } = DecodedDataReport(dataReport);
647
628
 
648
- if (data.length === 0) {
629
+ if (attributeReports.length === 0) {
649
630
  throw new MatterFlowError("Subscription result reporting undefined/no value not specified");
650
631
  }
651
- if (data.length > 1) {
632
+ if (attributeReports.length > 1) {
652
633
  throw new UnexpectedDataError("Unexpected response with more then one attribute");
653
634
  }
654
- const { value, version } = data[0];
635
+ const { value, version } = attributeReports[0];
655
636
  if (value === undefined)
656
637
  throw new MatterFlowError("Subscription result reporting undefined value not specified.");
657
638
 
658
- await this.#nodeStore?.persistAttributes([data[0]]);
639
+ await this.#nodeStore?.persistAttributes([attributeReports[0]]);
659
640
 
660
641
  listener?.(value, version);
661
642
  };
662
643
 
663
- this.registerSubscriptionListener(subscriptionId, subscriptionListener);
664
- if (updateTimeoutHandler !== undefined) {
665
- this.registerSubscriptionUpdateTimer(
644
+ await this.#registerSubscription(
645
+ {
646
+ id: subscriptionId,
666
647
  maximumPeerResponseTime,
667
- subscriptionId,
668
- maxInterval,
669
- updateTimeoutHandler,
670
- );
671
- }
672
- await subscriptionListener(report);
648
+ maxIntervalS: maxInterval,
649
+ onData: subscriptionListener,
650
+ onTimeout: updateTimeoutHandler,
651
+ },
652
+ report,
653
+ );
654
+ }
655
+
656
+ async #registerSubscription(subscription: RegisteredSubscription, initialReport: DataReport) {
657
+ this.#ownSubscriptionIds.add(subscription.id);
658
+ this.#subscriptionClient.add(subscription);
659
+ await subscription.onData(initialReport);
673
660
  }
674
661
 
675
662
  async subscribeEvent<T, E extends Event<T, any>>(options: {
@@ -736,30 +723,30 @@ export class InteractionClient {
736
723
  return;
737
724
  }
738
725
 
739
- const data = normalizeAndDecodeReadEventReport(dataReport.eventReports);
726
+ const { eventReports } = DecodedDataReport(dataReport);
740
727
 
741
- if (data.length === 0) {
728
+ if (eventReports.length === 0) {
742
729
  throw new MatterFlowError("Received empty subscription result value.");
743
730
  }
744
- if (data.length > 1) {
731
+ if (eventReports.length > 1) {
745
732
  throw new UnexpectedDataError("Unexpected response with more then one attribute.");
746
733
  }
747
- const { events } = data[0];
734
+ const { events } = eventReports[0];
748
735
  if (events === undefined)
749
736
  throw new MatterFlowError("Subscription result reporting undefined value not specified.");
750
737
 
751
738
  events.forEach(event => listener?.(event));
752
739
  };
753
- this.registerSubscriptionListener(subscriptionId, subscriptionListener);
754
- if (updateTimeoutHandler !== undefined) {
755
- this.registerSubscriptionUpdateTimer(
740
+ await this.#registerSubscription(
741
+ {
742
+ id: subscriptionId,
756
743
  maximumPeerResponseTime,
757
- subscriptionId,
758
- maxInterval,
759
- updateTimeoutHandler,
760
- );
761
- }
762
- subscriptionListener(report);
744
+ maxIntervalS: maxInterval,
745
+ onData: subscriptionListener,
746
+ onTimeout: updateTimeoutHandler,
747
+ },
748
+ report,
749
+ );
763
750
  }
764
751
 
765
752
  async subscribeAllAttributesAndEvents(options: {
@@ -957,39 +944,21 @@ export class InteractionClient {
957
944
  }
958
945
  }
959
946
  };
960
- this.registerSubscriptionListener(subscriptionId, async dataReport => {
961
- await subscriptionListener({
962
- ...dataReport,
963
- attributeReports:
964
- dataReport.attributeReports !== undefined
965
- ? normalizeAndDecodeReadAttributeReport(dataReport.attributeReports)
966
- : undefined,
967
- eventReports:
968
- dataReport.eventReports !== undefined
969
- ? normalizeAndDecodeReadEventReport(dataReport.eventReports)
970
- : undefined,
971
- });
972
- });
973
947
 
974
- if (updateTimeoutHandler !== undefined) {
975
- this.registerSubscriptionUpdateTimer(
948
+ const seedReport = DecodedDataReport(report);
949
+
950
+ await this.#registerSubscription(
951
+ {
952
+ id: subscriptionId,
976
953
  maximumPeerResponseTime,
977
- subscriptionId,
978
- maxInterval,
979
- updateTimeoutHandler,
980
- );
981
- }
954
+ maxIntervalS: maxInterval,
982
955
 
983
- const seedReport = {
984
- attributeReports:
985
- report.attributeReports !== undefined
986
- ? normalizeAndDecodeReadAttributeReport(report.attributeReports)
987
- : undefined,
988
- eventReports:
989
- report.eventReports !== undefined ? normalizeAndDecodeReadEventReport(report.eventReports) : undefined,
990
- subscriptionId,
991
- };
992
- await subscriptionListener(seedReport);
956
+ onData: dataReport => subscriptionListener(DecodedDataReport(dataReport)),
957
+
958
+ onTimeout: updateTimeoutHandler,
959
+ },
960
+ seedReport,
961
+ );
993
962
 
994
963
  if (dataVersionFilters !== undefined && dataVersionFilters.length > 0 && enrichCachedAttributeData) {
995
964
  this.#enrichCachedAttributeData(seedReport.attributeReports ?? [], dataVersionFilters);
@@ -1164,7 +1133,7 @@ export class InteractionClient {
1164
1133
  invoke: (messenger: InteractionClientMessenger) => Promise<T>,
1165
1134
  executeQueued = false,
1166
1135
  ): Promise<T> {
1167
- const messenger = await InteractionClientMessenger.create(this.exchangeProvider);
1136
+ const messenger = await InteractionClientMessenger.create(this.#exchangeProvider);
1168
1137
  let result: T;
1169
1138
  try {
1170
1139
  if (executeQueued) {
@@ -1181,27 +1150,6 @@ export class InteractionClient {
1181
1150
  return result;
1182
1151
  }
1183
1152
 
1184
- private registerSubscriptionUpdateTimer(
1185
- maximumPeerResponseTime: number,
1186
- subscriptionId: number,
1187
- maxIntervalS: number,
1188
- updateTimeoutHandler: Timer.Callback,
1189
- ) {
1190
- if (!this.#ownSubscriptionIds.has(subscriptionId)) {
1191
- throw new MatterFlowError(
1192
- `Cannot register update timer for subscription ${subscriptionId} because it is not owned by this client.`,
1193
- );
1194
- }
1195
- const maxIntervalMs = maxIntervalS * 1000 + maximumPeerResponseTime;
1196
-
1197
- const timer = Time.getTimer("Subscription timeout", maxIntervalMs, () => {
1198
- logger.info(`Subscription ${subscriptionId} timed out after ${maxIntervalMs}ms ...`);
1199
- this.removeSubscription(subscriptionId);
1200
- updateTimeoutHandler();
1201
- }).start();
1202
- this.#subscriptionClient.registerSubscriptionUpdateTimer(subscriptionId, timer);
1203
- }
1204
-
1205
1153
  removeAllSubscriptions() {
1206
1154
  for (const subscriptionId of this.#ownSubscriptionIds) {
1207
1155
  this.removeSubscription(subscriptionId);
@@ -1213,11 +1161,11 @@ export class InteractionClient {
1213
1161
  }
1214
1162
 
1215
1163
  get session() {
1216
- return this.exchangeProvider.session;
1164
+ return this.#exchangeProvider.session;
1217
1165
  }
1218
1166
 
1219
1167
  get channelType() {
1220
- return this.exchangeProvider.channelType;
1168
+ return this.#exchangeProvider.channelType;
1221
1169
  }
1222
1170
 
1223
1171
  /** Enrich cached data to get complete responses when data version filters were used. */
@@ -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) {