@robelest/convex-auth 0.0.4-preview.29 → 0.0.4-preview.31

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 (113) hide show
  1. package/dist/bin.js +143 -21
  2. package/dist/browser/index.d.ts +3 -13
  3. package/dist/browser/index.js +47 -12
  4. package/dist/browser/navigation.js +1 -1
  5. package/dist/browser/passkey.js +9 -9
  6. package/dist/browser/runtime.js +13 -15
  7. package/dist/client/core/types.d.ts +177 -72
  8. package/dist/client/core/types.js +6 -0
  9. package/dist/client/factors/device.js +3 -3
  10. package/dist/client/factors/totp.js +9 -9
  11. package/dist/client/index.d.ts +5 -4
  12. package/dist/client/index.js +122 -63
  13. package/dist/client/runtime/mutex.js +3 -2
  14. package/dist/component/_generated/component.d.ts +40 -0
  15. package/dist/component/convex.config.d.ts +2 -2
  16. package/dist/component/http.js +9 -0
  17. package/dist/component/index.d.ts +1 -1
  18. package/dist/component/model.d.ts +25 -25
  19. package/dist/component/model.js +2 -1
  20. package/dist/component/modules.js +1 -0
  21. package/dist/component/public/factors/passkeys.js +31 -1
  22. package/dist/component/public/identity/codes.js +1 -1
  23. package/dist/component/public/identity/tokens.js +2 -1
  24. package/dist/component/public/identity/verifiers.js +15 -5
  25. package/dist/component/public.js +2 -2
  26. package/dist/component/schema.d.ts +287 -285
  27. package/dist/component/schema.js +2 -1
  28. package/dist/core/index.d.ts +10 -24
  29. package/dist/core/index.js +8 -16
  30. package/dist/expo/index.d.ts +21 -0
  31. package/dist/expo/index.js +148 -0
  32. package/dist/expo/passkey.js +174 -0
  33. package/dist/providers/apple.d.ts +1 -1
  34. package/dist/providers/apple.js +6 -8
  35. package/dist/providers/custom.d.ts +1 -1
  36. package/dist/providers/custom.js +4 -7
  37. package/dist/providers/github.d.ts +1 -1
  38. package/dist/providers/github.js +5 -8
  39. package/dist/providers/google.d.ts +1 -1
  40. package/dist/providers/google.js +5 -8
  41. package/dist/providers/microsoft.d.ts +1 -1
  42. package/dist/providers/microsoft.js +5 -9
  43. package/dist/providers/password.d.ts +18 -37
  44. package/dist/providers/password.js +170 -115
  45. package/dist/providers/redirect.d.ts +1 -0
  46. package/dist/providers/redirect.js +20 -0
  47. package/dist/server/auth.d.ts +9 -24
  48. package/dist/server/auth.js +4 -8
  49. package/dist/server/{ctxCache.js → cache/context.js} +2 -2
  50. package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
  51. package/dist/server/config.js +10 -0
  52. package/dist/server/context.js +5 -14
  53. package/dist/server/contract.d.ts +2 -87
  54. package/dist/server/contract.js +1 -1
  55. package/dist/server/cookies.js +25 -1
  56. package/dist/server/core.js +1 -14
  57. package/dist/server/device.js +13 -12
  58. package/dist/server/env.js +10 -2
  59. package/dist/server/errors.js +24 -1
  60. package/dist/server/{auth-context.d.ts → facade.d.ts} +4 -46
  61. package/dist/server/{auth-context.js → facade.js} +23 -11
  62. package/dist/server/http.d.ts +7 -7
  63. package/dist/server/http.js +36 -7
  64. package/dist/server/identity/convex.d.ts +15 -0
  65. package/dist/server/identity/convex.js +1 -0
  66. package/dist/server/identity.js +30 -4
  67. package/dist/server/index.d.ts +5 -2
  68. package/dist/server/index.js +3 -1
  69. package/dist/server/mounts.d.ts +246 -93
  70. package/dist/server/mutations/code.js +7 -1
  71. package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
  72. package/dist/server/mutations/index.js +1 -1
  73. package/dist/server/mutations/invalidate.js +11 -1
  74. package/dist/server/mutations/oauth.js +25 -27
  75. package/dist/server/mutations/refresh.js +38 -7
  76. package/dist/server/mutations/signin.js +18 -2
  77. package/dist/server/mutations/signout.js +32 -10
  78. package/dist/server/mutations/store.js +2 -2
  79. package/dist/server/oauth/factory.js +13 -4
  80. package/dist/server/passkey.js +130 -115
  81. package/dist/server/prefetch.js +16 -9
  82. package/dist/server/redirects.js +11 -3
  83. package/dist/server/refresh.js +6 -1
  84. package/dist/server/runtime.d.ts +56 -40
  85. package/dist/server/runtime.js +340 -76
  86. package/dist/server/services/group.js +4 -0
  87. package/dist/server/sessions.d.ts +2 -1
  88. package/dist/server/sessions.js +22 -11
  89. package/dist/server/signin.js +25 -22
  90. package/dist/server/sso/domain.d.ts +159 -16
  91. package/dist/server/sso/domain.js +1 -1
  92. package/dist/server/sso/http.js +144 -60
  93. package/dist/server/sso/oidc.js +28 -12
  94. package/dist/server/sso/policy.js +30 -14
  95. package/dist/server/sso/provision.js +1 -1
  96. package/dist/server/sso/saml.js +18 -9
  97. package/dist/server/sso/scim.js +12 -4
  98. package/dist/server/sso/shared.js +5 -5
  99. package/dist/server/telemetry.js +61 -0
  100. package/dist/server/tokens.js +34 -6
  101. package/dist/server/totp.js +135 -106
  102. package/dist/server/types.d.ts +281 -152
  103. package/dist/server/url.js +1 -1
  104. package/dist/server/users.js +93 -53
  105. package/dist/server/utils/span.js +10 -1
  106. package/dist/server/wellknown.d.ts +130 -0
  107. package/dist/server/wellknown.js +195 -0
  108. package/dist/shared/errors.js +0 -1
  109. package/package.json +39 -4
  110. package/dist/server/constants.js +0 -6
  111. package/dist/server/oauth/index.js +0 -12
  112. package/dist/server/utils/dispatch.js +0 -36
  113. /package/dist/server/{componentContext.js → component/context.js} +0 -0
