@vercel/sandbox 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.16 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.18 build /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc
4
4
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @vercel/sandbox@0.0.16 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
2
+ > @vercel/sandbox@0.0.18 typecheck /home/runner/work/sandbox-sdk/sandbox-sdk/packages/sandbox
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @vercel/sandbox
2
2
 
3
+ ## 0.0.18
4
+
5
+ ### Patch Changes
6
+
7
+ - refresh oidc token if stale in development ([#106](https://github.com/vercel/sandbox-sdk/pull/106))
8
+
9
+ ## 0.0.17
10
+
11
+ ### Patch Changes
12
+
13
+ - Sandboxes can now expose up to 4 ports ([#99](https://github.com/vercel/sandbox-sdk/pull/99))
14
+
15
+ This applies to all SDK versions, but this SDK release documents the limit.
16
+
17
+ - Fix bug in Sandbox.writeFile, add support for absolute paths ([#97](https://github.com/vercel/sandbox-sdk/pull/97))
18
+
3
19
  ## 0.0.16
4
20
 
5
21
  ### Patch Changes
@@ -4,11 +4,13 @@ import { FileWriter } from "./file-writer";
4
4
  import { z } from "zod";
5
5
  export declare class APIClient extends BaseClient {
6
6
  private teamId;
7
+ private tokenExpiry;
7
8
  constructor(params: {
8
9
  host?: string;
9
10
  teamId: string;
10
11
  token: string;
11
12
  });
13
+ private ensureValidToken;
12
14
  protected request(path: string, params?: RequestParams): Promise<Response>;
13
15
  getSandbox(params: {
14
16
  sandboxId: string;
@@ -23,6 +25,7 @@ export declare class APIClient extends BaseClient {
23
25
  timeout: number;
24
26
  requestedAt: number;
25
27
  createdAt: number;
28
+ cwd: string;
26
29
  updatedAt: number;
27
30
  duration?: number | undefined;
28
31
  startedAt?: number | undefined;
@@ -65,6 +68,7 @@ export declare class APIClient extends BaseClient {
65
68
  timeout: number;
66
69
  requestedAt: number;
67
70
  createdAt: number;
71
+ cwd: string;
68
72
  updatedAt: number;
69
73
  duration?: number | undefined;
70
74
  startedAt?: number | undefined;
@@ -112,16 +116,19 @@ export declare class APIClient extends BaseClient {
112
116
  }): Promise<Parsed<{}>>;
113
117
  getFileWriter(params: {
114
118
  sandboxId: string;
119
+ extractDir: string;
115
120
  }): {
116
121
  response: Promise<Response>;
117
122
  writer: FileWriter;
118
123
  };
119
124
  writeFiles(params: {
120
125
  sandboxId: string;
126
+ cwd: string;
121
127
  files: {
122
128
  path: string;
123
129
  content: Buffer;
124
130
  }[];
131
+ extractDir: string;
125
132
  }): Promise<void>;
126
133
  readFile(params: {
127
134
  sandboxId: string;
@@ -13,6 +13,8 @@ const consume_readable_1 = require("../utils/consume-readable");
13
13
  const jsonlines_1 = __importDefault(require("jsonlines"));
14
14
  const os_1 = __importDefault(require("os"));
15
15
  const stream_1 = require("stream");
16
+ const normalizePath_1 = require("../utils/normalizePath");
17
+ const jwt_expiry_1 = require("../utils/jwt-expiry");
16
18
  class APIClient extends base_client_1.BaseClient {
17
19
  constructor(params) {
18
20
  super({
@@ -21,8 +23,24 @@ class APIClient extends base_client_1.BaseClient {
21
23
  debug: false,
22
24
  });
23
25
  this.teamId = params.teamId;
26
+ this.tokenExpiry = jwt_expiry_1.JwtExpiry.fromToken(params.token);
27
+ }
28
+ async ensureValidToken() {
29
+ if (!this.tokenExpiry) {
30
+ return;
31
+ }
32
+ const newExpiry = await this.tokenExpiry.tryRefresh();
33
+ if (!newExpiry) {
34
+ return;
35
+ }
36
+ this.tokenExpiry = newExpiry;
37
+ this.token = this.tokenExpiry.token;
38
+ if (this.tokenExpiry.payload) {
39
+ this.teamId = this.tokenExpiry.payload?.owner_id;
40
+ }
24
41
  }
25
42
  async request(path, params) {
43
+ await this.ensureValidToken();
26
44
  return super.request(path, {
27
45
  ...params,
28
46
  query: { teamId: this.teamId, ...params?.query },
@@ -78,7 +96,10 @@ class APIClient extends base_client_1.BaseClient {
78
96
  response: (async () => {
79
97
  return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, {
80
98
  method: "POST",
81
- headers: { "content-type": "application/gzip" },
99
+ headers: {
100
+ "content-type": "application/gzip",
101
+ "x-cwd": params.extractDir,
102
+ },
82
103
  body: await (0, consume_readable_1.consumeReadable)(writer.readable),
83
104
  });
84
105
  })(),
@@ -88,9 +109,17 @@ class APIClient extends base_client_1.BaseClient {
88
109
  async writeFiles(params) {
89
110
  const { writer, response } = this.getFileWriter({
90
111
  sandboxId: params.sandboxId,
112
+ extractDir: params.extractDir,
91
113
  });
92
114
  for (const file of params.files) {
93
- await writer.addFile({ name: file.path, content: file.content });
115
+ await writer.addFile({
116
+ name: (0, normalizePath_1.normalizePath)({
117
+ filePath: file.path,
118
+ extractDir: params.extractDir,
119
+ cwd: params.cwd,
120
+ }),
121
+ content: file.content,
122
+ });
94
123
  }
95
124
  writer.end();
96
125
  await (0, base_client_1.parseOrThrow)(validators_1.EmptyResponse, await response);
@@ -14,6 +14,7 @@ export declare const Sandbox: z.ZodObject<{
14
14
  stoppedAt: z.ZodOptional<z.ZodNumber>;
15
15
  duration: z.ZodOptional<z.ZodNumber>;
16
16
  createdAt: z.ZodNumber;
17
+ cwd: z.ZodString;
17
18
  updatedAt: z.ZodNumber;
18
19
  }, "strip", z.ZodTypeAny, {
19
20
  region: string;
@@ -25,6 +26,7 @@ export declare const Sandbox: z.ZodObject<{
25
26
  timeout: number;
26
27
  requestedAt: number;
27
28
  createdAt: number;
29
+ cwd: string;
28
30
  updatedAt: number;
29
31
  duration?: number | undefined;
30
32
  startedAt?: number | undefined;
@@ -40,6 +42,7 @@ export declare const Sandbox: z.ZodObject<{
40
42
  timeout: number;
41
43
  requestedAt: number;
42
44
  createdAt: number;
45
+ cwd: string;
43
46
  updatedAt: number;
44
47
  duration?: number | undefined;
45
48
  startedAt?: number | undefined;
@@ -101,6 +104,7 @@ export declare const SandboxResponse: z.ZodObject<{
101
104
  stoppedAt: z.ZodOptional<z.ZodNumber>;
102
105
  duration: z.ZodOptional<z.ZodNumber>;
103
106
  createdAt: z.ZodNumber;
107
+ cwd: z.ZodString;
104
108
  updatedAt: z.ZodNumber;
105
109
  }, "strip", z.ZodTypeAny, {
106
110
  region: string;
@@ -112,6 +116,7 @@ export declare const SandboxResponse: z.ZodObject<{
112
116
  timeout: number;
113
117
  requestedAt: number;
114
118
  createdAt: number;
119
+ cwd: string;
115
120
  updatedAt: number;
116
121
  duration?: number | undefined;
117
122
  startedAt?: number | undefined;
@@ -127,6 +132,7 @@ export declare const SandboxResponse: z.ZodObject<{
127
132
  timeout: number;
128
133
  requestedAt: number;
129
134
  createdAt: number;
135
+ cwd: string;
130
136
  updatedAt: number;
131
137
  duration?: number | undefined;
132
138
  startedAt?: number | undefined;
@@ -144,6 +150,7 @@ export declare const SandboxResponse: z.ZodObject<{
144
150
  timeout: number;
145
151
  requestedAt: number;
146
152
  createdAt: number;
153
+ cwd: string;
147
154
  updatedAt: number;
148
155
  duration?: number | undefined;
149
156
  startedAt?: number | undefined;
@@ -161,6 +168,7 @@ export declare const SandboxResponse: z.ZodObject<{
161
168
  timeout: number;
162
169
  requestedAt: number;
163
170
  createdAt: number;
171
+ cwd: string;
164
172
  updatedAt: number;
165
173
  duration?: number | undefined;
166
174
  startedAt?: number | undefined;
@@ -183,6 +191,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
183
191
  stoppedAt: z.ZodOptional<z.ZodNumber>;
184
192
  duration: z.ZodOptional<z.ZodNumber>;
185
193
  createdAt: z.ZodNumber;
194
+ cwd: z.ZodString;
186
195
  updatedAt: z.ZodNumber;
187
196
  }, "strip", z.ZodTypeAny, {
188
197
  region: string;
@@ -194,6 +203,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
194
203
  timeout: number;
195
204
  requestedAt: number;
196
205
  createdAt: number;
206
+ cwd: string;
197
207
  updatedAt: number;
198
208
  duration?: number | undefined;
199
209
  startedAt?: number | undefined;
@@ -209,6 +219,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
209
219
  timeout: number;
210
220
  requestedAt: number;
211
221
  createdAt: number;
222
+ cwd: string;
212
223
  updatedAt: number;
213
224
  duration?: number | undefined;
214
225
  startedAt?: number | undefined;
@@ -240,6 +251,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
240
251
  timeout: number;
241
252
  requestedAt: number;
242
253
  createdAt: number;
254
+ cwd: string;
243
255
  updatedAt: number;
244
256
  duration?: number | undefined;
245
257
  startedAt?: number | undefined;
@@ -262,6 +274,7 @@ export declare const SandboxAndRoutesResponse: z.ZodObject<{
262
274
  timeout: number;
263
275
  requestedAt: number;
264
276
  createdAt: number;
277
+ cwd: string;
265
278
  updatedAt: number;
266
279
  duration?: number | undefined;
267
280
  startedAt?: number | undefined;
@@ -16,6 +16,7 @@ exports.Sandbox = zod_1.z.object({
16
16
  stoppedAt: zod_1.z.number().optional(),
17
17
  duration: zod_1.z.number().optional(),
18
18
  createdAt: zod_1.z.number(),
19
+ cwd: zod_1.z.string(),
19
20
  updatedAt: zod_1.z.number(),
20
21
  });
21
22
  exports.SandboxRoute = zod_1.z.object({
package/dist/command.d.ts CHANGED
@@ -3,8 +3,11 @@ import { Signal } from "./utils/resolveSignal";
3
3
  /**
4
4
  * A command executed in a Sandbox.
5
5
  *
6
- * You can {@link wait} on commands to access their {@link CommandFinished.exitCode}, and
7
- * iterate over their output with {@link logs}.
6
+ * For detached commands, you can {@link wait} to get a {@link CommandFinished} instance
7
+ * with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}
8
+ * automatically waits and returns a {@link CommandFinished} instance.
9
+ *
10
+ * You can iterate over command output with {@link logs}.
8
11
  *
9
12
  * @see {@link Sandbox.runCommand} to start a command.
10
13
  *
@@ -67,9 +70,14 @@ export declare class Command {
67
70
  /**
68
71
  * Wait for a command to exit and populate its exit code.
69
72
  *
73
+ * This method is useful for detached commands where you need to wait
74
+ * for completion. For non-detached commands, {@link Sandbox.runCommand}
75
+ * automatically waits and returns a {@link CommandFinished} instance.
76
+ *
70
77
  * ```
71
- * await cmd.wait()
72
- * if (cmd.exitCode != 0) {
78
+ * const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });
79
+ * const result = await detachedCmd.wait();
80
+ * if (result.exitCode !== 0) {
73
81
  * console.error("Something went wrong...")
74
82
  * }
75
83
  * ```
@@ -117,14 +125,16 @@ export declare class Command {
117
125
  /**
118
126
  * A command that has finished executing.
119
127
  *
120
- * Contains the exit code of the command.
128
+ * The exit code is immediately available and populated upon creation.
129
+ * Unlike {@link Command}, you don't need to call wait() - the command
130
+ * has already completed execution.
121
131
  *
122
132
  * @hideconstructor
123
133
  */
124
134
  export declare class CommandFinished extends Command {
125
135
  /**
126
- * The exit code of the command, if available. This is set after
127
- * {@link wait} has returned.
136
+ * The exit code of the command. This is always populated for
137
+ * CommandFinished instances.
128
138
  */
129
139
  exitCode: number;
130
140
  /**
@@ -140,4 +150,13 @@ export declare class CommandFinished extends Command {
140
150
  cmd: CommandData;
141
151
  exitCode: number;
142
152
  });
153
+ /**
154
+ * The wait method is not needed for CommandFinished instances since
155
+ * the command has already completed and exitCode is populated.
156
+ *
157
+ * @deprecated This method is redundant for CommandFinished instances.
158
+ * The exitCode is already available.
159
+ * @returns This CommandFinished instance.
160
+ */
161
+ wait(): Promise<CommandFinished>;
143
162
  }
package/dist/command.js CHANGED
@@ -5,8 +5,11 @@ const resolveSignal_1 = require("./utils/resolveSignal");
5
5
  /**
6
6
  * A command executed in a Sandbox.
7
7
  *
8
- * You can {@link wait} on commands to access their {@link CommandFinished.exitCode}, and
9
- * iterate over their output with {@link logs}.
8
+ * For detached commands, you can {@link wait} to get a {@link CommandFinished} instance
9
+ * with the populated exit code. For non-detached commands, {@link Sandbox.runCommand}
10
+ * automatically waits and returns a {@link CommandFinished} instance.
11
+ *
12
+ * You can iterate over command output with {@link logs}.
10
13
  *
11
14
  * @see {@link Sandbox.runCommand} to start a command.
12
15
  *
@@ -64,9 +67,14 @@ class Command {
64
67
  /**
65
68
  * Wait for a command to exit and populate its exit code.
66
69
  *
70
+ * This method is useful for detached commands where you need to wait
71
+ * for completion. For non-detached commands, {@link Sandbox.runCommand}
72
+ * automatically waits and returns a {@link CommandFinished} instance.
73
+ *
67
74
  * ```
68
- * await cmd.wait()
69
- * if (cmd.exitCode != 0) {
75
+ * const detachedCmd = await sandbox.runCommand({ cmd: 'sleep', args: ['5'], detached: true });
76
+ * const result = await detachedCmd.wait();
77
+ * if (result.exitCode !== 0) {
70
78
  * console.error("Something went wrong...")
71
79
  * }
72
80
  * ```
@@ -145,7 +153,9 @@ exports.Command = Command;
145
153
  /**
146
154
  * A command that has finished executing.
147
155
  *
148
- * Contains the exit code of the command.
156
+ * The exit code is immediately available and populated upon creation.
157
+ * Unlike {@link Command}, you don't need to call wait() - the command
158
+ * has already completed execution.
149
159
  *
150
160
  * @hideconstructor
151
161
  */
@@ -161,5 +171,16 @@ class CommandFinished extends Command {
161
171
  super({ ...params });
162
172
  this.exitCode = params.exitCode;
163
173
  }
174
+ /**
175
+ * The wait method is not needed for CommandFinished instances since
176
+ * the command has already completed and exitCode is populated.
177
+ *
178
+ * @deprecated This method is redundant for CommandFinished instances.
179
+ * The exitCode is already available.
180
+ * @returns This CommandFinished instance.
181
+ */
182
+ async wait() {
183
+ return this;
184
+ }
164
185
  }
165
186
  exports.CommandFinished = CommandFinished;
package/dist/sandbox.d.ts CHANGED
@@ -31,7 +31,8 @@ export interface CreateSandboxParams {
31
31
  url: string;
32
32
  };
33
33
  /**
34
- * Array of port numbers to expose from the sandbox.
34
+ * Array of port numbers to expose from the sandbox. Sandboxes can
35
+ * expose up to 4 ports.
35
36
  */
36
37
  ports?: number[];
37
38
  /**
@@ -184,7 +185,7 @@ export declare class Sandbox {
184
185
  * @returns A {@link Command} or {@link CommandFinished}, depending on `detached`.
185
186
  * @internal
186
187
  */
187
- _runCommand(params: RunCommandParams): Promise<CommandFinished | Command>;
188
+ _runCommand(params: RunCommandParams): Promise<Command | CommandFinished>;
188
189
  /**
189
190
  * Create a directory in the filesystem of this sandbox.
190
191
  *
@@ -203,6 +204,8 @@ export declare class Sandbox {
203
204
  }): Promise<NodeJS.ReadableStream | null>;
204
205
  /**
205
206
  * Write files to the filesystem of this sandbox.
207
+ * Defaults to writing to /vercel/sandbox unless an absolute path is specified.
208
+ * Writes files using the `vercel-sandbox` user.
206
209
  *
207
210
  * @param files - Array of files with path and stream/buffer contents
208
211
  * @returns A promise that resolves when the files are written
package/dist/sandbox.js CHANGED
@@ -30,7 +30,7 @@ class Sandbox {
30
30
  * @returns A promise resolving to the created {@link Sandbox}.
31
31
  */
32
32
  static async create(params) {
33
- const credentials = (0, get_credentials_1.getCredentials)(params);
33
+ const credentials = await (0, get_credentials_1.getCredentials)(params);
34
34
  const client = new api_client_1.APIClient({
35
35
  teamId: credentials.teamId,
36
36
  token: credentials.token,
@@ -56,7 +56,7 @@ class Sandbox {
56
56
  * @returns A promise resolving to the {@link Sandbox}.
57
57
  */
58
58
  static async get(params) {
59
- const credentials = (0, get_credentials_1.getCredentials)(params);
59
+ const credentials = await (0, get_credentials_1.getCredentials)(params);
60
60
  const client = new api_client_1.APIClient({
61
61
  teamId: credentials.teamId,
62
62
  token: credentials.token,
@@ -165,6 +165,8 @@ class Sandbox {
165
165
  }
166
166
  /**
167
167
  * Write files to the filesystem of this sandbox.
168
+ * Defaults to writing to /vercel/sandbox unless an absolute path is specified.
169
+ * Writes files using the `vercel-sandbox` user.
168
170
  *
169
171
  * @param files - Array of files with path and stream/buffer contents
170
172
  * @returns A promise that resolves when the files are written
@@ -172,6 +174,8 @@ class Sandbox {
172
174
  async writeFiles(files) {
173
175
  return this.client.writeFiles({
174
176
  sandboxId: this.sandbox.id,
177
+ cwd: this.sandbox.cwd,
178
+ extractDir: "/",
175
179
  files: files,
176
180
  });
177
181
  }
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  export interface Credentials {
2
3
  /**
3
4
  * Authentication token for the Vercel API. It could be an OIDC token
@@ -23,4 +24,24 @@ export interface Credentials {
23
24
  * If both methods are used, the object properties take precedence over the
24
25
  * environment variable. If neither method is used, an error is thrown.
25
26
  */
26
- export declare function getCredentials<T>(params?: T | Credentials): Credentials;
27
+ export declare function getCredentials(params?: unknown): Promise<Credentials>;
28
+ /**
29
+ * Schema to validate the payload of the Vercel OIDC token where we expect
30
+ * to find the `teamId` and `projectId`.
31
+ */
32
+ export declare const schema: z.ZodObject<{
33
+ exp: z.ZodOptional<z.ZodNumber>;
34
+ iat: z.ZodOptional<z.ZodNumber>;
35
+ owner_id: z.ZodString;
36
+ project_id: z.ZodString;
37
+ }, "strip", z.ZodTypeAny, {
38
+ owner_id: string;
39
+ project_id: string;
40
+ exp?: number | undefined;
41
+ iat?: number | undefined;
42
+ }, {
43
+ owner_id: string;
44
+ project_id: string;
45
+ exp?: number | undefined;
46
+ iat?: number | undefined;
47
+ }>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.schema = void 0;
3
4
  exports.getCredentials = getCredentials;
4
5
  const oidc_1 = require("@vercel/oidc");
5
6
  const decode_base64_url_1 = require("./decode-base64-url");
@@ -14,12 +15,12 @@ const zod_1 = require("zod");
14
15
  * If both methods are used, the object properties take precedence over the
15
16
  * environment variable. If neither method is used, an error is thrown.
16
17
  */
17
- function getCredentials(params) {
18
+ async function getCredentials(params) {
18
19
  const credentials = getCredentialsFromParams(params ?? {});
19
20
  if (credentials) {
20
21
  return credentials;
21
22
  }
22
- const oidcToken = (0, oidc_1.getVercelOidcTokenSync)();
23
+ const oidcToken = await (0, oidc_1.getVercelOidcToken)();
23
24
  if (oidcToken) {
24
25
  return getCredentialsFromOIDCToken(oidcToken);
25
26
  }
@@ -32,6 +33,10 @@ function getCredentials(params) {
32
33
  * or none of them, otherwise an error is thrown.
33
34
  */
34
35
  function getCredentialsFromParams(params) {
36
+ // Type guard: params must be an object
37
+ if (!params || typeof params !== "object") {
38
+ return null;
39
+ }
35
40
  const missing = [
36
41
  "token" in params && typeof params.token === "string" ? null : "token",
37
42
  "teamId" in params && typeof params.teamId === "string" ? null : "teamId",
@@ -57,7 +62,9 @@ function getCredentialsFromParams(params) {
57
62
  * Schema to validate the payload of the Vercel OIDC token where we expect
58
63
  * to find the `teamId` and `projectId`.
59
64
  */
60
- const schema = zod_1.z.object({
65
+ exports.schema = zod_1.z.object({
66
+ exp: zod_1.z.number().optional().describe("Expiry timestamp (seconds since epoch)"),
67
+ iat: zod_1.z.number().optional().describe("Issued at timestamp"),
61
68
  owner_id: zod_1.z.string(),
62
69
  project_id: zod_1.z.string(),
63
70
  });
@@ -71,7 +78,7 @@ const schema = zod_1.z.object({
71
78
  */
72
79
  function getCredentialsFromOIDCToken(token) {
73
80
  try {
74
- const payload = schema.parse((0, decode_base64_url_1.decodeBase64Url)(token.split(".")[1]));
81
+ const payload = exports.schema.parse((0, decode_base64_url_1.decodeBase64Url)(token.split(".")[1]));
75
82
  return {
76
83
  token,
77
84
  projectId: payload.project_id,
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { schema } from "./get-credentials";
3
+ export declare class OidcRefreshError extends Error {
4
+ name: string;
5
+ }
6
+ /**
7
+ * Expiry implementation for JWT tokens (OIDC tokens).
8
+ * Parses the JWT once and provides fast expiry validation.
9
+ */
10
+ export declare class JwtExpiry {
11
+ readonly token: string;
12
+ private expiryTime;
13
+ readonly payload?: Readonly<z.infer<typeof schema>>;
14
+ static fromToken(token: string): JwtExpiry | null;
15
+ /**
16
+ * Creates a new JWT expiry checker.
17
+ *
18
+ * @param token - The JWT token to parse
19
+ */
20
+ constructor(token: string);
21
+ /**
22
+ * Checks if the JWT token is valid (not expired).
23
+ * @returns true if token is valid, false if expired or expiring soon
24
+ */
25
+ isValid(): boolean;
26
+ /**
27
+ * Gets the expiry date of the JWT token.
28
+ *
29
+ * @returns Date object representing when the token expires, or null if no expiry
30
+ */
31
+ getExpiryDate(): Date | null;
32
+ /**
33
+ * Refreshes the JWT token by fetching a new OIDC token.
34
+ *
35
+ * @returns Promise resolving to a new JwtExpiry instance with fresh token
36
+ */
37
+ refresh(): Promise<JwtExpiry>;
38
+ /**
39
+ * Refreshes the JWT token if it's expired or expiring soon.
40
+ */
41
+ tryRefresh(): Promise<JwtExpiry | null>;
42
+ }
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.JwtExpiry = exports.OidcRefreshError = void 0;
7
+ const decode_base64_url_1 = require("./decode-base64-url");
8
+ const get_credentials_1 = require("./get-credentials");
9
+ const oidc_1 = require("@vercel/oidc");
10
+ const ms_1 = __importDefault(require("ms"));
11
+ /** Time buffer before token expiry to consider it invalid (in milliseconds) */
12
+ const BUFFER_MS = (0, ms_1.default)("5m");
13
+ class OidcRefreshError extends Error {
14
+ constructor() {
15
+ super(...arguments);
16
+ this.name = "OidcRefreshError";
17
+ }
18
+ }
19
+ exports.OidcRefreshError = OidcRefreshError;
20
+ /**
21
+ * Expiry implementation for JWT tokens (OIDC tokens).
22
+ * Parses the JWT once and provides fast expiry validation.
23
+ */
24
+ class JwtExpiry {
25
+ static fromToken(token) {
26
+ if (!isJwtFormat(token)) {
27
+ return null;
28
+ }
29
+ else {
30
+ return new JwtExpiry(token);
31
+ }
32
+ }
33
+ /**
34
+ * Creates a new JWT expiry checker.
35
+ *
36
+ * @param token - The JWT token to parse
37
+ */
38
+ constructor(token) {
39
+ this.token = token;
40
+ try {
41
+ const tokenContents = token.split(".")[1];
42
+ this.payload = get_credentials_1.schema.parse((0, decode_base64_url_1.decodeBase64Url)(tokenContents));
43
+ this.expiryTime = this.payload.exp || null;
44
+ }
45
+ catch {
46
+ // Malformed token - treat as expired to trigger refresh
47
+ this.expiryTime = 0;
48
+ }
49
+ }
50
+ /**
51
+ * Checks if the JWT token is valid (not expired).
52
+ * @returns true if token is valid, false if expired or expiring soon
53
+ */
54
+ isValid() {
55
+ if (this.expiryTime === null) {
56
+ return false; // No expiry means malformed JWT
57
+ }
58
+ const now = Math.floor(Date.now() / 1000);
59
+ const buffer = BUFFER_MS / 1000;
60
+ return now + buffer < this.expiryTime;
61
+ }
62
+ /**
63
+ * Gets the expiry date of the JWT token.
64
+ *
65
+ * @returns Date object representing when the token expires, or null if no expiry
66
+ */
67
+ getExpiryDate() {
68
+ return this.expiryTime ? new Date(this.expiryTime * 1000) : null;
69
+ }
70
+ /**
71
+ * Refreshes the JWT token by fetching a new OIDC token.
72
+ *
73
+ * @returns Promise resolving to a new JwtExpiry instance with fresh token
74
+ */
75
+ async refresh() {
76
+ try {
77
+ const freshToken = await (0, oidc_1.getVercelOidcToken)();
78
+ return new JwtExpiry(freshToken);
79
+ }
80
+ catch (cause) {
81
+ throw new OidcRefreshError("Failed to refresh OIDC token", {
82
+ cause,
83
+ });
84
+ }
85
+ }
86
+ /**
87
+ * Refreshes the JWT token if it's expired or expiring soon.
88
+ */
89
+ async tryRefresh() {
90
+ if (this.isValid()) {
91
+ return null; // Still valid, no need to refresh
92
+ }
93
+ return this.refresh();
94
+ }
95
+ }
96
+ exports.JwtExpiry = JwtExpiry;
97
+ /**
98
+ * Checks if a token follows JWT format (has 3 parts separated by dots).
99
+ *
100
+ * @param token - The token to check
101
+ * @returns true if token appears to be a JWT, false otherwise
102
+ */
103
+ function isJwtFormat(token) {
104
+ return token.split(".").length === 3;
105
+ }