@stackframe/stack 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 +266 -0
  7. package/dist/components-page/stack-handler-client.js.map +1 -0
  8. package/dist/components-page/stack-handler.js +4 -243
  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 +242 -0
  17. package/dist/esm/components-page/stack-handler-client.js.map +1 -0
  18. package/dist/esm/components-page/stack-handler.js +5 -244
  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 +57 -14
  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 +257 -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 -98
  37. package/dist/index.d.ts +124 -98
  38. package/dist/lib/cookie.js +59 -14
  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 +256 -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 +8 -8
@@ -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_compile_time = require("@stackframe/stack-shared/dist/utils/compile-time");
40
41
  var import_env = require("@stackframe/stack-shared/dist/utils/env");
41
42
  var import_errors = require("@stackframe/stack-shared/dist/utils/errors");
@@ -61,6 +62,7 @@ var import_projects = require("../../projects/index.js");
61
62
  var import_teams = require("../../teams/index.js");
62
63
  var import_users = require("../../users/index.js");
63
64
  var import_common2 = require("./common.js");
65
+ var import_json = require("@stackframe/stack-shared/dist/utils/json");
64
66
  var import_common3 = require("./common.js");
65
67
  var sc = __toESM(require("@stackframe/stack-sc"));
66
68
  var import_stack_sc = require("@stackframe/stack-sc");
@@ -214,11 +216,15 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
214
216
  this._convexPartialUserCache = (0, import_common2.createCache)(
215
217
  async ([ctx]) => await this._getPartialUserFromConvex(ctx)
216
218
  );
219
+ this._trustedParentDomainCache = (0, import_common2.createCache)(
220
+ async ([domain]) => await this._getTrustedParentDomain(domain)
221
+ );
217
222
  this._anonymousSignUpInProgress = null;
218
223
  this._memoryTokenStore = (0, import_common2.createEmptyTokenStore)();
219
224
  this._nextServerCookiesTokenStores = /* @__PURE__ */ new WeakMap();
220
225
  this._requestTokenStores = /* @__PURE__ */ new WeakMap();
221
226
  this._storedBrowserCookieTokenStore = null;
