@stackframe/stack-shared 2.6.33 → 2.6.36

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,26 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.6.36
4
+
5
+ ### Patch Changes
6
+
7
+ - Various updates
8
+ - @stackframe/stack-sc@2.6.36
9
+
10
+ ## 2.6.35
11
+
12
+ ### Patch Changes
13
+
14
+ - Bugfixes
15
+ - @stackframe/stack-sc@2.6.35
16
+
17
+ ## 2.6.34
18
+
19
+ ### Patch Changes
20
+
21
+ - Bugfixes
22
+ - @stackframe/stack-sc@2.6.34
23
+
3
24
  ## 2.6.33
4
25
 
5
26
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { StackAssertionError } from "../utils/errors";
1
+ import { StackAssertionError, captureError } from "../utils/errors";
2
2
  import { isLocalhost } from "../utils/urls";
3
3
  export function getProductionModeErrors(project) {
4
4
  const errors = [];
@@ -15,10 +15,15 @@ export function getProductionModeErrors(project) {
15
15
  url = new URL(domain);
16
16
  }
17
17
  catch (e) {
18
- throw new StackAssertionError("Domain was somehow not a valid URL; we should've caught this when setting the domain in the first place", {
18
+ captureError("production-mode-domain-not-valid", new StackAssertionError("Domain was somehow not a valid URL; we should've caught this when setting the domain in the first place", {
19
19
  domain,
20
20
  projectId: project
21
+ }));
22
+ errors.push({
23
+ message: "Trusted domain is not a valid URL: " + domain,
24
+ relativeFixUrl: domainsFixUrl,
21
25
  });
26
+ continue;
22
27
  }
23
28
  if (isLocalhost(url)) {
24
29
  errors.push({
@@ -37,7 +37,7 @@ export declare class StackClientInterface {
37
37
  protected _networkRetry<T>(cb: () => Promise<Result<T, any>>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
38
38
  protected _networkRetryException<T>(cb: () => Promise<T>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
39
39
  fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
40
- protected sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
40
+ sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
41
41
  usedTokens: {
42
42
  accessToken: AccessToken;
43
43
  refreshToken: RefreshToken | null;
@@ -88,7 +88,7 @@ export class StackClientInterface {
88
88
  if (globalVar.navigator && !globalVar.navigator.onLine) {
89
89
  throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
90
90
  }
91
- throw this._createNetworkError(retriedResult.error, session, requestType);
91
+ throw await this._createNetworkError(retriedResult.error, session, requestType);
92
92
  }
93
93
  return retriedResult.data;
94
94
  }
@@ -230,7 +230,7 @@ export class StackClientInterface {
230
230
  return Result.error(e);
231
231
  }
232
232
  else {
233
- throw this._createNetworkError(e, session, requestType);
233
+ throw await this._createNetworkError(e, session, requestType);
234
234
  }
235
235
  }
236
236
  throw e;
@@ -149,7 +149,7 @@ const AccessTypeWithoutProjectId = createKnownErrorConstructor(InvalidProjectAut
149
149
  deindent `
150
150
  The x-stack-access-type header was '${accessType}', but the x-stack-project-id header was not provided.
151
151
 
152
- For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/auth#authentication
152
+ For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/overview#authentication
153
153
  `,
154
154
  {
155
155
  request_type: accessType,
@@ -160,7 +160,7 @@ const AccessTypeRequired = createKnownErrorConstructor(InvalidProjectAuthenticat
160
160
  deindent `
161
161
  You must specify an access level for this Stack project. Make sure project API keys are provided (eg. x-stack-publishable-client-key) and you set the x-stack-access-type header to 'client', 'server', or 'admin'.
162
162
 
163
- For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/auth#authentication
163
+ For more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/overview#authentication
164
164
  `,
165
165
  ], () => []);
166
166
  const InsufficientAccessType = createKnownErrorConstructor(InvalidProjectAuthentication, "INSUFFICIENT_ACCESS_TYPE", (actualAccessType, allowedAccessTypes) => [
@@ -33,16 +33,17 @@ export declare const jsonStringOrEmptySchema: yup.StringSchema<string | undefine
33
33
  export declare const base64Schema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
34
34
  export declare const passwordSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
35
35
  /**
36
- * A stricter email schema that does some additional checks for UX input.
36
+ * A stricter email schema that does some additional checks for UX input. (Some emails are allowed by the spec, for
37
+ * example `test@localhost` or `abc@gmail`, but almost certainly a user input error.)
37
38
  *
38
39
  * Note that some users in the DB have an email that doesn't match this regex, so most of the time you should use
39
40
  * `emailSchema` instead until we do the DB migration.
40
41
  */
41
42
  export declare const strictEmailSchema: (message: string | undefined) => yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
42
43
  export declare const emailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
43
- export declare const clientOrHigherAuthTypeSchema: yup.StringSchema<"client" | "server" | "admin" | undefined, yup.AnyObject, undefined, "">;
44
- export declare const serverOrHigherAuthTypeSchema: yup.StringSchema<"server" | "admin" | undefined, yup.AnyObject, undefined, "">;
45
- export declare const adminAuthTypeSchema: yup.StringSchema<"admin" | undefined, yup.AnyObject, undefined, "">;
44
+ export declare const clientOrHigherAuthTypeSchema: yup.StringSchema<"client" | "server" | "admin", yup.AnyObject, undefined, "">;
45
+ export declare const serverOrHigherAuthTypeSchema: yup.StringSchema<"server" | "admin", yup.AnyObject, undefined, "">;
46
+ export declare const adminAuthTypeSchema: yup.StringSchema<"admin", yup.AnyObject, undefined, "">;
46
47
  export declare const projectIdSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
47
48
  export declare const projectDisplayNameSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
48
49
  export declare const projectDescriptionSchema: yup.StringSchema<string | null | undefined, yup.AnyObject, undefined, "">;
@@ -146,5 +147,7 @@ export declare const contactChannelValueSchema: yup.StringSchema<string | undefi
146
147
  export declare const contactChannelUsedForAuthSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
147
148
  export declare const contactChannelIsVerifiedSchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
148
149
  export declare const contactChannelIsPrimarySchema: yup.BooleanSchema<boolean | undefined, yup.AnyObject, undefined, "">;
150
+ export declare const basicAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
151
+ export declare const neonAuthorizationHeaderSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
149
152
  export declare function yupDefinedWhen<S extends yup.AnyObject>(schema: S, triggerName: string, isValue: any): S;
150
153
  export {};
@@ -1,9 +1,11 @@
1
1
  import * as yup from "yup";
2
2
  import { KnownErrors } from ".";
3
3
  import { isBase64 } from "./utils/bytes";
4
- import { StackAssertionError } from "./utils/errors";
4
+ import { StackAssertionError, throwErr } from "./utils/errors";
5
+ import { decodeBasicAuthorizationHeader } from "./utils/http";
5
6
  import { allProviders } from "./utils/oauth";
6
7
  import { deepPlainClone, omit } from "./utils/objects";
8
+ import { isValidUrl } from "./utils/urls";
7
9
  import { isUuid } from "./utils/uuids";
8
10
  // eslint-disable-next-line no-restricted-syntax
9
11
  yup.addMethod(yup.string, "nonEmpty", function (message) {
@@ -160,18 +162,8 @@ export const adaptSchema = yupMixed();
160
162
  */
161
163
  export const urlSchema = yupString().test({
162
164
  name: 'url',
163
- message: 'Invalid URL',
164
- test: (value) => {
165
- if (!value)
166
- return true;
167
- try {
168
- new URL(value);
169
- return true;
170
- }
171
- catch {
172
- return false;
173
- }
174
- },
165
+ message: (params) => `${params.path} is not a valid URL`,
166
+ test: (value) => value == null || isValidUrl(value)
175
167
  });
176
168
  export const jsonSchema = yupMixed().nullable().defined().transform((value) => JSON.parse(JSON.stringify(value)));
177
169
  export const jsonStringSchema = yupString().test("json", (params) => `${params.path} is not valid JSON`, (value) => {
@@ -203,19 +195,20 @@ export const base64Schema = yupString().test("is-base64", (params) => `${params.
203
195
  });
204
196
  export const passwordSchema = yupString().max(70);
205
197
  /**
206
- * A stricter email schema that does some additional checks for UX input.
198
+ * A stricter email schema that does some additional checks for UX input. (Some emails are allowed by the spec, for
199
+ * example `test@localhost` or `abc@gmail`, but almost certainly a user input error.)
207
200
  *
208
201
  * Note that some users in the DB have an email that doesn't match this regex, so most of the time you should use
209
202
  * `emailSchema` instead until we do the DB migration.
210
203
  */
211
204
  // eslint-disable-next-line no-restricted-syntax
212
- export const strictEmailSchema = (message) => yupString().email(message).matches(/^.*@.*\..*$/, message);
205
+ export const strictEmailSchema = (message) => yupString().email(message).matches(/^.*@.*\.[^.][^.]+$/, message);
213
206
  // eslint-disable-next-line no-restricted-syntax
214
207
  export const emailSchema = yupString().email();
215
208
  // Request auth
216
- export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']);
217
- export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']);
218
- export const adminAuthTypeSchema = yupString().oneOf(['admin']);
209
+ export const clientOrHigherAuthTypeSchema = yupString().oneOf(['client', 'server', 'admin']).defined();
210
+ export const serverOrHigherAuthTypeSchema = yupString().oneOf(['server', 'admin']).defined();
211
+ export const adminAuthTypeSchema = yupString().oneOf(['admin']).defined();
219
212
  // Projects
220
213
  export const projectIdSchema = yupString().test((v) => v === undefined || v === "internal" || isUuid(v)).meta({ openapiField: { description: _idDescription('project'), exampleValue: 'e0b52f4d-dece-408c-af49-d23061bb0f8d' } });
221
214
  export const projectDisplayNameSchema = yupString().meta({ openapiField: { description: _displayNameDescription('project'), exampleValue: 'MyMusic' } });
@@ -360,6 +353,23 @@ export const contactChannelValueSchema = yupString().when('type', {
360
353
  export const contactChannelUsedForAuthSchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel is used for authentication. If this is set to `true`, the user will be able to sign in with the contact channel with password or OTP.', exampleValue: true } });
361
354
  export const contactChannelIsVerifiedSchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel has been verified. If this is set to `true`, the contact channel has been verified to belong to the user.', exampleValue: true } });
362
355
  export const contactChannelIsPrimarySchema = yupBoolean().meta({ openapiField: { description: 'Whether the contact channel is the primary contact channel. If this is set to `true`, it will be used for authentication and notifications by default.', exampleValue: true } });
356
+ // Headers
357
+ export const basicAuthorizationHeaderSchema = yupString().test('is-basic-authorization-header', 'Authorization header must be in the format "Basic <base64>"', (value) => {
358
+ if (!value)
359
+ return true;
360
+ return decodeBasicAuthorizationHeader(value) !== null;
361
+ });
362
+ // Neon integration
363
+ export const neonAuthorizationHeaderSchema = basicAuthorizationHeaderSchema.test('is-neon-authorization-header', 'Invalid client_id:client_secret values; did you use the correct values for the Neon integration?', (value) => {
364
+ if (!value)
365
+ return true;
366
+ const [clientId, clientSecret] = decodeBasicAuthorizationHeader(value) ?? throwErr(`Neon authz header invalid? This should've been validated by basicAuthorizationHeaderSchema: ${value}`);
367
+ for (const neonClientConfig of JSON.parse(process.env.STACK_NEON_INTEGRATION_CLIENTS_CONFIG || '[]')) {
368
+ if (clientId === neonClientConfig.client_id && clientSecret === neonClientConfig.client_secret)
369
+ return true;
370
+ }
371
+ return false;
372
+ });
363
373
  // Utils
364
374
  export function yupDefinedWhen(schema, triggerName, isValue) {
365
375
  return schema.when(triggerName, {
@@ -27,9 +27,12 @@ export declare class InternalSession {
27
27
  private _accessToken;
28
28
  private readonly _refreshToken;
29
29
  /**
30
- * Whether the session as a whole is known to be invalid. Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
30
+ * Whether the session as a whole is known to be invalid (ie. both access and refresh tokens are invalid). Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
31
31
  *
32
- * Applies to both the access token and the refresh token (it is possible for the access token to be invalid but the refresh token to be valid, in which case the session is still valid).
32
+ * It is possible for the access token to be invalid but the refresh token to be valid, in which case the session is
33
+ * still valid (just needs a refresh). It is also possible for the access token to be valid but the refresh token to
34
+ * be invalid, in which case the session is also valid (eg. if the refresh token is null because the user only passed
35
+ * in an access token, eg. in a server-side request handler).
33
36
  */
34
37
  private _knownToBeInvalid;
35
38
  private _refreshPromise;
package/dist/sessions.js CHANGED
@@ -25,14 +25,21 @@ export class InternalSession {
25
25
  constructor(_options) {
26
26
  this._options = _options;
27
27
  /**
28
- * Whether the session as a whole is known to be invalid. Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
28
+ * Whether the session as a whole is known to be invalid (ie. both access and refresh tokens are invalid). Used as a cache to avoid making multiple requests to the server (sessions never go back to being valid after being invalidated).
29
29
  *
30
- * Applies to both the access token and the refresh token (it is possible for the access token to be invalid but the refresh token to be valid, in which case the session is still valid).
30
+ * It is possible for the access token to be invalid but the refresh token to be valid, in which case the session is
31
+ * still valid (just needs a refresh). It is also possible for the access token to be valid but the refresh token to
32
+ * be invalid, in which case the session is also valid (eg. if the refresh token is null because the user only passed
33
+ * in an access token, eg. in a server-side request handler).
31
34
  */
32
35
  this._knownToBeInvalid = new Store(false);
33
36
  this._refreshPromise = null;
34
37
  this._accessToken = new Store(_options.accessToken ? new AccessToken(_options.accessToken) : null);
35
38
  this._refreshToken = _options.refreshToken ? new RefreshToken(_options.refreshToken) : null;
39
+ if (_options.accessToken === null && _options.refreshToken === null) {
40
+ // this session is already invalid
41
+ this._knownToBeInvalid.set(true);
42
+ }
36
43
  this.sessionKey = InternalSession.calculateSessionKey({ accessToken: _options.accessToken ?? null, refreshToken: _options.refreshToken });
37
44
  }
38
45
  static calculateSessionKey(ofTokens) {
@@ -0,0 +1,6 @@
1
+ export declare function getBrowserCompatibilityReport(): {
2
+ optionalChaining: string | boolean;
3
+ nullishCoalescing: string | boolean;
4
+ weakRef: string | boolean;
5
+ cryptoUuid: string | boolean;
6
+ };
@@ -0,0 +1,17 @@
1
+ export function getBrowserCompatibilityReport() {
2
+ const test = (snippet) => {
3
+ try {
4
+ (0, eval)(snippet);
5
+ return true;
6
+ }
7
+ catch (e) {
8
+ return `FAILED: ${e}`;
9
+ }
10
+ };
11
+ return {
12
+ optionalChaining: test("({})?.b?.c"),
13
+ nullishCoalescing: test("0 ?? 1"),
14
+ weakRef: test("new WeakRef({})"),
15
+ cryptoUuid: test("crypto.randomUUID()"),
16
+ };
17
+ }
@@ -3,11 +3,11 @@ export declare function throwErr(errorMessage: string, extraData?: any): never;
3
3
  export declare function throwErr(error: Error): never;
4
4
  export declare function throwErr(...args: StatusErrorConstructorParameters): never;
5
5
  /**
6
- * Concatenates the stacktraces of the given errors onto the first.
6
+ * Concatenates the (original) stacktraces of the given errors onto the first.
7
7
  *
8
8
  * Useful when you invoke an async function to receive a promise without awaiting it immediately. Browsers are smart
9
9
  * enough to keep track of the call stack in async function calls when you invoke `.then` within the same async tick,
10
- * but if you don't,
10
+ * but if you don't, the stacktrace will be lost.
11
11
  *
12
12
  * Here's an example of the unwanted behavior:
13
13
  *
@@ -24,16 +24,10 @@ export declare function throwErr(...args: StatusErrorConstructorParameters): nev
24
24
  * ```
25
25
  */
26
26
  export declare function concatStacktraces(first: Error, ...errors: Error[]): void;
27
- export declare class StackAssertionError extends Error implements ErrorWithCustomCapture {
28
- readonly extraData?: Record<string, any> | undefined;
29
- constructor(message: string, extraData?: Record<string, any> | undefined, options?: ErrorOptions);
30
- customCaptureExtraArgs: {
31
- cause?: {} | undefined;
32
- }[];
27
+ export declare class StackAssertionError extends Error {
28
+ readonly extraData?: (Record<string, any> & ErrorOptions) | undefined;
29
+ constructor(message: string, extraData?: (Record<string, any> & ErrorOptions) | undefined);
33
30
  }
34
- export type ErrorWithCustomCapture = {
35
- customCaptureExtraArgs: any[];
36
- };
37
31
  export declare function registerErrorSink(sink: (location: string, error: unknown) => void): void;
38
32
  export declare function captureError(location: string, error: unknown): void;
39
33
  type Status = {
@@ -1,4 +1,5 @@
1
1
  import { globalVar } from "./globals";
2
+ import { pick } from "./objects";
2
3
  export function throwErr(...args) {
3
4
  if (typeof args[0] === "string") {
4
5
  throw new StackAssertionError(args[0], args[1]);
@@ -17,11 +18,11 @@ function removeStacktraceNameLine(stack) {
17
18
  return stack.split("\n").slice(addsNameLine ? 1 : 0).join("\n");
18
19
  }
19
20
  /**
20
- * Concatenates the stacktraces of the given errors onto the first.
21
+ * Concatenates the (original) stacktraces of the given errors onto the first.
21
22
  *
22
23
  * Useful when you invoke an async function to receive a promise without awaiting it immediately. Browsers are smart
23
24
  * enough to keep track of the call stack in async function calls when you invoke `.then` within the same async tick,
24
- * but if you don't,
25
+ * but if you don't, the stacktrace will be lost.
25
26
  *
26
27
  * Here's an example of the unwanted behavior:
27
28
  *
@@ -50,16 +51,16 @@ export function concatStacktraces(first, ...errors) {
50
51
  }
51
52
  }
52
53
  export class StackAssertionError extends Error {
53
- constructor(message, extraData, options) {
54
+ constructor(message, extraData) {
54
55
  const disclaimer = `\n\nThis is likely an error in Stack. Please make sure you are running the newest version and report it.`;
55
- super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`, options);
56
+ super(`${message}${message.endsWith(disclaimer) ? "" : disclaimer}`, pick(extraData ?? {}, ["cause"]));
56
57
  this.extraData = extraData;
57
- this.customCaptureExtraArgs = [
58
- {
59
- ...this.extraData,
60
- ...this.cause ? { cause: this.cause } : {},
58
+ Object.defineProperty(this, "customCaptureExtraArgs", {
59
+ get() {
60
+ return [this.extraData];
61
61
  },
62
- ];
62
+ enumerable: false,
63
+ });
63
64
  }
64
65
  }
65
66
  StackAssertionError.prototype.name = "StackAssertionError";
@@ -92,7 +93,7 @@ export class StatusError extends Error {
92
93
  this.name = "StatusError";
93
94
  this.statusCode = status;
94
95
  if (!message) {
95
- throw new StackAssertionError("StatusError always requires a message unless a Status object is passed", {}, { cause: this });
96
+ throw new StackAssertionError("StatusError always requires a message unless a Status object is passed", { cause: this });
96
97
  }
97
98
  }
98
99
  isClientError() {
@@ -37,3 +37,5 @@ export declare const HTTP_METHODS: {
37
37
  };
38
38
  };
39
39
  export type HttpMethod = keyof typeof HTTP_METHODS;
40
+ export declare function decodeBasicAuthorizationHeader(value: string): [string, string] | null;
41
+ export declare function encodeBasicAuthorizationHeader(id: string, password: string): string;
@@ -1,3 +1,4 @@
1
+ import { decodeBase64, encodeBase64, isBase64 } from "./bytes";
1
2
  export const HTTP_METHODS = {
2
3
  "GET": {
3
4
  safe: true,
@@ -36,3 +37,22 @@ export const HTTP_METHODS = {
36
37
  idempotent: false,
37
38
  },
38
39
  };
40
+ export function decodeBasicAuthorizationHeader(value) {
41
+ const [type, encoded, ...rest] = value.split(' ');
42
+ if (rest.length > 0)
43
+ return null;
44
+ if (!encoded)
45
+ return null;
46
+ if (type !== 'Basic')
47
+ return null;
48
+ if (!isBase64(encoded))
49
+ return null;
50
+ const decoded = new TextDecoder().decode(decodeBase64(encoded));
51
+ const split = decoded.split(':');
52
+ return [split[0], split.slice(1).join(':')];
53
+ }
54
+ export function encodeBasicAuthorizationHeader(id, password) {
55
+ if (id.includes(':'))
56
+ throw new Error("Basic authorization header id cannot contain ':'");
57
+ return `Basic ${encodeBase64(new TextEncoder().encode(`${id}:${password}`))}`;
58
+ }
package/dist/utils/jwt.js CHANGED
@@ -3,6 +3,7 @@ import elliptic from "elliptic";
3
3
  import * as jose from "jose";
4
4
  import { JOSEError } from "jose/errors";
5
5
  import { encodeBase64Url } from "./bytes";
6
+ import { StackAssertionError } from "./errors";
6
7
  import { globalVar } from "./globals";
7
8
  import { pick } from "./objects";
8
9
  const STACK_SERVER_SECRET = process.env.STACK_SERVER_SECRET ?? "";
@@ -74,6 +75,9 @@ export async function getPublicJwkSet(secretOrPrivateJwk) {
74
75
  };
75
76
  }
76
77
  export function getPerAudienceSecret(options) {
78
+ if (options.audience === "kid") {
79
+ throw new StackAssertionError("You cannot use the 'kid' audience for a per-audience secret, see comment below in jwt.tsx");
80
+ }
77
81
  return jose.base64url.encode(crypto
78
82
  .createHash('sha256')
79
83
  // 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
@@ -69,7 +69,7 @@ declare class RetryError extends AggregateError {
69
69
  constructor(errors: unknown[]);
70
70
  get retries(): number;
71
71
  }
72
- declare function retry<T>(fn: () => Result<T> | Promise<Result<T>>, retries: number, { exponentialDelayBase }: {
72
+ declare function retry<T>(fn: (attempt: number) => Result<T> | Promise<Result<T>>, retries: number, { exponentialDelayBase }?: {
73
73
  exponentialDelayBase?: number | undefined;
74
74
  }): Promise<Result<T, RetryError>>;
75
75
  export {};
@@ -116,17 +116,17 @@ class RetryError extends AggregateError {
116
116
  }
117
117
  }
118
118
  RetryError.prototype.name = "RetryError";
119
- async function retry(fn, retries, { exponentialDelayBase = 2000 }) {
119
+ async function retry(fn, retries, { exponentialDelayBase = 1000 } = {}) {
120
120
  const errors = [];
121
121
  for (let i = 0; i < retries; i++) {
122
- const res = await fn();
122
+ const res = await fn(i);
123
123
  if (res.status === "ok") {
124
124
  return Result.ok(res.data);
125
125
  }
126
126
  else {
127
127
  errors.push(res.error);
128
128
  if (i < retries - 1) {
129
- await wait(Math.random() * exponentialDelayBase * 2 ** i);
129
+ await wait((Math.random() + 0.5) * exponentialDelayBase * (2 ** i));
130
130
  }
131
131
  }
132
132
  }
@@ -0,0 +1,2 @@
1
+ import * as Sentry from "@sentry/nextjs";
2
+ export declare const sentryBaseConfig: Sentry.BrowserOptions & Sentry.NodeOptions & Sentry.VercelEdgeOptions;
@@ -0,0 +1,15 @@
1
+ export const sentryBaseConfig = {
2
+ ignoreErrors: [
3
+ // React throws these errors when used with some browser extensions (eg. Google Translate)
4
+ "NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
5
+ "NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.",
6
+ ],
7
+ normalizeDepth: 5,
8
+ maxValueLength: 5000,
9
+ // Adjust this value in production, or use tracesSampler for greater control
10
+ tracesSampleRate: 1.0,
11
+ // Setting this option to true will print useful information to the console while you're setting up Sentry.
12
+ debug: false,
13
+ replaysOnErrorSampleRate: 1.0,
14
+ replaysSessionSampleRate: 1.0,
15
+ };
@@ -1,4 +1,5 @@
1
1
  export declare function createUrlIfValid(...args: ConstructorParameters<typeof URL>): URL | null;
2
+ export declare function isValidUrl(url: string): boolean;
2
3
  export declare function isLocalhost(urlOrString: string | URL): boolean;
3
4
  export declare function isRelative(url: string): boolean;
4
5
  export declare function getRelativePart(url: URL): string;
@@ -7,6 +7,9 @@ export function createUrlIfValid(...args) {
7
7
  return null;
8
8
  }
9
9
  }
10
+ export function isValidUrl(url) {
11
+ return !!createUrlIfValid(url);
12
+ }
10
13
  export function isLocalhost(urlOrString) {
11
14
  const url = createUrlIfValid(urlOrString);
12
15
  if (!url)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.6.33",
3
+ "version": "2.6.36",
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.33"
53
+ "@stackframe/stack-sc": "2.6.36"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@simplewebauthn/types": "^11.0.0",
@@ -61,7 +61,8 @@
61
61
  "rimraf": "^5.0.5",
62
62
  "react": "^18.2",
63
63
  "react-dom": "^18.2",
64
- "next": "^14.1.0"
64
+ "next": "^14.1.0",
65
+ "@sentry/nextjs": "^8.40.0"
65
66
  },
66
67
  "scripts": {
67
68
  "build": "tsc",