@actuallab/rpc 13.0.4 → 13.0.12

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.cjs CHANGED
@@ -33,7 +33,6 @@ __export(index_exports, {
33
33
  ARG_DELIMITER: () => ARG_DELIMITER,
34
34
  ENVELOPE_DELIMITER: () => ENVELOPE_DELIMITER,
35
35
  FRAME_DELIMITER: () => FRAME_DELIMITER,
36
- HANDSHAKE_TIMEOUT_MS: () => HANDSHAKE_TIMEOUT_MS,
37
36
  IncreasingSeqCompressor: () => IncreasingSeqCompressor,
38
37
  RPC_CLOSE_CODE_UNSUPPORTED_FORMAT: () => RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
39
38
  RpcCallStage: () => RpcCallStage,
@@ -46,6 +45,7 @@ __export(index_exports, {
46
45
  RpcInboundCall: () => RpcInboundCall,
47
46
  RpcInboundCallTracker: () => RpcInboundCallTracker,
48
47
  RpcJsonSerializationFormat: () => RpcJsonSerializationFormat,
48
+ RpcLimits: () => RpcLimits,
49
49
  RpcMessageChannelConnection: () => RpcMessageChannelConnection,
50
50
  RpcMessagePackCompactSerializationFormat: () => RpcMessagePackCompactSerializationFormat,
51
51
  RpcMessagePackSerializationFormat: () => RpcMessagePackSerializationFormat,
@@ -2290,9 +2290,6 @@ function yieldToEventLoop() {
2290
2290
  return new Promise((resolve) => setTimeout(resolve, 0));
2291
2291
  }
2292
2292
  var RPC_CLOSE_CODE_UNSUPPORTED_FORMAT = 4001;
2293
- var HANDSHAKE_TIMEOUT_MS = 1e4;
2294
- var KEEP_ALIVE_PERIOD_MS = 15e3;
2295
- var KEEP_ALIVE_TIMEOUT_MS = 55e3;
2296
2293
  var { debugLog: debugLog2, infoLog, warnLog: warnLog6, errorLog: errorLog2 } = getLogs("RpcPeer");
2297
2294
  var defaultConnectionUrlResolver = (peer) => {
2298
2295
  const formatKey = peer.serializationFormat.key;
@@ -2354,13 +2351,17 @@ var RpcPeer = class {
2354
2351
  /** Wall-clock time of the last inbound `$sys.KeepAlive`, or 0 if none yet
2355
2352
  * for the current connection. Useful for diagnostics. */
2356
2353
  _lastKeepAliveAt = 0;
2357
- /** Outbound keep-alive send period (per-peer override for tests). */
2358
- keepAlivePeriodMs = KEEP_ALIVE_PERIOD_MS;
2359
- /** Inbound keep-alive silence tolerance (per-peer override for tests). */
2360
- keepAliveTimeoutMs = KEEP_ALIVE_TIMEOUT_MS;
2354
+ /** Outbound keep-alive send period. Initialized from `hub.limits` at
2355
+ * construction; set directly to override on a single peer. */
2356
+ keepAlivePeriodMs;
2357
+ /** Inbound keep-alive silence tolerance. Initialized from `hub.limits`
2358
+ * at construction; set directly to override on a single peer. */
2359
+ keepAliveTimeoutMs;
2361
2360
  constructor(hub, ref) {
2362
2361
  this.hub = hub;
2363
2362
  this.ref = ref;
2363
+ this.keepAlivePeriodMs = hub.limits.keepAlivePeriodMs;
2364
+ this.keepAliveTimeoutMs = hub.limits.keepAliveTimeoutMs;
2364
2365
  }
2365
2366
  get connection() {
2366
2367
  return this._connection;
@@ -2679,9 +2680,14 @@ var RpcClientPeer = class extends RpcPeer {
2679
2680
  * `Cookie` headers to the WS upgrade, which the browser `WebSocket`
2680
2681
  * constructor can't do). Must be set before `start()`. */
2681
2682
  webSocketFactory;
2683
+ /** Max time to wait for the WebSocket to reach OPEN before aborting the
2684
+ * connection attempt. Initialized from `hub.limits` at construction;
2685
+ * set directly to override on a single peer. */
2686
+ connectTimeoutMs;
2682
2687
  /** Max time to wait for the server's handshake response before aborting
2683
- * the connection attempt. Mirrors .NET's `Hub.Limits.HandshakeTimeout`. */
2684
- handshakeTimeoutMs = HANDSHAKE_TIMEOUT_MS;
2688
+ * the connection attempt. Initialized from `hub.limits` at construction;
2689
+ * set directly to override on a single peer. */
2690
+ handshakeTimeoutMs;
2685
2691
  peerChanged = new import_core7.EventHandlerSet();
2686
2692
  /** Fired when the server rejects the connection due to an unsupported serialization format (close code 4010). */
2687
2693
  unsupportedFormat = new import_core7.EventHandlerSet();
@@ -2712,6 +2718,8 @@ var RpcClientPeer = class extends RpcPeer {
2712
2718
  */
2713
2719
  constructor(hub, url, mustStart = true) {
2714
2720
  super(hub, url);
2721
+ this.connectTimeoutMs = hub.limits.connectTimeoutMs;
2722
+ this.handshakeTimeoutMs = hub.limits.handshakeTimeoutMs;
2715
2723
  this.clientId = guidToBase64Url(this.id);
2716
2724
  const resolver = RpcSerializationFormatResolver.Default;
2717
2725
  this.serializationFormat = resolver.get(parseFormatFromUrl(url) ?? resolver.defaultFormatKey);
@@ -2788,7 +2796,20 @@ var RpcClientPeer = class extends RpcPeer {
2788
2796
  });
2789
2797
  this._pendingHandshake = new import_core7.PromiseSource();
2790
2798
  this.setupConnection(conn);
2791
- await Promise.race([conn.whenConnected, closedRejection]);
2799
+ const connectTimeoutMs = this.connectTimeoutMs;
2800
+ try {
2801
+ await (0, import_core7.withTimeout)(
2802
+ Promise.race([conn.whenConnected, closedRejection]),
2803
+ connectTimeoutMs,
2804
+ "Connect timeout"
2805
+ );
2806
+ } catch (e) {
2807
+ if (e instanceof import_core7.TimeoutError) {
2808
+ warnLog6?.log(`'${this.ref}': Connect timed out after ${connectTimeoutMs}ms`);
2809
+ conn.close();
2810
+ }
2811
+ throw e;
2812
+ }
2792
2813
  this._setConnectionState(2 /* Handshaking */);
2793
2814
  try {
2794
2815
  this.hub.systemCallSender.handshake(
@@ -2803,29 +2824,20 @@ var RpcClientPeer = class extends RpcPeer {
2803
2824
  throw e;
2804
2825
  }
2805
2826
  const handshakeTimeoutMs = this.handshakeTimeoutMs;
2806
- let handshakeTimer;
2807
- const handshakeTimeout = new Promise((_, reject) => {
2808
- handshakeTimer = setTimeout(() => {
2809
- reject(new Error("Handshake timeout"));
2810
- }, handshakeTimeoutMs);
2811
- });
2812
- handshakeTimeout.catch(() => {
2813
- });
2814
2827
  let remoteHandshake;
2815
2828
  try {
2816
- remoteHandshake = await Promise.race([
2817
- this._pendingHandshake.promise,
2818
- closedRejection,
2819
- handshakeTimeout
2820
- ]);
2829
+ remoteHandshake = await (0, import_core7.withTimeout)(
2830
+ Promise.race([this._pendingHandshake.promise, closedRejection]),
2831
+ handshakeTimeoutMs,
2832
+ "Handshake timeout"
2833
+ );
2821
2834
  } catch (e) {
2822
- if (e instanceof Error && e.message === "Handshake timeout") {
2835
+ if (e instanceof import_core7.TimeoutError) {
2823
2836
  warnLog6?.log(`'${this.ref}': Handshake timed out after ${handshakeTimeoutMs}ms`);
2824
2837
  conn.close();
2825
2838
  }
2826
2839
  throw e;
2827
2840
  } finally {
2828
- if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
2829
2841
  this._pendingHandshake = void 0;
2830
2842
  }
2831
2843
  this._remoteHandshakeIndex = remoteHandshake.Index ?? 0;
@@ -3057,6 +3069,32 @@ function _toBytes2(value) {
3057
3069
  return null;
3058
3070
  }
3059
3071
 
3072
+ // src/rpc-limits.ts
3073
+ var RpcLimits = class _RpcLimits {
3074
+ /** Process-wide default. Hubs read from this when they're constructed
3075
+ * unless caller assigns a custom instance to `hub.limits`. */
3076
+ static Default = new _RpcLimits();
3077
+ /** Max time to wait for the WebSocket to enter the OPEN state. On a
3078
+ * hung connect (mobile after network change, half-open after sleep)
3079
+ * the browser can take ~2 min to emit `onerror`/`onclose`; without
3080
+ * this cap the reconnect loop blocks for that whole window. */
3081
+ connectTimeoutMs = 1e4;
3082
+ /** Max time to wait for the server's handshake response after WS opens. */
3083
+ handshakeTimeoutMs = 1e4;
3084
+ /** Outbound `$sys.KeepAlive` send period. */
3085
+ keepAlivePeriodMs = 1e4;
3086
+ /** Reaper threshold — connection is force-closed (and the reconnect
3087
+ * loop takes over) if no inbound `$sys.KeepAlive` has been seen for
3088
+ * this long. Sized to tolerate a complete server stall plus most of
3089
+ * one keepalive cycle: the worst-case age of `_lastKeepAliveAt` is
3090
+ * `keepAlivePeriodMs + stall_duration`, so with these defaults the
3091
+ * tolerated stall is ~15 s. */
3092
+ keepAliveTimeoutMs = 25e3;
3093
+ constructor(overrides) {
3094
+ if (overrides) Object.assign(this, overrides);
3095
+ }
3096
+ };
3097
+
3060
3098
  // src/rpc-client-peer-reconnect-delayer.ts
3061
3099
  var import_core8 = require("@actuallab/core");
3062
3100
  var RpcClientPeerReconnectDelayer = class extends import_core8.RetryDelayer {
@@ -3671,6 +3709,13 @@ var RpcHub = class {
3671
3709
  /** Shared reconnect delayer used by every client peer in this hub. Swap in
3672
3710
  * a custom subclass (e.g. app-level signal-gated) before peers start. */
3673
3711
  reconnectDelayer = new RpcClientPeerReconnectDelayer();
3712
+ /** Connection-lifecycle timing limits. Peers constructed against this hub
3713
+ * read their initial `*Ms` field values from this instance; later
3714
+ * mutations affect only peers built afterward (existing peers can be
3715
+ * retuned by setting their `*Ms` fields directly). Defaults to the
3716
+ * process-wide {@link RpcLimits.Default} so a single global override
3717
+ * propagates to every hub that hasn't replaced this. */
3718
+ limits = RpcLimits.Default;
3674
3719
  /** URL used by {@link defaultPeer} to resolve / create the default client
3675
3720
  * peer. Must be set before the first {@link defaultPeer} access. */
3676
3721
  defaultPeerUrl;
@@ -4119,7 +4164,6 @@ var RpcPeerStateMonitor = class {
4119
4164
  ARG_DELIMITER,
4120
4165
  ENVELOPE_DELIMITER,
4121
4166
  FRAME_DELIMITER,
4122
- HANDSHAKE_TIMEOUT_MS,
4123
4167
  IncreasingSeqCompressor,
4124
4168
  RPC_CLOSE_CODE_UNSUPPORTED_FORMAT,
4125
4169
  RpcCallStage,
@@ -4132,6 +4176,7 @@ var RpcPeerStateMonitor = class {
4132
4176
  RpcInboundCall,
4133
4177
  RpcInboundCallTracker,
4134
4178
  RpcJsonSerializationFormat,
4179
+ RpcLimits,
4135
4180
  RpcMessageChannelConnection,
4136
4181
  RpcMessagePackCompactSerializationFormat,
4137
4182
  RpcMessagePackSerializationFormat,