@stackframe/stack-shared 2.5.15 → 2.5.17

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,18 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.5.17
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - @stackframe/stack-sc@2.5.17
9
+
10
+ ## 2.5.16
11
+
12
+ ### Patch Changes
13
+
14
+ - @stackframe/stack-sc@2.5.16
15
+
3
16
  ## 2.5.15
4
17
 
5
18
  ### Patch Changes
@@ -28,6 +28,8 @@ export declare class StackClientInterface {
28
28
  prodDashboard: string;
29
29
  prodBackend: string;
30
30
  }>;
31
+ protected _networkRetry<T>(cb: () => Promise<Result<T, any>>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
32
+ protected _networkRetryException<T>(cb: () => Promise<T>, session?: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<T>;
31
33
  fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
32
34
  protected sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
33
35
  usedTokens: {
@@ -47,13 +47,13 @@ export class StackClientInterface {
47
47
  }
48
48
  });
49
49
  const prodDashboard = await tryRequest(async () => {
50
- const res = await fetch("https://app.stackframe.com/health");
50
+ const res = await fetch("https://app.stack-auth.com/health");
51
51
  if (!res.ok) {
52
52
  throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
53
53
  }
54
54
  });
55
55
  const prodBackend = await tryRequest(async () => {
56
- const res = await fetch("https://api.stackframe.com/health");
56
+ const res = await fetch("https://api.stack-auth.com/health");
57
57
  if (!res.ok) {
58
58
  throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
59
59
  }
@@ -66,6 +66,28 @@ export class StackClientInterface {
66
66
  prodBackend,
67
67
  };
68
68
  }
69
+ async _networkRetry(cb, session, requestType) {
70
+ const retriedResult = await Result.retry(cb, 5, { exponentialDelayBase: 1000 });
71
+ // try to diagnose the error for the user
72
+ if (retriedResult.status === "error") {
73
+ if (!navigator.onLine) {
74
+ throw new Error("Failed to send Stack network request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
75
+ }
76
+ throw new Error(deindent `
77
+ Stack is unable to connect to the server. Please check your internet connection and try again.
78
+
79
+ If the problem persists, please contact Stack support and provide a screenshot of your entire browser console.
80
+
81
+ ${retriedResult.error}
82
+
83
+ ${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
84
+ `, { cause: retriedResult.error });
85
+ }
86
+ return retriedResult.data;
87
+ }
88
+ async _networkRetryException(cb, session, requestType) {
89
+ return await this._networkRetry(async () => await Result.fromThrowingAsync(cb), session, requestType);
90
+ }
69
91
  async fetchNewAccessToken(refreshToken) {
70
92
  if (!('publishableClientKey' in this.options)) {
71
93
  // TODO support it
@@ -81,7 +103,7 @@ export class StackClientInterface {
81
103
  client_secret: this.options.publishableClientKey,
82
104
  token_endpoint_auth_method: 'client_secret_post',
83
105
  };
84
- const rawResponse = await oauth.refreshTokenGrantRequest(as, client, refreshToken.token);
106
+ const rawResponse = await this._networkRetryException(async () => await oauth.refreshTokenGrantRequest(as, client, refreshToken.token));
85
107
  const response = await this._processResponse(rawResponse);
86
108
  if (response.status === "error") {
87
109
  const error = response.error;
@@ -108,23 +130,7 @@ export class StackClientInterface {
108
130
  session ??= this.createSession({
109
131
  refreshToken: null,
110
132
  });
111
- const retriedResult = await Result.retry(() => this.sendClientRequestInner(path, requestOptions, session, requestType), 5, { exponentialDelayBase: 1000 });
112
- // try to diagnose the error for the user
113
- if (retriedResult.status === "error") {
114
- if (!navigator.onLine) {
115
- throw new Error("Failed to send Stack request. It seems like you are offline. (window.navigator.onLine is falsy)", { cause: retriedResult.error });
116
- }
117
- throw new Error(deindent `
118
- Stack is unable to connect to the server. Please check your internet connection and try again.
119
-
120
- If the problem persists, please contact Stack support and provide a screenshot of your entire browser console.
121
-
122
- ${retriedResult.error}
123
-
124
- ${JSON.stringify(await this.runNetworkDiagnostics(session, requestType), null, 2)}
125
- `, { cause: retriedResult.error });
126
- }
127
- return retriedResult.data;
133
+ return await this._networkRetry(() => this.sendClientRequestInner(path, requestOptions, session, requestType), session, requestType);
128
134
  }
129
135
  createSession(options) {
130
136
  const session = new InternalSession({
@@ -213,7 +219,7 @@ export class StackClientInterface {
213
219
  catch (e) {
214
220
  if (e instanceof TypeError) {
215
221
  // Network error, retry
216
- console.log("Stack detected a network error, retrying.", e);
222
+ console.warn(`Stack detected a network error while fetching ${url}, retrying.`, e, { url });
217
223
  return Result.error(e);
218
224
  }
219
225
  throw e;
@@ -492,7 +498,7 @@ export class StackClientInterface {
492
498
  client_secret: this.options.publishableClientKey,
493
499
  token_endpoint_auth_method: 'client_secret_post',
494
500
  };
495
- const params = oauth.validateAuthResponse(as, client, options.oauthParams, options.state);
501
+ const params = await this._networkRetryException(async () => oauth.validateAuthResponse(as, client, options.oauthParams, options.state));
496
502
  if (oauth.isOAuth2Error(params)) {
497
503
  throw new StackAssertionError("Error validating outer OAuth response", { params }); // Handle OAuth 2.0 redirect error
498
504
  }
@@ -551,7 +557,7 @@ export class StackClientInterface {
551
557
  return user;
552
558
  }
553
559
  async listCurrentUserTeamPermissions(options, session) {
554
- const response = await this.sendClientRequest(`/team-permissions?team_id=${options.teamId}?user_id=me&recursive=${options.recursive}`, {}, session);
560
+ const response = await this.sendClientRequest(`/team-permissions?team_id=${options.teamId}&user_id=me&recursive=${options.recursive}`, {}, session);
555
561
  const result = await response.json();
556
562
  return result.items;
557
563
  }
@@ -16,6 +16,29 @@ export declare const currentUserCrud: import("../../crud").CrudSchemaFromOptions
16
16
  signed_up_at_millis: number;
17
17
  has_password: NonNullable<boolean | undefined>;
18
18
  auth_with_email: NonNullable<boolean | undefined>;
19
+ auth_methods: ({
20
+ type: "password";
21
+ identifier: string;
22
+ } | {
23
+ type: "otp";
24
+ contact_channel: {
25
+ type: "email";
26
+ email: string;
27
+ };
28
+ } | {
29
+ type: "oauth";
30
+ provider: {
31
+ type: string;
32
+ provider_user_id: string;
33
+ };
34
+ })[];
35
+ connected_accounts: {
36
+ type: "oauth";
37
+ provider: {
38
+ type: string;
39
+ provider_user_id: string;
40
+ };
41
+ }[];
19
42
  } & {
20
43
  selected_team: {
21
44
  id: string;
@@ -38,6 +61,8 @@ export declare const currentUserCrud: import("../../crud").CrudSchemaFromOptions
38
61
  has_password: undefined;
39
62
  auth_with_email: undefined;
40
63
  oauth_providers: undefined;
64
+ auth_methods: undefined;
65
+ connected_accounts: undefined;
41
66
  client_metadata: undefined;
42
67
  server_metadata: undefined;
43
68
  }, "">;
@@ -62,6 +87,29 @@ export declare const currentUserCrud: import("../../crud").CrudSchemaFromOptions
62
87
  id: string;
63
88
  account_id: string;
64
89
  }[];
90
+ auth_methods: ({
91
+ type: "password";
92
+ identifier: string;
93
+ } | {
94
+ type: "otp";
95
+ contact_channel: {
96
+ type: "email";
97
+ email: string;
98
+ };
99
+ } | {
100
+ type: "oauth";
101
+ provider: {
102
+ type: string;
103
+ provider_user_id: string;
104
+ };
105
+ })[];
106
+ connected_accounts: {
107
+ type: "oauth";
108
+ provider: {
109
+ type: string;
110
+ provider_user_id: string;
111
+ };
112
+ }[];
65
113
  client_metadata: {} | null;
66
114
  server_metadata: {} | null;
67
115
  } | null, import("yup").AnyObject, {
@@ -81,6 +129,8 @@ export declare const currentUserCrud: import("../../crud").CrudSchemaFromOptions
81
129
  has_password: undefined;
82
130
  auth_with_email: undefined;
83
131
  oauth_providers: undefined;
132
+ auth_methods: undefined;
133
+ connected_accounts: undefined;
84
134
  client_metadata: undefined;
85
135
  server_metadata: undefined;
86
136
  }, "">;
@@ -20,6 +20,8 @@ const clientReadSchema = usersCrudServerReadSchema.pick([
20
20
  "auth_with_email",
21
21
  "oauth_providers",
22
22
  "selected_team_id",
23
+ "auth_methods",
24
+ "connected_accounts",
23
25
  ]).concat(yupObject({
24
26
  selected_team: teamsCrudClientReadSchema.nullable().defined(),
25
27
  })).nullable().defined(); // TODO: next-release: make required
@@ -41,6 +41,29 @@ export declare const usersCrudServerReadSchema: import("yup").ObjectSchema<{
41
41
  id: string;
42
42
  account_id: string;
43
43
  }[];
44
+ auth_methods: ({
45
+ type: "password";
46
+ identifier: string;
47
+ } | {
48
+ type: "otp";
49
+ contact_channel: {
50
+ type: "email";
51
+ email: string;
52
+ };
53
+ } | {
54
+ type: "oauth";
55
+ provider: {
56
+ type: string;
57
+ provider_user_id: string;
58
+ };
59
+ })[];
60
+ connected_accounts: {
61
+ type: "oauth";
62
+ provider: {
63
+ type: string;
64
+ provider_user_id: string;
65
+ };
66
+ }[];
44
67
  client_metadata: {} | null;
45
68
  server_metadata: {} | null;
46
69
  }, import("yup").AnyObject, {
@@ -60,6 +83,8 @@ export declare const usersCrudServerReadSchema: import("yup").ObjectSchema<{
60
83
  has_password: undefined;
61
84
  auth_with_email: undefined;
62
85
  oauth_providers: undefined;
86
+ auth_methods: undefined;
87
+ connected_accounts: undefined;
63
88
  client_metadata: undefined;
64
89
  server_metadata: undefined;
65
90
  }, "">;
@@ -114,6 +139,29 @@ export declare const usersCrud: import("../../crud").CrudSchemaFromOptions<{
114
139
  id: string;
115
140
  account_id: string;
116
141
  }[];
142
+ auth_methods: ({
143
+ type: "password";
144
+ identifier: string;
145
+ } | {
146
+ type: "otp";
147
+ contact_channel: {
148
+ type: "email";
149
+ email: string;
150
+ };
151
+ } | {
152
+ type: "oauth";
153
+ provider: {
154
+ type: string;
155
+ provider_user_id: string;
156
+ };
157
+ })[];
158
+ connected_accounts: {
159
+ type: "oauth";
160
+ provider: {
161
+ type: string;
162
+ provider_user_id: string;
163
+ };
164
+ }[];
117
165
  client_metadata: {} | null;
118
166
  server_metadata: {} | null;
119
167
  }, import("yup").AnyObject, {
@@ -133,6 +181,8 @@ export declare const usersCrud: import("../../crud").CrudSchemaFromOptions<{
133
181
  has_password: undefined;
134
182
  auth_with_email: undefined;
135
183
  oauth_providers: undefined;
184
+ auth_methods: undefined;
185
+ connected_accounts: undefined;
136
186
  client_metadata: undefined;
137
187
  server_metadata: undefined;
138
188
  }, "">;
@@ -238,6 +288,29 @@ export declare const userCreatedWebhookEvent: {
238
288
  id: string;
239
289
  account_id: string;
240
290
  }[];
291
+ auth_methods: ({
292
+ type: "password";
293
+ identifier: string;
294
+ } | {
295
+ type: "otp";
296
+ contact_channel: {
297
+ type: "email";
298
+ email: string;
299
+ };
300
+ } | {
301
+ type: "oauth";
302
+ provider: {
303
+ type: string;
304
+ provider_user_id: string;
305
+ };
306
+ })[];
307
+ connected_accounts: {
308
+ type: "oauth";
309
+ provider: {
310
+ type: string;
311
+ provider_user_id: string;
312
+ };
313
+ }[];
241
314
  client_metadata: {} | null;
242
315
  server_metadata: {} | null;
243
316
  }, import("yup").AnyObject, {
@@ -257,6 +330,8 @@ export declare const userCreatedWebhookEvent: {
257
330
  has_password: undefined;
258
331
  auth_with_email: undefined;
259
332
  oauth_providers: undefined;
333
+ auth_methods: undefined;
334
+ connected_accounts: undefined;
260
335
  client_metadata: undefined;
261
336
  server_metadata: undefined;
262
337
  }, "">;
@@ -289,6 +364,29 @@ export declare const userUpdatedWebhookEvent: {
289
364
  id: string;
290
365
  account_id: string;
291
366
  }[];
367
+ auth_methods: ({
368
+ type: "password";
369
+ identifier: string;
370
+ } | {
371
+ type: "otp";
372
+ contact_channel: {
373
+ type: "email";
374
+ email: string;
375
+ };
376
+ } | {
377
+ type: "oauth";
378
+ provider: {
379
+ type: string;
380
+ provider_user_id: string;
381
+ };
382
+ })[];
383
+ connected_accounts: {
384
+ type: "oauth";
385
+ provider: {
386
+ type: string;
387
+ provider_user_id: string;
388
+ };
389
+ }[];
292
390
  client_metadata: {} | null;
293
391
  server_metadata: {} | null;
294
392
  }, import("yup").AnyObject, {
@@ -308,6 +406,8 @@ export declare const userUpdatedWebhookEvent: {
308
406
  has_password: undefined;
309
407
  auth_with_email: undefined;
310
408
  oauth_providers: undefined;
409
+ auth_methods: undefined;
410
+ connected_accounts: undefined;
311
411
  client_metadata: undefined;
312
412
  server_metadata: undefined;
313
413
  }, "">;
@@ -22,12 +22,35 @@ export const usersCrudServerReadSchema = fieldSchema.yupObject({
22
22
  profile_image_url: fieldSchema.profileImageUrlSchema.nullable().defined(),
23
23
  signed_up_at_millis: fieldSchema.signedUpAtMillisSchema.required(),
24
24
  has_password: fieldSchema.yupBoolean().required().meta({ openapiField: { description: 'Whether the user has a password associated with their account', exampleValue: true } }),
25
- auth_with_email: fieldSchema.yupBoolean().required().meta({ openapiField: { description: 'Whether the user can authenticate with their primary e-mail. If set to true, the user can log-in with credentials and/or magic link, if enabled in the project settings.', exampleValue: true } }),
25
+ /**
26
+ * @deprecated
27
+ */
28
+ auth_with_email: fieldSchema.yupBoolean().required().meta({ openapiField: { hidden: true, description: 'Whether the user can authenticate with their primary e-mail. If set to true, the user can log-in with credentials and/or magic link, if enabled in the project settings.', exampleValue: true } }),
29
+ /**
30
+ * @deprecated
31
+ */
26
32
  oauth_providers: fieldSchema.yupArray(fieldSchema.yupObject({
27
33
  id: fieldSchema.yupString().required(),
28
34
  account_id: fieldSchema.yupString().required(),
29
35
  email: fieldSchema.yupString().nullable(),
30
- }).required()).required().meta({ openapiField: { description: 'A list of OAuth providers connected to this account', exampleValue: [{ id: 'google', account_id: '12345', email: 'john.doe@gmail.com' }] } }),
36
+ }).required()).required().meta({ openapiField: { hidden: true, description: 'A list of OAuth providers connected to this account', exampleValue: [{ id: 'google', account_id: '12345', email: 'john.doe@gmail.com' }] } }),
37
+ auth_methods: fieldSchema.yupArray(fieldSchema.yupUnion(fieldSchema.yupObject({
38
+ type: fieldSchema.yupString().oneOf(['password']).required(),
39
+ identifier: fieldSchema.yupString().required(),
40
+ }).required(), fieldSchema.yupObject({
41
+ type: fieldSchema.yupString().oneOf(['otp']).required(),
42
+ contact_channel: fieldSchema.yupObject({
43
+ type: fieldSchema.yupString().oneOf(['email']).required(),
44
+ email: fieldSchema.yupString().required(),
45
+ }).required(),
46
+ }).required(), fieldSchema.yupObject({
47
+ type: fieldSchema.yupString().oneOf(['oauth']).required(),
48
+ provider: fieldSchema.userOAuthProviderSchema.required(),
49
+ }).required())).required().meta({ openapiField: { description: 'A list of authentication methods available for this user to sign in with', exampleValue: [{ "contact_channel": { "email": "john.doe@gmail.com", "type": "email", }, "type": "otp", }] } }),
50
+ connected_accounts: fieldSchema.yupArray(fieldSchema.yupUnion(fieldSchema.yupObject({
51
+ type: fieldSchema.yupString().oneOf(['oauth']).required(),
52
+ provider: fieldSchema.userOAuthProviderSchema.required(),
53
+ }).required())).required().meta({ openapiField: { description: 'A list of connected accounts to this user', exampleValue: [{ "provider": { "provider_user_id": "12345", "type": "google", }, "type": "oauth", }] } }),
31
54
  client_metadata: fieldSchema.userClientMetadataSchema,
32
55
  server_metadata: fieldSchema.userServerMetadataSchema,
33
56
  }).required();
@@ -31,6 +31,29 @@ export declare const webhookEvents: readonly [{
31
31
  id: string;
32
32
  account_id: string;
33
33
  }[];
34
+ auth_methods: ({
35
+ type: "password";
36
+ identifier: string;
37
+ } | {
38
+ type: "otp";
39
+ contact_channel: {
40
+ type: "email";
41
+ email: string;
42
+ };
43
+ } | {
44
+ type: "oauth";
45
+ provider: {
46
+ type: string;
47
+ provider_user_id: string;
48
+ };
49
+ })[];
50
+ connected_accounts: {
51
+ type: "oauth";
52
+ provider: {
53
+ type: string;
54
+ provider_user_id: string;
55
+ };
56
+ }[];
34
57
  client_metadata: {} | null;
35
58
  server_metadata: {} | null;
36
59
  }, yup.AnyObject, {
@@ -50,6 +73,8 @@ export declare const webhookEvents: readonly [{
50
73
  has_password: undefined;
51
74
  auth_with_email: undefined;
52
75
  oauth_providers: undefined;
76
+ auth_methods: undefined;
77
+ connected_accounts: undefined;
53
78
  client_metadata: undefined;
54
79
  server_metadata: undefined;
55
80
  }, "">;
@@ -81,6 +106,29 @@ export declare const webhookEvents: readonly [{
81
106
  id: string;
82
107
  account_id: string;
83
108
  }[];
109
+ auth_methods: ({
110
+ type: "password";
111
+ identifier: string;
112
+ } | {
113
+ type: "otp";
114
+ contact_channel: {
115
+ type: "email";
116
+ email: string;
117
+ };
118
+ } | {
119
+ type: "oauth";
120
+ provider: {
121
+ type: string;
122
+ provider_user_id: string;
123
+ };
124
+ })[];
125
+ connected_accounts: {
126
+ type: "oauth";
127
+ provider: {
128
+ type: string;
129
+ provider_user_id: string;
130
+ };
131
+ }[];
84
132
  client_metadata: {} | null;
85
133
  server_metadata: {} | null;
86
134
  }, yup.AnyObject, {
@@ -100,6 +148,8 @@ export declare const webhookEvents: readonly [{
100
148
  has_password: undefined;
101
149
  auth_with_email: undefined;
102
150
  oauth_providers: undefined;
151
+ auth_methods: undefined;
152
+ connected_accounts: undefined;
103
153
  client_metadata: undefined;
104
154
  server_metadata: undefined;
105
155
  }, "">;
@@ -360,5 +360,8 @@ export declare const KnownErrors: {
360
360
  InvalidStandardOAuthProviderId: KnownErrorConstructor<KnownError & KnownErrorBrand<"INVALID_STANDARD_OAUTH_PROVIDER_ID">, [any]> & {
361
361
  errorCode: "INVALID_STANDARD_OAUTH_PROVIDER_ID";
362
362
  };
363
+ InvalidAuthorizationCode: KnownErrorConstructor<KnownError & KnownErrorBrand<"INVALID_AUTHORIZATION_CODE">, []> & {
364
+ errorCode: "INVALID_AUTHORIZATION_CODE";
365
+ };
363
366
  };
364
367
  export {};
@@ -502,6 +502,10 @@ const InvalidStandardOAuthProviderId = createKnownErrorConstructor(KnownError, "
502
502
  provider_id: providerId,
503
503
  },
504
504
  ], (json) => [json.provider_id]);
505
+ const InvalidAuthorizationCode = createKnownErrorConstructor(KnownError, "INVALID_AUTHORIZATION_CODE", () => [
506
+ 400,
507
+ "The given authorization code is invalid.",
508
+ ], () => []);
505
509
  export const KnownErrors = {
506
510
  UnsupportedError,
507
511
  BodyParsingError,
@@ -583,6 +587,7 @@ export const KnownErrors = {
583
587
  TeamPermissionRequired,
584
588
  InvalidSharedOAuthProviderId,
585
589
  InvalidStandardOAuthProviderId,
590
+ InvalidAuthorizationCode,
586
591
  };
587
592
  // ensure that all known error codes are unique
588
593
  const knownErrorCodes = new Set();
@@ -12,6 +12,7 @@ export declare function yupMixed<A extends {}>(...args: Parameters<typeof yup.mi
12
12
  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, "">;
13
13
  export declare function yupTuple<T extends [unknown, ...unknown[]]>(...args: Parameters<typeof yup.tuple<T>>): yup.TupleSchema<T | undefined, yup.AnyObject, undefined, "">;
14
14
  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, "">;
15
+ export declare function yupUnion<T extends yup.ISchema<any>[]>(...args: T): yup.ISchema<yup.InferType<T[number]>>;
15
16
  export declare const adaptSchema: yup.MixedSchema<typeof StackAdaptSentinel | undefined, yup.AnyObject, undefined, "">;
16
17
  /**
17
18
  * Yup's URL schema does not recognize some URLs (including `http://localhost`) as a valid URL. This schema is a workaround for that.
@@ -63,6 +64,13 @@ export declare const profileImageUrlSchema: yup.StringSchema<string | undefined,
63
64
  export declare const signedUpAtMillisSchema: yup.NumberSchema<number | undefined, yup.AnyObject, undefined, "">;
64
65
  export declare const userClientMetadataSchema: yup.MixedSchema<{} | null, yup.AnyObject, undefined, "">;
65
66
  export declare const userServerMetadataSchema: yup.MixedSchema<{} | null, yup.AnyObject, undefined, "">;
67
+ export declare const userOAuthProviderSchema: yup.ObjectSchema<{
68
+ type: string;
69
+ provider_user_id: string;
70
+ }, yup.AnyObject, {
71
+ type: undefined;
72
+ provider_user_id: undefined;
73
+ }, "">;
66
74
  export declare const signInEmailSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
67
75
  export declare const emailOtpSignInCallbackUrlSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
68
76
  export declare const emailVerificationCallbackUrlSchema: yup.StringSchema<string | undefined, yup.AnyObject, undefined, "">;
@@ -1,4 +1,5 @@
1
1
  import * as yup from "yup";
2
+ import { StackAssertionError } from "./utils/errors";
2
3
  import { allProviders } from "./utils/oauth";
3
4
  import { isUuid } from "./utils/uuids";
4
5
  const _idDescription = (identify) => `The unique identifier of this ${identify}`;
@@ -55,6 +56,30 @@ export function yupObject(...args) {
55
56
  return object.default(undefined);
56
57
  }
57
58
  /* eslint-enable no-restricted-syntax */
59
+ export function yupUnion(...args) {
60
+ if (args.length === 0)
61
+ throw new Error('yupUnion must have at least one schema');
62
+ const [first] = args;
63
+ const firstDesc = first.describe();
64
+ for (const schema of args) {
65
+ const desc = schema.describe();
66
+ if (desc.type !== firstDesc.type)
67
+ throw new StackAssertionError(`yupUnion must have schemas of the same type (got: ${firstDesc.type} and ${desc.type})`, { first, schema, firstDesc, desc });
68
+ }
69
+ return yupMixed().required().test('is-one-of', 'Invalid value', async (value, context) => {
70
+ const errors = [];
71
+ for (const schema of args) {
72
+ try {
73
+ await schema.validate(value, context.options);
74
+ return true;
75
+ }
76
+ catch (e) {
77
+ errors.push(e);
78
+ }
79
+ }
80
+ throw new AggregateError(errors, 'Invalid value; must be one of the provided schemas');
81
+ });
82
+ }
58
83
  // Common
59
84
  export const adaptSchema = yupMixed();
60
85
  /**
@@ -160,6 +185,10 @@ export const profileImageUrlSchema = yupString().meta({ openapiField: { descript
160
185
  export const signedUpAtMillisSchema = yupNumber().meta({ openapiField: { description: _signedUpAtMillisDescription, exampleValue: 1630000000000 } });
161
186
  export const userClientMetadataSchema = jsonSchema.meta({ openapiField: { description: _clientMetaDataDescription('user'), exampleValue: { key: 'value' } } });
162
187
  export const userServerMetadataSchema = jsonSchema.meta({ openapiField: { description: _serverMetaDataDescription('user'), exampleValue: { key: 'value' } } });
188
+ export const userOAuthProviderSchema = yupObject({
189
+ type: yupString().required(),
190
+ provider_user_id: yupString().required(),
191
+ });
163
192
  // Auth
164
193
  export const signInEmailSchema = emailSchema.meta({ openapiField: { description: 'The email to sign in with.', exampleValue: 'johndoe@example.com' } });
165
194
  export const emailOtpSignInCallbackUrlSchema = urlSchema.meta({ openapiField: { description: 'The base callback URL to construct the magic link from. A query argument `code` with the verification code will be appended to it. The page should then make a request to the `/auth/otp/sign-in` endpoint.', exampleValue: 'https://example.com/handler/magic-link-callback' } });
@@ -13,3 +13,4 @@ export declare function rotateLeft(arr: readonly any[], n: number): any[];
13
13
  export declare function rotateRight(arr: readonly any[], n: number): any[];
14
14
  export declare function shuffle<T>(arr: readonly T[]): T[];
15
15
  export declare function outerProduct<T, U>(arr1: readonly T[], arr2: readonly U[]): [T, U][];
16
+ export declare function unique<T>(arr: readonly T[]): T[];
@@ -65,3 +65,6 @@ export function shuffle(arr) {
65
65
  export function outerProduct(arr1, arr2) {
66
66
  return arr1.flatMap((item1) => arr2.map((item2) => [item1, item2]));
67
67
  }
68
+ export function unique(arr) {
69
+ return [...new Set(arr)];
70
+ }
@@ -1,3 +1,4 @@
1
+ export declare function generateRandomValues(array: Uint8Array): any;
1
2
  /**
2
3
  * Generates a secure alphanumeric string using the system's cryptographically secure
3
4
  * random number generator.
@@ -1,5 +1,15 @@
1
1
  import { encodeBase32 } from "./bytes";
2
+ import { StackAssertionError } from "./errors";
2
3
  import { globalVar } from "./globals";
4
+ export function generateRandomValues(array) {
5
+ if (!globalVar.crypto) {
6
+ throw new StackAssertionError("Crypto API is not available in this environment. Are you using an old browser?");
7
+ }
8
+ if (!globalVar.crypto.getRandomValues) {
9
+ throw new StackAssertionError("crypto.getRandomValues is not available in this environment. Are you using an old browser?");
10
+ }
11
+ return globalVar.crypto.getRandomValues(array);
12
+ }
3
13
  /**
4
14
  * Generates a secure alphanumeric string using the system's cryptographically secure
5
15
  * random number generator.
@@ -7,7 +17,7 @@ import { globalVar } from "./globals";
7
17
  export function generateSecureRandomString(minBitsOfEntropy = 224) {
8
18
  const base32CharactersCount = Math.ceil(minBitsOfEntropy / 5);
9
19
  const bytesCount = Math.ceil(base32CharactersCount * 5 / 8);
10
- const randomBytes = globalVar.crypto.getRandomValues(new Uint8Array(bytesCount));
20
+ const randomBytes = generateRandomValues(new Uint8Array(bytesCount));
11
21
  const str = encodeBase32(randomBytes);
12
22
  return str.slice(str.length - base32CharactersCount).toLowerCase();
13
23
  }
@@ -12,6 +12,7 @@ export type AsyncResult<T, E = unknown, P = void> = Result<T, E> | ({
12
12
  });
13
13
  export declare const Result: {
14
14
  fromThrowing: typeof fromThrowing;
15
+ fromThrowingAsync: typeof fromThrowingAsync;
15
16
  fromPromise: typeof promiseToResult;
16
17
  ok<T>(data: T): {
17
18
  status: "ok";
@@ -60,6 +61,7 @@ declare function pending<P>(progress: P): AsyncResult<never, never, P> & {
60
61
  };
61
62
  declare function promiseToResult<T>(promise: Promise<T>): Promise<Result<T>>;
62
63
  declare function fromThrowing<T>(fn: () => T): Result<T, unknown>;
64
+ declare function fromThrowingAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>>;
63
65
  declare function mapResult<T, U, E = unknown, P = unknown>(result: Result<T, E>, fn: (data: T) => U): Result<U, E>;
64
66
  declare function mapResult<T, U, E = unknown, P = unknown>(result: AsyncResult<T, E, P>, fn: (data: T) => U): AsyncResult<U, E, P>;
65
67
  declare class RetryError extends AggregateError {
@@ -2,6 +2,7 @@ import { wait } from "./promises";
2
2
  import { deindent } from "./strings";
3
3
  export const Result = {
4
4
  fromThrowing,
5
+ fromThrowingAsync,
5
6
  fromPromise: promiseToResult,
6
7
  ok(data) {
7
8
  return {
@@ -71,6 +72,14 @@ function fromThrowing(fn) {
71
72
  return Result.error(error);
72
73
  }
73
74
  }
75
+ async function fromThrowingAsync(fn) {
76
+ try {
77
+ return Result.ok(await fn());
78
+ }
79
+ catch (error) {
80
+ return Result.error(error);
81
+ }
82
+ }
74
83
  function mapResult(result, fn) {
75
84
  if (result.status === "error")
76
85
  return {
@@ -1,4 +1,4 @@
1
- import { findLastIndex } from "./arrays";
1
+ import { findLastIndex, unique } from "./arrays";
2
2
  import { StackAssertionError } from "./errors";
3
3
  import { filterUndefined } from "./objects";
4
4
  export function typedToLowercase(s) {
@@ -207,6 +207,9 @@ export function nicify(value, options = {}) {
207
207
  return `[${resValues.join(", ")}]`;
208
208
  }
209
209
  }
210
+ if (value instanceof URL) {
211
+ return `URL(${JSON.stringify(value.toString())})`;
212
+ }
210
213
  const constructorName = [null, Object.prototype].includes(Object.getPrototypeOf(value)) ? null : (nicifiableClassNameOverrides.get(value.constructor) ?? value.constructor.name);
211
214
  const constructorString = constructorName ? `${nicifyPropertyString(constructorName)} ` : "";
212
215
  const entries = getNicifiableEntries(value).filter(([k]) => !hideFields.includes(k));
@@ -256,7 +259,16 @@ function nicifyPropertyString(str) {
256
259
  return JSON.stringify(str);
257
260
  }
258
261
  function getNicifiableKeys(value) {
259
- return ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.() ?? Object.keys(value).sort();
262
+ const overridden = ("getNicifiableKeys" in value ? value.getNicifiableKeys?.bind(value) : null)?.();
263
+ if (overridden != null)
264
+ return overridden;
265
+ const keys = Object.keys(value).sort();
266
+ if (value instanceof Error) {
267
+ if (value.cause)
268
+ keys.unshift("cause");
269
+ keys.unshift("message", "stack");
270
+ }
271
+ return unique(keys);
260
272
  }
261
273
  function getNicifiableEntries(value) {
262
274
  const recordLikes = [Headers];
@@ -1,7 +1,7 @@
1
- import { globalVar } from "./globals";
1
+ import { generateRandomValues } from "./crypto";
2
2
  export function generateUuid() {
3
3
  // crypto.randomUuid is not supported in all browsers, so this is a polyfill
4
- return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^ globalVar.crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
4
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^ generateRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
5
5
  }
6
6
  export function isUuid(str) {
7
7
  return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(str);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.5.15",
3
+ "version": "2.5.17",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -36,7 +36,7 @@
36
36
  "jose": "^5.2.2",
37
37
  "oauth4webapi": "^2.10.3",
38
38
  "uuid": "^9.0.1",
39
- "@stackframe/stack-sc": "2.5.15"
39
+ "@stackframe/stack-sc": "2.5.17"
40
40
  },
41
41
  "devDependencies": {
42
42
  "rimraf": "^5.0.5",