Skip to content
File

Blob: src/worker/dispatch/shared/sandbox-errors.ts

typescript89 lines
1import { type ExecutionSession, type Sandbox, type SessionOptions } from "@cloudflare/sandbox";
2import { isNoContainerInstanceError } from "@/worker/sandbox/container-errors";
3 
4// This file exists due to @cloudflare/sandbox not exporting the error types
5// see: https://github.com/cloudflare/sandbox-sdk/issues/517
6 
7const SESSION_ALREADY_EXISTS_CODE = "SESSION_ALREADY_EXISTS";
8const SESSION_ALREADY_EXISTS_NAME = "SessionAlreadyExistsError";
9const SESSION_DESTROYED_CODE = "SESSION_DESTROYED";
10const SESSION_DESTROYED_NAME = "SessionDestroyedError";
11const SESSION_ALREADY_EXISTS_PREFIX = `${SESSION_ALREADY_EXISTS_NAME}:`;
12const SESSION_DESTROYED_PREFIX = `${SESSION_DESTROYED_NAME}:`;
13 
14const getErrorCode = (error: unknown): string | null => {
15 if (typeof error !== "object" || error === null || !("code" in error)) {
16 return null;
17 }
18 
19 const { code } = error as { code?: unknown };
20 return typeof code === "string" ? code : null;
21};
22 
23const getErrorName = (error: unknown): string | null => {
24 if (typeof error !== "object" || error === null || !("name" in error)) {
25 return null;
26 }
27 
28 const { name } = error as { name?: unknown };
29 return typeof name === "string" ? name : null;
30};
31 
32const getErrorMessage = (error: unknown): string | null => {
33 if (error instanceof Error) {
34 return error.message;
35 }
36 
37 if (typeof error !== "object" || error === null || !("message" in error)) {
38 return null;
39 }
40 
41 const { message } = error as { message?: unknown };
42 return typeof message === "string" ? message : null;
43};
44 
45const hasErrorMessagePrefix = (error: unknown, prefix: string): boolean =>
46 getErrorMessage(error)?.startsWith(prefix) === true;
47 
48export const getOrCreateSandboxSession = async (
49 runtime: {
50 sandbox: Pick<Sandbox, "createSession">;
51 getSession(sessionId: string): Promise<ExecutionSession>;
52 },
53 options: Required<Pick<SessionOptions, "id">> & Pick<SessionOptions, "cwd" | "env" | "commandTimeoutMs">,
54): Promise<ExecutionSession> => {
55 try {
56 return await runtime.sandbox.createSession(options);
57 } catch (error) {
58 if (
59 getErrorCode(error) !== SESSION_ALREADY_EXISTS_CODE &&
60 getErrorName(error) !== SESSION_ALREADY_EXISTS_NAME &&
61 !hasErrorMessagePrefix(error, SESSION_ALREADY_EXISTS_PREFIX)
62 ) {
63 throw error;
64 }
65 
66 return await runtime.getSession(options.id);
67 }
68};
69 
70export const deleteSandboxSessionIfExists = async (
71 sandbox: Pick<Sandbox, "deleteSession">,
72 sessionId: string,
73): Promise<void> => {
74 try {
75 await sandbox.deleteSession(sessionId);
76 } catch (error) {
77 if (
78 isNoContainerInstanceError(error) ||
79 getErrorCode(error) === SESSION_DESTROYED_CODE ||
80 getErrorName(error) === SESSION_DESTROYED_NAME ||
81 hasErrorMessagePrefix(error, SESSION_DESTROYED_PREFIX)
82 ) {
83 return;
84 }
85 
86 throw error;
87 }
88};