@matter/protocol 0.17.3 → 0.17.4-alpha.0-20260623-5647634fc

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 (55) hide show
  1. package/dist/cjs/action/client/ClientInteraction.d.ts +2 -0
  2. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.js +6 -4
  4. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  5. package/dist/cjs/peer/Peer.d.ts +10 -1
  6. package/dist/cjs/peer/Peer.d.ts.map +1 -1
  7. package/dist/cjs/peer/Peer.js +15 -5
  8. package/dist/cjs/peer/Peer.js.map +1 -1
  9. package/dist/cjs/peer/PeerAddressMonitor.d.ts +19 -1
  10. package/dist/cjs/peer/PeerAddressMonitor.d.ts.map +1 -1
  11. package/dist/cjs/peer/PeerAddressMonitor.js +99 -65
  12. package/dist/cjs/peer/PeerAddressMonitor.js.map +1 -1
  13. package/dist/cjs/peer/PeerExchangeProvider.d.ts +5 -1
  14. package/dist/cjs/peer/PeerExchangeProvider.d.ts.map +1 -1
  15. package/dist/cjs/peer/PeerExchangeProvider.js +3 -0
  16. package/dist/cjs/peer/PeerExchangeProvider.js.map +1 -1
  17. package/dist/cjs/peer/PeerTimingParameters.d.ts +2 -2
  18. package/dist/cjs/peer/PeerTimingParameters.d.ts.map +1 -1
  19. package/dist/cjs/peer/PeerTimingParameters.js +2 -2
  20. package/dist/cjs/peer/PeerTimingParameters.js.map +1 -1
  21. package/dist/cjs/protocol/ExchangeProvider.d.ts +11 -0
  22. package/dist/cjs/protocol/ExchangeProvider.d.ts.map +1 -1
  23. package/dist/cjs/protocol/ExchangeProvider.js +8 -0
  24. package/dist/cjs/protocol/ExchangeProvider.js.map +1 -1
  25. package/dist/esm/action/client/ClientInteraction.d.ts +2 -0
  26. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  27. package/dist/esm/action/client/ClientInteraction.js +6 -4
  28. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  29. package/dist/esm/peer/Peer.d.ts +10 -1
  30. package/dist/esm/peer/Peer.d.ts.map +1 -1
  31. package/dist/esm/peer/Peer.js +15 -5
  32. package/dist/esm/peer/Peer.js.map +1 -1
  33. package/dist/esm/peer/PeerAddressMonitor.d.ts +19 -1
  34. package/dist/esm/peer/PeerAddressMonitor.d.ts.map +1 -1
  35. package/dist/esm/peer/PeerAddressMonitor.js +100 -65
  36. package/dist/esm/peer/PeerAddressMonitor.js.map +1 -1
  37. package/dist/esm/peer/PeerExchangeProvider.d.ts +5 -1
  38. package/dist/esm/peer/PeerExchangeProvider.d.ts.map +1 -1
  39. package/dist/esm/peer/PeerExchangeProvider.js +3 -0
  40. package/dist/esm/peer/PeerExchangeProvider.js.map +1 -1
  41. package/dist/esm/peer/PeerTimingParameters.d.ts +2 -2
  42. package/dist/esm/peer/PeerTimingParameters.d.ts.map +1 -1
  43. package/dist/esm/peer/PeerTimingParameters.js +3 -3
  44. package/dist/esm/peer/PeerTimingParameters.js.map +1 -1
  45. package/dist/esm/protocol/ExchangeProvider.d.ts +11 -0
  46. package/dist/esm/protocol/ExchangeProvider.d.ts.map +1 -1
  47. package/dist/esm/protocol/ExchangeProvider.js +8 -0
  48. package/dist/esm/protocol/ExchangeProvider.js.map +1 -1
  49. package/package.json +5 -5
  50. package/src/action/client/ClientInteraction.ts +7 -4
  51. package/src/peer/Peer.ts +20 -8
  52. package/src/peer/PeerAddressMonitor.ts +127 -92
  53. package/src/peer/PeerExchangeProvider.ts +5 -1
  54. package/src/peer/PeerTimingParameters.ts +5 -5
  55. package/src/protocol/ExchangeProvider.ts +12 -0
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import {
7
+ Abort,
7
8
  ChannelType,
8
9
  Diagnostic,
9
10
  isIpNetworkChannel,
@@ -14,6 +15,7 @@ import {
14
15
  } from "@matter/general";
15
16
  import { PeerUnresponsiveError } from "./PeerCommunicationError.js";
16
17
  const logger = Logger.get("PeerAddressMonitor");
18
+ const PROBE_MAX_RETRANSMISSIONS = 2;
17
19
  class PeerAddressMonitor {
18
20
  #peer;
19
21
  #abort;
@@ -22,9 +24,8 @@ class PeerAddressMonitor {
22
24
  #cooldownMax;
23
25
  #trackWork;
24
26
  #probing;
25
- // Fibonacci backoff state — resets when probed IP changes
27
+ // Fibonacci backoff state — grows on each successful probe, resets only on a real reachability scare
26
28
  #lastProbeAt;
27
- #lastProbedIp;
28
29
  #fibPrev = 0;
29
30
  #fibCurr = 0;
30
31
  constructor(peer, stabilizationDelay, probeCooldown, abort, trackWork) {
@@ -34,13 +35,7 @@ class PeerAddressMonitor {
34
35
  this.#cooldownMax = probeCooldown.maximum;
35
36
  this.#trackWork = trackWork;
36
37
  this.#timer = Time.getTimer("address check stabilization", stabilizationDelay, () => {
37
- if (!this.#probing) {
38
- this.#probing = this.#check().finally(() => {
39
- this.#probing = void 0;
40
- this.schedule();
41
- });
42
- this.#trackWork(this.#probing);
43
- }
38
+ this.#trackWork(this.verifyReachability({ reason: "address-change", abort: this.#abort }));
44
39
  });
45
40
  }
