better-auth 1.6.15 → 1.6.17

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 (105) hide show
  1. package/dist/api/index.d.mts +2 -2
  2. package/dist/api/index.mjs +3 -4
  3. package/dist/api/middlewares/origin-check.mjs +6 -1
  4. package/dist/api/rate-limiter/index.mjs +259 -73
  5. package/dist/api/routes/account.mjs +31 -11
  6. package/dist/api/routes/callback.mjs +3 -3
  7. package/dist/api/routes/index.d.mts +1 -1
  8. package/dist/api/routes/password.mjs +3 -4
  9. package/dist/api/routes/session.d.mts +12 -1
  10. package/dist/api/routes/session.mjs +16 -2
  11. package/dist/api/routes/sign-in.mjs +5 -5
  12. package/dist/api/routes/sign-up.mjs +2 -2
  13. package/dist/api/routes/update-session.mjs +9 -4
  14. package/dist/api/routes/update-user.mjs +10 -12
  15. package/dist/auth/base.mjs +11 -7
  16. package/dist/client/equality.d.mts +19 -0
  17. package/dist/client/equality.mjs +42 -0
  18. package/dist/client/index.d.mts +5 -4
  19. package/dist/client/index.mjs +2 -1
  20. package/dist/client/lynx/index.d.mts +6 -5
  21. package/dist/client/path-to-object.d.mts +5 -2
  22. package/dist/client/plugins/index.d.mts +4 -1
  23. package/dist/client/plugins/index.mjs +4 -1
  24. package/dist/client/query.d.mts +4 -3
  25. package/dist/client/query.mjs +27 -17
  26. package/dist/client/react/index.d.mts +6 -5
  27. package/dist/client/session-atom.mjs +129 -4
  28. package/dist/client/session-refresh.d.mts +3 -18
  29. package/dist/client/session-refresh.mjs +38 -49
  30. package/dist/client/solid/index.d.mts +6 -5
  31. package/dist/client/svelte/index.d.mts +6 -5
  32. package/dist/client/types.d.mts +2 -2
  33. package/dist/client/vanilla.d.mts +6 -5
  34. package/dist/client/vue/index.d.mts +6 -5
  35. package/dist/context/create-context.mjs +3 -2
  36. package/dist/context/store-capabilities.mjs +12 -0
  37. package/dist/cookies/index.mjs +30 -2
  38. package/dist/db/internal-adapter.mjs +56 -0
  39. package/dist/oauth2/link-account.d.mts +13 -0
  40. package/dist/oauth2/link-account.mjs +1 -1
  41. package/dist/package.mjs +1 -1
  42. package/dist/plugins/access/access.mjs +49 -19
  43. package/dist/plugins/admin/access/statement.d.mts +10 -10
  44. package/dist/plugins/admin/access/statement.mjs +2 -0
  45. package/dist/plugins/admin/admin.d.mts +6 -3
  46. package/dist/plugins/admin/client.d.mts +6 -4
  47. package/dist/plugins/admin/error-codes.d.mts +2 -0
  48. package/dist/plugins/admin/error-codes.mjs +3 -1
  49. package/dist/plugins/admin/routes.mjs +73 -5
  50. package/dist/plugins/admin/schema.d.mts +1 -0
  51. package/dist/plugins/admin/schema.mjs +2 -1
  52. package/dist/plugins/captcha/constants.mjs +8 -1
  53. package/dist/plugins/captcha/index.mjs +8 -2
  54. package/dist/plugins/captcha/types.d.mts +21 -0
  55. package/dist/plugins/captcha/verify-handlers/captchafox.mjs +2 -0
  56. package/dist/plugins/captcha/verify-handlers/cloudflare-turnstile.mjs +7 -2
  57. package/dist/plugins/captcha/verify-handlers/google-recaptcha.mjs +7 -2
  58. package/dist/plugins/captcha/verify-handlers/h-captcha.mjs +2 -0
  59. package/dist/plugins/device-authorization/routes.mjs +16 -9
  60. package/dist/plugins/email-otp/routes.mjs +23 -53
  61. package/dist/plugins/generic-oauth/index.mjs +7 -2
  62. package/dist/plugins/generic-oauth/routes.mjs +20 -9
  63. package/dist/plugins/haveibeenpwned/index.d.mts +1 -1
  64. package/dist/plugins/haveibeenpwned/index.mjs +5 -1
  65. package/dist/plugins/index.d.mts +5 -1
  66. package/dist/plugins/index.mjs +4 -1
  67. package/dist/plugins/jwt/index.mjs +2 -2
  68. package/dist/plugins/mcp/client/index.mjs +1 -0
  69. package/dist/plugins/mcp/index.mjs +8 -0
  70. package/dist/plugins/multi-session/index.mjs +7 -5
  71. package/dist/plugins/oauth-popup/client.d.mts +82 -0
  72. package/dist/plugins/oauth-popup/client.mjs +203 -0
  73. package/dist/plugins/oauth-popup/constants.d.mts +11 -0
  74. package/dist/plugins/oauth-popup/constants.mjs +11 -0
  75. package/dist/plugins/oauth-popup/error-codes.d.mts +11 -0
  76. package/dist/plugins/oauth-popup/error-codes.mjs +10 -0
  77. package/dist/plugins/oauth-popup/index.d.mts +67 -0
  78. package/dist/plugins/oauth-popup/index.mjs +227 -0
  79. package/dist/plugins/oauth-popup/types.d.mts +30 -0
  80. package/dist/plugins/oauth-proxy/index.mjs +2 -2
  81. package/dist/plugins/oauth-proxy/utils.mjs +16 -2
  82. package/dist/plugins/oidc-provider/index.mjs +10 -0
  83. package/dist/plugins/one-tap/client.mjs +12 -6
  84. package/dist/plugins/one-tap/index.d.mts +1 -0
  85. package/dist/plugins/one-tap/index.mjs +9 -5
  86. package/dist/plugins/one-time-token/index.mjs +1 -3
  87. package/dist/plugins/open-api/generator.mjs +7 -4
  88. package/dist/plugins/organization/adapter.d.mts +29 -1
  89. package/dist/plugins/organization/adapter.mjs +66 -6
  90. package/dist/plugins/organization/organization.mjs +2 -0
  91. package/dist/plugins/organization/routes/crud-invites.mjs +55 -31
  92. package/dist/plugins/organization/routes/crud-members.mjs +42 -6
  93. package/dist/plugins/organization/routes/crud-team.mjs +51 -5
  94. package/dist/plugins/organization/schema.d.mts +2 -0
  95. package/dist/plugins/phone-number/routes.mjs +41 -36
  96. package/dist/plugins/siwe/index.mjs +30 -3
  97. package/dist/plugins/siwe/parse-message.mjs +60 -0
  98. package/dist/plugins/two-factor/backup-codes/index.mjs +1 -1
  99. package/dist/plugins/two-factor/index.mjs +9 -1
  100. package/dist/plugins/two-factor/otp/index.mjs +11 -13
  101. package/dist/plugins/two-factor/totp/index.mjs +1 -1
  102. package/dist/plugins/two-factor/verify-two-factor.mjs +6 -2
  103. package/dist/plugins/username/index.mjs +6 -6
  104. package/dist/test-utils/test-instance.d.mts +6 -5
  105. package/package.json +10 -10