227
+ this._mostRecentQueuedCookieRefreshIndex = 0;
222
228
  /**
223
229
  * A map from token stores and session keys to sessions.
224
230
  *
@@ -234,13 +240,17 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
234
240
  }
235
241
  this._options = resolvedOptions;
236
242
  this._extraOptions = extraOptions;
243
+ const projectId = resolvedOptions.projectId ?? (0, import_common2.getDefaultProjectId)();
244
+ 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)) {
245
+ throw new Error(`Invalid project ID: ${projectId}. Project IDs must be UUIDs. Please check your environment variables and/or your StackApp.`);
246
+ }
237
247
  if (extraOptions && extraOptions.interface) {
238
248
  this._interface = extraOptions.interface;
239
249
  } else {
240
250
  this._interface = new import_stack_shared.StackClientInterface({
241
251
  getBaseUrl: () => (0, import_common2.getBaseUrl)(resolvedOptions.baseUrl),
242
252
  extraRequestHeaders: resolvedOptions.extraRequestHeaders ?? (0, import_common2.getDefaultExtraRequestHeaders)(),
243
- projectId: resolvedOptions.projectId ?? (0, import_common2.getDefaultProjectId)(),
253
+ projectId,
244
254
  clientVersion: import_common2.clientVersion,
245
255
  publishableClientKey: resolvedOptions.publishableClientKey ?? (0, import_common2.getDefaultPublishableClientKey)(),
246
256
  prepareRequest: async () => {
@@ -343,13 +353,90 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
343
353
  (0, import_promises.runAsynchronously)(this._checkFeatureSupport(name, options));
344
354
  throw new import_errors.StackAssertionError(`${name} is not currently supported. Please reach out to Stack support for more information.`);
345
355
  }
356
+ get _legacyRefreshTokenCookieName() {
357
+ return `stack-refresh-${this.projectId}`;
358
+ }
346
359
  get _refreshTokenCookieName() {
347
360
  return `stack-refresh-${this.projectId}`;
348
361
  }
362
+ _getRefreshTokenDefaultCookieNameForSecure(secure) {
363
+ return `${secure ? "__Host-" : ""}${this._refreshTokenCookieName}--default`;
364
+ }
365
+ _getCustomRefreshCookieName(domain) {
366
+ const encoded = (0, import_bytes.encodeBase32)(new TextEncoder().encode(domain.toLowerCase()));
367
+ return `${this._refreshTokenCookieName}--custom-${encoded}`;
368
+ }
369
+ _formatRefreshCookieValue(refreshToken, updatedAt) {
370
+ return JSON.stringify({
371
+ refresh_token: refreshToken,
372
+ updated_at_millis: updatedAt
373
+ });
374
+ }
375
+ _formatAccessCookieValue(refreshToken, accessToken) {
376
+ return refreshToken && accessToken ? JSON.stringify([refreshToken, accessToken]) : null;
377
+ }
378
+ _parseStructuredRefreshCookie(value) {
379
+ if (!value) {
380
+ return null;
381
+ }
382
+ const parsed = (0, import_json.parseJson)(value);
383
+ if (parsed.status !== "ok" || typeof parsed.data !== "object" || parsed.data === null) {
384
+ console.warn("Failed to parse structured refresh cookie");
385
+ return null;
386
+ }
387
+ const data = parsed.data;
388
+ const refreshToken = "refresh_token" in data && typeof data.refresh_token === "string" ? data.refresh_token : null;
389
+ const updatedAt = "updated_at_millis" in data && typeof data.updated_at_millis === "number" ? data.updated_at_millis : null;
390
+ if (!refreshToken) {
391
+ console.warn("Refresh token not found in structured refresh cookie");
392
+ return null;
393
+ }
394
+ return {
395
+ refreshToken,
396
+ updatedAt
397
+ };
398
+ }
399
+ _extractRefreshTokenFromCookieMap(cookies2) {
400
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
401
+ for (const name of legacyNames) {
402
+ const value = cookies2[name];
403
+ if (value) {
404
+ return { refreshToken: value, updatedAt: null };
405
+ }
406
+ }
407
+ let selected = null;
408
+ for (const [name, value] of Object.entries(cookies2)) {
409
+ if (!structuredPrefixes.some((prefix) => name.startsWith(prefix))) continue;
410
+ const parsed = this._parseStructuredRefreshCookie(value);
411
+ if (!parsed) continue;
412
+ const candidateUpdatedAt = parsed.updatedAt ?? Number.NEGATIVE_INFINITY;
413
+ const selectedUpdatedAt = selected?.updatedAt ?? Number.NEGATIVE_INFINITY;
414
+ if (!selected || candidateUpdatedAt > selectedUpdatedAt) {
415
+ selected = parsed;
416
+ }
417
+ }
418
+ if (!selected) {
419
+ return { refreshToken: null, updatedAt: null };
420
+ }
421
+ return {
422
+ refreshToken: selected.refreshToken,
423
+ updatedAt: selected.updatedAt ?? null
424
+ };
425
+ }
349
426
  _getTokensFromCookies(cookies2) {
350
- const refreshToken = cookies2.refreshTokenCookie;
351
- const accessTokenObject = cookies2.accessTokenCookie?.startsWith('["') ? JSON.parse(cookies2.accessTokenCookie) : null;
352
- const accessToken = accessTokenObject && refreshToken === accessTokenObject[0] ? accessTokenObject[1] : null;
427
+ const { refreshToken } = this._extractRefreshTokenFromCookieMap(cookies2);
428
+ const accessTokenCookie = cookies2[this._accessTokenCookieName] ?? null;
429
+ let accessToken = null;
430
+ if (accessTokenCookie && accessTokenCookie.startsWith('["')) {
431
+ const parsed = (0, import_json.parseJson)(accessTokenCookie);
432
+ 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") {
433
+ if (parsed.data[0] === refreshToken) {
434
+ accessToken = parsed.data[1];
435
+ }
436
+ } else {
437
+ console.warn("Access token cookie has invalid format");
438
+ }
439
+ }
353
440
  return {
354
441
  refreshToken,
355
442
  accessToken
@@ -358,17 +445,100 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
358
445
  get _accessTokenCookieName() {
359
446
  return `stack-access`;
360
447
  }
448
+ _getAllBrowserCookies() {
449
+ if (!(0, import_env.isBrowserLike)()) {
450
+ throw new import_errors.StackAssertionError("Cannot get browser cookies on the server!");
451
+ }
452
+ return cookie.parse(document.cookie || "");
453
+ }
454
+ _getRefreshTokenCookieNamePatterns() {
455
+ return {
456
+ legacyNames: [this._legacyRefreshTokenCookieName, "stack-refresh"],
457
+ structuredPrefixes: [
458
+ `${this._refreshTokenCookieName}--`,
459
+ `__Host-${this._refreshTokenCookieName}--`
460
+ ]
461
+ };
462
+ }
463
+ _collectRefreshTokenCookieNames(cookies2) {
464
+ const { legacyNames, structuredPrefixes } = this._getRefreshTokenCookieNamePatterns();
465
+ const names = /* @__PURE__ */ new Set();
466
+ for (const name of legacyNames) {
467
+ if (cookies2[name]) {
468
+ names.add(name);
469
+ }
470
+ }
471
+ for (const name of Object.keys(cookies2)) {
472
+ if (structuredPrefixes.some((prefix) => name.startsWith(prefix))) {
473
+ names.add(name);
474
+ }
475
+ }
476
+ return names;
477
+ }
478
+ _prepareRefreshCookieUpdate(existingCookies, refreshToken, accessToken, defaultCookieName) {
479
+ const cookieNames = this._collectRefreshTokenCookieNames(existingCookies);
480
+ cookieNames.delete(defaultCookieName);
481
+ const updatedAt = refreshToken ? Date.now() : null;
482
+ const refreshCookieValue = refreshToken && updatedAt !== null ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
483
+ const accessTokenPayload = this._formatAccessCookieValue(refreshToken, accessToken);
484
+ return {
485
+ updatedAt,
486
+ refreshCookieValue,
487
+ accessTokenPayload,
488
+ cookieNamesToDelete: [...cookieNames]
489
+ };
490
+ }
491
+ _queueCustomRefreshCookieUpdate(refreshToken, updatedAt, context) {
492
+ (0, import_promises.runAsynchronously)(async () => {
493
+ this._mostRecentQueuedCookieRefreshIndex++;
494
+ const updateIndex = this._mostRecentQueuedCookieRefreshIndex;
495
+ let hostname;
496
+ if ((0, import_env.isBrowserLike)()) {
497
+ hostname = window.location.hostname;
498
+ } else {
499
+ hostname = (await sc.headers?.())?.get("host");
500
+ }
501
+ if (!hostname) {
502
+ console.warn("No hostname found when queueing custom refresh cookie update");
503
+ return;
504
+ }
505
+ const domain = await this._trustedParentDomainCache.getOrWait([hostname], "read-write");
506
+ const setCookie = async (targetDomain, value2) => {
507
+ const name = this._getCustomRefreshCookieName(targetDomain);
508
+ const options = { maxAge: 60 * 60 * 24 * 365, domain: targetDomain, noOpIfServerComponent: true };
509
+ if (context === "browser") {
510
+ (0, import_cookie.setOrDeleteCookieClient)(name, value2, options);
511
+ } else {
512
+ await (0, import_cookie.setOrDeleteCookie)(name, value2, options);
513
+ }
514
+ };
515
+ if (domain.status === "error" || !domain.data || updateIndex !== this._mostRecentQueuedCookieRefreshIndex) {
516
+ return;
517
+ }
518
+ const value = refreshToken && updatedAt ? this._formatRefreshCookieValue(refreshToken, updatedAt) : null;
519
+ await setCookie(domain.data, value);
520
+ });
521
+ }
522
+ async _getTrustedParentDomain(currentDomain) {
523
+ const project = import_results.Result.orThrow(await this._interface.getClientProject());
524
+ const domains = project.config.domains.map((d) => d.domain.trim().replace(/^https?:\/\//, "").split("/")[0]?.toLowerCase());
525
+ const trustedWildcards = domains.filter((d) => d.startsWith("**."));
526
+ const parts = currentDomain.split(".");
527
+ for (let i = parts.length - 2; i >= 0; i--) {
528
+ const parentDomain = parts.slice(i).join(".");
529
+ if (domains.includes(parentDomain) && trustedWildcards.includes("**." + parentDomain)) {
530
+ return parentDomain;
531
+ }
532
+ }
533
+ return null;
534
+ }
361
535
  _getBrowserCookieTokenStore() {
362
536
  if (!(0, import_env.isBrowserLike)()) {
363
537
  throw new Error("Cannot use cookie token store on the server!");
364
538
  }
365
539
  if (this._storedBrowserCookieTokenStore === null) {
366
540
  const getCurrentValue = (old) => {
367
- const tokens = this._getTokensFromCookies({
368
- refreshTokenCookie: (0, import_cookie.getCookieClient)(this._refreshTokenCookieName) ?? (0, import_cookie.getCookieClient)("stack-refresh"),
369
- // keep old cookie name for backwards-compatibility
370
- accessTokenCookie: (0, import_cookie.getCookieClient)(this._accessTokenCookieName)
371
- });
541
+ const tokens = this._getTokensFromCookies(this._getAllBrowserCookies());
372
542
  return {
373
543
  refreshToken: tokens.refreshToken,
374
544
  accessToken: tokens.accessToken ?? (old?.refreshToken === tokens.refreshToken ? old.accessToken : null)
@@ -387,9 +557,19 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
387
557
  }, 100);
388
558
  this._storedBrowserCookieTokenStore.onChange((value) => {
389
559
  try {
390
- (0, import_cookie.setOrDeleteCookieClient)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365 });
391
- (0, import_cookie.setOrDeleteCookieClient)(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24 });
392
- (0, import_cookie.deleteCookieClient)("stack-refresh");
560
+ const refreshToken = value.refreshToken;
561
+ const secure = window.location.protocol === "https:";
562
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
563
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
564
+ this._getAllBrowserCookies(),
565
+ refreshToken,
566
+ value.accessToken ?? null,
567
+ defaultName
568
+ );
569
+ (0, import_cookie.setOrDeleteCookieClient)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, secure });
570
+ (0, import_cookie.setOrDeleteCookieClient)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24 });
571
+ cookieNamesToDelete.forEach((name) => (0, import_cookie.deleteCookieClient)(name));
572
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "browser");
393
573
  hasSucceededInWriting = true;