46
41
  /**
@@ -59,79 +54,119 @@ class PeerAddressMonitor {
59
54
  stop() {
60
55
  this.#timer.stop();
61
56
  }
62
- async #check() {
57
+ /**
58
+ * Tear down: stop the timer and await any in-flight run so teardown does not race a probe. The
59
+ * caller is expected to have aborted the peer first, so the in-flight run unwinds promptly.
60
+ */
61
+ async close() {
62
+ this.#timer.stop();
63
+ await this.#probing;
64
+ }
65
+ /**
66
+ * Verify the peer is reachable, driving recovery. Coalesces concurrent triggers into one run.
67
+ *
68
+ * An `address-change` run probes the current address then walks discovered alternates (migrating in
69
+ * place on success); a `session-suspect` run only probes the current address and never walks alternates.
70
+ * Returns `true` if the session is usable afterward (current address answered, or an alternate
71
+ * answered and the session migrated); `false` if no address answered and the session was closed.
72
+ */
73
+ async verifyReachability(options) {
74
+ if (this.#probing === void 0) {
75
+ this.#probing = this.#run(options.reason).finally(() => {
76
+ this.#probing = void 0;
77
+ if (options.reason === "address-change") {
78
+ this.schedule();
79
+ }
80
+ });
81
+ }
82
+ const run = this.#probing;
83
+ return options.abort !== void 0 && options.abort !== this.#abort ? await Abort.race(options.abort, run) ?? false : run;
84
+ }
85
+ async #run(reason) {
86
+ const abort = this.#abort;
63
87
  const session = this.#peer.newestSession();
64
88
  const interaction = this.#peer.interaction;
65
89
  if (!session || !interaction) {
66
- return;
90
+ return false;
67
91
  }
68
92
  const channel = session.channel.transportChannel;
