@robelest/convex-auth 0.0.4-preview.30 → 0.0.4-preview.32

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 (104) hide show
  1. package/dist/bin.js +125 -36
  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 +7 -7
  6. package/dist/browser/runtime.js +13 -15
  7. package/dist/client/core/types.d.ts +179 -63
  8. package/dist/client/core/types.js +6 -0
  9. package/dist/client/factors/totp.js +1 -1
  10. package/dist/client/index.d.ts +5 -4
  11. package/dist/client/index.js +115 -56
  12. package/dist/client/runtime/mutex.js +3 -2
  13. package/dist/component/_generated/component.d.ts +40 -0
  14. package/dist/component/http.js +9 -0
  15. package/dist/component/index.d.ts +1 -1
  16. package/dist/component/model.d.ts +27 -27
  17. package/dist/component/model.js +2 -1
  18. package/dist/component/modules.js +1 -0
  19. package/dist/component/public/factors/passkeys.js +31 -1
  20. package/dist/component/public/identity/codes.js +1 -1
  21. package/dist/component/public/identity/tokens.js +2 -1
  22. package/dist/component/public/identity/verifiers.js +15 -5
  23. package/dist/component/public.js +2 -2
  24. package/dist/component/schema.d.ts +287 -285
  25. package/dist/component/schema.js +2 -1
  26. package/dist/core/index.d.ts +8 -3
  27. package/dist/core/index.js +7 -2
  28. package/dist/expo/index.d.ts +21 -0
  29. package/dist/expo/index.js +148 -0
  30. package/dist/expo/passkey.js +174 -0
  31. package/dist/providers/apple.d.ts +1 -1
  32. package/dist/providers/apple.js +6 -8
  33. package/dist/providers/custom.d.ts +1 -1
  34. package/dist/providers/custom.js +4 -7
  35. package/dist/providers/github.d.ts +1 -1
  36. package/dist/providers/github.js +5 -8
  37. package/dist/providers/google.d.ts +1 -1
  38. package/dist/providers/google.js +5 -8
  39. package/dist/providers/microsoft.d.ts +1 -1
  40. package/dist/providers/microsoft.js +5 -9
  41. package/dist/providers/password.d.ts +18 -37
  42. package/dist/providers/password.js +170 -115
  43. package/dist/providers/redirect.d.ts +1 -0
  44. package/dist/providers/redirect.js +20 -0
  45. package/dist/server/auth.d.ts +6 -7
  46. package/dist/server/auth.js +3 -2
  47. package/dist/server/{ctxCache.js → cache/context.js} +2 -2
  48. package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
  49. package/dist/server/context.js +3 -10
  50. package/dist/server/contract.d.ts +2 -87
  51. package/dist/server/contract.js +1 -1
  52. package/dist/server/cookies.js +25 -1
  53. package/dist/server/core.js +1 -1
  54. package/dist/server/errors.js +24 -1
  55. package/dist/server/{auth-context.d.ts → facade.d.ts} +3 -45
  56. package/dist/server/{auth-context.js → facade.js} +11 -12
  57. package/dist/server/http.d.ts +7 -7
  58. package/dist/server/http.js +47 -7
  59. package/dist/server/{convexIdentity.d.ts → identity/convex.d.ts} +3 -3
  60. package/dist/server/index.d.ts +5 -3
  61. package/dist/server/index.js +3 -2
  62. package/dist/server/mounts.d.ts +171 -18
  63. package/dist/server/mutations/code.js +7 -1
  64. package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
  65. package/dist/server/mutations/index.js +1 -1
  66. package/dist/server/mutations/invalidate.js +11 -1
  67. package/dist/server/mutations/oauth.js +25 -27
  68. package/dist/server/mutations/signin.js +6 -0
  69. package/dist/server/mutations/signout.js +5 -0
  70. package/dist/server/mutations/store.js +1 -1
  71. package/dist/server/oauth/factory.js +11 -3
  72. package/dist/server/passkey.js +126 -110
  73. package/dist/server/prefetch.js +8 -1
  74. package/dist/server/redirects.js +11 -3
  75. package/dist/server/refresh.js +6 -1
  76. package/dist/server/runtime.d.ts +68 -37
  77. package/dist/server/runtime.js +318 -36
  78. package/dist/server/services/group.js +4 -0
  79. package/dist/server/sessions.js +1 -0
  80. package/dist/server/signin.js +8 -6
  81. package/dist/server/sso/domain.d.ts +159 -16
  82. package/dist/server/sso/domain.js +1 -1
  83. package/dist/server/sso/http.js +144 -60
  84. package/dist/server/sso/oidc.js +28 -12
  85. package/dist/server/sso/policy.js +30 -14
  86. package/dist/server/sso/provision.js +1 -1
  87. package/dist/server/sso/saml.js +18 -9
  88. package/dist/server/sso/scim.js +12 -4
  89. package/dist/server/sso/shared.js +5 -5
  90. package/dist/server/telemetry.js +3 -0
  91. package/dist/server/tokens.js +10 -2
  92. package/dist/server/totp.js +127 -100
  93. package/dist/server/types.d.ts +224 -151
  94. package/dist/server/url.js +1 -1
  95. package/dist/server/users.js +93 -53
  96. package/dist/server/wellknown.d.ts +75 -0
  97. package/dist/server/wellknown.js +198 -0
  98. package/dist/shared/errors.js +0 -1
  99. package/package.json +36 -4
  100. package/dist/server/oauth/index.js +0 -12
  101. package/dist/server/utils/dispatch.js +0 -36
  102. package/dist/shared/authResults.d.ts +0 -16
  103. /package/dist/server/{componentContext.js → component/context.js} +0 -0
  104. /package/dist/server/{convexIdentity.js → identity/convex.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", {
@@ -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,16 +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
6534
  await initializeAuthConfig(config);
6510
- await configureHttp(config);
6511
6535
  if (options.variables !== void 0) await configureOtherVariables(config, options.variables);
6512
6536
  else printFinalSuccessMessage(config);
6513
6537
  gt("Done! Happy building.");
6514
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
+ */
6515
6594
  const runCli = async (argv = process$1.argv) => {
6516
- 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);
6517
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
+ */
6518
6606
  function runCliMain(argv = process$1.argv) {
6519
6607
  runCli(argv).catch((err) => {
6520
6608
  console.error(err);
@@ -6790,6 +6878,33 @@ export const { signIn, signOut, store } = auth;
6790
6878
  O.success(`Created ${newAuthPath}`);
6791
6879
  }
6792
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
+ }
6793
6908
  async function initializeAuthCore(config) {
6794
6909
  logStep(config, "Initialize auth/core file");
6795
6910
  const sourceTemplate = `\
@@ -6826,7 +6941,7 @@ async function initializeAuthConfig(config) {
6826
6941
  export default {$$
6827
6942
  providers: [$$
6828
6943
  {$$
6829
- domain: process.env.CONVEX_SITE_URL,$$
6944
+ domain: process.env.CONVEX_SITE_URL + "/auth",$$
6830
6945
  applicationID: "convex",$$
6831
6946
  },$$
6832
6947
  ],$$
@@ -6852,38 +6967,6 @@ export default {$$
6852
6967
  O.success(`Created ${newPath}`);
6853
6968
  }
6854
6969
  }
6855
- async function configureHttp(config) {
6856
- logStep(config, "Configure http file");
6857
- const sourceTemplate = `\
6858
- import { httpRouter } from "convex/server";
6859
- import { auth } from "./auth";
6860
-
6861
- const http = httpRouter();
6862
-
6863
- auth.http.add(http);
6864
-
6865
- export default http;
6866
- `;
6867
- const source = templateToSource(sourceTemplate);
6868
- const httpPath = path.join(config.convexFolderPath, "http");
6869
- const existingHttpPath = existingNonEmptySourcePath(httpPath);
6870
- if (existingHttpPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingHttpPath, "utf8"), sourceTemplate)) O.success(`${existingHttpPath} is already set up.`);
6871
- else {
6872
- O.info(`You already have ${existingHttpPath}. Make sure it includes auth.http.add:`);
6873
- O.message(indent(`\n${source}\n`));
6874
- const ready = await ot({ message: "Ready to continue?" });
6875
- handleCancel(ready);
6876
- if (!ready) {
6877
- pt("Setup cancelled.");
6878
- process$1.exit(1);
6879
- }
6880
- }
6881
- else {
6882
- const newHttpPath = config.usesTypeScript ? `${httpPath}.ts` : `${httpPath}.js`;
6883
- writeFileSync(newHttpPath, source);
6884
- O.success(`Created ${newHttpPath}`);
6885
- }
6886
- }
6887
6970
  function validateVariablesConfig(value) {
6888
6971
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Expected an object at the top level.");
6889
6972
  const obj = value;
@@ -6947,9 +7030,11 @@ async function configureOtherVariables(config, json) {
6947
7030
  }
6948
7031
  if (variables.success !== void 0) O.success(variables.success);
6949
7032
  }
7033
+ /** @internal */
6950
7034
  function doesAlreadyMatchTemplate(existing, template) {
6951
7035
  return new RegExp(template.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\$\\\$/g, ".*").replace(/;\n/g, ";.*"), "s").test(existing);
6952
7036
  }
7037
+ /** @internal */
6953
7038
  function templateToSource(template) {
6954
7039
  return template.replace(/\$\$/g, "");
6955
7040
  }
@@ -7021,6 +7106,7 @@ function loadEnvFiles() {
7021
7106
  override: false
7022
7107
  });
7023
7108
  }
7109
+ /** @internal */
7024
7110
  function readConvexDeployment(options) {
7025
7111
  const { adminKey, url, prod, previewName, deploymentName } = options;
7026
7112
  if (url) return {
@@ -7063,6 +7149,7 @@ function readConvexDeployment(options) {
7063
7149
  }
7064
7150
  logErrorAndExit("Could not find a configured CONVEX_DEPLOYMENT. Did you forget to run `npx convex dev` first?");
7065
7151
  }
7152
+ /** @internal */
7066
7153
  function stripDeploymentTypePrefix(deployment) {
7067
7154
  const [type, name] = deployment.split(":");
7068
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\".");
@@ -7077,11 +7164,13 @@ function deploymentNameFromAdminKey(adminKey) {
7077
7164
  const parts = adminKey.split("|");
7078
7165
  return parts.length > 1 && !isPreviewDeployKey(adminKey) ? stripDeploymentTypePrefix(parts[0]) : null;
7079
7166
  }
7167
+ /** @internal */
7080
7168
  function deploymentTypeFromAdminKey(adminKey) {
7081
7169
  const type = adminKey.split(":")[0];
7082
7170
  if (type === "prod" || type === "dev" || type === "preview") return type;
7083
7171
  logErrorAndExit("Invalid admin key.", "Expected a typed key like \"dev:deployment|...\", \"prod:deployment|...\", or \"preview:...\".");
7084
7172
  }
7173
+ /** @internal */
7085
7174
  function isPreviewDeployKey(adminKey) {
7086
7175
  const parts = adminKey.split("|");
7087
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
  };
@@ -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
  };