@stackframe/react 2.8.69 → 2.8.70

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.
Files changed (26) hide show
  1. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +170 -22
  2. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  3. package/dist/esm/lib/stack-app/apps/implementations/common.js +1 -1
  4. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  5. package/dist/esm/lib/stack-app/apps/implementations/event-tracker.js +4 -12
  6. package/dist/esm/lib/stack-app/apps/implementations/event-tracker.js.map +1 -1
  7. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +108 -10
  8. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  9. package/dist/esm/lib/stack-app/apps/implementations/session-replay.js +37 -24
  10. package/dist/esm/lib/stack-app/apps/implementations/session-replay.js.map +1 -1
  11. package/dist/esm/lib/stack-app/users/index.js.map +1 -1
  12. package/dist/index.d.mts +73 -2
  13. package/dist/index.d.ts +73 -2
  14. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +170 -22
  15. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  16. package/dist/lib/stack-app/apps/implementations/common.js +1 -1
  17. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  18. package/dist/lib/stack-app/apps/implementations/event-tracker.js +4 -12
  19. package/dist/lib/stack-app/apps/implementations/event-tracker.js.map +1 -1
  20. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +108 -10
  21. package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  22. package/dist/lib/stack-app/apps/implementations/session-replay.js +37 -24
  23. package/dist/lib/stack-app/apps/implementations/session-replay.js.map +1 -1
  24. package/dist/lib/stack-app/connected-accounts/index.js.map +1 -1
  25. package/dist/lib/stack-app/users/index.js.map +1 -1
  26. package/package.json +3 -3
@@ -64,6 +64,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
64
64
  this._currentUserTeamsCache = createCacheBySession(async (session) => {
65
65
  return await this._interface.listCurrentUserTeams(session);
66
66
  });
