@better-auth/electron 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.
@@ -323,11 +323,6 @@ declare const electronClient: <O extends ElectronClientOptions>(options: O) => {
323
323
  mode?: RequestMode | undefined;
324
324
  cache?: RequestCache | undefined;
325
325
  credentials?: RequestCredentials | undefined;
326
- headers?: (HeadersInit & (HeadersInit | {
327
- accept: "application/json" | "text/plain" | "application/octet-stream";
328
- "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
329
- authorization: "Bearer" | "Basic";
330
- })) | undefined;
331
326
  integrity?: string | undefined;
332
327
  keepalive?: boolean | undefined;
333
328
  method?: string | undefined;
@@ -361,6 +356,12 @@ declare const electronClient: <O extends ElectronClientOptions>(options: O) => {
361
356
  prefix: string | (() => string | undefined) | undefined;
362
357
  value: string | (() => string | undefined) | undefined;
363
358
  }) | undefined;
359
+ headers?: {} | {
360
+ [x: string]: string | undefined;
361
+ accept?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream") | undefined;
362
+ "content-type"?: ((string & {}) | "application/x-www-form-urlencoded" | "application/json" | "text/plain" | "multipart/form-data" | "application/octet-stream") | undefined;
363
+ authorization?: ((string & {}) | `Bearer ${string}` | `Basic ${string}`) | undefined;
364
+ } | undefined;
364
365
  body?: any;
365
366
  query?: any;
366
367
  params?: any;
@@ -378,11 +379,6 @@ declare const electronClient: <O extends ElectronClientOptions>(options: O) => {
378
379
  mode?: RequestMode | undefined;
379
380
  cache?: RequestCache | undefined;
380
381
  credentials?: RequestCredentials | undefined;
381
- headers?: (HeadersInit & (HeadersInit | {
382
- accept: "application/json" | "text/plain" | "application/octet-stream";
383
- "content-type": "application/json" | "text/plain" | "application/x-www-form-urlencoded" | "multipart/form-data" | "application/octet-stream";
384
- authorization: "Bearer" | "Basic";
385
- })) | undefined;
386
382
  integrity?: string | undefined;
387
383
  keepalive?: boolean | undefined;
388
384
  method?: string | undefined;
@@ -416,6 +412,12 @@ declare const electronClient: <O extends ElectronClientOptions>(options: O) => {
416
412
  prefix: string | (() => string | undefined) | undefined;
417
413
  value: string | (() => string | undefined) | undefined;
418
414
  }) | undefined;
415
+ headers?: {} | {
416
+ [x: string]: string | undefined;
417
+ accept?: ((string & {}) | "application/json" | "text/plain" | "application/octet-stream") | undefined;
418
+ "content-type"?: ((string & {}) | "application/x-www-form-urlencoded" | "application/json" | "text/plain" | "multipart/form-data" | "application/octet-stream") | undefined;
419
+ authorization?: ((string & {}) | `Bearer ${string}` | `Basic ${string}`) | undefined;
420
+ } | undefined;
419
421
  body?: any;
420
422
  query?: any;
421
423
  params?: any;
package/dist/client.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as setupRenderer, a as authenticate, c as ElectronClientOptions, d as Storage, f as FetchUserImageResult, g as SetupRendererConfig, h as ExposedBridges, i as ElectronRequestAuthOptions, l as ElectronProxyClientOptions, m as normalizeUserOutput, n as handleDeepLink, o as kElectron, p as fetchUserImage, r as ElectronAuthenticateOptions, s as requestAuth, t as electronClient, u as ElectronSharedClientOptions, y as ElectronSharedOptions } from "./client-D6xRwPM0.mjs";
1
+ import { _ as setupRenderer, a as authenticate, c as ElectronClientOptions, d as Storage, f as FetchUserImageResult, g as SetupRendererConfig, h as ExposedBridges, i as ElectronRequestAuthOptions, l as ElectronProxyClientOptions, m as normalizeUserOutput, n as handleDeepLink, o as kElectron, p as fetchUserImage, r as ElectronAuthenticateOptions, s as requestAuth, t as electronClient, u as ElectronSharedClientOptions, y as ElectronSharedOptions } from "./client-hpd3q6M8.mjs";
2
2
  export { ElectronAuthenticateOptions, ElectronClientOptions, ElectronProxyClientOptions, ElectronRequestAuthOptions, ElectronSharedClientOptions, ElectronSharedOptions, ExposedBridges, FetchUserImageResult, SetupRendererConfig, Storage, authenticate, electronClient, fetchUserImage, handleDeepLink, kElectron, normalizeUserOutput, requestAuth, setupRenderer };
