@daytona/sdk 0.197.0 → 0.198.0-alpha.1
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/cjs/Daytona.d.ts +17 -1
- package/cjs/Daytona.js +36 -7
- package/cjs/Daytona.js.map +1 -1
- package/cjs/Sandbox.d.ts +71 -4
- package/cjs/Sandbox.js +374 -136
- package/cjs/Sandbox.js.map +1 -1
- package/cjs/utils/EventDispatcher.d.ts +85 -0
- package/cjs/utils/EventDispatcher.js +398 -0
- package/cjs/utils/EventDispatcher.js.map +1 -0
- package/cjs/utils/EventSubscriptionManager.d.ts +14 -0
- package/cjs/utils/EventSubscriptionManager.js +98 -0
- package/cjs/utils/EventSubscriptionManager.js.map +1 -0
- package/esm/Daytona.d.ts +17 -1
- package/esm/Daytona.js +36 -7
- package/esm/Daytona.js.map +1 -1
- package/esm/Sandbox.d.ts +71 -4
- package/esm/Sandbox.js +374 -136
- package/esm/Sandbox.js.map +1 -1
- package/esm/utils/EventDispatcher.d.ts +85 -0
- package/esm/utils/EventDispatcher.js +394 -0
- package/esm/utils/EventDispatcher.js.map +1 -0
- package/esm/utils/EventSubscriptionManager.d.ts +14 -0
- package/esm/utils/EventSubscriptionManager.js +94 -0
- package/esm/utils/EventSubscriptionManager.js.map +1 -0
- package/esm/utils/Import.js +1 -1
- package/package.json +5 -4
package/esm/Sandbox.js
CHANGED
|
@@ -16,6 +16,13 @@ import { CODE_TOOLBOX_LANGUAGE_LABEL } from './Daytona.js';
|
|
|
16
16
|
import { ComputerUse } from './ComputerUse.js';
|
|
17
17
|
import { CodeInterpreter } from './CodeInterpreter.js';
|
|
18
18
|
import { WithInstrumentation } from './utils/otel.decorator.js';
|
|
19
|
+
function withEvents(target, _context) {
|
|
20
|
+
return function (...args) {
|
|
21
|
+
;
|
|
22
|
+
this.ensureSubscribed();
|
|
23
|
+
return target.apply(this, args);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
19
26
|
/**
|
|
20
27
|
* Represents a Daytona Sandbox.
|
|
21
28
|
*
|
|
@@ -48,6 +55,7 @@ import { WithInstrumentation } from './utils/otel.decorator.js';
|
|
|
48
55
|
* @property {number} [autoPauseInterval] - Auto-pause interval in minutes
|
|
49
56
|
* @property {number} [autoArchiveInterval] - Auto-archive interval in minutes
|
|
50
57
|
* @property {number} [autoDeleteInterval] - Auto-delete interval in minutes
|
|
58
|
+
* @property {string} [autoDestroyAt] - When the Sandbox will be automatically destroyed (only set when a TTL is configured)
|
|
51
59
|
* @property {Array<SandboxVolume>} [volumes] - Volumes attached to the Sandbox (not returned by
|
|
52
60
|
* list results; call `refreshData()` on each item to populate)
|
|
53
61
|
* @property {BuildInfo} [buildInfo] - Build information for the Sandbox if it was created from dynamic build
|
|
@@ -76,6 +84,7 @@ let Sandbox = (() => {
|
|
|
76
84
|
let _createLspServer_decorators;
|
|
77
85
|
let _setLabels_decorators;
|
|
78
86
|
let _start_decorators;
|
|
87
|
+
let _recover_decorators;
|
|
79
88
|
let _stop_decorators;
|
|
80
89
|
let __experimental_fork_decorators;
|
|
81
90
|
let __experimental_createSnapshot_decorators;
|
|
@@ -84,14 +93,18 @@ let Sandbox = (() => {
|
|
|
84
93
|
let _waitUntilStarted_decorators;
|
|
85
94
|
let _waitUntilStopped_decorators;
|
|
86
95
|
let _refreshData_decorators;
|
|
96
|
+
let _refreshActivity_decorators;
|
|
87
97
|
let _setAutostopInterval_decorators;
|
|
88
98
|
let _setAutoPauseInterval_decorators;
|
|
99
|
+
let _setTtl_decorators;
|
|
89
100
|
let _setAutoArchiveInterval_decorators;
|
|
90
101
|
let _setAutoDeleteInterval_decorators;
|
|
91
102
|
let _updateNetworkSettings_decorators;
|
|
92
103
|
let _updateSecrets_decorators;
|
|
93
104
|
let _updateEnv_decorators;
|
|
94
105
|
let _getPreviewLink_decorators;
|
|
106
|
+
let _getSignedPreviewUrl_decorators;
|
|
107
|
+
let _expireSignedPreviewUrl_decorators;
|
|
95
108
|
let _archive_decorators;
|
|
96
109
|
let _resize_decorators;
|
|
97
110
|
let _waitForResizeComplete_decorators;
|
|
@@ -101,36 +114,41 @@ let Sandbox = (() => {
|
|
|
101
114
|
return class Sandbox {
|
|
102
115
|
static {
|
|
103
116
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
104
|
-
_getUserHomeDir_decorators = [WithInstrumentation()];
|
|
117
|
+
_getUserHomeDir_decorators = [WithInstrumentation(), withEvents];
|
|
105
118
|
_getMetricsLatest_decorators = [WithInstrumentation()];
|
|
106
119
|
_getMetrics_decorators = [WithInstrumentation()];
|
|
107
|
-
_getUserRootDir_decorators = [WithInstrumentation()];
|
|
108
|
-
_getWorkDir_decorators = [WithInstrumentation()];
|
|
109
|
-
_createLspServer_decorators = [WithInstrumentation()];
|
|
110
|
-
_setLabels_decorators = [WithInstrumentation()];
|
|
111
|
-
_start_decorators = [WithInstrumentation()];
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
_getUserRootDir_decorators = [WithInstrumentation(), withEvents];
|
|
121
|
+
_getWorkDir_decorators = [WithInstrumentation(), withEvents];
|
|
122
|
+
_createLspServer_decorators = [WithInstrumentation(), withEvents];
|
|
123
|
+
_setLabels_decorators = [WithInstrumentation(), withEvents];
|
|
124
|
+
_start_decorators = [WithInstrumentation(), withEvents];
|
|
125
|
+
_recover_decorators = [withEvents];
|
|
126
|
+
_stop_decorators = [WithInstrumentation(), withEvents];
|
|
127
|
+
__experimental_fork_decorators = [WithInstrumentation(), withEvents];
|
|
128
|
+
__experimental_createSnapshot_decorators = [WithInstrumentation(), withEvents];
|
|
129
|
+
_pause_decorators = [WithInstrumentation(), withEvents];
|
|
130
|
+
_delete_decorators = [WithInstrumentation(), withEvents];
|
|
131
|
+
_waitUntilStarted_decorators = [WithInstrumentation(), withEvents];
|
|
132
|
+
_waitUntilStopped_decorators = [WithInstrumentation(), withEvents];
|
|
133
|
+
_refreshData_decorators = [WithInstrumentation(), withEvents];
|
|
134
|
+
_refreshActivity_decorators = [withEvents];
|
|
135
|
+
_setAutostopInterval_decorators = [WithInstrumentation(), withEvents];
|
|
121
136
|
_setAutoPauseInterval_decorators = [WithInstrumentation()];
|
|
122
|
-
|
|
123
|
-
|
|
137
|
+
_setTtl_decorators = [WithInstrumentation()];
|
|
138
|
+
_setAutoArchiveInterval_decorators = [WithInstrumentation(), withEvents];
|
|
139
|
+
_setAutoDeleteInterval_decorators = [WithInstrumentation(), withEvents];
|
|
124
140
|
_updateNetworkSettings_decorators = [WithInstrumentation()];
|
|
125
141
|
_updateSecrets_decorators = [WithInstrumentation()];
|
|
126
142
|
_updateEnv_decorators = [WithInstrumentation()];
|
|
127
|
-
_getPreviewLink_decorators = [WithInstrumentation()];
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
_getPreviewLink_decorators = [WithInstrumentation(), withEvents];
|
|
144
|
+
_getSignedPreviewUrl_decorators = [withEvents];
|
|
145
|
+
_expireSignedPreviewUrl_decorators = [withEvents];
|
|
146
|
+
_archive_decorators = [WithInstrumentation(), withEvents];
|
|
147
|
+
_resize_decorators = [WithInstrumentation(), withEvents];
|
|
148
|
+
_waitForResizeComplete_decorators = [WithInstrumentation(), withEvents];
|
|
149
|
+
_createSshAccess_decorators = [WithInstrumentation(), withEvents];
|
|
150
|
+
_revokeSshAccess_decorators = [WithInstrumentation(), withEvents];
|
|
151
|
+
_validateSshAccess_decorators = [WithInstrumentation(), withEvents];
|
|
134
152
|
__esDecorate(this, null, _getUserHomeDir_decorators, { kind: "method", name: "getUserHomeDir", static: false, private: false, access: { has: obj => "getUserHomeDir" in obj, get: obj => obj.getUserHomeDir }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
135
153
|
__esDecorate(this, null, _getMetricsLatest_decorators, { kind: "method", name: "getMetricsLatest", static: false, private: false, access: { has: obj => "getMetricsLatest" in obj, get: obj => obj.getMetricsLatest }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
136
154
|
__esDecorate(this, null, _getMetrics_decorators, { kind: "method", name: "getMetrics", static: false, private: false, access: { has: obj => "getMetrics" in obj, get: obj => obj.getMetrics }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -139,6 +157,7 @@ let Sandbox = (() => {
|
|
|
139
157
|
__esDecorate(this, null, _createLspServer_decorators, { kind: "method", name: "createLspServer", static: false, private: false, access: { has: obj => "createLspServer" in obj, get: obj => obj.createLspServer }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
140
158
|
__esDecorate(this, null, _setLabels_decorators, { kind: "method", name: "setLabels", static: false, private: false, access: { has: obj => "setLabels" in obj, get: obj => obj.setLabels }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
141
159
|
__esDecorate(this, null, _start_decorators, { kind: "method", name: "start", static: false, private: false, access: { has: obj => "start" in obj, get: obj => obj.start }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
160
|
+
__esDecorate(this, null, _recover_decorators, { kind: "method", name: "recover", static: false, private: false, access: { has: obj => "recover" in obj, get: obj => obj.recover }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
142
161
|
__esDecorate(this, null, _stop_decorators, { kind: "method", name: "stop", static: false, private: false, access: { has: obj => "stop" in obj, get: obj => obj.stop }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
143
162
|
__esDecorate(this, null, __experimental_fork_decorators, { kind: "method", name: "_experimental_fork", static: false, private: false, access: { has: obj => "_experimental_fork" in obj, get: obj => obj._experimental_fork }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
144
163
|
__esDecorate(this, null, __experimental_createSnapshot_decorators, { kind: "method", name: "_experimental_createSnapshot", static: false, private: false, access: { has: obj => "_experimental_createSnapshot" in obj, get: obj => obj._experimental_createSnapshot }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -147,14 +166,18 @@ let Sandbox = (() => {
|
|
|
147
166
|
__esDecorate(this, null, _waitUntilStarted_decorators, { kind: "method", name: "waitUntilStarted", static: false, private: false, access: { has: obj => "waitUntilStarted" in obj, get: obj => obj.waitUntilStarted }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
148
167
|
__esDecorate(this, null, _waitUntilStopped_decorators, { kind: "method", name: "waitUntilStopped", static: false, private: false, access: { has: obj => "waitUntilStopped" in obj, get: obj => obj.waitUntilStopped }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
149
168
|
__esDecorate(this, null, _refreshData_decorators, { kind: "method", name: "refreshData", static: false, private: false, access: { has: obj => "refreshData" in obj, get: obj => obj.refreshData }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
169
|
+
__esDecorate(this, null, _refreshActivity_decorators, { kind: "method", name: "refreshActivity", static: false, private: false, access: { has: obj => "refreshActivity" in obj, get: obj => obj.refreshActivity }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
150
170
|
__esDecorate(this, null, _setAutostopInterval_decorators, { kind: "method", name: "setAutostopInterval", static: false, private: false, access: { has: obj => "setAutostopInterval" in obj, get: obj => obj.setAutostopInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
151
171
|
__esDecorate(this, null, _setAutoPauseInterval_decorators, { kind: "method", name: "setAutoPauseInterval", static: false, private: false, access: { has: obj => "setAutoPauseInterval" in obj, get: obj => obj.setAutoPauseInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
172
|
+
__esDecorate(this, null, _setTtl_decorators, { kind: "method", name: "setTtl", static: false, private: false, access: { has: obj => "setTtl" in obj, get: obj => obj.setTtl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
152
173
|
__esDecorate(this, null, _setAutoArchiveInterval_decorators, { kind: "method", name: "setAutoArchiveInterval", static: false, private: false, access: { has: obj => "setAutoArchiveInterval" in obj, get: obj => obj.setAutoArchiveInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
153
174
|
__esDecorate(this, null, _setAutoDeleteInterval_decorators, { kind: "method", name: "setAutoDeleteInterval", static: false, private: false, access: { has: obj => "setAutoDeleteInterval" in obj, get: obj => obj.setAutoDeleteInterval }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
154
175
|
__esDecorate(this, null, _updateNetworkSettings_decorators, { kind: "method", name: "updateNetworkSettings", static: false, private: false, access: { has: obj => "updateNetworkSettings" in obj, get: obj => obj.updateNetworkSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
155
176
|
__esDecorate(this, null, _updateSecrets_decorators, { kind: "method", name: "updateSecrets", static: false, private: false, access: { has: obj => "updateSecrets" in obj, get: obj => obj.updateSecrets }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
156
177
|
__esDecorate(this, null, _updateEnv_decorators, { kind: "method", name: "updateEnv", static: false, private: false, access: { has: obj => "updateEnv" in obj, get: obj => obj.updateEnv }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
157
178
|
__esDecorate(this, null, _getPreviewLink_decorators, { kind: "method", name: "getPreviewLink", static: false, private: false, access: { has: obj => "getPreviewLink" in obj, get: obj => obj.getPreviewLink }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
179
|
+
__esDecorate(this, null, _getSignedPreviewUrl_decorators, { kind: "method", name: "getSignedPreviewUrl", static: false, private: false, access: { has: obj => "getSignedPreviewUrl" in obj, get: obj => obj.getSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
180
|
+
__esDecorate(this, null, _expireSignedPreviewUrl_decorators, { kind: "method", name: "expireSignedPreviewUrl", static: false, private: false, access: { has: obj => "expireSignedPreviewUrl" in obj, get: obj => obj.expireSignedPreviewUrl }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
158
181
|
__esDecorate(this, null, _archive_decorators, { kind: "method", name: "archive", static: false, private: false, access: { has: obj => "archive" in obj, get: obj => obj.archive }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
159
182
|
__esDecorate(this, null, _resize_decorators, { kind: "method", name: "resize", static: false, private: false, access: { has: obj => "resize" in obj, get: obj => obj.resize }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
160
183
|
__esDecorate(this, null, _waitForResizeComplete_decorators, { kind: "method", name: "waitForResizeComplete", static: false, private: false, access: { has: obj => "waitForResizeComplete" in obj, get: obj => obj.waitForResizeComplete }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
@@ -167,6 +190,7 @@ let Sandbox = (() => {
|
|
|
167
190
|
axiosInstance;
|
|
168
191
|
sandboxApi;
|
|
169
192
|
getAnalyticsApiUrl;
|
|
193
|
+
subscriptionManager;
|
|
170
194
|
fs;
|
|
171
195
|
git;
|
|
172
196
|
process;
|
|
@@ -194,6 +218,7 @@ let Sandbox = (() => {
|
|
|
194
218
|
autoPauseInterval;
|
|
195
219
|
autoArchiveInterval;
|
|
196
220
|
autoDeleteInterval;
|
|
221
|
+
autoDestroyAt;
|
|
197
222
|
volumes;
|
|
198
223
|
buildInfo;
|
|
199
224
|
createdAt;
|
|
@@ -207,25 +232,27 @@ let Sandbox = (() => {
|
|
|
207
232
|
infoApi;
|
|
208
233
|
serverApi;
|
|
209
234
|
systemApi;
|
|
235
|
+
stateWaiters = [];
|
|
236
|
+
stateWaiterErrorMessageFns = new WeakMap();
|
|
237
|
+
subId;
|
|
210
238
|
/**
|
|
211
|
-
* Creates a new Sandbox instance
|
|
239
|
+
* Creates a new Sandbox instance.
|
|
240
|
+
*
|
|
241
|
+
* Internal: obtain sandboxes via {@link Daytona.create}, {@link Daytona.get}, or
|
|
242
|
+
* {@link Daytona.list} rather than constructing directly.
|
|
212
243
|
*
|
|
213
244
|
* @param {SandboxDto} sandboxDto - The API Sandbox instance
|
|
245
|
+
* @param {SandboxApi} sandboxApi - API client for Sandbox operations
|
|
246
|
+
* @param {InfoApi} infoApi - API client for info operations
|
|
247
|
+
* @param {EventSubscriptionManager} subscriptionManager - Event subscription manager for real-time updates
|
|
214
248
|
*/
|
|
215
|
-
constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi, getAnalyticsApiUrl) {
|
|
249
|
+
constructor(sandboxDto, clientConfig, axiosInstance, sandboxApi, getAnalyticsApiUrl, subscriptionManager) {
|
|
216
250
|
this.clientConfig = clientConfig;
|
|
217
251
|
this.axiosInstance = axiosInstance;
|
|
218
252
|
this.sandboxApi = sandboxApi;
|
|
219
253
|
this.getAnalyticsApiUrl = getAnalyticsApiUrl;
|
|
254
|
+
this.subscriptionManager = subscriptionManager;
|
|
220
255
|
this.processSandboxDto(sandboxDto);
|
|
221
|
-
// Set the toolbox base URL
|
|
222
|
-
let baseUrl = this.toolboxProxyUrl;
|
|
223
|
-
if (!baseUrl.endsWith('/')) {
|
|
224
|
-
baseUrl += '/';
|
|
225
|
-
}
|
|
226
|
-
this.axiosInstance.defaults.baseURL = baseUrl + this.id;
|
|
227
|
-
this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
|
|
228
|
-
// Initialize Services
|
|
229
256
|
const getPreviewToken = async () => (await this.getPreviewLink(1)).token;
|
|
230
257
|
this.fs = new FileSystem(this.clientConfig, new FileSystemApi(this.clientConfig, '', this.axiosInstance));
|
|
231
258
|
this.git = new Git(new GitApi(this.clientConfig, '', this.axiosInstance));
|
|
@@ -236,6 +263,7 @@ let Sandbox = (() => {
|
|
|
236
263
|
this.infoApi = new InfoApi(this.clientConfig, '', this.axiosInstance);
|
|
237
264
|
this.serverApi = new ServerApi(this.clientConfig, '', this.axiosInstance);
|
|
238
265
|
this.systemApi = new SystemApi(this.clientConfig, '', this.axiosInstance);
|
|
266
|
+
this.subscribeToEvents();
|
|
239
267
|
}
|
|
240
268
|
/**
|
|
241
269
|
* Gets the user's home directory path for the logged in user inside the Sandbox.
|
|
@@ -454,9 +482,16 @@ let Sandbox = (() => {
|
|
|
454
482
|
timeout: timeout * 1000,
|
|
455
483
|
});
|
|
456
484
|
const sandboxDto = response.data;
|
|
457
|
-
const
|
|
485
|
+
const sandboxWithProxyUrl = sandboxDto.toolboxProxyUrl
|
|
486
|
+
? sandboxDto
|
|
487
|
+
: {
|
|
488
|
+
...sandboxDto,
|
|
489
|
+
toolboxProxyUrl: (await this.sandboxApi.getToolboxProxyUrl(sandboxDto.id)).data.url,
|
|
490
|
+
};
|
|
491
|
+
const forkedSandbox = new Sandbox(sandboxWithProxyUrl, structuredClone(this.clientConfig), Daytona.createAxiosInstance(), this.sandboxApi, this.getAnalyticsApiUrl, this.subscriptionManager);
|
|
458
492
|
const timeElapsed = Date.now() - startTime;
|
|
459
|
-
|
|
493
|
+
const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
|
|
494
|
+
await forkedSandbox.waitUntilStarted(remainingTimeout);
|
|
460
495
|
return forkedSandbox;
|
|
461
496
|
}
|
|
462
497
|
/**
|
|
@@ -484,34 +519,19 @@ let Sandbox = (() => {
|
|
|
484
519
|
}
|
|
485
520
|
const startTime = Date.now();
|
|
486
521
|
const req = { name };
|
|
487
|
-
await this.sandboxApi.createSandboxSnapshot(this.id, req, undefined, {
|
|
522
|
+
const response = await this.sandboxApi.createSandboxSnapshot(this.id, req, undefined, {
|
|
488
523
|
timeout: timeout * 1000,
|
|
489
524
|
});
|
|
490
|
-
|
|
525
|
+
this.processSandboxDto(response.data);
|
|
491
526
|
const timeElapsed = Date.now() - startTime;
|
|
492
527
|
const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
|
|
493
528
|
await this.waitForSnapshotComplete(remainingTimeout);
|
|
494
529
|
}
|
|
495
530
|
async waitForSnapshotComplete(timeout) {
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
501
|
-
if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
|
|
502
|
-
throw new DaytonaError(`Sandbox ${this.id} snapshot failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
503
|
-
}
|
|
504
|
-
if (this.state !== SandboxState.SNAPSHOTTING) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
508
|
-
throw new DaytonaTimeoutError('Sandbox snapshot did not complete within the timeout period');
|
|
509
|
-
}
|
|
510
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
511
|
-
if (Date.now() - startTime > 5000) {
|
|
512
|
-
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
531
|
+
const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
|
|
532
|
+
const excludeStates = new Set([SandboxState.SNAPSHOTTING, ...errorStates]);
|
|
533
|
+
const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
|
|
534
|
+
return this.waitForState(targetStates, errorStates, timeout, 'Sandbox snapshot did not complete within the timeout period', (state) => `Sandbox ${this.id} snapshot failed with state: ${state}, error reason: ${this.errorReason}`);
|
|
515
535
|
}
|
|
516
536
|
/**
|
|
517
537
|
* Pauses the Sandbox, freezing all running processes.
|
|
@@ -542,31 +562,47 @@ let Sandbox = (() => {
|
|
|
542
562
|
await this.refreshData();
|
|
543
563
|
const timeElapsed = Date.now() - startTime;
|
|
544
564
|
const remainingTimeout = timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout;
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
await this.refreshData();
|
|
552
|
-
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
553
|
-
if (this.state === SandboxState.ERROR) {
|
|
554
|
-
throw new DaytonaError(`Sandbox ${this.id} pause failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
555
|
-
}
|
|
556
|
-
if (timeout > 0 && (Date.now() - startTime) / 1000 >= timeout) {
|
|
557
|
-
throw new DaytonaError(`Sandbox ${this.id} failed to pause within ${timeout} seconds`);
|
|
558
|
-
}
|
|
559
|
-
await new Promise((resolve) => globalThis.setTimeout(resolve, checkInterval));
|
|
560
|
-
checkInterval = Math.min(checkInterval * 1.5, 1000);
|
|
561
|
-
}
|
|
565
|
+
// Main's contract: pause completes when the sandbox has *left* PAUSING
|
|
566
|
+
// (paused, stopped, archived, ...), not only on exactly PAUSED.
|
|
567
|
+
const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
|
|
568
|
+
const excludeStates = new Set([SandboxState.PAUSING, ...errorStates]);
|
|
569
|
+
const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
|
|
570
|
+
await this.waitForState(targetStates, errorStates, remainingTimeout, 'Sandbox failed to become paused within the timeout period', (state) => `Sandbox ${this.id} pause failed with state: ${state}, error reason: ${this.errorReason}`);
|
|
562
571
|
}
|
|
563
572
|
/**
|
|
564
573
|
* Deletes the Sandbox.
|
|
574
|
+
*
|
|
575
|
+
* By default this returns as soon as the deletion request is accepted (matching
|
|
576
|
+
* historical behavior). Pass `wait = true` to block until the Sandbox reaches
|
|
577
|
+
* the 'destroyed' state.
|
|
578
|
+
*
|
|
579
|
+
* @param {number} [timeout] - Timeout in seconds for the request — and, when
|
|
580
|
+
* `wait` is true, for reaching 'destroyed'. 0 means no timeout.
|
|
581
|
+
* Defaults to 60-second timeout.
|
|
582
|
+
* @param {boolean} [wait] - If true, wait until the Sandbox is destroyed. Defaults to false.
|
|
565
583
|
* @returns {Promise<void>}
|
|
566
584
|
*/
|
|
567
|
-
async delete(timeout = 60) {
|
|
568
|
-
|
|
569
|
-
|
|
585
|
+
async delete(timeout = 60, wait = false) {
|
|
586
|
+
if (timeout < 0) {
|
|
587
|
+
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
588
|
+
}
|
|
589
|
+
const startTime = Date.now();
|
|
590
|
+
const response = await this.sandboxApi.deleteSandbox(this.id, undefined, { timeout: timeout * 1000 });
|
|
591
|
+
if (response.data) {
|
|
592
|
+
this.processSandboxDto(response.data);
|
|
593
|
+
}
|
|
594
|
+
try {
|
|
595
|
+
if (wait && this.state !== SandboxState.DESTROYED) {
|
|
596
|
+
const timeElapsed = Date.now() - startTime;
|
|
597
|
+
await this.waitForState([SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout ? Math.max(0.001, timeout - timeElapsed / 1000) : timeout, 'Sandbox failed to be destroyed within the timeout period', (state) => `Sandbox ${this.id} failed to delete with state: ${state}, error reason: ${this.errorReason}`, true);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
if (this.subId) {
|
|
602
|
+
this.subscriptionManager.unsubscribe(this.subId);
|
|
603
|
+
this.subId = undefined;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
570
606
|
}
|
|
571
607
|
/**
|
|
572
608
|
* Waits for the Sandbox to reach the 'started' state.
|
|
@@ -583,26 +619,10 @@ let Sandbox = (() => {
|
|
|
583
619
|
if (timeout < 0) {
|
|
584
620
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
585
621
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
while (this.state !== 'started') {
|
|
589
|
-
await this.refreshData();
|
|
590
|
-
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
591
|
-
if (this.state === 'started') {
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
if (this.state === 'error') {
|
|
595
|
-
const errMsg = `Sandbox ${this.id} failed to start with status: ${this.state}, error reason: ${this.errorReason}`;
|
|
596
|
-
throw new DaytonaError(errMsg);
|
|
597
|
-
}
|
|
598
|
-
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
599
|
-
throw new DaytonaTimeoutError('Sandbox failed to become ready within the timeout period');
|
|
600
|
-
}
|
|
601
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
602
|
-
if (Date.now() - startTime > 5000) {
|
|
603
|
-
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
604
|
-
}
|
|
622
|
+
if (this.state === SandboxState.STARTED) {
|
|
623
|
+
return;
|
|
605
624
|
}
|
|
625
|
+
return this.waitForState([SandboxState.STARTED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become ready within the timeout period', (state) => `Sandbox ${this.id} failed to start with status: ${state}, error reason: ${this.errorReason}`);
|
|
606
626
|
}
|
|
607
627
|
/**
|
|
608
628
|
* Wait for Sandbox to reach 'stopped' state.
|
|
@@ -619,27 +639,11 @@ let Sandbox = (() => {
|
|
|
619
639
|
if (timeout < 0) {
|
|
620
640
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
621
641
|
}
|
|
622
|
-
let checkInterval = 100;
|
|
623
|
-
const startTime = Date.now();
|
|
624
642
|
// Treat destroyed as stopped to cover ephemeral sandboxes that are automatically deleted after stopping
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
628
|
-
if (this.state === 'stopped' || this.state === 'destroyed') {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (this.state === 'error') {
|
|
632
|
-
const errMsg = `Sandbox failed to stop with status: ${this.state}, error reason: ${this.errorReason}`;
|
|
633
|
-
throw new DaytonaError(errMsg);
|
|
634
|
-
}
|
|
635
|
-
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
636
|
-
throw new DaytonaTimeoutError('Sandbox failed to become stopped within the timeout period');
|
|
637
|
-
}
|
|
638
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
639
|
-
if (Date.now() - startTime > 5000) {
|
|
640
|
-
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
641
|
-
}
|
|
643
|
+
if (this.state === SandboxState.STOPPED || this.state === SandboxState.DESTROYED) {
|
|
644
|
+
return;
|
|
642
645
|
}
|
|
646
|
+
return this.waitForState([SandboxState.STOPPED, SandboxState.DESTROYED], [SandboxState.ERROR, SandboxState.BUILD_FAILED], timeout, 'Sandbox failed to become stopped within the timeout period', (state) => `Sandbox failed to stop with status: ${state}, error reason: ${this.errorReason}`, true);
|
|
643
647
|
}
|
|
644
648
|
/**
|
|
645
649
|
* Refreshes the Sandbox data from the API.
|
|
@@ -726,6 +730,30 @@ let Sandbox = (() => {
|
|
|
726
730
|
await this.sandboxApi.setAutoPauseInterval(this.id, interval);
|
|
727
731
|
this.autoPauseInterval = interval;
|
|
728
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Set the TTL (maximum time to live) for the Sandbox.
|
|
735
|
+
*
|
|
736
|
+
* The Sandbox will be destroyed once the TTL elapses, counted as wall-clock time regardless of the
|
|
737
|
+
* Sandbox state - even if it is stopped, paused, or archived. Calling this method re-anchors the
|
|
738
|
+
* deadline from the current time. Call `refreshData()` afterwards to read the updated `autoDestroyAt`.
|
|
739
|
+
*
|
|
740
|
+
* @param {number} ttlMinutes - Number of minutes from now after which the Sandbox will be destroyed.
|
|
741
|
+
* Set to 0 to disable the TTL.
|
|
742
|
+
* @returns {Promise<void>}
|
|
743
|
+
* @throws {DaytonaError} - `DaytonaError` - If ttlMinutes is not a non-negative integer
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* // Destroy the Sandbox 1 hour from now
|
|
747
|
+
* await sandbox.setTtl(60);
|
|
748
|
+
* // Or disable the TTL
|
|
749
|
+
* await sandbox.setTtl(0);
|
|
750
|
+
*/
|
|
751
|
+
async setTtl(ttlMinutes) {
|
|
752
|
+
if (!Number.isInteger(ttlMinutes) || ttlMinutes < 0) {
|
|
753
|
+
throw new DaytonaValidationError('ttlMinutes must be a non-negative integer');
|
|
754
|
+
}
|
|
755
|
+
await this.sandboxApi.setTtl(this.id, ttlMinutes);
|
|
756
|
+
}
|
|
729
757
|
/**
|
|
730
758
|
* Set the auto-archive interval for the Sandbox.
|
|
731
759
|
*
|
|
@@ -948,25 +976,13 @@ let Sandbox = (() => {
|
|
|
948
976
|
if (timeout < 0) {
|
|
949
977
|
throw new DaytonaValidationError('Timeout must be a non-negative number');
|
|
950
978
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
while (this.state === SandboxState.RESIZING) {
|
|
954
|
-
await this.refreshData();
|
|
955
|
-
// @ts-expect-error this.refreshData() can modify this.state so this check is fine
|
|
956
|
-
if (this.state === SandboxState.ERROR || this.state === SandboxState.BUILD_FAILED) {
|
|
957
|
-
throw new DaytonaError(`Sandbox ${this.id} resize failed with state: ${this.state}, error reason: ${this.errorReason}`);
|
|
958
|
-
}
|
|
959
|
-
if (this.state !== SandboxState.RESIZING) {
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
if (timeout !== 0 && Date.now() - startTime > timeout * 1000) {
|
|
963
|
-
throw new DaytonaTimeoutError('Sandbox resize did not complete within the timeout period');
|
|
964
|
-
}
|
|
965
|
-
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
966
|
-
if (Date.now() - startTime > 5000) {
|
|
967
|
-
checkInterval = Math.min(checkInterval * 1.1, 1000);
|
|
968
|
-
}
|
|
979
|
+
if (this.state !== SandboxState.RESIZING) {
|
|
980
|
+
return;
|
|
969
981
|
}
|
|
982
|
+
const errorStates = [SandboxState.ERROR, SandboxState.BUILD_FAILED];
|
|
983
|
+
const excludeStates = new Set([SandboxState.RESIZING, ...errorStates]);
|
|
984
|
+
const targetStates = Object.values(SandboxState).filter((s) => !excludeStates.has(s));
|
|
985
|
+
return this.waitForState(targetStates, errorStates, timeout, 'Sandbox resize did not complete within the timeout period', (state) => `Sandbox ${this.id} resize failed with state: ${state}, error reason: ${this.errorReason}`);
|
|
970
986
|
}
|
|
971
987
|
/**
|
|
972
988
|
* Creates an SSH access token for the sandbox.
|
|
@@ -995,6 +1011,172 @@ let Sandbox = (() => {
|
|
|
995
1011
|
async validateSshAccess(token) {
|
|
996
1012
|
return (await this.sandboxApi.validateSshAccess(token)).data;
|
|
997
1013
|
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Subscribes to real-time events for this sandbox.
|
|
1016
|
+
* Auto-updates sandbox metadata on every event.
|
|
1017
|
+
*/
|
|
1018
|
+
subscribeToEvents() {
|
|
1019
|
+
if (this.subId) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
this.subId = this.subscriptionManager.subscribe(this.id, this.handleEvent.bind(this), [
|
|
1023
|
+
'sandbox.state.updated',
|
|
1024
|
+
'sandbox.created',
|
|
1025
|
+
]);
|
|
1026
|
+
}
|
|
1027
|
+
ensureSubscribed() {
|
|
1028
|
+
if (this.subId) {
|
|
1029
|
+
if (this.subscriptionManager.refresh(this.subId)) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
this.subId = undefined;
|
|
1033
|
+
}
|
|
1034
|
+
this.subscribeToEvents();
|
|
1035
|
+
}
|
|
1036
|
+
handleEvent(eventName, data) {
|
|
1037
|
+
if (!data || typeof data !== 'object')
|
|
1038
|
+
return;
|
|
1039
|
+
const raw = data.sandbox ?? data;
|
|
1040
|
+
if (!raw || typeof raw !== 'object')
|
|
1041
|
+
return;
|
|
1042
|
+
if (eventName === 'sandbox.created') {
|
|
1043
|
+
this.processSandboxDto(raw);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const newState = raw.state ?? data.newState;
|
|
1047
|
+
if (newState) {
|
|
1048
|
+
this.applyState(newState);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Waits for the sandbox to reach one of the target states.
|
|
1053
|
+
* Throws on error states or timeout.
|
|
1054
|
+
*
|
|
1055
|
+
* @param targetStates - States that indicate success.
|
|
1056
|
+
* @param errorStates - States that indicate failure.
|
|
1057
|
+
* @param timeout - Maximum time to wait in seconds. 0 means no timeout.
|
|
1058
|
+
* @param timeoutMessage - Error message when timeout is reached.
|
|
1059
|
+
* @param errorMessageFn - Function that produces an error message from the current state.
|
|
1060
|
+
* @param safeRefresh - If true, use refreshDataSafe() for polling (for delete operations where 404 is expected).
|
|
1061
|
+
*/
|
|
1062
|
+
waitForState(targetStates, errorStates, timeout, timeoutMessage, errorMessageFn, safeRefresh = false) {
|
|
1063
|
+
this.ensureSubscribed();
|
|
1064
|
+
return new Promise((resolve, reject) => {
|
|
1065
|
+
let timeoutTimer = null;
|
|
1066
|
+
let pollTimer = null;
|
|
1067
|
+
let settled = false;
|
|
1068
|
+
const waiter = {
|
|
1069
|
+
targetStates: new Set(targetStates),
|
|
1070
|
+
errorStates: new Set(errorStates),
|
|
1071
|
+
resolve: (_) => {
|
|
1072
|
+
if (settled)
|
|
1073
|
+
return;
|
|
1074
|
+
cleanup();
|
|
1075
|
+
resolve();
|
|
1076
|
+
},
|
|
1077
|
+
reject: (err) => {
|
|
1078
|
+
if (settled)
|
|
1079
|
+
return;
|
|
1080
|
+
cleanup();
|
|
1081
|
+
reject(err);
|
|
1082
|
+
},
|
|
1083
|
+
};
|
|
1084
|
+
this.stateWaiters.push(waiter);
|
|
1085
|
+
this.stateWaiterErrorMessageFns.set(waiter, errorMessageFn);
|
|
1086
|
+
const cleanup = () => {
|
|
1087
|
+
if (settled)
|
|
1088
|
+
return;
|
|
1089
|
+
settled = true;
|
|
1090
|
+
if (timeoutTimer)
|
|
1091
|
+
clearTimeout(timeoutTimer);
|
|
1092
|
+
if (pollTimer)
|
|
1093
|
+
clearTimeout(pollTimer);
|
|
1094
|
+
this.removeStateWaiter(waiter);
|
|
1095
|
+
};
|
|
1096
|
+
// Fast-path only on cached *target* states (parity with main's pre-check).
|
|
1097
|
+
// Cached error states are deliberately NOT evaluated here — main always
|
|
1098
|
+
// refreshed before failing, so a stale ERROR must survive one refresh.
|
|
1099
|
+
if (this.state && waiter.targetStates.has(this.state)) {
|
|
1100
|
+
waiter.resolve(this.state);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
if (timeout !== 0) {
|
|
1104
|
+
timeoutTimer = setTimeout(() => {
|
|
1105
|
+
void (async () => {
|
|
1106
|
+
if (settled)
|
|
1107
|
+
return;
|
|
1108
|
+
// Parity with main: complete one final refresh-then-evaluate before
|
|
1109
|
+
// rejecting, so a clamped/short timeout still observes the latest state.
|
|
1110
|
+
let refreshed = false;
|
|
1111
|
+
try {
|
|
1112
|
+
if (safeRefresh) {
|
|
1113
|
+
refreshed = await this.refreshDataSafe();
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
await this.refreshData();
|
|
1117
|
+
refreshed = true;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
catch {
|
|
1121
|
+
// fall through to the timeout rejection below
|
|
1122
|
+
}
|
|
1123
|
+
if (!settled) {
|
|
1124
|
+
// Explicit evaluation: applyState() no-ops on unchanged state, so a
|
|
1125
|
+
// persistent error state would otherwise time out generically here.
|
|
1126
|
+
// Only evaluate when the refresh succeeded — a failed refresh leaves
|
|
1127
|
+
// the cached state stale and must not be treated as authoritative.
|
|
1128
|
+
if (refreshed && this.checkStateWaiter(waiter, this.state)) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
cleanup();
|
|
1132
|
+
reject(new DaytonaTimeoutError(timeoutMessage));
|
|
1133
|
+
}
|
|
1134
|
+
})();
|
|
1135
|
+
}, timeout * 1000);
|
|
1136
|
+
}
|
|
1137
|
+
// Poll as a safety net for missed state changes. With an active event subscription
|
|
1138
|
+
// a sparse 1s cadence suffices; without events (polling mode) replicate main's
|
|
1139
|
+
// cadence exactly: 100ms steady for the first 5s, then exponential backoff capped at 1s.
|
|
1140
|
+
const streaming = !!this.subId;
|
|
1141
|
+
const pollStart = Date.now();
|
|
1142
|
+
let pollInterval = streaming ? 1000 : 100;
|
|
1143
|
+
const doPoll = async () => {
|
|
1144
|
+
if (settled)
|
|
1145
|
+
return;
|
|
1146
|
+
let refreshed = false;
|
|
1147
|
+
try {
|
|
1148
|
+
if (safeRefresh) {
|
|
1149
|
+
refreshed = await this.refreshDataSafe();
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
await this.refreshData();
|
|
1153
|
+
refreshed = true;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
waiter.reject(error instanceof Error ? error : new DaytonaError(String(error)));
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (!settled) {
|
|
1161
|
+
// Evaluate the refreshed state explicitly: applyState() no-ops when the
|
|
1162
|
+
// state is unchanged, so a persistent error state would otherwise never
|
|
1163
|
+
// reject the waiter. Only evaluate when the refresh succeeded — a failed
|
|
1164
|
+
// safe refresh leaves the cached state stale, so keep polling instead.
|
|
1165
|
+
if (refreshed && this.checkStateWaiter(waiter, this.state)) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (!streaming && Date.now() - pollStart > 5000) {
|
|
1169
|
+
pollInterval = Math.min(pollInterval * 1.1, 1000);
|
|
1170
|
+
}
|
|
1171
|
+
pollTimer = setTimeout(() => {
|
|
1172
|
+
void doPoll();
|
|
1173
|
+
}, pollInterval);
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
// First poll runs immediately (main refreshed before any state evaluation).
|
|
1177
|
+
void doPoll();
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
998
1180
|
/**
|
|
999
1181
|
* Assigns the API sandbox data to the Sandbox object.
|
|
1000
1182
|
*
|
|
@@ -1002,6 +1184,7 @@ let Sandbox = (() => {
|
|
|
1002
1184
|
* @returns {void}
|
|
1003
1185
|
*/
|
|
1004
1186
|
processSandboxDto(sandboxDto) {
|
|
1187
|
+
const newState = sandboxDto.state;
|
|
1005
1188
|
this.id = sandboxDto.id;
|
|
1006
1189
|
this.name = sandboxDto.name;
|
|
1007
1190
|
this.organizationId = sandboxDto.organizationId;
|
|
@@ -1014,7 +1197,6 @@ let Sandbox = (() => {
|
|
|
1014
1197
|
this.gpu = sandboxDto.gpu;
|
|
1015
1198
|
this.memory = sandboxDto.memory;
|
|
1016
1199
|
this.disk = sandboxDto.disk;
|
|
1017
|
-
this.state = sandboxDto.state;
|
|
1018
1200
|
this.errorReason = sandboxDto.errorReason;
|
|
1019
1201
|
this.recoverable = sandboxDto.recoverable;
|
|
1020
1202
|
this.backupState = sandboxDto.backupState;
|
|
@@ -1022,10 +1204,10 @@ let Sandbox = (() => {
|
|
|
1022
1204
|
this.autoPauseInterval = sandboxDto.autoPauseInterval;
|
|
1023
1205
|
this.autoArchiveInterval = sandboxDto.autoArchiveInterval;
|
|
1024
1206
|
this.autoDeleteInterval = sandboxDto.autoDeleteInterval;
|
|
1207
|
+
this.autoDestroyAt = sandboxDto.autoDestroyAt;
|
|
1025
1208
|
this.createdAt = sandboxDto.createdAt;
|
|
1026
1209
|
this.updatedAt = sandboxDto.updatedAt;
|
|
1027
1210
|
this.lastActivityAt = sandboxDto.lastActivityAt;
|
|
1028
|
-
this.toolboxProxyUrl = sandboxDto.toolboxProxyUrl;
|
|
1029
1211
|
// Fields only present in the full SandboxDto (not returned by list endpoint)
|
|
1030
1212
|
if ('env' in sandboxDto) {
|
|
1031
1213
|
this.env = sandboxDto.env;
|
|
@@ -1037,6 +1219,21 @@ let Sandbox = (() => {
|
|
|
1037
1219
|
this.buildInfo = sandboxDto.buildInfo;
|
|
1038
1220
|
this.backupCreatedAt = sandboxDto.backupCreatedAt;
|
|
1039
1221
|
}
|
|
1222
|
+
const newProxyUrl = sandboxDto.toolboxProxyUrl;
|
|
1223
|
+
if (newProxyUrl && newProxyUrl !== this.toolboxProxyUrl && this.axiosInstance) {
|
|
1224
|
+
let baseUrl = newProxyUrl;
|
|
1225
|
+
if (!baseUrl.endsWith('/')) {
|
|
1226
|
+
baseUrl += '/';
|
|
1227
|
+
}
|
|
1228
|
+
this.axiosInstance.defaults.baseURL = baseUrl + this.id;
|
|
1229
|
+
this.clientConfig.basePath = this.axiosInstance.defaults.baseURL;
|
|
1230
|
+
}
|
|
1231
|
+
if (newProxyUrl) {
|
|
1232
|
+
this.toolboxProxyUrl = newProxyUrl;
|
|
1233
|
+
}
|
|
1234
|
+
if (newState) {
|
|
1235
|
+
this.applyState(newState);
|
|
1236
|
+
}
|
|
1040
1237
|
}
|
|
1041
1238
|
/**
|
|
1042
1239
|
* Refreshes the Sandbox data from the API, but does not throw an error if the sandbox has been deleted.
|
|
@@ -1044,15 +1241,56 @@ let Sandbox = (() => {
|
|
|
1044
1241
|
*
|
|
1045
1242
|
* @returns {Promise<void>}
|
|
1046
1243
|
*/
|
|
1244
|
+
/**
|
|
1245
|
+
* @returns true when the refresh produced authoritative state (success, or 404
|
|
1246
|
+
* mapped to DESTROYED); false when a transient error was swallowed and
|
|
1247
|
+
* the cached state is stale.
|
|
1248
|
+
*/
|
|
1047
1249
|
async refreshDataSafe() {
|
|
1048
1250
|
try {
|
|
1049
1251
|
await this.refreshData();
|
|
1252
|
+
return true;
|
|
1050
1253
|
}
|
|
1051
1254
|
catch (error) {
|
|
1052
1255
|
if (error instanceof DaytonaNotFoundError) {
|
|
1053
|
-
this.
|
|
1256
|
+
this.applyState(SandboxState.DESTROYED);
|
|
1257
|
+
return true;
|
|
1054
1258
|
}
|
|
1259
|
+
// Other errors are deliberately swallowed (parity with main): transient
|
|
1260
|
+
// failures (e.g. 502) mid-poll must not abort stop()/delete() waits.
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
applyState(newState) {
|
|
1265
|
+
if (newState === this.state) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
this.state = newState;
|
|
1269
|
+
for (const waiter of [...this.stateWaiters]) {
|
|
1270
|
+
this.checkStateWaiter(waiter, this.state);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
checkStateWaiter(waiter, state) {
|
|
1274
|
+
if (!state) {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
if (waiter.targetStates.has(state)) {
|
|
1278
|
+
waiter.resolve(state);
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
if (waiter.errorStates.has(state)) {
|
|
1282
|
+
const errorMessageFn = this.stateWaiterErrorMessageFns.get(waiter);
|
|
1283
|
+
waiter.reject(new DaytonaError(errorMessageFn ? errorMessageFn(state) : `Sandbox ${this.id} failed with state: ${state}`));
|
|
1284
|
+
return true;
|
|
1285
|
+
}
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
removeStateWaiter(waiter) {
|
|
1289
|
+
const index = this.stateWaiters.indexOf(waiter);
|
|
1290
|
+
if (index !== -1) {
|
|
1291
|
+
this.stateWaiters.splice(index, 1);
|
|
1055
1292
|
}
|
|
1293
|
+
this.stateWaiterErrorMessageFns.delete(waiter);
|
|
1056
1294
|
}
|
|
1057
1295
|
};
|
|
1058
1296
|
})();
|