67
+ /** @deprecated Used by legacy getConnectedAccount(providerId) — uses old per-provider access token endpoint */
67
68
  this._currentUserOAuthConnectionAccessTokensCache = createCacheBySession(
68
69
  async (session, [providerId, scope]) => {
69
70
  try {
@@ -77,6 +78,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
77
78
  return null;
78
79
  }
79
80
  );
81
+ /** @deprecated Used by legacy getConnectedAccount(providerId) — combines token check + redirect */
80
82
  this._currentUserOAuthConnectionCache = createCacheBySession(
81
83
  async (session, [providerId, scope, redirect]) => {
82
84
  return await this._getUserOAuthConnectionCacheFn({
@@ -90,6 +92,49 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
90
92
  });
91
93
  }
92
94
  );
95
+ this._currentUserConnectedAccountsCache = createCacheBySession(
96
+ async (session) => {
97
+ const result = await this._interface.listConnectedAccounts(session);
98
+ return result.items.map((item) => this._createOAuthConnectionFromCrudItem(item, session));
99
+ }
100
+ );
101
+ this._currentUserOAuthConnectionAccessTokensByAccountCache = createCacheBySession(
102
+ async (session, [providerId, providerAccountId, scope]) => {
103
+ try {
104
+ const result = await this._interface.createProviderAccessTokenByAccount(providerId, providerAccountId, scope, session);
105
+ return { accessToken: result.access_token };
106
+ } catch (err) {
107
+ if (KnownErrors.OAuthConnectionDoesNotHaveRequiredScope.isInstance(err) || KnownErrors.OAuthConnectionNotConnectedToUser.isInstance(err)) {
108
+ return null;
109
+ }
110
+ throw err;
111
+ }
112
+ }
113
+ );
114
+ this._currentUserValidConnectedAccountForProviderCache = createCacheBySession(
115
+ async (session, [provider, scopeString]) => {
116
+ const connectedAccounts = Result.orThrow(await this._currentUserConnectedAccountsCache.getOrWait([session], "write-only"));
117
+ const matchingAccounts = connectedAccounts.filter((a) => a.provider === provider);
118
+ const scopes = scopeString ? scopeString.split(" ") : void 0;
119
+ for (const account of matchingAccounts) {
120
+ const tokenResult = await account.getAccessToken({ scopes });
121
+ if (tokenResult.status === "ok") {
122
+ return account;
123
+ }
124
+ }
125
+ await addNewOAuthProviderOrScope(
126
+ this._interface,
127
+ {
128
+ provider,
129
+ redirectUrl: this.urls.oauthCallback,
130
+ errorRedirectUrl: this.urls.error,
131
+ providerScope: mergeScopeStrings(scopeString, (this._oauthScopesOnSignIn[provider] ?? []).join(" "))
132
+ },
133
+ session
134
+ );
135
+ return await neverResolve();
136
+ }
137
+ );
93
138
  this._teamMemberProfilesCache = createCacheBySession(
94
139
  async (session, [teamId]) => {
95
140
  return await this._interface.listTeamMemberProfiles({ teamId }, session);
@@ -260,14 +305,14 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
260
305
  this._initUniqueIdentifier();
261
306
  }
262
307
  this._analyticsOptions = resolvedOptions.analytics;
308
+ const getAnalyticsAccessToken = async () => {
309
+ this._ensurePersistentTokenStore();
310
+ return await (await this.getUser({ or: "anonymous" })).getAccessToken();
311
+ };
263
312
  if (isBrowserLike() && this._analyticsOptions?.replays?.enabled === true) {
264
313
  this._sessionRecorder = new SessionRecorder({
265
314
  projectId: this.projectId,
266
- getAccessToken: async () => {
267
- const session = await this._getSession();
268
- const tokens = await session.getOrFetchLikelyValidTokens(2e4, 75e3);
269
- return tokens?.accessToken.token ?? null;
270
- },
315
+ getAccessToken: getAnalyticsAccessToken,
271
316
  sendBatch: async (body, opts) => {
272
317
  return await this._interface.sendSessionReplayBatch(body, await this._getSession(), opts);
273
318
  }
@@ -277,11 +322,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
277
322
  if (isBrowserLike() && this.projectId === "internal") {
278
323
  this._eventTracker = new EventTracker({
279
324
  projectId: this.projectId,
280
- getAccessToken: async () => {
281
- const session = await this._getSession();
282
- const tokens = await session.getOrFetchLikelyValidTokens(2e4, 75e3);
283
- return tokens?.accessToken.token ?? null;
284
- },
325
+ getAccessToken: getAnalyticsAccessToken,
285
326
  sendBatch: async (body, opts) => {
286
327
  return await this._interface.sendAnalyticsEventBatch(body, await this._getSession(), opts);
287
328
  }
@@ -297,6 +338,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
297
338
  return await createPlaceholderCookieHelper();
298
339
  }
299
340
  }
341
+ /** @deprecated Used by legacy getConnectedAccount(providerId) — combines user check + token check + redirect into one cache */
300
342
  async _getUserOAuthConnectionCacheFn(options) {
301
343
  const user = await options.getUser();
302
344
  let hasConnection = true;
@@ -329,24 +371,58 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
329
371
  } else if (!hasConnection) {
330
372
  return null;
331
373
  }
374
+ const matchingProvider = user.oauth_providers.find((p) => p.id === options.providerId);
375
+ const providerAccountId = matchingProvider?.account_id ?? "";
332
376
  return {
333
377
  id: options.providerId,
378
+ // deprecated, for backward compat
379
+ provider: options.providerId,
380
+ providerAccountId,
334
381
  async getAccessToken() {
335
382
  const result = await options.getOrWaitOAuthToken();
336
383
  if (!result) {
337
- throw new StackAssertionError("No access token available");
384
+ throw new StackAssertionError(`Failed to retrieve an access token for this connected account (provider: ${options.providerId}). This usually means the OAuth refresh token has been revoked or expired. The user needs to re-authorize by calling \`linkConnectedAccount\` or using \`getOrLinkConnectedAccount\`.`);
338
385
  }
339
386
  return result;
340
387
  },
341
388
  useAccessToken() {
342
389
  const result = options.useOAuthToken();
343
390
  if (!result) {
344
- throw new StackAssertionError("No access token available");
391
+ throw new StackAssertionError(`Failed to retrieve an access token for this connected account (provider: ${options.providerId}). This usually means the OAuth refresh token has been revoked or expired. The user needs to re-authorize by calling \`linkConnectedAccount\` or using \`getOrLinkConnectedAccount\`.`);
345
392
  }
346
393
  return result;
347
394
  }
348
395
  };
349
396
  }
397
+ _createOAuthConnectionFromCrudItem(item, session) {
398
+ const app = this;
399
+ const providerId = item.provider;
400
+ const providerAccountId = item.provider_account_id;
401
+ return {
402
+ id: providerId,
403
+ // deprecated, for backward compat
404
+ provider: providerId,
405
+ providerAccountId,
406
+ async getAccessToken(options) {
407
+ const scopeString = options?.scopes?.join(" ") ?? "";
408
+ const result = Result.orThrow(await app._currentUserOAuthConnectionAccessTokensByAccountCache.getOrWait([session, providerId, providerAccountId, scopeString], "write-only"));
409
+ if (!result) {
410
+ const scopeDetail = scopeString ? `The requested scopes [${scopeString}] are not available on the existing token.` : "The OAuth refresh token has likely been revoked or expired.";
411
+ return Result.error(new KnownErrors.OAuthAccessTokenNotAvailable(providerId, `${scopeDetail} The user needs to re-authorize by calling \`linkConnectedAccount\` or using \`getOrLinkConnectedAccount\`.`));
412
+ }
413
+ return Result.ok(result);
414
+ },
415
+ useAccessToken(options) {
416
+ const scopeString = options?.scopes?.join(" ") ?? "";
417
+ const result = useAsyncCache(app._currentUserOAuthConnectionAccessTokensByAccountCache, [session, providerId, providerAccountId, scopeString], "connection.useAccessToken()");
418
+ if (!result) {
419
+ const scopeDetail = scopeString ? `The requested scopes [${scopeString}] are not available on the existing token.` : "The OAuth refresh token has likely been revoked or expired.";
420
+ return Result.error(new KnownErrors.OAuthAccessTokenNotAvailable(providerId, `${scopeDetail} The user needs to re-authorize by calling \`linkConnectedAccount\` or using \`getOrLinkConnectedAccount\`.`));
421
+ }
422
+ return Result.ok(result);
423
+ }
424
+ };
425
+ }
350
426
  _initUniqueIdentifier() {
351
427
  if (!this._uniqueIdentifier) {
352
428
  throw new StackAssertionError("Unique identifier not initialized");
@@ -978,7 +1054,10 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
978
1054
  },
979
1055
  session
980
1056
  );
981
- await app._currentUserOAuthProvidersCache.refresh([session]);
1057
+ await Promise.all([
1058
+ app._currentUserOAuthProvidersCache.refresh([session]),
1059
+ app._currentUserConnectedAccountsCache.refresh([session])
1060
+ ]);
982
1061
  return Result.ok(void 0);
983
1062
  } catch (error) {
984
1063
  if (KnownErrors.OAuthProviderAccountIdAlreadyUsedForSignIn.isInstance(error)) {
@@ -989,7 +1068,10 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
989
1068
  },
990
1069
  async delete() {
991
1070
  await app._interface.deleteOAuthProvider(crud.user_id, crud.id, session);
992
- await app._currentUserOAuthProvidersCache.refresh([session]);
1071
+ await Promise.all([
1072
+ app._currentUserOAuthProvidersCache.refresh([session]),
1073
+ app._currentUserConnectedAccountsCache.refresh([session])
1074
+ ]);
993
1075
  }
994
1076
  };
995
1077
  }
@@ -1153,13 +1235,36 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1153
1235
  }
1154
1236
  _createUserExtraFromCurrent(crud, session) {
1155
1237
  const app = this;
1156
- async function getConnectedAccount(id, options) {
1157
- const scopeString = options?.scopes?.join(" ");
1158
- return Result.orThrow(await app._currentUserOAuthConnectionCache.getOrWait([session, id, scopeString || "", options?.or === "redirect"], "write-only"));
1159
- }
1160
- function useConnectedAccount(id, options) {
1161
- const scopeString = options?.scopes?.join(" ");
1162
- return useAsyncCache(app._currentUserOAuthConnectionCache, [session, id, scopeString || "", options?.or === "redirect"], "user.useConnectedAccount()");
1238
+ async function getConnectedAccount(idOrAccount, options) {
1239
+ const scopeString = options?.scopes?.join(" ") ?? "";
1240
+ if (typeof idOrAccount === "object" && "provider" in idOrAccount && "providerAccountId" in idOrAccount) {
1241
+ const { provider, providerAccountId } = idOrAccount;
1242
+ const connectedAccounts = Result.orThrow(await app._currentUserConnectedAccountsCache.getOrWait([session], "write-only"));
1243
+ const found = connectedAccounts.find(
1244
+ (a) => a.provider === provider && a.providerAccountId === providerAccountId
1245
+ );
1246
+ if (!found) {
1247
+ return null;
1248
+ }
1249
+ return found;
1250
+ }
1251
+ return Result.orThrow(await app._currentUserOAuthConnectionCache.getOrWait([session, idOrAccount, scopeString, options?.or === "redirect"], "write-only"));
1252
+ }
1253
+ function useConnectedAccount(idOrAccount, options) {
1254
+ const scopeString = options?.scopes?.join(" ") ?? "";
1255
+ if (typeof idOrAccount === "object" && "provider" in idOrAccount && "providerAccountId" in idOrAccount) {
1256
+ const { provider, providerAccountId } = idOrAccount;
1257
+ const connectedAccounts = useAsyncCache(
1258
+ app._currentUserConnectedAccountsCache,
1259
+ [session],
1260
+ "user.useConnectedAccount()"
1261
+ );
1262
+ const found = connectedAccounts.find(
1263
+ (a) => a.provider === provider && a.providerAccountId === providerAccountId
1264
+ );
1265
+ return found ?? null;
1266
+ }
1267
+ return useAsyncCache(app._currentUserOAuthConnectionCache, [session, idOrAccount, scopeString, options?.or === "redirect"], "user.useConnectedAccount()");
1163
1268
  }
1164
1269
  return {
1165
1270
  async getActiveSessions() {
@@ -1181,6 +1286,42 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1181
1286
  getConnectedAccount,
1182
1287
  useConnectedAccount,
1183
1288
  // THIS_LINE_PLATFORM react-like
1289
+ async listConnectedAccounts() {
1290
+ return Result.orThrow(await app._currentUserConnectedAccountsCache.getOrWait([session], "write-only"));
1291
+ },
1292
+ useConnectedAccounts() {
1293
+ return useAsyncCache(app._currentUserConnectedAccountsCache, [session], "user.useConnectedAccounts()");
1294
+ },
1295
+ async linkConnectedAccount(provider, options) {
1296
+ const scopeString = options?.scopes?.join(" ") ?? "";
1297
+ await addNewOAuthProviderOrScope(
1298
+ app._interface,
1299
+ {
1300
+ provider,
1301
+ redirectUrl: app.urls.oauthCallback,
1302
+ errorRedirectUrl: app.urls.error,
1303
+ providerScope: mergeScopeStrings(scopeString, (app._oauthScopesOnSignIn[provider] ?? []).join(" "))
1304
+ },
1305
+ session
1306
+ );
1307
+ await neverResolve();
1308
+ },
1309
+ async getOrLinkConnectedAccount(provider, options) {
1310
+ const connectedAccounts = Result.orThrow(await app._currentUserConnectedAccountsCache.getOrWait([session], "write-only"));
1311
+ const matchingAccounts = connectedAccounts.filter((a) => a.provider === provider);
1312
+ for (const account of matchingAccounts) {
1313
+ const tokenResult = await account.getAccessToken({ scopes: options?.scopes });
1314
+ if (tokenResult.status === "ok") {
1315
+ return account;
1316
+ }
1317
+ }
1318
+ await this.linkConnectedAccount(provider, options);
1319
+ return await neverResolve();
1320
+ },
1321
+ useOrLinkConnectedAccount(provider, options) {
1322
+ const scopeString = options?.scopes?.join(" ") ?? "";
1323
+ return useAsyncCache(app._currentUserValidConnectedAccountForProviderCache, [session, provider, scopeString], "user.useOrLinkConnectedAccount()");
1324
+ },
1184
1325
  async getTeam(teamId) {
1185
1326
  const teams = await this.listTeams();
1186
1327
  return teams.find((t) => t.id === teamId) ?? null;
@@ -1833,6 +1974,8 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1833
1974
  case void 0:
1834
1975
  case "anonymous-if-exists[deprecated]":
1835
1976
  case "return-null": {
1977
+ crud = null;
1978
+ break;
1836
1979
  }
1837
1980
  }
1838
1981
  }
@@ -2250,6 +2393,8 @@ ${url}`);
2250
2393
  return false;
2251
2394
  }
2252
2395
  async _signOut(session, options) {
2396
+ this._eventTracker?.clearBuffer();
2397
+ this._sessionRecorder?.clearBuffer();
2253
2398
  await storeLock.withWriteLock(async () => {
2254
2399
  await this._interface.signOut(session);
2255
2400
  if (options?.redirectUrl) {
@@ -2355,7 +2500,10 @@ ${url}`);
2355
2500
  await this._refreshSession(session);
2356
2501
  }
2357
2502
  async _refreshSession(session) {
2358
- await this._currentUserCache.refresh([session]);
2503
+ await Promise.all([
2504
+ this._currentUserCache.refresh([session]),
2505
+ this._currentUserConnectedAccountsCache.refresh([session])
2506
+ ]);
2359
2507
  session.suggestAccessTokenExpired();
2360
2508
  }
2361
2509
  async _refreshUsers() {