package/dist/client.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-BPpah8cV.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BeZU0Td6.mjs";
2
2
  import { n as isProcessType, r as parseProtocolScheme, t as getChannelPrefixWithDelimiter } from "./utils-DxDKRT6e.mjs";
3
3
  import { BetterAuthError } from "@better-auth/core/error";
4
4
  import { base64, base64Url } from "@better-auth/utils/base64";
@@ -579,6 +579,16 @@ const electronClient = (options) => {
579
579
  const cookieName = `${opts.storagePrefix}.cookie`;
580
580
  const localCacheName = `${opts.storagePrefix}.local_cache`;
581
581
  const { getDecrypted, setEncrypted } = storageAdapter(opts.storage, new Set([cookieName, localCacheName]));
582
+ const clearSessionCache = () => {
583
+ setEncrypted(cookieName, "{}");
584
+ store?.atoms.session?.set({
585
+ ...store.atoms.session.get(),
586
+ data: null,
587
+ error: null,
588
+ isPending: false
589
+ });
590
+ setEncrypted(localCacheName, "{}");
591
+ };
582
592
  if ((isDevelopment() || isTest()) && /^(?!\.)(?!.*\.\.)(?!.*\.$)[^.]+\.[^.]+$/.test(scheme)) console.warn("The provided scheme does not follow the reverse domain name notation. For example: `app.example.com` -> `com.example.app`.");
583
593
  return {
584
594
  id: "electron",
@@ -598,16 +608,7 @@ const electronClient = (options) => {
598
608
  "electron-origin": `${scheme}:/`,
599
609
  "x-skip-oauth-proxy": "true"
600
610
  };
601
- if (url.endsWith("/sign-out")) {
602
- setEncrypted(cookieName, "{}");
603
- store?.atoms.session?.set({
604
- ...store.atoms.session.get(),
605
- data: null,
606
- error: null,
607
- isPending: false
608
- });
609
- setEncrypted(localCacheName, "{}");
610
- }
611
+ if (url.endsWith("/sign-out")) clearSessionCache();
611
612
  return {
612
613
  url,
613
614
  options
@@ -630,6 +631,7 @@ const electronClient = (options) => {
630
631
  const data = context.data;
631
632
  setEncrypted(localCacheName, JSON.stringify(data));
632
633
  }
634
+ if (context.request.url.toString().includes("/sign-out")) clearSessionCache();
633
635
  },
634
636
  onError: async (context) => {
635
637
  webContents.getFocusedWebContents()?.send(`${getChannelPrefixWithDelimiter(opts.channelPrefix)}error`, {
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { f as FetchUserImageResult, m as normalizeUserOutput, p as fetchUserImage, v as ElectronOptions, y as ElectronSharedOptions } from "./client-D6xRwPM0.mjs";
1
+ import { f as FetchUserImageResult, m as normalizeUserOutput, p as fetchUserImage, v as ElectronOptions, y as ElectronSharedOptions } from "./client-hpd3q6M8.mjs";
2
2
  import * as better_auth0 from "better-auth";
3
3
  import * as z from "zod";
4
4
  import * as _better_auth_core0 from "@better-auth/core";
@@ -336,6 +336,7 @@ declare const electron: (options?: ElectronOptions | undefined) => {
336
336
  INVALID_CODE_VERIFIER: better_auth0.RawError<"INVALID_CODE_VERIFIER">;
337
337
  MISSING_STATE: better_auth0.RawError<"MISSING_STATE">;
338
338
  MISSING_PKCE: better_auth0.RawError<"MISSING_PKCE">;
339
+ INVALID_PKCE_METHOD: better_auth0.RawError<"INVALID_PKCE_METHOD">;
339
340
  };
340
341
  };
341
342
  //#endregion
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-BPpah8cV.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BeZU0Td6.mjs";
2
2
  import { createAuthMiddleware } from "@better-auth/core/api";
3
3
  import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
4
4
  import { base64Url } from "@better-auth/utils/base64";
@@ -22,7 +22,8 @@ const ELECTRON_ERROR_CODES = defineErrorCodes({
22
22
  MISSING_CODE_CHALLENGE: "missing code challenge",
23
23
  INVALID_CODE_VERIFIER: "Invalid code verifier",
24
24
  MISSING_STATE: "state is required",
25
- MISSING_PKCE: "pkce is required"
25
+ MISSING_PKCE: "pkce is required",
26
+ INVALID_PKCE_METHOD: "PKCE method must be S256"
26
27
  });
27
28
  //#endregion
28
29
  //#region src/routes.ts
@@ -56,18 +57,16 @@ const electronToken = (_opts) => createAuthEndpoint("/electron/token", {
56
57
  }
57
58
  }
58
59
  }, async (ctx) => {
59
- const token = await ctx.context.internalAdapter.findVerificationValue(`electron:${ctx.body.token}`);
60
- if (!token || token.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("NOT_FOUND", ELECTRON_ERROR_CODES.INVALID_TOKEN);
60
+ const token = await ctx.context.internalAdapter.consumeVerificationValue(`electron:${ctx.body.token}`);
61
+ if (!token) throw APIError.from("NOT_FOUND", ELECTRON_ERROR_CODES.INVALID_TOKEN);
61
62
  const tokenRecord = safeJSONParse$1(token.value);
62
63
  if (!tokenRecord) throw APIError.from("INTERNAL_SERVER_ERROR", ELECTRON_ERROR_CODES.INVALID_TOKEN);
63
64
  if (tokenRecord.state !== ctx.body.state) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.STATE_MISMATCH);
64
65
  if (!tokenRecord.codeChallenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_CODE_CHALLENGE);
65
- if (tokenRecord.codeChallengeMethod === "s256") {
66
- const codeChallenge = Buffer.from(base64Url.decode(tokenRecord.codeChallenge));
67
- const codeVerifier = Buffer.from(await createHash("SHA-256").digest(ctx.body.code_verifier));
68
- if (codeChallenge.length !== codeVerifier.length || !timingSafeEqual(codeChallenge, codeVerifier)) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_CODE_VERIFIER);
69
- } else if (tokenRecord.codeChallenge !== ctx.body.code_verifier) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_CODE_VERIFIER);
70
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`electron:${ctx.body.token}`);
66
+ if (tokenRecord.codeChallengeMethod !== "s256") throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_PKCE_METHOD);
67
+ const codeChallenge = Buffer.from(base64Url.decode(tokenRecord.codeChallenge));
68
+ const codeVerifier = Buffer.from(await createHash("SHA-256").digest(ctx.body.code_verifier));
69
+ if (codeChallenge.length !== codeVerifier.length || !timingSafeEqual(codeChallenge, codeVerifier)) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_CODE_VERIFIER);
71
70
  const user = await ctx.context.internalAdapter.findUserById(tokenRecord.userId);
