@actuallab/rpc 14.3.5 → 14.3.26

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.
package/dist/index.d.cts CHANGED
@@ -981,6 +981,9 @@ declare abstract class RpcPeer {
981
981
  * stale `$sys.Reconnect` that references an older connection generation
982
982
  * (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */
983
983
  protected _ownHandshakeIndex: number;
984
+ /** Whether the current connection generation has already used its single
985
+ * `$sys.Reconnect`. Mirrors C# `RpcPeerConnectionState.TryClaimReconnect`. */
986
+ protected _isReconnectClaimed: boolean;
984
987
  protected _connection: RpcConnection | undefined;
985
988
  /** Mirror of `_connectionState === Connected`. True only after the
986
989
  * handshake round-trip completes; outbound `call()` checks this to
@@ -1013,6 +1016,8 @@ declare abstract class RpcPeer {
1013
1016
  get connectionState(): RpcConnectionState;
1014
1017
  get isConnected(): boolean;
1015
1018
  get ownHandshakeIndex(): number;
1019
+ tryClaimReconnect(): boolean;
1020
+ protected nextOwnHandshakeIndex(): number;
1016
1021
  protected _setConnectionState(value: RpcConnectionState): void;
1017
1022
  /** Resolves on the next transition into `Connected` — or immediately if
1018
1023
  * already in that state. */
@@ -1516,6 +1521,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1516
1521
  private _complete;
1517
1522
  private _failProtocol;
1518
1523
  private _isBeyondAckWindow;
1524
+ private _ackWindowSize;
1519
1525
  private _ackWindowError;
1520
1526
  private _gapError;
1521
1527
  private _notifyConsumer;
package/dist/index.d.ts CHANGED
@@ -981,6 +981,9 @@ declare abstract class RpcPeer {
981
981
  * stale `$sys.Reconnect` that references an older connection generation
982
982
  * (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */
983
983
  protected _ownHandshakeIndex: number;
984
+ /** Whether the current connection generation has already used its single
985
+ * `$sys.Reconnect`. Mirrors C# `RpcPeerConnectionState.TryClaimReconnect`. */
986
+ protected _isReconnectClaimed: boolean;
984
987
  protected _connection: RpcConnection | undefined;
985
988
  /** Mirror of `_connectionState === Connected`. True only after the
986
989
  * handshake round-trip completes; outbound `call()` checks this to
@@ -1013,6 +1016,8 @@ declare abstract class RpcPeer {
1013
1016
  get connectionState(): RpcConnectionState;
1014
1017
  get isConnected(): boolean;
1015
1018
  get ownHandshakeIndex(): number;
1019
+ tryClaimReconnect(): boolean;
1020
+ protected nextOwnHandshakeIndex(): number;
1016
1021
  protected _setConnectionState(value: RpcConnectionState): void;
1017
1022
  /** Resolves on the next transition into `Connected` — or immediately if
1018
1023
  * already in that state. */
@@ -1516,6 +1521,7 @@ declare class RpcStream<T> implements AsyncIterable<T>, IRpcObject {
1516
1521
  private _complete;
1517
1522
  private _failProtocol;
1518
1523
  private _isBeyondAckWindow;
1524
+ private _ackWindowSize;
1519
1525
  private _ackWindowError;
1520
1526
  private _gapError;
1521
1527
  private _notifyConsumer;
package/dist/index.js CHANGED
@@ -1172,6 +1172,15 @@ var RpcSystemCallHandler = class {
1172
1172
  );
1173
1173
  return;
1174
1174
  }
1175
+ if (!peer.tryClaimReconnect()) {
1176
+ peer.hub.systemCallSender.error(
1177
+ peer.connection,
1178
+ peer.serializationFormat,
1179
+ relatedId,
1180
+ new Error("TooLateToReconnect: this connection was already reconnected")
1181
+ );
1182
+ return;
1183
+ }
1175
1184
  let unknownIds = [];
1176
1185
  try {
1177
1186
  const stagesRaw = args[1];
@@ -1912,6 +1921,7 @@ function _compactBufferedUnsentSuffix(buffer, firstUnsentIndex, canSkipTo) {
1912
1921
  var { warnLog: warnLog6 } = getLogs("RpcStream");
1913
1922
  var DEFAULT_ACK_PERIOD2 = 30;
1914
1923
  var DEFAULT_ACK_ADVANCE2 = 61;
1924
+ var MAX_BATCH_SIZE = 1024;
1915
1925
  function parseStreamRef(value) {
1916
1926
  if (typeof value === "string") {
1917
1927
  const parts = value.split(",");
@@ -2099,6 +2109,10 @@ var RpcStream = class _RpcStream {
2099
2109
  this._failProtocol(`invalid $sys.B index/length: ${String(index)}/${String(items.length)}`);
2100
2110
  return;
2101
2111
  }
2112
+ if (items.length > MAX_BATCH_SIZE) {
2113
+ this._failProtocol(`$sys.B batch is too large: ${items.length} > ${MAX_BATCH_SIZE}`);
2114
+ return;
2115
+ }
2102
2116
  if (index > this._nextExpectedIndex) {
2103
2117
  warnLog6?.log(`batch index gap: localId=${this.id.localId}, expected=${this._nextExpectedIndex}, received=${index}`);
2104
2118
  if (!this.allowReconnect) {
@@ -2233,11 +2247,23 @@ var RpcStream = class _RpcStream {
2233
2247
  // last acknowledged index. The sending side is the only place that window
2234
2248
  // was ever enforced, which left a flooding remote free to grow our buffer
2235
2249
  // without bound (I6) — so we enforce our half of the contract here.
2250
+ //
2251
+ // The window is credited by consumption, never by `_ackSentUpTo`: a reset ack
2252
+ // re-bases to what we've *received*, so crediting it would let a peer ratchet
2253
+ // its own limit up by alternating "fill the window" with a forced gap.
2254
+ // Twice ackAdvance is what a conforming sender can leave buffered (ackAdvance
2255
+ // in steady state, plus ackAdvance more after a reset re-bases it), and
2256
+ // ackPeriod covers the lag of acks behind consumption.
2236
2257
  _isBeyondAckWindow(nextExpectedIndex) {
2237
- return this.ackAdvance > 0 && nextExpectedIndex > this._ackSentUpTo + this.ackAdvance;
2258
+ if (this.ackAdvance <= 0)
2259
+ return false;
2260
+ return nextExpectedIndex > this._nextConsumedIndex + this._ackWindowSize();
2261
+ }
2262
+ _ackWindowSize() {
2263
+ return 2 * this.ackAdvance + this.ackPeriod;
2238
2264
  }
2239
2265
  _ackWindowError(nextExpectedIndex) {
2240
- return `Stream ran past its ack window: reached ${nextExpectedIndex}, acked ${this._ackSentUpTo}, ackAdvance ${this.ackAdvance}`;
2266
+ return `Stream ran past its ack window: reached ${nextExpectedIndex}, consumed ${this._nextConsumedIndex}, ackAdvance ${this.ackAdvance}`;
2241
2267
  }
2242
2268
  _gapError(index) {
2243
2269
  return new Error(
@@ -2619,6 +2645,9 @@ var RpcPeer = class {
2619
2645
  * stale `$sys.Reconnect` that references an older connection generation
2620
2646
  * (C# `RpcSystemCalls.Reconnect`'s `ownHandshake.Index` check). */
2621
2647
  _ownHandshakeIndex = 0;
2648
+ /** Whether the current connection generation has already used its single
2649
+ * `$sys.Reconnect`. Mirrors C# `RpcPeerConnectionState.TryClaimReconnect`. */
2650
+ _isReconnectClaimed = false;
2622
2651
  _connection;
2623
2652
  /** Mirror of `_connectionState === Connected`. True only after the
2624
2653
  * handshake round-trip completes; outbound `call()` checks this to
@@ -2689,6 +2718,19 @@ var RpcPeer = class {
2689
2718
  get ownHandshakeIndex() {
2690
2719
  return this._ownHandshakeIndex;
2691
2720
  }
2721
+ // A conforming peer sends `$sys.Reconnect` at most once per connection, so anything
2722
+ // beyond that is a replay - and the reconciliation it performs is proportional to the
2723
+ // call ids it carries, with nothing else capping how often it can arrive.
2724
+ tryClaimReconnect() {
2725
+ if (this._isReconnectClaimed)
2726
+ return false;
2727
+ this._isReconnectClaimed = true;
2728
+ return true;
2729
+ }
2730
+ nextOwnHandshakeIndex() {
2731
+ this._isReconnectClaimed = false;
2732
+ return ++this._ownHandshakeIndex;
2733
+ }
2692
2734
  _setConnectionState(value) {
2693
2735
  if (this._connectionState === value) return;
2694
2736
  const previousState = this._connectionState;
@@ -3224,7 +3266,7 @@ var RpcClientPeer = class extends RpcPeer {
3224
3266
  this.serializationFormat,
3225
3267
  this.id,
3226
3268
  this.hub.hubId,
3227
- ++this._ownHandshakeIndex
3269
+ this.nextOwnHandshakeIndex()
3228
3270
  );
3229
3271
  } catch (e) {
3230
3272
  warnLog7?.log(`'${this.ref}': Failed to send handshake`, e);
@@ -3489,7 +3531,7 @@ var RpcServerPeer = class extends RpcPeer {
3489
3531
  this.serializationFormat,
3490
3532
  this.id,
3491
3533
  this.hub.hubId,
3492
- ++this._ownHandshakeIndex
3534
+ this.nextOwnHandshakeIndex()
3493
3535
  );
3494
3536
  this._setConnectionState(3 /* Connected */);
3495
3537
  }