@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
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(client_app_impl_exports);
36
36
  var import_browser = require("@simplewebauthn/browser");
37
37
  var import_stack_shared = require("@stackframe/stack-shared");
38
38
  var import_sessions = require("@stackframe/stack-shared/dist/sessions");
39
+ var import_bytes = require("@stackframe/stack-shared/dist/utils/bytes");
39
40
  var import_env = require("@stackframe/stack-shared/dist/utils/env");
40
41
  var import_errors = require("@stackframe/stack-shared/dist/utils/errors");
41
42
  var import_maps = require("@stackframe/stack-shared/dist/utils/maps");
@@ -59,6 +60,7 @@ var import_projects = require("../../projects/index.js");
59
60
  var import_teams = require("../../teams/index.js");
60
61
  var import_users = require("../../users/index.js");
61
62
  var import_common2 = require("./common.js");
63
+ var import_json = require("@stackframe/stack-shared/dist/utils/json");
62
64
  var import_common3 = require("./common.js");
63
65
  var isReactServer = false;
64
66
  var process = globalThis.process ?? { env: {} };
@@ -209,11 +211,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
209
211
  this._convexPartialUserCache = (0, import_common2.createCache)(
210
212
  async ([ctx]) => await this._getPartialUserFromConvex(ctx)
211
213
  );
214
+ this._trustedParentDomainCache = (0, import_common2.createCache)(
215
+ async ([domain]) => await this._getTrustedParentDomain(domain)
216
+ );
212
217
  this._anonymousSignUpInProgress = null;
213
218
  this._memoryTokenStore = (0, import_common2.createEmptyTokenStore)();
214
219
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
215
220
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
216
221
  this._storedBrowserCookieTokenStore = null;
