@stackframe/stack-shared 2.4.14 → 2.4.21

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,29 @@
1
1
  # @stackframe/stack-shared
2
2
 
3
+ ## 2.4.21
4
+
5
+ ### Patch Changes
6
+
7
+ - Bugfixes
8
+ - Updated dependencies
9
+ - @stackframe/stack-sc@2.4.21
10
+
11
+ ## 2.4.20
12
+
13
+ ### Patch Changes
14
+
15
+ - Support multiple projects on the same domain
16
+ - Updated dependencies
17
+ - @stackframe/stack-sc@2.4.20
18
+
19
+ ## 2.4.19
20
+
21
+ ### Patch Changes
22
+
23
+ - Sync package versions
24
+ - Updated dependencies
25
+ - @stackframe/stack-sc@2.4.19
26
+
3
27
  ## 2.4.14
4
28
 
5
29
  ### Patch Changes
@@ -1,10 +1,10 @@
1
1
  import { ServerAuthApplicationOptions, StackServerInterface } from "./serverInterface";
2
2
  import { EmailConfigJson, ProjectJson, SharedProvider, StandardProvider } from "./clientInterface";
3
- import { Session } from "../sessions";
3
+ import { InternalSession } from "../sessions";
4
4
  export type AdminAuthApplicationOptions = Readonly<ServerAuthApplicationOptions & ({
5
5
  superSecretAdminKey: string;
6
6
  } | {
7
- projectOwnerSession: Session;
7
+ projectOwnerSession: InternalSession;
8
8
  })>;