package/dist/bin.js CHANGED
@@ -6253,6 +6253,15 @@ gradient.pastel = pastel;
6253
6253
 
6254
6254
  //#endregion
6255
6255
  //#region src/cli/keys.ts
6256
+ /**
6257
+ * Generate a fresh JWT signing keypair, JWKS payload, and secret-encryption key.
6258
+ *
6259
+ * Used by the Convex Auth setup wizard to provision required environment
6260
+ * variables on a target Convex deployment.
6261
+ *
6262
+ * @returns Generated `JWT_PRIVATE_KEY`, `JWKS`, and `AUTH_SECRET_ENCRYPTION_KEY` values.
6263
+ * @internal
6264
+ */
6256
6265
  async function generateKeys() {
6257
6266
  try {
6258
6267
  const keys = await generateKeyPair("EdDSA", {
@@ -6266,7 +6275,7 @@ async function generateKeys() {
6266
6275
  ...publicKey
6267
6276
  }] });
6268
6277
  return {
6269
- JWT_PRIVATE_KEY: `${privateKey.trimEnd().replace(/\n/g, " ")}`,
6278
+ JWT_PRIVATE_KEY: privateKey.trimEnd(),
6270
6279
  JWKS: jwks,
6271
6280
  AUTH_SECRET_ENCRYPTION_KEY: randomBytes(32).toString("base64url")
6272
6281
  };
@@ -6408,6 +6417,11 @@ function printHelp() {
6408
6417
  printBanner();
6409
6418
  console.log(" Add code and set environment variables for @robelest/convex-auth.\n");
6410
6419
  console.log(" Full docs: https://auth.estifanos.com\n");
6420
+ console.log(" Commands:\n");
6421
+ console.log(" setup Scaffold files and set env vars");
6422
+ console.log(" doctor Verify env, files, and mounted auth endpoints");
6423
+ console.log(" urls Print auth endpoint and provider callback URLs");
6424
+ console.log(" keys Generate signing/encryption keys\n");
6411
6425
  console.log(" Options:\n");
6412
6426
  for (const [name, def] of flagDefs) {
6413
6427
  const flag = def.type === "boolean" ? `--${name}` : `--${name} <value>`;
@@ -6416,7 +6430,16 @@ function printHelp() {
6416
6430
  console.log();
6417
6431
  }
6418
6432
  function parseArgs(argv) {
6419
- const args = argv.slice(2);
6433
+ const rawArgs = argv.slice(2);
6434
+ const knownCommands = new Set([
6435
+ "setup",
6436
+ "doctor",
6437
+ "urls",
6438
+ "keys"
6439
+ ]);
6440
+ const firstArg = rawArgs[0];
6441
+ const command = knownCommands.has(firstArg ?? "") ? firstArg : "setup";
6442
+ const args = command === "setup" && !knownCommands.has(firstArg ?? "") ? rawArgs : rawArgs.slice(1);
6420
6443
  const strings = /* @__PURE__ */ new Map();
6421
6444
  const booleans = /* @__PURE__ */ new Set();
6422
6445
  let i = 0;
@@ -6454,6 +6477,7 @@ function parseArgs(argv) {
6454
6477
  process$1.exit(0);
6455
6478
  }
6456
6479
  return {
6480
+ command,
6457
6481
  siteUrl: strings.get("site-url"),
6458
6482
  secondaryUrl: strings.get("secondary-url"),
6459
6483
  variables: strings.get("variables"),
@@ -6505,15 +6529,80 @@ async function runSetup(options) {
6505
6529
  await modifyTsConfig(config);
6506
6530
  await configureConvexConfig(config);
6507
6531
  await initializeAuth(config);
6532
+ await initializeHttp(config);
6508
6533
  await initializeAuthCore(config);
6509
- await configureHttp(config);
6534
+ await initializeAuthConfig(config);
6510
6535
  if (options.variables !== void 0) await configureOtherVariables(config, options.variables);
6511
6536
  else printFinalSuccessMessage(config);
6512
6537
  gt("Done! Happy building.");
6513
6538
  }
6539
+ async function runDoctor(options) {
6540
+ validateDeploymentSelectionOptions(options);
6541
+ printBanner();
6542
+ mt("Checking Convex Auth setup...");
6543
+ const convexFolderPath = readConvexJson().functions ?? "convex";
6544
+ const configPath = existingNonEmptySourcePath(path.join(convexFolderPath, "convex.config"));
6545
+ const authPath = existingNonEmptySourcePath(path.join(convexFolderPath, "auth"));
6546
+ const authConfigPath = existingNonEmptySourcePath(path.join(convexFolderPath, "auth.config"));
6547
+ if (configPath === null) O.warn("Missing convex.config.ts/js.");
6548
+ else O.success(`Found ${configPath}`);
6549
+ if (authPath === null) O.warn("Missing auth.ts/js.");
6550
+ else O.success(`Found ${authPath}`);
6551
+ if (authConfigPath === null) O.warn("Missing auth.config.ts/js.");
6552
+ else O.success(`Found ${authConfigPath}`);
6553
+ gt("Doctor checks complete.");
6554
+ }
6555
+ async function runUrls(options) {
6556
+ validateDeploymentSelectionOptions(options);
6557
+ printBanner();
6558
+ const authSiteUrl = `${(readConvexDeployment(options).options.url ?? process$1.env.CONVEX_SITE_URL ?? "https://<deployment>.convex.site").replace(/\/$/, "")}/auth`;
6559
+ O.info("Convex Auth URLs:");
6560
+ O.message([
6561
+ `Issuer: ${authSiteUrl}`,
6562
+ `OpenID configuration: ${authSiteUrl}/.well-known/openid-configuration`,
6563
+ `JWKS: ${authSiteUrl}/.well-known/jwks.json`,
6564
+ `OAuth sign-in base: ${authSiteUrl}/signin/<provider>`,
6565
+ `OAuth callback base: ${authSiteUrl}/callback/<provider>`,
6566
+ `SSO connections base: ${authSiteUrl}/connections/<connectionId>`
6567
+ ].join("\n"));
6568
+ }
6569
+ async function runKeys(options) {
6570
+ validateDeploymentSelectionOptions(options);
6571
+ printBanner();
6572
+ const convexJson = readConvexJson();
6573
+ const deployment = readConvexDeployment(options);
6574
+ await configureKeys({
6575
+ isExpo: false,
6576
+ isNextjs: false,
6577
+ isVite: false,
6578
+ usesTypeScript: true,
6579
+ convexFolderPath: convexJson.functions ?? "convex",
6580
+ deployment,
6581
+ step: 1
6582
+ });
6583
+ }
6584
+ /**
6585
+ * Run the interactive Convex Auth setup wizard.
6586
+ *
6587
+ * Parses CLI flags, detects the target Convex deployment, configures required
6588
+ * environment variables, and scaffolds the expected auth files in the current
6589
+ * project.
6590
+ *
6591
+ * @param argv - Process arguments. Defaults to `process.argv`.
6592
+ * @returns A promise that resolves when setup completes successfully.
6593
+ */
6514
6594
  const runCli = async (argv = process$1.argv) => {
6515
- await runSetup(parseArgs(argv));
6595
+ const options = parseArgs(argv);
6596
+ if (options.command === "setup") await runSetup(options);
6597
+ else if (options.command === "doctor") await runDoctor(options);
6598
+ else if (options.command === "urls") await runUrls(options);
6599
+ else if (options.command === "keys") await runKeys(options);
6516
6600
  };
6601
+ /**
6602
+ * Run the Convex Auth CLI and exit the process on failure.
6603
+ *
6604
+ * @param argv - Process arguments. Defaults to `process.argv`.
6605
+ */
6517
6606
  function runCliMain(argv = process$1.argv) {
6518
6607
  runCli(argv).catch((err) => {
6519
6608
  console.error(err);
@@ -6789,6 +6878,33 @@ export const { signIn, signOut, store } = auth;
6789
6878
  O.success(`Created ${newAuthPath}`);
6790
6879
  }
6791
6880
  }
6881
+ async function initializeHttp(config) {
6882
+ logStep(config, "Initialize HTTP auth routes");
6883
+ const sourceTemplate = `\
6884
+ import { auth } from "./auth";
6885
+
6886
+ export default auth.http();
6887
+ `;
6888
+ const source = templateToSource(sourceTemplate);
6889
+ const httpPath = path.join(config.convexFolderPath, "http");
6890
+ const existingPath = existingNonEmptySourcePath(httpPath);
6891
+ if (existingPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingPath, "utf8"), sourceTemplate)) O.success(`${existingPath} is already set up.`);
6892
+ else {
6893
+ O.info(`You already have ${existingPath}. Make sure it mounts Convex Auth protocol routes:`);
6894
+ O.message(indent(`\n${source}\n`));
6895
+ const ready = await ot({ message: "Ready to continue?" });
6896
+ handleCancel(ready);
6897
+ if (!ready) {
6898
+ pt("Setup cancelled.");
6899
+ process$1.exit(1);
6900
+ }
6901
+ }
6902
+ else {
6903
+ const newPath = config.usesTypeScript ? `${httpPath}.ts` : `${httpPath}.js`;
6904
+ writeFileSync(newPath, source);
6905
+ O.success(`Created ${newPath}`);
6906
+ }
6907
+ }
6792
6908
  async function initializeAuthCore(config) {
6793
6909
  logStep(config, "Initialize auth/core file");
6794
6910
  const sourceTemplate = `\
@@ -6819,24 +6935,24 @@ export const auth = createAuthContext(components.auth);
6819
6935
  O.success(`Created ${newPath}`);
6820
6936
  }
6821
6937
  }
6822
- async function configureHttp(config) {
6823
- logStep(config, "Configure http file");
6938
+ async function initializeAuthConfig(config) {
6939
+ logStep(config, "Initialize auth.config file");
6824
6940
  const sourceTemplate = `\
6825
- import { httpRouter } from "convex/server";
6826
- import { auth } from "./auth";
6827
-
6828
- const http = httpRouter();
6829
-
6830
- auth.http.add(http);
6831
-
6832
- export default http;
6941
+ export default {$$
6942
+ providers: [$$
6943
+ {$$
6944
+ domain: process.env.CONVEX_SITE_URL + "/auth",$$
6945
+ applicationID: "convex",$$
6946
+ },$$
6947
+ ],$$
6948
+ };
6833
6949
  `;
6834
6950
  const source = templateToSource(sourceTemplate);
6835
- const httpPath = path.join(config.convexFolderPath, "http");
6836
- const existingHttpPath = existingNonEmptySourcePath(httpPath);
6837
- if (existingHttpPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingHttpPath, "utf8"), sourceTemplate)) O.success(`${existingHttpPath} is already set up.`);
6951
+ const authConfigPath = path.join(config.convexFolderPath, "auth.config");
6952
+ const existingPath = existingNonEmptySourcePath(authConfigPath);
6953
+ if (existingPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingPath, "utf8"), sourceTemplate)) O.success(`${existingPath} is already set up.`);
6838
6954
  else {
6839
- O.info(`You already have ${existingHttpPath}. Make sure it includes auth.http.add:`);
6955
+ O.info(`You already have ${existingPath}. Make sure it trusts CONVEX_SITE_URL as the Convex auth issuer:`);
6840
6956
  O.message(indent(`\n${source}\n`));
6841
6957
  const ready = await ot({ message: "Ready to continue?" });
6842
6958
  handleCancel(ready);
@@ -6846,9 +6962,9 @@ export default http;
6846
6962
  }
6847
6963
  }
6848
6964
  else {
6849
- const newHttpPath = config.usesTypeScript ? `${httpPath}.ts` : `${httpPath}.js`;
6850
- writeFileSync(newHttpPath, source);
6851
- O.success(`Created ${newHttpPath}`);
6965
+ const newPath = config.usesTypeScript ? `${authConfigPath}.ts` : `${authConfigPath}.js`;
6966
+ writeFileSync(newPath, source);
6967
+ O.success(`Created ${newPath}`);
6852
6968
  }
6853
6969
  }
6854
6970
  function validateVariablesConfig(value) {
@@ -6914,9 +7030,11 @@ async function configureOtherVariables(config, json) {
6914
7030
  }
6915
7031
  if (variables.success !== void 0) O.success(variables.success);
6916
7032
  }
7033
+ /** @internal */
6917
7034
  function doesAlreadyMatchTemplate(existing, template) {
6918
7035
  return new RegExp(template.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\$\\\$/g, ".*").replace(/;\n/g, ";.*"), "s").test(existing);
6919
7036
  }
7037
+ /** @internal */
6920
7038
  function templateToSource(template) {
6921
7039
  return template.replace(/\$\$/g, "");
6922
7040
  }
@@ -6988,6 +7106,7 @@ function loadEnvFiles() {
6988
7106
  override: false
6989
7107
  });
6990
7108
  }
7109
+ /** @internal */
6991
7110
  function readConvexDeployment(options) {
6992
7111
  const { adminKey, url, prod, previewName, deploymentName } = options;
6993
7112
  if (url) return {
@@ -7030,6 +7149,7 @@ function readConvexDeployment(options) {
7030
7149
  }
7031
7150
  logErrorAndExit("Could not find a configured CONVEX_DEPLOYMENT. Did you forget to run `npx convex dev` first?");
7032
7151
  }
7152
+ /** @internal */
7033
7153
  function stripDeploymentTypePrefix(deployment) {
7034
7154
  const [type, name] = deployment.split(":");
7035
7155
  if (type !== "prod" && type !== "dev" && type !== "preview" || !name) logErrorAndExit("Invalid CONVEX_DEPLOYMENT.", "Expected a typed deployment like \"dev:my-deployment\", \"prod:my-deployment\", or \"preview:my-deployment\".");
@@ -7044,11 +7164,13 @@ function deploymentNameFromAdminKey(adminKey) {
7044
7164
  const parts = adminKey.split("|");
7045
7165
  return parts.length > 1 && !isPreviewDeployKey(adminKey) ? stripDeploymentTypePrefix(parts[0]) : null;
7046
7166
  }
7167
+ /** @internal */
7047
7168
  function deploymentTypeFromAdminKey(adminKey) {
7048
7169
  const type = adminKey.split(":")[0];
7049
7170
  if (type === "prod" || type === "dev" || type === "preview") return type;
7050
7171
  logErrorAndExit("Invalid admin key.", "Expected a typed key like \"dev:deployment|...\", \"prod:deployment|...\", or \"preview:...\".");
7051
7172
  }
7173
+ /** @internal */
7052
7174
  function isPreviewDeployKey(adminKey) {
7053
7175
  const parts = adminKey.split("|");
7054
7176
  if (parts.length === 1) return false;
@@ -1,4 +1,4 @@
1
- import { AuthApiRefs, BrowserAuthClient, ClientOptions } from "../client/core/types.js";
1
+ import { AuthApiRefs, ClientOptions, PlatformAuthClient } from "../client/core/types.js";
2
2
  import "../client/index.js";
3
3
 
4
4
  //#region src/browser/index.d.ts
@@ -13,18 +13,8 @@ import "../client/index.js";
13
13
  * @typeParam Api - Auth API references that control which factor helpers are
14
14
  * available on the returned client.
15
15
  * @returns A browser auth client with the configured auth helpers.
16
- *
17
- * @example
18
- * ```ts
19
- * import { ConvexReactClient } from "convex/react";
20
- * import { client } from "@robelest/convex-auth/browser";
21
- * import { api } from "../convex/_generated/api";
22
- *
23
- * const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
24
- * const auth = client({ convex, api: api.auth });
25
- * ```
26
16
  */
27
- declare function client<Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs>(options: ClientOptions<Api>): BrowserAuthClient<Api>;
17
+ declare function client<Api extends AuthApiRefs<boolean, boolean, boolean> = AuthApiRefs>(options: ClientOptions<Api>): PlatformAuthClient<Api>;
28
18
  //#endregion
29
- export { type AuthApiRefs, type BrowserAuthClient as AuthClient, type ClientOptions, client };
19
+ export { type AuthApiRefs, type PlatformAuthClient as AuthClient, type ClientOptions, client };
30
20
  //# sourceMappingURL=index.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { LOG_LEVELS, logMessage } from "../shared/log.js";
1
2
  import { ClientAdapterFactoriesLive, ClientAdaptersLive } from "../client/services/adapters.js";
2
3
  import { ClientHttpLive } from "../client/services/http.js";
3
4
  import { resolveClientServices } from "../client/services/resolve.js";
@@ -13,7 +14,7 @@ import { ConvexHttpClient } from "convex/browser";
13
14
  *
14
15
  * This entrypoint wraps the framework-agnostic `client(...)`
15
16
  * helper with browser defaults such as `ConvexHttpClient`, local storage, URL
16
- * replacement, and passkey adapters.
17
+ * replacement, OAuth launching, and passkey adapters.
17
18
  *
18
19
  * @module
19
20
  */
@@ -28,16 +29,6 @@ import { ConvexHttpClient } from "convex/browser";
28
29
  * @typeParam Api - Auth API references that control which factor helpers are
29
30
  * available on the returned client.
30
31
  * @returns A browser auth client with the configured auth helpers.
31
- *
32
- * @example
33
- * ```ts
34
- * import { ConvexReactClient } from "convex/react";
35
- * import { client } from "@robelest/convex-auth/browser";
36
- * import { api } from "../convex/_generated/api";
37
- *
38
- * const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
39
- * const auth = client({ convex, api: api.auth });
40
- * ```
41
32
  */
42
33
  function client(options) {
43
34
  const url = options.proxyPath === void 0 ? options.url ?? inferConvexUrl(options.convex) : void 0;
@@ -50,7 +41,7 @@ function client(options) {
50
41
  }),
51
42
  http: ClientHttpLive(options.proxyPath !== void 0 ? null : options.httpClient ?? (url ? new ConvexHttpClient(url) : null))
52
43
  });
53
- return client$1({
44
+ const baseClient = client$1({
54
45
  ...options,
55
46
  storage: options.storage === void 0 && options.proxyPath !== void 0 ? null : options.storage,
56
47
  runtime: services.runtime,
@@ -58,6 +49,49 @@ function client(options) {
58
49
  adapterFactories: services.adapterFactories,
59
50
  httpClient: services.httpClient
60
51
  });
52
+ const completeOAuth = async (input) => {
53
+ const result = await baseClient.completeOAuth(input);
54
+ if (result.handled && result.cleanupUrl && services.runtime.location) {
55
+ const current = services.runtime.location.get();
56
+ const cleanupUrl = result.cleanupUrl;
57
+ const relativeUrl = current !== null && cleanupUrl.origin === current.origin ? `${cleanupUrl.pathname}${cleanupUrl.search}${cleanupUrl.hash}` : cleanupUrl.toString();
58
+ await services.runtime.location.replace(relativeUrl);
59
+ }
60
+ return result;
61
+ };
62
+ const initialize = async () => {
63
+ await baseClient.initialize();
64
+ const current = services.runtime.location?.get();
65
+ if (current?.searchParams.has("code") && current.searchParams.has("state")) await completeOAuth(current);
66
+ };
67
+ const signIn = async (provider, ...args) => {
68
+ const params = args[0];
69
+ const result = await baseClient.signIn(provider, params);
70
+ if (result.kind === "redirect") await services.runtime.oauth?.open(result.redirect);
71
+ return result;
72
+ };
73
+ const browserClient = {
74
+ get state() {
75
+ return baseClient.state;
76
+ },
77
+ initialize,
78
+ param: baseClient.param,
79
+ get invite() {
80
+ return baseClient.invite;
81
+ },
82
+ completeOAuth,
83
+ signIn,
84
+ signOut: baseClient.signOut,
85
+ onChange: baseClient.onChange,
86
+ destroy: baseClient.destroy,
87
+ ..."totp" in baseClient ? { totp: baseClient.totp } : {},
88
+ ..."device" in baseClient ? { device: baseClient.device } : {},
89
+ ..."passkey" in baseClient ? { passkey: baseClient.passkey } : {}
90
+ };
91
+ initialize().catch((error) => {
92
+ logMessage("convex-auth/browser", LOG_LEVELS.ERROR, ["[convex-auth] Browser client initialization failed:", error]);
93
+ });
94
+ return browserClient;
61
95
  }
62
96
  function mergeBrowserRuntime(runtime) {
63
97
  const defaults = createBrowserRuntime();
@@ -68,6 +102,7 @@ function mergeBrowserRuntime(runtime) {
68
102
  proxy: runtime?.proxy ?? defaults.proxy,
69
103
  storage: runtime?.storage === void 0 ? defaults.storage : runtime.storage,
70
104
  location: runtime?.location ?? defaults.location,
105
+ oauth: runtime?.oauth ?? defaults.oauth,
71
106
  sync: runtime?.sync ?? defaults.sync,
72
107
  mutex: runtime?.mutex ?? defaults.mutex
73
108
  };
@@ -4,7 +4,7 @@ const BrowserNavigationLive = {
4
4
  replace: (url) => {
5
5
  if (typeof window !== "undefined") window.history.replaceState({}, "", url);
6
6
  },
7
- redirect: (url) => {
7
+ open: (url) => {
8
8
  if (typeof window !== "undefined") window.location.href = url.toString();
9
9
  }
10
10
  };
@@ -8,7 +8,7 @@ function createPasskeyClient(deps) {
8
8
  if (result.kind !== "signedIn") return { kind: "started" };
9
9
  return await setTokenAndMaybeWait(proxy ? {
10
10
  shouldStore: false,
11
- tokens: result.tokens === null ? null : { token: result.tokens.token },
11
+ tokens: result.session === null ? null : { token: result.session.token },
12
12
  waitForHandshake: true,
13
13
  context: {
14
14
  provider: "passkey",
@@ -16,7 +16,7 @@ function createPasskeyClient(deps) {
16
16
  }
17
17
  } : {
18
18
  shouldStore: true,
19
- tokens: result.tokens,
19
+ tokens: result.session,
20
20
  waitForHandshake: true,
21
21
  context: {
22
22
  provider: "passkey",
@@ -37,7 +37,7 @@ function createPasskeyClient(deps) {
37
37
  },
38
38
  register: async (opts) => {
39
39
  const phase1Params = {
40
- flow: "registerOptions",
40
+ flow: "register",
41
41
  email: opts?.email,
42
42
  userName: opts?.userName,
43
43
  userDisplayName: opts?.userDisplayName
@@ -79,7 +79,7 @@ function createPasskeyClient(deps) {
79
79
  const response = credential.response;
80
80
  const transports = typeof response.getTransports === "function" ? response.getTransports() : void 0;
81
81
  const phase2Params = {
82
- flow: "registerVerify",
82
+ flow: "verify",
83
83
  clientDataJSON: base64urlEncode(response.clientDataJSON),
84
84
  attestationObject: base64urlEncode(response.attestationObject),
85
85
  transports,
@@ -100,11 +100,11 @@ function createPasskeyClient(deps) {
100
100
  params: phase2Params,
101
101
  verifier: phase1Result.verifier
102
102
  });
103
- return handleSignedInResult(phase2Result, "registerVerify");
103
+ return handleSignedInResult(phase2Result, "verify");
104
104
  },
105
- authenticate: async (opts) => {
105
+ signIn: async (opts) => {
106
106
  const phase1Params = {
107
- flow: "authOptions",
107
+ flow: "signIn",
108
108
  email: opts?.email
109
109
  };
110
110
  let phase1Result;
@@ -139,7 +139,7 @@ function createPasskeyClient(deps) {
139
139
  if (!credential) throw new Error("Passkey authentication was cancelled");
140
140
  const response = credential.response;
141
141
  const phase2Params = {
142
- flow: "authVerify",
142
+ flow: "verify",
143
143
  credentialId: base64urlEncode(credential.rawId),
144
144
  clientDataJSON: base64urlEncode(response.clientDataJSON),
145
145
  authenticatorData: base64urlEncode(response.authenticatorData),
@@ -159,7 +159,7 @@ function createPasskeyClient(deps) {
159
159
  params: phase2Params,
160
160
  verifier: phase1Result.verifier
161
161
  });
162
- return handleSignedInResult(phase2Result, "authVerify");
162
+ return handleSignedInResult(phase2Result, "verify");
163
163
  }
164
164
  };
165
165
  }
@@ -11,14 +11,10 @@ const browserStorage = {
11
11
  }
12
12
  },
13
13
  async setItem(key, value) {
14
- try {
15
- localStorage.setItem(key, value);
16
- } catch {}
14
+ localStorage.setItem(key, value);
17
15
  },
18
16
  async removeItem(key) {
19
- try {
20
- localStorage.removeItem(key);
21
- } catch {}
17
+ localStorage.removeItem(key);
22
18
  }
23
19
  };
24
20
  /** @internal */
@@ -30,7 +26,8 @@ function base64urlEncode(buffer) {
30
26
  }
31
27
  /** @internal */
32
28
  function base64urlDecode(str) {
33
- const padded = str.replace(/-/g, "+").replace(/_/g, "/");
29
+ const normalized = str.replace(/-/g, "+").replace(/_/g, "/");
30
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
34
31
  const binary = atob(padded);
35
32
  const bytes = new Uint8Array(binary.length);
36
33
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
@@ -52,14 +49,15 @@ function createBrowserRuntime() {
52
49
  replace: (url) => {
53
50
  BrowserNavigationLive.replace(url);
54
51
  return Promise.resolve();
55
- },
56
- redirect: (url) => {
57
- BrowserNavigationLive.redirect(url);
58
- return Promise.resolve();
59
52
  }
60
53
  },
54
+ oauth: { open: (url) => {
55
+ BrowserNavigationLive.open(url);
56
+ return Promise.resolve();
57
+ } },
61
58
  mutex: { withKey: (key, callback) => BrowserLocksLive.withKey(key, callback) },
62
59
  proxy: { fetch: async (body, proxyPath) => {
60
+ if (typeof window === "undefined" || window.location?.origin === void 0) throw new Error("Browser proxy fetch is unavailable outside the browser runtime.");
63
61
  return fetch(new URL(proxyPath, window.location.origin), {
64
62
  method: "POST",
65
63
  headers: { "Content-Type": "application/json" },
@@ -70,18 +68,18 @@ function createBrowserRuntime() {
70
68
  sync: { subscribe: (key, callback) => {
71
69
  if (typeof window === "undefined") return null;
72
70
  const registry = getStorageListenerRegistry();
73
- const existingSubscription = registry[key];
74
- if (existingSubscription !== void 0) existingSubscription();
71
+ if (registry[key] === void 0) registry[key] = /* @__PURE__ */ new Set();
75
72
  const controller = new AbortController();
76
73
  window.addEventListener("storage", (event) => {
77
74
  if (event.key !== key) return;
78
75
  callback(event.newValue ?? null);
79
76
  }, { signal: controller.signal });
80
77
  const unsubscribe = () => {
81
- if (registry[key] === unsubscribe) delete registry[key];
78
+ registry[key]?.delete(unsubscribe);
79
+ if (registry[key]?.size === 0) delete registry[key];
82
80
  controller.abort();
83
81
  };
84
- registry[key] = unsubscribe;
82
+ registry[key].add(unsubscribe);
85
83
  return unsubscribe;
86
84
  } }
87
85
  };