@stackframe/stack-shared 2.6.30 → 2.6.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.6.32
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - @stackframe/stack-sc@2.6.32
9
+
10
+ ## 2.6.31
11
+
12
+ ### Patch Changes
13
+
14
+ - Bugfixes
15
+ - @stackframe/stack-sc@2.6.31
16
+
3
17
  ## 2.6.30
4
18
 
5
19
  ### Patch Changes
@@ -30,7 +30,7 @@ export class StackAdminInterface extends StackServerInterface {
30
30
  return await response.json();
31
31
  }
32
32
  async createApiKey(options) {
33
- const response = await this.sendServerRequest("/internal/api-keys", {
33
+ const response = await this.sendAdminRequest("/internal/api-keys", {
34
34
  method: "POST",
35
35
  headers: {
36
36
  "content-type": "application/json",
@@ -33,6 +33,7 @@ export declare class StackClientInterface {
33
33
  prodDashboard: string;
34
34
  prodBackend: string;
35
35
  }>;
36
+ protected _createNetworkError(cause: Error, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Error>;
36
37
  protected _networkRetry<T>(cb: () => Promise<Result<T, any>>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
37
38
  protected _networkRetryException<T>(cb: () => Promise<T>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
38
39
  fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
@@ -186,6 +187,7 @@ export declare class StackClientInterface {
186
187
  createProject(project: InternalProjectsCrud['Client']['Create'], session: InternalSession): Promise<InternalProjectsCrud['Client']['Read']>;
187
188
  createProviderAccessToken(provider: string, scope: string, session: InternalSession): Promise<ConnectedAccountAccessTokenCrud['Client']['Read']>;
188
189
  createClientTeam(data: TeamsCrud['Client']['Create'], session: InternalSession): Promise<TeamsCrud['Client']['Read']>;
190
+ deleteTeam(teamId: string, session: InternalSession): Promise<void>;
189
191
  deleteCurrentUser(session: InternalSession): Promise<void>;
190
192
  createClientContactChannel(data: ContactChannelsCrud['Client']['Create'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
191
193
  updateClientContactChannel(id: string, data: ContactChannelsCrud['Client']['Update'], session: InternalSession): Promise<ContactChannelsCrud['Client']['Read']>;
@@ -5,7 +5,9 @@ import { AccessToken, InternalSession } from '../sessions';
5
5
  import { generateSecureRandomString } from '../utils/crypto';
6
6
  import { StackAssertionError, throwErr } from '../utils/errors';
7
7
  import { globalVar } from '../utils/globals';
8
+ import { HTTP_METHODS } from '../utils/http';
8
9
  import { filterUndefined } from '../utils/objects';
10
+ import { wait } from '../utils/promises';
9
11
  import { Result } from "../utils/results";
10
12
  import { deindent } from '../utils/strings';
11
13
  export class StackClientInterface {
@@ -68,6 +70,17 @@ export class StackClientInterface {
68
70
  prodBackend,
69
71
  };
70
72
  }
73
+ async _createNetworkError(cause, session, requestType) {
74
+ return new Error(deindent `
75
+ Stack Auth is unable to connect to the server. Please check your internet connection and try again.
76
+
77
+ If the problem persists, please contact support and provide a screenshot of your entire browser console.
78
+
79
+ ${cause}
80
+
81
+ ${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
82
+ `, { cause: cause });
83
+ }
71
84
  async _networkRetry(cb, session, requestType) {
72
85
  const retriedResult = await Result.retry(cb, 5, { exponentialDelayBase: 1000 });
73
86
  // try to diagnose the error for the user
@@ -75,15 +88,7 @@ export class StackClientInterface {
75
88
  if (globalVar.navigator && !globalVar.navigator.onLine) {
76
89
  throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
77
90
  }
78
- throw new Error(deindent `
79
- Stack Auth is unable to connect to the server. Please check your internet connection and try again.
80
-
81
- If the problem persists, please contact support and provide a screenshot of your entire browser console.
82
-
83
- ${retriedResult.error}
84
-
85
- ${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
86
- `, { cause: retriedResult.error });
91
+ throw this._createNetworkError(retriedResult.error, session, requestType);
87
92
  }
88
93
  return retriedResult.data;
89
94
  }
@@ -220,8 +225,13 @@ export class StackClientInterface {
220
225
  }
221
226
  catch (e) {
222
227
  if (e instanceof TypeError) {
223
- // Network error, retry
224
- return Result.error(e);
228
+ // Likely to be a network error. Retry if the request is idempotent, throw network error otherwise.
229
+ if (HTTP_METHODS[(params.method ?? "GET")].idempotent) {
230
+ return Result.error(e);
231
+ }
232
+ else {
233
+ throw this._createNetworkError(e, session, requestType);
234
+ }
225
235
  }
226
236
  throw e;
227
237
  }
@@ -254,10 +264,25 @@ export class StackClientInterface {
254
264
  if (res.ok) {
255
265
  return Result.ok(res);
256
266
  }
267
+ else if (res.status === 429) {
268
+ // Rate limited, so retry if we can
269
+ const retryAfter = res.headers.get("Retry-After");
270
+ if (retryAfter !== null) {
271
+ await wait(Number(retryAfter) * 1000);
272
+ return Result.error(new Error(`Rate limited, retrying after ${retryAfter} seconds`));
273
+ }
274
+ return Result.error(new Error("Rate limited, no retry-after header received"));
275
+ }
257
276
  else {
258
277
  const error = await res.text();
278
+ const errorObj = new StackAssertionError(`Failed to send request to ${url}: ${res.status} ${error}`, { request: params, res });
279
+ if (res.status === 508 && error.includes("INFINITE_LOOP_DETECTED")) {
280
+ // Some Vercel deployments seem to have an odd infinite loop bug. In that case, retry.
281
+ // See: https://github.com/stack-auth/stack/issues/319
282
+ return Result.error(errorObj);
283
+ }
259
284
  // Do not retry, throw error instead of returning one
260
- throw new StackAssertionError(`Failed to send request to ${url}: ${res.status} ${error}`, { request: params, res });
285
+ throw errorObj;
261
286
  }
262
287
  }
263
288
  async _processResponse(rawRes) {
@@ -812,6 +837,11 @@ export class StackClientInterface {
812
837
  }, session);
813
838
  return await response.json();
814
839
  }
840
+ async deleteTeam(teamId, session) {
841
+ await this.sendClientRequest(`/teams/${teamId}`, {
842
+ method: "DELETE",
843
+ }, session);
844
+ }
815
845
  async deleteCurrentUser(session) {
816
846
  await this.sendClientRequest("/users/me", {
817
847
  method: "DELETE",
@@ -20,6 +20,7 @@ export declare function yupMixed<A extends {}>(...args: Parameters<typeof yup.mi
20
20
  export declare function yupArray<A extends yup.Maybe<yup.AnyObject> = yup.AnyObject, B = any>(...args: Parameters<typeof yup.array<A, B>>): yup.ArraySchema<B[] | undefined, A, undefined, "">;
21
21
  export declare function yupTuple<T extends [unknown, ...unknown[]]>(...args: Parameters<typeof yup.tuple<T>>): yup.TupleSchema<T | undefined, yup.AnyObject, undefined, "">;
22
22
  export declare function yupObject<A extends yup.Maybe<yup.AnyObject>, B extends yup.ObjectShape>(...args: Parameters<typeof yup.object<A, B>>): yup.ObjectSchema<yup.TypeFromShape<B, yup.AnyObject> extends infer T ? T extends yup.TypeFromShape<B, yup.AnyObject> ? T extends {} ? { [k in keyof T]: T[k]; } : T : never : never, yup.AnyObject, yup.DefaultFromShape<B> extends infer T_1 ? T_1 extends yup.DefaultFromShape<B> ? T_1 extends {} ? { [k_1 in keyof T_1]: T_1[k_1]; } : T_1 : never : never, "">;
23
+ export declare function yupNever(): yup.MixedSchema<never>;
23
24
  export declare function yupUnion<T extends yup.ISchema<any>[]>(...args: T): yup.MixedSchema<yup.InferType<T[number]>>;
24
25
  export declare const adaptSchema: yup.MixedSchema<typeof StackAdaptSentinel | undefined, yup.AnyObject, undefined, "">;
25
26
  /**
@@ -123,6 +123,9 @@ export function yupObject(...args) {
123
123
  // we don't want to update the type of `object` to have a default flag
124
124
  return object.default(undefined);
125
125
  }
126
+ export function yupNever() {
127
+ return yupMixed().test('never', 'This value should never be reached', () => false);
128
+ }
126
129
  export function yupUnion(...args) {
127
130
  if (args.length === 0)
128
131
  throw new Error('yupUnion must have at least one schema');
@@ -171,7 +174,7 @@ export const urlSchema = yupString().test({
171
174
  },
172
175
  });
173
176
  export const jsonSchema = yupMixed().nullable().defined().transform((value) => JSON.parse(JSON.stringify(value)));
174
- export const jsonStringSchema = yupString().test("json", "Invalid JSON format", (value) => {
177
+ export const jsonStringSchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
175
178
  if (value == null)
176
179
  return true;
177
180
  try {
@@ -182,7 +185,7 @@ export const jsonStringSchema = yupString().test("json", "Invalid JSON format",
182
185
  return false;
183
186
  }
184
187
  });
185
- export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON format", (value) => {
188
+ export const jsonStringOrEmptySchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
186
189
  if (!value)
187
190
  return true;
188
191
  try {
@@ -193,7 +196,7 @@ export const jsonStringOrEmptySchema = yupString().test("json", "Invalid JSON fo
193
196
  return false;
194
197
  }
195
198
  });
196
- export const base64Schema = yupString().test("is-base64", "Invalid base64 format", (value) => {
199
+ export const base64Schema = yupString().test("is-base64", (params) => `${params.path} is not valid base64`, (value) => {
197
200
  if (value == null)
198
201
  return true;
199
202
  return isBase64(value);
@@ -4,6 +4,7 @@ export declare function encodeBase64(input: Uint8Array): string;
4
4
  export declare function decodeBase64(input: string): Uint8Array;
5
5
  export declare function encodeBase64Url(input: Uint8Array): string;
6
6
  export declare function decodeBase64Url(input: string): Uint8Array;
7
+ export declare function decodeBase64OrBase64Url(input: string): Uint8Array;
7
8
  export declare function isBase32(input: string): boolean;
8
9
  export declare function isBase64(input: string): boolean;
9
10
  export declare function isBase64Url(input: string): boolean;
@@ -82,6 +82,17 @@ export function decodeBase64Url(input) {
82
82
  }
83
83
  return decodeBase64(input.replace(/-/g, "+").replace(/_/g, "/") + "====".slice((input.length - 1) % 4 + 1));
84
84
  }
85
+ export function decodeBase64OrBase64Url(input) {
86
+ if (isBase64Url(input)) {
87
+ return decodeBase64Url(input);
88
+ }
89
+ else if (isBase64(input)) {
90
+ return decodeBase64(input);
91
+ }
92
+ else {
93
+ throw new StackAssertionError("Invalid base64 or base64url string");
94
+ }
95
+ }
85
96
  export function isBase32(input) {
86
97
  for (const char of input) {
87
98
  if (char === " ")
package/dist/utils/env.js CHANGED
@@ -3,6 +3,10 @@ import { deindent } from "./strings";
3
3
  export function isBrowserLike() {
4
4
  return typeof window !== "undefined" && typeof document !== "undefined" && typeof document.createElement !== "undefined";
5
5
  }
6
+ // newName: oldName
7
+ const ENV_VAR_RENAME = {
8
+ NEXT_PUBLIC_STACK_API_URL: ['STACK_BASE_URL', 'NEXT_PUBLIC_STACK_URL'],
9
+ };
6
10
  /**
7
11
  * Returns the environment variable with the given name, returning the default (if given) or throwing an error (otherwise) if it's undefined or the empty string.
8
12
  */
@@ -21,7 +25,30 @@ export function getEnvVariable(name, defaultValue) {
21
25
  Use getNextRuntime() instead.
22
26
  `);
23
27
  }
24
- return ((process.env[name] || defaultValue) ?? throwErr(`Missing environment variable: ${name}`)) || (defaultValue ?? throwErr(`Empty environment variable: ${name}`));
28
+ // throw error if the old name is used as the retrieve key
29
+ for (const [newName, oldNames] of Object.entries(ENV_VAR_RENAME)) {
30
+ if (oldNames.includes(name)) {
31
+ throwErr(`Environment variable ${name} has been renamed to ${newName}. Please update your configuration to use the new name.`);
32
+ }
33
+ }
34
+ let value = process.env[name];
35
+ // check the key under the old name if the new name is not found
36
+ if (!value && ENV_VAR_RENAME[name]) {
37
+ for (const oldName of ENV_VAR_RENAME[name]) {
38
+ value = process.env[oldName];
39
+ if (value)
40
+ break;
41
+ }
42
+ }
43
+ if (value === undefined) {
44
+ if (defaultValue !== undefined) {
45
+ value = defaultValue;
46
+ }
47
+ else {
48
+ throwErr(`Missing environment variable: ${name}`);
49
+ }
50
+ }
51
+ return value;
25
52
  }
26
53
  export function getNextRuntime() {
27
54
  // This variable is compiled into the client bundle, so we can't use getEnvVariable here.
@@ -1,3 +1,4 @@
1
+ export declare function sha512(input: Uint8Array | string): Promise<string>;
1
2
  export declare function hashPassword(password: string): Promise<string>;
2
3
  export declare function comparePassword(password: string, hash: string): Promise<boolean>;
3
4
  export declare function isPasswordHashValid(hash: string): Promise<boolean>;
@@ -1,5 +1,11 @@
1
1
  import bcrypt from 'bcrypt';
2
2
  import { StackAssertionError } from './errors';
3
+ export async function sha512(input) {
4
+ const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
5
+ return await crypto.subtle.digest("SHA-512", bytes).then(buf => {
6
+ return Array.prototype.map.call(new Uint8Array(buf), x => (('00' + x.toString(16)).slice(-2))).join('');
7
+ });
8
+ }
3
9
  export async function hashPassword(password) {
4
10
  const passwordBytes = new TextEncoder().encode(password);
5
11
  if (passwordBytes.length >= 72) {
@@ -1,2 +1,39 @@
1
- export declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
2
- export type HttpMethod = typeof HTTP_METHODS[number];
1
+ export declare const HTTP_METHODS: {
2
+ readonly GET: {
3
+ readonly safe: true;
4
+ readonly idempotent: true;
5
+ };
6
+ readonly POST: {
7
+ readonly safe: false;
8
+ readonly idempotent: false;
9
+ };
10
+ readonly PUT: {
11
+ readonly safe: false;
12
+ readonly idempotent: true;
13
+ };
14
+ readonly DELETE: {
15
+ readonly safe: false;
16
+ readonly idempotent: true;
17
+ };
18
+ readonly PATCH: {
19
+ readonly safe: false;
20
+ readonly idempotent: false;
21
+ };
22
+ readonly OPTIONS: {
23
+ readonly safe: true;
24
+ readonly idempotent: true;
25
+ };
26
+ readonly HEAD: {
27
+ readonly safe: true;
28
+ readonly idempotent: true;
29
+ };
30
+ readonly TRACE: {
31
+ readonly safe: true;
32
+ readonly idempotent: true;
33
+ };
34
+ readonly CONNECT: {
35
+ readonly safe: false;
36
+ readonly idempotent: false;
37
+ };
38
+ };
39
+ export type HttpMethod = keyof typeof HTTP_METHODS;
@@ -1 +1,38 @@
1
- export const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "TRACE", "CONNECT"];
1
+ export const HTTP_METHODS = {
2
+ "GET": {
3
+ safe: true,
4
+ idempotent: true,
5
+ },
6
+ "POST": {
7
+ safe: false,
8
+ idempotent: false,
9
+ },
10
+ "PUT": {
11
+ safe: false,
12
+ idempotent: true,
13
+ },
14
+ "DELETE": {
15
+ safe: false,
16
+ idempotent: true,
17
+ },
18
+ "PATCH": {
19
+ safe: false,
20
+ idempotent: false,
21
+ },
22
+ "OPTIONS": {
23
+ safe: true,
24
+ idempotent: true,
25
+ },
26
+ "HEAD": {
27
+ safe: true,
28
+ idempotent: true,
29
+ },
30
+ "TRACE": {
31
+ safe: true,
32
+ idempotent: true,
33
+ },
34
+ "CONNECT": {
35
+ safe: false,
36
+ idempotent: false,
37
+ },
38
+ };
@@ -11,21 +11,26 @@ export declare function verifyJWT(options: {
11
11
  issuer: string;
12
12
  jwt: string;
13
13
  }): Promise<jose.JWTPayload>;
14
- export declare function getPrivateJwk(secret: string): Promise<{
15
- kty: string;
16
- crv: string;
14
+ export type PrivateJwk = {
15
+ kty: "EC";
16
+ alg: "ES256";
17
+ crv: "P-256";
18
+ kid: string;
17
19
  d: string;
18
20
  x: string;
19
21
  y: string;
20
- }>;
21
- export declare function getPublicJwkSet(secret: string): Promise<{
22
- keys: {
23
- kid: string;
24
- x: string;
25
- kty: string;
26
- crv: string;
27
- y: string;
28
- }[];
22
+ };
23
+ export declare function getPrivateJwk(secret: string): Promise<PrivateJwk>;
24
+ export type PublicJwk = {
25
+ kty: "EC";
26
+ alg: "ES256";
27
+ crv: "P-256";
28
+ kid: string;
29
+ x: string;
30
+ y: string;
31
+ };
32
+ export declare function getPublicJwkSet(secretOrPrivateJwk: string | PrivateJwk): Promise<{
33
+ keys: PublicJwk[];
29
34
  }>;
30
35
  export declare function getPerAudienceSecret(options: {
31
36
  audience: string;
package/dist/utils/jwt.js CHANGED
@@ -1,12 +1,11 @@
1
+ import crypto from "crypto";
1
2
  import elliptic from "elliptic";
2
3
  import * as jose from "jose";
4
+ import { JOSEError } from "jose/errors";
3
5
  import { encodeBase64Url } from "./bytes";
4
- import { getEnvVariable } from "./env";
5
6
  import { globalVar } from "./globals";
6
7
  import { pick } from "./objects";
7
- import crypto from "crypto";
8
- import { JOSEError } from "jose/errors";
9
- const STACK_SERVER_SECRET = getEnvVariable("STACK_SERVER_SECRET");
8
+ const STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
10
9
  try {
11
10
  jose.base64url.decode(STACK_SERVER_SECRET);
12
11
  }
@@ -60,21 +59,25 @@ export async function getPrivateJwk(secret) {
60
59
  return {
61
60
  kty: 'EC',
62
61
  crv: 'P-256',
62
+ alg: 'ES256',
63
+ kid: getKid({ secret }),
63
64
  d: encodeBase64Url(priv),
64
65
  x: encodeBase64Url(publicKey.getX().toBuffer()),
65
66
  y: encodeBase64Url(publicKey.getY().toBuffer()),
66
67
  };
67
68
  }
68
- export async function getPublicJwkSet(secret) {
69
- const privateJwk = await getPrivateJwk(secret);
70
- const jwk = pick(privateJwk, ["kty", "crv", "x", "y"]);
69
+ export async function getPublicJwkSet(secretOrPrivateJwk) {
70
+ const privateJwk = typeof secretOrPrivateJwk === "string" ? await getPrivateJwk(secretOrPrivateJwk) : secretOrPrivateJwk;
71
+ const jwk = pick(privateJwk, ["kty", "alg", "crv", "x", "y", "kid"]);
71
72
  return {
72
- keys: [{ ...jwk, kid: getKid({ secret }) }],
73
+ keys: [jwk],
73
74
  };
74
75
  }
75
76
  export function getPerAudienceSecret(options) {
76
77
  return jose.base64url.encode(crypto
77
78
  .createHash('sha256')
79
+ // TODO we should prefix a string like "stack-audience-secret" before we hash so you can't use `getKid(...)` to get the secret for eg. the "kid" audience if the same secret value is used
80
+ // Sadly doing this modification is a bit annoying as we need to leave the old keys to be valid for a little longer
78
81
  .update(JSON.stringify([options.secret, options.audience]))
79
82
  .digest());
80
83
  }
@@ -82,6 +85,6 @@ export function getPerAudienceSecret(options) {
82
85
  export function getKid(options) {
83
86
  return jose.base64url.encode(crypto
84
87
  .createHash('sha256')
85
- .update(JSON.stringify([options.secret, "kid"]))
88
+ .update(JSON.stringify([options.secret, "kid"])) // TODO see above in getPerAudienceSecret
86
89
  .digest()).slice(0, 12);
87
90
  }
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { IncomingMessage, ServerResponse } from "http";
4
+ declare class ServerResponseWithBodyChunks extends ServerResponse {
5
+ bodyChunks: Uint8Array[];
6
+ _send(data: string, encoding: BufferEncoding, callback?: (() => void) | null, byteLength?: number): void;
7
+ }
8
+ export declare function createNodeHttpServerDuplex(options: {
9
+ method: string;
10
+ originalUrl?: URL;
11
+ url: URL;
12
+ headers: Headers;
13
+ body: Uint8Array;
14
+ }): Promise<[IncomingMessage, ServerResponseWithBodyChunks]>;
15
+ export {};
@@ -0,0 +1,38 @@
1
+ import { IncomingMessage, ServerResponse } from "http";
2
+ import { getRelativePart } from "./urls";
3
+ class ServerResponseWithBodyChunks extends ServerResponse {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.bodyChunks = [];
7
+ }
8
+ // note: we actually override this, even though it's private in the parent
9
+ _send(data, encoding, callback, byteLength) {
10
+ if (typeof encoding === "function") {
11
+ callback = encoding;
12
+ encoding = "utf-8";
13
+ }
14
+ const encodedBuffer = new Uint8Array(Buffer.from(data, encoding));
15
+ this.bodyChunks.push(encodedBuffer);
16
+ callback?.();
17
+ }
18
+ }
19
+ export async function createNodeHttpServerDuplex(options) {
20
+ // See https://github.com/nodejs/node/blob/main/lib/_http_incoming.js
21
+ // and https://github.com/nodejs/node/blob/main/lib/_http_common.js (particularly the `parserXyz` functions)
22
+ const incomingMessage = new IncomingMessage({
23
+ encrypted: options.originalUrl?.protocol === "https:", // trick frameworks into believing this is an HTTPS request
24
+ });
25
+ incomingMessage.httpVersionMajor = 1;
26
+ incomingMessage.httpVersionMinor = 1;
27
+ incomingMessage.httpVersion = '1.1';
28
+ incomingMessage.method = options.method;
29
+ incomingMessage.url = getRelativePart(options.url);
30
+ incomingMessage.originalUrl = options.originalUrl && getRelativePart(options.originalUrl); // originalUrl is an extension used by some servers; for example, oidc-provider reads it to construct the paths for the .well-known/openid-configuration
31
+ const rawHeaders = [...options.headers.entries()].flat();
32
+ incomingMessage._addHeaderLines(rawHeaders, rawHeaders.length);
33
+ incomingMessage.push(Buffer.from(options.body));
34
+ incomingMessage.complete = true;
35
+ incomingMessage.push(null); // to emit end event, see: https://github.com/nodejs/node/blob/4cf6fabce20eb3050c5b543d249e931ea3d3cad5/lib/_http_common.js#L150
36
+ const serverResponse = new ServerResponseWithBodyChunks(incomingMessage);
37
+ return [incomingMessage, serverResponse];
38
+ }
@@ -210,6 +210,9 @@ export function nicify(value, options = {}) {
210
210
  if (value instanceof URL) {
211
211
  return `URL(${JSON.stringify(value.toString())})`;
212
212
  }
213
+ if (ArrayBuffer.isView(value)) {
214
+ return `${value.constructor.name}([${value.toString()}])`;
215
+ }
213
216
  const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
214
217
  const constructorString = constructorName ? `${nicifyPropertyString(constructorName)} ` : "";
215
218
  const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.30",
3
+ "version": "2.6.32",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -50,7 +50,7 @@
50
50
  "oauth4webapi": "^2.10.3",
51
51
  "semver": "^7.6.3",
52
52
  "uuid": "^9.0.1",
53
- "@stackframe/stack-sc": "2.6.30"
53
+ "@stackframe/stack-sc": "2.6.32"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@simplewebauthn/types": "^11.0.0",