@cloudflare/sandbox 0.9.3 → 0.9.4

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.
@@ -765,8 +765,8 @@ function createErrorFromResponse(errorResponse, options) {
765
765
  /**
766
766
  * Container startup retry configuration
767
767
  */
768
- const DEFAULT_RETRY_TIMEOUT_MS = 12e4;
769
- const MIN_TIME_FOR_RETRY_MS = 15e3;
768
+ const DEFAULT_RETRY_TIMEOUT_MS$1 = 12e4;
769
+ const MIN_TIME_FOR_RETRY_MS$1 = 15e3;
770
770
  /**
771
771
  * Abstract base transport with shared retry logic
772
772
  *
@@ -780,7 +780,7 @@ var BaseTransport = class {
780
780
  constructor(config) {
781
781
  this.config = config;
782
782
  this.logger = config.logger ?? createNoOpLogger();
783
- this.retryTimeoutMs = config.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;
783
+ this.retryTimeoutMs = config.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS$1;
784
784
  }
785
785
  setRetryTimeoutMs(ms) {
786
786
  this.retryTimeoutMs = ms;
@@ -802,7 +802,7 @@ var BaseTransport = class {
802
802
  if (response.status === 503) {
803
803
  const elapsed = Date.now() - startTime;
804
804
  const remaining = this.retryTimeoutMs - elapsed;
805
- if (remaining > MIN_TIME_FOR_RETRY_MS) {
805
+ if (remaining > MIN_TIME_FOR_RETRY_MS$1) {
806
806
  const delay = Math.min(3e3 * 2 ** attempt, 3e4);
807
807
  this.logger.info("Container not ready, retrying", {
808
808
  status: response.status,
@@ -2756,6 +2756,9 @@ function normalizeBackupExcludePattern(pattern) {
2756
2756
  //#endregion
2757
2757
  //#region src/container-control/connection.ts
2758
2758
  const DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
2759
+ const DEFAULT_RETRY_TIMEOUT_MS = 12e4;
2760
+ const MIN_TIME_FOR_RETRY_MS = 15e3;
2761
+ const MAX_RETRY_BACKOFF_MS = 3e4;
2759
2762
  /**
2760
2763
  * Manages a capnweb WebSocket RPC session to the container.
2761
2764
  *
@@ -2773,10 +2776,12 @@ var ContainerControlConnection = class {
2773
2776
  containerStub;
2774
2777
  port;
2775
2778
  logger;
2779
+ retryTimeoutMs;
2776
2780
  constructor(options) {
2777
2781
  this.containerStub = options.stub;
2778
2782
  this.port = options.port ?? 3e3;
2779
2783
  this.logger = options.logger ?? createNoOpLogger();
2784
+ this.retryTimeoutMs = options.retryTimeoutMs ?? DEFAULT_RETRY_TIMEOUT_MS;
2780
2785
  this.transport = new DeferredTransport();
2781
2786
  this.session = new RpcSession(this.transport);
2782
2787
  this.stub = this.session.getRemoteMain();
@@ -2826,20 +2831,17 @@ var ContainerControlConnection = class {
2826
2831
  this.connected = false;
2827
2832
  this.connectPromise = null;
2828
2833
  }
2834
+ /**
2835
+ * Update the 503 retry budget without recreating the connection. Takes
2836
+ * effect on the next `connect()`; an in-flight connect uses the value
2837
+ * captured at start. Mirrors `WebSocketTransport.setRetryTimeoutMs`.
2838
+ */
2839
+ setRetryTimeoutMs(ms) {
2840
+ this.retryTimeoutMs = ms;
2841
+ }
2829
2842
  async doConnect() {
2830
- const controller = new AbortController();
2831
- const timeout = setTimeout(() => controller.abort(), DEFAULT_CONNECT_TIMEOUT_MS);
2832
2843
  try {
2833
- const url = `http://localhost:${this.port}/rpc`;
2834
- const request = new Request(url, {
2835
- headers: {
2836
- Upgrade: "websocket",
2837
- Connection: "Upgrade"
2838
- },
2839
- signal: controller.signal
2840
- });
2841
- const response = await this.containerStub.fetch(request);
2842
- clearTimeout(timeout);
2844
+ const response = await this.fetchUpgradeWithRetry();
2843
2845
  if (response.status !== 101) throw new Error(`WebSocket upgrade failed: ${response.status} ${response.statusText}`);
2844
2846
  const ws = response.webSocket;
2845
2847
  if (!ws) throw new Error("No WebSocket in upgrade response");
@@ -2858,13 +2860,63 @@ var ContainerControlConnection = class {
2858
2860
  this.connected = true;
2859
2861
  this.logger.debug("ContainerControlConnection established", { port: this.port });
2860
2862
  } catch (error) {
2861
- clearTimeout(timeout);
2862
2863
  this.connected = false;
2863
2864
  this.transport.abort(error);
2864
2865
  this.logger.error("ContainerControlConnection failed", error instanceof Error ? error : new Error(String(error)));
2865
2866
  throw error;
2866
2867
  }
2867
2868
  }
2869
+ /**
2870
+ * Issue WebSocket upgrade fetches, retrying transient 503 responses with
2871
+ * exponential backoff (3s → 6s → 12s → … capped at 30s) until either
2872
+ * the upgrade succeeds, a non-503 status is returned, or the retry budget
2873
+ * runs out. Mirrors `WebSocketTransport.fetchUpgradeWithRetry` so both
2874
+ * transports behave the same way during container startup.
2875
+ */
2876
+ async fetchUpgradeWithRetry() {
2877
+ const retryTimeoutMs = this.retryTimeoutMs;
2878
+ const startTime = Date.now();
2879
+ let attempt = 0;
2880
+ while (true) {
2881
+ const response = await this.fetchUpgradeAttempt();
2882
+ if (response.status !== 503) return response;
2883
+ const remaining = retryTimeoutMs - (Date.now() - startTime);
2884
+ if (remaining <= MIN_TIME_FOR_RETRY_MS) return response;
2885
+ const delay = Math.min(3e3 * 2 ** attempt, MAX_RETRY_BACKOFF_MS);
2886
+ this.logger.info("ContainerControlConnection upgrade returned 503, retrying", {
2887
+ attempt: attempt + 1,
2888
+ delayMs: delay,
2889
+ remainingSec: Math.floor(remaining / 1e3)
2890
+ });
2891
+ await this.sleep(delay);
2892
+ attempt++;
2893
+ }
2894
+ }
2895
+ /**
2896
+ * Single WebSocket-upgrade fetch attempt. Owns its own AbortController so
2897
+ * each retry gets a fresh per-attempt connect timeout independent of the
2898
+ * total retry budget.
2899
+ */
2900
+ async fetchUpgradeAttempt() {
2901
+ const controller = new AbortController();
2902
+ const timeout = setTimeout(() => controller.abort(), DEFAULT_CONNECT_TIMEOUT_MS);
2903
+ try {
2904
+ const url = `http://localhost:${this.port}/rpc`;
2905
+ const request = new Request(url, {
2906
+ headers: {
2907
+ Upgrade: "websocket",
2908
+ Connection: "Upgrade"
2909
+ },
2910
+ signal: controller.signal
2911
+ });
2912
+ return await this.containerStub.fetch(request);
2913
+ } finally {
2914
+ clearTimeout(timeout);
2915
+ }
2916
+ }
2917
+ sleep(ms) {
2918
+ return new Promise((resolve) => setTimeout(resolve, ms));
2919
+ }
2868
2920
  };
2869
2921
  /**
2870
2922
  * RPC transport that queues sends and blocks receives until a WebSocket
@@ -3093,7 +3145,8 @@ var ContainerControlClient = class {
3093
3145
  this.connOptions = {
3094
3146
  stub: options.stub,
3095
3147
  port: options.port,
3096
- logger: options.logger
3148
+ logger: options.logger,
3149
+ retryTimeoutMs: options.retryTimeoutMs
3097
3150
  };
3098
3151
  this.idleDisconnectMs = options.idleDisconnectMs ?? DEFAULT_IDLE_DISCONNECT_MS;
3099
3152
  this.busyPollIntervalMs = options.busyPollIntervalMs ?? BUSY_POLL_INTERVAL_MS;
@@ -3226,7 +3279,15 @@ var ContainerControlClient = class {
3226
3279
  get interpreter() {
3227
3280
  return wrapStub(this.getConnection().rpc().interpreter, this.renewActivity);
3228
3281
  }
3229
- setRetryTimeoutMs(_ms) {}
3282
+ /**
3283
+ * Update the 503 upgrade-retry budget. Applies to the current connection
3284
+ * (if any) and is remembered for any future connections created after the
3285
+ * client is torn down and reconnected.
3286
+ */
3287
+ setRetryTimeoutMs(ms) {
3288
+ this.connOptions.retryTimeoutMs = ms;
3289
+ this.conn?.setRetryTimeoutMs(ms);
3290
+ }
3230
3291
  getTransportMode() {
3231
3292
  return "rpc";
3232
3293
  }
@@ -4196,7 +4257,7 @@ function isLocalhostPattern(hostname) {
4196
4257
  * This file is auto-updated by .github/changeset-version.ts during releases
4197
4258
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
4198
4259
  */
4199
- const SDK_VERSION = "0.9.3";
4260
+ const SDK_VERSION = "0.9.4";
4200
4261
 
4201
4262
  //#endregion
4202
4263
  //#region src/sandbox.ts
@@ -4528,6 +4589,7 @@ var Sandbox = class Sandbox extends Container {
4528
4589
  stub: this,
4529
4590
  port: 3e3,
4530
4591
  logger: this.logger,
4592
+ retryTimeoutMs: this.computeRetryTimeoutMs(),
4531
4593
  onActivity: () => {
4532
4594
  this.renewActivityTimeout();
4533
4595
  },
@@ -7506,4 +7568,4 @@ var Sandbox = class Sandbox extends Container {
7506
7568
 
7507
7569
  //#endregion
7508
7570
  export { DesktopInvalidOptionsError as A, CommandClient as C, BackupNotFoundError as D, BackupExpiredError as E, InvalidBackupConfigError as F, ProcessExitedBeforeReadyError as I, ProcessReadyTimeoutError as L, DesktopProcessCrashedError as M, DesktopStartFailedError as N, BackupRestoreError as O, DesktopUnavailableError as P, RPCTransportError as R, DesktopClient as S, BackupCreateError as T, UtilityClient as _, BucketMountError as a, GitClient as b, MissingCredentialsError as c, parseSSEStream as d, responseToAsyncIterable as f, SandboxClient as g, streamFile as h, proxyTerminal as i, DesktopNotStartedError as j, DesktopInvalidCoordinatesError as k, S3FSMountError as l, collectFile as m, getSandbox as n, BucketUnmountError as o, CodeInterpreter as p, proxyToSandbox as r, InvalidMountConfigError as s, Sandbox as t, asyncIterableToSSEStream as u, ProcessClient as v, BackupClient as w, FileClient as x, PortClient as y, SessionTerminatedError as z };
7509
- //# sourceMappingURL=sandbox-BAuU-2a0.js.map
7571
+ //# sourceMappingURL=sandbox-CdWjEUHl.js.map