@daytonaio/sdk 0.193.0-alpha.1 → 0.193.0
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/LICENSE +1 -1
- package/cjs/CodeInterpreter.js +1 -1
- package/cjs/CodeInterpreter.js.map +1 -1
- package/cjs/ComputerUse.js.map +1 -1
- package/cjs/Daytona.d.ts +5 -11
- package/cjs/Daytona.js +16 -48
- package/cjs/Daytona.js.map +1 -1
- package/cjs/FileSystem.js +1 -1
- package/cjs/FileSystem.js.map +1 -1
- package/cjs/Git.js +1 -1
- package/cjs/Git.js.map +1 -1
- package/cjs/Image.js +1 -1
- package/cjs/Image.js.map +1 -1
- package/cjs/LspServer.js +1 -1
- package/cjs/LspServer.js.map +1 -1
- package/cjs/ObjectStorage.js +1 -1
- package/cjs/ObjectStorage.js.map +1 -1
- package/cjs/Process.js +1 -1
- package/cjs/Process.js.map +1 -1
- package/cjs/PtyHandle.js +1 -1
- package/cjs/PtyHandle.js.map +1 -1
- package/cjs/Sandbox.d.ts +1 -5
- package/cjs/Sandbox.js +3 -7
- package/cjs/Sandbox.js.map +1 -1
- package/cjs/Secret.d.ts +139 -0
- package/cjs/Secret.js +143 -0
- package/cjs/Secret.js.map +1 -0
- package/cjs/Snapshot.js +1 -1
- package/cjs/Snapshot.js.map +1 -1
- package/cjs/Volume.js +1 -1
- package/cjs/Volume.js.map +1 -1
- package/cjs/errors/DaytonaError.d.ts +2 -4
- package/cjs/errors/DaytonaError.js +7 -11
- package/cjs/errors/DaytonaError.js.map +1 -1
- package/cjs/index.d.ts +1 -2
- package/cjs/index.js +2 -6
- package/cjs/index.js.map +1 -1
- package/cjs/types/Charts.js +1 -1
- package/cjs/types/Charts.js.map +1 -1
- package/cjs/types/CodeInterpreter.js +1 -1
- package/cjs/types/CodeInterpreter.js.map +1 -1
- package/cjs/types/ExecuteResponse.js +1 -1
- package/cjs/types/ExecuteResponse.js.map +1 -1
- package/cjs/types/Pty.js +1 -1
- package/cjs/types/Pty.js.map +1 -1
- package/cjs/utils/Binary.js +1 -1
- package/cjs/utils/Binary.js.map +1 -1
- package/cjs/utils/FileTransfer.js +1 -1
- package/cjs/utils/FileTransfer.js.map +1 -1
- package/cjs/utils/Import.js +1 -1
- package/cjs/utils/Import.js.map +1 -1
- package/cjs/utils/Multipart.js +1 -1
- package/cjs/utils/Multipart.js.map +1 -1
- package/cjs/utils/Runtime.js +1 -1
- package/cjs/utils/Runtime.js.map +1 -1
- package/cjs/utils/Stream.js.map +1 -1
- package/cjs/utils/WebSocket.js +1 -1
- package/cjs/utils/WebSocket.js.map +1 -1
- package/cjs/utils/otel.decorator.js +1 -1
- package/cjs/utils/otel.decorator.js.map +1 -1
- package/esm/CodeInterpreter.js +1 -1
- package/esm/CodeInterpreter.js.map +1 -1
- package/esm/ComputerUse.js.map +1 -1
- package/esm/Daytona.d.ts +5 -11
- package/esm/Daytona.js +17 -49
- package/esm/Daytona.js.map +1 -1
- package/esm/FileSystem.js +1 -1
- package/esm/FileSystem.js.map +1 -1
- package/esm/Git.js +1 -1
- package/esm/Git.js.map +1 -1
- package/esm/Image.js +1 -1
- package/esm/Image.js.map +1 -1
- package/esm/LspServer.js +1 -1
- package/esm/LspServer.js.map +1 -1
- package/esm/ObjectStorage.js +1 -1
- package/esm/ObjectStorage.js.map +1 -1
- package/esm/Process.js +1 -1
- package/esm/Process.js.map +1 -1
- package/esm/PtyHandle.js +1 -1
- package/esm/PtyHandle.js.map +1 -1
- package/esm/Sandbox.d.ts +1 -5
- package/esm/Sandbox.js +3 -7
- package/esm/Sandbox.js.map +1 -1
- package/esm/Secret.d.ts +139 -0
- package/esm/Secret.js +140 -0
- package/esm/Secret.js.map +1 -0
- package/esm/Snapshot.js +1 -1
- package/esm/Snapshot.js.map +1 -1
- package/esm/Volume.js +1 -1
- package/esm/Volume.js.map +1 -1
- package/esm/errors/DaytonaError.d.ts +2 -4
- package/esm/errors/DaytonaError.js +7 -11
- package/esm/errors/DaytonaError.js.map +1 -1
- package/esm/index.d.ts +1 -2
- package/esm/index.js +1 -2
- package/esm/index.js.map +1 -1
- package/esm/types/Charts.js +1 -1
- package/esm/types/Charts.js.map +1 -1
- package/esm/types/CodeInterpreter.js +1 -1
- package/esm/types/CodeInterpreter.js.map +1 -1
- package/esm/types/ExecuteResponse.js +1 -1
- package/esm/types/ExecuteResponse.js.map +1 -1
- package/esm/types/Pty.js +1 -1
- package/esm/types/Pty.js.map +1 -1
- package/esm/utils/Binary.js +1 -1
- package/esm/utils/Binary.js.map +1 -1
- package/esm/utils/FileTransfer.js +1 -1
- package/esm/utils/FileTransfer.js.map +1 -1
- package/esm/utils/Import.js +2 -2
- package/esm/utils/Import.js.map +1 -1
- package/esm/utils/Multipart.js +1 -1
- package/esm/utils/Multipart.js.map +1 -1
- package/esm/utils/Runtime.js +1 -1
- package/esm/utils/Runtime.js.map +1 -1
- package/esm/utils/Stream.js.map +1 -1
- package/esm/utils/WebSocket.js +1 -1
- package/esm/utils/WebSocket.js.map +1 -1
- package/esm/utils/otel.decorator.js +1 -1
- package/esm/utils/otel.decorator.js.map +1 -1
- package/package.json +6 -7
- package/cjs/Session.d.ts +0 -152
- package/cjs/Session.js +0 -523
- package/cjs/Session.js.map +0 -1
- package/esm/Session.d.ts +0 -152
- package/esm/Session.js +0 -516
- package/esm/Session.js.map +0 -1
package/esm/Session.d.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import type { AxiosInstance } from 'axios';
|
|
2
|
-
import { DaytonaError } from './errors/DaytonaError.js';
|
|
3
|
-
/**
|
|
4
|
-
* One frame on the streaming WS protocol. Mirrors the daemon-side frame format 1:1.
|
|
5
|
-
*/
|
|
6
|
-
export type SessionFrameType = 'stdout' | 'stderr' | 'error' | 'display' | 'control';
|
|
7
|
-
export interface SessionDisplay {
|
|
8
|
-
formats: string[];
|
|
9
|
-
/** Mime → payload (base64 for binary). */
|
|
10
|
-
data: Record<string, string>;
|
|
11
|
-
}
|
|
12
|
-
export interface SessionExecutionError {
|
|
13
|
-
name: string;
|
|
14
|
-
value?: string;
|
|
15
|
-
traceback?: string;
|
|
16
|
-
}
|
|
17
|
-
export interface SessionRunResult {
|
|
18
|
-
stdout: string;
|
|
19
|
-
stderr: string;
|
|
20
|
-
/** The execution error, or `null` when the code ran successfully. */
|
|
21
|
-
error: SessionExecutionError | null;
|
|
22
|
-
displays: SessionDisplay[];
|
|
23
|
-
durationMs: number;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Signed access bundle for a session, returned by the API.
|
|
27
|
-
*
|
|
28
|
-
* `token` is exposed for revocation / observability; the SDK does not send it
|
|
29
|
-
* as an `Authorization` header.
|
|
30
|
-
*/
|
|
31
|
-
export interface SessionAccess {
|
|
32
|
-
httpUrl: string;
|
|
33
|
-
wsUrl: string;
|
|
34
|
-
token: string;
|
|
35
|
-
/** ISO timestamp; the access is refreshed shortly before this. */
|
|
36
|
-
tokenExpiresAt: string;
|
|
37
|
-
/**
|
|
38
|
-
* Per-session WS capability. Sent on the first execute frame so the daemon
|
|
39
|
-
* can authorize the run. Absent on older servers; the SDK sends `''` then.
|
|
40
|
-
*/
|
|
41
|
-
wsAuth?: string;
|
|
42
|
-
}
|
|
43
|
-
export interface SessionRef {
|
|
44
|
-
id: string;
|
|
45
|
-
language: string;
|
|
46
|
-
cwd?: string;
|
|
47
|
-
createdAt: string;
|
|
48
|
-
lastUsedAt?: string;
|
|
49
|
-
expiresAt: string;
|
|
50
|
-
/** Present on createSession / refresh responses; omitted on listSessions. */
|
|
51
|
-
access?: SessionAccess;
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Reference to an existing session to run code against, optionally carrying a
|
|
55
|
-
* primed access bundle to skip an extra access lookup.
|
|
56
|
-
*/
|
|
57
|
-
export interface SessionContext {
|
|
58
|
-
id: string;
|
|
59
|
-
access?: SessionAccess;
|
|
60
|
-
}
|
|
61
|
-
/** Options for {@link SessionService.createSession}. */
|
|
62
|
-
export interface CreateSessionOptions {
|
|
63
|
-
template?: string;
|
|
64
|
-
language?: string;
|
|
65
|
-
cwd?: string;
|
|
66
|
-
}
|
|
67
|
-
/** A runtime template available for sessions. */
|
|
68
|
-
export interface SessionTemplate {
|
|
69
|
-
name: string;
|
|
70
|
-
description?: string;
|
|
71
|
-
languages: string[];
|
|
72
|
-
packages?: string[];
|
|
73
|
-
}
|
|
74
|
-
/** A package available within a session template. */
|
|
75
|
-
export interface SessionPackage {
|
|
76
|
-
name: string;
|
|
77
|
-
version: string;
|
|
78
|
-
hasNativeBindings?: boolean;
|
|
79
|
-
}
|
|
80
|
-
export interface SessionRunOptions {
|
|
81
|
-
language?: 'python' | 'typescript' | 'javascript';
|
|
82
|
-
template?: string;
|
|
83
|
-
context?: SessionContext;
|
|
84
|
-
env?: Record<string, string>;
|
|
85
|
-
timeout?: number;
|
|
86
|
-
}
|
|
87
|
-
export interface SessionRunStreamOptions extends SessionRunOptions {
|
|
88
|
-
onStdout?: (chunk: string) => void;
|
|
89
|
-
onStderr?: (chunk: string) => void;
|
|
90
|
-
onError?: (err: SessionExecutionError) => void;
|
|
91
|
-
onDisplay?: (display: SessionDisplay) => void;
|
|
92
|
-
onControl?: (text: string) => void;
|
|
93
|
-
signal?: AbortSignal;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* SessionInvalidatedError is the SDK projection of HTTP 410
|
|
97
|
-
* `error.name=SessionInvalidated`. Surfaces when the underlying sandbox
|
|
98
|
-
* has been rolled (death / snapshot drift / autostop).
|
|
99
|
-
*/
|
|
100
|
-
export declare class SessionInvalidatedError extends DaytonaError {
|
|
101
|
-
sessionId: string;
|
|
102
|
-
invalidatedAt: string;
|
|
103
|
-
constructor(sessionId: string, invalidatedAt: string);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* SessionExpiredError is the SDK projection of HTTP 410
|
|
107
|
-
* `error.name=SessionExpired`. `reason` distinguishes idle vs absolute TTL.
|
|
108
|
-
*/
|
|
109
|
-
export declare class SessionExpiredError extends DaytonaError {
|
|
110
|
-
sessionId: string;
|
|
111
|
-
expiredAt: string;
|
|
112
|
-
reason: 'idle' | 'absolute';
|
|
113
|
-
constructor(sessionId: string, expiredAt: string, reason: 'idle' | 'absolute');
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* SessionService is the user-facing SDK surface for the sessions product.
|
|
117
|
-
*
|
|
118
|
-
* Use `createSession` to start a persistent session and `run` / `runStream`
|
|
119
|
-
* to execute code against it. Calls to `run` / `runStream` can also be made
|
|
120
|
-
* without an explicit session — passing only a `template` / `language` — to
|
|
121
|
-
* execute one-shot code transparently. `listTemplates` and `listPackages`
|
|
122
|
-
* describe the runtimes available for sessions.
|
|
123
|
-
*/
|
|
124
|
-
export declare class SessionService {
|
|
125
|
-
private readonly http;
|
|
126
|
-
private readonly ctxAccess;
|
|
127
|
-
private readonly transientAccess;
|
|
128
|
-
private transientSupported;
|
|
129
|
-
constructor(http: AxiosInstance);
|
|
130
|
-
run(code: string, options?: SessionRunOptions): Promise<SessionRunResult>;
|
|
131
|
-
runStream(code: string, options?: SessionRunStreamOptions): Promise<SessionRunResult>;
|
|
132
|
-
createSession(options: CreateSessionOptions): Promise<SessionRef>;
|
|
133
|
-
listSessions(template?: string): Promise<SessionRef[]>;
|
|
134
|
-
deleteSession(id: string): Promise<void>;
|
|
135
|
-
listTemplates(): Promise<SessionTemplate[]>;
|
|
136
|
-
listPackages(templateName: string, language: string): Promise<SessionPackage[]>;
|
|
137
|
-
private runInternal;
|
|
138
|
-
private ensureAccess;
|
|
139
|
-
private runWsDirect;
|
|
140
|
-
private fetchSessionAccess;
|
|
141
|
-
private fetchTransient;
|
|
142
|
-
private runLegacy;
|
|
143
|
-
private runCodeRunLegacy;
|
|
144
|
-
private runStreamLegacy;
|
|
145
|
-
private isExpired;
|
|
146
|
-
private evictAccess;
|
|
147
|
-
private isRouteMissing404;
|
|
148
|
-
private applyFrame;
|
|
149
|
-
private resolveWebSocketImpl;
|
|
150
|
-
private translateError;
|
|
151
|
-
}
|
|
152
|
-
//# sourceMappingURL=Session.d.ts.map
|
package/esm/Session.js
DELETED
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright Daytona Platforms Inc.
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
import axios from 'axios';
|
|
6
|
-
import { DaytonaError } from './errors/DaytonaError.js';
|
|
7
|
-
/**
|
|
8
|
-
* HTTP status of an error, whether it's a raw AxiosError or a DaytonaError
|
|
9
|
-
* already normalized by the shared axios interceptors.
|
|
10
|
-
*/
|
|
11
|
-
function errorStatus(err) {
|
|
12
|
-
if (axios.isAxiosError(err)) {
|
|
13
|
-
return err.response?.status;
|
|
14
|
-
}
|
|
15
|
-
if (err instanceof DaytonaError) {
|
|
16
|
-
return err.statusCode;
|
|
17
|
-
}
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Raw response body of an error, from a raw AxiosError or a normalized
|
|
22
|
-
* DaytonaError (which preserves the body on `.data`).
|
|
23
|
-
*/
|
|
24
|
-
function errorBody(err) {
|
|
25
|
-
if (axios.isAxiosError(err)) {
|
|
26
|
-
return err.response?.data;
|
|
27
|
-
}
|
|
28
|
-
if (err instanceof DaytonaError) {
|
|
29
|
-
return err.data;
|
|
30
|
-
}
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* SessionInvalidatedError is the SDK projection of HTTP 410
|
|
35
|
-
* `error.name=SessionInvalidated`. Surfaces when the underlying sandbox
|
|
36
|
-
* has been rolled (death / snapshot drift / autostop).
|
|
37
|
-
*/
|
|
38
|
-
export class SessionInvalidatedError extends DaytonaError {
|
|
39
|
-
sessionId;
|
|
40
|
-
invalidatedAt;
|
|
41
|
-
constructor(sessionId, invalidatedAt) {
|
|
42
|
-
super(`Session ${sessionId} has been invalidated at ${invalidatedAt}`);
|
|
43
|
-
this.name = 'SessionInvalidatedError';
|
|
44
|
-
this.sessionId = sessionId;
|
|
45
|
-
this.invalidatedAt = invalidatedAt;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* SessionExpiredError is the SDK projection of HTTP 410
|
|
50
|
-
* `error.name=SessionExpired`. `reason` distinguishes idle vs absolute TTL.
|
|
51
|
-
*/
|
|
52
|
-
export class SessionExpiredError extends DaytonaError {
|
|
53
|
-
sessionId;
|
|
54
|
-
expiredAt;
|
|
55
|
-
reason;
|
|
56
|
-
constructor(sessionId, expiredAt, reason) {
|
|
57
|
-
super(`Session ${sessionId} expired (${reason}) at ${expiredAt}`);
|
|
58
|
-
this.name = 'SessionExpiredError';
|
|
59
|
-
this.sessionId = sessionId;
|
|
60
|
-
this.expiredAt = expiredAt;
|
|
61
|
-
this.reason = reason;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const REFRESH_SKEW_SECONDS = 60;
|
|
65
|
-
class LegacyFallback extends Error {
|
|
66
|
-
}
|
|
67
|
-
class WsAuthError extends Error {
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* SessionService is the user-facing SDK surface for the sessions product.
|
|
71
|
-
*
|
|
72
|
-
* Use `createSession` to start a persistent session and `run` / `runStream`
|
|
73
|
-
* to execute code against it. Calls to `run` / `runStream` can also be made
|
|
74
|
-
* without an explicit session — passing only a `template` / `language` — to
|
|
75
|
-
* execute one-shot code transparently. `listTemplates` and `listPackages`
|
|
76
|
-
* describe the runtimes available for sessions.
|
|
77
|
-
*/
|
|
78
|
-
export class SessionService {
|
|
79
|
-
http;
|
|
80
|
-
// SDK-side caches keyed by context id and `${template}:${language}` for
|
|
81
|
-
// one-shot transients. Survive across calls; eviction happens on auth
|
|
82
|
-
// failure, expiry, or explicit deleteSession.
|
|
83
|
-
ctxAccess = new Map();
|
|
84
|
-
transientAccess = new Map();
|
|
85
|
-
// Latches once the API confirms /transients is missing (older server).
|
|
86
|
-
transientSupported = null;
|
|
87
|
-
constructor(http) {
|
|
88
|
-
this.http = http;
|
|
89
|
-
}
|
|
90
|
-
async run(code, options = {}) {
|
|
91
|
-
return this.runInternal(code, options, undefined);
|
|
92
|
-
}
|
|
93
|
-
async runStream(code, options = {}) {
|
|
94
|
-
const { onStdout, onStderr, onError, onDisplay, onControl, signal, ...runOpts } = options;
|
|
95
|
-
return this.runInternal(code, runOpts, { onStdout, onStderr, onError, onDisplay, onControl, signal });
|
|
96
|
-
}
|
|
97
|
-
async createSession(options) {
|
|
98
|
-
try {
|
|
99
|
-
const { data } = await this.http.post('/sessions', options);
|
|
100
|
-
if (data.access) {
|
|
101
|
-
this.ctxAccess.set(data.id, data.access);
|
|
102
|
-
}
|
|
103
|
-
return data;
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
throw this.translateError(err);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async listSessions(template) {
|
|
110
|
-
try {
|
|
111
|
-
const { data } = await this.http.get('/sessions', {
|
|
112
|
-
params: template ? { template } : undefined,
|
|
113
|
-
});
|
|
114
|
-
return data;
|
|
115
|
-
}
|
|
116
|
-
catch (err) {
|
|
117
|
-
throw this.translateError(err);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async deleteSession(id) {
|
|
121
|
-
try {
|
|
122
|
-
await this.http.delete(`/sessions/${encodeURIComponent(id)}`);
|
|
123
|
-
this.ctxAccess.delete(id);
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
throw this.translateError(err);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async listTemplates() {
|
|
130
|
-
try {
|
|
131
|
-
const { data } = await this.http.get('/sessions/templates');
|
|
132
|
-
return data;
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
throw this.translateError(err);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async listPackages(templateName, language) {
|
|
139
|
-
try {
|
|
140
|
-
const { data } = await this.http.get(`/sessions/templates/${encodeURIComponent(templateName)}/packages`, {
|
|
141
|
-
params: { language },
|
|
142
|
-
});
|
|
143
|
-
return data;
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
throw this.translateError(err);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
// -- direct-to-sandbox hot path ----------------------------------------
|
|
150
|
-
async runInternal(code, options, handlers) {
|
|
151
|
-
if (!options.context && this.transientSupported === false) {
|
|
152
|
-
return this.runLegacy(code, options, handlers);
|
|
153
|
-
}
|
|
154
|
-
let sessionId;
|
|
155
|
-
let access;
|
|
156
|
-
let reset;
|
|
157
|
-
try {
|
|
158
|
-
;
|
|
159
|
-
({ sessionId, access, reset } = await this.ensureAccess(options));
|
|
160
|
-
}
|
|
161
|
-
catch (err) {
|
|
162
|
-
if (err instanceof LegacyFallback) {
|
|
163
|
-
if (!options.context) {
|
|
164
|
-
this.transientSupported = false;
|
|
165
|
-
}
|
|
166
|
-
return this.runLegacy(code, options, handlers);
|
|
167
|
-
}
|
|
168
|
-
throw err;
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
return await this.runWsDirect(sessionId, access, code, options, reset, handlers);
|
|
172
|
-
}
|
|
173
|
-
catch (err) {
|
|
174
|
-
// Recover once from a stale cached session, then retry on a fresh one:
|
|
175
|
-
// - WsAuthError: the signed WS token was rejected (expired/rotated key).
|
|
176
|
-
// - SessionInvalidatedError on a *transient* run: the cached transient
|
|
177
|
-
// context was GC'd or its instance rolled away. Re-minting yields a
|
|
178
|
-
// fresh context. For an explicit context the caller owns the
|
|
179
|
-
// lifecycle, so an invalidation is terminal and must propagate.
|
|
180
|
-
const recoverable = err instanceof WsAuthError || (err instanceof SessionInvalidatedError && !options.context);
|
|
181
|
-
if (recoverable) {
|
|
182
|
-
this.evictAccess(options, sessionId);
|
|
183
|
-
try {
|
|
184
|
-
;
|
|
185
|
-
({ sessionId, access, reset } = await this.ensureAccess(options));
|
|
186
|
-
}
|
|
187
|
-
catch (refreshErr) {
|
|
188
|
-
if (refreshErr instanceof LegacyFallback) {
|
|
189
|
-
if (!options.context) {
|
|
190
|
-
this.transientSupported = false;
|
|
191
|
-
}
|
|
192
|
-
return this.runLegacy(code, options, handlers);
|
|
193
|
-
}
|
|
194
|
-
throw refreshErr;
|
|
195
|
-
}
|
|
196
|
-
return this.runWsDirect(sessionId, access, code, options, reset, handlers);
|
|
197
|
-
}
|
|
198
|
-
throw err;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async ensureAccess(options) {
|
|
202
|
-
if (options.context) {
|
|
203
|
-
const sessionId = options.context.id;
|
|
204
|
-
// Inline access from a freshly-created context primes the cache —
|
|
205
|
-
// avoids an immediate GET /access right after createSession().
|
|
206
|
-
if (options.context.access && !this.ctxAccess.has(sessionId)) {
|
|
207
|
-
this.ctxAccess.set(sessionId, options.context.access);
|
|
208
|
-
}
|
|
209
|
-
let access = this.ctxAccess.get(sessionId);
|
|
210
|
-
if (!access || this.isExpired(access)) {
|
|
211
|
-
access = await this.fetchSessionAccess(sessionId);
|
|
212
|
-
this.ctxAccess.set(sessionId, access);
|
|
213
|
-
}
|
|
214
|
-
return { sessionId, access, reset: false };
|
|
215
|
-
}
|
|
216
|
-
const key = `${options.template ?? ''}:${options.language ?? ''}`;
|
|
217
|
-
const cached = this.transientAccess.get(key);
|
|
218
|
-
if (cached && !this.isExpired(cached.access)) {
|
|
219
|
-
return { sessionId: cached.sessionId, access: cached.access, reset: true };
|
|
220
|
-
}
|
|
221
|
-
const fresh = await this.fetchTransient(options.template, options.language);
|
|
222
|
-
this.transientAccess.set(key, fresh);
|
|
223
|
-
this.transientSupported = true;
|
|
224
|
-
return { sessionId: fresh.sessionId, access: fresh.access, reset: true };
|
|
225
|
-
}
|
|
226
|
-
async runWsDirect(sessionId, access, code, options, reset, handlers) {
|
|
227
|
-
const WebSocketImpl = await this.resolveWebSocketImpl();
|
|
228
|
-
return new Promise((resolve, reject) => {
|
|
229
|
-
const ws = new WebSocketImpl(access.wsUrl);
|
|
230
|
-
const aggregated = {
|
|
231
|
-
stdout: '',
|
|
232
|
-
stderr: '',
|
|
233
|
-
error: null,
|
|
234
|
-
displays: [],
|
|
235
|
-
durationMs: 0,
|
|
236
|
-
};
|
|
237
|
-
const startedAt = Date.now();
|
|
238
|
-
let resolved = false;
|
|
239
|
-
let opened = false;
|
|
240
|
-
const finish = (resolvedOrErr) => {
|
|
241
|
-
if (resolved)
|
|
242
|
-
return;
|
|
243
|
-
resolved = true;
|
|
244
|
-
void options;
|
|
245
|
-
handlers?.signal?.removeEventListener('abort', onAbort);
|
|
246
|
-
try {
|
|
247
|
-
ws.close();
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
/* close errors are non-fatal here */
|
|
251
|
-
}
|
|
252
|
-
if (resolvedOrErr instanceof Error) {
|
|
253
|
-
reject(resolvedOrErr);
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
resolvedOrErr.durationMs = Date.now() - startedAt;
|
|
257
|
-
resolve(resolvedOrErr);
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
const onAbort = () => finish(new DaytonaError('runStream aborted by caller'));
|
|
261
|
-
if (handlers?.signal?.aborted) {
|
|
262
|
-
finish(new DaytonaError('runStream aborted by caller'));
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
handlers?.signal?.addEventListener('abort', onAbort, { once: true });
|
|
266
|
-
ws.onopen = () => {
|
|
267
|
-
opened = true;
|
|
268
|
-
ws.send(JSON.stringify({
|
|
269
|
-
code,
|
|
270
|
-
envs: options.env ?? {},
|
|
271
|
-
timeout: options.timeout ?? 0,
|
|
272
|
-
reset,
|
|
273
|
-
wsAuth: access.wsAuth ?? '',
|
|
274
|
-
}));
|
|
275
|
-
};
|
|
276
|
-
ws.onmessage = (ev) => {
|
|
277
|
-
try {
|
|
278
|
-
const frame = JSON.parse(typeof ev.data === 'string' ? ev.data : Buffer.from(ev.data).toString());
|
|
279
|
-
this.applyFrame(frame, aggregated, handlers);
|
|
280
|
-
}
|
|
281
|
-
catch {
|
|
282
|
-
// malformed frame — ignore, daemon never emits non-JSON frames in normal operation
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
// Node's `ws` exposes the HTTP status code on the unexpected-response
|
|
286
|
-
// event before `error` fires. Hook it if available; the browser
|
|
287
|
-
// WebSocket gives us no such hook.
|
|
288
|
-
const wsAny = ws;
|
|
289
|
-
if (typeof wsAny.on === 'function') {
|
|
290
|
-
wsAny.on('unexpected-response', (_req, res) => {
|
|
291
|
-
const status = res?.statusCode;
|
|
292
|
-
if (status === 401 || status === 403) {
|
|
293
|
-
finish(new WsAuthError());
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
// 404 (context gone), 400 (proxy: "Is the Sandbox started?"),
|
|
297
|
-
// 5xx — all map to invalidation. The caller's recovery action
|
|
298
|
-
// is the same: drop the context and create a fresh one.
|
|
299
|
-
finish(new SessionInvalidatedError(sessionId, new Date().toISOString()));
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
ws.onerror = () => {
|
|
304
|
-
if (!opened) {
|
|
305
|
-
// Pre-open error with no status — treat as sandbox-gone.
|
|
306
|
-
finish(new SessionInvalidatedError(sessionId, new Date().toISOString()));
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
finish(new DaytonaError('session websocket error'));
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
ws.onclose = () => finish(aggregated);
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
async fetchSessionAccess(sessionId) {
|
|
316
|
-
try {
|
|
317
|
-
const { data } = await this.http.get(`/sessions/${encodeURIComponent(sessionId)}/access`);
|
|
318
|
-
return data;
|
|
319
|
-
}
|
|
320
|
-
catch (err) {
|
|
321
|
-
const status = errorStatus(err);
|
|
322
|
-
if (status === 404) {
|
|
323
|
-
if (this.isRouteMissing404(errorBody(err))) {
|
|
324
|
-
throw new LegacyFallback();
|
|
325
|
-
}
|
|
326
|
-
throw new SessionInvalidatedError(sessionId, new Date().toISOString());
|
|
327
|
-
}
|
|
328
|
-
if (status === 410) {
|
|
329
|
-
throw this.translateError(err);
|
|
330
|
-
}
|
|
331
|
-
throw err;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
async fetchTransient(template, language) {
|
|
335
|
-
try {
|
|
336
|
-
const body = {};
|
|
337
|
-
if (template !== undefined)
|
|
338
|
-
body.template = template;
|
|
339
|
-
if (language !== undefined)
|
|
340
|
-
body.language = language;
|
|
341
|
-
const { data } = await this.http.post('/sessions/transients', body);
|
|
342
|
-
if (!data.access) {
|
|
343
|
-
throw new LegacyFallback();
|
|
344
|
-
}
|
|
345
|
-
return { sessionId: data.id, access: data.access };
|
|
346
|
-
}
|
|
347
|
-
catch (err) {
|
|
348
|
-
if (err instanceof LegacyFallback) {
|
|
349
|
-
throw err;
|
|
350
|
-
}
|
|
351
|
-
if (errorStatus(err) === 404 && this.isRouteMissing404(errorBody(err))) {
|
|
352
|
-
throw new LegacyFallback();
|
|
353
|
-
}
|
|
354
|
-
throw err;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
async runLegacy(code, options, handlers) {
|
|
358
|
-
if (handlers) {
|
|
359
|
-
return this.runStreamLegacy(code, options, handlers);
|
|
360
|
-
}
|
|
361
|
-
return this.runCodeRunLegacy(code, options);
|
|
362
|
-
}
|
|
363
|
-
async runCodeRunLegacy(code, options) {
|
|
364
|
-
try {
|
|
365
|
-
const { data } = await this.http.post('/sessions/code-run', { code, ...options });
|
|
366
|
-
return {
|
|
367
|
-
...data,
|
|
368
|
-
error: data.error ?? null,
|
|
369
|
-
displays: data.displays ?? [],
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
catch (err) {
|
|
373
|
-
throw this.translateError(err);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
async runStreamLegacy(code, options, handlers) {
|
|
377
|
-
let data;
|
|
378
|
-
try {
|
|
379
|
-
;
|
|
380
|
-
({ data } = await this.http.post('/sessions/connect', options));
|
|
381
|
-
}
|
|
382
|
-
catch (err) {
|
|
383
|
-
throw this.translateError(err);
|
|
384
|
-
}
|
|
385
|
-
const WebSocketImpl = await this.resolveWebSocketImpl();
|
|
386
|
-
return new Promise((resolve, reject) => {
|
|
387
|
-
const ws = new WebSocketImpl(data.wsUrl);
|
|
388
|
-
const aggregated = {
|
|
389
|
-
stdout: '',
|
|
390
|
-
stderr: '',
|
|
391
|
-
error: null,
|
|
392
|
-
displays: [],
|
|
393
|
-
durationMs: 0,
|
|
394
|
-
};
|
|
395
|
-
const startedAt = Date.now();
|
|
396
|
-
let resolved = false;
|
|
397
|
-
const finish = (resolvedOrErr) => {
|
|
398
|
-
if (resolved)
|
|
399
|
-
return;
|
|
400
|
-
resolved = true;
|
|
401
|
-
handlers.signal?.removeEventListener('abort', onAbort);
|
|
402
|
-
try {
|
|
403
|
-
ws.close();
|
|
404
|
-
}
|
|
405
|
-
catch {
|
|
406
|
-
/* close errors are non-fatal here */
|
|
407
|
-
}
|
|
408
|
-
if (resolvedOrErr instanceof Error) {
|
|
409
|
-
reject(resolvedOrErr);
|
|
410
|
-
}
|
|
411
|
-
else {
|
|
412
|
-
resolvedOrErr.durationMs = Date.now() - startedAt;
|
|
413
|
-
resolve(resolvedOrErr);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
const onAbort = () => finish(new DaytonaError('runStream aborted by caller'));
|
|
417
|
-
if (handlers.signal?.aborted) {
|
|
418
|
-
finish(new DaytonaError('runStream aborted by caller'));
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
handlers.signal?.addEventListener('abort', onAbort, { once: true });
|
|
422
|
-
ws.onopen = () => {
|
|
423
|
-
ws.send(JSON.stringify({ code, envs: options.env, timeout: options.timeout, wsAuth: data.wsAuth ?? '' }));
|
|
424
|
-
};
|
|
425
|
-
ws.onmessage = (ev) => {
|
|
426
|
-
try {
|
|
427
|
-
const frame = JSON.parse(typeof ev.data === 'string' ? ev.data : Buffer.from(ev.data).toString());
|
|
428
|
-
this.applyFrame(frame, aggregated, handlers);
|
|
429
|
-
}
|
|
430
|
-
catch {
|
|
431
|
-
/* ignore malformed frames */
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
ws.onerror = () => finish(new DaytonaError('session websocket error'));
|
|
435
|
-
ws.onclose = () => finish(aggregated);
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
// -- helpers -----------------------------------------------------------
|
|
439
|
-
isExpired(access) {
|
|
440
|
-
const expiry = new Date(access.tokenExpiresAt).getTime();
|
|
441
|
-
return Number.isFinite(expiry) ? Date.now() + REFRESH_SKEW_SECONDS * 1000 >= expiry : true;
|
|
442
|
-
}
|
|
443
|
-
evictAccess(options, sessionId) {
|
|
444
|
-
if (options.context) {
|
|
445
|
-
this.ctxAccess.delete(sessionId);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
this.transientAccess.delete(`${options.template ?? ''}:${options.language ?? ''}`);
|
|
449
|
-
}
|
|
450
|
-
isRouteMissing404(body) {
|
|
451
|
-
// A non-JSON / empty body means we never reached the route layer — treat as route-missing.
|
|
452
|
-
if (!body || typeof body !== 'object')
|
|
453
|
-
return true;
|
|
454
|
-
const b = body;
|
|
455
|
-
// Nest's unknown-route 404 carries `message:"Cannot GET /..."`. A genuine
|
|
456
|
-
// missing-session NotFoundException carries the SAME `error:"Not Found"` but
|
|
457
|
-
// a descriptive message (e.g. "Session <id> not found."), so we must NOT
|
|
458
|
-
// key off `error` — only the message (or its absence) is a reliable signal.
|
|
459
|
-
if (typeof b.message === 'string') {
|
|
460
|
-
return b.message.startsWith('Cannot ') || b.message.trim() === '';
|
|
461
|
-
}
|
|
462
|
-
// No message field at all → not a recognizable missing-session envelope; assume route-missing.
|
|
463
|
-
return b.message === undefined || b.message === null;
|
|
464
|
-
}
|
|
465
|
-
applyFrame(frame, aggregated, handlers) {
|
|
466
|
-
switch (frame.type) {
|
|
467
|
-
case 'stdout':
|
|
468
|
-
aggregated.stdout += frame.text ?? '';
|
|
469
|
-
handlers?.onStdout?.(frame.text ?? '');
|
|
470
|
-
break;
|
|
471
|
-
case 'stderr':
|
|
472
|
-
aggregated.stderr += frame.text ?? '';
|
|
473
|
-
handlers?.onStderr?.(frame.text ?? '');
|
|
474
|
-
break;
|
|
475
|
-
case 'error': {
|
|
476
|
-
const e = { name: frame.name ?? 'Error', value: frame.value, traceback: frame.traceback };
|
|
477
|
-
aggregated.error = e;
|
|
478
|
-
handlers?.onError?.(e);
|
|
479
|
-
break;
|
|
480
|
-
}
|
|
481
|
-
case 'display': {
|
|
482
|
-
const d = { formats: frame.formats ?? [], data: frame.data ?? {} };
|
|
483
|
-
aggregated.displays.push(d);
|
|
484
|
-
handlers?.onDisplay?.(d);
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
case 'control':
|
|
488
|
-
handlers?.onControl?.(frame.text ?? '');
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
async resolveWebSocketImpl() {
|
|
493
|
-
if (typeof WebSocket !== 'undefined') {
|
|
494
|
-
return WebSocket;
|
|
495
|
-
}
|
|
496
|
-
const wsModule = (await import('ws').catch(() => null));
|
|
497
|
-
if (!wsModule?.default) {
|
|
498
|
-
throw new DaytonaError("WebSocket support not available; install the 'ws' package or run in a browser/Node 22+");
|
|
499
|
-
}
|
|
500
|
-
return wsModule.default;
|
|
501
|
-
}
|
|
502
|
-
translateError(err) {
|
|
503
|
-
if (errorStatus(err) === 410) {
|
|
504
|
-
const body = errorBody(err);
|
|
505
|
-
const e = body?.error;
|
|
506
|
-
if (e?.name === 'SessionInvalidated' && e.sessionId && e.invalidatedAt) {
|
|
507
|
-
return new SessionInvalidatedError(e.sessionId, e.invalidatedAt);
|
|
508
|
-
}
|
|
509
|
-
if (e?.name === 'SessionExpired' && e.sessionId && e.expiredAt && e.reason) {
|
|
510
|
-
return new SessionExpiredError(e.sessionId, e.expiredAt, e.reason);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
return err;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
//# sourceMappingURL=Session.js.map
|