@cloudflare/sandbox 0.8.0 → 0.8.2

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.js CHANGED
@@ -737,6 +737,9 @@ var BaseTransport = class {
737
737
  setRetryTimeoutMs(ms) {
738
738
  this.retryTimeoutMs = ms;
739
739
  }
740
+ getRetryTimeoutMs() {
741
+ return this.retryTimeoutMs;
742
+ }
740
743
  /**
741
744
  * Fetch with automatic retry for 503 (container starting)
742
745
  *
@@ -841,6 +844,8 @@ var HttpTransport = class extends BaseTransport {
841
844
  const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
842
845
  const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
843
846
  const DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
847
+ const DEFAULT_IDLE_DISCONNECT_MS = 1e3;
848
+ const MIN_TIME_FOR_CONNECT_RETRY_MS = 15e3;
844
849
  /**
845
850
  * WebSocket transport implementation
846
851
  *
@@ -852,6 +857,7 @@ var WebSocketTransport = class extends BaseTransport {
852
857
  state = "disconnected";
853
858
  pendingRequests = /* @__PURE__ */ new Map();
854
859
  connectPromise = null;
860
+ idleDisconnectTimer = null;
855
861
  boundHandleMessage;
856
862
  boundHandleClose;
857
863
  constructor(config) {
@@ -876,6 +882,7 @@ var WebSocketTransport = class extends BaseTransport {
876
882
  * callers share the same connection attempt.
877
883
  */
878
884
  async connect() {
885
+ this.clearIdleDisconnectTimer();
879
886
  if (this.isConnected()) return;
880
887
  if (this.connectPromise) return this.connectPromise;
881
888
  this.connectPromise = this.doConnect();
@@ -943,6 +950,26 @@ var WebSocketTransport = class extends BaseTransport {
943
950
  if (this.config.stub) await this.connectViaFetch();
944
951
  else await this.connectViaWebSocket();
945
952
  }
953
+ async fetchUpgradeWithRetry(attemptUpgrade) {
954
+ const retryTimeoutMs = this.getRetryTimeoutMs();
955
+ const startTime = Date.now();
956
+ let attempt = 0;
957
+ while (true) {
958
+ const response = await attemptUpgrade();
959
+ if (response.status !== 503) return response;
960
+ const remaining = retryTimeoutMs - (Date.now() - startTime);
961
+ if (remaining <= MIN_TIME_FOR_CONNECT_RETRY_MS) return response;
962
+ const delay = Math.min(3e3 * 2 ** attempt, 3e4);
963
+ this.logger.info("WebSocket container not ready, retrying", {
964
+ status: response.status,
965
+ attempt: attempt + 1,
966
+ delayMs: delay,
967
+ remainingSec: Math.floor(remaining / 1e3)
968
+ });
969
+ await this.sleep(delay);
970
+ attempt++;
971
+ }
972
+ }
946
973
  /**
947
974
  * Connect using fetch-based WebSocket (Cloudflare Workers style)
948
975
  * This is required when running inside a Durable Object.
@@ -952,20 +979,10 @@ var WebSocketTransport = class extends BaseTransport {
952
979
  */
953
980
  async connectViaFetch() {
954
981
  const timeoutMs = this.config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
955
- const controller = new AbortController();
956
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
957
982
  try {
958
983
  const wsPath = new URL(this.config.wsUrl).pathname;
959
984
  const httpUrl = `http://localhost:${this.config.port || 3e3}${wsPath}`;
960
- const request = new Request(httpUrl, {
961
- headers: {
962
- Upgrade: "websocket",
963
- Connection: "Upgrade"
964
- },
965
- signal: controller.signal
966
- });
967
- const response = await this.config.stub.fetch(request);
968
- clearTimeout(timeout);
985
+ const response = await this.fetchUpgradeWithRetry(() => this.fetchUpgradeAttempt(httpUrl, timeoutMs));
969
986
  if (response.status !== 101) throw new Error(`WebSocket upgrade failed: ${response.status} ${response.statusText}`);
970
987
  const ws = response.webSocket;
971
988
  if (!ws) throw new Error("No WebSocket in upgrade response");
@@ -976,12 +993,27 @@ var WebSocketTransport = class extends BaseTransport {
976
993
  this.ws.addEventListener("message", this.boundHandleMessage);
977
994
  this.logger.debug("WebSocket connected via fetch", { url: this.config.wsUrl });
978
995
  } catch (error) {
979
- clearTimeout(timeout);
980
996
  this.state = "error";
981
997
  this.logger.error("WebSocket fetch connection failed", error instanceof Error ? error : new Error(String(error)));
982
998
  throw error;
983
999
  }
984
1000
  }
1001
+ async fetchUpgradeAttempt(httpUrl, timeoutMs) {
1002
+ const controller = new AbortController();
1003
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1004
+ try {
1005
+ const request = new Request(httpUrl, {
1006
+ headers: {
1007
+ Upgrade: "websocket",
1008
+ Connection: "Upgrade"
1009
+ },
1010
+ signal: controller.signal
1011
+ });
1012
+ return await this.config.stub.fetch(request);
1013
+ } finally {
1014
+ clearTimeout(timeout);
1015
+ }
1016
+ }
985
1017
  /**
986
1018
  * Connect using standard WebSocket API (browser/Node style)
987
1019
  */
@@ -1026,6 +1058,7 @@ var WebSocketTransport = class extends BaseTransport {
1026
1058
  */
1027
1059
  async request(method, path$1, body, headers) {
1028
1060
  await this.connect();
1061
+ this.clearIdleDisconnectTimer();
1029
1062
  const id = generateRequestId();
1030
1063
  const request = {
1031
1064
  type: "request",
@@ -1039,12 +1072,14 @@ var WebSocketTransport = class extends BaseTransport {
1039
1072
  const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
1040
1073
  const timeoutId = setTimeout(() => {
1041
1074
  this.pendingRequests.delete(id);
1075
+ this.scheduleIdleDisconnect();
1042
1076
  reject(/* @__PURE__ */ new Error(`Request timeout after ${timeoutMs}ms: ${method} ${path$1}`));
1043
1077
  }, timeoutMs);
1044
1078
  this.pendingRequests.set(id, {
1045
1079
  resolve: (response) => {
1046
1080
  clearTimeout(timeoutId);
1047
1081
  this.pendingRequests.delete(id);
1082
+ this.scheduleIdleDisconnect();
1048
1083
  resolve({
1049
1084
  status: response.status,
1050
1085
  body: response.body
@@ -1053,6 +1088,7 @@ var WebSocketTransport = class extends BaseTransport {
1053
1088
  reject: (error) => {
1054
1089
  clearTimeout(timeoutId);
1055
1090
  this.pendingRequests.delete(id);
1091
+ this.scheduleIdleDisconnect();
1056
1092
  reject(error);
1057
1093
  },
1058
1094
  isStreaming: false,
@@ -1063,6 +1099,7 @@ var WebSocketTransport = class extends BaseTransport {
1063
1099
  } catch (error) {
1064
1100
  clearTimeout(timeoutId);
1065
1101
  this.pendingRequests.delete(id);
1102
+ this.scheduleIdleDisconnect();
1066
1103
  reject(error instanceof Error ? error : new Error(String(error)));
1067
1104
  }
1068
1105
  });
@@ -1083,6 +1120,7 @@ var WebSocketTransport = class extends BaseTransport {
1083
1120
  */
1084
1121
  async requestStream(method, path$1, body, headers) {
1085
1122
  await this.connect();
1123
+ this.clearIdleDisconnectTimer();
1086
1124
  const id = generateRequestId();
1087
1125
  const request = {
1088
1126
  type: "request",
@@ -1099,6 +1137,7 @@ var WebSocketTransport = class extends BaseTransport {
1099
1137
  const createIdleTimeout = () => {
1100
1138
  return setTimeout(() => {
1101
1139
  this.pendingRequests.delete(id);
1140
+ this.scheduleIdleDisconnect();
1102
1141
  const error = /* @__PURE__ */ new Error(`Stream idle timeout after ${idleTimeoutMs}ms: ${method} ${path$1}`);
1103
1142
  if (firstMessageReceived) try {
1104
1143
  streamController?.error(error);
@@ -1126,6 +1165,7 @@ var WebSocketTransport = class extends BaseTransport {
1126
1165
  });
1127
1166
  }
1128
1167
  this.pendingRequests.delete(id);
1168
+ this.scheduleIdleDisconnect();
1129
1169
  }
1130
1170
  });
1131
1171
  this.pendingRequests.set(id, {
@@ -1133,6 +1173,7 @@ var WebSocketTransport = class extends BaseTransport {
1133
1173
  const pending = this.pendingRequests.get(id);
1134
1174
  if (pending?.timeoutId) clearTimeout(pending.timeoutId);
1135
1175
  this.pendingRequests.delete(id);
1176
+ this.scheduleIdleDisconnect();
1136
1177
  if (!firstMessageReceived) {
1137
1178
  firstMessageReceived = true;
1138
1179
  if (response.status >= 400) rejectStream(/* @__PURE__ */ new Error(`Stream error: ${response.status} - ${JSON.stringify(response.body)}`));
@@ -1149,6 +1190,7 @@ var WebSocketTransport = class extends BaseTransport {
1149
1190
  const pending = this.pendingRequests.get(id);
1150
1191
  if (pending?.timeoutId) clearTimeout(pending.timeoutId);
1151
1192
  this.pendingRequests.delete(id);
1193
+ this.scheduleIdleDisconnect();
1152
1194
  if (firstMessageReceived) try {
1153
1195
  streamController?.error(error);
1154
1196
  } catch {}
@@ -1173,6 +1215,7 @@ var WebSocketTransport = class extends BaseTransport {
1173
1215
  });
1174
1216
  if (pending.timeoutId) clearTimeout(pending.timeoutId);
1175
1217
  this.pendingRequests.delete(id);
1218
+ this.scheduleIdleDisconnect();
1176
1219
  }
1177
1220
  pending.bufferedChunks = void 0;
1178
1221
  }
@@ -1186,6 +1229,7 @@ var WebSocketTransport = class extends BaseTransport {
1186
1229
  } catch (error) {
1187
1230
  clearTimeout(timeoutId);
1188
1231
  this.pendingRequests.delete(id);
1232
+ this.scheduleIdleDisconnect();
1189
1233
  rejectStream(error instanceof Error ? error : new Error(String(error)));
1190
1234
  }
1191
1235
  });
@@ -1272,6 +1316,7 @@ var WebSocketTransport = class extends BaseTransport {
1272
1316
  });
1273
1317
  if (pending.timeoutId) clearTimeout(pending.timeoutId);
1274
1318
  this.pendingRequests.delete(chunk.id);
1319
+ this.scheduleIdleDisconnect();
1275
1320
  }
1276
1321
  }
1277
1322
  /**
@@ -1283,6 +1328,7 @@ var WebSocketTransport = class extends BaseTransport {
1283
1328
  const idleTimeoutMs = this.config.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;
1284
1329
  pending.timeoutId = setTimeout(() => {
1285
1330
  this.pendingRequests.delete(id);
1331
+ this.scheduleIdleDisconnect();
1286
1332
  if (pending.streamController) try {
1287
1333
  pending.streamController.error(/* @__PURE__ */ new Error(`Stream idle timeout after ${idleTimeoutMs}ms`));
1288
1334
  } catch {}
@@ -1325,6 +1371,7 @@ var WebSocketTransport = class extends BaseTransport {
1325
1371
  * Cleanup resources
1326
1372
  */
1327
1373
  cleanup() {
1374
+ this.clearIdleDisconnectTimer();
1328
1375
  if (this.ws) {
1329
1376
  this.ws.removeEventListener("close", this.boundHandleClose);
1330
1377
  this.ws.removeEventListener("message", this.boundHandleMessage);
@@ -1336,6 +1383,23 @@ var WebSocketTransport = class extends BaseTransport {
1336
1383
  for (const pending of this.pendingRequests.values()) if (pending.timeoutId) clearTimeout(pending.timeoutId);
1337
1384
  this.pendingRequests.clear();
1338
1385
  }
1386
+ scheduleIdleDisconnect() {
1387
+ if (!this.isConnected() || this.pendingRequests.size > 0) return;
1388
+ this.clearIdleDisconnectTimer();
1389
+ this.idleDisconnectTimer = setTimeout(() => {
1390
+ this.idleDisconnectTimer = null;
1391
+ if (this.pendingRequests.size === 0 && this.isConnected()) {
1392
+ this.logger.debug("Disconnecting idle WebSocket transport");
1393
+ this.cleanup();
1394
+ }
1395
+ }, DEFAULT_IDLE_DISCONNECT_MS);
1396
+ }
1397
+ clearIdleDisconnectTimer() {
1398
+ if (this.idleDisconnectTimer) {
1399
+ clearTimeout(this.idleDisconnectTimer);
1400
+ this.idleDisconnectTimer = null;
1401
+ }
1402
+ }
1339
1403
  };
1340
1404
 
1341
1405
  //#endregion
@@ -3562,7 +3626,7 @@ function buildS3fsSource(bucket, prefix) {
3562
3626
  * This file is auto-updated by .github/changeset-version.ts during releases
3563
3627
  * DO NOT EDIT MANUALLY - Changes will be overwritten on the next version bump
3564
3628
  */
3565
- const SDK_VERSION = "0.8.0";
3629
+ const SDK_VERSION = "0.8.2";
3566
3630
 
3567
3631
  //#endregion
3568
3632
  //#region src/sandbox.ts