72
71
  if (!user) throw APIError.from("INTERNAL_SERVER_ERROR", BASE_ERROR_CODES.USER_NOT_FOUND);
73
72
  const session = await ctx.context.internalAdapter.createSession(user.id);
@@ -119,13 +118,14 @@ const electronInitOAuthProxy = (opts) => createAuthEndpoint("/electron/init-oaut
119
118
  }, async (ctx) => {
120
119
  const isSocialProvider = SocialProviderListEnum.safeParse(ctx.query.provider);
121
120
  if (!isSocialProvider && !ctx.context.getPlugin("generic-oauth")) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.PROVIDER_NOT_FOUND);
121
+ if (ctx.query.code_challenge_method && ctx.query.code_challenge_method.toLowerCase() !== "s256") throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_PKCE_METHOD);
122
122
  const headers = new Headers(ctx.request?.headers);
123
123
  headers.set("origin", new URL(ctx.context.baseURL).origin);
124
124
  let setCookie = null;
125
125
  const searchParams = new URLSearchParams();
126
126
  searchParams.set("client_id", opts.clientID || "electron");
127
127
  searchParams.set("code_challenge", ctx.query.code_challenge);
128
- searchParams.set("code_challenge_method", ctx.query.code_challenge_method || "plain");
128
+ searchParams.set("code_challenge_method", "S256");
129
129
  searchParams.set("state", ctx.query.state);
