Skip to content

Commit 993e509

Browse files
authored
feat: support for private previews and preview tokens (#63)
1 parent eed86b1 commit 993e509

File tree

10 files changed

+2614
-782
lines changed

10 files changed

+2614
-782
lines changed

openapi.json

Lines changed: 1537 additions & 538 deletions
Large diffs are not rendered by default.

src/bin/commands/sandbox/index.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { forkSandbox } from "./fork";
33
import { hibernateSandbox } from "./hibernate";
44
import { listSandboxes } from "./list";
55
import { shutdownSandbox } from "./shutdown";
6+
import {
7+
listPreviewTokens,
8+
createPreviewToken,
9+
revokeAllPreviewTokens,
10+
revokePreviewToken,
11+
updatePreviewToken,
12+
} from "./preview-tokens";
613

714
const DEFAULT_LIMIT = 100;
815

@@ -95,10 +102,11 @@ export const sandboxCommand: CommandModule = {
95102
return yargs.positional("id", {
96103
describe: "ID of the sandbox to fork",
97104
type: "string",
105+
demandOption: true,
98106
});
99107
},
100108
handler: async (argv) => {
101-
await forkSandbox(argv.id as string);
109+
await forkSandbox(argv.id);
102110
},
103111
})
104112
.command({
@@ -129,6 +137,113 @@ export const sandboxCommand: CommandModule = {
129137
await shutdownSandbox(argv.id);
130138
},
131139
})
140+
.command({
141+
command: "preview-tokens",
142+
describe: "Manage preview tokens",
143+
builder: (yargs) => {
144+
return yargs
145+
.command({
146+
command: "list <id>",
147+
describe: "List preview tokens",
148+
builder: (yargs) => {
149+
return yargs.positional("id", {
150+
describe: "ID of the sandbox",
151+
type: "string",
152+
demandOption: true,
153+
});
154+
},
155+
handler: async (argv) => {
156+
await listPreviewTokens(argv.id);
157+
},
158+
})
159+
.command({
160+
command: "create <id>",
161+
describe: "Create a preview token",
162+
builder: (yargs) => {
163+
return yargs
164+
.positional("id", {
165+
describe: "ID of the sandbox",
166+
type: "string",
167+
demandOption: true,
168+
})
169+
.option("expires-at", {
170+
alias: "e",
171+
describe:
172+
"Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to create a token that never expires.",
173+
type: "string",
174+
});
175+
},
176+
handler: async (argv) => {
177+
await createPreviewToken(argv.id, argv["expires-at"]);
178+
},
179+
})
180+
.command({
181+
command: "update <sandbox-id> <preview-token-id>",
182+
describe:
183+
"Update the expiration date of a preview token, if no expiration",
184+
builder: (yargs) => {
185+
return yargs
186+
.positional("sandbox-id", {
187+
describe: "ID of the sandbox",
188+
type: "string",
189+
demandOption: true,
190+
})
191+
.positional("preview-token-id", {
192+
describe: "ID of the preview token",
193+
type: "string",
194+
demandOption: true,
195+
})
196+
.option("expires-at", {
197+
alias: "e",
198+
describe:
199+
"Expiration date (ISO 8601 format, e.g. 2024-12-31T23:59:59Z). Can be omitted to remove the expiration date.",
200+
type: "string",
201+
});
202+
},
203+
handler: async (argv) => {
204+
await updatePreviewToken(
205+
argv["sandbox-id"],
206+
argv["preview-token-id"],
207+
argv["expires-at"]
208+
);
209+
},
210+
})
211+
.command({
212+
command: "revoke <sandbox-id> <preview-token-id>",
213+
describe: "Revoke preview token(s)",
214+
builder: (yargs) => {
215+
return yargs
216+
.positional("sandbox-id", {
217+
describe: "ID of the sandbox",
218+
type: "string",
219+
demandOption: true,
220+
})
221+
.positional("preview-token-id", {
222+
describe: "ID of the preview token",
223+
type: "string",
224+
demandOption: true,
225+
})
226+
.option("all", {
227+
alias: "a",
228+
describe: "Revoke all preview tokens",
229+
type: "boolean",
230+
});
231+
},
232+
handler: async (argv) => {
233+
if (argv.all) {
234+
await revokeAllPreviewTokens(argv["sandbox-id"]);
235+
} else {
236+
await revokePreviewToken(
237+
argv["sandbox-id"],
238+
argv["preview-token-id"]
239+
);
240+
}
241+
},
242+
})
243+
.demandCommand(1, "Please specify a preview-tokens command");
244+
},
245+
handler: () => {},
246+
})
132247
.demandCommand(1, "Please specify a sandbox command");
133248
},
134249
handler: () => {},
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import ora from "ora";
2+
import Table from "cli-table3";
3+
import { CodeSandbox } from "../../../";
4+
5+
function formatDate(date: Date): string {
6+
return date.toLocaleString();
7+
}
8+
9+
export async function listPreviewTokens(sandboxId: string) {
10+
const sdk = new CodeSandbox();
11+
const spinner = ora("Fetching preview tokens...").start();
12+
13+
try {
14+
const tokens = await sdk.sandbox.previewTokens.list(sandboxId);
15+
spinner.stop();
16+
17+
if (tokens.length === 0) {
18+
console.log("No preview tokens found");
19+
return;
20+
}
21+
22+
const table = new Table({
23+
head: ["ID", "PREFIX", "LAST USED", "EXPIRES"],
24+
style: {
25+
head: ["bold"],
26+
border: [],
27+
},
28+
chars: {
29+
top: "",
30+
"top-mid": "",
31+
"top-left": "",
32+
"top-right": "",
33+
bottom: "",
34+
"bottom-mid": "",
35+
"bottom-left": "",
36+
"bottom-right": "",
37+
left: "",
38+
"left-mid": "",
39+
right: "",
40+
"right-mid": "",
41+
mid: "",
42+
"mid-mid": "",
43+
middle: " ",
44+
},
45+
});
46+
47+
tokens.forEach((token) => {
48+
table.push([
49+
token.tokenId,
50+
token.tokenPrefix,
51+
token.lastUsedAt ? formatDate(token.lastUsedAt) : "Never",
52+
token.expiresAt ? formatDate(token.expiresAt) : "Never",
53+
]);
54+
});
55+
56+
console.log(table.toString());
57+
} catch (error) {
58+
spinner.fail("Failed to fetch preview tokens");
59+
throw error;
60+
}
61+
}
62+
63+
export async function createPreviewToken(
64+
sandboxId: string,
65+
expiresAt?: string
66+
) {
67+
const sdk = new CodeSandbox();
68+
const spinner = ora("Creating preview token...").start();
69+
70+
try {
71+
const token = await sdk.sandbox.previewTokens.create(
72+
sandboxId,
73+
expiresAt ? new Date(expiresAt) : null
74+
);
75+
spinner.stop();
76+
77+
const table = new Table({
78+
head: ["TOKEN", "ID", "LAST USED", "EXPIRES"],
79+
style: {
80+
head: ["bold"],
81+
border: [],
82+
},
83+
chars: {
84+
top: "",
85+
"top-mid": "",
86+
"top-left": "",
87+
"top-right": "",
88+
bottom: "",
89+
"bottom-mid": "",
90+
"bottom-left": "",
91+
"bottom-right": "",
92+
left: "",
93+
"left-mid": "",
94+
right: "",
95+
"right-mid": "",
96+
mid: "",
97+
"mid-mid": "",
98+
middle: " ",
99+
},
100+
});
101+
102+
table.push([
103+
token.token,
104+
token.tokenId,
105+
token.lastUsedAt ? formatDate(token.lastUsedAt) : "Never",
106+
token.expiresAt ? formatDate(token.expiresAt) : "Never",
107+
]);
108+
109+
console.log("Preview token created successfully:");
110+
console.log(table.toString());
111+
} catch (error) {
112+
spinner.fail("Failed to create preview token");
113+
throw error;
114+
}
115+
}
116+
117+
export async function revokePreviewToken(
118+
sandboxId: string,
119+
previewTokenId: string
120+
) {
121+
const sdk = new CodeSandbox();
122+
const spinner = ora("Revoking preview token...").start();
123+
124+
try {
125+
await sdk.sandbox.previewTokens.revoke(sandboxId, previewTokenId);
126+
spinner.stop();
127+
console.log("Preview token revoked successfully");
128+
} catch (error) {
129+
spinner.fail("Failed to revoke preview token");
130+
throw error;
131+
}
132+
}
133+
134+
export async function updatePreviewToken(
135+
sandboxId: string,
136+
previewTokenId: string,
137+
expiresAt?: string
138+
) {
139+
const sdk = new CodeSandbox();
140+
const spinner = ora("Updating preview token...").start();
141+
142+
try {
143+
await sdk.sandbox.previewTokens.update(
144+
sandboxId,
145+
previewTokenId,
146+
expiresAt ? new Date(expiresAt) : null
147+
);
148+
spinner.stop();
149+
console.log("Preview token updated successfully");
150+
} catch (error) {
151+
spinner.fail("Failed to update preview token");
152+
throw error;
153+
}
154+
}
155+
156+
export async function revokeAllPreviewTokens(sandboxId: string) {
157+
const sdk = new CodeSandbox();
158+
const spinner = ora("Revoking all preview tokens...").start();
159+
160+
try {
161+
await sdk.sandbox.previewTokens.revokeAll(sandboxId);
162+
spinner.stop();
163+
console.log("All preview tokens have been revoked");
164+
} catch (error) {
165+
spinner.fail("Failed to revoke preview tokens");
166+
throw error;
167+
}
168+
}

src/browser.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ function isStartData(
2424
* requires your CodeSandbox API token to be sent with every request. This makes it
2525
* unsafe to use from the browser, where you don't want to expose your API token.
2626
*
27-
* With this helper function, you can create a connection to a sandbox without
28-
* exposing your API token.
27+
* With this helper function, you can generate a sandbox on the server, and then share a single-use
28+
* token that can be used to create a connection to that sandbox from the browser.
2929
*
3030
* ## Example
3131
*

0 commit comments

Comments
 (0)