222
+ this._mostRecentQueuedCookieRefreshIndex = 0;
217
223
  /**
218
224
  * A map from token stores and session keys to sessions.
219
225
  *
@@ -229,13 +235,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
229
235
  }
230
236
  this._options = resolvedOptions;
231
237
  this._extraOptions = extraOptions;
238
+ const projectId = resolvedOptions.projectId ?? (0, import_common2.getDefaultProjectId)();
239
+ 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)) {
240
+ throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
241
+ }
232
242
  if (extraOptions && extraOptions.interface) {
233
243
  this._interface = extraOptions.interface;
234
244
  } else {
235
245
  this._interface = new import_stack_shared.StackClientInterface({
236
246
  getBaseUrl: () => (0, import_common2.getBaseUrl)(resolvedOptions.baseUrl),
237
247
  extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? (0, import_common2.getDefaultExtraRequestHeaders)(),
238
- projectId: resolvedOptions.projectId ?? (0, import_common2.getDefaultProjectId)(),
248
+ projectId,
239
249
  clientVersion: import_common2.clientVersion,
240
250
  publishableClientKey: resolvedOptions.publishableClientKey ?? (0, import_common2.getDefaultPublishableClientKey)(),
241
251
  prepareRequest: async () => {
@@ -336,13 +346,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
336
346
  (0, import_promises.runAsynchronously)(this._checkFeatureSupport(name, options));
337
347
  throw new import_errors.StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
338
348
  }
349
+ get _legacyRefreshTokenCookieName() {
350
+ return `stack-refresh-${this.projectId}`;
351
+ }
339
352
  get _refreshTokenCookieName() {
340
353
  return `stack-refresh-${this.projectId}`;
341
354
  }
355
+ _getRefreshTokenDefaultCookieNameForSecure(secure) {
356
+ return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
357
+ }
358
+ _getCustomRefreshCookieName(domain) {
359
+ const encoded = (0, import_bytes.encodeBase32)(new TextEncoder().encode(domain.toLowerCase()));
360
+ return `${this._refreshTokenCookieName}--custom-${encoded}`;
361
+ }
362
+ _formatRefreshCookieValue(refreshToken, updatedAt) {
363
+ return JSON.stringify({
364
+ refresh_token: refreshToken,
365
+ updated_at_millis: updatedAt
366
+ });
367
+ }
368
+ _formatAccessCookieValue(refreshToken, accessToken) {
369
+ return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
370
+ }
371
+ _parseStructuredRefreshCookie(value) {
372
+ if (!value) {
373
+ return null;
374
+ }
375
+ const parsed = (0, import_json.parseJson)(value);
376
+ if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
377
+ console.warn("Failed to parse structured refresh cookie");
378
+ return null;
379
+ }
380
+ const data = parsed.data;
381
+ const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
382
+ const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
383
+ if (!refreshToken) {
384
+ console.warn("Refresh token not found in structured refresh cookie");
385
+ return null;
386
+ }
387
+ return {
388
+ refreshToken,
389
+ updatedAt
390
+ };
391
+ }
392
+ _extractRefreshTokenFromCookieMap(cookies) {
393
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
394
+ for (const name of legacyNames) {
395
+ const value = cookies[name];
396
+ if (value) {
397
+ return { refreshToken: value, updatedAt: null };
398
+ }
399
+ }
400
+ let selected = null;
401
+ for (const [name, value] of Object.entries(cookies)) {
402
+ if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
403
+ const parsed = this._parseStructuredRefreshCookie(value);
404
+ if (!parsed) continue;
405
+ const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
406
+ const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
407
+ if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
408
+ selected = parsed;
409
+ }
410
+ }
411
+ if (!selected) {
412
+ return { refreshToken: null, updatedAt: null };
413
+ }
414
+ return {
415
+ refreshToken: selected.refreshToken,
416
+ updatedAt: selected.updatedAt ?? null
417
+ };
418
+ }
342
419
  _getTokensFromCookies(cookies) {
343
- const refreshToken = cookies.refreshTokenCookie;
344
- const accessTokenObject = cookies.accessTokenCookie?.startsWith('["') ? JSON.parse(cookies.accessTokenCookie) : null;
345
- const accessToken = accessTokenObject && refreshToken === accessTokenObject[0] ? accessTokenObject[1] : null;
420
+ const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies);
421
+ const accessTokenCookie = cookies[this._accessTokenCookieName] ?? null;
422
+ let accessToken = null;
423
+ if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
424
+ const parsed = (0, import_json.parseJson)(accessTokenCookie);
425
+ 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") {
426
+ if (parsed.data[0] === refreshToken) {
427
+ accessToken = parsed.data[1];
428
+ }
429
+ } else {
430
+ console.warn("Access token cookie has invalid format");
431
+ }
432
+ }
346
433
  return {
347
434
  refreshToken,
348
435
  accessToken
@@ -351,17 +438,98 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
351
438
  get _accessTokenCookieName() {
352
439
  return `stack-access`;
353
440
  }
441
+ _getAllBrowserCookies() {
442
+ if (!(0, import_env.isBrowserLike)()) {
443
+ throw new import_errors.StackAssertionError("Cannot get browser cookies on the server!");
444
+ }
445
+ return cookie.parse(document.cookie || "");
446
+ }
447
+ _getRefreshTokenCookieNamePatterns() {
448
+ return {
449
+ legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
450
+ structuredPrefixes: [
451
+ `${this._refreshTokenCookieName}--`,
452
+ `__Host-${this._refreshTokenCookieName}--`
453
+ ]
454
+ };
455
+ }
456
+ _collectRefreshTokenCookieNames(cookies) {
457
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
458
+ const names = /* @__PURE__ */ new Set();
459
+ for (const name of legacyNames) {
460
+ if (cookies[name]) {
461
+ names.add(name);
462
+ }
463
+ }
464
+ for (const name of Object.keys(cookies)) {
465
+ if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
466
+ names.add(name);
467
+ }
468
+ }
469
+ return names;
470
+ }
471
+ _prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
472
+ const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
473
+ cookieNames.delete(defaultCookieName);
474
+ const updatedAt = refreshToken ? Date.now() : null;
475
+ const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
476
+ const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
477
+ return {
478
+ updatedAt,
479
+ refreshCookieValue,
480
+ accessTokenPayload,
481
+ cookieNamesToDelete: [...cookieNames]
482
+ };
483
+ }
484
+ _queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
485
+ (0, import_promises.runAsynchronously)(async () => {
486
+ this._mostRecentQueuedCookieRefreshIndex++;
487
+ const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
488
+ let hostname;
489
+ if ((0, import_env.isBrowserLike)()) {
490
+ hostname = window.location.hostname;
491
+ }
492
+ if (!hostname) {
493
+ console.warn("No hostname found when queueing custom refresh cookie update");
494
+ return;
495
+ }
496
+ const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
497
+ const setCookie = async (targetDomain, value2) => {
498
+ const name = this._getCustomRefreshCookieName(targetDomain);
499
+ const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
500
+ if (context === "browser") {
501
+ (0, import_cookie.setOrDeleteCookieClient)(name, value2, options);
502
+ } else {
503
+ await (0, import_cookie.setOrDeleteCookie)(name, value2, options);
504
+ }
505
+ };
506
+ if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
507
+ return;
508
+ }
509
+ const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
510
+ await setCookie(domain.data, value);
511
+ });
512
+ }
513
+ async _getTrustedParentDomain(currentDomain) {
514
+ const project = import_results.Result.orThrow(await this._interface.getClientProject());
515
+ const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
516
+ const trustedWildcards = domains.filter((d) => d.startsWith("**."));
517
+ const parts = currentDomain.split(".");
518
+ for (let i = parts.length - 2; i >= 0; i--) {
519
+ const parentDomain = parts.slice(i).join(".");
520
+ if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
521
+ return parentDomain;
522
+ }
523
+ }
524
+ return null;
525
+ }
354
526
  _getBrowserCookieTokenStore() {
355
527
  if (!(0, import_env.isBrowserLike)()) {
356
528
  throw new Error("Cannot use cookie token store on the server!");
357
529
  }
358
530
  if (this._storedBrowserCookieTokenStore === null) {
359
531
  const getCurrentValue = (old) => {
360
- const tokens = this._getTokensFromCookies({
361
- refreshTokenCookie: (0, import_cookie.getCookieClient)(this._refreshTokenCookieName) ?? (0, import_cookie.getCookieClient)("stack-refresh"),
362
- // keep old cookie name for backwards-compatibility
363
- accessTokenCookie: (0, import_cookie.getCookieClient)(this._accessTokenCookieName)
364
- });
532
+ const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
365
533
  return {
366
534
  refreshToken: tokens.refreshToken,
367
535
  accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
@@ -380,9 +548,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
380
548
  }, 100);
381
549
  this._storedBrowserCookieTokenStore.onChange((value) => {
382
550
  try {
383
- (0, import_cookie.setOrDeleteCookieClient)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
384
- (0, import_cookie.setOrDeleteCookieClient)(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24 });
385
- (0, import_cookie.deleteCookieClient)("stack-refresh");
551
+ const refreshToken = value.refreshToken;
552
+ const secure = window.location.protocol === "https:";
553
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
554
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
555
+ this._getAllBrowserCookies(),
556
+ refreshToken,
557
+ value.accessToken ?? null,
558
+ defaultName
559
+ );
560
+ (0, import_cookie.setOrDeleteCookieClient)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
561
+ (0, import_cookie.setOrDeleteCookieClient)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
562
+ cookieNamesToDelete.forEach((name) => (0, import_cookie.deleteCookieClient)(name));
563
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
386
564
  hasSucceededInWriting = true;
387
565
  } catch (e) {
388
566
  if (!(0, import_env.isBrowserLike)()) {
@@ -405,18 +583,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
405
583
  if ((0, import_env.isBrowserLike)()) {
406
584
  return this._getBrowserCookieTokenStore();
407
585
  } else {
408
- const tokens = this._getTokensFromCookies({
409
- refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
410
- // keep old cookie name for backwards-compatibility
411
- accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
412
- });
586
+ const tokens = this._getTokensFromCookies(cookieHelper.getAll());
413
587
  const store = new import_stores.Store(tokens);
414
588
  store.onChange((value) => {
415
589
  (0, import_promises.runAsynchronously)(async () => {
590
+ const refreshToken = value.refreshToken;
591
+ const secure = await (0, import_cookie.isSecure)();
592
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
593
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
594
+ cookieHelper.getAll(),
595
+ refreshToken,
596
+ value.accessToken ?? null,
597
+ defaultName
598
+ );
416
599
  await Promise.all([
417
- (0, import_cookie.setOrDeleteCookie)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
418
- (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
600
+ (0, import_cookie.setOrDeleteCookie)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
601
+ (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
419
602
  ]);
603
+ if (cookieNamesToDelete.length > 0) {
604
+ await Promise.all(
605
+ cookieNamesToDelete.map(
606
+ (name) => (0, import_cookie.setOrDeleteCookie)(name, null, { noOpIfServerComponent: true })
607
+ )
608
+ );
609
+ }
610
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
420
611
  });
421
612
  });
422
613
  return store;
@@ -447,11 +638,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
447
638
  }
448
639
  const cookieHeader = tokenStoreInit.headers.get("cookie");
449
640
  const parsed = cookie.parse(cookieHeader || "");
450
- const res = new import_stores.Store({
451
- refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
452
- // keep old cookie name for backwards-compatibility
453
- accessToken: parsed[this._accessTokenCookieName] || null
454
- });
641
+ const res = new import_stores.Store(this._getTokensFromCookies(parsed));
455
642
  this._requestTokenStores.set(tokenStoreInit, res);
456
643
  return res;
457
644
  } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
@@ -797,35 +984,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
797
984
  const tokens = await this.currentSession.getTokens();
798
985
  return tokens;
799
986
  },
800
- async registerPasskey(options) {
801
- const hostname = (await app._getCurrentUrl())?.hostname;
802
- if (!hostname) {
803
- throw new import_errors.StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
804
- }
805
- const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
806
- if (initiationResult.status !== "ok") {
807
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
808
- }
809
- const { options_json, code } = initiationResult.data;
810
- if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
811
- throw new import_errors.StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
812
- }
813
- options_json.rp.id = hostname;
814
- let attResp;
815
- try {
816
- attResp = await (0, import_browser.startRegistration)({ optionsJSON: options_json });
817
- } catch (error) {
818
- if (error instanceof import_browser.WebAuthnError) {
819
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyWebAuthnError(error.message, error.name));
820
- } else {
821
- (0, import_errors.captureError)("passkey-registration-failed", error);
822
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
823
- }
824
- }
825
- const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
826
- await app._refreshUser(session);
827
- return registrationResult;
828
- },
829
987
  signOut(options) {
830
988
  return app._signOut(session, options);
831
989
  }
@@ -1074,6 +1232,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1074
1232
  async getOAuthProvider(id) {
1075
1233
  const providers = await this.listOAuthProviders();
1076
1234
  return providers.find((p) => p.id === id) ?? null;
1235
+ },
1236
+ async registerPasskey(options) {
1237
+ const hostname = (await app._getCurrentUrl())?.hostname;
1238
+ if (!hostname) {
1239
+ throw new import_errors.StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
1240
+ }
1241
+ const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
1242
+ if (initiationResult.status !== "ok") {
1243
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
1244
+ }
1245
+ const { options_json, code } = initiationResult.data;
1246
+ if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
1247
+ throw new import_errors.StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
1248
+ }
1249
+ options_json.rp.id = hostname;
1250
+ let attResp;
1251
+ try {
1252
+ attResp = await (0, import_browser.startRegistration)({ optionsJSON: options_json });
1253
+ } catch (error) {
1254
+ if (error instanceof import_browser.WebAuthnError) {
1255
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyWebAuthnError(error.message, error.name));
1256
+ } else {
1257
+ (0, import_errors.captureError)("passkey-registration-failed", error);
1258
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
1259
+ }
1260
+ }
1261
+ const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
1262
+ await app._refreshUser(session);
1263
+ return registrationResult;
1077
1264
  }
1078
1265
  };
1079
1266
  }
@@ -1842,10 +2029,22 @@ ${url}`);
1842
2029
  });
1843
2030
  }
1844
2031
  async signOut(options) {
1845
- const user = await this.getUser();
2032
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
2033
+ if (user) {
2034
+ await user.signOut({ redirectUrl: options?.redirectUrl });
2035
+ }
2036
+ }
2037
+ async getAuthHeaders(options) {
2038
+ return {
2039
+ "x-stack-auth": JSON.stringify(await this.getAuthJson(options))
2040
+ };
2041
+ }
2042
+ async getAuthJson(options) {
2043
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1846
2044
  if (user) {
1847
- await user.signOut(options);
2045
+ return await user.getAuthJson();
1848
2046
  }
2047
+ return { accessToken: null, refreshToken: null };
1849
2048
  }
1850
2049
  async getProject() {
1851
2050
  const crud = import_results.Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));