130
130
  const res = await betterFetch(`${isSocialProvider ? "/sign-in/social" : "/sign-in/oauth2"}?${searchParams.toString()}`, {
131
131
  baseURL: ctx.context.baseURL,
@@ -204,11 +204,12 @@ const electron = (options) => {
204
204
  return !!(ctx.path?.startsWith("/sign-in") || ctx.path?.startsWith("/sign-up") || ctx.path?.startsWith("/callback") || ctx.path?.startsWith("/oauth2/callback") || ctx.path?.startsWith("/magic-link/verify") || ctx.path?.startsWith("/email-otp/verify-email") || ctx.path?.startsWith("/verify-email") || ctx.path?.startsWith("/one-tap/callback") || ctx.path?.startsWith("/passkey/verify-authentication") || ctx.path?.startsWith("/phone-number/verify"));
205
205
  };
206
206
  const handleTransfer = async (ctx, payload) => {
207
- const { client_id, state, code_challenge, code_challenge_method = "plain" } = payload;
207
+ const { client_id, state, code_challenge, code_challenge_method } = payload;
208
208
  const userId = ctx.context.session?.user.id || ctx.context.newSession?.user.id;
209
209
  if (!userId || client_id !== opts.clientID) return null;
210
210
  if (!state) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_STATE);
211
211
  if (!code_challenge) throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.MISSING_PKCE);
212
+ if (code_challenge_method?.toLowerCase() !== "s256") throw APIError.from("BAD_REQUEST", ELECTRON_ERROR_CODES.INVALID_PKCE_METHOD);
212
213
  const redirectCookieName = `${opts.cookiePrefix}.${opts.clientID}`;
213
214
  const identifier = generateRandomString(32, "a-z", "A-Z", "0-9");
214
215
  const codeExpiresInMs = opts.codeExpiresIn * 1e3;
@@ -218,7 +219,7 @@ const electron = (options) => {
218
219
  value: JSON.stringify({
219
220
  userId,
220
221
  codeChallenge: code_challenge,
221
- codeChallengeMethod: code_challenge_method.toLowerCase(),
222
+ codeChallengeMethod: "s256",
222
223
  state
223
224
  }),
224
225
  expiresAt
@@ -259,7 +260,7 @@ const electron = (options) => {
259
260
  const querySchema = z.object({
260
261
  client_id: z.string(),
261
262
  code_challenge: z.string().nonempty(),
262
- code_challenge_method: z.string().optional().default("plain"),
263
+ code_challenge_method: z.string().optional(),
263
264
  state: z.string().nonempty()
264
265
  });
265
266
  const cookie = ctx.context.createAuthCookie("transfer_token", { maxAge: opts.codeExpiresIn });
@@ -31,6 +31,7 @@ type DBAdapterDebugLogOption = boolean | {
31
31
  delete?: boolean | undefined;
32
32
  deleteMany?: boolean | undefined;
33
33
  consumeOne?: boolean | undefined;
34
+ incrementOne?: boolean | undefined;
34
35
  count?: boolean | undefined;
35
36
  } | {
36
37
  /**
@@ -206,7 +207,7 @@ interface DBAdapterFactoryConfig<Options extends BetterAuthOptions = BetterAuthO
206
207
  /**
207
208
  * The action which was called from the adapter.
208
209
  */
209
- action: "create" | "update" | "findOne" | "findMany" | "updateMany" | "delete" | "deleteMany" | "consumeOne" | "count";
210
+ action: "create" | "update" | "findOne" | "findMany" | "updateMany" | "delete" | "deleteMany" | "consumeOne" | "incrementOne" | "count";
210
211
  /**
211
212
  * The model name.
212
213
  */
@@ -444,6 +445,36 @@ type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
444
445
  model: string;
445
446
  where: Where[];
446
447
  }) => Promise<T | null>;
448
+ /**
449
+ * Atomically apply signed numeric deltas to a single row matching the where
450
+ * clause. For each entry in `increment`, the operation applies
451
+ * `field = field + delta` in one atomic step; a negative delta decrements.
452
+ *
453
+ * The `where` clause is both the selector AND the guard: comparison
454
+ * operators are honored, so passing `{ field: "remaining", operator: "gt",
455
+ * value: 0 }` only mutates the row while `remaining` is still above zero.
456
+ * When the guard matches no row, the operation makes no change and returns
457
+ * `null`.
458
+ *
459
+ * The optional `set` map assigns absolute values to fields in the same
460
+ * atomic operation, alongside the increments.
461
+ *
462
+ * Returns the updated row, or `null` when the guard matched no row. Under
463
+ * concurrent invocation against the same row, this is the race-safe
464
+ * primitive for guarded counter updates (e.g. decrementing a remaining-uses
465
+ * counter only while it is still positive).
466
+ *
467
+ * Always defined on the factory-wrapped adapter. When the underlying
468
+ * `CustomAdapter` does not implement `incrementOne`, the factory provides a
469
+ * fallback that wraps `findMany + updateMany` in `transaction(...)` and
470
+ * re-applies the where clause as a compare-and-swap guard on the update.
471
+ */
472
+ incrementOne: <T>(data: {
473
+ model: string;
474
+ where: Where[];
475
+ increment: Record<string, number>;
476
+ set?: Record<string, unknown> | undefined;
477
+ }) => Promise<T | null>;
447
478
  /**
448
479
  * Execute multiple operations in a transaction.
449
480
  * If the adapter doesn't support transactions, operations will be executed sequentially.
@@ -538,6 +569,25 @@ interface CustomAdapter {
538
569
  model: string;
539
570
  where: CleanedWhere[];
540
571
  }) => Promise<T | null>;
572
+ /**
573
+ * Optional native atomic guarded counter mutation. Applies
574
+ * `field = field + delta` for each entry in `increment` (negative deltas
575
+ * decrement), with `where` acting as both selector and guard and `set`
576
+ * assigning absolute values in the same operation. Returns the updated row,
577
+ * or `null` when the guard matched no row.
578
+ *
579
+ * Implementing this natively (e.g. `UPDATE ... SET n = n + $delta WHERE ...
580
+ * RETURNING *`) gives one round trip and the strongest race-safety
581
+ * guarantee. When omitted, the adapter factory provides a transaction-based
582
+ * fallback over `findMany + updateMany`. TODO(increment-one-required):
583
+ * tighten to required in the next minor on `next`.
584
+ */
585
+ incrementOne?: <T>(data: {
586
+ model: string;
587
+ where: CleanedWhere[];
588
+ increment: Record<string, number>;
589
+ set?: Record<string, unknown> | undefined;
590
+ }) => Promise<T | null>;
541
591
  count: ({
542
592
  model,
543
593
  where
@@ -1293,7 +1343,12 @@ interface GoogleOptions extends ProviderOptions<GoogleProfile> {
1293
1343
  */
1294
1344
  display?: ("page" | "popup" | "touch" | "wap") | undefined;
1295
1345
  /**
1296
- * The hosted domain of the user
1346
+ * The hosted domain (Google Workspace) the user must belong to.
1347
+ *
1348
+ * This is sent to Google as the `hd` authorization hint and, when set, is
1349
+ * also enforced against the `hd` claim of the returned id token/profile.
1350
+ * Sign-in is rejected when the claim is missing or does not match, so this
1351
+ * can be used to restrict sign-in to a Workspace domain.
1297
1352
  */
1298
1353
  hd?: string | undefined;
1299
1354
  }
@@ -3976,14 +4031,14 @@ type BaseAccount = z.infer<typeof accountSchema>;
3976
4031
  */
3977
4032
  type Account<DBOptions extends BetterAuthOptions["account"] = BetterAuthOptions["account"], Plugins extends BetterAuthOptions["plugins"] = BetterAuthOptions["plugins"]> = Prettify$1<BaseAccount & InferDBFieldsFromOptions<DBOptions> & InferDBFieldsFromPlugins<"account", Plugins>>; //#endregion
3978
4033
  //#endregion
3979
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/helper.d.mts
4034
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/helper.d.mts
3980
4035
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
3981
4036
  type IsEmptyObject<T> = keyof T extends never ? true : false;
3982
4037
  type UnionToIntersection<Union> = (Union extends unknown ? (distributedUnion: Union) => void : never) extends ((mergedIntersection: infer Intersection) => void) ? Intersection & Union : never;
3983
4038
  type InferParamPath<Path> = Path extends `${infer _Start}:${infer Param}/${infer Rest}` ? { [K in Param | keyof InferParamPath<Rest>]: string } : Path extends `${infer _Start}:${infer Param}` ? { [K in Param]: string } : Path extends `${infer _Start}/${infer Rest}` ? InferParamPath<Rest> : {};
3984
4039
  type InferParamWildCard<Path> = Path extends `${infer _Start}/*:${infer Param}/${infer Rest}` | `${infer _Start}/**:${infer Param}/${infer Rest}` ? { [K in Param | keyof InferParamPath<Rest>]: string } : Path extends `${infer _Start}/*` ? { [K in "_"]: string } : Path extends `${infer _Start}/${infer Rest}` ? InferParamWildCard<Rest> : {}; //#endregion
3985
4040
  //#endregion
3986
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/standard-schema.d.mts
4041
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/standard-schema.d.mts
3987
4042
  //#region src/standard-schema.d.ts
3988
4043
  /** The Standard Schema interface. */
3989
4044
  interface StandardSchemaV1$1<Input = unknown, Output = Input> {
@@ -4041,7 +4096,7 @@ declare namespace StandardSchemaV1$1 {
4041
4096
  type InferOutput<Schema extends StandardSchemaV1$1> = NonNullable<Schema["~standard"]["types"]>["output"];
4042
4097
  } //#endregion
4043
4098
  //#endregion
4044
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/error.d.mts
4099
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/error.d.mts
4045
4100
  declare const statusCodes: {
4046
4101
  OK: number;
4047
4102
  CREATED: number;
@@ -4119,7 +4174,7 @@ declare const APIError: new (status?: Status | "OK" | "CREATED" | "ACCEPTED" | "
4119
4174
  errorStack: string | undefined;
4120
4175
  }; //#endregion
4121
4176
  //#endregion
4122
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/cookies.d.mts
4177
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/cookies.d.mts
4123
4178
  //#region src/cookies.d.ts
4124
4179
  type CookiePrefixOptions = "host" | "secure";
4125
4180
  type CookieOptions = {
@@ -4209,7 +4264,7 @@ type CookieOptions = {
4209
4264
  prefix?: CookiePrefixOptions;
4210
4265
  };
4211
4266
  //#endregion
4212
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/openapi.d.mts
4267
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/openapi.d.mts
4213
4268
  //#region src/openapi.d.ts
4214
4269
  type OpenAPISchemaType = "string" | "number" | "integer" | "boolean" | "array" | "object";
4215
4270
  interface OpenAPIParameter {
@@ -4231,7 +4286,7 @@ interface OpenAPIParameter {
4231
4286
  };
4232
4287
  }
4233
4288
  //#endregion
4234
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/endpoint.d.mts
4289
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/endpoint.d.mts
4235
4290
  //#region src/endpoint.d.ts
4236
4291
  interface EndpointBaseOptions {
4237
4292
  /**
@@ -4552,6 +4607,22 @@ type EndpointContext<Path extends string, Options extends EndpointOptions, Conte
4552
4607
  * @returns - The cookie string
4553
4608
  */
4554
4609
  setSignedCookie: (key: string, value: string, secret: string, options?: CookieOptions) => Promise<string>;
4610
+ /**
4611
+ * Response headers
4612
+ *
4613
+ * The live `Headers` for the response being built in the current
4614
+ * request. Read it to inspect what has already been queued, e.g. to
4615
+ * avoid emitting a `Set-Cookie` twice or to check headers set by an
4616
+ * earlier handler in the chain.
4617
+ *
4618
+ * @example
4619
+ * ```ts
4620
+ * const alreadySet = ctx.responseHeaders
4621
+ * .getSetCookie()
4622
+ * .some((c) => c.startsWith("session="));
4623
+ * ```
4624
+ */
4625
+ responseHeaders: Headers;
4555
4626
  /**
4556
4627
  * JSON
4557
4628
  *
@@ -4594,7 +4665,7 @@ type Endpoint<Path extends string = string, Options extends EndpointOptions = En
4594
4665
  path: Path;
4595
4666
  }; //#endregion
4596
4667
  //#endregion
4597
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/middleware.d.mts
4668
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/middleware.d.mts
4598
4669
  //#region src/middleware.d.ts
4599
4670
  interface MiddlewareOptions extends Omit<EndpointOptions, "method"> {}
4600
4671
  type MiddlewareContext<Options extends MiddlewareOptions, Context = {}> = EndpointContext<string, Options & {
@@ -4701,7 +4772,7 @@ type Middleware<Options extends MiddlewareOptions = MiddlewareOptions, Handler e
4701
4772
  options: Options;
4702
4773
  }; //#endregion
4703
4774
  //#endregion
4704
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/context.d.mts
4775
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/context.d.mts
4705
4776
  //#region src/context.d.ts
4706
4777
  type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
4707
4778
  type Method = HTTPMethod | "*";
@@ -4917,6 +4988,22 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
4917
4988
  * pair at single-use credential consumption sites.
4918
4989
  */
4919
4990
  consumeVerificationValue(identifier: string): Promise<Verification | null>;
4991
+ /**
4992
+ * First-writer-wins create keyed by a deterministic primary key derived from
4993
+ * `identifier`. Returns `true` when this caller created the row and `false`
4994
+ * when a row for the same identifier already existed.
4995
+ *
4996
+ * The dual of `consumeVerificationValue`: reserve races to create a marker
4997
+ * exactly once, where consume races to delete one exactly once. Use it for
4998
+ * replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
4999
+ * wins. The database path is atomic via the primary key; the
5000
+ * secondary-storage-only path is best-effort under concurrency.
5001
+ */
5002
+ reserveVerificationValue(data: {
5003
+ identifier: string;
5004
+ value: string;
5005
+ expiresAt: Date;
5006
+ }): Promise<boolean>;
4920
5007
  updateVerificationByIdentifier(identifier: string, data: Partial<Verification>): Promise<Verification>;
4921
5008
  refreshUserSessions(user: User): Promise<void>;
4922
5009
  }
@@ -5403,6 +5490,35 @@ type BaseURLConfig = string | DynamicBaseURLConfig;
5403
5490
  interface BetterAuthRateLimitStorage {
5404
5491
  get: (key: string) => Promise<RateLimit | null | undefined>;
5405
5492
  set: (key: string, value: RateLimit, update?: boolean | undefined) => Promise<void>;
5493
+ /**
5494
+ * Atomically records one request against `key` within the `window`
5495
+ * (in seconds) and reports whether it is allowed.
5496
+ *
5497
+ * When `allowed` is true the request was counted within the active window;
5498
+ * when `allowed` is false the limit was already reached and `retryAfter` is
5499
+ * the number of seconds until the window frees up. Whether the window slides
5500
+ * or is fixed depends on the backing storage: the database backend resets
5501
+ * once the window elapses, while secondary storage uses a fixed time-to-live
5502
+ * set when the window first opens.
5503
+ *
5504
+ * Performing the check and the increment in a single step closes the
5505
+ * concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
5506
+ * requests can no longer all pass a stale read before any increment lands.
5507
+ *
5508
+ * Optional for backwards compatibility. A storage without it falls back to
5509
+ * the legacy non-atomic `get`/`set` path, which is best-effort under
5510
+ * concurrency.
5511
+ *
5512
+ * TODO(rate-limit-consume-required): make this the sole required member on
5513
+ * `next`, dropping `get`/`set` and the non-atomic fallback.
5514
+ */
5515
+ consume?: (key: string, rule: {
5516
+ window: number;
5517
+ max: number;
5518
+ }) => Promise<{
5519
+ allowed: boolean;
5520
+ retryAfter: number | null;
5521
+ }>;
5406
5522
  }
5407
5523
  type BetterAuthRateLimitRule = {
5408
5524
  /**
@@ -6856,6 +6972,21 @@ interface SecondaryStorage {
6856
6972
  * security-sensitive consume paths.
6857
6973
  */
6858
6974
  getAndDelete?: (key: string) => Awaitable<unknown>;
6975
+ /**
6976
+ * Atomically increment the counter at `key` by one, returning the
6977
+ * post-increment value.
6978
+ *
6979
+ * When the key is absent, it is created with a value of `1` and the given
6980
+ * `ttl` (in SECONDS). The TTL is applied only on creation; later increments
6981
+ * never extend it, so the counter expires a fixed window after it was first
6982
+ * created.
6983
+ *
6984
+ * This is optional for backwards compatibility with existing secondary
6985
+ * storage implementations. TODO(secondary-storage-increment-required): make
6986
+ * this required for secondary-storage-backed rate limiting in the next minor
6987
+ * on `next`.
6988
+ */
6989
+ increment?: (key: string, ttl: number) => Awaitable<number>;
6859
6990
  set: (
6860
6991
  /**
6861
6992
  * Key to store
package/dist/preload.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import electron, { contextBridge } from "electron";
2
- //#region ../../node_modules/.pnpm/better-call@1.3.5_zod@4.3.6/node_modules/better-call/dist/error.mjs
2
+ //#region ../../node_modules/.pnpm/better-call@1.3.6_zod@4.3.6/node_modules/better-call/dist/error.mjs
3
3
  function isErrorStackTraceLimitWritable() {
4
4
  const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit");
5
5
  if (desc === void 0) return Object.isExtensible(Error);
package/dist/proxy.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { l as ElectronProxyClientOptions } from "./client-D6xRwPM0.mjs";
1
+ import { l as ElectronProxyClientOptions } from "./client-hpd3q6M8.mjs";
2
2
  import { electron } from "./index.mjs";
3
3
 
4
4
  //#region src/proxy.d.ts
package/dist/proxy.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as PACKAGE_VERSION } from "./version-BPpah8cV.mjs";
1
+ import { t as PACKAGE_VERSION } from "./version-BeZU0Td6.mjs";
2
2
  import { r as parseProtocolScheme } from "./utils-DxDKRT6e.mjs";
3
3
  import { parseCookies } from "better-auth/cookies";
4
4
  //#region src/proxy.ts
@@ -1,4 +1,4 @@
1
- import { d as Storage } from "./client-D6xRwPM0.mjs";
1
+ import { d as Storage } from "./client-hpd3q6M8.mjs";
2
2
  import { Options } from "conf";
3
3
 
4
4
  //#region src/storage.d.ts
@@ -1,5 +1,5 @@
1
1
  //#endregion
2
2
  //#region src/version.ts
3
- const PACKAGE_VERSION = "1.6.15";
3
+ const PACKAGE_VERSION = "1.6.17";
4
4
  //#endregion
5
5
  export { PACKAGE_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth/electron",
3
- "version": "1.6.15",
3
+ "version": "1.6.17",
4
4
  "description": "Better Auth integration for Electron applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -80,17 +80,17 @@
80
80
  "better-sqlite3": "^12.6.2",
81
81
  "electron": "^41.2.2",
82
82
  "tsdown": "0.21.1",
83
- "@better-auth/core": "1.6.15",
84
- "better-auth": "1.6.15"
83
+ "better-auth": "1.6.17",
84
+ "@better-auth/core": "1.6.17"
85
85
  },
86
86
  "peerDependencies": {
87
87
  "@better-auth/utils": "0.4.1",
88
- "@better-fetch/fetch": "1.1.21",
89
- "better-call": "1.3.5",
88
+ "@better-fetch/fetch": "1.3.0",
89
+ "better-call": "1.3.6",
90
90
  "conf": "^15.0.2",
91
91
  "electron": ">=36.0.0",
92
- "@better-auth/core": "^1.6.15",
93
- "better-auth": "^1.6.15"
92
+ "@better-auth/core": "^1.6.17",
93
+ "better-auth": "^1.6.17"
94
94
  },
95
95
  "peerDependenciesMeta": {
96
96
  "electron": {