9
9
  export type OAuthProviderUpdateOptions = {
10
10
  id: string;
@@ -67,7 +67,7 @@ export type ApiKeySetCreateOptions = {
67
67
  export declare class StackAdminInterface extends StackServerInterface {
68
68
  readonly options: AdminAuthApplicationOptions;
69
69
  constructor(options: AdminAuthApplicationOptions);
70
- protected sendAdminRequest(path: string, options: RequestInit, session: Session | null, requestType?: "admin"): Promise<Response & {
70
+ protected sendAdminRequest(path: string, options: RequestInit, session: InternalSession | null, requestType?: "admin"): Promise<Response & {
71
71
  usedTokens: {
72
72
  accessToken: import("../sessions").AccessToken;
73
73
  refreshToken: import("../sessions").RefreshToken | null;
@@ -80,5 +80,5 @@ export declare class StackAdminInterface extends StackServerInterface {
80
80
  createApiKeySet(options: ApiKeySetCreateOptions): Promise<ApiKeySetFirstViewJson>;
81
81
  listApiKeySets(): Promise<ApiKeySetJson[]>;
82
82
  revokeApiKeySetById(id: string): Promise<void>;
83
- getApiKeySet(id: string, session: Session): Promise<ApiKeySetJson>;
83
+ getApiKeySet(id: string, session: InternalSession): Promise<ApiKeySetJson>;
84
84
  }
@@ -2,7 +2,7 @@ import { Result } from "../utils/results";
2
2
  import { ReadonlyJson } from '../utils/json';
3
3
  import { KnownErrors } from '../known-errors';
4
4
  import { ProjectUpdateOptions } from './adminInterface';
5
- import { AccessToken, RefreshToken, Session } from '../sessions';
5
+ import { AccessToken, RefreshToken, InternalSession } from '../sessions';
6
6
  type UserCustomizableJson = {
7
7
  displayName: string | null;
8
8
  clientMetadata: ReadonlyJson;
@@ -43,7 +43,7 @@ export type ClientInterfaceOptions = {
43
43
  } & ({
44
44
  publishableClientKey: string;
45
45
  } | {
46
- projectOwnerSession: Session;
46
+ projectOwnerSession: InternalSession;
47
47
  });
48
48
  export type SharedProvider = "shared-github" | "shared-google" | "shared-facebook" | "shared-microsoft" | "shared-spotify";
49
49
  export declare const sharedProviders: readonly ["shared-github", "shared-google", "shared-facebook", "shared-microsoft", "shared-spotify"];
@@ -129,14 +129,14 @@ export declare class StackClientInterface {
129
129
  get projectId(): string;
130
130
  getApiUrl(): string;
131
131
  fetchNewAccessToken(refreshToken: RefreshToken): Promise<AccessToken | null>;
132
- protected sendClientRequest(path: string, requestOptions: RequestInit, session: Session | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
132
+ protected sendClientRequest(path: string, requestOptions: RequestInit, session: InternalSession | null, requestType?: "client" | "server" | "admin"): Promise<Response & {
133
133
  usedTokens: {
134
134
  accessToken: AccessToken;
135
135
  refreshToken: RefreshToken | null;
136
136
  } | null;
137
137
  }>;
138
- createSession(options: Omit<ConstructorParameters<typeof Session>[0], "refreshAccessTokenCallback">): Session;
139
- protected sendClientRequestAndCatchKnownError<E extends typeof KnownErrors[keyof KnownErrors]>(path: string, requestOptions: RequestInit, tokenStoreOrNull: Session | null, errorsToCatch: readonly E[]): Promise<Result<Response & {
138
+ createSession(options: Omit<ConstructorParameters<typeof InternalSession>[0], "refreshAccessTokenCallback">): InternalSession;
139
+ protected sendClientRequestAndCatchKnownError<E extends typeof KnownErrors[keyof KnownErrors]>(path: string, requestOptions: RequestInit, tokenStoreOrNull: InternalSession | null, errorsToCatch: readonly E[]): Promise<Result<Response & {
140
140
  usedTokens: {
141
141
  accessToken: AccessToken;
142
142
  refreshToken: RefreshToken | null;
@@ -145,7 +145,7 @@ export declare class StackClientInterface {
145
145
  private sendClientRequestInner;
146
146
  private _processResponse;
147
147
  sendForgotPasswordEmail(email: string, redirectUrl: string): Promise<KnownErrors["UserNotFound"] | undefined>;
148
- sendVerificationEmail(emailVerificationRedirectUrl: string, session: Session): Promise<KnownErrors["EmailAlreadyVerified"] | undefined>;
148
+ sendVerificationEmail(emailVerificationRedirectUrl: string, session: InternalSession): Promise<KnownErrors["EmailAlreadyVerified"] | undefined>;
149
149
  sendMagicLinkEmail(email: string, redirectUrl: string): Promise<KnownErrors["RedirectUrlNotWhitelisted"] | undefined>;
150
150
  resetPassword(options: {
151
151
  code: string;
@@ -157,18 +157,18 @@ export declare class StackClientInterface {
157
157
  updatePassword(options: {
158
158
  oldPassword: string;
159
159
  newPassword: string;
160
- }, session: Session): Promise<KnownErrors["PasswordMismatch"] | KnownErrors["PasswordRequirementsNotMet"] | undefined>;
160
+ }, session: InternalSession): Promise<KnownErrors["PasswordMismatch"] | KnownErrors["PasswordRequirementsNotMet"] | undefined>;
161
161
  verifyPasswordResetCode(code: string): Promise<KnownErrors["PasswordResetCodeError"] | undefined>;
162
162
  verifyEmail(code: string): Promise<KnownErrors["EmailVerificationError"] | undefined>;
163
- signInWithCredential(email: string, password: string, session: Session): Promise<KnownErrors["EmailPasswordMismatch"] | {
163
+ signInWithCredential(email: string, password: string, session: InternalSession): Promise<KnownErrors["EmailPasswordMismatch"] | {
164
164
  accessToken: string;
165
165
  refreshToken: string;
166
166
  }>;
167
- signUpWithCredential(email: string, password: string, emailVerificationRedirectUrl: string, session: Session): Promise<KnownErrors["UserEmailAlreadyExists"] | KnownErrors["PasswordRequirementsNotMet"] | {
167
+ signUpWithCredential(email: string, password: string, emailVerificationRedirectUrl: string, session: InternalSession): Promise<KnownErrors["UserEmailAlreadyExists"] | KnownErrors["PasswordRequirementsNotMet"] | {
168
168
  accessToken: string;
169
169
  refreshToken: string;
170
170
  }>;
171
- signInWithMagicLink(code: string, session: Session): Promise<KnownErrors["MagicLinkError"] | {
171
+ signInWithMagicLink(code: string, session: InternalSession): Promise<KnownErrors["MagicLinkError"] | {
172
172
  newUser: boolean;
173
173
  accessToken: string;
174
174
  refreshToken: string;
@@ -179,20 +179,20 @@ export declare class StackClientInterface {
179
179
  accessToken: string;
180
180
  refreshToken: string;
181
181
  }>;
182
- signOut(session: Session): Promise<void>;
183
- getClientUserByToken(tokenStore: Session): Promise<Result<UserJson>>;
182
+ signOut(session: InternalSession): Promise<void>;
183
+ getClientUserByToken(tokenStore: InternalSession): Promise<Result<UserJson>>;
184
184
  listClientUserTeamPermissions(options: {
185
185
  teamId: string;
186
186
  type: 'global' | 'team';
187
187
  direct: boolean;
188
- }, session: Session): Promise<PermissionDefinitionJson[]>;
189
- listClientUserTeams(session: Session): Promise<TeamJson[]>;
188
+ }, session: InternalSession): Promise<PermissionDefinitionJson[]>;
189
+ listClientUserTeams(session: InternalSession): Promise<TeamJson[]>;
190
190
  getClientProject(): Promise<Result<ClientProjectJson>>;
191
- setClientUserCustomizableData(update: UserUpdateJson, session: Session): Promise<void>;
192
- listProjects(session: Session): Promise<ProjectJson[]>;
191
+ setClientUserCustomizableData(update: UserUpdateJson, session: InternalSession): Promise<void>;
192
+ listProjects(session: InternalSession): Promise<ProjectJson[]>;
193
193
  createProject(project: ProjectUpdateOptions & {
194
194
  displayName: string;
195
- }, session: Session): Promise<ProjectJson>;
195
+ }, session: InternalSession): Promise<ProjectJson>;
196
196
  }
197
197
  export declare function getProductionModeErrors(project: ProjectJson): ProductionModeError[];
198
198
  export {};
@@ -4,7 +4,7 @@ import { KnownError, KnownErrors } from '../known-errors';
4
4
  import { StackAssertionError, captureError, throwErr } from '../utils/errors';
5
5
  import { cookies } from '@stackframe/stack-sc';
6
6
  import { generateSecureRandomString } from '../utils/crypto';
7
- import { AccessToken, Session } from '../sessions';
7
+ import { AccessToken, InternalSession } from '../sessions';
8
8
  import { globalVar } from '../utils/globals';
9
9
  export const sharedProviders = [
10
10
  "shared-github",
@@ -88,7 +88,7 @@ export class StackClientInterface {
88
88
  return await Result.orThrowAsync(Result.retry(() => this.sendClientRequestInner(path, requestOptions, session, requestType), 5, { exponentialDelayBase: 1000 }));
89
89
  }
90
90
  createSession(options) {
91
- const session = new Session({
91
+ const session = new InternalSession({
92
92
  refreshAccessTokenCallback: async (refreshToken) => await this.fetchNewAccessToken(refreshToken),
93
93
  ...options,
94
94
  });
@@ -466,7 +466,7 @@ export class StackClientInterface {
466
466
  await res.json();
467
467
  }
468
468
  }
469
- session.invalidate();
469
+ session.markInvalid();
470
470
  }
471
471
  async getClientUserByToken(tokenStore) {
472
472
  const response = await this.sendClientRequest("/current-user", {}, tokenStore);
@@ -2,7 +2,7 @@ import { ClientInterfaceOptions, UserJson, StackClientInterface, OrglikeJson, Us
2
2
  import { Result } from "../utils/results";
3
3
  import { ReadonlyJson } from "../utils/json";
4
4
  import { EmailTemplateCrud, ListEmailTemplatesCrud } from "./crud/email-templates";
5
- import { Session } from "../sessions";
5
+ import { InternalSession } from "../sessions";
6
6
  export type ServerUserJson = UserJson & {
7
7
  serverMetadata: ReadonlyJson;
8
8
  };
@@ -29,27 +29,27 @@ export type ServerPermissionDefinitionJson = PermissionDefinitionJson & ServerPe
29
29
  export type ServerAuthApplicationOptions = (ClientInterfaceOptions & ({
30
30
  readonly secretServerKey: string;
31
31
  } | {
32
- readonly projectOwnerSession: Session;
32
+ readonly projectOwnerSession: InternalSession;
33
33
  }));
34
34
  export declare const emailTemplateTypes: readonly ["EMAIL_VERIFICATION", "PASSWORD_RESET", "MAGIC_LINK"];
35
35
  export type EmailTemplateType = typeof emailTemplateTypes[number];
36
36
  export declare class StackServerInterface extends StackClientInterface {
37
37
  options: ServerAuthApplicationOptions;
38
38
  constructor(options: ServerAuthApplicationOptions);
39
- protected sendServerRequest(path: string, options: RequestInit, session: Session | null, requestType?: "server" | "admin"): Promise<Response & {
39
+ protected sendServerRequest(path: string, options: RequestInit, session: InternalSession | null, requestType?: "server" | "admin"): Promise<Response & {
40
40
  usedTokens: {
41
41
  accessToken: import("../sessions").AccessToken;
42
42
  refreshToken: import("../sessions").RefreshToken | null;
43
43
  } | null;
44
44
  }>;
45
- getServerUserByToken(session: Session): Promise<Result<ServerUserJson>>;
45
+ getServerUserByToken(session: InternalSession): Promise<Result<ServerUserJson>>;
46
46
  getServerUserById(userId: string): Promise<Result<ServerUserJson>>;
47
47
  listServerUserTeamPermissions(options: {
48
48
  teamId: string;
49
49
  type: 'global' | 'team';
50
50
  direct: boolean;
51
- }, session: Session): Promise<ServerPermissionDefinitionJson[]>;
52
- listServerUserTeams(session: Session): Promise<ServerTeamJson[]>;
51
+ }, session: InternalSession): Promise<ServerPermissionDefinitionJson[]>;
52
+ listServerUserTeams(session: InternalSession): Promise<ServerTeamJson[]>;
53
53
  listPermissionDefinitions(): Promise<ServerPermissionDefinitionJson[]>;
54
54
  createPermissionDefinition(data: ServerPermissionDefinitionCustomizableJson): Promise<ServerPermissionDefinitionJson>;
55
55
  updatePermissionDefinition(permissionId: string, data: Partial<ServerPermissionDefinitionCustomizableJson>): Promise<void>;
@@ -7,18 +7,18 @@ export declare class RefreshToken {
7
7
  constructor(token: string);
8
8
  }
9
9
  /**
10
- * A session represents a user's session, which may or may not be valid. It may contain an access token, a refresh token, or both.
10
+ * An InternalSession represents a user's session, which may or may not be valid. It may contain an access token, a refresh token, or both.
11
11
  *
12
- * A session never changes which user or session it belongs to, but the tokens may change over time.
12
+ * A session never changes which user or session it belongs to, but the tokens in it may change over time.
13
13
  */
14
- export declare class Session {
14
+ export declare class InternalSession {
15
15
  private readonly _options;
16
16
  /**
17
17
  * Each session has a session key that depends on the tokens inside. If the session has a refresh token, the session key depends only on the refresh token. If the session does not have a refresh token, the session key depends only on the access token.
18
18
  *
19
19
  * Multiple Session objects may have the same session key, which implies that they represent the same session by the same user. Furthermore, a session's key never changes over the lifetime of a session object.
20
20
  *
21
- * This makes session keys useful for caching and indexing sessions.
21
+ * This is useful for caching and indexing sessions.
22
22
  */
23
23
  readonly sessionKey: string;
24
24
  /**
@@ -42,15 +42,34 @@ export declare class Session {
42
42
  refreshToken: string | null;
43
43
  accessToken?: string | null;
44
44
  }): string;
45
- invalidate(): void;
45
+ /**
46
+ * Marks the session object as invalid, meaning that the refresh and access tokens can no longer be used.
47
+ */
48
+ markInvalid(): void;
46
49
  onInvalidate(callback: () => void): {
47
50
  unsubscribe: () => void;
48
51
  };
52
+ /**
53
+ * Returns the access token if it is found in the cache, fetching it otherwise.
54
+ *
55
+ * This is usually the function you want to call to get an access token. When using the access token, you should catch errors that occur if it expires, and call `markAccessTokenExpired` to mark the token as expired if so (after which a call to this function will always refetch the token).
56
+ *
57
+ * @returns null if the session is known to be invalid, cached tokens if they exist in the cache (which may or may not be valid still), or new tokens otherwise.
58
+ */
49
59
  getPotentiallyExpiredTokens(): Promise<{
50
60
  accessToken: AccessToken;
51
61
  refreshToken: RefreshToken | null;
52
62
  } | null>;
53
- getNewlyFetchedTokens(): Promise<{
63
+ /**
64
+ * Fetches new tokens that are, at the time of fetching, guaranteed to be valid.
65
+ *
66
+ * The newly generated tokens are shortlived, so it's good practice not to rely on their validity (if possible). However, this function is useful in some cases where you only want to pass access tokens to a service, and you want to make sure said access token has the longest possible lifetime.
67
+ *
68
+ * In most cases, you should prefer `getPotentiallyExpiredTokens` with a fallback to `markAccessTokenExpired` and a retry mechanism if the endpoint rejects the token.
69
+ *
70
+ * @returns null if the session is known to be invalid, or new tokens otherwise (which, at the time of fetching, are guaranteed to be valid).
71
+ */
72
+ fetchNewTokens(): Promise<{
54
73
  accessToken: AccessToken;
55
74
  refreshToken: RefreshToken | null;
56
75
  } | null>;
package/dist/sessions.js CHANGED
@@ -12,18 +12,18 @@ export class RefreshToken {
12
12
  }
13
13
  }
14
14
  /**
15
- * A session represents a user's session, which may or may not be valid. It may contain an access token, a refresh token, or both.
15
+ * An InternalSession represents a user's session, which may or may not be valid. It may contain an access token, a refresh token, or both.
16
16
  *
17
- * A session never changes which user or session it belongs to, but the tokens may change over time.
17
+ * A session never changes which user or session it belongs to, but the tokens in it may change over time.
18
18
  */
19
- export class Session {
19
+ export class InternalSession {
20
20
  _options;
21
21
  /**
22
22
  * Each session has a session key that depends on the tokens inside. If the session has a refresh token, the session key depends only on the refresh token. If the session does not have a refresh token, the session key depends only on the access token.
23
23
  *
24
24
  * Multiple Session objects may have the same session key, which implies that they represent the same session by the same user. Furthermore, a session's key never changes over the lifetime of a session object.
25
25
  *
26
- * This makes session keys useful for caching and indexing sessions.
26
+ * This is useful for caching and indexing sessions.
27
27
  */
28
28
  sessionKey;
29
29
  /**
@@ -42,7 +42,7 @@ export class Session {
42
42
  this._options = _options;
43
43
  this._accessToken = new Store(_options.accessToken ? new AccessToken(_options.accessToken) : null);
44
44
  this._refreshToken = _options.refreshToken ? new RefreshToken(_options.refreshToken) : null;
45
- this.sessionKey = Session.calculateSessionKey({ accessToken: _options.accessToken ?? null, refreshToken: _options.refreshToken });
45
+ this.sessionKey = InternalSession.calculateSessionKey({ accessToken: _options.accessToken ?? null, refreshToken: _options.refreshToken });
46
46
  }
47
47
  static calculateSessionKey(ofTokens) {
48
48
  if (ofTokens.refreshToken) {
@@ -55,18 +55,37 @@ export class Session {
55
55
  return "not-logged-in";
56
56
  }
57
57
  }
58
- invalidate() {
58
+ /**
59
+ * Marks the session object as invalid, meaning that the refresh and access tokens can no longer be used.
60
+ */
61
+ markInvalid() {
59
62
  this._accessToken.set(null);
60
63
  this._knownToBeInvalid.set(true);
61
64
  }
62
65
  onInvalidate(callback) {
63
66
  return this._knownToBeInvalid.onChange(() => callback());
64
67
  }
68
+ /**
69
+ * Returns the access token if it is found in the cache, fetching it otherwise.
70
+ *
71
+ * This is usually the function you want to call to get an access token. When using the access token, you should catch errors that occur if it expires, and call `markAccessTokenExpired` to mark the token as expired if so (after which a call to this function will always refetch the token).
72
+ *
73
+ * @returns null if the session is known to be invalid, cached tokens if they exist in the cache (which may or may not be valid still), or new tokens otherwise.
74
+ */
65
75
  async getPotentiallyExpiredTokens() {
66
76
  const accessToken = await this._getPotentiallyExpiredAccessToken();
67
77
  return accessToken ? { accessToken, refreshToken: this._refreshToken } : null;
68
78
  }
69
- async getNewlyFetchedTokens() {
79
+ /**
80
+ * Fetches new tokens that are, at the time of fetching, guaranteed to be valid.
81
+ *
82
+ * The newly generated tokens are shortlived, so it's good practice not to rely on their validity (if possible). However, this function is useful in some cases where you only want to pass access tokens to a service, and you want to make sure said access token has the longest possible lifetime.
83
+ *
84
+ * In most cases, you should prefer `getPotentiallyExpiredTokens` with a fallback to `markAccessTokenExpired` and a retry mechanism if the endpoint rejects the token.
85
+ *
86
+ * @returns null if the session is known to be invalid, or new tokens otherwise (which, at the time of fetching, are guaranteed to be valid).
87
+ */
88
+ async fetchNewTokens() {
70
89
  const accessToken = await this._getNewlyFetchedAccessToken();
71
90
  return accessToken ? { accessToken, refreshToken: this._refreshToken } : null;
72
91
  }
@@ -117,7 +136,7 @@ export class Session {
117
136
  this._refreshPromise = null;
118
137
  this._accessToken.set(accessToken);
119
138
  if (!accessToken) {
120
- this.invalidate();
139
+ this.markInvalid();
121
140
  }
122
141
  }
123
142
  return accessToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackframe/stack-shared",
3
- "version": "2.4.14",
3
+ "version": "2.4.21",
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": "1.5.6"
29
+ "@stackframe/stack-sc": "2.4.21"
30
30
  },
31
31
  "devDependencies": {
32
32
  "rimraf": "^5.0.5",