From e4681fa56b6154520b84f9b207ab2975e5ecfcf2 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 28 Apr 2025 08:35:20 +0200 Subject: [PATCH 01/18] WIP --- packages/projects-docs/pages/sdk/_meta.json | 33 ++++-- .../pages/sdk/create-sandbox.mdx | 67 +++++++++++ .../pages/sdk/create-session.mdx | 28 +++++ .../projects-docs/pages/sdk/filesystem.mdx | 13 +- packages/projects-docs/pages/sdk/fork.mdx | 3 +- .../projects-docs/pages/sdk/hibernate.mdx | 30 +++++ packages/projects-docs/pages/sdk/index.mdx | 26 ++-- packages/projects-docs/pages/sdk/ports.mdx | 14 +-- .../pages/sdk/restart-shutdown.mdx | 29 +++++ .../projects-docs/pages/sdk/sandboxes.mdx | 112 ------------------ packages/projects-docs/pages/sdk/sessions.mdx | 84 +++++++++---- packages/projects-docs/pages/sdk/shells.mdx | 12 +- packages/projects-docs/pages/sdk/specs.mdx | 2 +- packages/projects-docs/pages/sdk/tasks.mdx | 12 +- .../pages/sdk/update-sandbox.mdx | 24 ++++ 15 files changed, 309 insertions(+), 180 deletions(-) create mode 100644 packages/projects-docs/pages/sdk/create-sandbox.mdx create mode 100644 packages/projects-docs/pages/sdk/create-session.mdx create mode 100644 packages/projects-docs/pages/sdk/hibernate.mdx create mode 100644 packages/projects-docs/pages/sdk/restart-shutdown.mdx delete mode 100644 packages/projects-docs/pages/sdk/sandboxes.mdx create mode 100644 packages/projects-docs/pages/sdk/update-sandbox.mdx diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index 0ae02175..3752a3f3 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -8,17 +8,38 @@ "newWindow": true }, - "-- api-reference": { + "-- sandboxes": { "type": "separator", - "title": "API Reference" + "title": "Sandboxes" }, - "sandboxes": "Sandboxes", + "create-sandbox": "Create Sandbox", + "sessions": "Sessions", + "hibernate": "Hibernate", + "fork": "Fork", + "restart-shutdown": "Restart & Shutdown", + "specs": "VM Specs", + "persistence": "Persistence", + "update-sandbox": "Update Sandbox", + + "-- websocket-sessions": { + "type": "separator", + "title": "WebSocket Sessions" + }, + "create-session": "Create Session", "filesystem": "File System", "shells": "Shells", "ports": "Ports & Previews", "tasks": "Tasks & Setup", - "sessions": "Sessions", - "specs": "VM Specs", + + "-- rest-session": { + "type": "separator", + "title": "REST Sessions" + }, + + "-- ssh-session": { + "type": "separator", + "title": "SSH Sessions" + }, "-- resources": { "type": "separator", @@ -28,7 +49,5 @@ "snapshot-builder": "Snapshot Builder", "environment": "Environment", "docker": "Docker & Docker Compose", - "persistence": "Persistence", - "fork": "Fork", "faq": "FAQ" } diff --git a/packages/projects-docs/pages/sdk/create-sandbox.mdx b/packages/projects-docs/pages/sdk/create-sandbox.mdx new file mode 100644 index 00000000..4850bf4c --- /dev/null +++ b/packages/projects-docs/pages/sdk/create-sandbox.mdx @@ -0,0 +1,67 @@ +--- +title: Create Sandbox +description: Learn how to create sandboxes with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Create Sandbox + +Sandboxes are the main building block of the CodeSandbox SDK. They represent a single project that you can run, fork, and modify. They are backed by a Firecracker VM. Sandboxes are completely isolated, and persisted, so you can securely run untrusted code in them. + +## Creating a Sandbox + +You can create a sandbox by calling `sandbox.create()`: + +```ts +import { CodeSandbox } from '@codesandbox/sdk' +const sdk = new CodeSandbox(); + +const sandbox = await sdk.sandbox.create(); +``` + +If no argument is provided to `sandbox.create()`, we'll create a sandbox based on our [Universal](https://codesandbox.io/p/devbox/universal-pcz35m) template on CodeSandbox. You can also pass in a template id, either from [our collection of templates](/sdk/snapshot-library) or by creating your own snapshot using our [Snapshot Builder](/sdk/snapshot-builder). + +```ts +import { CodeSandbox } from '@codesandbox/sdk' +const sdk = new CodeSandbox(); + +const sandbox = await sdk.sandbox.create({ + type: 'template', + template: 'some-sandbox-id' +}); +``` + +## Reference Existing Sandbox + +Reference an existing sandbox by calling `sdk.sandbox.ref(id)`: + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id'); +``` + +## Create from Git + +You can create a Sandbox from a git repository as well. + +```ts +const sandbox = await sdk.sandbox.create({ + type: 'git', + url: '', + accessToken: '' +}); +``` + +## Create from JSON + +You can create a Sandbox inline as well. We will hash the files and use it as a tag for the Sandbox. That means if a Sandbox already exists with the hash we will rather return that Sandbox. + +```ts +const sandbox = await sdk.sandbox.create({ + type: 'json', + files: { + 'src/index.js': 'console.log("Hello World")' + }, + tasks: {} +}); +``` diff --git a/packages/projects-docs/pages/sdk/create-session.mdx b/packages/projects-docs/pages/sdk/create-session.mdx new file mode 100644 index 00000000..8da9f3bf --- /dev/null +++ b/packages/projects-docs/pages/sdk/create-session.mdx @@ -0,0 +1,28 @@ +--- +title: Create Session +description: Learn how to create sessions with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Create Session + +The default session for a Sandbox is a WebSocket connection. This connection is designed to hold a session over time with minimum overhead to message passing. The WebSocket connection also allows you to listen to changes to ports, shell output etc. + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.createSession() + +await session.fs.writeTextFile('test.txt', 'Hello World'); +``` + +## Disconnecting the Session + +Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') +const session = await sandbox.createSession() + +await session.disconnect(); +``` diff --git a/packages/projects-docs/pages/sdk/filesystem.mdx b/packages/projects-docs/pages/sdk/filesystem.mdx index 4ac3e798..50b62107 100644 --- a/packages/projects-docs/pages/sdk/filesystem.mdx +++ b/packages/projects-docs/pages/sdk/filesystem.mdx @@ -19,20 +19,21 @@ operations are relative to the workspace directory of the sandbox (which is `/pr You can read & write files using an api that's similer to the Node.js fs module: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.createSession() // Writing text files -await sandbox.fs.writeTextFile("./hello.txt", "Hello, world!"); +await session.fs.writeTextFile("./hello.txt", "Hello, world!"); // Reading text files -const content = await sandbox.fs.readTextFile("./hello.txt"); +const content = await session.fs.readTextFile("./hello.txt"); console.log(content); // Writing binary files -await sandbox.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); +await session.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); // Reading binary files -const content = await sandbox.fs.readFile("./hello.bin"); +const content = await session.fs.readFile("./hello.bin"); console.log(content); ``` @@ -43,7 +44,7 @@ Uploading and downloading files can be done using the same methods as writing an ```ts import fs from "node:fs"; -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); const myBinaryFile = fs.readFileSync("./my-binary-file"); await sandbox.fs.writeFile("./my-binary-file", myBinaryFile); diff --git a/packages/projects-docs/pages/sdk/fork.mdx b/packages/projects-docs/pages/sdk/fork.mdx index 29aa978b..e880d74d 100644 --- a/packages/projects-docs/pages/sdk/fork.mdx +++ b/packages/projects-docs/pages/sdk/fork.mdx @@ -13,9 +13,10 @@ import { CodeSandbox } from '@codesandbox/sdk' const sdk = new CodeSandbox(); const sandbox = await sdk.sandbox.create(); +const session = sandbox.createSession() // Run anything on the sandbox -await sandbox.shells.run('echo test > test.txt'); +await session.shells.run('echo test > test.txt'); const sandbox2 = await sandbox.fork(); diff --git a/packages/projects-docs/pages/sdk/hibernate.mdx b/packages/projects-docs/pages/sdk/hibernate.mdx new file mode 100644 index 00000000..90910c92 --- /dev/null +++ b/packages/projects-docs/pages/sdk/hibernate.mdx @@ -0,0 +1,30 @@ +--- +title: Hibernate +description: Learn how to hibernate sandboxes with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Hibernate + +When you're done with a sandbox, you can hibernate it. This will save the memory state of the sandbox, so it will resume from the same state when you start it again. + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') + +await sandbox.hibernate(); +``` + +When creating or restarting a sandbox, you can also set a hibernation timeout between 1 minute and 24 hours. By default this timeout is 5 minutes for free users, and 30 minutes for paid users. + +```ts +import { CodeSandbox } from '@codesandbox/sdk' +const sdk = new CodeSandbox(); + +const sandbox = await sdk.sandbox.create({ + type: 'template', + hibernationTimeoutSeconds: 60 * 60 * 1 // 1 hour +}); +``` + +When you set a hibernation timeout, the sandbox will hibernate after the specified period of inactivity (no calls from the SDK). While the SDK remains connected, we recommend either explicitly hibernating the sandbox or disconnecting from it when you're done using it. Since resuming only takes a few seconds, you can be aggressive with hibernation to conserve resources. diff --git a/packages/projects-docs/pages/sdk/index.mdx b/packages/projects-docs/pages/sdk/index.mdx index 62975a36..da6561aa 100644 --- a/packages/projects-docs/pages/sdk/index.mdx +++ b/packages/projects-docs/pages/sdk/index.mdx @@ -4,6 +4,7 @@ description: Learn how CodeSandbox works and the different types of projects you --- import Hero from "../../../../shared-components/Hero.js"; +import { Callout } from 'nextra-theme-docs' +The `connect` method connects you to the sandbox with a websocket connection, but you can also use `rest`, `json` or `ssh` for other types of connections. + diff --git a/packages/projects-docs/pages/sdk/ports.mdx b/packages/projects-docs/pages/sdk/ports.mdx index 42ac03c5..56f85ae2 100644 --- a/packages/projects-docs/pages/sdk/ports.mdx +++ b/packages/projects-docs/pages/sdk/ports.mdx @@ -31,7 +31,7 @@ You can obtain a preview token by calling `sandbox.previewTokens.create`. You ca // Create a preview token with a custom expiration time const previewToken = await sandbox.previewTokens.create({ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now -}); +}) // Let's say that the sandbox is running something on port 3000 const port = await sandbox.ports.waitForPort(3000); @@ -100,7 +100,7 @@ const signedUrl = port.getSignedPreviewUrl(token); In some cases, a private sandbox might be running an API server that you want to access. In those cases, you should put the preview token in the `csb-preview-token` header. ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect() // Start a development server sandbox.shells.run("npm run dev"); @@ -129,7 +129,7 @@ The Ports API is available under `sandbox.ports`. It provides methods for monito You can listen for ports being opened and closed in your sandbox: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Listen for ports being opened const listener1 = sandbox.ports.onDidPortOpen((portInfo) => { @@ -152,7 +152,7 @@ listener2.dispose(); You can get information about currently opened ports: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get all opened ports const openPorts = sandbox.ports.getOpenedPorts(); @@ -172,7 +172,7 @@ if (previewUrl) { When starting services, you often need to wait for a port to become available: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Start a development server sandbox.shells.run("npm run dev"); @@ -189,7 +189,7 @@ console.log(`Dev server is ready at: ${portInfo.getPreviewUrl()}`); Here's a complete example of starting a web server and getting its preview URL: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Start the server sandbox.shells.run("npx serve -y ."); @@ -211,7 +211,7 @@ console.log(`Server is running at: When working with multiple services, you might want to monitor several ports: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Start monitoring before launching services sandbox.ports.onDidPortOpen((portInfo) => { diff --git a/packages/projects-docs/pages/sdk/restart-shutdown.mdx b/packages/projects-docs/pages/sdk/restart-shutdown.mdx new file mode 100644 index 00000000..68c88497 --- /dev/null +++ b/packages/projects-docs/pages/sdk/restart-shutdown.mdx @@ -0,0 +1,29 @@ +--- +title: Restart & Shutdown +description: Learn how to restart and shutdown sandboxes with the CodeSandbox SDK. +--- + +# Restart & Shutdown + +## Restart + +Restarting a Sandbox starts it from a clean slate. It will install and build its configured resources and also run with the last version of the agent. + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') + + +await sandbox.restart(); +``` + +## Shutdown + +You can also shutdown a sandbox. This will shut down the sandbox without creating a memory snapshot. Next time the sandbox is started, it will boot from a clean state (but your files in `/project/sandbox` will be preserved). + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') + +await sandbox.shutdown(); +``` + +Generally you should shutdown a sandbox if you want to start from a clean state. diff --git a/packages/projects-docs/pages/sdk/sandboxes.mdx b/packages/projects-docs/pages/sdk/sandboxes.mdx deleted file mode 100644 index a766dc31..00000000 --- a/packages/projects-docs/pages/sdk/sandboxes.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Sandboxes -description: Learn how to create sandboxes with the CodeSandbox SDK. ---- - -import { Callout } from 'nextra-theme-docs' - -# Sandboxes - -Sandboxes are the main building block of the CodeSandbox SDK. They represent a single project that you can run, fork, and modify. They are backed by a Firecracker VM. Sandboxes are completely isolated, and persisted, so you can securely run untrusted code in them. - -## Creating a Sandbox - -You can create a sandbox by calling `sandbox.create()`: - -```ts -import { CodeSandbox } from '@codesandbox/sdk' -const sdk = new CodeSandbox(); - -const sandbox = await sdk.sandbox.create(); -``` - -If no argument is provided to `sandbox.create()`, we'll create a sandbox based on our [Universal](https://codesandbox.io/p/devbox/universal-pcz35m) template on CodeSandbox. You can also pass in a template id, either from [our collection of templates](/sdk/snapshot-library) or by creating your own snapshot using our [Snapshot Builder](/sdk/snapshot-builder). - -## Opening an Existing Sandbox - -You can also open an existing sandbox by calling `sandbox.open()`: - -```ts -const sandbox = await sdk.sandbox.open('sandbox-id'); -``` - -This will start the sandbox and connect to it. - -## Opening a Sandbox from the Browser - -It's possible to connect to a sandbox directly from the browser without sharing your API key with the frontend. You can do this by generating a single-use token from the server: - -```ts -import express from 'express'; -import { CodeSandbox } from '@codesandbox/sdk' - -const app = express(); -const sdk = new CodeSandbox(); - -app.get('/api/start-sandbox/:id', async (req, res) => { - const startData = await sdk.sandbox.start(req.params.id); - - res.json(startData); -}); -``` - -From the browser, you can use this start data to connect to the sandbox: - -```ts -import { connectToSandbox } from '@codesandbox/sdk/browser' - -// Fetch the start data from the server -const startData = await fetch('/api/start-sandbox/some-sandbox-id').then(res => res.json()); - -const sandbox = await connectToSandbox(startData); - -// Now you can do whatever you normally do using the SDK -await sandbox.fs.writeFile('./index.html', '

Hello World

'); -sandbox.shells.run('npx -y serve .') -console.log((await sandbox.ports.waitForPort(3000)).getPreviewUrl()) -``` - - -Some APIs are not available when connecting from the browser. For example, you can't hibernate, shutdown or fork a sandbox. - - -## Hibernation & Hibernation Timeout - -When you're done with a sandbox, you can hibernate it. This will save the memory state of the sandbox, so it will resume from the same state when you start it again. - -```ts -await sandbox.hibernate(); -``` - -When starting a sandbox, you can also set a hibernation timeout between 1 minute and 24 hours. By default this timeout is 5 minutes for free users, and 30 minutes for paid users. - -```ts -import { CodeSandbox } from '@codesandbox/sdk' -const sdk = new CodeSandbox(); - -const sandbox = await sdk.sandbox.create({ - hibernationTimeoutSeconds: 60 * 60 * 1 // 1 hour -}); -``` - -When you set a hibernation timeout, the sandbox will hibernate after the specified period of inactivity (no calls from the SDK). While the SDK remains connected, we recommend either explicitly hibernating the sandbox or disconnecting from it when you're done using it. Since resuming only takes a few seconds, you can be aggressive with hibernation to conserve resources. - -## Disconnecting from a Sandbox - -Alternatively, you can disconnect from the sandbox. In this case, it will automatically hibernate after the timeout: - -```ts -await sandbox.disconnect(); -``` - -You can do this if you want a user to still interact with the sandbox, but don't need to keep the SDK connected. - -## Shutdown - -Finally, you can also shutdown a sandbox. This will shut down the sandbox without creating a memory snapshot. Next time the sandbox is started, it will boot from a clean state (but your files in `/project/sandbox` will be preserved). - -```ts -await sandbox.shutdown(); -``` - -Generally you should shutdown a sandbox if you want to start from a clean state. diff --git a/packages/projects-docs/pages/sdk/sessions.mdx b/packages/projects-docs/pages/sdk/sessions.mdx index 4a8d9810..d0a1e7d0 100644 --- a/packages/projects-docs/pages/sdk/sessions.mdx +++ b/packages/projects-docs/pages/sdk/sessions.mdx @@ -7,10 +7,18 @@ import { Callout } from 'nextra-theme-docs' # Sessions -Normally, when you create a sandbox, we create a single user (called `pitcher-host`) inside the sandbox, which you can use to interact with it. This user has write access to any API. We refer to this session as the "global" session. +To interact with a Sandbox you have to create a session. The SDK offers different types of sessions depending on your needs: + +- **WebSocket (default)**: A long lived WebSocket connection with minimum overhead to message passing. Can listen to port changes, shell outputs etc. Can be used in the browser +- **REST**: When you do not want to manage a connection, but rather do one off request/response +- **SSH**: When you want to interact with the full environment at low level + +By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. But you can choose +to create your own user sessions with specific permissions. ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.createSession() // Now I have a session where I act as `pitcher-host` user. // Any action I do will be done as `pitcher-host` user. @@ -21,13 +29,12 @@ await sandbox.fs.writeTextFile('test.txt', 'Hello World'); When you run `whoami`, it will say you're `root`. That's because we run every session inside a Docker container where you are `root`. The container itself, however, is started as the user of your session. -Now, let's say you want another user to connect with the sandbox as well but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. In that case, you can create a new session. +Now, let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. ```ts -const sandbox = await sdk.sandbox.create(); - -// Now create a new session that only has read access to the sandbox. -const session = await sandbox.sessions.createReadOnly(); +// Now create a new session that only has read access to the sandbox +const sandbox = await sdk.sandbox.create() +const session = await sandbox.createSession({ user: 'anonymous', permission: 'read' }) // I have access to the normal sandbox api, but I only have read access // This means I cannot write files or open shells, but I _can_ read them @@ -35,7 +42,8 @@ await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an await session.fs.readTextFile('test.txt'); // This will work. // I can also create sessions with write access -const session2 = await sandbox.sessions.create('my-session-id', { +const session2 = await sandbox.createSession({ + user: 'some-user-reference', permission: 'write', }); await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work @@ -43,38 +51,64 @@ await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work If you create a session with the same id as before (e.g., `my-session-id`), we will reuse the existing session. -## Sessions Inside the Browser +Examples of use cases are: -By default, we will automatically connect to the session when calling `sandbox.sessions.create()`. However, if you want to connect from the browser, you can instead create a session on the server and share a token with the browser, which the browser can then use to connect. Here is an example: +- **Shared Branches / Sandboxes**: When building a collaborative code editor, you can allow multiple users to connect to the same branch and collaboratively edit files without the ability to affect each other. Every user can have their own secrets (like git token) stored in `~/.private` so they can commit and pull with their own credentials without sharing those with others +- **Anonymous Previews**: If you want to share a sandbox with someone else and give them access to reading files & shells, you can create a read-only session when they connect to the sandbox. This way, they can see what's running and how it works, but they cannot change the code. If they want to make changes, you can call `sandbox.fork()` to create a copy and give them access to a write session there + +## Sessions in the Browser + +If you want your users to connect to the Sandbox from the browser you can rather create a browser session and pass it to the client. ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.ref('sandbox-id'); -const sessionInfo = await sandbox.sessions.create('my-new-session', { permission: 'write', autoConnect: false }); +// Pass this session to the browser +const session = await sandbox.createBrowserSession({ + user: 'some-user-reference', + permission: 'write' +}); +``` -// Now pass sessionInfo to the browser, and inside the browser do: +And in the browser: +```ts import { connectToSandbox } from '@codesandbox/sdk/browser'; -const session = await connectToSandbox(sessionInfo); +const session = await connectToSandbox(sessionFromServer); ``` -## Storage +## Sessions using Rest -Every session will have the same filesystem as the global session. This means that if one user creates a file inside the workspace folder (or even the home folder), other users will be able to see it. There is one exception to this: the `~/.private` folder will be private for each session. No other session (including the global session) will be able to read or write to files inside this folder. +The sandboxes also expose a Rest api which can be a more useful protocol to use as you do not have to manage an ongoing connection. The Rest session +is not as extensive, but supports most features. - -The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. - +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') +const session = sandbox.createRestSession({ + user: 'some-user-reference', + permission: 'write' +}) -## Use Cases +await session.fs.writeTextFile('test.ext', 'Hello World') +``` -You can use this API for multiple use cases: +### Sessions using SSH -### Shared Branches / Sandboxes +Not sure the use case here or how it works. -When building a collaborative code editor, you can allow multiple users to connect to the same branch and collaboratively edit files without the ability to affect each other. Every user can have their own secrets (like git token) stored in `~/.private` so they can commit and pull with their own credentials without sharing those with others. +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') +const session = sandbox.createSshSession({ + user: 'some-user-reference', + permission: 'write' +}) +``` -### Anonymous Previews +## Storage -If you want to share a sandbox with someone else and give them access to reading files & shells, you can create a read-only session when they connect to the sandbox. This way, they can see what's running and how it works, but they cannot change the code. If they want to make changes, you can call `sandbox.fork()` to create a copy and give them access to a write session there. +Every session will have the same filesystem as the global session. This means that if one user creates a file inside the workspace folder (or even the home folder), other users will be able to see it. There is one exception to this: the `~/.private` folder will be private for each session. No other session (including the global session) will be able to read or write to files inside this folder. + + +The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. + diff --git a/packages/projects-docs/pages/sdk/shells.mdx b/packages/projects-docs/pages/sdk/shells.mdx index 4b38e60d..fec41942 100644 --- a/packages/projects-docs/pages/sdk/shells.mdx +++ b/packages/projects-docs/pages/sdk/shells.mdx @@ -18,10 +18,10 @@ The Shell API is available under `sandbox.shells`. It provides methods for creat You can create an interactive shell that allows you to send commands and receive output: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect() // Create a new shell (bash is default) -const shell = sandbox.shells.create('bash'); +const shell = sandbox.shells.create('bash') // Listen to shell output const shellListenDisposer = shell.onOutput((output) => { @@ -47,7 +47,7 @@ We recommend to use `sandbox.shells.create` when you want to create an interacti For simple command execution, you can use the `run` method which returns a promise with the command's output: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Run a single command const command = sandbox.shells.run("npm install"); @@ -72,7 +72,7 @@ console.log(result.output, result.exitCode); The Shell API includes built-in support for running code in different programming languages: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Run JavaScript code const jsResult = await sandbox.shells.js.run(` @@ -92,7 +92,7 @@ These interpreters are built on top of the `run` method, so you can use the same You can list and reconnect to existing shells: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get all shells const shells = await sandbox.shells.getShells(); @@ -111,7 +111,7 @@ console.log(shell.getOutput()); ### Starting a server and waiting for the port to open ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Run in background by not awaiting the command const shell = sandbox.shells.run("npx -y serve ."); diff --git a/packages/projects-docs/pages/sdk/specs.mdx b/packages/projects-docs/pages/sdk/specs.mdx index 213741d7..5f87cb6d 100644 --- a/packages/projects-docs/pages/sdk/specs.mdx +++ b/packages/projects-docs/pages/sdk/specs.mdx @@ -42,5 +42,5 @@ await sandbox.updateTier(VMTier.Medium); This will change the VM specs of the sandbox dynamically, without rebooting. - Be careful when scaling the VM specs of a running sandbox down. If you scale down the VM too much, it might not have enough resources to run your tasks and will slow to a crawl. + Be careful when scaling down the VM specs of a running sandbox. If you scale down the VM too much, it might not have enough resources to run your tasks and will slow to a crawl. diff --git a/packages/projects-docs/pages/sdk/tasks.mdx b/packages/projects-docs/pages/sdk/tasks.mdx index ad7993e3..13740e69 100644 --- a/packages/projects-docs/pages/sdk/tasks.mdx +++ b/packages/projects-docs/pages/sdk/tasks.mdx @@ -151,7 +151,7 @@ Here's a more complete example showing various task configurations: Setup tasks run automatically when a sandbox starts. They typically handle installation of dependencies and initial builds. You can monitor and control setup tasks using the Setup API: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect() // Listen to setup progress sandbox.setup.onSetupProgressUpdate((progress) => { @@ -216,7 +216,7 @@ Regular tasks are defined in the `tasks` section of your `tasks.json` file. Each You can get all available tasks in your sandbox: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get all tasks const tasks = await sandbox.tasks.getTasks(); @@ -230,7 +230,7 @@ for (const task of tasks) { You can run a task using its ID: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Run a specific task const task = await sandbox.tasks.runTask("dev"); @@ -248,7 +248,7 @@ if (task.ports.length > 0) { You can get information about a specific task: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get a specific task const task = await sandbox.tasks.getTask("build"); @@ -270,7 +270,7 @@ if (task) { Here's an example of running a development server task and waiting for it to be ready: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get the dev task const task = await sandbox.tasks.getTask("dev"); @@ -294,7 +294,7 @@ if (task.preview?.port) { You can run multiple tasks and monitor their ports: ```ts -const sandbox = await sdk.sandbox.create(); +const sandbox = await sdk.sandbox.create().connect(); // Get all tasks that should run at start const tasks = await sandbox.tasks.getTasks(); diff --git a/packages/projects-docs/pages/sdk/update-sandbox.mdx b/packages/projects-docs/pages/sdk/update-sandbox.mdx new file mode 100644 index 00000000..1b9a1794 --- /dev/null +++ b/packages/projects-docs/pages/sdk/update-sandbox.mdx @@ -0,0 +1,24 @@ +--- +title: Update Sandbox +description: Learn how to keep Sandboxes up to date with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Update Sandbox + +Every Sandbox has an agent running on it. This agent is the what allows you to interact with the Sandbox environment +When a new version of the agent is published existing sandboxes needs to restart to get that new version. + +You can check if the current sandbox is up to date using `isUpToDate()` + +```ts +const sandbox = sdk.sandbox.ref('sandbox-id') +const isUpToDate = await sandbox.isUpToDate() + +if (!isUpToDate) { + await sandbox.restart() +} +``` + +It is up to you deciding on the best user experience. At CodeSandbox we would show a notification when the agent was out of date and the user could choose when to update. From 3f27ca311cb837bc56a187235fd6335ec97924ea Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 29 Apr 2025 17:00:57 +0200 Subject: [PATCH 02/18] more iterations --- .../pages/sdk/create-sandbox.mdx | 8 ++-- packages/projects-docs/pages/sdk/index.mdx | 24 +++++------ packages/projects-docs/pages/sdk/sessions.mdx | 43 +++++++++---------- 3 files changed, 36 insertions(+), 39 deletions(-) diff --git a/packages/projects-docs/pages/sdk/create-sandbox.mdx b/packages/projects-docs/pages/sdk/create-sandbox.mdx index 4850bf4c..b5265894 100644 --- a/packages/projects-docs/pages/sdk/create-sandbox.mdx +++ b/packages/projects-docs/pages/sdk/create-sandbox.mdx @@ -27,8 +27,8 @@ import { CodeSandbox } from '@codesandbox/sdk' const sdk = new CodeSandbox(); const sandbox = await sdk.sandbox.create({ - type: 'template', - template: 'some-sandbox-id' + source: 'template', + id: 'some-sandbox-id' }); ``` @@ -46,7 +46,7 @@ You can create a Sandbox from a git repository as well. ```ts const sandbox = await sdk.sandbox.create({ - type: 'git', + source: 'git', url: '', accessToken: '' }); @@ -58,7 +58,7 @@ You can create a Sandbox inline as well. We will hash the files and use it as a ```ts const sandbox = await sdk.sandbox.create({ - type: 'json', + source: 'json', files: { 'src/index.js': 'console.log("Hello World")' }, diff --git a/packages/projects-docs/pages/sdk/index.mdx b/packages/projects-docs/pages/sdk/index.mdx index da6561aa..0fc68148 100644 --- a/packages/projects-docs/pages/sdk/index.mdx +++ b/packages/projects-docs/pages/sdk/index.mdx @@ -43,39 +43,39 @@ Now you can create a sandbox and run a server: import { CodeSandbox } from "@codesandbox/sdk"; const sdk = new CodeSandbox(process.env.CSB_API_KEY!); -const sandbox = await sdk.sandbox.create() -let session = await sandbox.connect() +const sandbox = await sdk.sandbox.fromTemplate() -await session.shells.python.run("print(1+1)"); -await session.shells.run('echo "Hello World"'); +// Create a client to interact with the environment +const client = await sandbox.connect() + +await client.shells.python.run("print(1+1)"); +await client.shells.run('echo "Hello World"'); // We can also start shells in the background by not awaiting them -const shellInfo = session.shells.run("npx -y serve ."); +const shellInfo = client.shells.run("npx -y serve ."); // Wait for port to open -const portInfo = await session.ports.waitForPort(3000); +const portInfo = await client.ports.waitForPort(3000); console.log(portInfo.getPreviewUrl()); // And, we can clone(!) the sandbox! This takes 1-3s. const sandbox2 = await sandbox.fork() -session = await sandbox2.connect() +const client2 = await sandbox2.connect() // Sandbox 2 will have the same processes running as sandbox 1 -const portInfo2 = await session.ports.waitForPort(3000); +const portInfo2 = await client2.ports.waitForPort(3000); console.log(portInfo2.getPreviewUrl()); // Finally, we can hibernate the sandbox. This will snapshot the sandbox and stop it. // Next time you start the sandbox, it will continue where it left off, as we created a memory snapshot. // The sandboxes will also automatically resume if a network request comes in for the // servers they have started. -await sandbox.hibernate(); await sandbox2.hibernate(); // Resume the sandbox -await sandbox.resume() -session = await sandbox.connect() +const newClient2 = await sandbox2.connect() ``` -The `connect` method connects you to the sandbox with a websocket connection, but you can also use `rest`, `json` or `ssh` for other types of connections. +The `createWebSocketSession` method connects you to the sandbox with a websocket connection, but you can also use `createBrowserSession`, `createRestSession` or `createSshSession` for other types of sessions. diff --git a/packages/projects-docs/pages/sdk/sessions.mdx b/packages/projects-docs/pages/sdk/sessions.mdx index d0a1e7d0..7e356a34 100644 --- a/packages/projects-docs/pages/sdk/sessions.mdx +++ b/packages/projects-docs/pages/sdk/sessions.mdx @@ -9,32 +9,36 @@ import { Callout } from 'nextra-theme-docs' To interact with a Sandbox you have to create a session. The SDK offers different types of sessions depending on your needs: -- **WebSocket (default)**: A long lived WebSocket connection with minimum overhead to message passing. Can listen to port changes, shell outputs etc. Can be used in the browser -- **REST**: When you do not want to manage a connection, but rather do one off request/response -- **SSH**: When you want to interact with the full environment at low level +- **WebSocket**: A long lived WebSocket connection with minimum overhead to message passing. Can listen to port changes, shell outputs etc. Can be used in the browser +- **Browser**: The same long lived WebSocket connection, but in the browser +- **Rest**: When you do not want to manage a connection, but rather do one off request/response +- **Ssh**: When you want to interact with the full environment at low level -By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. But you can choose -to create your own user sessions with specific permissions. +By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. ```ts const sandbox = await sdk.sandbox.create() const session = await sandbox.createSession() +const client = await sandbox.connect() // Now I have a session where I act as `pitcher-host` user. // Any action I do will be done as `pitcher-host` user. -await sandbox.fs.writeTextFile('test.txt', 'Hello World'); +await client.fs.writeTextFile('test.txt', 'Hello World'); ``` When you run `whoami`, it will say you're `root`. That's because we run every session inside a Docker container where you are `root`. The container itself, however, is started as the user of your session. -Now, let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. +But you can choose to create your own user sessions with specific permissions. Let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. ```ts // Now create a new session that only has read access to the sandbox const sandbox = await sdk.sandbox.create() -const session = await sandbox.createSession({ user: 'anonymous', permission: 'read' }) +const session = await sandbox.createWebSocketSession({ + user: 'anonymous', + permission: 'read' +}) // I have access to the normal sandbox api, but I only have read access // This means I cannot write files or open shells, but I _can_ read them @@ -42,19 +46,20 @@ await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an await session.fs.readTextFile('test.txt'); // This will work. // I can also create sessions with write access -const session2 = await sandbox.createSession({ +const session2 = await sandbox.createWebSocketSession({ user: 'some-user-reference', permission: 'write', }); await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work ``` -If you create a session with the same id as before (e.g., `my-session-id`), we will reuse the existing session. +## Storage -Examples of use cases are: +Every session will have the same filesystem as the global session. This means that if one user creates a file inside the workspace folder (or even the home folder), other users will be able to see it. There is one exception to this: the `~/.private` folder will be private for each session. No other session (including the global session) will be able to read or write to files inside this folder. -- **Shared Branches / Sandboxes**: When building a collaborative code editor, you can allow multiple users to connect to the same branch and collaboratively edit files without the ability to affect each other. Every user can have their own secrets (like git token) stored in `~/.private` so they can commit and pull with their own credentials without sharing those with others -- **Anonymous Previews**: If you want to share a sandbox with someone else and give them access to reading files & shells, you can create a read-only session when they connect to the sandbox. This way, they can see what's running and how it works, but they cannot change the code. If they want to make changes, you can call `sandbox.fork()` to create a copy and give them access to a write session there + +The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. + ## Sessions in the Browser @@ -90,10 +95,10 @@ const session = sandbox.createRestSession({ permission: 'write' }) -await session.fs.writeTextFile('test.ext', 'Hello World') +await session.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) ``` -### Sessions using SSH +## Sessions using Ssh Not sure the use case here or how it works. @@ -104,11 +109,3 @@ const session = sandbox.createSshSession({ permission: 'write' }) ``` - -## Storage - -Every session will have the same filesystem as the global session. This means that if one user creates a file inside the workspace folder (or even the home folder), other users will be able to see it. There is one exception to this: the `~/.private` folder will be private for each session. No other session (including the global session) will be able to read or write to files inside this folder. - - -The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. - From 1b64488b3f2620e7b9c74b0a4e24c84e19b9a474 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 29 Apr 2025 20:53:43 +0200 Subject: [PATCH 03/18] coming together --- packages/projects-docs/pages/sdk/_meta.json | 15 ++-- packages/projects-docs/pages/sdk/connect.mdx | 83 +++++++++++++++++++ .../projects-docs/pages/sdk/create-resume.mdx | 83 +++++++++++++++++++ .../pages/sdk/create-sandbox.mdx | 67 --------------- .../pages/sdk/create-session.mdx | 28 ------- .../projects-docs/pages/sdk/filesystem.mdx | 21 ++--- packages/projects-docs/pages/sdk/fork.mdx | 2 +- packages/projects-docs/pages/sdk/index.mdx | 22 +++-- packages/projects-docs/pages/sdk/ports.mdx | 2 + .../pages/sdk/restart-shutdown.mdx | 1 - packages/projects-docs/pages/sdk/sessions.mdx | 58 +++++-------- packages/projects-docs/pages/sdk/shells.mdx | 29 ++++--- packages/projects-docs/pages/sdk/specs.mdx | 6 +- .../pages/sdk/update-sandbox.mdx | 5 +- 14 files changed, 238 insertions(+), 184 deletions(-) create mode 100644 packages/projects-docs/pages/sdk/connect.mdx create mode 100644 packages/projects-docs/pages/sdk/create-resume.mdx delete mode 100644 packages/projects-docs/pages/sdk/create-sandbox.mdx delete mode 100644 packages/projects-docs/pages/sdk/create-session.mdx diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index 3752a3f3..145b087b 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -12,7 +12,7 @@ "type": "separator", "title": "Sandboxes" }, - "create-sandbox": "Create Sandbox", + "create-resume": "Create & Resume", "sessions": "Sessions", "hibernate": "Hibernate", "fork": "Fork", @@ -21,11 +21,11 @@ "persistence": "Persistence", "update-sandbox": "Update Sandbox", - "-- websocket-sessions": { + "-- connect-reference": { "type": "separator", - "title": "WebSocket Sessions" + "title": "Connect" }, - "create-session": "Create Session", + "connect": "Connect", "filesystem": "File System", "shells": "Shells", "ports": "Ports & Previews", @@ -33,12 +33,7 @@ "-- rest-session": { "type": "separator", - "title": "REST Sessions" - }, - - "-- ssh-session": { - "type": "separator", - "title": "SSH Sessions" + "title": "Rest" }, "-- resources": { diff --git a/packages/projects-docs/pages/sdk/connect.mdx b/packages/projects-docs/pages/sdk/connect.mdx new file mode 100644 index 00000000..f0d57081 --- /dev/null +++ b/packages/projects-docs/pages/sdk/connect.mdx @@ -0,0 +1,83 @@ +--- +title: Connect +description: Learn how to connect to a sandbox with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Connect + +The default session for a Sandbox is a WebSocket connection. This connection is designed to hold a session over time with minimum overhead to message passing. The WebSocket connection also allows you to listen to changes to ports, shell output etc. + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +await session.fs.writeTextFile('test.txt', 'Hello World'); +``` + +## Connect in Browser + +Connecting to a Sandbox in the browser requires some collaboration with your server. Create an endpoint that will resume the sandbox and create a browser session: + +```ts +export const GET = async ({ params }) => { + const sandbox = await sdk.sandbox.resume(params.sandboxId); + const session = await sandbox.createBrowserSession(); + + return session +} +``` + +And in the browser: + +```ts +import { connectToSandbox } from '@codesandbox/sdk/browser'; + +const session = await connectToSandbox({ + getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()) +}); + +await session.fs.writeTextFile('test.txt', 'Hello World'); +``` + +The Browser sessions automatically manages the connection and will reconnect if the connection is lost. This is controlled by an option called `onFocusChange`. + +```ts +const session = await connectToSandbox({ + getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()), + onFocusChange: (notify) => { + const onVisibilityChange = () => { + notify(document.visibilityState === 'visible'); + } + + document.addEventListener('visibilitychange', onVisibilityChange); + + return () => { + document.removeEventListener('visibilitychange', onVisibilityChange); + } + } +}); +``` + +By telling the browser session when it is in focus it will automatically reconnect when hibernated, unless you explicitly disconnected the session. + +While the `connectToSandbox` promise is resolving you can also listen to initialization events to show a loading state: + +```ts +const session = await connectToSandbox({ + getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()), + onInitCb: (event) => {} +}); +``` + +## Disconnecting the Session + +Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. + +```ts +const sandbox = await sdk.sandbox.resume('sandbox-id') +const session = await sandbox.connect() + +await session.disconnect(); +``` diff --git a/packages/projects-docs/pages/sdk/create-resume.mdx b/packages/projects-docs/pages/sdk/create-resume.mdx new file mode 100644 index 00000000..e9f1595f --- /dev/null +++ b/packages/projects-docs/pages/sdk/create-resume.mdx @@ -0,0 +1,83 @@ +--- +title: Create Sandbox +description: Learn how to create sandboxes with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Create Sandbox + +Sandboxes are the main building block of the CodeSandbox SDK. They represent a single project that you can run, fork, and modify. They are backed by a Firecracker VM. Sandboxes are completely isolated, and persisted, so you can securely run untrusted code in them. + +## Creating a Sandbox + +You can create a sandbox by calling `sandbox.create()`: + +```ts +import { CodeSandbox } from '@codesandbox/sdk' +const sdk = new CodeSandbox(); + +const sandbox = await sdk.sandbox.create(); +``` + +If no argument is provided to `sandbox.create()`, we'll create a sandbox based on our [Universal](https://codesandbox.io/p/devbox/universal-pcz35m) template on CodeSandbox. You can also pass in a specific template id from [our collection of templates](/sdk/snapshot-library) or by creating your own snapshot using our [Snapshot Builder](/sdk/snapshot-builder). Additionally you can choose other sources like [Git](/sdk/sandbox#git) or [Files](/sdk/sandbox#files). + +The `sandbox` object represents the instance of a sandbox running in our infrastructure. You can use it to manage the sandbox and connect to it. In addition to its methods it has the following properties: + +- `id`: The unique identifier of the sandbox. +- `isUpToDate`: Whether the sandbox is up to date with the latest agent version. +- `cluster`: The cluster the sandbox is running on. + +### Create from Template + +```ts +const sandbox = await sdk.sandbox.create({ + source: 'template', + id: 'some-sandbox-id' +}) +``` + +### Create from Git + +```ts +const sandbox = await sdk.sandbox.create({ + source: 'git', + url: 'https://...', + branch: 'main' +}) +``` + +### Create from Files + +```ts +const sandbox = await sdk.sandbox.create({ + source: 'files', + files: { + 'index.js': 'console.log("Hello World")' + } +}) +``` + +### Additional options + +For any of the above sources you can also pass the following options: + +```ts +const sandbox = await sdk.sandbox.create({ + source: 'template', + title: 'my-sandbox', + description: 'My sandbox', + tags: ['my-tag'], + +}) +``` + +## Resume Existing Sandbox + +Resume an existing sandbox by calling `sdk.sandbox.resume(id)`: + +```ts +const sandbox = await sdk.sandbox.resume('sandbox-id'); +``` + +This will resume the sandbox from hibernation and let you connect to it. diff --git a/packages/projects-docs/pages/sdk/create-sandbox.mdx b/packages/projects-docs/pages/sdk/create-sandbox.mdx deleted file mode 100644 index b5265894..00000000 --- a/packages/projects-docs/pages/sdk/create-sandbox.mdx +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Create Sandbox -description: Learn how to create sandboxes with the CodeSandbox SDK. ---- - -import { Callout } from 'nextra-theme-docs' - -# Create Sandbox - -Sandboxes are the main building block of the CodeSandbox SDK. They represent a single project that you can run, fork, and modify. They are backed by a Firecracker VM. Sandboxes are completely isolated, and persisted, so you can securely run untrusted code in them. - -## Creating a Sandbox - -You can create a sandbox by calling `sandbox.create()`: - -```ts -import { CodeSandbox } from '@codesandbox/sdk' -const sdk = new CodeSandbox(); - -const sandbox = await sdk.sandbox.create(); -``` - -If no argument is provided to `sandbox.create()`, we'll create a sandbox based on our [Universal](https://codesandbox.io/p/devbox/universal-pcz35m) template on CodeSandbox. You can also pass in a template id, either from [our collection of templates](/sdk/snapshot-library) or by creating your own snapshot using our [Snapshot Builder](/sdk/snapshot-builder). - -```ts -import { CodeSandbox } from '@codesandbox/sdk' -const sdk = new CodeSandbox(); - -const sandbox = await sdk.sandbox.create({ - source: 'template', - id: 'some-sandbox-id' -}); -``` - -## Reference Existing Sandbox - -Reference an existing sandbox by calling `sdk.sandbox.ref(id)`: - -```ts -const sandbox = sdk.sandbox.ref('sandbox-id'); -``` - -## Create from Git - -You can create a Sandbox from a git repository as well. - -```ts -const sandbox = await sdk.sandbox.create({ - source: 'git', - url: '', - accessToken: '' -}); -``` - -## Create from JSON - -You can create a Sandbox inline as well. We will hash the files and use it as a tag for the Sandbox. That means if a Sandbox already exists with the hash we will rather return that Sandbox. - -```ts -const sandbox = await sdk.sandbox.create({ - source: 'json', - files: { - 'src/index.js': 'console.log("Hello World")' - }, - tasks: {} -}); -``` diff --git a/packages/projects-docs/pages/sdk/create-session.mdx b/packages/projects-docs/pages/sdk/create-session.mdx deleted file mode 100644 index 8da9f3bf..00000000 --- a/packages/projects-docs/pages/sdk/create-session.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Create Session -description: Learn how to create sessions with the CodeSandbox SDK. ---- - -import { Callout } from 'nextra-theme-docs' - -# Create Session - -The default session for a Sandbox is a WebSocket connection. This connection is designed to hold a session over time with minimum overhead to message passing. The WebSocket connection also allows you to listen to changes to ports, shell output etc. - -```ts -const sandbox = await sdk.sandbox.create() -const session = await sandbox.createSession() - -await session.fs.writeTextFile('test.txt', 'Hello World'); -``` - -## Disconnecting the Session - -Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. - -```ts -const sandbox = sdk.sandbox.ref('sandbox-id') -const session = await sandbox.createSession() - -await session.disconnect(); -``` diff --git a/packages/projects-docs/pages/sdk/filesystem.mdx b/packages/projects-docs/pages/sdk/filesystem.mdx index 50b62107..4b7fecc0 100644 --- a/packages/projects-docs/pages/sdk/filesystem.mdx +++ b/packages/projects-docs/pages/sdk/filesystem.mdx @@ -20,7 +20,7 @@ You can read & write files using an api that's similer to the Node.js fs module: ```ts const sandbox = await sdk.sandbox.create() -const session = await sandbox.createSession() +const session = await sandbox.connect() // Writing text files await session.fs.writeTextFile("./hello.txt", "Hello, world!"); @@ -44,19 +44,20 @@ Uploading and downloading files can be done using the same methods as writing an ```ts import fs from "node:fs"; -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() const myBinaryFile = fs.readFileSync("./my-binary-file"); -await sandbox.fs.writeFile("./my-binary-file", myBinaryFile); +await session.fs.writeFile("./my-binary-file", myBinaryFile); -const content = await sandbox.fs.readFile("./my-binary-file"); +const content = await session.fs.readFile("./my-binary-file"); fs.writeFileSync("./my-binary-file", content); ``` You can also download a file or directory by generating a download URL to a zip file. This download URL is valid for 5 minutes: ```ts -const { downloadUrl } = await sandbox.fs.download("./"); +const { downloadUrl } = await session.fs.download("./"); console.log(downloadUrl); ``` @@ -65,7 +66,7 @@ console.log(downloadUrl); You can list files & directories in a directory using the `readdir` method. ```ts -const filesOrDirs = await sandbox.fs.readdir("./"); +const filesOrDirs = await session.fs.readdir("./"); console.log(filesOrDirs); ``` @@ -75,9 +76,9 @@ console.log(filesOrDirs); You can copy, rename & delete files using the `copy`, `rename` & `remove` methods. ```ts -await sandbox.fs.copy("./hello.txt", "./hello-copy.txt"); -await sandbox.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); -await sandbox.fs.remove("./hello-renamed.txt"); +await session.fs.copy("./hello.txt", "./hello-copy.txt"); +await session.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); +await session.fs.remove("./hello-renamed.txt"); ``` ### Watching Files @@ -85,7 +86,7 @@ await sandbox.fs.remove("./hello-renamed.txt"); You can watch files for file changes, additions and deletions using the `watch` method. ```ts -const watcher = await sandbox.fs.watch("./", { recursive: true, excludes: [".git"] }); +const watcher = await session.fs.watch("./", { recursive: true, excludes: [".git"] }); watcher.onEvent((event) => { console.log(event); diff --git a/packages/projects-docs/pages/sdk/fork.mdx b/packages/projects-docs/pages/sdk/fork.mdx index e880d74d..cf62c6e9 100644 --- a/packages/projects-docs/pages/sdk/fork.mdx +++ b/packages/projects-docs/pages/sdk/fork.mdx @@ -13,7 +13,7 @@ import { CodeSandbox } from '@codesandbox/sdk' const sdk = new CodeSandbox(); const sandbox = await sdk.sandbox.create(); -const session = sandbox.createSession() +const session = await sandbox.connect() // Run anything on the sandbox await session.shells.run('echo test > test.txt'); diff --git a/packages/projects-docs/pages/sdk/index.mdx b/packages/projects-docs/pages/sdk/index.mdx index 0fc68148..41ef913a 100644 --- a/packages/projects-docs/pages/sdk/index.mdx +++ b/packages/projects-docs/pages/sdk/index.mdx @@ -43,27 +43,25 @@ Now you can create a sandbox and run a server: import { CodeSandbox } from "@codesandbox/sdk"; const sdk = new CodeSandbox(process.env.CSB_API_KEY!); -const sandbox = await sdk.sandbox.fromTemplate() +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() -// Create a client to interact with the environment -const client = await sandbox.connect() - -await client.shells.python.run("print(1+1)"); -await client.shells.run('echo "Hello World"'); +await session.shells.python.run("print(1+1)"); +await session.shells.run('echo "Hello World"'); // We can also start shells in the background by not awaiting them -const shellInfo = client.shells.run("npx -y serve ."); +const shellInfo = session.shells.run("npx -y serve ."); // Wait for port to open -const portInfo = await client.ports.waitForPort(3000); +const portInfo = await session.ports.waitForPort(3000); console.log(portInfo.getPreviewUrl()); // And, we can clone(!) the sandbox! This takes 1-3s. const sandbox2 = await sandbox.fork() -const client2 = await sandbox2.connect() +const session2 = await sandbox2.connect() // Sandbox 2 will have the same processes running as sandbox 1 -const portInfo2 = await client2.ports.waitForPort(3000); +const portInfo2 = await session2.ports.waitForPort(3000); console.log(portInfo2.getPreviewUrl()); // Finally, we can hibernate the sandbox. This will snapshot the sandbox and stop it. @@ -73,9 +71,9 @@ console.log(portInfo2.getPreviewUrl()); await sandbox2.hibernate(); // Resume the sandbox -const newClient2 = await sandbox2.connect() +const newSession2 = await sandbox2.connect() ``` -The `createWebSocketSession` method connects you to the sandbox with a websocket connection, but you can also use `createBrowserSession`, `createRestSession` or `createSshSession` for other types of sessions. +The `connect` method connects you to the sandbox with a websocket connection, but you can also use `createBrowserSession` or `createRestApi` for other types of connections. diff --git a/packages/projects-docs/pages/sdk/ports.mdx b/packages/projects-docs/pages/sdk/ports.mdx index 56f85ae2..2a70a420 100644 --- a/packages/projects-docs/pages/sdk/ports.mdx +++ b/packages/projects-docs/pages/sdk/ports.mdx @@ -29,6 +29,8 @@ You can obtain a preview token by calling `sandbox.previewTokens.create`. You ca ```ts // Create a preview token with a custom expiration time +const sandbox = await sdk.sandbox.resume('some-id') +const session = await sandbox.connect() const previewToken = await sandbox.previewTokens.create({ expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now }) diff --git a/packages/projects-docs/pages/sdk/restart-shutdown.mdx b/packages/projects-docs/pages/sdk/restart-shutdown.mdx index 68c88497..7507787f 100644 --- a/packages/projects-docs/pages/sdk/restart-shutdown.mdx +++ b/packages/projects-docs/pages/sdk/restart-shutdown.mdx @@ -12,7 +12,6 @@ Restarting a Sandbox starts it from a clean slate. It will install and build its ```ts const sandbox = sdk.sandbox.ref('sandbox-id') - await sandbox.restart(); ``` diff --git a/packages/projects-docs/pages/sdk/sessions.mdx b/packages/projects-docs/pages/sdk/sessions.mdx index 7e356a34..70114939 100644 --- a/packages/projects-docs/pages/sdk/sessions.mdx +++ b/packages/projects-docs/pages/sdk/sessions.mdx @@ -7,23 +7,15 @@ import { Callout } from 'nextra-theme-docs' # Sessions -To interact with a Sandbox you have to create a session. The SDK offers different types of sessions depending on your needs: - -- **WebSocket**: A long lived WebSocket connection with minimum overhead to message passing. Can listen to port changes, shell outputs etc. Can be used in the browser -- **Browser**: The same long lived WebSocket connection, but in the browser -- **Rest**: When you do not want to manage a connection, but rather do one off request/response -- **Ssh**: When you want to interact with the full environment at low level - By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. ```ts const sandbox = await sdk.sandbox.create() -const session = await sandbox.createSession() -const client = await sandbox.connect() +const session = await sandbox.connect() // Now I have a session where I act as `pitcher-host` user. // Any action I do will be done as `pitcher-host` user. -await client.fs.writeTextFile('test.txt', 'Hello World'); +await session.fs.writeTextFile('test.txt', 'Hello World'); ``` @@ -35,8 +27,8 @@ But you can choose to create your own user sessions with specific permissions. L ```ts // Now create a new session that only has read access to the sandbox const sandbox = await sdk.sandbox.create() -const session = await sandbox.createWebSocketSession({ - user: 'anonymous', +const session = await sandbox.connect({ + id: 'anonymous', permission: 'read' }) @@ -46,8 +38,8 @@ await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an await session.fs.readTextFile('test.txt'); // This will work. // I can also create sessions with write access -const session2 = await sandbox.createWebSocketSession({ - user: 'some-user-reference', +const session2 = await sandbox.connect({ + id: 'some-user-reference', permission: 'write', }); await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work @@ -63,16 +55,15 @@ The `~/.private` folder will not be persisted between restarts of the sandbox. T ## Sessions in the Browser -If you want your users to connect to the Sandbox from the browser you can rather create a browser session and pass it to the client. +If you want your users to connect to the Sandbox from the browser you can rather create an endpoint that responds with a browser session: ```ts -const sandbox = await sdk.sandbox.ref('sandbox-id'); +export const GET = async ({ params }) => { + const sandbox = await sdk.sandbox.resume(params.sandboxId); + const session = await sandbox.createBrowserSession(); -// Pass this session to the browser -const session = await sandbox.createBrowserSession({ - user: 'some-user-reference', - permission: 'write' -}); + return session +} ``` And in the browser: @@ -80,7 +71,11 @@ And in the browser: ```ts import { connectToSandbox } from '@codesandbox/sdk/browser'; -const session = await connectToSandbox(sessionFromServer); +const session = await connectToSandbox(() => + fetch(`/api/sandboxes/123`).then(res => res.json()) +); + +await session.fs.writeTextFile('test.txt', 'Hello World'); ``` ## Sessions using Rest @@ -89,23 +84,8 @@ The sandboxes also expose a Rest api which can be a more useful protocol to use is not as extensive, but supports most features. ```ts -const sandbox = sdk.sandbox.ref('sandbox-id') -const session = sandbox.createRestSession({ - user: 'some-user-reference', - permission: 'write' -}) +const sandbox = await sdk.sandbox.resume('sandbox-id') +const session = await sandbox.createRestSession() await session.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) ``` - -## Sessions using Ssh - -Not sure the use case here or how it works. - -```ts -const sandbox = sdk.sandbox.ref('sandbox-id') -const session = sandbox.createSshSession({ - user: 'some-user-reference', - permission: 'write' -}) -``` diff --git a/packages/projects-docs/pages/sdk/shells.mdx b/packages/projects-docs/pages/sdk/shells.mdx index fec41942..4fe13a6f 100644 --- a/packages/projects-docs/pages/sdk/shells.mdx +++ b/packages/projects-docs/pages/sdk/shells.mdx @@ -18,10 +18,11 @@ The Shell API is available under `sandbox.shells`. It provides methods for creat You can create an interactive shell that allows you to send commands and receive output: ```ts -const sandbox = await sdk.sandbox.create().connect() +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Create a new shell (bash is default) -const shell = sandbox.shells.create('bash') +const shell = session.shells.create('bash') // Listen to shell output const shellListenDisposer = shell.onOutput((output) => { @@ -47,10 +48,11 @@ We recommend to use `sandbox.shells.create` when you want to create an interacti For simple command execution, you can use the `run` method which returns a promise with the command's output: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Run a single command -const command = sandbox.shells.run("npm install"); +const command = session.shells.run("npm install"); // Listen to real-time output command.onOutput((output) => { @@ -72,15 +74,16 @@ console.log(result.output, result.exitCode); The Shell API includes built-in support for running code in different programming languages: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Run JavaScript code -const jsResult = await sandbox.shells.js.run(` +const jsResult = await session.shells.js.run(` console.log("Hello from Node.js!"); `); // Run Python code -const pythonResult = await sandbox.shells.python.run(` +const pythonResult = await session.shells.python.run(` print("Hello from Python!") `); ``` @@ -92,13 +95,14 @@ These interpreters are built on top of the `run` method, so you can use the same You can list and reconnect to existing shells: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Get all shells -const shells = await sandbox.shells.getShells(); +const shells = await session.shells.getShells(); // Reconnect to an existing shell -const shell = await sandbox.shells.open(shellId); +const shell = await session.shells.open(shellId); // Check shell status and info console.log(shell.status); // "RUNNING" | "FINISHED" | "ERROR" | "KILLED" | "RESTARTING" @@ -111,10 +115,11 @@ console.log(shell.getOutput()); ### Starting a server and waiting for the port to open ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Run in background by not awaiting the command -const shell = sandbox.shells.run("npx -y serve ."); +const shell = session.shells.run("npx -y serve ."); const portInfo = await shell.waitForPort(3000); diff --git a/packages/projects-docs/pages/sdk/specs.mdx b/packages/projects-docs/pages/sdk/specs.mdx index 5f87cb6d..95a977e0 100644 --- a/packages/projects-docs/pages/sdk/specs.mdx +++ b/packages/projects-docs/pages/sdk/specs.mdx @@ -18,13 +18,17 @@ You can start a sandbox with a specific VM tier by passing the `vmTier` option t import { CodeSandbox, VMTier } from "@codesandbox/sdk"; const sdk = new CodeSandbox(); -const sandbox = await sdk.sandbox.create({ vmTier: VMTier.Small }); +const sandbox = await sdk.sandbox.create({ + source: 'template', + vmTier: VMTier.Small +}); ``` You can also approximate the VM size: ```ts const sandbox = await sdk.sandbox.create({ + source: 'template', vmTier: VMTier.fromSpecs({ cpu: 4, memGiB: 8 }), }); ``` diff --git a/packages/projects-docs/pages/sdk/update-sandbox.mdx b/packages/projects-docs/pages/sdk/update-sandbox.mdx index 1b9a1794..85ad71aa 100644 --- a/packages/projects-docs/pages/sdk/update-sandbox.mdx +++ b/packages/projects-docs/pages/sdk/update-sandbox.mdx @@ -13,10 +13,9 @@ When a new version of the agent is published existing sandboxes needs to restart You can check if the current sandbox is up to date using `isUpToDate()` ```ts -const sandbox = sdk.sandbox.ref('sandbox-id') -const isUpToDate = await sandbox.isUpToDate() +const sandbox = await sdk.sandbox.resume('sandbox-id') -if (!isUpToDate) { +if (!sandbox.isUpToDate) { await sandbox.restart() } ``` From b833d70d43ad272b69ec4685bfed8d96aa9c4e24 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 10:45:10 +0200 Subject: [PATCH 04/18] fix preview tokens --- packages/projects-docs/pages/sdk/_meta.json | 5 +- .../projects-docs/pages/sdk/create-resume.mdx | 1 + .../projects-docs/pages/sdk/filesystem.mdx | 24 +-- .../projects-docs/pages/sdk/hibernate.mdx | 2 +- packages/projects-docs/pages/sdk/ports.mdx | 142 +++--------------- .../pages/sdk/preview-tokens.mdx | 114 ++++++++++++++ .../pages/sdk/restart-shutdown.mdx | 4 +- .../{sessions.mdx => sessions-clients.mdx} | 43 +++--- 8 files changed, 177 insertions(+), 158 deletions(-) create mode 100644 packages/projects-docs/pages/sdk/preview-tokens.mdx rename packages/projects-docs/pages/sdk/{sessions.mdx => sessions-clients.mdx} (69%) diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index 145b087b..3dca4d8f 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -13,7 +13,8 @@ "title": "Sandboxes" }, "create-resume": "Create & Resume", - "sessions": "Sessions", + "sessions-clients": "Sessions & Clients", + "preview-tokens": "Preview Tokens", "hibernate": "Hibernate", "fork": "Fork", "restart-shutdown": "Restart & Shutdown", @@ -28,7 +29,7 @@ "connect": "Connect", "filesystem": "File System", "shells": "Shells", - "ports": "Ports & Previews", + "ports": "Ports", "tasks": "Tasks & Setup", "-- rest-session": { diff --git a/packages/projects-docs/pages/sdk/create-resume.mdx b/packages/projects-docs/pages/sdk/create-resume.mdx index e9f1595f..1afbb309 100644 --- a/packages/projects-docs/pages/sdk/create-resume.mdx +++ b/packages/projects-docs/pages/sdk/create-resume.mdx @@ -27,6 +27,7 @@ The `sandbox` object represents the instance of a sandbox running in our infrast - `id`: The unique identifier of the sandbox. - `isUpToDate`: Whether the sandbox is up to date with the latest agent version. - `cluster`: The cluster the sandbox is running on. +- `bootupType`: The type of bootup, `RUNNING`, `CLEAN`, `RESUME` or `FORK`. ### Create from Template diff --git a/packages/projects-docs/pages/sdk/filesystem.mdx b/packages/projects-docs/pages/sdk/filesystem.mdx index 4b7fecc0..7213109b 100644 --- a/packages/projects-docs/pages/sdk/filesystem.mdx +++ b/packages/projects-docs/pages/sdk/filesystem.mdx @@ -20,20 +20,20 @@ You can read & write files using an api that's similer to the Node.js fs module: ```ts const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() +const client = await sandbox.connect() // Writing text files -await session.fs.writeTextFile("./hello.txt", "Hello, world!"); +await client.fs.writeTextFile("./hello.txt", "Hello, world!"); // Reading text files -const content = await session.fs.readTextFile("./hello.txt"); +const content = await client.fs.readTextFile("./hello.txt"); console.log(content); // Writing binary files -await session.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); +await client.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); // Reading binary files -const content = await session.fs.readFile("./hello.bin"); +const content = await client.fs.readFile("./hello.bin"); console.log(content); ``` @@ -45,12 +45,12 @@ Uploading and downloading files can be done using the same methods as writing an import fs from "node:fs"; const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() +const client = await sandbox.connect() const myBinaryFile = fs.readFileSync("./my-binary-file"); -await session.fs.writeFile("./my-binary-file", myBinaryFile); +await client.fs.writeFile("./my-binary-file", myBinaryFile); -const content = await session.fs.readFile("./my-binary-file"); +const content = await client.fs.readFile("./my-binary-file"); fs.writeFileSync("./my-binary-file", content); ``` @@ -66,7 +66,7 @@ console.log(downloadUrl); You can list files & directories in a directory using the `readdir` method. ```ts -const filesOrDirs = await session.fs.readdir("./"); +const filesOrDirs = await client.fs.readdir("./"); console.log(filesOrDirs); ``` @@ -76,9 +76,9 @@ console.log(filesOrDirs); You can copy, rename & delete files using the `copy`, `rename` & `remove` methods. ```ts -await session.fs.copy("./hello.txt", "./hello-copy.txt"); -await session.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); -await session.fs.remove("./hello-renamed.txt"); +await client.fs.copy("./hello.txt", "./hello-copy.txt"); +await client.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); +await client.fs.remove("./hello-renamed.txt"); ``` ### Watching Files diff --git a/packages/projects-docs/pages/sdk/hibernate.mdx b/packages/projects-docs/pages/sdk/hibernate.mdx index 90910c92..2635cde8 100644 --- a/packages/projects-docs/pages/sdk/hibernate.mdx +++ b/packages/projects-docs/pages/sdk/hibernate.mdx @@ -10,7 +10,7 @@ import { Callout } from 'nextra-theme-docs' When you're done with a sandbox, you can hibernate it. This will save the memory state of the sandbox, so it will resume from the same state when you start it again. ```ts -const sandbox = sdk.sandbox.ref('sandbox-id') +const sandbox = await sdk.sandbox.resume('sandbox-id') await sandbox.hibernate(); ``` diff --git a/packages/projects-docs/pages/sdk/ports.mdx b/packages/projects-docs/pages/sdk/ports.mdx index 2a70a420..0b9668ea 100644 --- a/packages/projects-docs/pages/sdk/ports.mdx +++ b/packages/projects-docs/pages/sdk/ports.mdx @@ -12,116 +12,11 @@ The Ports API allows you to monitor and interact with HTTP ports in your sandbox Whenever a port is opened within a sandbox, we'll automatically expose it under `https://-.csb.app`. -If the sandbox is private, we'll ask the user to sign in to open the preview. We're currently working on an API to allow creating signed URLs for private sandboxes, or selecting which ports are exposed and which are closed. +If the sandbox is private you need to use the [preview tokens](./preview-tokens). Also, we'll automatically resume a sandbox whenever a port is accessed while the sandbox is hibernated. -## Private Previews (Preview Tokens) - -If a sandbox is private, its previews won't be accessible unless a preview token is provided. A preview token can be provided through: - -- The url: `https://:id-:port.csb.app?preview_token=:token` -- A header: `csb-preview-token: :token` -- A cookie: `csb_preview_token=:token` - -You can obtain a preview token by calling `sandbox.previewTokens.create`. You can also set a custom expiration time for the token: - -```ts -// Create a preview token with a custom expiration time -const sandbox = await sdk.sandbox.resume('some-id') -const session = await sandbox.connect() -const previewToken = await sandbox.previewTokens.create({ - expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now -}) - -// Let's say that the sandbox is running something on port 3000 -const port = await sandbox.ports.waitForPort(3000); - -// Get the signed preview URL for that port using the preview token we just generated -const signedUrl = port.getSignedPreviewUrl(previewToken.token); - -// This URL will have access to the sandbox until the token expires -console.log(signedUrl); - -// When you open this URL in the browser, we will automatically put the preview token in a -// cookie so subsequent requests from the same browser don't require the token. -``` - -If you want to revoke a token, you can call `sandbox.previewTokens.list` to get the token's ID and then call `sandbox.previewTokens.revoke` with the ID. - -### Managing Preview Tokens (CLI) - -The CLI allows you to manage preview tokens of a sandbox. You can list, revoke and update preview tokens. - -``` -$ csb sandbox preview-tokens :sandbox-id list - ID PREFIX LAST USED EXPIRES - prv_Hca52PUyFHVJsGkciXXbEq prv_v1_8uKY Never Never - prv_HbE8wC6veXWwcazFYrdUfy prv_v1__ki7 Never Never - prv_MWMhMjbQiY3jSagfaG6D7R prv_v1_i7iI Never Never -``` - -You can also revoke a preview token this way: - -``` -$ csb sandbox preview-tokens :sandbox-id revoke prv_Hca52PUyFHVJsGkciXXbEq -``` - -If you want to revoke all preview tokens for a sandbox, you can do so with: - -``` -$ csb sandbox preview-tokens :sandbox-id revoke --all -``` - -And you can update the expiration time of a preview token: - -``` -$ csb sandbox preview-tokens :sandbox-id update prv_Hca52PUyFHVJsGkciXXbEq --expires-at 2025-03-01 -``` - -### Running from the Browser - -When you use the SDK [in the browser](https://codesandbox.io/docs/sdk/sandboxes#opening-a-sandbox-from-the-browser), you cannot generate a preview token from the browser, because it requires an API key. - -Instead, you can generate a preview token on the server, and then pass that to the browser. - -```ts -const previewToken = await sdk.sandbox.previewTokens.create('sandbox-id'); - -// Then pass `previewToken.token` to the browser -// In the browser -const token = '...'; - -const port = await sandbox.ports.waitForPort(3000); -const signedUrl = port.getSignedPreviewUrl(token); -``` - -### Using the Token for API Requests - -In some cases, a private sandbox might be running an API server that you want to access. In those cases, you should put the preview token in the `csb-preview-token` header. - -```ts -const sandbox = await sdk.sandbox.create().connect() - -// Start a development server -sandbox.shells.run("npm run dev"); - -// Wait for the dev server port to open -const portInfo = await sandbox.ports.waitForPort(3000); - -// Generate the preview token -const previewToken = await sandbox.previewTokens.create(); - -const url = portInfo.getPreviewUrl(); - -const response = await fetch(url, { - headers: { - 'csb-preview-token': previewToken.token, - }, -}); -``` - ## API The Ports API is available under `sandbox.ports`. It provides methods for monitoring port activity and getting preview URLs for web services. @@ -131,16 +26,17 @@ The Ports API is available under `sandbox.ports`. It provides methods for monito You can listen for ports being opened and closed in your sandbox: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() // Listen for ports being opened -const listener1 = sandbox.ports.onDidPortOpen((portInfo) => { +const listener1 = client.ports.onDidPortOpen((portInfo) => { console.log(`Port ${portInfo.port} opened`); console.log(`Preview URL: ${portInfo.getPreviewUrl()}`); }); // Listen for ports being closed -const listener2 = sandbox.ports.onDidPortClose((port) => { +const listener2 = client.ports.onDidPortClose((port) => { console.log(`Port ${port} closed`); }); @@ -154,16 +50,17 @@ listener2.dispose(); You can get information about currently opened ports: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() // Get all opened ports -const openPorts = sandbox.ports.getOpenedPorts(); +const openPorts = client.ports.getOpenedPorts(); for (const port of openPorts) { console.log(`Port ${port.port} is open at ${port.hostname}`); } // Get preview URL for a specific port -const previewUrl = sandbox.ports.getPreviewUrl(3000); +const previewUrl = client.ports.getPreviewUrl(3000); if (previewUrl) { console.log(`Preview available at: ${previewUrl}`); } @@ -174,13 +71,14 @@ if (previewUrl) { When starting services, you often need to wait for a port to become available: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() // Start a development server -sandbox.shells.run("npm run dev"); +client.shells.run("npm run dev"); // Wait for the dev server port to open -const portInfo = await sandbox.ports.waitForPort(3000); +const portInfo = await client.ports.waitForPort(3000); console.log(`Dev server is ready at: ${portInfo.getPreviewUrl()}`); ``` @@ -191,13 +89,14 @@ console.log(`Dev server is ready at: ${portInfo.getPreviewUrl()}`); Here's a complete example of starting a web server and getting its preview URL: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() // Start the server -sandbox.shells.run("npx serve -y ."); +client.shells.run("npx serve -y ."); // Wait for the server to be ready -const portInfo = await sandbox.ports.waitForPort(3000); +const portInfo = await client.ports.waitForPort(3000); // Get the preview URL with custom protocol const httpUrl = portInfo.getPreviewUrl("http://"); @@ -213,10 +112,11 @@ console.log(`Server is running at: When working with multiple services, you might want to monitor several ports: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() // Start monitoring before launching services -sandbox.ports.onDidPortOpen((portInfo) => { +client.ports.onDidPortOpen((portInfo) => { switch (portInfo.port) { case 3000: console.log("Frontend server ready"); @@ -231,5 +131,5 @@ sandbox.ports.onDidPortOpen((portInfo) => { }); // Start your services -sandbox.shells.run("npm run start:all"); +client.shells.run("npm run start:all"); ``` diff --git a/packages/projects-docs/pages/sdk/preview-tokens.mdx b/packages/projects-docs/pages/sdk/preview-tokens.mdx new file mode 100644 index 00000000..e2bd4321 --- /dev/null +++ b/packages/projects-docs/pages/sdk/preview-tokens.mdx @@ -0,0 +1,114 @@ +--- +title: Preview Tokens +description: Learn about preview tokens in CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Preview Tokens + +If a sandbox is private, its previews won't be accessible unless a preview token is provided. A preview token can be provided through: + +- The url: `https://:id-:port.csb.app?preview_token=:token` +- A header: `csb-preview-token: :token` +- A cookie: `csb_preview_token=:token` + +You can obtain a preview token by calling `sandbox.previewTokens.create`. You can also set a custom expiration time for the token: + +```ts +const sandbox = await sdk.sandbox.resume('some-id') +const previewToken = await sandbox.previewTokens.create({ + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now +}) + +// Pass in the port and token to create the url for +const signedUrl = sandbox.previewTokens.getSignedPreviewUrl(3000, previewToken.token) + +// This URL will have access to the sandbox until the token expires +console.log(signedUrl); +``` + + +When you open a signed URL in the browser, we will automatically put the preview token in a +cookie so subsequent requests from the same browser don't require the token. + + +If you want to revoke a token, you can call `sandbox.previewTokens.list` to get the token's ID and then call `sandbox.previewTokens.revoke` with the ID. + +### Managing Preview Tokens (CLI) + +The CLI allows you to manage preview tokens of a sandbox. You can list, revoke and update preview tokens. + +``` +$ csb sandbox preview-tokens :sandbox-id list + ID PREFIX LAST USED EXPIRES + prv_Hca52PUyFHVJsGkciXXbEq prv_v1_8uKY Never Never + prv_HbE8wC6veXWwcazFYrdUfy prv_v1__ki7 Never Never + prv_MWMhMjbQiY3jSagfaG6D7R prv_v1_i7iI Never Never +``` + +You can also revoke a preview token this way: + +``` +$ csb sandbox preview-tokens :sandbox-id revoke prv_Hca52PUyFHVJsGkciXXbEq +``` + +If you want to revoke all preview tokens for a sandbox, you can do so with: + +``` +$ csb sandbox preview-tokens :sandbox-id revoke --all +``` + +And you can update the expiration time of a preview token: + +``` +$ csb sandbox preview-tokens :sandbox-id update prv_Hca52PUyFHVJsGkciXXbEq --expires-at 2025-03-01 +``` + +### Running from the Browser + +When you use the SDK [in the browser](https://codesandbox.io/docs/sdk/sandboxes#opening-a-sandbox-from-the-browser), you cannot generate a preview token from the browser, because it requires an API key. + +Instead, you can generate a preview token on the server, and then pass that to the browser. + +```ts + +export const POST = async ({ params, body }) => { + const sandbox = sdk.sandbox.ref(params.id) + const previewToken = await sandbox.previewTokens.create(); + + return sandbox.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) +} + +// In the browser ask for the signed url when it makes most sense, here +// asking for it while waiting for the port to open +const [signedUrl] = await Promise.all([ + fetchSignedUrlFromServer(3000), + client.ports.waitForPort(3000) +]) +``` + +### Using the Token for API Requests + +In some cases, a private sandbox might be running an API server that you want to access. In those cases, you should put the preview token in the `csb-preview-token` header. + +```ts +const sandbox = await sdk.sandbox.create() +const client = await sandbox.connect() + +// Start a development server +client.shells.run("npm run dev"); + +// Wait for the dev server port to open +const portInfo = await client.ports.waitForPort(3000); + +// Generate the preview token +const previewToken = await sandbox.previewTokens.create(); +const url = 'https://' + portInfo.url + +const response = await fetch(url, { + headers: { + 'csb-preview-token': previewToken.token, + }, +}); +``` diff --git a/packages/projects-docs/pages/sdk/restart-shutdown.mdx b/packages/projects-docs/pages/sdk/restart-shutdown.mdx index 7507787f..abb9ad73 100644 --- a/packages/projects-docs/pages/sdk/restart-shutdown.mdx +++ b/packages/projects-docs/pages/sdk/restart-shutdown.mdx @@ -10,7 +10,7 @@ description: Learn how to restart and shutdown sandboxes with the CodeSandbox SD Restarting a Sandbox starts it from a clean slate. It will install and build its configured resources and also run with the last version of the agent. ```ts -const sandbox = sdk.sandbox.ref('sandbox-id') +const sandbox = await sdk.sandbox.resume('sandbox-id') await sandbox.restart(); ``` @@ -20,7 +20,7 @@ await sandbox.restart(); You can also shutdown a sandbox. This will shut down the sandbox without creating a memory snapshot. Next time the sandbox is started, it will boot from a clean state (but your files in `/project/sandbox` will be preserved). ```ts -const sandbox = sdk.sandbox.ref('sandbox-id') +const sandbox = await sdk.sandbox.resume('sandbox-id') await sandbox.shutdown(); ``` diff --git a/packages/projects-docs/pages/sdk/sessions.mdx b/packages/projects-docs/pages/sdk/sessions-clients.mdx similarity index 69% rename from packages/projects-docs/pages/sdk/sessions.mdx rename to packages/projects-docs/pages/sdk/sessions-clients.mdx index 70114939..cb0ea000 100644 --- a/packages/projects-docs/pages/sdk/sessions.mdx +++ b/packages/projects-docs/pages/sdk/sessions-clients.mdx @@ -1,21 +1,21 @@ --- -title: Sessions -description: Sessions allow you to create multiple users inside a single sandbox. +title: Sessions & Clients +description: Learn how to connect to Sandboxes with the CodeSandbox SDK. --- import { Callout } from 'nextra-theme-docs' -# Sessions +# Sessions & Clients By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. ```ts const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() +const client = await sandbox.connect() // Now I have a session where I act as `pitcher-host` user. // Any action I do will be done as `pitcher-host` user. -await session.fs.writeTextFile('test.txt', 'Hello World'); +await client.fs.writeTextFile('test.txt', 'Hello World'); ``` @@ -25,27 +25,27 @@ When you run `whoami`, it will say you're `root`. That's because we run every se But you can choose to create your own user sessions with specific permissions. Let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. ```ts -// Now create a new session that only has read access to the sandbox +// Now configure a session that only has read access to the sandbox const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect({ +const client = await sandbox.connect({ id: 'anonymous', permission: 'read' }) // I have access to the normal sandbox api, but I only have read access // This means I cannot write files or open shells, but I _can_ read them -await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an error. -await session.fs.readTextFile('test.txt'); // This will work. +await client.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an error. +await client.fs.readTextFile('test.txt'); // This will work. // I can also create sessions with write access -const session2 = await sandbox.connect({ +const client2 = await sandbox.connect({ id: 'some-user-reference', permission: 'write', }); -await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work +await client2.fs.writeTextFile('test.ext', 'Hello World'); // This will work ``` -## Storage +### Storage Every session will have the same filesystem as the global session. This means that if one user creates a file inside the workspace folder (or even the home folder), other users will be able to see it. There is one exception to this: the `~/.private` folder will be private for each session. No other session (including the global session) will be able to read or write to files inside this folder. @@ -53,13 +53,14 @@ Every session will have the same filesystem as the global session. This means th The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. -## Sessions in the Browser +## Browser Client If you want your users to connect to the Sandbox from the browser you can rather create an endpoint that responds with a browser session: ```ts export const GET = async ({ params }) => { const sandbox = await sdk.sandbox.resume(params.sandboxId); + // Also default to global session, but can configure custom session const session = await sandbox.createBrowserSession(); return session @@ -71,21 +72,23 @@ And in the browser: ```ts import { connectToSandbox } from '@codesandbox/sdk/browser'; -const session = await connectToSandbox(() => - fetch(`/api/sandboxes/123`).then(res => res.json()) -); +const client = await connectToSandbox({ + id: 'some-sandbox', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()) +}); -await session.fs.writeTextFile('test.txt', 'Hello World'); +await client.fs.writeTextFile('test.txt', 'Hello World'); ``` -## Sessions using Rest +## Rest Client The sandboxes also expose a Rest api which can be a more useful protocol to use as you do not have to manage an ongoing connection. The Rest session is not as extensive, but supports most features. ```ts const sandbox = await sdk.sandbox.resume('sandbox-id') -const session = await sandbox.createRestSession() +// Also defaults to global session, but can configure custom session +const client = await sandbox.createRestClient() -await session.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) +await client.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) ``` From 527060f21236d51bb233c122210b1f50c0cea40d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 15:00:14 +0200 Subject: [PATCH 05/18] updated docs --- .../projects-docs/pages/sdk/create-resume.mdx | 2 +- packages/projects-docs/pages/sdk/fork.mdx | 4 +-- .../projects-docs/pages/sdk/hibernate.mdx | 2 +- packages/projects-docs/pages/sdk/index.mdx | 26 +++++++++---------- .../pages/sdk/preview-tokens.mdx | 15 +++++------ .../pages/sdk/restart-shutdown.mdx | 4 +-- packages/projects-docs/pages/sdk/specs.mdx | 2 ++ .../pages/sdk/update-sandbox.mdx | 2 +- 8 files changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/projects-docs/pages/sdk/create-resume.mdx b/packages/projects-docs/pages/sdk/create-resume.mdx index 1afbb309..cc360a19 100644 --- a/packages/projects-docs/pages/sdk/create-resume.mdx +++ b/packages/projects-docs/pages/sdk/create-resume.mdx @@ -22,7 +22,7 @@ const sandbox = await sdk.sandbox.create(); If no argument is provided to `sandbox.create()`, we'll create a sandbox based on our [Universal](https://codesandbox.io/p/devbox/universal-pcz35m) template on CodeSandbox. You can also pass in a specific template id from [our collection of templates](/sdk/snapshot-library) or by creating your own snapshot using our [Snapshot Builder](/sdk/snapshot-builder). Additionally you can choose other sources like [Git](/sdk/sandbox#git) or [Files](/sdk/sandbox#files). -The `sandbox` object represents the instance of a sandbox running in our infrastructure. You can use it to manage the sandbox and connect to it. In addition to its methods it has the following properties: +The `sandbox` object represents the instance of a sandbox running in our infrastructure. The methods available here requires the Sandbox to be running. Additionally you have: - `id`: The unique identifier of the sandbox. - `isUpToDate`: Whether the sandbox is up to date with the latest agent version. diff --git a/packages/projects-docs/pages/sdk/fork.mdx b/packages/projects-docs/pages/sdk/fork.mdx index cf62c6e9..c570a8ea 100644 --- a/packages/projects-docs/pages/sdk/fork.mdx +++ b/packages/projects-docs/pages/sdk/fork.mdx @@ -18,7 +18,7 @@ const session = await sandbox.connect() // Run anything on the sandbox await session.shells.run('echo test > test.txt'); -const sandbox2 = await sandbox.fork(); +const sandbox2 = await sdk.sandbox.fork(sandbox.id); // Now we have two sandboxes that have the same fs & memory state! ``` @@ -37,7 +37,7 @@ const sandbox = await sdk.sandbox.create(); // Do work -await sandbox.hibernate(); +await sdk.sandbox.hibernate(sandbox.id); ``` Creating a memory snapshot can take between 3-10 seconds. Resuming from a memory snapshot takes between 0.5-2 seconds. diff --git a/packages/projects-docs/pages/sdk/hibernate.mdx b/packages/projects-docs/pages/sdk/hibernate.mdx index 2635cde8..1139001b 100644 --- a/packages/projects-docs/pages/sdk/hibernate.mdx +++ b/packages/projects-docs/pages/sdk/hibernate.mdx @@ -12,7 +12,7 @@ When you're done with a sandbox, you can hibernate it. This will save the memory ```ts const sandbox = await sdk.sandbox.resume('sandbox-id') -await sandbox.hibernate(); +await sdk.sandbox.hibernate(sandbox.id); ``` When creating or restarting a sandbox, you can also set a hibernation timeout between 1 minute and 24 hours. By default this timeout is 5 minutes for free users, and 30 minutes for paid users. diff --git a/packages/projects-docs/pages/sdk/index.mdx b/packages/projects-docs/pages/sdk/index.mdx index 41ef913a..a3c7ddfc 100644 --- a/packages/projects-docs/pages/sdk/index.mdx +++ b/packages/projects-docs/pages/sdk/index.mdx @@ -44,36 +44,36 @@ import { CodeSandbox } from "@codesandbox/sdk"; const sdk = new CodeSandbox(process.env.CSB_API_KEY!); const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() +const client = await sandbox.connect() -await session.shells.python.run("print(1+1)"); -await session.shells.run('echo "Hello World"'); +await client.shells.python.run("print(1+1)"); +await client.shells.run('echo "Hello World"'); // We can also start shells in the background by not awaiting them -const shellInfo = session.shells.run("npx -y serve ."); +const shellInfo = client.shells.run("npx -y serve ."); // Wait for port to open -const portInfo = await session.ports.waitForPort(3000); -console.log(portInfo.getPreviewUrl()); +const portInfo = await client.ports.waitForPort(3000); +console.log(portInfo.url); // And, we can clone(!) the sandbox! This takes 1-3s. -const sandbox2 = await sandbox.fork() -const session2 = await sandbox2.connect() +const sandbox2 = await sdk.sandbox.fork(sandbox.id) +const client2 = await sandbox2.connect() // Sandbox 2 will have the same processes running as sandbox 1 -const portInfo2 = await session2.ports.waitForPort(3000); -console.log(portInfo2.getPreviewUrl()); +const portInfo2 = await client2.ports.waitForPort(3000); +console.log(portInfo2.url); // Finally, we can hibernate the sandbox. This will snapshot the sandbox and stop it. // Next time you start the sandbox, it will continue where it left off, as we created a memory snapshot. // The sandboxes will also automatically resume if a network request comes in for the // servers they have started. -await sandbox2.hibernate(); +await sdk.sandbox.hibernate(sandbox2.id); // Resume the sandbox -const newSession2 = await sandbox2.connect() +const resumedSandbox = await sdk.sandbox.resume(sandbox2.id) ``` -The `connect` method connects you to the sandbox with a websocket connection, but you can also use `createBrowserSession` or `createRestApi` for other types of connections. +The `connect` method connects you to the sandbox with a websocket connection, but you can also use `createBrowserSession` or `createRestClient` for other types of connections. diff --git a/packages/projects-docs/pages/sdk/preview-tokens.mdx b/packages/projects-docs/pages/sdk/preview-tokens.mdx index e2bd4321..cefde4a6 100644 --- a/packages/projects-docs/pages/sdk/preview-tokens.mdx +++ b/packages/projects-docs/pages/sdk/preview-tokens.mdx @@ -16,13 +16,12 @@ If a sandbox is private, its previews won't be accessible unless a preview token You can obtain a preview token by calling `sandbox.previewTokens.create`. You can also set a custom expiration time for the token: ```ts -const sandbox = await sdk.sandbox.resume('some-id') -const previewToken = await sandbox.previewTokens.create({ +const previewToken = await sdk.sandbox.previewTokens.create('some-sandbox-id', { expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now }) -// Pass in the port and token to create the url for -const signedUrl = sandbox.previewTokens.getSignedPreviewUrl(3000, previewToken.token) +// Pass in the port and token to create the url for the a preview +const signedUrl = sdk.sandbox.previewTokens.getSignedPreviewUrl(3000, previewToken.token) // This URL will have access to the sandbox until the token expires console.log(signedUrl); @@ -72,12 +71,10 @@ When you use the SDK [in the browser](https://codesandbox.io/docs/sdk/sandboxes# Instead, you can generate a preview token on the server, and then pass that to the browser. ```ts - export const POST = async ({ params, body }) => { - const sandbox = sdk.sandbox.ref(params.id) - const previewToken = await sandbox.previewTokens.create(); + const previewToken = await sdk.sandbox.previewTokens.create(params.id); - return sandbox.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) + return sdk.sandbox.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) } // In the browser ask for the signed url when it makes most sense, here @@ -103,7 +100,7 @@ client.shells.run("npm run dev"); const portInfo = await client.ports.waitForPort(3000); // Generate the preview token -const previewToken = await sandbox.previewTokens.create(); +const previewToken = await sdk.sandbox.previewTokens.create(sandbox.id); const url = 'https://' + portInfo.url const response = await fetch(url, { diff --git a/packages/projects-docs/pages/sdk/restart-shutdown.mdx b/packages/projects-docs/pages/sdk/restart-shutdown.mdx index abb9ad73..477d9345 100644 --- a/packages/projects-docs/pages/sdk/restart-shutdown.mdx +++ b/packages/projects-docs/pages/sdk/restart-shutdown.mdx @@ -12,7 +12,7 @@ Restarting a Sandbox starts it from a clean slate. It will install and build its ```ts const sandbox = await sdk.sandbox.resume('sandbox-id') -await sandbox.restart(); +await sdk.sandbox.restart(sandbox.id); ``` ## Shutdown @@ -22,7 +22,7 @@ You can also shutdown a sandbox. This will shut down the sandbox without creatin ```ts const sandbox = await sdk.sandbox.resume('sandbox-id') -await sandbox.shutdown(); +await sdk.sandbox.shutdown(sandbox.id); ``` Generally you should shutdown a sandbox if you want to start from a clean state. diff --git a/packages/projects-docs/pages/sdk/specs.mdx b/packages/projects-docs/pages/sdk/specs.mdx index 95a977e0..ce6e5761 100644 --- a/packages/projects-docs/pages/sdk/specs.mdx +++ b/packages/projects-docs/pages/sdk/specs.mdx @@ -40,6 +40,8 @@ This will pick the smallest VM tier that can fit the specs you provided. You can change the VM specs of a running sandbox by calling the `sandbox.updateTier` method: ```ts +const sandbox = await sdk.sandbox.resume('some-id') + await sandbox.updateTier(VMTier.Medium); ``` diff --git a/packages/projects-docs/pages/sdk/update-sandbox.mdx b/packages/projects-docs/pages/sdk/update-sandbox.mdx index 85ad71aa..d54e3aa4 100644 --- a/packages/projects-docs/pages/sdk/update-sandbox.mdx +++ b/packages/projects-docs/pages/sdk/update-sandbox.mdx @@ -16,7 +16,7 @@ You can check if the current sandbox is up to date using `isUpToDate()` const sandbox = await sdk.sandbox.resume('sandbox-id') if (!sandbox.isUpToDate) { - await sandbox.restart() + await sdk.sandbox.restart(sandbox.id) } ``` From c8bcdfceca8ce9aaa400da4906d086c199bb9f88 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 30 Apr 2025 18:44:10 +0200 Subject: [PATCH 06/18] use sessions --- packages/projects-docs/pages/sdk/_meta.json | 11 +-- packages/projects-docs/pages/sdk/browser.mdx | 75 +++++++++++++++++++ packages/projects-docs/pages/sdk/connect.mdx | 55 -------------- .../projects-docs/pages/sdk/create-resume.mdx | 3 +- .../projects-docs/pages/sdk/filesystem.mdx | 24 +++--- packages/projects-docs/pages/sdk/index.mdx | 14 ++-- packages/projects-docs/pages/sdk/ports.mdx | 30 ++++---- .../pages/sdk/preview-tokens.mdx | 12 +-- .../{sessions-clients.mdx => sessions.mdx} | 31 ++++---- packages/projects-docs/pages/sdk/tasks.mdx | 48 ++++++------ 10 files changed, 166 insertions(+), 137 deletions(-) create mode 100644 packages/projects-docs/pages/sdk/browser.mdx rename packages/projects-docs/pages/sdk/{sessions-clients.mdx => sessions.mdx} (79%) diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index 3dca4d8f..de0b2574 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -13,7 +13,7 @@ "title": "Sandboxes" }, "create-resume": "Create & Resume", - "sessions-clients": "Sessions & Clients", + "sessions": "Sessions", "preview-tokens": "Preview Tokens", "hibernate": "Hibernate", "fork": "Fork", @@ -22,19 +22,20 @@ "persistence": "Persistence", "update-sandbox": "Update Sandbox", - "-- connect-reference": { + "-- websocket-sessions": { "type": "separator", - "title": "Connect" + "title": "WebSocket Sessions" }, "connect": "Connect", + "browser": "Connect Browser", "filesystem": "File System", "shells": "Shells", "ports": "Ports", "tasks": "Tasks & Setup", - "-- rest-session": { + "-- rest-sessions": { "type": "separator", - "title": "Rest" + "title": "Rest Sessions" }, "-- resources": { diff --git a/packages/projects-docs/pages/sdk/browser.mdx b/packages/projects-docs/pages/sdk/browser.mdx new file mode 100644 index 00000000..9e5c18d6 --- /dev/null +++ b/packages/projects-docs/pages/sdk/browser.mdx @@ -0,0 +1,75 @@ +--- +title: Connect Browser +description: Learn how to connect to a sandbox in the browser with the CodeSandbox SDK. +--- + +import { Callout } from 'nextra-theme-docs' + +# Connect Browser + +Connecting to a Sandbox in the browser requires some collaboration with your server. Create an endpoint that will resume the sandbox and create a browser session: + +```ts +export const GET = async ({ params }) => { + const sandbox = await sdk.sandbox.resume(params.sandboxId); + const session = await sandbox.createBrowserSession(); + + return session +} +``` + +And in the browser: + +```ts +import { connectToSandbox } from '@codesandbox/sdk/browser'; + +const session = await connectToSandbox({ + id: '123', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()) +}); + +await session.fs.writeTextFile('test.txt', 'Hello World'); +``` + +The Browser sessions automatically manages the connection and will reconnect if the connection is lost. This is controlled by an option called `onFocusChange`. + +```ts +const session = await connectToSandbox({ + id: '123', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()), + onFocusChange: (notify) => { + const onVisibilityChange = () => { + notify(document.visibilityState === 'visible'); + } + + document.addEventListener('visibilitychange', onVisibilityChange); + + return () => { + document.removeEventListener('visibilitychange', onVisibilityChange); + } + } +}); +``` + +By telling the browser session when it is in focus it will automatically reconnect when hibernated, unless you explicitly disconnected the session. + +While the `connectToSandbox` promise is resolving you can also listen to initialization events to show a loading state: + +```ts +const session = await connectToSandbox({ + id: '123', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()), + onInitCb: (event) => {} +}); +``` + +## Disconnecting the Session + +Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. + +```ts +const sandbox = await sdk.sandbox.resume('sandbox-id') +const session = await sandbox.connect() + +await session.disconnect(); +``` diff --git a/packages/projects-docs/pages/sdk/connect.mdx b/packages/projects-docs/pages/sdk/connect.mdx index f0d57081..f1858629 100644 --- a/packages/projects-docs/pages/sdk/connect.mdx +++ b/packages/projects-docs/pages/sdk/connect.mdx @@ -16,61 +16,6 @@ const session = await sandbox.connect() await session.fs.writeTextFile('test.txt', 'Hello World'); ``` -## Connect in Browser - -Connecting to a Sandbox in the browser requires some collaboration with your server. Create an endpoint that will resume the sandbox and create a browser session: - -```ts -export const GET = async ({ params }) => { - const sandbox = await sdk.sandbox.resume(params.sandboxId); - const session = await sandbox.createBrowserSession(); - - return session -} -``` - -And in the browser: - -```ts -import { connectToSandbox } from '@codesandbox/sdk/browser'; - -const session = await connectToSandbox({ - getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()) -}); - -await session.fs.writeTextFile('test.txt', 'Hello World'); -``` - -The Browser sessions automatically manages the connection and will reconnect if the connection is lost. This is controlled by an option called `onFocusChange`. - -```ts -const session = await connectToSandbox({ - getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()), - onFocusChange: (notify) => { - const onVisibilityChange = () => { - notify(document.visibilityState === 'visible'); - } - - document.addEventListener('visibilitychange', onVisibilityChange); - - return () => { - document.removeEventListener('visibilitychange', onVisibilityChange); - } - } -}); -``` - -By telling the browser session when it is in focus it will automatically reconnect when hibernated, unless you explicitly disconnected the session. - -While the `connectToSandbox` promise is resolving you can also listen to initialization events to show a loading state: - -```ts -const session = await connectToSandbox({ - getSession: () => fetch(`/api/sandboxes/123`).then(res => res.json()), - onInitCb: (event) => {} -}); -``` - ## Disconnecting the Session Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. diff --git a/packages/projects-docs/pages/sdk/create-resume.mdx b/packages/projects-docs/pages/sdk/create-resume.mdx index cc360a19..ba1c92d1 100644 --- a/packages/projects-docs/pages/sdk/create-resume.mdx +++ b/packages/projects-docs/pages/sdk/create-resume.mdx @@ -69,7 +69,8 @@ const sandbox = await sdk.sandbox.create({ title: 'my-sandbox', description: 'My sandbox', tags: ['my-tag'], - + privacy: 'public' + path: '/users/some-user-folder' }) ``` diff --git a/packages/projects-docs/pages/sdk/filesystem.mdx b/packages/projects-docs/pages/sdk/filesystem.mdx index 7213109b..4b7fecc0 100644 --- a/packages/projects-docs/pages/sdk/filesystem.mdx +++ b/packages/projects-docs/pages/sdk/filesystem.mdx @@ -20,20 +20,20 @@ You can read & write files using an api that's similer to the Node.js fs module: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Writing text files -await client.fs.writeTextFile("./hello.txt", "Hello, world!"); +await session.fs.writeTextFile("./hello.txt", "Hello, world!"); // Reading text files -const content = await client.fs.readTextFile("./hello.txt"); +const content = await session.fs.readTextFile("./hello.txt"); console.log(content); // Writing binary files -await client.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); +await session.fs.writeFile("./hello.bin", new Uint8Array([1, 2, 3])); // Reading binary files -const content = await client.fs.readFile("./hello.bin"); +const content = await session.fs.readFile("./hello.bin"); console.log(content); ``` @@ -45,12 +45,12 @@ Uploading and downloading files can be done using the same methods as writing an import fs from "node:fs"; const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() const myBinaryFile = fs.readFileSync("./my-binary-file"); -await client.fs.writeFile("./my-binary-file", myBinaryFile); +await session.fs.writeFile("./my-binary-file", myBinaryFile); -const content = await client.fs.readFile("./my-binary-file"); +const content = await session.fs.readFile("./my-binary-file"); fs.writeFileSync("./my-binary-file", content); ``` @@ -66,7 +66,7 @@ console.log(downloadUrl); You can list files & directories in a directory using the `readdir` method. ```ts -const filesOrDirs = await client.fs.readdir("./"); +const filesOrDirs = await session.fs.readdir("./"); console.log(filesOrDirs); ``` @@ -76,9 +76,9 @@ console.log(filesOrDirs); You can copy, rename & delete files using the `copy`, `rename` & `remove` methods. ```ts -await client.fs.copy("./hello.txt", "./hello-copy.txt"); -await client.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); -await client.fs.remove("./hello-renamed.txt"); +await session.fs.copy("./hello.txt", "./hello-copy.txt"); +await session.fs.rename("./hello-copy.txt", "./hello-renamed.txt"); +await session.fs.remove("./hello-renamed.txt"); ``` ### Watching Files diff --git a/packages/projects-docs/pages/sdk/index.mdx b/packages/projects-docs/pages/sdk/index.mdx index a3c7ddfc..c3e52d25 100644 --- a/packages/projects-docs/pages/sdk/index.mdx +++ b/packages/projects-docs/pages/sdk/index.mdx @@ -44,24 +44,24 @@ import { CodeSandbox } from "@codesandbox/sdk"; const sdk = new CodeSandbox(process.env.CSB_API_KEY!); const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() -await client.shells.python.run("print(1+1)"); -await client.shells.run('echo "Hello World"'); +await session.shells.python.run("print(1+1)"); +await session.shells.run('echo "Hello World"'); // We can also start shells in the background by not awaiting them -const shellInfo = client.shells.run("npx -y serve ."); +const shellInfo = session.shells.run("npx -y serve ."); // Wait for port to open -const portInfo = await client.ports.waitForPort(3000); +const portInfo = await session.ports.waitForPort(3000); console.log(portInfo.url); // And, we can clone(!) the sandbox! This takes 1-3s. const sandbox2 = await sdk.sandbox.fork(sandbox.id) -const client2 = await sandbox2.connect() +const session2 = await sandbox2.connect() // Sandbox 2 will have the same processes running as sandbox 1 -const portInfo2 = await client2.ports.waitForPort(3000); +const portInfo2 = await session2.ports.waitForPort(3000); console.log(portInfo2.url); // Finally, we can hibernate the sandbox. This will snapshot the sandbox and stop it. diff --git a/packages/projects-docs/pages/sdk/ports.mdx b/packages/projects-docs/pages/sdk/ports.mdx index 0b9668ea..7d3247ea 100644 --- a/packages/projects-docs/pages/sdk/ports.mdx +++ b/packages/projects-docs/pages/sdk/ports.mdx @@ -27,16 +27,16 @@ You can listen for ports being opened and closed in your sandbox: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Listen for ports being opened -const listener1 = client.ports.onDidPortOpen((portInfo) => { +const listener1 = session.ports.onDidPortOpen((portInfo) => { console.log(`Port ${portInfo.port} opened`); console.log(`Preview URL: ${portInfo.getPreviewUrl()}`); }); // Listen for ports being closed -const listener2 = client.ports.onDidPortClose((port) => { +const listener2 = session.ports.onDidPortClose((port) => { console.log(`Port ${port} closed`); }); @@ -51,16 +51,16 @@ You can get information about currently opened ports: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Get all opened ports -const openPorts = client.ports.getOpenedPorts(); +const openPorts = session.ports.getOpenedPorts(); for (const port of openPorts) { console.log(`Port ${port.port} is open at ${port.hostname}`); } // Get preview URL for a specific port -const previewUrl = client.ports.getPreviewUrl(3000); +const previewUrl = session.ports.getPreviewUrl(3000); if (previewUrl) { console.log(`Preview available at: ${previewUrl}`); } @@ -72,13 +72,13 @@ When starting services, you often need to wait for a port to become available: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Start a development server -client.shells.run("npm run dev"); +session.shells.run("npm run dev"); // Wait for the dev server port to open -const portInfo = await client.ports.waitForPort(3000); +const portInfo = await session.ports.waitForPort(3000); console.log(`Dev server is ready at: ${portInfo.getPreviewUrl()}`); ``` @@ -90,13 +90,13 @@ Here's a complete example of starting a web server and getting its preview URL: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Start the server -client.shells.run("npx serve -y ."); +session.shells.run("npx serve -y ."); // Wait for the server to be ready -const portInfo = await client.ports.waitForPort(3000); +const portInfo = await session.ports.waitForPort(3000); // Get the preview URL with custom protocol const httpUrl = portInfo.getPreviewUrl("http://"); @@ -113,10 +113,10 @@ When working with multiple services, you might want to monitor several ports: ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +const session = await sandbox.connect() // Start monitoring before launching services -client.ports.onDidPortOpen((portInfo) => { +session.ports.onDidPortOpen((portInfo) => { switch (portInfo.port) { case 3000: console.log("Frontend server ready"); @@ -131,5 +131,5 @@ client.ports.onDidPortOpen((portInfo) => { }); // Start your services -client.shells.run("npm run start:all"); +session.shells.run("npm run start:all"); ``` diff --git a/packages/projects-docs/pages/sdk/preview-tokens.mdx b/packages/projects-docs/pages/sdk/preview-tokens.mdx index cefde4a6..c32e81a1 100644 --- a/packages/projects-docs/pages/sdk/preview-tokens.mdx +++ b/packages/projects-docs/pages/sdk/preview-tokens.mdx @@ -13,15 +13,15 @@ If a sandbox is private, its previews won't be accessible unless a preview token - A header: `csb-preview-token: :token` - A cookie: `csb_preview_token=:token` -You can obtain a preview token by calling `sandbox.previewTokens.create`. You can also set a custom expiration time for the token: +You can obtain a preview token by calling `sdk.previewTokens.create`. You can also set a custom expiration time for the token: ```ts -const previewToken = await sdk.sandbox.previewTokens.create('some-sandbox-id', { +const previewToken = await sdk.previewTokens.create('some-sandbox-id', { expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // 1 week from now }) // Pass in the port and token to create the url for the a preview -const signedUrl = sdk.sandbox.previewTokens.getSignedPreviewUrl(3000, previewToken.token) +const signedUrl = sdk.previewTokens.getSignedPreviewUrl(3000, previewToken.token) // This URL will have access to the sandbox until the token expires console.log(signedUrl); @@ -72,9 +72,9 @@ Instead, you can generate a preview token on the server, and then pass that to t ```ts export const POST = async ({ params, body }) => { - const previewToken = await sdk.sandbox.previewTokens.create(params.id); + const previewToken = await sdk.previewTokens.create(params.id); - return sdk.sandbox.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) + return sdk.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) } // In the browser ask for the signed url when it makes most sense, here @@ -100,7 +100,7 @@ client.shells.run("npm run dev"); const portInfo = await client.ports.waitForPort(3000); // Generate the preview token -const previewToken = await sdk.sandbox.previewTokens.create(sandbox.id); +const previewToken = await sdk.previewTokens.create(sandbox.id); const url = 'https://' + portInfo.url const response = await fetch(url, { diff --git a/packages/projects-docs/pages/sdk/sessions-clients.mdx b/packages/projects-docs/pages/sdk/sessions.mdx similarity index 79% rename from packages/projects-docs/pages/sdk/sessions-clients.mdx rename to packages/projects-docs/pages/sdk/sessions.mdx index cb0ea000..613de375 100644 --- a/packages/projects-docs/pages/sdk/sessions-clients.mdx +++ b/packages/projects-docs/pages/sdk/sessions.mdx @@ -1,21 +1,22 @@ --- -title: Sessions & Clients +title: Sessions description: Learn how to connect to Sandboxes with the CodeSandbox SDK. --- import { Callout } from 'nextra-theme-docs' -# Sessions & Clients +# Sessions By default a session is bound to a user called `pitcher-host`. This user has write access to any API. We refer to this session as the "global" session. ```ts const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect() +// "connect" creates a WebSocketSession +const session = await sandbox.connect() // Now I have a session where I act as `pitcher-host` user. // Any action I do will be done as `pitcher-host` user. -await client.fs.writeTextFile('test.txt', 'Hello World'); +await session.fs.writeTextFile('test.txt', 'Hello World'); ``` @@ -27,22 +28,22 @@ But you can choose to create your own user sessions with specific permissions. L ```ts // Now configure a session that only has read access to the sandbox const sandbox = await sdk.sandbox.create() -const client = await sandbox.connect({ +const session = await sandbox.connect({ id: 'anonymous', permission: 'read' }) // I have access to the normal sandbox api, but I only have read access // This means I cannot write files or open shells, but I _can_ read them -await client.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an error. -await client.fs.readTextFile('test.txt'); // This will work. +await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an error. +await session.fs.readTextFile('test.txt'); // This will work. // I can also create sessions with write access -const client2 = await sandbox.connect({ +const session2 = await sandbox.connect({ id: 'some-user-reference', permission: 'write', }); -await client2.fs.writeTextFile('test.ext', 'Hello World'); // This will work +await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work ``` ### Storage @@ -53,7 +54,7 @@ Every session will have the same filesystem as the global session. This means th The `~/.private` folder will not be persisted between restarts of the sandbox. This means that files inside this folder will disappear between restarts. -## Browser Client +## Browser Session If you want your users to connect to the Sandbox from the browser you can rather create an endpoint that responds with a browser session: @@ -72,15 +73,15 @@ And in the browser: ```ts import { connectToSandbox } from '@codesandbox/sdk/browser'; -const client = await connectToSandbox({ +const session = await connectToSandbox({ id: 'some-sandbox', getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()) }); -await client.fs.writeTextFile('test.txt', 'Hello World'); +await session.fs.writeTextFile('test.txt', 'Hello World'); ``` -## Rest Client +## Rest Session The sandboxes also expose a Rest api which can be a more useful protocol to use as you do not have to manage an ongoing connection. The Rest session is not as extensive, but supports most features. @@ -88,7 +89,7 @@ is not as extensive, but supports most features. ```ts const sandbox = await sdk.sandbox.resume('sandbox-id') // Also defaults to global session, but can configure custom session -const client = await sandbox.createRestClient() +const session = await sandbox.createRestClient() -await client.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) +await session.fs.writeTextFile({ path: 'test.ext', content: 'Hello World' }) ``` diff --git a/packages/projects-docs/pages/sdk/tasks.mdx b/packages/projects-docs/pages/sdk/tasks.mdx index 13740e69..f9746630 100644 --- a/packages/projects-docs/pages/sdk/tasks.mdx +++ b/packages/projects-docs/pages/sdk/tasks.mdx @@ -151,20 +151,21 @@ Here's a more complete example showing various task configurations: Setup tasks run automatically when a sandbox starts. They typically handle installation of dependencies and initial builds. You can monitor and control setup tasks using the Setup API: ```ts -const sandbox = await sdk.sandbox.create().connect() +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Listen to setup progress -sandbox.setup.onSetupProgressUpdate((progress) => { +session.setup.onSetupProgressUpdate((progress) => { console.log(`Setup progress: ${progress.currentStepIndex + 1}/${progress.steps.length}`); console.log(`Current step: ${progress.steps[progress.currentStepIndex].name}`); }); // Get current progress -const progress = await sandbox.setup.getProgress(); +const progress = await session.setup.getProgress(); console.log(`Setup state: ${progress.state}`); // Wait for setup to finish -const result = await sandbox.setup.waitForFinish(); +const result = await session.setup.waitForFinish(); if (result.state === "FINISHED") { console.log("Setup completed successfully"); } @@ -216,10 +217,11 @@ Regular tasks are defined in the `tasks` section of your `tasks.json` file. Each You can get all available tasks in your sandbox: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Get all tasks -const tasks = await sandbox.tasks.getTasks(); +const tasks = await session.tasks.getTasks(); for (const task of tasks) { console.log(`Task: ${task.name} (${task.command})`); } @@ -230,16 +232,17 @@ for (const task of tasks) { You can run a task using its ID: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Run a specific task -const task = await sandbox.tasks.runTask("dev"); +const task = await session.tasks.runTask("dev"); console.log(`Started task: ${task.name}`); // If the task opens a port, you can access it if (task.ports.length > 0) { const port = task.ports[0]; - console.log(`Preview available at: ${port.getPreviewUrl()}`); + console.log(`Preview available at: ${port.url}`); } ``` @@ -248,10 +251,11 @@ if (task.ports.length > 0) { You can get information about a specific task: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Get a specific task -const task = await sandbox.tasks.getTask("build"); +const task = await session.tasks.getTask("build"); if (task) { console.log(`Task: ${task.name}`); console.log(`Command: ${task.command}`); @@ -270,22 +274,23 @@ if (task) { Here's an example of running a development server task and waiting for it to be ready: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Get the dev task -const task = await sandbox.tasks.getTask("dev"); +const task = await session.tasks.getTask("dev"); if (!task) { throw new Error("Dev task not found"); } // Run the task -await sandbox.tasks.runTask(task.id); +await session.tasks.runTask(task.id); // If the task has a preview port configured if (task.preview?.port) { // Wait for the port to open - const portInfo = await sandbox.ports.waitForPort(task.preview.port); - console.log(`Dev server ready at: ${portInfo.getPreviewUrl()}`); + const portInfo = await session.ports.waitForPort(task.preview.port); + console.log(`Dev server ready at: ${portInfo.url}`); } ``` @@ -294,25 +299,26 @@ if (task.preview?.port) { You can run multiple tasks and monitor their ports: ```ts -const sandbox = await sdk.sandbox.create().connect(); +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() // Get all tasks that should run at start -const tasks = await sandbox.tasks.getTasks(); +const tasks = await session.tasks.getTasks(); // Run all startup tasks for (const task of tasks) { console.log(`Starting ${task.name}...`); - sandbox.tasks.runTask(task.id); + await session.tasks.runTask(task.id); } // Monitor ports for all tasks -sandbox.ports.onDidPortOpen((portInfo) => { +session.ports.onDidPortOpen((portInfo) => { const task = tasks.find(t => t.preview?.port === portInfo.port ); if (task) { - console.log(`${task.name} is ready at: ${portInfo.getPreviewUrl()}`); + console.log(`${task.name} is ready at: ${portInfo.url}`); } }); ``` From 02d917feb1d11647d1feac2d029b596c362a313b Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 6 May 2025 11:18:54 +0200 Subject: [PATCH 07/18] updated docs --- packages/projects-docs/pages/sdk/_meta.json | 1 + packages/projects-docs/pages/sdk/docker.mdx | 2 +- packages/projects-docs/pages/sdk/ports.mdx | 21 +---- .../pages/sdk/preview-tokens.mdx | 4 +- packages/projects-docs/pages/sdk/previews.mdx | 91 +++++++++++++++++++ packages/projects-docs/pages/sdk/shells.mdx | 2 +- 6 files changed, 101 insertions(+), 20 deletions(-) create mode 100644 packages/projects-docs/pages/sdk/previews.mdx diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index de0b2574..5d54caa8 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -28,6 +28,7 @@ }, "connect": "Connect", "browser": "Connect Browser", + "previews": "Previews", "filesystem": "File System", "shells": "Shells", "ports": "Ports", diff --git a/packages/projects-docs/pages/sdk/docker.mdx b/packages/projects-docs/pages/sdk/docker.mdx index bcb32b5d..9e7bb614 100644 --- a/packages/projects-docs/pages/sdk/docker.mdx +++ b/packages/projects-docs/pages/sdk/docker.mdx @@ -123,7 +123,7 @@ const [appPort, dbPort] = await Promise.all([ ]); console.log(` -App running at: ${appPort.getPreviewUrl()} +App running at: ${appPort.url} Database available at: ${dbPort.hostname}:${dbPort.port} `); ``` diff --git a/packages/projects-docs/pages/sdk/ports.mdx b/packages/projects-docs/pages/sdk/ports.mdx index 7d3247ea..09245a28 100644 --- a/packages/projects-docs/pages/sdk/ports.mdx +++ b/packages/projects-docs/pages/sdk/ports.mdx @@ -32,7 +32,7 @@ const session = await sandbox.connect() // Listen for ports being opened const listener1 = session.ports.onDidPortOpen((portInfo) => { console.log(`Port ${portInfo.port} opened`); - console.log(`Preview URL: ${portInfo.getPreviewUrl()}`); + console.log(`Preview URL: ${portInfo.url}`); }); // Listen for ports being closed @@ -56,13 +56,7 @@ const session = await sandbox.connect() // Get all opened ports const openPorts = session.ports.getOpenedPorts(); for (const port of openPorts) { - console.log(`Port ${port.port} is open at ${port.hostname}`); -} - -// Get preview URL for a specific port -const previewUrl = session.ports.getPreviewUrl(3000); -if (previewUrl) { - console.log(`Preview available at: ${previewUrl}`); + console.log(`Port ${port.port} is open at ${port.url}`); } ``` @@ -79,7 +73,7 @@ session.shells.run("npm run dev"); // Wait for the dev server port to open const portInfo = await session.ports.waitForPort(3000); -console.log(`Dev server is ready at: ${portInfo.getPreviewUrl()}`); +console.log(`Dev server is ready at: ${portInfo.url}`); ``` ## Examples @@ -98,13 +92,8 @@ session.shells.run("npx serve -y ."); // Wait for the server to be ready const portInfo = await session.ports.waitForPort(3000); -// Get the preview URL with custom protocol -const httpUrl = portInfo.getPreviewUrl("http://"); -const httpsUrl = portInfo.getPreviewUrl(); // defaults to https:// - -console.log(`Server is running at: -- HTTP: ${httpUrl} -- HTTPS: ${httpsUrl}`); +// Apply protocol to the url +const url = 'https://' + portInfo.url ``` ### Monitoring Multiple Ports diff --git a/packages/projects-docs/pages/sdk/preview-tokens.mdx b/packages/projects-docs/pages/sdk/preview-tokens.mdx index c32e81a1..95c5dbb6 100644 --- a/packages/projects-docs/pages/sdk/preview-tokens.mdx +++ b/packages/projects-docs/pages/sdk/preview-tokens.mdx @@ -21,7 +21,7 @@ const previewToken = await sdk.previewTokens.create('some-sandbox-id', { }) // Pass in the port and token to create the url for the a preview -const signedUrl = sdk.previewTokens.getSignedPreviewUrl(3000, previewToken.token) +const signedUrl = sdk.previewTokens.getSignedPreviewUrl(previewToken, 3000) // This URL will have access to the sandbox until the token expires console.log(signedUrl); @@ -74,7 +74,7 @@ Instead, you can generate a preview token on the server, and then pass that to t export const POST = async ({ params, body }) => { const previewToken = await sdk.previewTokens.create(params.id); - return sdk.previewTokens.getSignedPreviewUrl(body.port, previewToken.token) + return sdk.previewTokens.getSignedPreviewUrl(previewToken, body.port) } // In the browser ask for the signed url when it makes most sense, here diff --git a/packages/projects-docs/pages/sdk/previews.mdx b/packages/projects-docs/pages/sdk/previews.mdx new file mode 100644 index 00000000..0b508d5f --- /dev/null +++ b/packages/projects-docs/pages/sdk/previews.mdx @@ -0,0 +1,91 @@ +--- +title: Previews +description: Learn how you can interact with previews in your sandbox. +--- + +import { Callout } from 'nextra-theme-docs' + +# Previews + +The previews API allows you to create and interact with previews in the browser. You can navigate, listen to messages and even inject custom code into the preview. + + +The previews only works in the browser session. + + +## API + +```ts +const session = await connectToSandbox({ + id: sandboxId, + getSession: (id) => fetchJson('/browser-sessions/' + id) +}) + +const port = await session.ports.waitForPort(5173) +const preview = session.previews.create('https://' + port.url) + +document.querySelector('#preview-container').append(preview.iframe) +``` + +## Messages + +You can listen to messages from the preview: + +```ts +preview.onMessage((message) => { + switch (message.type) { + case 'SET_URL': { + // Url that was set + message.url + // If you can move backward + message.back + // If you can move forward + message.forward + break; + } + case 'RELOAD': { + // If preview reloaded + } + case 'PREVIEW_UNLOADING': { + // Preview will unload + } + } +}) +``` + + +By injecting your own code you can extend these messages to your own. + + +## Navigate + +```ts +// Replace the current url +preview.setUrl("...") + +// Navigate back +preview.back() + +// Navigate forward +preview.forward() + +// Reload the iframe +preview.reload() +``` + +## Inject and Invoke + +You can inject any code into the preview. This is useful if you want to insert custom UI, background processes etc. The injected code has access to listening to messages, sending messages and any variables (scope) you send to the injected code. + +```ts +preview.onMessage((message) => { + console.log(message) +}) + +preview.injectAndInvoke(function MyInjectedFunction({ previewProtocol, previewWindow, scope }) { + alert(scope.message) + previewProtocol.sendMessage({ type: 'ALERTED' }) +}, { + message: 'Hello World' +}) +``` diff --git a/packages/projects-docs/pages/sdk/shells.mdx b/packages/projects-docs/pages/sdk/shells.mdx index 4fe13a6f..89888ddc 100644 --- a/packages/projects-docs/pages/sdk/shells.mdx +++ b/packages/projects-docs/pages/sdk/shells.mdx @@ -123,5 +123,5 @@ const shell = session.shells.run("npx -y serve ."); const portInfo = await shell.waitForPort(3000); -console.log(portInfo.getPreviewUrl()); +console.log(portInfo.url); ``` From 640d61d31abf344accfa8a0a102f3e8d0f049ed9 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Thu, 8 May 2025 15:23:25 +0200 Subject: [PATCH 08/18] change to new terminals, commands and intepreters --- packages/projects-docs/pages/sdk/_meta.json | 4 +- packages/projects-docs/pages/sdk/commands.mdx | 62 ++++++++++++++++ .../projects-docs/pages/sdk/interpreters.mdx | 35 ++++++++++ .../projects-docs/pages/sdk/terminals.mdx | 70 +++++++++++++++++++ 4 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 packages/projects-docs/pages/sdk/commands.mdx create mode 100644 packages/projects-docs/pages/sdk/interpreters.mdx create mode 100644 packages/projects-docs/pages/sdk/terminals.mdx diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index 5d54caa8..dcf88bd7 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -30,7 +30,9 @@ "browser": "Connect Browser", "previews": "Previews", "filesystem": "File System", - "shells": "Shells", + "terminals": "Terminals", + "commands": "Commands", + "interpreters": "Interpreters", "ports": "Ports", "tasks": "Tasks & Setup", diff --git a/packages/projects-docs/pages/sdk/commands.mdx b/packages/projects-docs/pages/sdk/commands.mdx new file mode 100644 index 00000000..36d84ab9 --- /dev/null +++ b/packages/projects-docs/pages/sdk/commands.mdx @@ -0,0 +1,62 @@ +--- +title: Commands +description: Learn how the CodeSandbox SDK's commands work. +--- + +import { Callout } from 'nextra-theme-docs' + +# Commands + +The Commands API allows you to run commands in your sandbox. A command is also a shell, but unlike a terminal it will clean itself up when the command is executed. + +## API + +The Commands API is available under `sandbox.commands`. + +### Running Commands + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +// Run a single command +const command = session.commands.run("npm install"); + +// Listen to real-time output +command.onOutput((output) => { + console.log(output); +}); + +// Optionally cancel the command if it's not finished +if (Math.random() > 0.5) { + command.kill(); +} + +// Wait for completion and get results, it will throw if +// the command resulted in an error +const output = await command.getOutput(); + +console.log(output); +``` + +## Long running commands + +Some commands are long running, like starting a server. In this case you can use the `waitForPort` method to wait for the port to open: + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +// Run a long running command +const command = session.commands.run("npx -y serve ."); + +// You will need to manually kill it +command.kill() + +// Or you can also restart a command that is already running +command.restart() +``` + + +Just like terminals, commands can also run beyond the current session. Use `session.commands.getAll()` to retrieve any existing commands. + diff --git a/packages/projects-docs/pages/sdk/interpreters.mdx b/packages/projects-docs/pages/sdk/interpreters.mdx new file mode 100644 index 00000000..6a3f826b --- /dev/null +++ b/packages/projects-docs/pages/sdk/interpreters.mdx @@ -0,0 +1,35 @@ +--- +title: Interpreters +description: Learn how the CodeSandbox SDK's interpreters work. +--- + +import { Callout } from 'nextra-theme-docs' + +# Interpreters + +The Interpreter API allows you to run code in different programming languages in your sandbox. + +## API + +The Interpreter API is available under `sandbox.interpreters`. + +### Running Code + +The Interpreter API includes built-in support for running code in different programming languages: + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +// Run JavaScript code +const jsResult = await session.interpreters.javascript("'Hello from Node.js!'"); + +// Run Python code +const pythonResult = await session.interpreters.python("'Hello from Python!'"); +``` + +These interpreters will return the last expression as output, unless you use the `console` etc., which will then become part of the returned output. + + +Note that the interpreters will not resolve until the interpretation resolves. We are open to changing this behavior if you have any feedback or suggestions! Or you can always wield your own by using commands. + diff --git a/packages/projects-docs/pages/sdk/terminals.mdx b/packages/projects-docs/pages/sdk/terminals.mdx new file mode 100644 index 00000000..61a47f6e --- /dev/null +++ b/packages/projects-docs/pages/sdk/terminals.mdx @@ -0,0 +1,70 @@ +--- +title: Terminals +description: Learn how the CodeSandbox SDK's terminals work. +--- + +import { Callout } from 'nextra-theme-docs' + +# Terminals + +The Terminals API allows you to create, manage and interact with terminal processes in your sandbox. A terminal is a shell process which you can write to, listen to and is often combined with xTerm in the browser to allow users to get a full terminal experience. + +## API + +The Terminals API is available under `sandbox.terminals`. + +### Creating Terminals + +You can create an interactive terminal that allows you to write, run commands and receive output: + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +// Create a new terminal (bash is default) +const terminal = session.terminals.create() + +// Listen to terminal output +const onOutputDisposer = terminal.onOutput((output) => { + console.log(output); +}); + +// Run commands +await terminal.run("echo 'Hello, world!'"); + +// Kill the terminal when done +await terminal.kill(); + +// Dispose the listener when done +onOutputDisposer.dispose(); +``` + + +Typically you want terminals to live beyond the immediate session of the user. Use the `session.terminals.getAll()` method to retrieve any existing terminals. + + +### Browser Terminal + +The terminal API is often combined with [xTerm](https://www.npmjs.com/package/@xterm/xterm) to allow users to get a full terminal experience. + +```ts +import { connectToSandbox } from "@codesandbox/sdk/browser"; +import { Terminal } from '@xterm/xterm' + +const session = await connectToSandbox({ + id: 'sandbox-id', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()), +}) +const terminal = await session.terminals.create() +const xterm = new Terminal() + +xterm.open(document.getElementById('terminal')) + +terminal.onOutput((output) => { + xterm.write(output) +}); + +xterm.onData((data) => { + terminal.write(data) +}) +``` From 0fae29b30abfcc69a68683f708f6b18f62039242 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 12 May 2025 13:31:51 +0200 Subject: [PATCH 09/18] fix docs --- packages/projects-docs/pages/sdk/_meta.json | 12 +- .../{previews.mdx => browser-previews.mdx} | 29 +-- packages/projects-docs/pages/sdk/browser.mdx | 18 +- packages/projects-docs/pages/sdk/connect.mdx | 4 +- .../projects-docs/pages/sdk/create-resume.mdx | 16 +- .../projects-docs/pages/sdk/hibernate.mdx | 4 + .../pages/sdk/restart-shutdown.mdx | 10 +- packages/projects-docs/pages/sdk/setup.mdx | 86 ++++++++ packages/projects-docs/pages/sdk/tasks.mdx | 205 +++--------------- 9 files changed, 170 insertions(+), 214 deletions(-) rename packages/projects-docs/pages/sdk/{previews.mdx => browser-previews.mdx} (66%) create mode 100644 packages/projects-docs/pages/sdk/setup.mdx diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index dcf88bd7..ee4657f1 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -14,27 +14,31 @@ }, "create-resume": "Create & Resume", "sessions": "Sessions", - "preview-tokens": "Preview Tokens", "hibernate": "Hibernate", "fork": "Fork", "restart-shutdown": "Restart & Shutdown", "specs": "VM Specs", "persistence": "Persistence", "update-sandbox": "Update Sandbox", - + "-- previews": { + "type": "separator", + "title": "Previews" + }, + "preview-tokens": "Preview Tokens", + "browser-previews": "Browser Previews", "-- websocket-sessions": { "type": "separator", "title": "WebSocket Sessions" }, "connect": "Connect", "browser": "Connect Browser", - "previews": "Previews", "filesystem": "File System", "terminals": "Terminals", "commands": "Commands", "interpreters": "Interpreters", "ports": "Ports", - "tasks": "Tasks & Setup", + "tasks": "Tasks", + "setup": "Setup", "-- rest-sessions": { "type": "separator", diff --git a/packages/projects-docs/pages/sdk/previews.mdx b/packages/projects-docs/pages/sdk/browser-previews.mdx similarity index 66% rename from packages/projects-docs/pages/sdk/previews.mdx rename to packages/projects-docs/pages/sdk/browser-previews.mdx index 0b508d5f..d1d97cab 100644 --- a/packages/projects-docs/pages/sdk/previews.mdx +++ b/packages/projects-docs/pages/sdk/browser-previews.mdx @@ -1,32 +1,29 @@ --- -title: Previews +title: Browser Previews description: Learn how you can interact with previews in your sandbox. --- import { Callout } from 'nextra-theme-docs' -# Previews +# Browser Previews -The previews API allows you to create and interact with previews in the browser. You can navigate, listen to messages and even inject custom code into the preview. - - -The previews only works in the browser session. - +The browser previews API allows you to create and interact with previews in the browser. You can navigate, listen to messages and even inject custom code into the preview. ## API ```ts -const session = await connectToSandbox({ - id: sandboxId, - getSession: (id) => fetchJson('/browser-sessions/' + id) -}) +import { createPreview } from '@codesandbox/sdk/browser' -const port = await session.ports.waitForPort(5173) -const preview = session.previews.create('https://' + port.url) +const { url } = await fetch('/api/preview-tokens/' + sandboxId).then(res => res.json()) +const preview = createPreview(url) -document.querySelector('#preview-container').append(preview.iframe) +document.querySelector('#preview-container').appendChild(preview.iframe) ``` + +If you are running public previews you can also just use the port url directly without a preview token. + + ## Messages You can listen to messages from the preview: @@ -89,3 +86,7 @@ preview.injectAndInvoke(function MyInjectedFunction({ previewProtocol, previewWi message: 'Hello World' }) ``` + + +Note that the function passed to `injectAndInvoke` will be stringified and sent to the preview. Be careful about your build tool transforming this function into invalid code. + diff --git a/packages/projects-docs/pages/sdk/browser.mdx b/packages/projects-docs/pages/sdk/browser.mdx index 9e5c18d6..1e958b7a 100644 --- a/packages/projects-docs/pages/sdk/browser.mdx +++ b/packages/projects-docs/pages/sdk/browser.mdx @@ -65,11 +65,21 @@ const session = await connectToSandbox({ ## Disconnecting the Session -Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. +Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. You can alternatively hibernate the sandbox explicitly from the server. ```ts -const sandbox = await sdk.sandbox.resume('sandbox-id') -const session = await sandbox.connect() +import { connectToSandbox } from '@codesandbox/sdk/browser' -await session.disconnect(); +const session = await connectToSandbox({ + id: '123', + getSession: (id) => fetch(`/api/sandboxes/${id}`).then(res => res.json()), +}) + +// Disconnect returns a promise that resolves when the session is disconnected +session.disconnect(); + +// Optionally hibernate the sandbox explicitly +fetch('/api/sandboxes/' + sandbox.id + '/hibernate', { + method: 'POST' +}) ``` diff --git a/packages/projects-docs/pages/sdk/connect.mdx b/packages/projects-docs/pages/sdk/connect.mdx index f1858629..87c25023 100644 --- a/packages/projects-docs/pages/sdk/connect.mdx +++ b/packages/projects-docs/pages/sdk/connect.mdx @@ -24,5 +24,7 @@ Disconnecting the session will end the session and automatically hibernate the s const sandbox = await sdk.sandbox.resume('sandbox-id') const session = await sandbox.connect() -await session.disconnect(); +// Disconnect returns a promise of when all pending +// messages are handled and socket is actually disconnected +session.disconnect(); ``` diff --git a/packages/projects-docs/pages/sdk/create-resume.mdx b/packages/projects-docs/pages/sdk/create-resume.mdx index ba1c92d1..16cf82c9 100644 --- a/packages/projects-docs/pages/sdk/create-resume.mdx +++ b/packages/projects-docs/pages/sdk/create-resume.mdx @@ -44,17 +44,11 @@ const sandbox = await sdk.sandbox.create({ const sandbox = await sdk.sandbox.create({ source: 'git', url: 'https://...', - branch: 'main' -}) -``` - -### Create from Files - -```ts -const sandbox = await sdk.sandbox.create({ - source: 'files', - files: { - 'index.js': 'console.log("Hello World")' + branch: 'main', + async setup(session) { + await session.commands.run('pnpm install') + await session.commands.run('pnpm run dev') + await session.ports.waitForPort(5173) } }) ``` diff --git a/packages/projects-docs/pages/sdk/hibernate.mdx b/packages/projects-docs/pages/sdk/hibernate.mdx index 1139001b..60e322e3 100644 --- a/packages/projects-docs/pages/sdk/hibernate.mdx +++ b/packages/projects-docs/pages/sdk/hibernate.mdx @@ -28,3 +28,7 @@ const sandbox = await sdk.sandbox.create({ ``` When you set a hibernation timeout, the sandbox will hibernate after the specified period of inactivity (no calls from the SDK). While the SDK remains connected, we recommend either explicitly hibernating the sandbox or disconnecting from it when you're done using it. Since resuming only takes a few seconds, you can be aggressive with hibernation to conserve resources. + + +If you resume a Sandbox during hibernation it will first finish creating the snapshot and then resume. That means very aggressive hibernation can create some inconsistency in resume times. + diff --git a/packages/projects-docs/pages/sdk/restart-shutdown.mdx b/packages/projects-docs/pages/sdk/restart-shutdown.mdx index 477d9345..4046faad 100644 --- a/packages/projects-docs/pages/sdk/restart-shutdown.mdx +++ b/packages/projects-docs/pages/sdk/restart-shutdown.mdx @@ -3,6 +3,8 @@ title: Restart & Shutdown description: Learn how to restart and shutdown sandboxes with the CodeSandbox SDK. --- +import { Callout } from 'nextra-theme-docs' + # Restart & Shutdown ## Restart @@ -10,11 +12,15 @@ description: Learn how to restart and shutdown sandboxes with the CodeSandbox SD Restarting a Sandbox starts it from a clean slate. It will install and build its configured resources and also run with the last version of the agent. ```ts -const sandbox = await sdk.sandbox.resume('sandbox-id') +let sandbox = await sdk.sandbox.resume('sandbox-id') -await sdk.sandbox.restart(sandbox.id); +sandbox = await sdk.sandbox.restart(sandbox.id); ``` + +Note that when restarting you get a new reference to the Sandbox. You will need to create a new session from that new reference. + + ## Shutdown You can also shutdown a sandbox. This will shut down the sandbox without creating a memory snapshot. Next time the sandbox is started, it will boot from a clean state (but your files in `/project/sandbox` will be preserved). diff --git a/packages/projects-docs/pages/sdk/setup.mdx b/packages/projects-docs/pages/sdk/setup.mdx new file mode 100644 index 00000000..88f41a48 --- /dev/null +++ b/packages/projects-docs/pages/sdk/setup.mdx @@ -0,0 +1,86 @@ +--- +title: Setup +description: Learn how the CodeSandbox SDK's setup work. +--- + +import { Callout } from 'nextra-theme-docs' + +# Setup + +Setup tasks are configured in your project's `.codesandbox/tasks.json` file. This file defines both setup tasks that run when the sandbox starts, and regular tasks that can be run on-demand. + +Setup tasks run in order when initializing your sandbox. They're typically used for installation and preparation steps: + +```json +{ + "setupTasks": [ + { + "name": "Install Dependencies", + "command": "pnpm install" + }, + { + "name": "Copy Environment File", + "command": "cp .env.example .env" + }, + "pnpm run build" // Short form for { "name": "pnpm run build", "command": "pnpm run build" } + ] +} +``` + +## API + +Setup tasks run automatically when a sandbox starts. They typically handle installation of dependencies and initial builds. You can monitor and control setup tasks using the Setup API: + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect() + +// Listen to setup progress +session.setup.onSetupProgressUpdate((progress) => { + console.log(`Setup progress: ${progress.currentStepIndex + 1}/${progress.steps.length}`); + console.log(`Current step: ${progress.steps[progress.currentStepIndex].name}`); +}); + +// Get current progress +const progress = await session.setup.getProgress(); +console.log(`Setup state: ${progress.state}`); + +// Wait for setup to finish +const result = await session.setup.waitForFinish(); +if (result.state === "FINISHED") { + console.log("Setup completed successfully"); +} +``` + +### Setup Progress + +The setup progress includes the following information: + +```ts +type SetupProgress = { + state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; + steps: { + name: string; + command: string; + shellId: string | null; + finishStatus: "SUCCEEDED" | "FAILED" | "SKIPPED" | null; + }[]; + currentStepIndex: number; +}; +``` + +## Setup Tasks vs Docker Build: When to Use Which? + +Setup tasks are used for any preparation work needed in the `/project/sandbox` directory, such as: + +- Installing dependencies +- Building assets +- Running initial compilations + +Docker build, on the other hand, should be used for: + +- Setting up the container environment +- Installing system-level dependencies +- Configuring global tools + +This separation exists because the `/project/sandbox` directory is only available after the container starts. diff --git a/packages/projects-docs/pages/sdk/tasks.mdx b/packages/projects-docs/pages/sdk/tasks.mdx index f9746630..547ade6d 100644 --- a/packages/projects-docs/pages/sdk/tasks.mdx +++ b/packages/projects-docs/pages/sdk/tasks.mdx @@ -1,5 +1,5 @@ --- -title: Tasks & Setup +title: Tasks description: Learn how the CodeSandbox SDK's tasks work. --- @@ -9,37 +9,9 @@ import { Callout } from 'nextra-theme-docs' The Tasks API allows you to manage and run predefined commands in your sandbox. Tasks are typically defined in your project's configuration and can include development servers, build processes, tests, or any other command-line operations. - -We might still change the Task API in the future to make it better suited for the SDK, let us know if you have any feedback or suggestions! - - ## Configuration -Tasks are configured in your project's `.codesandbox/tasks.json` file. This file defines both setup tasks that run when the sandbox starts, and regular tasks that can be run on-demand. - -### Setup Tasks - -Setup tasks run in order when initializing your sandbox. They're typically used for installation and preparation steps: - -```json -{ - "setupTasks": [ - { - "name": "Install Dependencies", - "command": "pnpm install" - }, - { - "name": "Copy Environment File", - "command": "cp .env.example .env" - }, - "pnpm run build" // Short form for { "name": "pnpm run build", "command": "pnpm run build" } - ] -} -``` - -### Regular Tasks - -Regular tasks can be run at any time and support more configuration options: +Tasks are configured in your project's `.codesandbox/tasks.json` file. ```json { @@ -48,9 +20,6 @@ Regular tasks can be run at any time and support more configuration options: "name": "Development Server", "command": "pnpm dev", "runAtStart": true, - "preview": { - "port": 3000 - }, "restartOn": { "files": ["package.json", "pnpm-lock.yaml"], // Restart when package.json or pnpm-lock.yaml changes "clone": true, // Restart right after this VM was cloned from another VM @@ -82,8 +51,6 @@ Each task can have the following options: - `name`: Display name for the task - `command`: The command to execute - `runAtStart`: Whether to run the task when the sandbox starts -- `preview`: Configuration for task preview - - `port`: Port number to preview - `restartOn`: Configure when the task should restart - `files`: Array of file patterns that trigger restart when changed - `clone`: Restart when this VM was cloned from another VM @@ -95,25 +62,11 @@ Here's a more complete example showing various task configurations: ```json { - "setupTasks": [ - { - "name": "Install Dependencies", - "command": "pnpm install" - }, - { - "name": "Copy Environment", - "command": "cp .env.example .env.local" - } - ], "tasks": { "dev": { "name": "Development", "command": "pnpm dev", "runAtStart": true, - "preview": { - "port": 3000, - "prLink": "direct" - }, "restartOn": { "files": ["package.json", ".env.local"], "branch": false, @@ -122,10 +75,7 @@ Here's a more complete example showing various task configurations: }, "storybook": { "name": "Storybook", - "command": "pnpm storybook", - "preview": { - "port": 6006 - } + "command": "pnpm storybook" }, "test:watch": { "name": "Test Watch", @@ -146,72 +96,10 @@ Here's a more complete example showing various task configurations: } ``` -## Setup Tasks - -Setup tasks run automatically when a sandbox starts. They typically handle installation of dependencies and initial builds. You can monitor and control setup tasks using the Setup API: - -```ts -const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() - -// Listen to setup progress -session.setup.onSetupProgressUpdate((progress) => { - console.log(`Setup progress: ${progress.currentStepIndex + 1}/${progress.steps.length}`); - console.log(`Current step: ${progress.steps[progress.currentStepIndex].name}`); -}); - -// Get current progress -const progress = await session.setup.getProgress(); -console.log(`Setup state: ${progress.state}`); - -// Wait for setup to finish -const result = await session.setup.waitForFinish(); -if (result.state === "FINISHED") { - console.log("Setup completed successfully"); -} -``` - -### Setup Tasks vs Docker Build: When to Use Which? - -Setup tasks are used for any preparation work needed in the `/project/sandbox` directory, such as: - -- Installing dependencies -- Building assets -- Running initial compilations - -Docker build, on the other hand, should be used for: - -- Setting up the container environment -- Installing system-level dependencies -- Configuring global tools - -This separation exists because the `/project/sandbox` directory is only available after the container starts. - -### Setup Progress - -The setup progress includes the following information: - -```ts -type SetupProgress = { - state: "IDLE" | "IN_PROGRESS" | "FINISHED" | "STOPPED"; - steps: { - name: string; - command: string; - shellId: string | null; - finishStatus: "SUCCEEDED" | "FAILED" | "SKIPPED" | null; - }[]; - currentStepIndex: number; -}; -``` - -## Tasks +## API The Tasks API is available under `sandbox.tasks`. It provides methods for listing, retrieving, and running tasks in your sandbox. - -Regular tasks are defined in the `tasks` section of your `tasks.json` file. Each task has a unique ID and can be configured to run automatically when the sandbox starts by setting `runAtStart: true`. They will start after setup has completed. - - ### Listing Tasks You can get all available tasks in your sandbox: @@ -221,7 +109,8 @@ const sandbox = await sdk.sandbox.create() const session = await sandbox.connect() // Get all tasks -const tasks = await session.tasks.getTasks(); +const tasks = session.tasks.getAll(); + for (const task of tasks) { console.log(`Task: ${task.name} (${task.command})`); } @@ -234,16 +123,17 @@ You can run a task using its ID: ```ts const sandbox = await sdk.sandbox.create() const session = await sandbox.connect() +const task = session.tasks.get("dev"); -// Run a specific task -const task = await session.tasks.runTask("dev"); -console.log(`Started task: ${task.name}`); +// Will restart the task if already running +await task.run() +await task.restart() +await task.stop() -// If the task opens a port, you can access it -if (task.ports.length > 0) { - const port = task.ports[0]; - console.log(`Preview available at: ${port.url}`); -} + +// You can wait for a port to open on the task +const port = await task.waitForPort() +console.log(`Preview available at: ${port.url}`); ``` ### Getting Task Information @@ -255,70 +145,29 @@ const sandbox = await sdk.sandbox.create() const session = await sandbox.connect() // Get a specific task -const task = await session.tasks.getTask("build"); +const task = session.tasks.get("build"); + if (task) { console.log(`Task: ${task.name}`); console.log(`Command: ${task.command}`); + // "RUNNING" | "FINISHED" | "ERROR" | "KILLED" | "RESTARTING" | "IDLE + console.log(`Status: ${task.status}`); console.log(`Runs at start: ${task.runAtStart}`); - - if (task.shellId) { - console.log("Task is currently running"); - } } ``` -## Examples - -### Starting a Development Server +### Opening shell -Here's an example of running a development server task and waiting for it to be ready: +Tasks are an abstraction over commands. You can open the underlying shell to see its current output and listen to output updates. ```ts const sandbox = await sdk.sandbox.create() const session = await sandbox.connect() +const task = session.tasks.get('dev') -// Get the dev task -const task = await session.tasks.getTask("dev"); -if (!task) { - throw new Error("Dev task not found"); -} - -// Run the task -await session.tasks.runTask(task.id); - -// If the task has a preview port configured -if (task.preview?.port) { - // Wait for the port to open - const portInfo = await session.ports.waitForPort(task.preview.port); - console.log(`Dev server ready at: ${portInfo.url}`); -} -``` - -### Running Multiple Tasks - -You can run multiple tasks and monitor their ports: - -```ts -const sandbox = await sdk.sandbox.create() -const session = await sandbox.connect() - -// Get all tasks that should run at start -const tasks = await session.tasks.getTasks(); - -// Run all startup tasks -for (const task of tasks) { - console.log(`Starting ${task.name}...`); - await session.tasks.runTask(task.id); -} - -// Monitor ports for all tasks -session.ports.onDidPortOpen((portInfo) => { - const task = tasks.find(t => - t.preview?.port === portInfo.port - ); - - if (task) { - console.log(`${task.name} is ready at: ${portInfo.url}`); - } -}); +// Output will not be emitted until you open the task +task.onOutput((output) => { + console.log(output) +}) +const output = await task.open() ``` From ee088501606d97cf06ea691ad13e3fa0d475ee04 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 12 May 2025 15:22:56 +0200 Subject: [PATCH 10/18] build templtaes --- packages/projects-docs/pages/sdk/_meta.json | 6 +-- ...apshot-builder.mdx => build-templates.mdx} | 23 ++++----- packages/projects-docs/pages/sdk/index.mdx | 2 +- packages/projects-docs/pages/sdk/setup.mdx | 47 +++++++------------ packages/projects-docs/pages/sdk/shells.mdx | 4 +- packages/projects-docs/pages/sdk/tasks.mdx | 13 +++++ 6 files changed, 49 insertions(+), 46 deletions(-) rename packages/projects-docs/pages/sdk/{snapshot-builder.mdx => build-templates.mdx} (88%) diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index ee4657f1..34bf8230 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -40,17 +40,17 @@ "tasks": "Tasks", "setup": "Setup", - "-- rest-sessions": { + "-- CLI": { "type": "separator", - "title": "Rest Sessions" + "title": "CLI" }, + "build-templates": "Build Templates", "-- resources": { "type": "separator", "title": "Resources" }, "snapshot-library": "Snapshot Library", - "snapshot-builder": "Snapshot Builder", "environment": "Environment", "docker": "Docker & Docker Compose", "faq": "FAQ" diff --git a/packages/projects-docs/pages/sdk/snapshot-builder.mdx b/packages/projects-docs/pages/sdk/build-templates.mdx similarity index 88% rename from packages/projects-docs/pages/sdk/snapshot-builder.mdx rename to packages/projects-docs/pages/sdk/build-templates.mdx index 55f94491..f07fb92b 100644 --- a/packages/projects-docs/pages/sdk/snapshot-builder.mdx +++ b/packages/projects-docs/pages/sdk/build-templates.mdx @@ -1,21 +1,21 @@ --- -title: Snapshot Builder +title: Build Templates description: Learn how to create your own templates with the CodeSandbox SDK. --- import Video from '../../../../shared-components/Video' -# Snapshot Builder +# Build Templates If you’re using CodeSandbox SDK, there’s a big chance that you want to customize the base environment of your sandboxes. You might want to preload a custom Docker image, or prestart a server that should be available as soon as you create a sandbox. -You can use our CLI to create a memory snapshot that has all this data preloaded. This CLI will build a sandbox based on a folder, load the docker image, start the servers and finally create a memory snapshot that can be used when creating new sandboxes. +You can use our CLI to create a distributed memory snapshot that has all this data preloaded. This CLI will build a sandbox based on a folder, load the docker image, start the servers and finally create a memory snapshot that can be used when creating new sandboxes. It will do this for all our clusters improving latency and availability. New sandboxes can be created from this memory snapshot, which means that new sandboxes will “hit the ground running” with the servers running during snapshot creation. Creating a sandbox from a memory snapshot takes 1-3 seconds. -But you can choose to create your own user sessions with specific permissions. Let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. +## Custom Sessions + +But you can choose to create your own user sessions with specific permissions, git access and even environment variables. Let's say you want a user to connect with the sandbox but don't want them to act on behalf of your global session. Perhaps you want them to have different permissions, or you want to isolate their actions from the global session. + +### Permissions + +By default a user has `write` access, but you can configure a session to have `read` access. ```ts // Now configure a session that only has read access to the sandbox @@ -37,13 +43,39 @@ const session = await sandbox.connect({ // This means I cannot write files or open shells, but I _can_ read them await session.fs.writeTextFile('test.txt', 'Hello World'); // This will throw an error. await session.fs.readTextFile('test.txt'); // This will work. +``` -// I can also create sessions with write access -const session2 = await sandbox.connect({ - id: 'some-user-reference', - permission: 'write', -}); -await session2.fs.writeTextFile('test.ext', 'Hello World'); // This will work +### Git + +By passing the users git access token the user will be able to use git commands inside the sandbox and access whatever the git token gives them access to. + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect({ + id: 'anonymous', + git: { + accessToken: 'github-token', + email: "foo@bar.com", + name: "Foo Bar" + } +}) +``` + +### Environment variables + +By passing environment variables these variables will be available to the user inside the commands and terminals run by the user. + +```ts +const sandbox = await sdk.sandbox.create() +const session = await sandbox.connect({ + id: 'anonymous', + env: { + FOO: 'bar' + } +}) + +const output = await session.commands.run('echo $FOO') +console.log(output) // bar ``` ### Storage diff --git a/packages/projects-docs/pages/sdk/setup.mdx b/packages/projects-docs/pages/sdk/setup.mdx index 5c2fd257..327fe988 100644 --- a/packages/projects-docs/pages/sdk/setup.mdx +++ b/packages/projects-docs/pages/sdk/setup.mdx @@ -37,8 +37,8 @@ const session = await sandbox.connect() console.log(`Setup status: ${session.setup.status}`); -// Wait for the whole thing to finish -await session.setup.waitForFinish() +// Wait for the whole thing to complete +await session.setup.waitUntilComplete() // Or handle each step const steps = await session.setup.getSteps() @@ -54,7 +54,7 @@ for (const step of steps) { console.log(output) }) - await step.waitForFinish() + await step.waitUntilComplete() } ``` From ed00d0b9cafe4c2c03768d03dc8db714fb0eaf66 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Wed, 14 May 2025 15:41:06 +0200 Subject: [PATCH 12/18] fixes --- packages/projects-docs/pages/sdk/commands.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/projects-docs/pages/sdk/commands.mdx b/packages/projects-docs/pages/sdk/commands.mdx index 5061850c..3cd1cfff 100644 --- a/packages/projects-docs/pages/sdk/commands.mdx +++ b/packages/projects-docs/pages/sdk/commands.mdx @@ -19,7 +19,7 @@ The Commands API is available under `sandbox.commands`. const sandbox = await sdk.sandbox.create() const session = await sandbox.connect() -// Run until completion +// Run until completion, also supports an array of commands const output = await session.commands.run("npm install"); console.log(output) From 5db6a4ee613e2e0738b3d837469df60902a55fb5 Mon Sep 17 00:00:00 2001 From: James <91065484+JamesACS@users.noreply.github.com> Date: Wed, 14 May 2025 17:13:57 +0000 Subject: [PATCH 13/18] Copywriting pass --- packages/projects-docs/pages/sdk/_meta.json | 14 +- .../pages/sdk/browser-previews.mdx | 2 +- packages/projects-docs/pages/sdk/browser.mdx | 10 +- .../pages/sdk/build-templates.mdx | 24 +- packages/projects-docs/pages/sdk/commands.mdx | 4 +- packages/projects-docs/pages/sdk/connect.mdx | 4 +- .../projects-docs/pages/sdk/create-resume.mdx | 31 +- .../projects-docs/pages/sdk/environment.mdx | 4 +- .../projects-docs/pages/sdk/filesystem.mdx | 6 +- packages/projects-docs/pages/sdk/fork.mdx | 8 +- .../projects-docs/pages/sdk/hibernate.mdx | 6 +- packages/projects-docs/pages/sdk/index.mdx | 48 +- .../projects-docs/pages/sdk/interpreters.mdx | 2 +- .../projects-docs/pages/sdk/persistence.mdx | 14 +- packages/projects-docs/pages/sdk/ports.mdx | 6 +- .../{preview-hosts.md => preview-hosts.mdx} | 4 +- .../pages/sdk/preview-tokens.mdx | 14 +- packages/projects-docs/pages/sdk/pricing.mdx | 2 + .../pages/sdk/restart-shutdown.mdx | 6 +- packages/projects-docs/pages/sdk/sessions.mdx | 18 +- .../pages/sdk/snapshot-library.mdx | 6 +- packages/projects-docs/pages/sdk/specs.mdx | 2 +- packages/projects-docs/pages/sdk/tasks.mdx | 9 +- .../projects-docs/pages/sdk/terminals.mdx | 4 +- .../pages/sdk/update-sandbox.mdx | 7 +- .../projects-docs/pages/sdk/use-cases.mdx | 30 +- pnpm-lock.yaml | 7369 ++++++++--------- 27 files changed, 3432 insertions(+), 4222 deletions(-) rename packages/projects-docs/pages/sdk/{preview-hosts.md => preview-hosts.mdx} (57%) diff --git a/packages/projects-docs/pages/sdk/_meta.json b/packages/projects-docs/pages/sdk/_meta.json index cd2dbc07..487d1862 100644 --- a/packages/projects-docs/pages/sdk/_meta.json +++ b/packages/projects-docs/pages/sdk/_meta.json @@ -1,12 +1,8 @@ { "index": "Introduction", "use-cases": "Use Cases", + "faq": "FAQ", "pricing": "Pricing", - "contact": { - "title": "Contact Us", - "href": "https://codesandbox.io/support#form", - "newWindow": true - }, "-- sandboxes": { "type": "separator", @@ -54,5 +50,11 @@ "snapshot-library": "Snapshot Library", "environment": "Environment", "docker": "Docker & Docker Compose", - "faq": "FAQ" + "shells": "Shells (Deprecated)", + "contact": { + "title": "Contact Us", + "href": "https://codesandbox.io/support#form", + "newWindow": true + } + } diff --git a/packages/projects-docs/pages/sdk/browser-previews.mdx b/packages/projects-docs/pages/sdk/browser-previews.mdx index d1d97cab..7d31c7e3 100644 --- a/packages/projects-docs/pages/sdk/browser-previews.mdx +++ b/packages/projects-docs/pages/sdk/browser-previews.mdx @@ -72,7 +72,7 @@ preview.reload() ## Inject and Invoke -You can inject any code into the preview. This is useful if you want to insert custom UI, background processes etc. The injected code has access to listening to messages, sending messages and any variables (scope) you send to the injected code. +You can inject any code into the preview. This is useful if you want to insert custom UI elements, background processes.etc The injected code can listen to messages, send messages and has access to any variables (scope) you send to the injected code. ```ts preview.onMessage((message) => { diff --git a/packages/projects-docs/pages/sdk/browser.mdx b/packages/projects-docs/pages/sdk/browser.mdx index 1e958b7a..62c741df 100644 --- a/packages/projects-docs/pages/sdk/browser.mdx +++ b/packages/projects-docs/pages/sdk/browser.mdx @@ -7,7 +7,7 @@ import { Callout } from 'nextra-theme-docs' # Connect Browser -Connecting to a Sandbox in the browser requires some collaboration with your server. Create an endpoint that will resume the sandbox and create a browser session: +Connecting to a Sandbox in the browser requires some collaboration with your server. Create an endpoint that will resume the sandbox and then create a browser session: ```ts export const GET = async ({ params }) => { @@ -18,7 +18,7 @@ export const GET = async ({ params }) => { } ``` -And in the browser: +Then in the browser: ```ts import { connectToSandbox } from '@codesandbox/sdk/browser'; @@ -31,7 +31,7 @@ const session = await connectToSandbox({ await session.fs.writeTextFile('test.txt', 'Hello World'); ``` -The Browser sessions automatically manages the connection and will reconnect if the connection is lost. This is controlled by an option called `onFocusChange`. +The Browser session automatically manages the connection and will reconnect if the connection is lost. This is controlled by an option called `onFocusChange`. ```ts const session = await connectToSandbox({ @@ -51,7 +51,7 @@ const session = await connectToSandbox({ }); ``` -By telling the browser session when it is in focus it will automatically reconnect when hibernated, unless you explicitly disconnected the session. +If you tell the browser session when it is in focus it will automatically reconnect when hibernated. Unless you explicitly disconnect the session. While the `connectToSandbox` promise is resolving you can also listen to initialization events to show a loading state: @@ -65,7 +65,7 @@ const session = await connectToSandbox({ ## Disconnecting the Session -Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. You can alternatively hibernate the sandbox explicitly from the server. +Disconnecting the session will end the session and automatically hibernate the sandbox after a timeout. You can also hibernate the sandbox explicitly from the server. ```ts import { connectToSandbox } from '@codesandbox/sdk/browser' diff --git a/packages/projects-docs/pages/sdk/build-templates.mdx b/packages/projects-docs/pages/sdk/build-templates.mdx index f07fb92b..9ebfa4a2 100644 --- a/packages/projects-docs/pages/sdk/build-templates.mdx +++ b/packages/projects-docs/pages/sdk/build-templates.mdx @@ -7,11 +7,11 @@ import Video from '../../../../shared-components/Video' # Build Templates -If you’re using CodeSandbox SDK, there’s a big chance that you want to customize the base environment of your sandboxes. You might want to preload a custom Docker image, or prestart a server that should be available as soon as you create a sandbox. +If you’re using CodeSandbox SDK, you will likely want to customize the base environment of your sandboxes. Maybe you want to preload a custom Docker image, or prestart a server so it's available as soon as you create a sandbox. -You can use our CLI to create a distributed memory snapshot that has all this data preloaded. This CLI will build a sandbox based on a folder, load the docker image, start the servers and finally create a memory snapshot that can be used when creating new sandboxes. It will do this for all our clusters improving latency and availability. +You can use our CLI to create a distributed memory snapshot that has all this data preloaded. This CLI will build a sandbox based on that folder, load the docker image, start any servers and finally create a memory snapshot that can be used when creating new sandboxes. It will do this for all our clusters improving latency and ensuring availability. -New sandboxes can be created from this memory snapshot, which means that new sandboxes will “hit the ground running” with the servers running during snapshot creation. Creating a sandbox from a memory snapshot takes 1-3 seconds. +New sandboxes can then be created from this memory snapshot, which means that those sandboxes will “hit the ground running”. Creating your new environment from the memory snapshot in only 1-3 seconds.