69
- if (!isIpNetworkChannel(channel)) {
70
- return;
71
- }
72
- if (channel.type === ChannelType.TCP) {
73
- return;
93
+ if (!isIpNetworkChannel(channel) || channel.type === ChannelType.TCP) {
94
+ return true;
74
95
  }
75
96
  const currentAddress = channel.networkAddress;
76
- const currentIp = currentAddress.ip;
77
97
  const discoveredAddresses = this.#peer.service.addresses;
78
- if (!discoveredAddresses.size) {
79
- return;
80
- }
81
- if (discoveredAddresses.has(currentAddress)) {
82
- return;
83
- }
84
- const via = session.via;
85
- if (currentIp !== this.#lastProbedIp) {
98
+ if (reason === "session-suspect") {
86
99
  this.#resetBackoff();
87
100
  }
88
- const lastKnownGood = Timestamp(Math.max(this.#lastProbeAt ?? 0, session.activeTimestamp));
89
- const cooldown = this.#currentCooldown;
90
- if (lastKnownGood > 0 && Timestamp.delta(lastKnownGood) < cooldown) {
91
- return;
101
+ if (reason === "address-change") {
102
+ const stillDiscovered = [...discoveredAddresses].some((addr) => ServerAddress.isEqual(addr, currentAddress));
103
+ if (!discoveredAddresses.size || stillDiscovered) {
104
+ return true;
105
+ }
106
+ const lastKnownGood = Timestamp(Math.max(this.#lastProbeAt ?? 0, session.activeTimestamp));
107
+ if (lastKnownGood > 0 && Timestamp.delta(lastKnownGood) < this.#currentCooldown) {
108
+ return true;
109
+ }
110
+ logger.info(
111
+ session.via,
112
+ "Session address",
113
+ Diagnostic.strong(ServerAddress.urlFor(currentAddress)),
114
+ "no longer in mDNS results, probing"
115
+ );
116
+ this.#lastProbeAt = Time.nowMs;
92
117
  }
93
- logger.info(
94
- via,
95
- "Session address",
96
- Diagnostic.strong(ServerAddress.urlFor(currentAddress)),
97
- "no longer in mDNS results, probing"
98
- );
99
- this.#lastProbeAt = Time.nowMs;
100
- this.#lastProbedIp = currentIp;
101
118
  const network = this.#peer.network;
102
119
  const probeNetwork = network.probeAddress ?? network;
103
- if (await interaction.probe({ network: probeNetwork.id, abort: this.#abort, suppressPeerLoss: true })) {
104
- this.#advanceBackoff();
105
- return;
106
- }
107
- for (const ipAddress of discoveredAddresses) {
108
- if (this.#abort.aborted) {
109
- return;
120
+ if (await interaction.probe({
121
+ network: probeNetwork.id,
122
+ abort,
123
+ suppressPeerLoss: true,
124
+ maxRetransmissions: PROBE_MAX_RETRANSMISSIONS
125
+ })) {
126
+ if (reason === "address-change") {
127
+ this.#advanceBackoff();
110
128
  }
111
- const address = { ...ipAddress, type: "udp" };
112
- if (await interaction.probe({
113
- network: probeNetwork.id,
114
- abort: this.#abort,
115
- addressOverride: address,
116
- suppressPeerLoss: true
117
- })) {
118
- logger.info(
119
- via,
120
- "Discovered address reachable, migrating session to",
121
- Diagnostic.strong(ServerAddress.urlFor(address))
122
- );
123
- session.channel.networkAddress = address;
124
- interaction.subscriptions.closeForPeer(this.#peer.address);
125
- this.#resetBackoff();
126
- return;
129
+ return true;
130
+ }
131
+ if (reason === "address-change") {
132
+ for (const ipAddress of discoveredAddresses) {
133
+ if (abort.aborted) {
134
+ return false;
135
+ }
136
+ if (ServerAddress.isEqual(ipAddress, currentAddress)) {
137
+ continue;
138
+ }
139
+ const address = { ...ipAddress, type: "udp" };
140
+ if (await interaction.probe({
141
+ network: probeNetwork.id,
142
+ abort,
143
+ addressOverride: address,
144
+ suppressPeerLoss: true,
145
+ maxRetransmissions: PROBE_MAX_RETRANSMISSIONS
146
+ })) {
147
+ logger.info(
148
+ session.via,
149
+ "Discovered address reachable, migrating session to",
150
+ Diagnostic.strong(ServerAddress.urlFor(address))
151
+ );
152
+ session.channel.networkAddress = address;
153
+ interaction.subscriptions.closeForPeer(this.#peer.address);
154
+ this.#resetBackoff();
155
+ return true;
156
+ }
127
157
  }
158
+ this.#resetBackoff();
128
159
  }
129
- this.#resetBackoff();
130
- if (this.#abort.aborted) {
131
- return;
160
+ if (abort.aborted) {
161
+ return false;
162
+ }
163
+ logger.info(session.via, "All probes failed, closing session");
164
+ try {
165
+ await session.handlePeerLoss({ cause: new PeerUnresponsiveError() });
166
+ } catch (error) {
167
+ logger.warn(session.via, "Error closing unreachable session", error);
132
168
  }
133
- logger.info(via, "All probes failed, closing session");
134
- await session.handlePeerLoss({ cause: new PeerUnresponsiveError() });
169
+ return false;
135
170
  }
136
171
  /** Current cooldown duration based on Fibonacci position. */
137
172
  get #currentCooldown() {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/peer/PeerAddressMonitor.ts"],
4
- "mappings": "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",
4
+ "mappings": "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",
5
5
  "names": []
6
6
  }
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
  import { PeerAddress } from "#peer/PeerAddress.js";
7
- import { ExchangeProvider, NewExchangeOptions } from "#protocol/ExchangeProvider.js";
7
+ import { ExchangeProvider, NewExchangeOptions, type ReachabilityReason } from "#protocol/ExchangeProvider.js";
8
8
  import type { MessageExchange } from "#protocol/MessageExchange.js";
9
9
  import { ChannelType, Duration } from "@matter/general";
10
10
  import { Peer } from "./Peer.js";
@@ -23,6 +23,10 @@ export declare class PeerExchangeProvider extends ExchangeProvider {
23
23
  */
24
24
  get channelType(): ChannelType;
25
25
  connect(options?: NewExchangeOptions): Promise<void>;
26
+ verifyReachability(options: {
27
+ reason: ReachabilityReason;
28
+ abort?: AbortSignal;
29
+ }): Promise<boolean>;
26
30
  initiateExchange(options?: NewExchangeOptions): Promise<MessageExchange>;
27
31
  maximumPeerResponseTime(expectedProcessingTime?: Duration, includeMaximumSendingTime?: boolean): Duration;
28
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PeerExchangeProvider.d.ts","sourceRoot":"","sources":["../../../src/peer/PeerExchangeProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;;IAItD,YAAY,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAKtD;IAED,IAAI,iBAAiB,WAEpB;IAED,IAAI,WAAW,gBAEd;IAED;;;OAGG;IACH,IAAI,WAAW,gBAMd;IAEc,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CASlE;IAEc,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwEtF;IAEQ,uBAAuB,CAAC,sBAAsB,CAAC,EAAE,QAAQ,EAAE,yBAAyB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAQjH;CACJ"}
1
+ {"version":3,"file":"PeerExchangeProvider.d.ts","sourceRoot":"","sources":["../../../src/peer/PeerExchangeProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAC9G,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAiB,MAAM,iBAAiB,CAAC;AAEvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,gBAAgB;;IAItD,YAAY,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAKtD;IAED,IAAI,iBAAiB,WAEpB;IAED,IAAI,WAAW,gBAEd;IAED;;;OAGG;IACH,IAAI,WAAW,gBAMd;IAEc,OAAO,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CASlE;IAEQ,kBAAkB,CAAC,OAAO,EAAE;QAAE,MAAM,EAAE,kBAAkB,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1G;IAEc,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwEtF;IAEQ,uBAAuB,CAAC,sBAAsB,CAAC,EAAE,QAAQ,EAAE,yBAAyB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAQjH;CACJ"}
@@ -44,6 +44,9 @@ class PeerExchangeProvider extends ExchangeProvider {
44
44
  preferredTransport: options?.preferredTransport
45
45
  });
46
46
  }
47
+ verifyReachability(options) {
48
+ return this.#peer.verifyReachability(options);
49
+ }
47
50
  async initiateExchange(options) {
48
51
  const abort = options?.abort;
49
52
  const isGroup = PeerAddress.isGroup(this.#peer.address);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/peer/PeerExchangeProvider.ts"],
4
- "mappings": "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",
4
+ "mappings": "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",
5
5
  "names": []
6
6
  }
@@ -91,11 +91,11 @@ export interface PeerTimingParameters {
91
91
  */
92
92
  addressChangeStabilizationDelay: Duration;
93
93
  /**
94
- * Probe cooldown range for address-change probes on the same IP.
94
+ * Probe cooldown range for address-change probes.
95
95
  *
96
96
  * When mDNS keeps reporting the session IP as gone but probes succeed, the cooldown grows
97
97
  * using a Fibonacci-like sequence from {@link minimum} to {@link maximum}. The cooldown
98
- * resets when the probed IP changes or a probe fails.
98
+ * resets on a probe failure or a subscription-liveness scare, not on a mere address change.
99
99
  *
100
100
  * Probes are also suppressed while the session is actively receiving data — the cooldown
101
101
  * is measured from whichever is more recent: the last probe or the last received message.
@@ -1 +1 @@
1
- {"version":3,"file":"PeerTimingParameters.d.ts","sourceRoot":"","sources":["../../../src/peer/PeerTimingParameters.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAmD,MAAM,iBAAiB,CAAC;AAE5F;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;;;OAKG;IACH,wBAAwB,EAAE,QAAQ,CAAC;IAEnC;;;;OAIG;IACH,oCAAoC,EAAE,QAAQ,CAAC;IAE/C;;;;OAIG;IACH,sBAAsB,EAAE,QAAQ,CAAC;IAEjC;;;;;;;OAOG;IACH,sBAAsB,EAAE,QAAQ,CAAC;IAEjC;;;;OAIG;IACH,mBAAmB,EAAE,QAAQ,CAAC;IAE9B;;;;OAIG;IACH,wBAAwB,EAAE,QAAQ,CAAC;IAEnC;;;;;OAKG;IACH,oBAAoB,EAAE,QAAQ,CAAC;IAE/B;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAE/B;;;;;;;OAOG;IACH,oBAAoB,EAAE,QAAQ,CAAC;IAE/B;;;;;OAKG;IACH,mBAAmB,EAAE;QACjB,mEAAmE;QACnE,aAAa,EAAE,QAAQ,CAAC;QAExB,gFAAgF;QAChF,OAAO,EAAE,QAAQ,CAAC;KACrB,CAAC;IAEF;;;;;OAKG;IACH,+BAA+B,EAAE,QAAQ,CAAC;IAE1C;;;;;;;;;OASG;IACH,0BAA0B,EAAE;QACxB,wEAAwE;QACxE,OAAO,EAAE,QAAQ,CAAC;QAElB,wDAAwD;QACxD,OAAO,EAAE,QAAQ,CAAC;KACrB,CAAC;CACL;AAQD,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,wBAiB3E;AAED,yBAAiB,oBAAoB,CAAC,CAAC;IACnC;;;;;OAKG;IACH,SAAgB,KAAK,CAAC,IAAI,EAAE,oBAAoB,EAAE,SAAS,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAIhH;IAKM,MAAM,QAAQ,EAAE,oBAsBtB,CAAC;CACL"}
1
+ {"version":3,"file":"PeerTimingParameters.d.ts","sourceRoot":"","sources":["../../../src/peer/PeerTimingParameters.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAA0D,MAAM,iBAAiB,CAAC;AAEnG;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;;;OAKG;IACH,wBAAwB,EAAE,QAAQ,CAAC;IAEnC;;;;OAIG;IACH,oCAAoC,EAAE,QAAQ,CAAC;IAE/C;;;;OAIG;IACH,sBAAsB,EAAE,QAAQ,CAAC;IAEjC;;;;;;;OAOG;IACH,sBAAsB,EAAE,QAAQ,CAAC;IAEjC;;;;OAIG;IACH,mBAAmB,EAAE,QAAQ,CAAC;IAE9B;;;;OAIG;IACH,wBAAwB,EAAE,QAAQ,CAAC;IAEnC;;;;;OAKG;IACH,oBAAoB,EAAE,QAAQ,CAAC;IAE/B;;;;;OAKG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAE/B;;;;;;;OAOG;IACH,oBAAoB,EAAE,QAAQ,CAAC;IAE/B;;;;;OAKG;IACH,mBAAmB,EAAE;QACjB,mEAAmE;QACnE,aAAa,EAAE,QAAQ,CAAC;QAExB,gFAAgF;QAChF,OAAO,EAAE,QAAQ,CAAC;KACrB,CAAC;IAEF;;;;;OAKG;IACH,+BAA+B,EAAE,QAAQ,CAAC;IAE1C;;;;;;;;;OASG;IACH,0BAA0B,EAAE;QACxB,wEAAwE;QACxE,OAAO,EAAE,QAAQ,CAAC;QAElB,wDAAwD;QACxD,OAAO,EAAE,QAAQ,CAAC;KACrB,CAAC;CACL;AAQD,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,wBAiB3E;AAED,yBAAiB,oBAAoB,CAAC,CAAC;IACnC;;;;;OAKG;IACH,SAAgB,KAAK,CAAC,IAAI,EAAE,oBAAoB,EAAE,SAAS,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAIhH;IAKM,MAAM,QAAQ,EAAE,oBAsBtB,CAAC;CACL"}
@@ -3,7 +3,7 @@
3
3
  * Copyright 2022-2026 Matter.js Authors
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { merge as mergeObjects, Millis, Minutes, Seconds } from "@matter/general";
6
+ import { Hours, merge as mergeObjects, Millis, Minutes, Seconds } from "@matter/general";
7
7
  const complete = /* @__PURE__ */ Symbol("complete-timing-parameters");
8
8
  function PeerTimingParameters(options) {
9
9
  if (options && options[complete]) {
@@ -47,8 +47,8 @@ function PeerTimingParameters(options) {
47
47
  },
48
48
  addressChangeStabilizationDelay: Seconds(10),
49
49
  addressChangeProbeCooldown: {
50
- minimum: Minutes(2),
51
- maximum: Minutes(60)
50
+ minimum: Minutes(10),
51
+ maximum: Hours(24)
52
52
  }
53
53
  };
54
54
  })(PeerTimingParameters || (PeerTimingParameters = {}));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/peer/PeerTimingParameters.ts"],
4
- "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAmB,SAAS,cAAc,QAAQ,SAAS,eAAe;AAuH1E,MAAM,WAAW,uBAAO,4BAA4B;AAM7C,SAAS,qBAAqB,SAAyC;AAC1E,MAAI,WAAY,QAAqB,QAAQ,GAAG;AAC5C,WAAO;AAAA,EACX;AAEA,QAAM,SAAS,EAAE,GAAG,qBAAqB,SAAS;AAClD,MAAI,SAAS;AACT,eAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACpC,YAAM,QAAS,QAAoC,GAAG;AACtD,UAAI,UAAU,QAAW;AACrB,eAAO,GAAG,IAAI;AAAA,MAClB;AAAA,IACJ;AAAA,EACJ;AACA,SAAO,QAAQ,IAAI;AAEnB,SAAO;AACX;AAAA,CAEO,CAAUA,0BAAV;AAOI,WAAS,MAAM,MAA4B,WAAgE;AAC9G,UAAM,SAAS,aAAa,MAAM,SAAS;AAC3C,WAAO,QAAQ,IAAI;AACnB,WAAO;AAAA,EACX;AAJO,EAAAA,sBAAS;AAMhB,QAAM,iCAAiC,QAAQ,CAAC;AAGzC,EAAMA,sBAAA,WAAiC;AAAA,IAC1C,0BAA0B,QAAQ,EAAE;AAAA,IACpC,sCAAsC;AAAA;AAAA;AAAA,IAItC,wBAAwB,QAAQ,EAAE;AAAA,IAClC,wBAAwB,QAAQ,EAAE;AAAA,IAClC,qBAAqB,QAAQ,CAAC;AAAA,IAC9B,0BAA0B,QAAQ,CAAC;AAAA,IACnC,sBAAsB,QAAQ,CAAC;AAAA,IAC/B,wBAAwB;AAAA,IACxB,sBAAsB,OAAO,iCAAiC,CAAC;AAAA,IAC/D,qBAAqB;AAAA,MACjB,eAAe,QAAQ,EAAE;AAAA,MACzB,SAAS,QAAQ,EAAE;AAAA,IACvB;AAAA,IACA,iCAAiC,QAAQ,EAAE;AAAA,IAC3C,4BAA4B;AAAA,MACxB,SAAS,QAAQ,CAAC;AAAA,MAClB,SAAS,QAAQ,EAAE;AAAA,IACvB;AAAA,EACJ;AAAA,GAtCa;",
4
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,SAAmB,OAAO,SAAS,cAAc,QAAQ,SAAS,eAAe;AAuHjF,MAAM,WAAW,uBAAO,4BAA4B;AAM7C,SAAS,qBAAqB,SAAyC;AAC1E,MAAI,WAAY,QAAqB,QAAQ,GAAG;AAC5C,WAAO;AAAA,EACX;AAEA,QAAM,SAAS,EAAE,GAAG,qBAAqB,SAAS;AAClD,MAAI,SAAS;AACT,eAAW,OAAO,OAAO,KAAK,OAAO,GAAG;AACpC,YAAM,QAAS,QAAoC,GAAG;AACtD,UAAI,UAAU,QAAW;AACrB,eAAO,GAAG,IAAI;AAAA,MAClB;AAAA,IACJ;AAAA,EACJ;AACA,SAAO,QAAQ,IAAI;AAEnB,SAAO;AACX;AAAA,CAEO,CAAUA,0BAAV;AAOI,WAAS,MAAM,MAA4B,WAAgE;AAC9G,UAAM,SAAS,aAAa,MAAM,SAAS;AAC3C,WAAO,QAAQ,IAAI;AACnB,WAAO;AAAA,EACX;AAJO,EAAAA,sBAAS;AAMhB,QAAM,iCAAiC,QAAQ,CAAC;AAGzC,EAAMA,sBAAA,WAAiC;AAAA,IAC1C,0BAA0B,QAAQ,EAAE;AAAA,IACpC,sCAAsC;AAAA;AAAA;AAAA,IAItC,wBAAwB,QAAQ,EAAE;AAAA,IAClC,wBAAwB,QAAQ,EAAE;AAAA,IAClC,qBAAqB,QAAQ,CAAC;AAAA,IAC9B,0BAA0B,QAAQ,CAAC;AAAA,IACnC,sBAAsB,QAAQ,CAAC;AAAA,IAC/B,wBAAwB;AAAA,IACxB,sBAAsB,OAAO,iCAAiC,CAAC;AAAA,IAC/D,qBAAqB;AAAA,MACjB,eAAe,QAAQ,EAAE;AAAA,MACzB,SAAS,QAAQ,EAAE;AAAA,IACvB;AAAA,IACA,iCAAiC,QAAQ,EAAE;AAAA,IAC3C,4BAA4B;AAAA,MACxB,SAAS,QAAQ,EAAE;AAAA,MACnB,SAAS,MAAM,EAAE;AAAA,IACrB;AAAA,EACJ;AAAA,GAtCa;",
5
5
  "names": ["PeerTimingParameters"]
6
6
  }
@@ -50,6 +50,8 @@ export interface NewExchangeOptions extends Omit<InteractionSettings, "transacti
50
50
  */
51
51
  preferredTransport?: ChannelType;
52
52
  }
53
+ /** Why a reachability check was requested. Gates entry conditions, not the probe mechanics. */
54
+ export type ReachabilityReason = "address-change" | "session-suspect";
53
55
  /**
54
56
  * Interface for obtaining a message exchange with a specific peer.
55
57
  */
@@ -74,6 +76,15 @@ export declare abstract class ExchangeProvider {
74
76
  * The default implementation is a no-op (already connected).
75
77
  */
76
78
  connect(_options?: NewExchangeOptions): Promise<void>;
79
+ /**
80
+ * Verify the peer is reachable, driving recovery (alternate-address migration or session close).
81
+ *
82
+ * The default reports reachable; only peer-backed providers have a reachability authority and override this.
83
+ */
84
+ verifyReachability(_options: {
85
+ reason: ReachabilityReason;
86
+ abort?: AbortSignal;
87
+ }): Promise<boolean>;
77
88
  }
78
89
  /**
79
90
  * Manages an exchange over an established channel.
@@ -1 +1 @@
1
- {"version":3,"file":"ExchangeProvider.d.ts","sourceRoot":"","sources":["../../../src/protocol/ExchangeProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAI1E;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC;IAChF;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,QAAQ,CAAC;IAE9B;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;IAEnC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,wGAAwG;IACxG,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAEhC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,WAAW,CAAC;CACpC;AAED;;GAEG;AACH,8BAAsB,gBAAgB;IACtB,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,eAAe;IAA/D,YAA+B,eAAe,EAAE,eAAe,EAAI;IAEnE,QAAQ,CAAC,uBAAuB,CAAC,sBAAsB,CAAC,EAAE,QAAQ,EAAE,yBAAyB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACnH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClF,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE7C;;;;;OAKG;IACH,IAAI,OAAO,IAAI,aAAa,GAAG,SAAS,CAEvC;IAED;;;;OAIG;IACG,OAAO,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAG;CACjE;AAED;;GAEG;AACH,qBAAa,gCAAiC,SAAQ,gBAAgB;;IAGlE,YAAY,eAAe,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAGnE;IAED,IAAI,iBAAiB,WAEpB;IAEK,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAM7E;IAED,IAAI,WAAW,gBAEd;IAED,IAAa,OAAO,kBAEnB;IAED,uBAAuB,CACnB,sBAAsB,WAAuC,EAC7D,yBAAyB,CAAC,EAAE,OAAO,YAOtC;IAED,IAAI,WAAW;;;kBAId;CACJ"}
1
+ {"version":3,"file":"ExchangeProvider.d.ts","sourceRoot":"","sources":["../../../src/protocol/ExchangeProvider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAI1E;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC;IAChF;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,QAAQ,CAAC;IAE9B;;;OAGG;IACH,eAAe,CAAC,EAAE,gBAAgB,CAAC;IAEnC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,wGAAwG;IACxG,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAEhC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,WAAW,CAAC;CACpC;AAED,+FAA+F;AAC/F,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAEtE;;GAEG;AACH,8BAAsB,gBAAgB;IACtB,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,eAAe;IAA/D,YAA+B,eAAe,EAAE,eAAe,EAAI;IAEnE,QAAQ,CAAC,uBAAuB,CAAC,sBAAsB,CAAC,EAAE,QAAQ,EAAE,yBAAyB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACnH,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClF,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE7C;;;;;OAKG;IACH,IAAI,OAAO,IAAI,aAAa,GAAG,SAAS,CAEvC;IAED;;;;OAIG;IACG,OAAO,CAAC,QAAQ,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAG;IAE9D;;;;OAIG;IACG,kBAAkB,CAAC,QAAQ,EAAE;QAAE,MAAM,EAAE,kBAAkB,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAExG;CACJ;AAED;;GAEG;AACH,qBAAa,gCAAiC,SAAQ,gBAAgB;;IAGlE,YAAY,eAAe,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,EAGnE;IAED,IAAI,iBAAiB,WAEpB;IAEK,gBAAgB,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,eAAe,CAAC,CAM7E;IAED,IAAI,WAAW,gBAEd;IAED,IAAa,OAAO,kBAEnB;IAED,uBAAuB,CACnB,sBAAsB,WAAuC,EAC7D,yBAAyB,CAAC,EAAE,OAAO,YAOtC;IAED,IAAI,WAAW;;;kBAId;CACJ"}
@@ -27,6 +27,14 @@ class ExchangeProvider {
27
27
  */
28
28
  async connect(_options) {
29
29
  }
30
+ /**
31
+ * Verify the peer is reachable, driving recovery (alternate-address migration or session close).
32
+ *
33
+ * The default reports reachable; only peer-backed providers have a reachability authority and override this.
34
+ */
35
+ async verifyReachability(_options) {
36
+ return true;
37
+ }
30
38
  }
31
39
  class DedicatedChannelExchangeProvider extends ExchangeProvider {
32
40
  #session;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/protocol/ExchangeProvider.ts"],
4
- "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAS,mBAAmB;AAG5B,SAAS,+BAA+B;AACxC,SAAS,WAAW;AAqDb,MAAe,iBAAiB;AAAA,EACnC,YAA+B,iBAAkC;AAAlC;AAAA,EAAmC;AAAA,EAAnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc/B,IAAI,UAAqC;AACrC,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,UAA8C;AAAA,EAAC;AACjE;AAKO,MAAM,yCAAyC,iBAAiB;AAAA,EACnE;AAAA,EAEA,YAAY,iBAAkC,SAAwB;AAClE,UAAM,eAAe;AACrB,SAAK,WAAW;AAAA,EACpB;AAAA,EAEA,IAAI,oBAAoB;AACpB,WAAO,KAAK,SAAS,WAAW;AAAA,EACpC;AAAA,EAEA,MAAM,iBAAiB,SAAwD;AAG3E,WAAO,KAAK,gBAAgB,2BAA2B,KAAK,UAAU,yBAAyB;AAAA,MAC3F,wBAAwB,SAAS;AAAA,IACrC,CAAC;AAAA,EACL;AAAA,EAEA,IAAI,cAAc;AACd,WAAO,KAAK,SAAS,QAAQ,iBAAiB;AAAA,EAClD;AAAA,EAEA,IAAa,UAAU;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,wBACI,yBAAyB,IAAI,kCAC7B,2BACF;AACE,WAAO,KAAK,gBAAgB;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,IAAI,cAAc;AACd,QAAI,YAAY,GAAG,KAAK,QAAQ,GAAG;AAC/B,aAAO,KAAK,SAAS;AAAA,IACzB;AAAA,EACJ;AACJ;",
4
+ "mappings": "AAAA;AAAA;AAAA;AAAA;AAAA;AASA,SAAS,mBAAmB;AAG5B,SAAS,+BAA+B;AACxC,SAAS,WAAW;AAwDb,MAAe,iBAAiB;AAAA,EACnC,YAA+B,iBAAkC;AAAlC;AAAA,EAAmC;AAAA,EAAnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc/B,IAAI,UAAqC;AACrC,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,UAA8C;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO7D,MAAM,mBAAmB,UAAiF;AACtG,WAAO;AAAA,EACX;AACJ;AAKO,MAAM,yCAAyC,iBAAiB;AAAA,EACnE;AAAA,EAEA,YAAY,iBAAkC,SAAwB;AAClE,UAAM,eAAe;AACrB,SAAK,WAAW;AAAA,EACpB;AAAA,EAEA,IAAI,oBAAoB;AACpB,WAAO,KAAK,SAAS,WAAW;AAAA,EACpC;AAAA,EAEA,MAAM,iBAAiB,SAAwD;AAG3E,WAAO,KAAK,gBAAgB,2BAA2B,KAAK,UAAU,yBAAyB;AAAA,MAC3F,wBAAwB,SAAS;AAAA,IACrC,CAAC;AAAA,EACL;AAAA,EAEA,IAAI,cAAc;AACd,WAAO,KAAK,SAAS,QAAQ,iBAAiB;AAAA,EAClD;AAAA,EAEA,IAAa,UAAU;AACnB,WAAO,KAAK;AAAA,EAChB;AAAA,EAEA,wBACI,yBAAyB,IAAI,kCAC7B,2BACF;AACE,WAAO,KAAK,gBAAgB;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACJ;AAAA,EACJ;AAAA,EAEA,IAAI,cAAc;AACd,QAAI,YAAY,GAAG,KAAK,QAAQ,GAAG;AAC/B,aAAO,KAAK,SAAS;AAAA,IACzB;AAAA,EACJ;AACJ;",
5
5
  "names": []
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matter/protocol",
3
- "version": "0.17.3",
3
+ "version": "0.17.4-alpha.0-20260623-5647634fc",
4
4
  "description": "Low-level APIs for Matter interaction",
5
5
  "keywords": [
6
6
  "iot",
@@ -35,12 +35,12 @@
35
35
  "#*": "./src/*"
36
36
  },
37
37
  "dependencies": {
38
- "@matter/general": "0.17.3",
39
- "@matter/model": "0.17.3",
40
- "@matter/types": "0.17.3"
38
+ "@matter/general": "0.17.4-alpha.0-20260623-5647634fc",
39
+ "@matter/model": "0.17.4-alpha.0-20260623-5647634fc",
40
+ "@matter/types": "0.17.4-alpha.0-20260623-5647634fc"
41
41
  },
42
42
  "devDependencies": {
43
- "@matter/testing": "0.17.3"
43
+ "@matter/testing": "0.17.4-alpha.0-20260623-5647634fc"
44
44
  },
45
45
  "files": [
46
46
  "dist/**/*",
@@ -299,6 +299,7 @@ export class ClientInteraction<
299
299
  await messenger.sendReadRequest(Read({ fabricFilter: false }), {
300
300
  abort,
301
301
  suppressPeerLoss: options?.suppressPeerLoss,
302
+ maxRetransmissions: options?.maxRetransmissions,
302
303
  });
303
304
  for await (const _report of messenger.readDataReports({ abort }));
304
305
  logger.info(
@@ -968,12 +969,11 @@ export class ClientInteraction<
968
969
  abort: session?.abort,
969
970
  retries: this.#sustainRetries,
970
971
  read,
971
- // TCP has 1:1 session-connection binding plus OS keep-alive — the session is
972
- // evicted when the connection drops, so no liveness probe is needed.
973
972
  probe: abort =>
974
973
  this.#exchangeProvider.channelType === ChannelType.TCP
975
- ? Promise.resolve(true)
976
- : this.probe({ abort }),
974
+ ? // TCP evicts the session when its connection drops, so no liveness probe is needed.
975
+ Promise.resolve(true)
976
+ : this.#exchangeProvider.verifyReachability({ reason: "session-suspect", abort }),
977
977
  });
978
978
  } else {
979
979
  subscription = await subscribe(request);
@@ -1143,6 +1143,9 @@ export interface ClientProbeOptions {
1143
1143
 
1144
1144
  /** Suppress peer-loss reporting so the session stays alive even if the probe fails. */
1145
1145
  suppressPeerLoss?: boolean;
1146
+
1147
+ /** Cap MRP transmission attempts for this probe (default: MRP.MAX_TRANSMISSIONS). */
1148
+ maxRetransmissions?: number;
1146
1149
  }
1147
1150
 
1148
1151
  async function* readChunks(messenger: InteractionClientMessenger, abort: Abort) {
package/src/peer/Peer.ts CHANGED
@@ -9,7 +9,7 @@ import type { NodeProtocol } from "#action/protocols.js";
9
9
  import { DiscoveryData } from "#common/Scanner.js";
10
10
  import { getOperationalDeviceQname } from "#mdns/MdnsConsts.js";
11
11
  import { PeerAddress } from "#peer/PeerAddress.js";
12
- import type { ExchangeProvider } from "#protocol/ExchangeProvider.js";
12
+ import type { ExchangeProvider, ReachabilityReason } from "#protocol/ExchangeProvider.js";
13
13
  import type { NodeSession } from "#session/NodeSession.js";
14
14
  import { SessionParameters } from "#session/SessionParameters.js";
15
15
  import {
@@ -142,7 +142,8 @@ export class Peer {
142
142
  }
143
143
  } else {
144
144
  tagUdp(channel.networkAddress);
145
- channel.networkAddressChanged.on(tagUdp);
145
+ // MessageChannel.networkAddressChanged survives socket swaps; the transport channel's does not.
146
+ session.channel.networkAddressChanged.on(tagUdp);
146
147
  }
147
148
  }
148
149
  }
@@ -150,9 +151,8 @@ export class Peer {
150
151
  // Remove session and detach listener when destroyed
151
152
  session.closing.on(() => {
152
153
  this.#sessions.delete(session);
153
- const channel = session.channel.transportChannel;
154
- if (isIpNetworkChannel(channel)) {
155
- channel.networkAddressChanged.off(tagUdp);
154
+ if (isIpNetworkChannel(session.channel.transportChannel)) {
155
+ session.channel.networkAddressChanged.off(tagUdp);
156
156
  }
157
157
  });
158
158
 
@@ -488,11 +488,11 @@ export class Peer {
488
488
 
489
489
  this.#observers.close();
490
490
 
491
- // Cancel pending address check
492
- this.#addressMonitor?.stop();
493
-
494
491
  this.#abort(new ClosedError("Peer closed"));
495
492
 
493
+ // Stop the address monitor and await any in-flight reachability run (the abort above makes it unwind).
494
+ await this.#addressMonitor?.close();
495
+
496
496
  for (const session of this.#context.sessions.sessionsFor(this.address)) {
497
497
  await session.initiateClose();
498
498
  }
@@ -552,6 +552,18 @@ export class Peer {
552
552
  return this.#addressMonitor;
553
553
  }
554
554
 
555
+ /**
556
+ * Verify this peer is reachable, driving recovery via the address monitor.
557
+ *
558
+ * Returns `true` if the session is usable afterward, `false` if it was closed.
559
+ */
560
+ async verifyReachability(options: { reason: ReachabilityReason; abort?: AbortSignal }): Promise<boolean> {
561
+ if (!this.hasSession) {
562
+ return false;
563
+ }
564
+ return this.#addressCheck.verifyReachability(options);
565
+ }
566
+
555
567
  #initiateConnection(options?: Peer.ConnectOptions) {
556
568
  if (this.#connecting) {
557
569
  if (options?.kick) {