@stackframe/react 2.8.48 → 2.8.51

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 (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/components-page/password-reset.js +6 -5
  3. package/dist/components-page/password-reset.js.map +1 -1
  4. package/dist/components-page/sign-out.js +2 -12
  5. package/dist/components-page/sign-out.js.map +1 -1
  6. package/dist/components-page/stack-handler-client.js +274 -0
  7. package/dist/components-page/stack-handler-client.js.map +1 -0
  8. package/dist/components-page/stack-handler.js +4 -239
  9. package/dist/components-page/stack-handler.js.map +1 -1
  10. package/dist/components-page/team-invitation.js +6 -5
  11. package/dist/components-page/team-invitation.js.map +1 -1
  12. package/dist/esm/components-page/password-reset.js +3 -2
  13. package/dist/esm/components-page/password-reset.js.map +1 -1
  14. package/dist/esm/components-page/sign-out.js +2 -2
  15. package/dist/esm/components-page/sign-out.js.map +1 -1
  16. package/dist/esm/components-page/stack-handler-client.js +250 -0
  17. package/dist/esm/components-page/stack-handler-client.js.map +1 -0
  18. package/dist/esm/components-page/stack-handler.js +4 -239
  19. package/dist/esm/components-page/stack-handler.js.map +1 -1
  20. package/dist/esm/components-page/team-invitation.js +3 -2
  21. package/dist/esm/components-page/team-invitation.js.map +1 -1
  22. package/dist/esm/lib/cookie.js +36 -7
  23. package/dist/esm/lib/cookie.js.map +1 -1
  24. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  25. package/dist/esm/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  26. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js +255 -56
  27. package/dist/esm/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  28. package/dist/esm/lib/stack-app/apps/implementations/common.js +3 -3
  29. package/dist/esm/lib/stack-app/apps/implementations/common.js.map +1 -1
  30. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js +39 -0
  31. package/dist/esm/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  32. package/dist/esm/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  33. package/dist/esm/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  34. package/dist/esm/lib/stack-app/common.js.map +1 -1
  35. package/dist/esm/lib/stack-app/users/index.js.map +1 -1
  36. package/dist/index.d.mts +124 -89
  37. package/dist/index.d.ts +124 -89
  38. package/dist/lib/cookie.js +38 -7
  39. package/dist/lib/cookie.js.map +1 -1
  40. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js +17 -0
  41. package/dist/lib/stack-app/apps/implementations/admin-app-impl.js.map +1 -1
  42. package/dist/lib/stack-app/apps/implementations/client-app-impl.js +254 -55
  43. package/dist/lib/stack-app/apps/implementations/client-app-impl.js.map +1 -1
  44. package/dist/lib/stack-app/apps/implementations/common.js +2 -2
  45. package/dist/lib/stack-app/apps/implementations/common.js.map +1 -1
  46. package/dist/lib/stack-app/apps/implementations/server-app-impl.js +49 -0
  47. package/dist/lib/stack-app/apps/implementations/server-app-impl.js.map +1 -1
  48. package/dist/lib/stack-app/apps/interfaces/admin-app.js.map +1 -1
  49. package/dist/lib/stack-app/apps/interfaces/client-app.js.map +1 -1
  50. package/dist/lib/stack-app/common.js.map +1 -1
  51. package/dist/lib/stack-app/users/index.js.map +1 -1
  52. package/package.json +5 -5
@@ -2,6 +2,7 @@
2
2
  import { WebAuthnError, startAuthentication, startRegistration } from "@simplewebauthn/browser";
3
3
  import { KnownErrors, StackClientInterface } from "@stackframe/stack-shared";
4
4
  import { InternalSession } from "@stackframe/stack-shared/dist/sessions";
5
+ import { encodeBase32 } from "@stackframe/stack-shared/dist/utils/bytes";
5
6
  import { isBrowserLike } from "@stackframe/stack-shared/dist/utils/env";
6
7
  import { StackAssertionError, captureError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
7
8
  import { DependenciesMap } from "@stackframe/stack-shared/dist/utils/maps";
@@ -17,7 +18,7 @@ import * as cookie from "cookie";
17
18
  import React, { useCallback, useMemo } from "react";
18
19
  import { constructRedirectUrl } from "../../../../utils/url.js";
19
20
  import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "../../../auth.js";
20
- import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, getCookieClient, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
21
+ import { createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, isSecure as isSecureCookieContext, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie.js";
21
22
  import { apiKeyCreationOptionsToCrud } from "../../api-keys/index.js";
22
23
  import { stackAppInternalsSymbol } from "../../common.js";
23
24
  import { contactChannelCreateOptionsToCrud, contactChannelUpdateOptionsToCrud } from "../../contact-channels/index.js";
@@ -25,6 +26,7 @@ import { adminProjectCreateOptionsToCrud } from "../../projects/index.js";
25
26
  import { teamCreateOptionsToCrud, teamUpdateOptionsToCrud } from "../../teams/index.js";
26
27
  import { attachUserDestructureGuard, userUpdateOptionsToCrud } from "../../users/index.js";
27
28
  import { clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getBaseUrl, getDefaultExtraRequestHeaders, getDefaultProjectId, getDefaultPublishableClientKey, getUrls, resolveConstructorOptions } from "./common.js";
29
+ import { parseJson } from "@stackframe/stack-shared/dist/utils/json";
28
30
  import { useAsyncCache } from "./common.js";
29
31
  var isReactServer = false;
30
32
  var process = globalThis.process ?? { env: {} };
@@ -175,11 +177,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
175
177
  this._convexPartialUserCache = createCache(
176
178
  async ([ctx]) => await this._getPartialUserFromConvex(ctx)
177
179
  );
180
+ this._trustedParentDomainCache = createCache(
181
+ async ([domain]) => await this._getTrustedParentDomain(domain)
182
+ );
178
183
  this._anonymousSignUpInProgress = null;
179
184
  this._memoryTokenStore = createEmptyTokenStore();
180
185
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
181
186
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
182
187
  this._storedBrowserCookieTokenStore = null;
188
+ this._mostRecentQueuedCookieRefreshIndex = 0;
183
189
  /**
184
190
  * A map from token stores and session keys to sessions.
185
191
  *
@@ -195,13 +201,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
195
201
  }
196
202
  this._options = resolvedOptions;
197
203
  this._extraOptions = extraOptions;
204
+ const projectId = resolvedOptions.projectId ?? getDefaultProjectId();
205
+ if (projectId !== "internal" && !projectId.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i)) {
206
+ throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
207
+ }
198
208
  if (extraOptions && extraOptions.interface) {
199
209
  this._interface = extraOptions.interface;
200
210
  } else {
201
211
  this._interface = new StackClientInterface({
202
212
  getBaseUrl: () => getBaseUrl(resolvedOptions.baseUrl),
203
213
  extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? getDefaultExtraRequestHeaders(),
204
- projectId: resolvedOptions.projectId ?? getDefaultProjectId(),
214
+ projectId,
205
215
  clientVersion,
206
216
  publishableClientKey: resolvedOptions.publishableClientKey ?? getDefaultPublishableClientKey(),
207
217
  prepareRequest: async () => {
@@ -302,13 +312,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
302
312
  runAsynchronously(this._checkFeatureSupport(name, options));
303
313
  throw new StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
304
314
  }
315
+ get _legacyRefreshTokenCookieName() {
316
+ return `stack-refresh-${this.projectId}`;
317
+ }
305
318
  get _refreshTokenCookieName() {
306
319
  return `stack-refresh-${this.projectId}`;
307
320
  }
321
+ _getRefreshTokenDefaultCookieNameForSecure(secure) {
322
+ return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
323
+ }
324
+ _getCustomRefreshCookieName(domain) {
325
+ const encoded = encodeBase32(new TextEncoder().encode(domain.toLowerCase()));
326
+ return `${this._refreshTokenCookieName}--custom-${encoded}`;
327
+ }
328
+ _formatRefreshCookieValue(refreshToken, updatedAt) {
329
+ return JSON.stringify({
330
+ refresh_token: refreshToken,
331
+ updated_at_millis: updatedAt
332
+ });
333
+ }
334
+ _formatAccessCookieValue(refreshToken, accessToken) {
335
+ return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
336
+ }
337
+ _parseStructuredRefreshCookie(value) {
338
+ if (!value) {
339
+ return null;
340
+ }
341
+ const parsed = parseJson(value);
342
+ if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
343
+ console.warn("Failed to parse structured refresh cookie");
344
+ return null;
345
+ }
346
+ const data = parsed.data;
347
+ const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
348
+ const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
349
+ if (!refreshToken) {
350
+ console.warn("Refresh token not found in structured refresh cookie");
351
+ return null;
352
+ }
353
+ return {
354
+ refreshToken,
355
+ updatedAt
356
+ };
357
+ }
358
+ _extractRefreshTokenFromCookieMap(cookies) {
359
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
360
+ for (const name of legacyNames) {
361
+ const value = cookies[name];
362
+ if (value) {
363
+ return { refreshToken: value, updatedAt: null };
364
+ }
365
+ }
366
+ let selected = null;
367
+ for (const [name, value] of Object.entries(cookies)) {
368
+ if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
369
+ const parsed = this._parseStructuredRefreshCookie(value);
370
+ if (!parsed) continue;
371
+ const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
372
+ const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
373
+ if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
374
+ selected = parsed;
375
+ }
376
+ }
377
+ if (!selected) {
378
+ return { refreshToken: null, updatedAt: null };
379
+ }
380
+ return {
381
+ refreshToken: selected.refreshToken,
382
+ updatedAt: selected.updatedAt ?? null
383
+ };
384
+ }
308
385
  _getTokensFromCookies(cookies) {
309
- const refreshToken = cookies.refreshTokenCookie;
310
- const accessTokenObject = cookies.accessTokenCookie?.startsWith('["') ? JSON.parse(cookies.accessTokenCookie) : null;
311
- const accessToken = accessTokenObject && refreshToken === accessTokenObject[0] ? accessTokenObject[1] : null;
386
+ const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies);
387
+ const accessTokenCookie = cookies[this._accessTokenCookieName] ?? null;
388
+ let accessToken = null;
389
+ if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
390
+ const parsed = parseJson(accessTokenCookie);
391
+ if (parsed.status === "ok" && typeof parsed.data === "object" && parsed.data !== null && Array.isArray(parsed.data) && parsed.data.length === 2 && typeof parsed.data[0] === "string" && typeof parsed.data[1] === "string") {
392
+ if (parsed.data[0] === refreshToken) {
393
+ accessToken = parsed.data[1];
394
+ }
395
+ } else {
396
+ console.warn("Access token cookie has invalid format");
397
+ }
398
+ }
312
399
  return {
313
400
  refreshToken,
314
401
  accessToken
@@ -317,17 +404,98 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
317
404
  get _accessTokenCookieName() {
318
405
  return `stack-access`;
319
406
  }
407
+ _getAllBrowserCookies() {
408
+ if (!isBrowserLike()) {
409
+ throw new StackAssertionError("Cannot get browser cookies on the server!");
410
+ }
411
+ return cookie.parse(document.cookie || "");
412
+ }
413
+ _getRefreshTokenCookieNamePatterns() {
414
+ return {
415
+ legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
416
+ structuredPrefixes: [
417
+ `${this._refreshTokenCookieName}--`,
418
+ `__Host-${this._refreshTokenCookieName}--`
419
+ ]
420
+ };
421
+ }
422
+ _collectRefreshTokenCookieNames(cookies) {
423
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
424
+ const names = /* @__PURE__ */ new Set();
425
+ for (const name of legacyNames) {
426
+ if (cookies[name]) {
427
+ names.add(name);
428
+ }
429
+ }
430
+ for (const name of Object.keys(cookies)) {
431
+ if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
432
+ names.add(name);
433
+ }
434
+ }
435
+ return names;
436
+ }
437
+ _prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
438
+ const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
439
+ cookieNames.delete(defaultCookieName);
440
+ const updatedAt = refreshToken ? Date.now() : null;
441
+ const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
442
+ const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
443
+ return {
444
+ updatedAt,
445
+ refreshCookieValue,
446
+ accessTokenPayload,
447
+ cookieNamesToDelete: [...cookieNames]
448
+ };
449
+ }
450
+ _queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
451
+ runAsynchronously(async () => {
452
+ this._mostRecentQueuedCookieRefreshIndex++;
453
+ const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
454
+ let hostname;
455
+ if (isBrowserLike()) {
456
+ hostname = window.location.hostname;
457
+ }
458
+ if (!hostname) {
459
+ console.warn("No hostname found when queueing custom refresh cookie update");
460
+ return;
461
+ }
462
+ const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
463
+ const setCookie = async (targetDomain, value2) => {
464
+ const name = this._getCustomRefreshCookieName(targetDomain);
465
+ const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
466
+ if (context === "browser") {
467
+ setOrDeleteCookieClient(name, value2, options);
468
+ } else {
469
+ await setOrDeleteCookie(name, value2, options);
470
+ }
471
+ };
472
+ if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
473
+ return;
474
+ }
475
+ const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
476
+ await setCookie(domain.data, value);
477
+ });
478
+ }
479
+ async _getTrustedParentDomain(currentDomain) {
480
+ const project = Result.orThrow(await this._interface.getClientProject());
481
+ const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
482
+ const trustedWildcards = domains.filter((d) => d.startsWith("**."));
483
+ const parts = currentDomain.split(".");
484
+ for (let i = parts.length - 2; i >= 0; i--) {
485
+ const parentDomain = parts.slice(i).join(".");
486
+ if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
487
+ return parentDomain;
488
+ }
489
+ }
490
+ return null;
491
+ }
320
492
  _getBrowserCookieTokenStore() {
321
493
  if (!isBrowserLike()) {
322
494
  throw new Error("Cannot use cookie token store on the server!");
323
495
  }
324
496
  if (this._storedBrowserCookieTokenStore === null) {
325
497
  const getCurrentValue = (old) => {
326
- const tokens = this._getTokensFromCookies({
327
- refreshTokenCookie: getCookieClient(this._refreshTokenCookieName) ?? getCookieClient("stack-refresh"),
328
- // keep old cookie name for backwards-compatibility
329
- accessTokenCookie: getCookieClient(this._accessTokenCookieName)
330
- });
498
+ const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
331
499
  return {
332
500
  refreshToken: tokens.refreshToken,
333
501
  accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
@@ -346,9 +514,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
346
514
  }, 100);
347
515
  this._storedBrowserCookieTokenStore.onChange((value) => {
348
516
  try {
349
- setOrDeleteCookieClient(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
350
- setOrDeleteCookieClient(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24 });
351
- deleteCookieClient("stack-refresh");
517
+ const refreshToken = value.refreshToken;
518
+ const secure = window.location.protocol === "https:";
519
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
520
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
521
+ this._getAllBrowserCookies(),
522
+ refreshToken,
523
+ value.accessToken ?? null,
524
+ defaultName
525
+ );
526
+ setOrDeleteCookieClient(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
527
+ setOrDeleteCookieClient(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
528
+ cookieNamesToDelete.forEach((name) => deleteCookieClient(name));
529
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
352
530
  hasSucceededInWriting = true;
353
531
  } catch (e) {
354
532
  if (!isBrowserLike()) {
@@ -371,18 +549,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
371
549
  if (isBrowserLike()) {
372
550
  return this._getBrowserCookieTokenStore();
373
551
  } else {
374
- const tokens = this._getTokensFromCookies({
375
- refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
376
- // keep old cookie name for backwards-compatibility
377
- accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
378
- });
552
+ const tokens = this._getTokensFromCookies(cookieHelper.getAll());
379
553
  const store = new Store(tokens);
380
554
  store.onChange((value) => {
381
555
  runAsynchronously(async () => {
556
+ const refreshToken = value.refreshToken;
557
+ const secure = await isSecureCookieContext();
558
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
559
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
560
+ cookieHelper.getAll(),
561
+ refreshToken,
562
+ value.accessToken ?? null,
563
+ defaultName
564
+ );
382
565
  await Promise.all([
383
- setOrDeleteCookie(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
384
- setOrDeleteCookie(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
566
+ setOrDeleteCookie(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
567
+ setOrDeleteCookie(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
385
568
  ]);
569
+ if (cookieNamesToDelete.length > 0) {
570
+ await Promise.all(
571
+ cookieNamesToDelete.map(
572
+ (name) => setOrDeleteCookie(name, null, { noOpIfServerComponent: true })
573
+ )
574
+ );
575
+ }
576
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
386
577
  });
387
578
  });
388
579
  return store;
@@ -413,11 +604,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
413
604
  }
414
605
  const cookieHeader = tokenStoreInit.headers.get("cookie");
415
606
  const parsed = cookie.parse(cookieHeader || "");
416
- const res = new Store({
417
- refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
418
- // keep old cookie name for backwards-compatibility
419
- accessToken: parsed[this._accessTokenCookieName] || null
420
- });
607
+ const res = new Store(this._getTokensFromCookies(parsed));
421
608
  this._requestTokenStores.set(tokenStoreInit, res);
422
609
  return res;
423
610
  } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
@@ -763,35 +950,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
763
950
  const tokens = await this.currentSession.getTokens();
764
951
  return tokens;
765
952
  },
766
- async registerPasskey(options) {
767
- const hostname = (await app._getCurrentUrl())?.hostname;
768
- if (!hostname) {
769
- throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
770
- }
771
- const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
772
- if (initiationResult.status !== "ok") {
773
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
774
- }
775
- const { options_json, code } = initiationResult.data;
776
- if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
777
- throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
778
- }
779
- options_json.rp.id = hostname;
780
- let attResp;
781
- try {
782
- attResp = await startRegistration({ optionsJSON: options_json });
783
- } catch (error) {
784
- if (error instanceof WebAuthnError) {
785
- return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
786
- } else {
787
- captureError("passkey-registration-failed", error);
788
- return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
789
- }
790
- }
791
- const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
792
- await app._refreshUser(session);
793
- return registrationResult;
794
- },
795
953
  signOut(options) {
796
954
  return app._signOut(session, options);
797
955
  }
@@ -1040,6 +1198,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1040
1198
  async getOAuthProvider(id) {
1041
1199
  const providers = await this.listOAuthProviders();
1042
1200
  return providers.find((p) => p.id === id) ?? null;
1201
+ },
1202
+ async registerPasskey(options) {
1203
+ const hostname = (await app._getCurrentUrl())?.hostname;
1204
+ if (!hostname) {
1205
+ throw new StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
1206
+ }
1207
+ const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
1208
+ if (initiationResult.status !== "ok") {
1209
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
1210
+ }
1211
+ const { options_json, code } = initiationResult.data;
1212
+ if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
1213
+ throw new StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
1214
+ }
1215
+ options_json.rp.id = hostname;
1216
+ let attResp;
1217
+ try {
1218
+ attResp = await startRegistration({ optionsJSON: options_json });
1219
+ } catch (error) {
1220
+ if (error instanceof WebAuthnError) {
1221
+ return Result.error(new KnownErrors.PasskeyWebAuthnError(error.message, error.name));
1222
+ } else {
1223
+ captureError("passkey-registration-failed", error);
1224
+ return Result.error(new KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
1225
+ }
1226
+ }
1227
+ const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
1228
+ await app._refreshUser(session);
1229
+ return registrationResult;
1043
1230
  }
1044
1231
  };
1045
1232
  }
@@ -1808,10 +1995,22 @@ ${url}`);
1808
1995
  });
1809
1996
  }
1810
1997
  async signOut(options) {
1811
- const user = await this.getUser();
1998
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1999
+ if (user) {
2000
+ await user.signOut({ redirectUrl: options?.redirectUrl });
2001
+ }
2002
+ }
2003
+ async getAuthHeaders(options) {
2004
+ return {
2005
+ "x-stack-auth": JSON.stringify(await this.getAuthJson(options))
2006
+ };
2007
+ }
2008
+ async getAuthJson(options) {
2009
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1812
2010
  if (user) {
1813
- await user.signOut(options);
2011
+ return await user.getAuthJson();
1814
2012
  }
2013
+ return { accessToken: null, refreshToken: null };
1815
2014
  }
1816
2015
  async getProject() {
1817
2016
  const crud = Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));