@@ -45,6 +45,17 @@ declare const getSession: <Option extends BetterAuthOptions>() => better_call0.S
45
45
  session: Session$1<Option["session"], Option["plugins"]>;
46
46
  user: User$1<Option["user"], Option["plugins"]>;
47
47
  } | null>;
48
+ /**
49
+ * Whether the deployment keeps sessions in a durable server-side store
50
+ * (a database or secondary storage) rather than only in the signed cookie.
51
+ *
52
+ * Sensitive operations use this to decide whether the cookie cache is merely an
53
+ * optimization that must be bypassed for an authoritative read (`true`), or the
54
+ * only place the session lives and therefore the authority itself (`false`, for
55
+ * stateless / DB-less deployments). Pass the result as `disableCookieCache` so a
56
+ * revoked-but-cached session cannot authorize a sensitive action.
57
+ */
58
+ declare const isStateful: (ctx: GenericEndpointContext) => boolean;
48
59
  declare const getSessionFromCtx: <U extends Record<string, any> = Record<string, any>, S extends Record<string, any> = Record<string, any>>(ctx: GenericEndpointContext, config?: {
49
60
  disableCookieCache?: boolean;
50
61
  disableRefresh?: boolean;
@@ -409,4 +420,4 @@ declare const revokeOtherSessions: better_call0.StrictEndpoint<"/revoke-other-se
409
420
  status: boolean;
410
421
  }>;
411
422
  //#endregion
412
- export { freshSessionMiddleware, getSession, getSessionFromCtx, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
423
+ export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
@@ -1,4 +1,5 @@
1
1
  import { isAPIError } from "../../utils/is-api-error.mjs";
2
+ import { hasServerSessionStore } from "../../context/store-capabilities.mjs";
2
3
  import { symmetricDecodeJWT, verifyJWT } from "../../crypto/jwt.mjs";
3
4
  import { parseSessionOutput, parseUserOutput } from "../../db/schema.mjs";
4
5
  import { getDate } from "../../utils/date.mjs";
@@ -265,6 +266,17 @@ const getSession = () => createAuthEndpoint("/get-session", {
265
266
  throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
266
267
  }
267
268
  });
269
+ /**
270
+ * Whether the deployment keeps sessions in a durable server-side store
271
+ * (a database or secondary storage) rather than only in the signed cookie.
272
+ *
273
+ * Sensitive operations use this to decide whether the cookie cache is merely an
274
+ * optimization that must be bypassed for an authoritative read (`true`), or the
275
+ * only place the session lives and therefore the authority itself (`false`, for
276
+ * stateless / DB-less deployments). Pass the result as `disableCookieCache` so a
277
+ * revoked-but-cached session cannot authorize a sensitive action.
278
+ */
279
+ const isStateful = (ctx) => hasServerSessionStore(ctx.context.options);
268
280
  const getSessionFromCtx = async (ctx, config) => {
269
281
  if (ctx.context.session) return ctx.context.session;
270
282
  const session = await getSession()({
@@ -276,7 +288,9 @@ const getSessionFromCtx = async (ctx, config) => {
276
288
  returnStatus: false,
277
289
  query: {
278
290
  ...config,
279
- ...ctx.query
291
+ ...ctx.query,
292
+ disableCookieCache: config?.disableCookieCache || ctx.query?.disableCookieCache,
293
+ disableRefresh: config?.disableRefresh || ctx.query?.disableRefresh
280
294
  }
281
295
  }).catch(() => {
282
296
  return null;
@@ -486,4 +500,4 @@ const revokeOtherSessions = createAuthEndpoint("/revoke-other-sessions", {
486
500
  return ctx.json({ status: true });
487
501
  });
488
502
  //#endregion
489
- export { freshSessionMiddleware, getSession, getSessionFromCtx, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
503
+ export { freshSessionMiddleware, getSession, getSessionFromCtx, isStateful, listSessions, requestOnlySessionMiddleware, revokeOtherSessions, revokeSession, revokeSessions, sensitiveSessionMiddleware, sessionMiddleware };
@@ -80,7 +80,7 @@ const signInSocial = () => createAuthEndpoint("/sign-in/social", {
80
80
  }
81
81
  const { token, nonce } = c.body.idToken;
82
82
  if (!await provider.verifyIdToken(token, nonce)) {
83
- c.context.logger.error("Invalid id token", { provider: c.body.provider });
83
+ c.context.logger.warn("Invalid id token", { provider: c.body.provider });
84
84
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_TOKEN);
85
85
  }
86
86
  const userInfo = await provider.getUserInfo({
@@ -205,26 +205,26 @@ const signInEmail = () => createAuthEndpoint("/sign-in/email", {
205
205
  const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
206
206
  if (!user) {
207
207
  await ctx.context.password.hash(password);
208
- ctx.context.logger.error("User not found", { email });
208
+ ctx.context.logger.warn("User not found");
209
209
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
210
210
  }
211
211
  const credentialAccount = user.accounts.find((a) => a.providerId === "credential");
212
212
  if (!credentialAccount) {
213
213
  await ctx.context.password.hash(password);
214
- ctx.context.logger.error("Credential account not found", { email });
214
+ ctx.context.logger.warn("Credential account not found");
215
215
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
216
216
  }
217
217
  const currentPassword = credentialAccount?.password;
218
218
  if (!currentPassword) {
219
219
  await ctx.context.password.hash(password);
220
- ctx.context.logger.error("Password not found", { email });
220
+ ctx.context.logger.warn("Password not found");
221
221
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
222
222
  }
223
223
  if (!await ctx.context.password.verify({
224
224
  hash: currentPassword,
225
225
  password
226
226
  })) {
227
- ctx.context.logger.error("Invalid password");
227
+ ctx.context.logger.warn("Invalid password");
228
228
  throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.INVALID_EMAIL_OR_PASSWORD);
229
229
  }
230
230
  if (ctx.context.options?.emailAndPassword?.requireEmailVerification && !user.user.emailVerified) {
@@ -150,12 +150,12 @@ const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
150
150
  if (!password || typeof password !== "string") throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_PASSWORD);
151
151
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
152
152
  if (password.length < minPasswordLength) {
153
- ctx.context.logger.error("Password is too short");
153
+ ctx.context.logger.warn("Password is too short");
154
154
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
155
155
  }
156
156
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
157
157
  if (password.length > maxPasswordLength) {
158
- ctx.context.logger.error("Password is too long");
158
+ ctx.context.logger.warn("Password is too long");
159
159
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
160
160
  }
161
161
  const shouldReturnGenericDuplicateResponse = ctx.context.options.emailAndPassword.requireEmailVerification || ctx.context.options.emailAndPassword.autoSignIn === false;
@@ -1,6 +1,6 @@
1
1
  import { parseSessionInput, parseSessionOutput } from "../../db/schema.mjs";
2
- import { setSessionCookie } from "../../cookies/index.mjs";
3
- import { sessionMiddleware } from "./session.mjs";
2
+ import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
3
+ import { isStateful, sessionMiddleware } from "./session.mjs";
4
4
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
5
5
  import { createAuthEndpoint } from "@better-auth/core/api";
6
6
  import * as z from "zod";
@@ -34,10 +34,15 @@ const updateSession = () => createAuthEndpoint("/update-session", {
34
34
  const session = ctx.context.session;
35
35
  const additionalFields = parseSessionInput(ctx.context.options, body, "update");
36
36
  if (Object.keys(additionalFields).length === 0) throw APIError.fromStatus("BAD_REQUEST", { message: "No fields to update" });
37
- const newSession = await ctx.context.internalAdapter.updateSession(session.session.token, {
37
+ const updatedSession = await ctx.context.internalAdapter.updateSession(session.session.token, {
38
38
  ...additionalFields,
39
39
  updatedAt: /* @__PURE__ */ new Date()
40
- }) ?? {
40
+ });
41
+ if (!updatedSession && isStateful(ctx)) {
42
+ deleteSessionCookie(ctx);
43
+ throw APIError.from("UNAUTHORIZED", BASE_ERROR_CODES.FAILED_TO_GET_SESSION);
44
+ }
45
+ const newSession = updatedSession ?? {
41
46
  ...session.session,
42
47
  ...additionalFields,
43
48
  updatedAt: /* @__PURE__ */ new Date()
@@ -2,7 +2,7 @@ import { originCheck } from "../middlewares/origin-check.mjs";
2
2
  import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
3
3
  import { generateRandomString } from "../../crypto/random.mjs";
4
4
  import { deleteSessionCookie, setSessionCookie } from "../../cookies/index.mjs";
5
- import { getSessionFromCtx, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
5
+ import { getSessionFromCtx, isStateful, sensitiveSessionMiddleware, sessionMiddleware } from "./session.mjs";
6
6
  import { createEmailVerificationToken } from "./email-verification.mjs";
7
7
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
8
8
  import { createAuthEndpoint } from "@better-auth/core/api";
@@ -150,12 +150,12 @@ const changePassword = createAuthEndpoint("/change-password", {
150
150
  const session = ctx.context.session;
151
151
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
152
152
  if (newPassword.length < minPasswordLength) {
153
- ctx.context.logger.error("Password is too short");
153
+ ctx.context.logger.warn("Password is too short");
154
154
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
155
155
  }
156
156
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
157
157
  if (newPassword.length > maxPasswordLength) {
158
- ctx.context.logger.error("Password is too long");
158
+ ctx.context.logger.warn("Password is too long");
159
159
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
160
160
  }
161
161
  const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
@@ -182,7 +182,7 @@ const changePassword = createAuthEndpoint("/change-password", {
182
182
  user: parseUserOutput(ctx.context.options, session.user)
183
183
  });
184
184
  });
185
- const setPassword = createAuthEndpoint({
185
+ const setPassword = createAuthEndpoint.serverOnly({
186
186
  method: "POST",
187
187
  body: z.object({ newPassword: z.string().meta({ description: "The new password to set is required" }) }),
188
188
  use: [sensitiveSessionMiddleware]
@@ -191,12 +191,12 @@ const setPassword = createAuthEndpoint({
191
191
  const session = ctx.context.session;
192
192
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
193
193
  if (newPassword.length < minPasswordLength) {
194
- ctx.context.logger.error("Password is too short");
194
+ ctx.context.logger.warn("Password is too short");
195
195
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);
196
196
  }
197
197
  const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
198
198
  if (newPassword.length > maxPasswordLength) {
199
- ctx.context.logger.error("Password is too long");
199
+ ctx.context.logger.warn("Password is too long");
200
200
  throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PASSWORD_TOO_LONG);
201
201
  }
202
202
  const account = (await ctx.context.internalAdapter.findAccounts(session.user.id)).find((account) => account.providerId === "credential" && account.password);
@@ -354,17 +354,15 @@ const deleteUserCallback = createAuthEndpoint("/delete-user/callback", {
354
354
  code: "NOT_FOUND"
355
355
  });
356
356
  }
357
- const session = await getSessionFromCtx(ctx);
357
+ const session = await getSessionFromCtx(ctx, { disableCookieCache: isStateful(ctx) });
358
358
  if (!session) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.FAILED_TO_GET_USER_INFO);
359
- const token = await ctx.context.internalAdapter.findVerificationValue(`delete-account-${ctx.query.token}`);
360
- if (!token || token.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
361
- if (token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
359
+ const token = await ctx.context.internalAdapter.consumeVerificationValue(`delete-account-${ctx.query.token}`);
360
+ if (!token || token.value !== session.user.id) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.INVALID_TOKEN);
362
361
  const beforeDelete = ctx.context.options.user.deleteUser?.beforeDelete;
363
362
  if (beforeDelete) await beforeDelete(session.user, ctx.request);
364
363
  await ctx.context.internalAdapter.deleteUser(session.user.id);
365
364
  await ctx.context.internalAdapter.deleteUserSessions(session.user.id);
366
365
  await ctx.context.internalAdapter.deleteAccounts(session.user.id);
367
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`delete-account-${ctx.query.token}`);
368
366
  deleteSessionCookie(ctx);
369
367
  const afterDelete = ctx.context.options.user.deleteUser?.afterDelete;
370
368
  if (afterDelete) await afterDelete(session.user, ctx.request);
@@ -414,7 +412,7 @@ const changeEmail = createAuthEndpoint("/change-email", {
414
412
  }
415
413
  const newEmail = ctx.body.newEmail.toLowerCase();
416
414
  if (newEmail === ctx.context.session.user.email) {
417
- ctx.context.logger.error("Email is the same");
415
+ ctx.context.logger.warn("Email is the same");
418
416
  throw APIError.fromStatus("BAD_REQUEST", { message: "Email is the same" });
419
417
  }
420
418
  /**
@@ -14,16 +14,20 @@ const createBetterAuth = (options, initFn) => {
14
14
  let handlerCtx;
15
15
  if (isDynamicBaseURLConfig(options.baseURL)) handlerCtx = await resolveRequestContext(ctx, request, resolveDynamicTrustedProxyHeaders(ctx.options));
16
16
  else {
17
- handlerCtx = ctx;
17
+ handlerCtx = Object.create(Object.getPrototypeOf(ctx), Object.getOwnPropertyDescriptors(ctx));
18
+ let trustOptions = ctx.options;
18
19
  if (!ctx.options.baseURL) {
19
20
  const baseURL = getBaseURL(void 0, basePath, request, void 0, ctx.options.advanced?.trustedProxyHeaders);
20
- if (baseURL) {
21
- ctx.baseURL = baseURL;
22
- ctx.options.baseURL = getOrigin(ctx.baseURL) || void 0;
23
- } else throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
21
+ if (!baseURL) throw new BetterAuthError("Could not get base URL from request. Please provide a valid base URL.");
22
+ handlerCtx.baseURL = baseURL;
23
+ handlerCtx.options = {
24
+ ...ctx.options,
25
+ baseURL: getOrigin(baseURL) || void 0
26
+ };
27
+ trustOptions = handlerCtx.options;
24
28
  }
25
- handlerCtx.trustedOrigins = await getTrustedOrigins(ctx.options, request);
26
- handlerCtx.trustedProviders = await getTrustedProviders(ctx.options, request);
29
+ handlerCtx.trustedOrigins = await getTrustedOrigins(trustOptions, request);
30
+ handlerCtx.trustedProviders = await getTrustedProviders(trustOptions, request);
27
31
  }
28
32
  const { handler } = router(handlerCtx, options);
29
33
  return runWithAdapter(handlerCtx.adapter, () => handler(request));
@@ -0,0 +1,19 @@
1
+ import { Store, StoreValue } from "nanostores";
2
+
3
+ //#region src/client/equality.d.ts
4
+ /**
5
+ * Deep structural equality for JSON-serializable values.
6
+ * Handles: primitives, null, arrays, and plain objects.
7
+ * Short-circuits on referential equality at every recursion level.
8
+ */
9
+ declare function isJsonEqual(a: unknown, b: unknown): boolean;
10
+ /**
11
+ * Attach an equality gate to a nanostores atom via `onSet`.
12
+ * When `isEqual(currentValue, newValue)` returns true, the `set()` call
13
+ * is aborted: no listeners fire, no framework re-renders occur.
14
+ *
15
+ * Returns the unsubscribe function from `onSet`.
16
+ */
17
+ declare function withEquality<S extends Store>(store: S, isEqual: (a: StoreValue<S>, b: StoreValue<S>) => boolean): () => void;
18
+ //#endregion
19
+ export { isJsonEqual, withEquality };
@@ -0,0 +1,42 @@
1
+ import { onSet } from "nanostores";
2
+ //#region src/client/equality.ts
3
+ function isPlainObject(value) {
4
+ if (typeof value !== "object" || value === null) return false;
5
+ const prototype = Object.getPrototypeOf(value);
6
+ return prototype === Object.prototype || prototype === null;
7
+ }
8
+ /**
9
+ * Deep structural equality for JSON-serializable values.
10
+ * Handles: primitives, null, arrays, and plain objects.
11
+ * Short-circuits on referential equality at every recursion level.
12
+ */
13
+ function isJsonEqual(a, b) {
14
+ if (a === b) return true;
15
+ if (Array.isArray(a) && Array.isArray(b)) {
16
+ if (a.length !== b.length) return false;
17
+ for (let i = 0; i < a.length; i++) if (!isJsonEqual(a[i], b[i])) return false;
18
+ return true;
19
+ }
20
+ if (isPlainObject(a) && isPlainObject(b)) {
21
+ const keysA = Object.keys(a);
22
+ const keysB = Object.keys(b);
23
+ if (keysA.length !== keysB.length) return false;
24
+ for (const key of keysA) if (!(key in b) || !isJsonEqual(a[key], b[key])) return false;
25
+ return true;
26
+ }
27
+ return false;
28
+ }
29
+ /**
30
+ * Attach an equality gate to a nanostores atom via `onSet`.
31
+ * When `isEqual(currentValue, newValue)` returns true, the `set()` call
32
+ * is aborted: no listeners fire, no framework re-renders occur.
33
+ *
34
+ * Returns the unsubscribe function from `onSet`.
35
+ */
36
+ function withEquality(store, isEqual) {
37
+ return onSet(store, ({ newValue, abort }) => {
38
+ if (isEqual(store.value, newValue)) abort();
39
+ });
40
+ }
41
+ //#endregion
42
+ export { isJsonEqual, withEquality };
@@ -1,12 +1,13 @@
1
1
  import { ExtractPluginField, HasRequiredKeys, InferPluginFieldFromTuple, IsAny, OverrideMerge, Prettify, PrettifyDeep, RequiredKeysOf, StripEmptyObjects, UnionToIntersection } from "../types/helper.mjs";
2
- import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
2
+ import { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest } from "./path-to-object.mjs";
3
3
  import { BetterAuthClientOptions, BetterAuthClientPlugin, ClientAtomListener, ClientStore, InferActions, InferAdditionalFromClient, InferClientAPI, InferErrorCodes, InferSessionFromClient, InferUserFromClient, IsSignal, SessionQueryParams } from "./types.mjs";
4
4
  import { BroadcastChannel, BroadcastListener, BroadcastMessage, getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
5
+ import { isJsonEqual, withEquality } from "./equality.mjs";
5
6
  import { FocusListener, FocusManager, kFocusManager } from "./focus-manager.mjs";
6
7
  import { OnlineListener, OnlineManager, kOnlineManager } from "./online-manager.mjs";
7
8
  import { parseJSON } from "./parser.mjs";
8
- import { AuthQueryAtom, useAuthQuery } from "./query.mjs";
9
- import { SessionRefreshOptions, SessionResponse, createSessionRefreshManager } from "./session-refresh.mjs";
9
+ import { AuthQueryAtom, AuthQueryState, useAuthQuery } from "./query.mjs";
10
+ import { SessionRefreshOptions, createSessionRefreshManager } from "./session-refresh.mjs";
10
11
  import { AuthClient, createAuthClient } from "./vanilla.mjs";
11
12
  import { AccessControl, ArrayElement, ExactRoleStatements, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, Statements, SubArray, Subset } from "../plugins/access/types.mjs";
12
13
  import { AuthorizeResponse, createAccessControl, role } from "../plugins/access/access.mjs";
@@ -31,4 +32,4 @@ declare function InferAuth<O extends {
31
32
  options: BetterAuthOptions;
32
33
  }>(): O["options"];
33
34
  //#endregion
34
- export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions, SessionResponse, Statements, StripEmptyObjects, SubArray, Subset, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, type UnionToIntersection, createAccessControl, createAuthClient, createSessionRefreshManager, defaultRolesSchema, getGlobalBroadcastChannel, getOrgAdapter, hasPermission, invitationSchema, invitationStatus, kBroadcastChannel, kFocusManager, kOnlineManager, memberSchema, organization, organizationRoleSchema, organizationSchema, parseJSON, parseRoles, role, roleSchema, teamMemberSchema, teamSchema, useAuthQuery };
35
+ export { AccessControl, ArrayElement, AuthClient, AuthQueryAtom, AuthQueryState, AuthorizeResponse, BetterAuthClientOptions, BetterAuthClientPlugin, BroadcastChannel, BroadcastListener, BroadcastMessage, CamelCase, ClientAtomListener, ClientStore, type DBPrimitive, DefaultOrganizationPlugin, DynamicAccessControlEndpoints, ExactRoleStatements, ExtractPluginField, type FocusListener, type FocusManager, HasRequiredKeys, InferActions, InferAdditionalFromClient, InferAuth, InferClientAPI, InferCtx, InferErrorCodes, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPlugin, InferPluginFieldFromTuple, InferRoute, InferRoutes, InferSessionFromClient, InferSessionUpdateCtx, InferSignUpEmailCtx, InferTeam, InferUserFromClient, InferUserUpdateCtx, Invitation, InvitationInput, InvitationStatus, IsAny, IsSignal, Member, MemberInput, MergeRoutes, type OnlineListener, type OnlineManager, Organization, OrganizationCreator, OrganizationEndpoints, OrganizationInput, OrganizationOptions, OrganizationPlugin, OrganizationRole, OrganizationSchema, OverrideMerge, PathToObject, Prettify, PrettifyDeep, ProxyRequest, RequiredKeysOf, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SessionQueryParams, SessionRefreshOptions, Statements, StripEmptyObjects, SubArray, Subset, Team, TeamEndpoints, TeamInput, TeamMember, TeamMemberInput, type UnionToIntersection, createAccessControl, createAuthClient, createSessionRefreshManager, defaultRolesSchema, getGlobalBroadcastChannel, getOrgAdapter, hasPermission, invitationSchema, invitationStatus, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, memberSchema, organization, organizationRoleSchema, organizationSchema, parseJSON, parseRoles, role, roleSchema, teamMemberSchema, teamSchema, useAuthQuery, withEquality };
@@ -1,5 +1,6 @@
1
1
  import { PACKAGE_VERSION } from "../version.mjs";
2
2
  import { getGlobalBroadcastChannel, kBroadcastChannel } from "./broadcast-channel.mjs";
3
+ import { isJsonEqual, withEquality } from "./equality.mjs";
3
4
  import { kFocusManager } from "./focus-manager.mjs";
4
5
  import { kOnlineManager } from "./online-manager.mjs";
5
6
  import { parseJSON } from "./parser.mjs";
@@ -18,4 +19,4 @@ function InferAuth() {
18
19
  return {};
19
20
  }
20
21
  //#endregion
21
- export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery };
22
+ export { InferAuth, InferPlugin, createAuthClient, createSessionRefreshManager, getGlobalBroadcastChannel, isJsonEqual, kBroadcastChannel, kFocusManager, kOnlineManager, parseJSON, useAuthQuery, withEquality };
@@ -69,11 +69,6 @@ declare function createAuthClient<Option extends BetterAuthClientOptions>(option
69
69
  priority?: RequestPriority | undefined;
70
70
  cache?: RequestCache | undefined;
71
71
  credentials?: RequestCredentials;
72
- headers?: (HeadersInit & (HeadersInit | {
73
- accept: "application/json" | "text/plain" | "application/octet-stream";
74
- "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
75
- authorization: "Bearer" | "Basic";
76
- })) | undefined;
77
72
  integrity?: string | undefined;
78
73
  keepalive?: boolean | undefined;
79
74
  method: string;
@@ -103,6 +98,12 @@ declare function createAuthClient<Option extends BetterAuthClientOptions>(option
103
98
  prefix: string | (() => string | undefined) | undefined;
104
99
  value: string | (() => string | undefined) | undefined;
105
100
  }) | undefined;
101
+ headers?: {} | {
102
+ [x: string]: string | undefined;
103
+ accept?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream") | undefined;
104
+ "content-type"?: ((string & {}) | "application/x-www-form-urlencoded" | "application/json" | "text/plain" | "application/octet-stream" | "multipart/form-data") | undefined;
105
+ authorization?: ((string & {}) | `Bearer ${string}` | `Basic ${string}`) | undefined;
106
+ } | undefined;
106
107
  body?: any;
107
108
  query?: any;
108
109
  params?: any;
@@ -30,6 +30,9 @@ type InferUserUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions
30
30
  name?: string | undefined;
31
31
  fetchOptions?: FetchOptions | undefined;
32
32
  } & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "user", "input">>>;
33
+ type InferSessionUpdateCtx<ClientOpts extends BetterAuthClientOptions, FetchOptions extends ClientFetchOption> = {
34
+ fetchOptions?: FetchOptions | undefined;
35
+ } & Partial<UnionToIntersection<InferAdditionalFromClient<ClientOpts, "session", "input">>>;
33
36
  type InferCtxQuery<C extends InputContext<any, any>, FetchOptions extends ClientFetchOption> = C["query"] extends Record<string, any> ? {
34
37
  query: C["query"];
35
38
  fetchOptions?: FetchOptions | undefined;
@@ -51,7 +54,7 @@ type InferRoute<API, COpts extends BetterAuthClientOptions> = API extends Record
51
54
  scope: "http";
52
55
  } | {
53
56
  scope: "server";
54
- } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
57
+ } ? {} : PathToObject<T["path"], T extends ((ctx: infer C) => infer R) ? C extends InputContext<any, any> ? <FetchOptions extends ClientFetchOption<Partial<C["body"]> & Record<string, any>, Partial<C["query"]> & Record<string, any>, C["params"]>>(...data: HasRequiredKeys<InferCtx<C, FetchOptions>> extends true ? [Prettify$1<T["path"] extends `/sign-up/email` ? InferSignUpEmailCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>, FetchOptions?] : [Prettify$1<T["path"] extends `/update-user` ? InferUserUpdateCtx<COpts, FetchOptions> : T["path"] extends `/update-session` ? InferSessionUpdateCtx<COpts, FetchOptions> : InferCtx<C, FetchOptions>>?, FetchOptions?]) => Promise<BetterFetchResponse<T["options"]["metadata"] extends {
55
58
  CUSTOM_SESSION: boolean;
56
59
  } ? MergeCustomSessionWithInferred<NonNullable<Awaited<R>>, COpts> : T["path"] extends "/get-session" ? {
57
60
  user: InferUserFromClient<COpts>;
@@ -69,4 +72,4 @@ type ProxyRequest = {
69
72
  [key: string]: any;
70
73
  };
71
74
  //#endregion
72
- export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
75
+ export { CamelCase, InferCtx, InferRoute, InferRoutes, InferSessionUpdateCtx, InferSignUpEmailCtx, InferUserUpdateCtx, MergeRoutes, PathToObject, ProxyRequest };
@@ -19,6 +19,8 @@ import { JWKOptions, JWSAlgorithms, Jwk, JwtOptions } from "../../plugins/jwt/ty
19
19
  import { AuthorizationQuery, Client, CodeVerificationValue, OAuthAccessToken, OIDCMetadata, OIDCOptions, TokenBody } from "../../plugins/oidc-provider/types.mjs";
20
20
  import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
21
21
  import { MultiSessionConfig } from "../../plugins/multi-session/index.mjs";
22
+ import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
23
+ import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
22
24
  import { OneTimeTokenOptions } from "../../plugins/one-time-token/index.mjs";
23
25
  import { PhoneNumberOptions, UserWithPhoneNumber } from "../../plugins/phone-number/types.mjs";
24
26
  import { BackupCodeOptions, backupCode2fa, encodeBackupCodes, generateBackupCodes, getBackupCodes, verifyBackupCode } from "../../plugins/two-factor/backup-codes/index.mjs";
@@ -44,6 +46,7 @@ import { jwtClient } from "../../plugins/jwt/client.mjs";
44
46
  import { LastLoginMethodClientConfig, lastLoginMethodClient } from "../../plugins/last-login-method/client.mjs";
45
47
  import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
46
48
  import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
49
+ import { SignInPopupOptions, SignInPopupResult, createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
47
50
  import { OidcClientPlugin, oidcClient } from "../../plugins/oidc-provider/client.mjs";
48
51
  import { GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, oneTapClient } from "../../plugins/one-tap/client.mjs";
49
52
  import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
@@ -53,4 +56,4 @@ import { phoneNumberClient } from "../../plugins/phone-number/client.mjs";
53
56
  import { siweClient } from "../../plugins/siwe/client.mjs";
54
57
  import { usernameClient } from "../../plugins/username/client.mjs";
55
58
  import { InferServerPlugin } from "./infer-plugin.mjs";
56
- export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
59
+ export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, AdminClientOptions, AdminOptions, AnonymousOptions, AnonymousSession, Auth0Options, AuthorizationQuery, BackupCodeOptions, BaseOAuthProviderOptions, Client, CodeVerificationValue, EMAIL_OTP_ERROR_CODES, ExtractPluginField, type FieldAttributeToObject, GENERIC_OAUTH_ERROR_CODES, GenericOAuthConfig, GenericOAuthOptions, GoogleOneTapActionOptions, GoogleOneTapOptions, GsiButtonConfiguration, GumroadOptions, HasRequiredKeys, HubSpotOptions, InferAdminRolesFromOption, InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferPluginFieldFromTuple, InferServerPlugin, InferTeam, Invitation, InvitationInput, InvitationStatus, IsAny, JWKOptions, JWSAlgorithms, Jwk, JwtOptions, KeycloakOptions, LastLoginMethodClientConfig, LineOptions, MULTI_SESSION_ERROR_CODES, Member, MemberInput, MicrosoftEntraIdOptions, MultiSessionConfig, OAUTH_POPUP_ERROR_CODES, OAuthAccessToken, OIDCMetadata, OIDCOptions, ORGANIZATION_ERROR_CODES, OTPOptions, OidcClientPlugin, OktaOptions, OneTimeTokenOptions, Organization, OrganizationClientOptions, OrganizationInput, OrganizationRole, OrganizationSchema, OverrideMerge, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, PatreonOptions, PhoneNumberOptions, Prettify, PrettifyDeep, type RemoveFieldsWithReturnedFalse, RequiredKeysOf, SessionWithImpersonatedBy, SignInPopupOptions, SignInPopupResult, SlackOptions, StripEmptyObjects, TOTPOptions, TWO_FACTOR_ERROR_CODES, Team, TeamInput, TeamMember, TeamMemberInput, TokenBody, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, UnionToIntersection, UserWithAnonymous, UserWithPhoneNumber, UserWithRole, UserWithTwoFactor, adminClient, anonymousClient, auth0, backupCode2fa, clientSideHasPermission, createSignInPopup, customSessionClient, defaultRolesSchema, deviceAuthorizationClient, emailOTPClient, encodeBackupCodes, generateBackupCodes, genericOAuthClient, getBackupCodes, getStoredPopupToken, gumroad, hubspot, inferAdditionalFields, inferOrgAdditionalFields, invitationSchema, invitationStatus, jwtClient, keycloak, lastLoginMethodClient, line, magicLinkClient, memberSchema, microsoftEntraId, multiSessionClient, oauthPopupClient, oidcClient, okta, oneTapClient, oneTimeTokenClient, organizationClient, organizationRoleSchema, organizationSchema, otp2fa, patreon, phoneNumberClient, popupBearerFetchPlugin, roleSchema, schema, siweClient, slack, teamMemberSchema, teamSchema, totp2fa, twoFactorClient, usernameClient, verifyBackupCode };
@@ -14,6 +14,9 @@ import { lastLoginMethodClient } from "../../plugins/last-login-method/client.mj
14
14
  import { magicLinkClient } from "../../plugins/magic-link/client.mjs";
15
15
  import { MULTI_SESSION_ERROR_CODES } from "../../plugins/multi-session/error-codes.mjs";
16
16
  import { multiSessionClient } from "../../plugins/multi-session/client.mjs";
17
+ import { POPUP_TOKEN_STORAGE_KEY } from "../../plugins/oauth-popup/constants.mjs";
18
+ import { OAUTH_POPUP_ERROR_CODES } from "../../plugins/oauth-popup/error-codes.mjs";
19
+ import { createSignInPopup, getStoredPopupToken, oauthPopupClient, popupBearerFetchPlugin } from "../../plugins/oauth-popup/client.mjs";
17
20
  import { oidcClient } from "../../plugins/oidc-provider/client.mjs";
18
21
  import { oneTapClient } from "../../plugins/one-tap/client.mjs";
19
22
  import { oneTimeTokenClient } from "../../plugins/one-time-token/client.mjs";
@@ -27,4 +30,4 @@ import { twoFactorClient } from "../../plugins/two-factor/client.mjs";
27
30
  import { USERNAME_ERROR_CODES } from "../../plugins/username/error-codes.mjs";
28
31
  import { usernameClient } from "../../plugins/username/client.mjs";
29
32
  import { InferServerPlugin } from "./infer-plugin.mjs";
30
- export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, siweClient, twoFactorClient, usernameClient };
33
+ export { ADMIN_ERROR_CODES, ANONYMOUS_ERROR_CODES, EMAIL_OTP_ERROR_CODES, GENERIC_OAUTH_ERROR_CODES, InferServerPlugin, MULTI_SESSION_ERROR_CODES, OAUTH_POPUP_ERROR_CODES, ORGANIZATION_ERROR_CODES, PHONE_NUMBER_ERROR_CODES, POPUP_TOKEN_STORAGE_KEY, TWO_FACTOR_ERROR_CODES, USERNAME_ERROR_CODES, adminClient, anonymousClient, clientSideHasPermission, createSignInPopup, customSessionClient, deviceAuthorizationClient, emailOTPClient, genericOAuthClient, getStoredPopupToken, inferAdditionalFields, inferOrgAdditionalFields, jwtClient, lastLoginMethodClient, magicLinkClient, multiSessionClient, oauthPopupClient, oidcClient, oneTapClient, oneTimeTokenClient, organizationClient, phoneNumberClient, popupBearerFetchPlugin, siweClient, twoFactorClient, usernameClient };
@@ -4,7 +4,7 @@ import { PreinitializedWritableAtom } from "nanostores";
4
4
  import { BetterFetch, BetterFetchError } from "@better-fetch/fetch";
5
5
 
6
6
  //#region src/client/query.d.ts
7
- type AuthQueryAtom<T> = PreinitializedWritableAtom<{
7
+ type AuthQueryState<T> = {
8
8
  data: null | T;
9
9
  error: null | BetterFetchError;
10
10
  isPending: boolean;
@@ -12,11 +12,12 @@ type AuthQueryAtom<T> = PreinitializedWritableAtom<{
12
12
  refetch: (queryParams?: {
13
13
  query?: SessionQueryParams;
14
14
  } | undefined) => Promise<void>;
15
- }>;
15
+ };
16
+ type AuthQueryAtom<T> = PreinitializedWritableAtom<AuthQueryState<T>>;
16
17
  declare const useAuthQuery: <T>(initializedAtom: PreinitializedWritableAtom<any> | PreinitializedWritableAtom<any>[], path: string, $fetch: BetterFetch, options?: (((value: {
17
18
  data: null | T;
18
19
  error: null | BetterFetchError;
19
20
  isPending: boolean;
20
21
  }) => ClientFetchOption) | ClientFetchOption) | undefined) => AuthQueryAtom<T>;
21
22
  //#endregion
22
- export { AuthQueryAtom, useAuthQuery };
23
+ export { AuthQueryAtom, AuthQueryState, useAuthQuery };
@@ -1,6 +1,10 @@
1
+ import { isJsonEqual, withEquality } from "./equality.mjs";
1
2
  import { atom, onMount } from "nanostores";
2
3
  //#region src/client/query.ts
3
4
  const isServer = () => typeof window === "undefined";
5
+ function isAuthQueryStateEqual(a, b) {
6
+ return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
7
+ }
4
8
  const useAuthQuery = (initializedAtom, path, $fetch, options) => {
5
9
  const value = atom({
6
10
  data: null,
@@ -9,6 +13,7 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
9
13
  isRefetching: false,
10
14
  refetch: (queryParams) => fn(queryParams)
11
15
  });
16
+ withEquality(value, isAuthQueryStateEqual);
12
17
  const fn = async (queryParams) => {
13
18
  return new Promise((resolve) => {
14
19
  const opts = typeof options === "function" ? options({
@@ -23,8 +28,10 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
23
28
  ...queryParams?.query
24
29
  },
25
30
  async onSuccess(context) {
31
+ const current = value.get();
32
+ const stableData = current.data != null && context.data != null && isJsonEqual(current.data, context.data) ? current.data : context.data;
26
33
  value.set({
27
- data: context.data,
34
+ data: stableData,
28
35
  error: null,
29
36
  isPending: false,
30
37
  isRefetching: false,
@@ -73,23 +80,26 @@ const useAuthQuery = (initializedAtom, path, $fetch, options) => {
73
80
  };
74
81
  initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
75
82
  let isInitialized = false;
76
- for (const initAtom of initializedAtom) initAtom.subscribe(async () => {
77
- if (isServer()) return;
78
- if (isInitialized) await fn();
79
- else onMount(value, () => {
80
- const timeoutId = setTimeout(async () => {
81
- if (!isInitialized) {
82
- isInitialized = true;
83
- await fn();
84
- }
85
- }, 0);
86
- return () => {
87
- value.off();
88
- initAtom.off();
89
- clearTimeout(timeoutId);
90
- };
83
+ const cleanups = [];
84
+ for (const initAtom of initializedAtom) {
85
+ const unbind = initAtom.subscribe(async () => {
86
+ if (isServer()) return;
87
+ if (isInitialized) await fn();
88
+ else onMount(value, () => {
89
+ const timeoutId = setTimeout(async () => {
90
+ if (!isInitialized) {
91
+ isInitialized = true;
92
+ await fn();
93
+ }
94
+ }, 0);
95
+ return () => {
96
+ for (const u of cleanups) u();
97
+ clearTimeout(timeoutId);
98
+ };
99
+ });
91
100
  });
92
- });
101
+ cleanups.push(unbind);
102
+ }
93
103
  return value;
94
104
  };
95
105
  //#endregion
@@ -70,11 +70,6 @@ declare function createAuthClient<Option extends BetterAuthClientOptions>(option
70
70
  priority?: RequestPriority | undefined;
71
71
  cache?: RequestCache | undefined;
72
72
  credentials?: RequestCredentials;
73
- headers?: (HeadersInit & (HeadersInit | {
74
- accept: "application/json" | "text/plain" | "application/octet-stream";
75
- "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
76
- authorization: "Bearer" | "Basic";
77
- })) | undefined;
78
73
  integrity?: string | undefined;
79
74
  keepalive?: boolean | undefined;
80
75
  method: string;
@@ -104,6 +99,12 @@ declare function createAuthClient<Option extends BetterAuthClientOptions>(option
104
99
  prefix: string | (() => string | undefined) | undefined;
105
100
  value: string | (() => string | undefined) | undefined;
106
101
  }) | undefined;
102
+ headers?: {} | {
103
+ [x: string]: string | undefined;
104
+ accept?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream") | undefined;
105
+ "content-type"?: ((string & {}) | "application/x-www-form-urlencoded" | "application/json" | "text/plain" | "application/octet-stream" | "multipart/form-data") | undefined;
106
+ authorization?: ((string & {}) | `Bearer ${string}` | `Basic ${string}`) | undefined;
107
+ } | undefined;
107
108
  body?: any;
108
109
  query?: any;
109
110
  params?: any;