394
574
  } catch (e) {
395
575
  if (!(0, import_env.isBrowserLike)()) {
@@ -412,18 +592,31 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
412
592
  if ((0, import_env.isBrowserLike)()) {
413
593
  return this._getBrowserCookieTokenStore();
414
594
  } else {
415
- const tokens = this._getTokensFromCookies({
416
- refreshTokenCookie: cookieHelper.get(this._refreshTokenCookieName) ?? cookieHelper.get("stack-refresh"),
417
- // keep old cookie name for backwards-compatibility
418
- accessTokenCookie: cookieHelper.get(this._accessTokenCookieName)
419
- });
595
+ const tokens = this._getTokensFromCookies(cookieHelper.getAll());
420
596
  const store = new import_stores.Store(tokens);
421
597
  store.onChange((value) => {
422
598
  (0, import_promises.runAsynchronously)(async () => {
599
+ const refreshToken = value.refreshToken;
600
+ const secure = await (0, import_cookie.isSecure)();
601
+ const defaultName = this._getRefreshTokenDefaultCookieNameForSecure(secure);
602
+ const { updatedAt, refreshCookieValue, accessTokenPayload, cookieNamesToDelete } = this._prepareRefreshCookieUpdate(
603
+ cookieHelper.getAll(),
604
+ refreshToken,
605
+ value.accessToken ?? null,
606
+ defaultName
607
+ );
423
608
  await Promise.all([
424
- (0, import_cookie.setOrDeleteCookie)(this._refreshTokenCookieName, value.refreshToken, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
425
- (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, value.accessToken ? JSON.stringify([value.refreshToken, value.accessToken]) : null, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
609
+ (0, import_cookie.setOrDeleteCookie)(defaultName, refreshCookieValue, { maxAge: 60 * 60 * 24 * 365, noOpIfServerComponent: true }),
610
+ (0, import_cookie.setOrDeleteCookie)(this._accessTokenCookieName, accessTokenPayload, { maxAge: 60 * 60 * 24, noOpIfServerComponent: true })
426
611
  ]);
612
+ if (cookieNamesToDelete.length > 0) {
613
+ await Promise.all(
614
+ cookieNamesToDelete.map(
615
+ (name) => (0, import_cookie.setOrDeleteCookie)(name, null, { noOpIfServerComponent: true })
616
+ )
617
+ );
618
+ }
619
+ this._queueCustomRefreshCookieUpdate(refreshToken, updatedAt, "server");
427
620
  });
428
621
  });
429
622
  return store;
@@ -454,11 +647,7 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
454
647
  }
455
648
  const cookieHeader = tokenStoreInit.headers.get("cookie");
456
649
  const parsed = cookie.parse(cookieHeader || "");
457
- const res = new import_stores.Store({
458
- refreshToken: parsed[this._refreshTokenCookieName] || parsed["stack-refresh"] || null,
459
- // keep old cookie name for backwards-compatibility
460
- accessToken: parsed[this._accessTokenCookieName] || null
461
- });
650
+ const res = new import_stores.Store(this._getTokensFromCookies(parsed));
462
651
  this._requestTokenStores.set(tokenStoreInit, res);
463
652
  return res;
464
653
  } else if ("accessToken" in tokenStoreInit || "refreshToken" in tokenStoreInit) {
@@ -804,35 +993,6 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
804
993
  const tokens = await this.currentSession.getTokens();
805
994
  return tokens;
806
995
  },
807
- async registerPasskey(options) {
808
- const hostname = (await app._getCurrentUrl())?.hostname;
809
- if (!hostname) {
810
- throw new import_errors.StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
811
- }
812
- const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
813
- if (initiationResult.status !== "ok") {
814
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
815
- }
816
- const { options_json, code } = initiationResult.data;
817
- if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
818
- throw new import_errors.StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
819
- }
820
- options_json.rp.id = hostname;
821
- let attResp;
822
- try {
823
- attResp = await (0, import_browser.startRegistration)({ optionsJSON: options_json });
824
- } catch (error) {
825
- if (error instanceof import_browser.WebAuthnError) {
826
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyWebAuthnError(error.message, error.name));
827
- } else {
828
- (0, import_errors.captureError)("passkey-registration-failed", error);
829
- return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
830
- }
831
- }
832
- const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
833
- await app._refreshUser(session);
834
- return registrationResult;
835
- },
836
996
  signOut(options) {
837
997
  return app._signOut(session, options);
838
998
  }
@@ -1081,6 +1241,35 @@ var __StackClientAppImplIncomplete = class __StackClientAppImplIncomplete {
1081
1241
  async getOAuthProvider(id) {
1082
1242
  const providers = await this.listOAuthProviders();
1083
1243
  return providers.find((p) => p.id === id) ?? null;
1244
+ },
1245
+ async registerPasskey(options) {
1246
+ const hostname = (await app._getCurrentUrl())?.hostname;
1247
+ if (!hostname) {
1248
+ throw new import_errors.StackAssertionError("hostname must be provided if the Stack App does not have a redirect method");
1249
+ }
1250
+ const initiationResult = await app._interface.initiatePasskeyRegistration({}, session);
1251
+ if (initiationResult.status !== "ok") {
1252
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to get initiation options for passkey registration"));
1253
+ }
1254
+ const { options_json, code } = initiationResult.data;
1255
+ if (options_json.rp.id !== "THIS_VALUE_WILL_BE_REPLACED.example.com") {
1256
+ throw new import_errors.StackAssertionError(`Expected returned RP ID from server to equal sentinel, but found ${options_json.rp.id}`);
1257
+ }
1258
+ options_json.rp.id = hostname;
1259
+ let attResp;
1260
+ try {
1261
+ attResp = await (0, import_browser.startRegistration)({ optionsJSON: options_json });
1262
+ } catch (error) {
1263
+ if (error instanceof import_browser.WebAuthnError) {
1264
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyWebAuthnError(error.message, error.name));
1265
+ } else {
1266
+ (0, import_errors.captureError)("passkey-registration-failed", error);
1267
+ return import_results.Result.error(new import_stack_shared.KnownErrors.PasskeyRegistrationFailed("Failed to start passkey registration due to unknown error"));
1268
+ }
1269
+ }
1270
+ const registrationResult = await app._interface.registerPasskey({ credential: attResp, code }, session);
1271
+ await app._refreshUser(session);
1272
+ return registrationResult;
1084
1273
  }
1085
1274
  };
1086
1275
  }
@@ -1854,10 +2043,22 @@ ${url}`);
1854
2043
  });
1855
2044
  }
1856
2045
  async signOut(options) {
1857
- const user = await this.getUser();
2046
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
2047
+ if (user) {
2048
+ await user.signOut({ redirectUrl: options?.redirectUrl });
2049
+ }
2050
+ }
2051
+ async getAuthHeaders(options) {
2052
+ return {
2053
+ "x-stack-auth": JSON.stringify(await this.getAuthJson(options))
2054
+ };
2055
+ }
2056
+ async getAuthJson(options) {
2057
+ const user = await this.getUser({ tokenStore: options?.tokenStore ?? void 0 });
1858
2058
  if (user) {
1859
- await user.signOut(options);
2059
+ return await user.getAuthJson();
1860
2060
  }
2061
+ return { accessToken: null, refreshToken: null };
1861
2062
  }
1862
2063
  async getProject() {
1863
2064
  const crud = import_results.Result.orThrow(await this._currentProjectCache.getOrWait([], "write-only"));