@stackframe/stack-shared 2.4.21 → 2.4.22
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 +8 -0
- package/dist/interface/adminInterface.d.ts +0 -1
- package/dist/interface/clientInterface.d.ts +25 -3
- package/dist/interface/clientInterface.js +46 -30
- package/dist/interface/crud/oauth.d.ts +61 -0
- package/dist/interface/crud/oauth.js +12 -0
- package/dist/known-errors.d.ts +21 -0
- package/dist/known-errors.js +31 -0
- package/dist/utils/react.js +19 -1
- package/dist/utils/strings.d.ts +2 -0
- package/dist/utils/strings.js +10 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -78,7 +78,6 @@ export type OAuthProviderConfigJson = {
|
|
|
78
78
|
type: StandardProvider;
|
|
79
79
|
clientId: string;
|
|
80
80
|
clientSecret: string;
|
|
81
|
-
tenantId?: string;
|
|
82
81
|
});
|
|
83
82
|
export type EmailConfigJson = ({
|
|
84
83
|
type: "standard";
|
|
@@ -173,9 +172,29 @@ export declare class StackClientInterface {
|
|
|
173
172
|
accessToken: string;
|
|
174
173
|
refreshToken: string;
|
|
175
174
|
}>;
|
|
176
|
-
getOAuthUrl(
|
|
177
|
-
|
|
175
|
+
getOAuthUrl(options: {
|
|
176
|
+
provider: string;
|
|
177
|
+
redirectUrl: string;
|
|
178
|
+
errorRedirectUrl: string;
|
|
179
|
+
afterCallbackRedirectUrl?: string;
|
|
180
|
+
codeChallenge: string;
|
|
181
|
+
state: string;
|
|
182
|
+
type: "authenticate" | "link";
|
|
183
|
+
providerScope?: string;
|
|
184
|
+
} & ({
|
|
185
|
+
type: "authenticate";
|
|
186
|
+
} | {
|
|
187
|
+
type: "link";
|
|
188
|
+
session: InternalSession;
|
|
189
|
+
})): Promise<string>;
|
|
190
|
+
callOAuthCallback(options: {
|
|
191
|
+
oauthParams: URLSearchParams;
|
|
192
|
+
redirectUri: string;
|
|
193
|
+
codeVerifier: string;
|
|
194
|
+
state: string;
|
|
195
|
+
}): Promise<{
|
|
178
196
|
newUser: boolean;
|
|
197
|
+
afterCallbackRedirectUrl?: string;
|
|
179
198
|
accessToken: string;
|
|
180
199
|
refreshToken: string;
|
|
181
200
|
}>;
|
|
@@ -193,6 +212,9 @@ export declare class StackClientInterface {
|
|
|
193
212
|
createProject(project: ProjectUpdateOptions & {
|
|
194
213
|
displayName: string;
|
|
195
214
|
}, session: InternalSession): Promise<ProjectJson>;
|
|
215
|
+
getAccessToken(provider: string, scope: string, session: InternalSession): Promise<{
|
|
216
|
+
accessToken: string;
|
|
217
|
+
}>;
|
|
196
218
|
}
|
|
197
219
|
export declare function getProductionModeErrors(project: ProjectJson): ProductionModeError[];
|
|
198
220
|
export {};
|
|
@@ -66,11 +66,6 @@ export class StackClientInterface {
|
|
|
66
66
|
const body = await response.data.text();
|
|
67
67
|
throw new Error(`Failed to send refresh token request: ${response.status} ${body}`);
|
|
68
68
|
}
|
|
69
|
-
let challenges;
|
|
70
|
-
if ((challenges = oauth.parseWwwAuthenticateChallenges(response.data))) {
|
|
71
|
-
// TODO Handle WWW-Authenticate Challenges as needed
|
|
72
|
-
throw new StackAssertionError("OAuth WWW-Authenticate challenge not implemented", { challenges });
|
|
73
|
-
}
|
|
74
69
|
const result = await oauth.processRefreshTokenResponse(as, client, response.data);
|
|
75
70
|
if (oauth.isOAuth2Error(result)) {
|
|
76
71
|
// TODO Handle OAuth 2.0 response body error
|
|
@@ -128,28 +123,28 @@ export class StackClientInterface {
|
|
|
128
123
|
* However, Cloudflare Workers don't actually support `credentials`, so we only set it
|
|
129
124
|
* if Cloudflare-exclusive globals are not detected. https://github.com/cloudflare/workers-sdk/issues/2514
|
|
130
125
|
*/
|
|
131
|
-
..."WebSocketPair" in globalVar ? {} : {
|
|
126
|
+
...("WebSocketPair" in globalVar ? {} : {
|
|
132
127
|
credentials: "omit",
|
|
133
|
-
},
|
|
128
|
+
}),
|
|
134
129
|
...options,
|
|
135
130
|
headers: {
|
|
136
131
|
"X-Stack-Override-Error-Status": "true",
|
|
137
132
|
"X-Stack-Project-Id": this.projectId,
|
|
138
133
|
"X-Stack-Request-Type": requestType,
|
|
139
134
|
"X-Stack-Client-Version": this.options.clientVersion,
|
|
140
|
-
...tokenObj ? {
|
|
135
|
+
...(tokenObj ? {
|
|
141
136
|
"Authorization": "StackSession " + tokenObj.accessToken.token,
|
|
142
137
|
"X-Stack-Access-Token": tokenObj.accessToken.token,
|
|
143
|
-
} : {},
|
|
144
|
-
...tokenObj?.refreshToken ? {
|
|
138
|
+
} : {}),
|
|
139
|
+
...(tokenObj?.refreshToken ? {
|
|
145
140
|
"X-Stack-Refresh-Token": tokenObj.refreshToken.token,
|
|
146
|
-
} : {},
|
|
147
|
-
...'publishableClientKey' in this.options ? {
|
|
141
|
+
} : {}),
|
|
142
|
+
...('publishableClientKey' in this.options ? {
|
|
148
143
|
"X-Stack-Publishable-Client-Key": this.options.publishableClientKey,
|
|
149
|
-
} : {},
|
|
150
|
-
...adminTokenObj ? {
|
|
144
|
+
} : {}),
|
|
145
|
+
...(adminTokenObj ? {
|
|
151
146
|
"X-Stack-Admin-Access-Token": adminTokenObj.accessToken.token,
|
|
152
|
-
} : {},
|
|
147
|
+
} : {}),
|
|
153
148
|
/**
|
|
154
149
|
* Next.js until v15 would cache fetch requests by default, and forcefully disabling it was nearly impossible.
|
|
155
150
|
*
|
|
@@ -164,9 +159,9 @@ export class StackClientInterface {
|
|
|
164
159
|
/**
|
|
165
160
|
* Cloudflare Workers does not support cache, so don't pass it there
|
|
166
161
|
*/
|
|
167
|
-
..."WebSocketPair" in globalVar ? {} : {
|
|
162
|
+
...("WebSocketPair" in globalVar ? {} : {
|
|
168
163
|
cache: "no-store",
|
|
169
|
-
},
|
|
164
|
+
}),
|
|
170
165
|
};
|
|
171
166
|
let rawRes;
|
|
172
167
|
try {
|
|
@@ -386,8 +381,8 @@ export class StackClientInterface {
|
|
|
386
381
|
newUser: result.newUser,
|
|
387
382
|
};
|
|
388
383
|
}
|
|
389
|
-
async getOAuthUrl(
|
|
390
|
-
const updatedRedirectUrl = new URL(redirectUrl);
|
|
384
|
+
async getOAuthUrl(options) {
|
|
385
|
+
const updatedRedirectUrl = new URL(options.redirectUrl);
|
|
391
386
|
for (const key of ["code", "state"]) {
|
|
392
387
|
if (updatedRedirectUrl.searchParams.has(key)) {
|
|
393
388
|
console.warn("Redirect URL already contains " + key + " parameter, removing it as it will be overwritten by the OAuth callback");
|
|
@@ -398,19 +393,31 @@ export class StackClientInterface {
|
|
|
398
393
|
// TODO fix
|
|
399
394
|
throw new Error("Admin session token is currently not supported for OAuth");
|
|
400
395
|
}
|
|
401
|
-
const url = new URL(this.getApiUrl() + "/auth/authorize/" + provider.toLowerCase());
|
|
396
|
+
const url = new URL(this.getApiUrl() + "/auth/authorize/" + options.provider.toLowerCase());
|
|
402
397
|
url.searchParams.set("client_id", this.projectId);
|
|
403
398
|
url.searchParams.set("client_secret", this.options.publishableClientKey);
|
|
404
399
|
url.searchParams.set("redirect_uri", updatedRedirectUrl.toString());
|
|
405
400
|
url.searchParams.set("scope", "openid");
|
|
406
|
-
url.searchParams.set("state", state);
|
|
401
|
+
url.searchParams.set("state", options.state);
|
|
407
402
|
url.searchParams.set("grant_type", "authorization_code");
|
|
408
|
-
url.searchParams.set("code_challenge", codeChallenge);
|
|
403
|
+
url.searchParams.set("code_challenge", options.codeChallenge);
|
|
409
404
|
url.searchParams.set("code_challenge_method", "S256");
|
|
410
405
|
url.searchParams.set("response_type", "code");
|
|
406
|
+
url.searchParams.set("type", options.type);
|
|
407
|
+
url.searchParams.set("errorRedirectUrl", options.errorRedirectUrl);
|
|
408
|
+
if (options.afterCallbackRedirectUrl) {
|
|
409
|
+
url.searchParams.set("afterCallbackRedirectUrl", options.afterCallbackRedirectUrl);
|
|
410
|
+
}
|
|
411
|
+
if (options.type === "link") {
|
|
412
|
+
const tokens = await options.session.getPotentiallyExpiredTokens();
|
|
413
|
+
url.searchParams.set("token", tokens?.accessToken.token || "");
|
|
414
|
+
if (options.providerScope) {
|
|
415
|
+
url.searchParams.set("providerScope", options.providerScope);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
411
418
|
return url.toString();
|
|
412
419
|
}
|
|
413
|
-
async callOAuthCallback(
|
|
420
|
+
async callOAuthCallback(options) {
|
|
414
421
|
if (!('publishableClientKey' in this.options)) {
|
|
415
422
|
// TODO fix
|
|
416
423
|
throw new Error("Admin session token is currently not supported for OAuth");
|
|
@@ -425,16 +432,11 @@ export class StackClientInterface {
|
|
|
425
432
|
client_secret: this.options.publishableClientKey,
|
|
426
433
|
token_endpoint_auth_method: 'client_secret_basic',
|
|
427
434
|
};
|
|
428
|
-
const params = oauth.validateAuthResponse(as, client, oauthParams, state);
|
|
435
|
+
const params = oauth.validateAuthResponse(as, client, options.oauthParams, options.state);
|
|
429
436
|
if (oauth.isOAuth2Error(params)) {
|
|
430
437
|
throw new StackAssertionError("Error validating outer OAuth response", { params }); // Handle OAuth 2.0 redirect error
|
|
431
438
|
}
|
|
432
|
-
const response = await oauth.authorizationCodeGrantRequest(as, client, params, redirectUri, codeVerifier);
|
|
433
|
-
let challenges;
|
|
434
|
-
if ((challenges = oauth.parseWwwAuthenticateChallenges(response))) {
|
|
435
|
-
// TODO Handle WWW-Authenticate Challenges as needed
|
|
436
|
-
throw new StackAssertionError("Outer OAuth WWW-Authenticate challenge not implemented", { challenges });
|
|
437
|
-
}
|
|
439
|
+
const response = await oauth.authorizationCodeGrantRequest(as, client, params, options.redirectUri, options.codeVerifier);
|
|
438
440
|
const result = await oauth.processAuthorizationCodeOAuth2Response(as, client, response);
|
|
439
441
|
if (oauth.isOAuth2Error(result)) {
|
|
440
442
|
// TODO Handle OAuth 2.0 response body error
|
|
@@ -442,6 +444,7 @@ export class StackClientInterface {
|
|
|
442
444
|
}
|
|
443
445
|
return {
|
|
444
446
|
newUser: result.newUser,
|
|
447
|
+
afterCallbackRedirectUrl: result.afterCallbackRedirectUrl,
|
|
445
448
|
accessToken: result.access_token,
|
|
446
449
|
refreshToken: result.refresh_token ?? throwErr("Refresh token not found in outer OAuth response"),
|
|
447
450
|
};
|
|
@@ -523,6 +526,19 @@ export class StackClientInterface {
|
|
|
523
526
|
const json = await fetchResponse.json();
|
|
524
527
|
return json;
|
|
525
528
|
}
|
|
529
|
+
async getAccessToken(provider, scope, session) {
|
|
530
|
+
const response = await this.sendClientRequest(`/auth/access-token/${provider}`, {
|
|
531
|
+
method: "POST",
|
|
532
|
+
headers: {
|
|
533
|
+
"content-type": "application/json",
|
|
534
|
+
},
|
|
535
|
+
body: JSON.stringify({ scope }),
|
|
536
|
+
}, session);
|
|
537
|
+
const json = await response.json();
|
|
538
|
+
return {
|
|
539
|
+
accessToken: json.accessToken,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
526
542
|
}
|
|
527
543
|
export function getProductionModeErrors(project) {
|
|
528
544
|
const errors = [];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CrudTypeOf } from "../../crud";
|
|
2
|
+
import * as yup from "yup";
|
|
3
|
+
export declare const accessTokenReadSchema: yup.ObjectSchema<{
|
|
4
|
+
accessToken: string;
|
|
5
|
+
}, yup.AnyObject, {
|
|
6
|
+
accessToken: undefined;
|
|
7
|
+
}, "">;
|
|
8
|
+
export declare const accessTokenCreateSchema: yup.ObjectSchema<{
|
|
9
|
+
scope: string | undefined;
|
|
10
|
+
}, yup.AnyObject, {
|
|
11
|
+
scope: undefined;
|
|
12
|
+
}, "">;
|
|
13
|
+
export declare const accessTokenCrud: {
|
|
14
|
+
client: {
|
|
15
|
+
createSchema: yup.ObjectSchema<{
|
|
16
|
+
scope: string | undefined;
|
|
17
|
+
}, yup.AnyObject, {
|
|
18
|
+
scope: undefined;
|
|
19
|
+
}, "">;
|
|
20
|
+
readSchema: yup.ObjectSchema<{
|
|
21
|
+
accessToken: string;
|
|
22
|
+
}, yup.AnyObject, {
|
|
23
|
+
accessToken: undefined;
|
|
24
|
+
}, "">;
|
|
25
|
+
updateSchema: undefined;
|
|
26
|
+
deleteSchema: undefined;
|
|
27
|
+
};
|
|
28
|
+
server: {
|
|
29
|
+
createSchema: yup.ObjectSchema<{
|
|
30
|
+
scope: string | undefined;
|
|
31
|
+
}, yup.AnyObject, {
|
|
32
|
+
scope: undefined;
|
|
33
|
+
}, "">;
|
|
34
|
+
readSchema: yup.ObjectSchema<{
|
|
35
|
+
accessToken: string;
|
|
36
|
+
}, yup.AnyObject, {
|
|
37
|
+
accessToken: undefined;
|
|
38
|
+
}, "">;
|
|
39
|
+
updateSchema: undefined;
|
|
40
|
+
deleteSchema: undefined;
|
|
41
|
+
};
|
|
42
|
+
admin: {
|
|
43
|
+
createSchema: yup.ObjectSchema<{
|
|
44
|
+
scope: string | undefined;
|
|
45
|
+
}, yup.AnyObject, {
|
|
46
|
+
scope: undefined;
|
|
47
|
+
}, "">;
|
|
48
|
+
readSchema: yup.ObjectSchema<{
|
|
49
|
+
accessToken: string;
|
|
50
|
+
}, yup.AnyObject, {
|
|
51
|
+
accessToken: undefined;
|
|
52
|
+
}, "">;
|
|
53
|
+
updateSchema: undefined;
|
|
54
|
+
deleteSchema: undefined;
|
|
55
|
+
};
|
|
56
|
+
hasCreate: boolean;
|
|
57
|
+
hasRead: boolean;
|
|
58
|
+
hasUpdate: boolean;
|
|
59
|
+
hasDelete: boolean;
|
|
60
|
+
};
|
|
61
|
+
export type AccessTokenCrud = CrudTypeOf<typeof accessTokenCrud>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCrud } from "../../crud";
|
|
2
|
+
import * as yup from "yup";
|
|
3
|
+
export const accessTokenReadSchema = yup.object({
|
|
4
|
+
accessToken: yup.string().required(),
|
|
5
|
+
}).required();
|
|
6
|
+
export const accessTokenCreateSchema = yup.object({
|
|
7
|
+
scope: yup.string().optional(),
|
|
8
|
+
}).required();
|
|
9
|
+
export const accessTokenCrud = createCrud({
|
|
10
|
+
clientReadSchema: accessTokenReadSchema,
|
|
11
|
+
clientCreateSchema: accessTokenCreateSchema,
|
|
12
|
+
});
|
package/dist/known-errors.d.ts
CHANGED
|
@@ -236,11 +236,32 @@ export declare const KnownErrors: {
|
|
|
236
236
|
PermissionScopeMismatch: KnownErrorConstructor<KnownError & KnownErrorBrand<"PERMISSION_SCOPE_MISMATCH">, [permissionId: string, permissionScope: PermissionDefinitionScopeJson, testScope: PermissionDefinitionScopeJson]> & {
|
|
237
237
|
errorCode: "PERMISSION_SCOPE_MISMATCH";
|
|
238
238
|
};
|
|
239
|
+
UserNotInTeam: KnownErrorConstructor<KnownError & KnownErrorBrand<"USER_NOT_IN_TEAM">, [userId: string, teamId: string]> & {
|
|
240
|
+
errorCode: "USER_NOT_IN_TEAM";
|
|
241
|
+
};
|
|
239
242
|
TeamNotFound: KnownErrorConstructor<KnownError & KnownErrorBrand<"TEAM_NOT_FOUND">, [teamId: string]> & {
|
|
240
243
|
errorCode: "TEAM_NOT_FOUND";
|
|
241
244
|
};
|
|
242
245
|
EmailTemplateAlreadyExists: KnownErrorConstructor<KnownError & KnownErrorBrand<"EMAIL_TEMPLATE_ALREADY_EXISTS">, []> & {
|
|
243
246
|
errorCode: "EMAIL_TEMPLATE_ALREADY_EXISTS";
|
|
244
247
|
};
|
|
248
|
+
OAuthConnectionNotConnectedToUser: KnownErrorConstructor<KnownError & KnownErrorBrand<"OAUTH_CONNECTION_NOT_CONNECTED_TO_USER">, []> & {
|
|
249
|
+
errorCode: "OAUTH_CONNECTION_NOT_CONNECTED_TO_USER";
|
|
250
|
+
};
|
|
251
|
+
OAuthConnectionAlreadyConnectedToAnotherUser: KnownErrorConstructor<KnownError & KnownErrorBrand<"OAUTH_CONNECTION_ALREADY_CONNECTED_TO_ANOTHER_USER">, []> & {
|
|
252
|
+
errorCode: "OAUTH_CONNECTION_ALREADY_CONNECTED_TO_ANOTHER_USER";
|
|
253
|
+
};
|
|
254
|
+
OAuthConnectionDoesNotHaveRequiredScope: KnownErrorConstructor<KnownError & KnownErrorBrand<"OAUTH_CONNECTION_DOES_NOT_HAVE_REQUIRED_SCOPE">, []> & {
|
|
255
|
+
errorCode: "OAUTH_CONNECTION_DOES_NOT_HAVE_REQUIRED_SCOPE";
|
|
256
|
+
};
|
|
257
|
+
OAuthExtraScopeNotAvailableWithSharedOAuthKeys: KnownErrorConstructor<KnownError & KnownErrorBrand<"OAUTH_EXTRA_SCOPE_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS">, []> & {
|
|
258
|
+
errorCode: "OAUTH_EXTRA_SCOPE_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS";
|
|
259
|
+
};
|
|
260
|
+
OAuthAccessTokenNotAvailableWithSharedOAuthKeys: KnownErrorConstructor<KnownError & KnownErrorBrand<"OAUTH_ACCESS_TOKEN_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS">, []> & {
|
|
261
|
+
errorCode: "OAUTH_ACCESS_TOKEN_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS";
|
|
262
|
+
};
|
|
263
|
+
UserAlreadyConnectedToAnotherOAuthConnection: KnownErrorConstructor<KnownError & KnownErrorBrand<"USER_ALREADY_CONNECTED_TO_ANOTHER_OAUTH_CONNECTION">, []> & {
|
|
264
|
+
errorCode: "USER_ALREADY_CONNECTED_TO_ANOTHER_OAUTH_CONNECTION";
|
|
265
|
+
};
|
|
245
266
|
};
|
|
246
267
|
export {};
|
package/dist/known-errors.js
CHANGED
|
@@ -341,6 +341,30 @@ const EmailTemplateAlreadyExists = createKnownErrorConstructor(KnownError, "EMAI
|
|
|
341
341
|
400,
|
|
342
342
|
"Email template already exists.",
|
|
343
343
|
], () => []);
|
|
344
|
+
const OAuthConnectionNotConnectedToUser = createKnownErrorConstructor(KnownError, "OAUTH_CONNECTION_NOT_CONNECTED_TO_USER", () => [
|
|
345
|
+
400,
|
|
346
|
+
"The OAuth connection is not connected to any user.",
|
|
347
|
+
], () => []);
|
|
348
|
+
const OAuthConnectionAlreadyConnectedToAnotherUser = createKnownErrorConstructor(KnownError, "OAUTH_CONNECTION_ALREADY_CONNECTED_TO_ANOTHER_USER", () => [
|
|
349
|
+
400,
|
|
350
|
+
"The OAuth connection is already connected to another user.",
|
|
351
|
+
], () => []);
|
|
352
|
+
const OAuthConnectionDoesNotHaveRequiredScope = createKnownErrorConstructor(KnownError, "OAUTH_CONNECTION_DOES_NOT_HAVE_REQUIRED_SCOPE", () => [
|
|
353
|
+
400,
|
|
354
|
+
"The OAuth connection does not have the required scope.",
|
|
355
|
+
], () => []);
|
|
356
|
+
const OAuthExtraScopeNotAvailableWithSharedOAuthKeys = createKnownErrorConstructor(KnownError, "OAUTH_EXTRA_SCOPE_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS", () => [
|
|
357
|
+
400,
|
|
358
|
+
"Extra scopes are not available with shared OAuth keys. Please add your own OAuth keys on the Stack dashboard to use extra scopes.",
|
|
359
|
+
], () => []);
|
|
360
|
+
const OAuthAccessTokenNotAvailableWithSharedOAuthKeys = createKnownErrorConstructor(KnownError, "OAUTH_ACCESS_TOKEN_NOT_AVAILABLE_WITH_SHARED_OAUTH_KEYS", () => [
|
|
361
|
+
400,
|
|
362
|
+
"Access tokens are not available with shared OAuth keys. Please add your own OAuth keys on the Stack dashboard to use access tokens.",
|
|
363
|
+
], () => []);
|
|
364
|
+
const UserAlreadyConnectedToAnotherOAuthConnection = createKnownErrorConstructor(KnownError, "USER_ALREADY_CONNECTED_TO_ANOTHER_OAUTH_CONNECTION", () => [
|
|
365
|
+
400,
|
|
366
|
+
"The user is already connected to another OAuth account. Did you maybe selected the wrong account?",
|
|
367
|
+
], () => []);
|
|
344
368
|
export const KnownErrors = {
|
|
345
369
|
UnsupportedError,
|
|
346
370
|
BodyParsingError,
|
|
@@ -406,8 +430,15 @@ export const KnownErrors = {
|
|
|
406
430
|
EmailAlreadyVerified,
|
|
407
431
|
PermissionNotFound,
|
|
408
432
|
PermissionScopeMismatch,
|
|
433
|
+
UserNotInTeam,
|
|
409
434
|
TeamNotFound,
|
|
410
435
|
EmailTemplateAlreadyExists,
|
|
436
|
+
OAuthConnectionNotConnectedToUser,
|
|
437
|
+
OAuthConnectionAlreadyConnectedToAnotherUser,
|
|
438
|
+
OAuthConnectionDoesNotHaveRequiredScope,
|
|
439
|
+
OAuthExtraScopeNotAvailableWithSharedOAuthKeys,
|
|
440
|
+
OAuthAccessTokenNotAvailableWithSharedOAuthKeys,
|
|
441
|
+
UserAlreadyConnectedToAnotherOAuthConnection,
|
|
411
442
|
};
|
|
412
443
|
// ensure that all known error codes are unique
|
|
413
444
|
const knownErrorCodes = new Set();
|
package/dist/utils/react.js
CHANGED
|
@@ -32,7 +32,25 @@ export function suspend() {
|
|
|
32
32
|
export function suspendIfSsr(caller) {
|
|
33
33
|
if (!isBrowserLike()) {
|
|
34
34
|
const error = Object.assign(new Error(deindent `
|
|
35
|
-
${caller ?? "This code path"} attempted to display a loading indicator during SSR by falling back to the nearest Suspense boundary. If you see this error, it means no Suspense boundary was found, and no loading indicator could be displayed.
|
|
35
|
+
${caller ?? "This code path"} attempted to display a loading indicator during SSR by falling back to the nearest Suspense boundary. If you see this error, it means no Suspense boundary was found, and no loading indicator could be displayed.
|
|
36
|
+
|
|
37
|
+
This usually has one of three causes:
|
|
38
|
+
|
|
39
|
+
1. You are missing a loading.tsx file in your app directory. Fix it by adding a loading.tsx file in your app directory.
|
|
40
|
+
|
|
41
|
+
2. The component is rendered in the root (outermost) layout.tsx or template.tsx file. Next.js does not wrap those files in a Suspense boundary, even if there is a loading.tsx file in the same folder. To fix it, wrap your layout inside a route group like this:
|
|
42
|
+
|
|
43
|
+
- app
|
|
44
|
+
- layout.tsx // contains <html> and <body>, alongside providers and other components that don't need ${caller ?? "this code path"}
|
|
45
|
+
- loading.tsx // required for suspense
|
|
46
|
+
- (main)
|
|
47
|
+
- layout.tsx // contains the main layout of your app, like a sidebar or a header, and can use ${caller ?? "this code path"}
|
|
48
|
+
- route.tsx // your actual main page
|
|
49
|
+
- the rest of your app
|
|
50
|
+
|
|
51
|
+
For more information on this approach, see Next's documentation on route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups
|
|
52
|
+
|
|
53
|
+
3. You caught this error with try-catch or a custom error boundary. Fix this by rethrowing the error or not catching it in the first place.
|
|
36
54
|
|
|
37
55
|
See: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout
|
|
38
56
|
|
package/dist/utils/strings.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare function trimLines(s: string): string;
|
|
|
39
39
|
export declare function templateIdentity(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;
|
|
40
40
|
export declare function deindent(code: string): string;
|
|
41
41
|
export declare function deindent(strings: TemplateStringsArray | readonly string[], ...values: any[]): string;
|
|
42
|
+
export declare function extractScopes(scope: string, removeDuplicates?: boolean): string[];
|
|
43
|
+
export declare function mergeScopeStrings(...scopes: string[]): string;
|
|
42
44
|
export declare function nicify(value: unknown, { depth }?: {
|
|
43
45
|
depth?: number | undefined;
|
|
44
46
|
}): string;
|
package/dist/utils/strings.js
CHANGED
|
@@ -92,6 +92,16 @@ export function deindent(strings, ...values) {
|
|
|
92
92
|
});
|
|
93
93
|
return templateIdentity(deindentedStrings, ...indentedValues);
|
|
94
94
|
}
|
|
95
|
+
export function extractScopes(scope, removeDuplicates = true) {
|
|
96
|
+
const trimmedString = scope.trim();
|
|
97
|
+
const scopesArray = trimmedString.split(/\s+/);
|
|
98
|
+
const filtered = scopesArray.filter(scope => scope.length > 0);
|
|
99
|
+
return removeDuplicates ? [...new Set(filtered)] : filtered;
|
|
100
|
+
}
|
|
101
|
+
export function mergeScopeStrings(...scopes) {
|
|
102
|
+
const allScope = scopes.map((s) => extractScopes(s)).flat().join(" ");
|
|
103
|
+
return extractScopes(allScope).join(" ");
|
|
104
|
+
}
|
|
95
105
|
export function nicify(value, { depth = 5 } = {}) {
|
|
96
106
|
switch (typeof value) {
|
|
97
107
|
case "string":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stackframe/stack-shared",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.22",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"jose": "^5.2.2",
|
|
27
27
|
"oauth4webapi": "^2.10.3",
|
|
28
28
|
"uuid": "^9.0.1",
|
|
29
|
-
"@stackframe/stack-sc": "2.4.
|
|
29
|
+
"@stackframe/stack-sc": "2.4.22"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"rimraf": "^5.0.5",
|