nuxt-convex-module 0.12.3 → 0.14.0

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 (45) hide show
  1. package/dist/devtools-client/_nuxt/C-J9qZjr.js +3 -0
  2. package/dist/devtools-client/_nuxt/CB1p6iYX.js +1 -0
  3. package/dist/devtools-client/_nuxt/{COXexnyN.js → CtC_CmOY.js} +2 -2
  4. package/dist/devtools-client/_nuxt/{entry.DsZySRVW.css → entry.jOWrhpYd.css} +1 -1
  5. package/dist/devtools-client/index.html +1 -1
  6. package/dist/module.d.mts +18 -0
  7. package/dist/module.json +1 -1
  8. package/dist/module.mjs +129 -38
  9. package/dist/runtime/better-auth/nuxt/middleware.js +8 -2
  10. package/dist/runtime/better-auth/nuxt/no-session-vary.d.ts +38 -0
  11. package/dist/runtime/better-auth/nuxt/no-session-vary.js +21 -0
  12. package/dist/runtime/better-auth/nuxt/server.js +4 -1
  13. package/dist/runtime/better-auth/nuxt/shared-render.d.ts +9 -0
  14. package/dist/runtime/better-auth/nuxt/shared-render.js +17 -0
  15. package/dist/runtime/better-auth/vue/auth-client-loader.d.ts +17 -0
  16. package/dist/runtime/better-auth/vue/auth-client-loader.js +186 -0
  17. package/dist/runtime/better-auth/vue/client.d.ts +3 -3
  18. package/dist/runtime/better-auth/vue/cross-domain.d.ts +26 -0
  19. package/dist/runtime/better-auth/vue/cross-domain.js +52 -35
  20. package/dist/runtime/better-auth/vue/index.d.ts +2 -1
  21. package/dist/runtime/better-auth/vue/index.js +1 -0
  22. package/dist/runtime/better-auth/vue/plugin.client.js +21 -2
  23. package/dist/runtime/better-auth/vue/plugin.server.js +23 -3
  24. package/dist/runtime/better-auth/vue/token-store.js +25 -1
  25. package/dist/runtime/better-auth/vue/use-better-auth.d.ts +11 -5
  26. package/dist/runtime/better-auth/vue/use-better-auth.js +135 -32
  27. package/dist/runtime/nuxt/composables/use-async-paginated-query.d.ts +1 -1
  28. package/dist/runtime/nuxt/composables/use-async-paginated-query.js +5 -3
  29. package/dist/runtime/nuxt/composables/use-async-query.d.ts +4 -1
  30. package/dist/runtime/nuxt/composables/use-async-query.js +6 -3
  31. package/dist/runtime/nuxt/csp.d.ts +2 -1
  32. package/dist/runtime/nuxt/csp.js +9 -3
  33. package/dist/runtime/nuxt/function-name.d.ts +9 -0
  34. package/dist/runtime/nuxt/function-name.js +38 -0
  35. package/dist/runtime/nuxt/index.js +4 -3
  36. package/dist/runtime/nuxt/testing.d.ts +21 -0
  37. package/dist/runtime/nuxt/testing.js +1 -0
  38. package/dist/runtime/vue/auth/index.d.ts +2 -1
  39. package/dist/runtime/vue/auth/index.js +62 -13
  40. package/dist/runtime/vue/composables/use-upload-queue.d.ts +70 -3
  41. package/dist/runtime/vue/composables/use-upload-queue.js +74 -19
  42. package/dist/runtime/vue/index.d.ts +1 -1
  43. package/package.json +19 -12
  44. package/dist/devtools-client/_nuxt/AqohFEu1.js +0 -1
  45. package/dist/devtools-client/_nuxt/BPtiIkWa.js +0 -3
@@ -0,0 +1,186 @@
1
+ import { getCurrentScope, onScopeDispose, shallowRef, watch } from "vue";
2
+ let loaded;
3
+ let loading;
4
+ let deferred = false;
5
+ const waiting = /* @__PURE__ */ new Set();
6
+ let evaluated;
7
+ let stopSignal;
8
+ export function loadAuthClient() {
9
+ if (evaluated) registerAuthClient(evaluated);
10
+ loading ??= import("#convex/auth-client").then(
11
+ ({ authClient }) => {
12
+ registerAuthClient(authClient);
13
+ return authClient;
14
+ },
15
+ (error) => {
16
+ loading = void 0;
17
+ throw error;
18
+ }
19
+ );
20
+ return loading;
21
+ }
22
+ export function registerEvaluatedAuthClient(client) {
23
+ if (loaded || evaluated === client) return;
24
+ if (!deferred) return registerAuthClient(client);
25
+ evaluated = client;
26
+ const signal = client.$store?.atoms?.$sessionSignal;
27
+ stopSignal = signal?.listen?.(() => registerAuthClient(client));
28
+ }
29
+ export function registerAuthClient(client) {
30
+ if (loaded) return;
31
+ stopSignal?.();
32
+ stopSignal = void 0;
33
+ evaluated = void 0;
34
+ loaded = client;
35
+ loading ??= Promise.resolve(client);
36
+ const listeners = [...waiting];
37
+ waiting.clear();
38
+ for (const listener of listeners) {
39
+ try {
40
+ listener(client);
41
+ } catch (error) {
42
+ report(error);
43
+ }
44
+ }
45
+ }
46
+ function report(error) {
47
+ if (typeof globalThis.reportError === "function") globalThis.reportError(error);
48
+ else queueMicrotask(() => {
49
+ throw error;
50
+ });
51
+ }
52
+ export function loadedAuthClient() {
53
+ return loaded;
54
+ }
55
+ export function onAuthClient(listener) {
56
+ if (loaded) {
57
+ listener(loaded);
58
+ return () => {
59
+ };
60
+ }
61
+ waiting.add(listener);
62
+ return () => waiting.delete(listener);
63
+ }
64
+ export function deferAuthClient() {
65
+ if (deferred || loaded) return;
66
+ deferred = true;
67
+ const onVisible = () => {
68
+ if (document.visibilityState !== "visible") return;
69
+ void loadAuthClient().then(() => document.removeEventListener("visibilitychange", onVisible), () => {
70
+ });
71
+ };
72
+ document.addEventListener("visibilitychange", onVisible);
73
+ }
74
+ export function retryAuthClientLoad() {
75
+ if (loaded || typeof window === "undefined") return;
76
+ const retry = () => {
77
+ if (document.visibilityState !== "visible") return;
78
+ void loadAuthClient().then(() => {
79
+ window.removeEventListener("online", retry);
80
+ document.removeEventListener("visibilitychange", retry);
81
+ }, () => {
82
+ });
83
+ };
84
+ window.addEventListener("online", retry);
85
+ document.addEventListener("visibilitychange", retry);
86
+ }
87
+ export function isAuthClientDeferred() {
88
+ return deferred && !loaded;
89
+ }
90
+ function reach(client, path) {
91
+ let owner;
92
+ let value = client;
93
+ for (const key of path) {
94
+ owner = value;
95
+ value = value[key];
96
+ }
97
+ return { owner, value };
98
+ }
99
+ function call(client, path, args) {
100
+ const { owner, value } = reach(client, path);
101
+ return Reflect.apply(value, owner, args);
102
+ }
103
+ const HOOK = /^use[A-Z]/;
104
+ function pendingSubscription(path, args) {
105
+ let unsubscribe;
106
+ const stopWaiting = onAuthClient((client) => {
107
+ unsubscribe = call(client, path, args);
108
+ });
109
+ void loadAuthClient().catch(() => {
110
+ });
111
+ return () => {
112
+ stopWaiting();
113
+ unsubscribe?.();
114
+ };
115
+ }
116
+ function pendingHook(path, args) {
117
+ const scope = getCurrentScope();
118
+ const refetch = async (...refetchArgs) => {
119
+ await loadAuthClient();
120
+ const next = state.value.refetch;
121
+ if (next === refetch) return;
122
+ return next(...refetchArgs);
123
+ };
124
+ const state = shallowRef({
125
+ data: null,
126
+ error: null,
127
+ isPending: true,
128
+ isRefetching: false,
129
+ refetch
130
+ });
131
+ if (import.meta.server) return state;
132
+ const stopWaiting = onAuthClient((client) => {
133
+ const follow = () => watch(call(client, path, args), (value) => {
134
+ state.value = value;
135
+ }, { immediate: true, flush: "sync" });
136
+ if (!scope) follow();
137
+ else if (scope.active) scope.run(follow);
138
+ });
139
+ if (scope) onScopeDispose(stopWaiting);
140
+ void loadAuthClient().catch(() => loadAuthClient()).catch((error) => {
141
+ if (!loaded) state.value = { ...state.value, isPending: false, error };
142
+ });
143
+ return state;
144
+ }
145
+ const withReceivers = /* @__PURE__ */ new WeakMap();
146
+ function withReceiver(member, owner) {
147
+ if (typeof member !== "function" || typeof owner !== "object" || owner === null) return member;
148
+ let byMethod = withReceivers.get(owner);
149
+ if (!byMethod) withReceivers.set(owner, byMethod = /* @__PURE__ */ new WeakMap());
150
+ let wrapped = byMethod.get(member);
151
+ if (!wrapped) {
152
+ wrapped = new Proxy(member, {
153
+ apply: (fn, _this, args) => Reflect.apply(fn, owner, args)
154
+ });
155
+ byMethod.set(member, wrapped);
156
+ }
157
+ return wrapped;
158
+ }
159
+ function lazyPath(path) {
160
+ return new Proxy(() => {
161
+ }, {
162
+ get(_target, key) {
163
+ if (loaded) {
164
+ const owner = reach(loaded, path).value;
165
+ return withReceiver(owner[key], owner);
166
+ }
167
+ if (key === "then") return void 0;
168
+ return lazyPath([...path, key]);
169
+ },
170
+ apply(_target, _this, args) {
171
+ if (loaded) return call(loaded, path, args);
172
+ const name = path.at(-1);
173
+ if (typeof name === "string" && HOOK.test(name) && typeof args[0] !== "function") return pendingHook(path, args);
174
+ if (name === "subscribe" || name === "listen") return pendingSubscription(path, args);
175
+ return loadAuthClient().then((client) => call(client, path, args));
176
+ }
177
+ });
178
+ }
179
+ export const lazyAuthClient = lazyPath([]);
180
+ export function __resetAuthClientLoaderForTests() {
181
+ deferred = false;
182
+ stopSignal?.();
183
+ stopSignal = void 0;
184
+ evaluated = void 0;
185
+ waiting.clear();
186
+ }
@@ -1,6 +1,6 @@
1
1
  import { convexClient } from '@convex-dev/better-auth/client/plugins';
2
- import { createAuthClient } from 'better-auth/vue';
3
- export declare const authClient: ReturnType<typeof createAuthClient<{
2
+ import { type VueAuthClient } from 'better-auth/vue';
3
+ export declare const authClient: VueAuthClient<{
4
4
  plugins: ReturnType<typeof convexClient>[];
5
- }>>;
5
+ }>;
6
6
  export type AuthClient = typeof authClient;
@@ -1,3 +1,29 @@
1
+ import type { AuthClient } from '#convex/auth-client';
2
+ /**
3
+ * An {@link AuthClient} that also includes the `crossDomainClient()` plugin.
4
+ *
5
+ * The aliased client's type only reflects the plugins the app installed (the
6
+ * bundled default omits `crossDomainClient()`), so this describes the
7
+ * cross-domain additions itself.
8
+ *
9
+ * @public
10
+ */
11
+ export type AuthClientWithCrossDomain = AuthClient & {
12
+ updateSession: () => void;
13
+ crossDomain?: {
14
+ oneTimeToken: {
15
+ verify: (args: {
16
+ token: string;
17
+ }) => Promise<{
18
+ data?: {
19
+ session?: {
20
+ token: string;
21
+ } | null;
22
+ } | null;
23
+ }>;
24
+ };
25
+ };
26
+ };
1
27
  /**
2
28
  * Options for {@link consumeCrossDomainOneTimeToken}.
3
29
  *
@@ -1,4 +1,4 @@
1
- import { authClient } from "#convex/auth-client";
1
+ import { loadAuthClient, onAuthClient } from "./auth-client-loader.js";
2
2
  const normalizePathname = (path) => {
3
3
  const slashed = path.startsWith("/") ? path : `/${path}`;
4
4
  let end = slashed.length;
@@ -12,41 +12,58 @@ export async function consumeCrossDomainOneTimeToken(options = {}) {
12
12
  }
13
13
  const url = new URL(window.location.href);
14
14
  const token = url.searchParams.get("ott");
15
- if (token) {
16
- const authClientWithCrossDomain = authClient;
17
- url.searchParams.delete("ott");
18
- window.history.replaceState({}, "", url);
19
- if (!authClientWithCrossDomain.crossDomain) {
20
- return;
21
- }
22
- if (options.callbackRoute && normalizePathname(url.pathname) !== normalizePathname(options.callbackRoute)) {
23
- console.warn(
24
- `[nuxt-convex-module] Ignoring cross-domain one-time token on "${url.pathname}" \u2014 consumption is restricted to "${options.callbackRoute}" (convex.betterAuth.crossDomainCallbackRoute).`
25
- );
26
- return;
27
- }
28
- if (import.meta.dev && !options.callbackRoute) {
29
- console.warn(
30
- `[nuxt-convex-module] Exchanged a cross-domain one-time token on "${url.pathname}" with no \`convex.betterAuth.crossDomainCallbackRoute\` set \u2014 any page can complete sign-in. Restrict it to your callback route.`
31
- );
32
- }
33
- try {
34
- const result = await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
35
- token
36
- });
37
- const session = result.data?.session;
38
- if (session) {
39
- await authClient.getSession({
40
- fetchOptions: {
41
- headers: {
42
- Authorization: `Bearer ${session.token}`
43
- }
15
+ if (!token) return;
16
+ url.searchParams.delete("ott");
17
+ window.history.replaceState({}, "", url);
18
+ const authClient = await loadAuthClient().catch(() => loadAuthClient()).catch((error) => {
19
+ console.warn("[nuxt-convex-module] Failed to load the Better Auth client:", error);
20
+ });
21
+ if (!authClient) {
22
+ onAuthClient((client) => void exchangeKeptToken(client, token, url, options));
23
+ return;
24
+ }
25
+ await exchangeOneTimeToken(authClient, token, url, options);
26
+ }
27
+ async function exchangeKeptToken(authClient, token, url, options) {
28
+ const current = await Promise.resolve().then(() => authClient.getSession()).catch(() => void 0);
29
+ if (!current || current.error || current.data?.session) {
30
+ if (!current?.data?.session) console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", current?.error ?? "the session lookup failed");
31
+ return;
32
+ }
33
+ await exchangeOneTimeToken(authClient, token, url, options);
34
+ }
35
+ async function exchangeOneTimeToken(authClient, token, url, options) {
36
+ const authClientWithCrossDomain = authClient;
37
+ if (!authClientWithCrossDomain?.crossDomain) {
38
+ return;
39
+ }
40
+ if (options.callbackRoute && normalizePathname(url.pathname) !== normalizePathname(options.callbackRoute)) {
41
+ console.warn(
42
+ `[nuxt-convex-module] Ignoring cross-domain one-time token on "${url.pathname}" \u2014 consumption is restricted to "${options.callbackRoute}" (convex.betterAuth.crossDomainCallbackRoute).`
43
+ );
44
+ return;
45
+ }
46
+ if (import.meta.dev && !options.callbackRoute) {
47
+ console.warn(
48
+ `[nuxt-convex-module] Exchanged a cross-domain one-time token on "${url.pathname}" with no \`convex.betterAuth.crossDomainCallbackRoute\` set \u2014 any page can complete sign-in. Restrict it to your callback route.`
49
+ );
50
+ }
51
+ try {
52
+ const result = await authClientWithCrossDomain.crossDomain.oneTimeToken.verify({
53
+ token
54
+ });
55
+ const session = result.data?.session;
56
+ if (session) {
57
+ await authClientWithCrossDomain.getSession({
58
+ fetchOptions: {
59
+ headers: {
60
+ Authorization: `Bearer ${session.token}`
44
61
  }
45
- });
46
- await authClientWithCrossDomain.updateSession();
47
- }
48
- } catch (error) {
49
- console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", error);
62
+ }
63
+ });
64
+ await authClientWithCrossDomain.updateSession();
50
65
  }
66
+ } catch (error) {
67
+ console.warn("[nuxt-convex-module] Failed to consume cross-domain one-time token:", error);
51
68
  }
52
69
  }
@@ -6,8 +6,9 @@
6
6
  */
7
7
  export { useBetterAuth, type UseBetterAuthReturn, type BetterAuthSession, type BetterAuthUser } from './use-better-auth.js';
8
8
  export { authClient, type AuthClient } from '#convex/auth-client';
9
+ export { loadAuthClient } from './auth-client-loader.js';
9
10
  export { usePreloadedAuthQuery } from './hydration.js';
10
- export { consumeCrossDomainOneTimeToken, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
11
+ export { consumeCrossDomainOneTimeToken, type AuthClientWithCrossDomain, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
11
12
  export { resolveAuthRedirect } from './redirect.js';
12
13
  export { AuthBoundary } from './auth-boundary.js';
13
14
  export { convexClient, crossDomainClient } from '@convex-dev/better-auth/client/plugins';
@@ -1,5 +1,6 @@
1
1
  export { useBetterAuth } from "./use-better-auth.js";
2
2
  export { authClient } from "#convex/auth-client";
3
+ export { loadAuthClient } from "./auth-client-loader.js";
3
4
  export { usePreloadedAuthQuery } from "./hydration.js";
4
5
  export { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
5
6
  export { resolveAuthRedirect } from "./redirect.js";
@@ -2,18 +2,37 @@ import { defineNuxtPlugin, useRuntimeConfig, useState } from "#app";
2
2
  import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
3
3
  import { ConvexAuthStateKey, createScopedConvexAuthState } from "../../vue/auth/index.js";
4
4
  import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
5
- import { useBetterAuth } from "./use-better-auth.js";
5
+ import { settleSessionWithoutClient, useBetterAuth } from "./use-better-auth.js";
6
+ import { deferAuthClient } from "./auth-client-loader.js";
7
+ import { NO_SESSION_KEY } from "./token-store.js";
6
8
  import { consumeCrossDomainOneTimeToken } from "./cross-domain.js";
9
+ function holdsCrossDomainSession() {
10
+ return [() => localStorage, () => sessionStorage].some((open) => {
11
+ try {
12
+ const storage = open();
13
+ for (let index = 0; index < storage.length; index++) {
14
+ const key = storage.key(index);
15
+ if (key?.endsWith("_cookie") && storage.getItem(key)?.includes("session_token")) return true;
16
+ }
17
+ } catch {
18
+ }
19
+ return false;
20
+ });
21
+ }
7
22
  export default defineNuxtPlugin({
8
23
  name: "nuxt-convex-module:better-auth:client",
9
24
  async setup(nuxtApp) {
10
- const { url, crossDomainCallbackRoute } = useRuntimeConfig().public.convex;
25
+ const { url, crossDomainCallbackRoute, deferAuthClient: mayDefer } = useRuntimeConfig().public.convex;
11
26
  if (!url) {
12
27
  console.warn("[nuxt-convex-module] No Convex deployment URL configured \u2014 client and auth not created. Set NUXT_PUBLIC_CONVEX_URL or `convex.url` in nuxt.config.");
13
28
  return;
14
29
  }
15
30
  const client = new ConvexVueClient(url);
16
31
  nuxtApp.vueApp.provide(ConvexClientKey, client);
32
+ if (mayDefer !== false && useState(NO_SESSION_KEY, () => false).value && !new URL(window.location.href).searchParams.has("ott") && !holdsCrossDomainSession()) {
33
+ deferAuthClient();
34
+ nuxtApp.hook("app:suspense:resolve", settleSessionWithoutClient);
35
+ }
17
36
  await consumeCrossDomainOneTimeToken({
18
37
  callbackRoute: crossDomainCallbackRoute || void 0
19
38
  });
@@ -1,10 +1,11 @@
1
1
  import { computed } from "vue";
2
- import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent, useResponseHeader } from "#app";
2
+ import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent, useRequestHeaders, useResponseHeader } from "#app";
3
3
  import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
4
4
  import { CONVEX_INITIAL_TOKEN_KEY, closeAfterRender } from "../../nuxt/config.js";
5
5
  import { convexAuth } from "../nuxt/server.js";
6
6
  import { ConvexAuthStateKey } from "../../vue/auth/index.js";
7
- import { BetterAuthTokenStoreKey, createBetterAuthTokenStore, seedInitialToken } from "./token-store.js";
7
+ import { CACHING_HEADERS, isSharedRender, NO_SESSION_CONTEXT_KEY } from "../nuxt/shared-render.js";
8
+ import { BetterAuthTokenStoreKey, createBetterAuthTokenStore, hasSessionCookie, NO_SESSION_KEY, seedInitialToken } from "./token-store.js";
8
9
  export async function prefetchAuthToken(event, initialToken, cacheControl) {
9
10
  try {
10
11
  const token = await convexAuth(event).getToken();
@@ -12,10 +13,20 @@ export async function prefetchAuthToken(event, initialToken, cacheControl) {
12
13
  if (token && event) {
13
14
  cacheControl.value = "private, no-store";
14
15
  }
16
+ return true;
15
17
  } catch (error) {
16
18
  console.warn("[nuxt-convex-module] Failed to prefetch auth token for SSR:", error);
19
+ return false;
17
20
  }
18
21
  }
22
+ function renderContext(event) {
23
+ const context = event.context;
24
+ return {
25
+ routeRules: context._nitro?.routeRules,
26
+ cache: context.cache,
27
+ headers: Object.fromEntries(CACHING_HEADERS.map((name) => [name, event.node?.res?.getHeader?.(name)]))
28
+ };
29
+ }
19
30
  export default defineNuxtPlugin({
20
31
  name: "nuxt-convex-module:better-auth:server",
21
32
  async setup(nuxtApp) {
@@ -36,8 +47,17 @@ export default defineNuxtPlugin({
36
47
  nuxtApp.vueApp.provide(ConvexAuthStateKey, ssrAuthState);
37
48
  const event = useRequestEvent();
38
49
  if (event && !import.meta.prerender) {
39
- await prefetchAuthToken(event, initialToken, useResponseHeader("Cache-Control"));
50
+ const cacheControl = useResponseHeader("Cache-Control");
51
+ const noSession = useState(NO_SESSION_KEY, () => false);
52
+ const { cookie } = useRequestHeaders(["cookie"]);
53
+ const answered = await prefetchAuthToken(event, initialToken, cacheControl);
40
54
  seedInitialToken(tokenStore, initialToken.value);
55
+ noSession.value = answered && !initialToken.value && !hasSessionCookie(cookie) && !isSharedRender(renderContext(event));
56
+ nuxtApp.hooks.hook("app:rendered", () => {
57
+ if (noSession.value && isSharedRender(renderContext(event))) noSession.value = false;
58
+ const context = event.context;
59
+ context[NO_SESSION_CONTEXT_KEY] = noSession.value;
60
+ });
41
61
  }
42
62
  }
43
63
  });
@@ -5,14 +5,38 @@ export function createBetterAuthTokenStore() {
5
5
  cachedTokenVersion: null,
6
6
  pendingToken: null,
7
7
  pendingTokenVersion: null,
8
- initialTokenUsed: false
8
+ initialTokenUsed: false,
9
+ initialTokenSessionId: void 0
9
10
  };
10
11
  }
11
12
  export const BetterAuthTokenStoreKey = Symbol("BetterAuthTokenStore");
13
+ export const NO_SESSION_KEY = "convex:betterAuth:noSession";
14
+ export function hasSessionCookie(cookie) {
15
+ return !!cookie && cookie.split(";").some((pair) => pair.split("=", 1)[0].trim().endsWith("session_token"));
16
+ }
12
17
  export const PENDING_AUTH_VERSION = "__pending__";
18
+ export function readTokenClaims(token) {
19
+ try {
20
+ const payload = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
21
+ return JSON.parse(atob(payload)) ?? void 0;
22
+ } catch {
23
+ return void 0;
24
+ }
25
+ }
26
+ const getTokenSessionId = (token) => {
27
+ const sessionId = readTokenClaims(token)?.sessionId;
28
+ return typeof sessionId === "string" ? sessionId : void 0;
29
+ };
13
30
  export function seedInitialToken(store, initialToken) {
14
31
  if (store.initialTokenUsed || !initialToken) return;
15
32
  store.cachedToken.value = initialToken;
16
33
  store.cachedTokenVersion = PENDING_AUTH_VERSION;
34
+ store.initialTokenSessionId = getTokenSessionId(initialToken);
17
35
  store.initialTokenUsed = true;
18
36
  }
37
+ export function clearToken(store) {
38
+ store.cachedToken.value = null;
39
+ store.cachedTokenVersion = null;
40
+ store.pendingToken = null;
41
+ store.pendingTokenVersion = null;
42
+ }
@@ -1,6 +1,6 @@
1
1
  import type { AuthTokenFetcher } from 'convex/browser';
2
2
  import { type ComputedRef } from 'vue';
3
- import { type AuthClient } from '#convex/auth-client';
3
+ import type { AuthClient } from '#convex/auth-client';
4
4
  export type BetterAuthSession = AuthClient['useSession'] extends {
5
5
  (): infer R;
6
6
  (useFetch: any): any;
@@ -13,9 +13,9 @@ export type BetterAuthUser = {
13
13
  } & Record<string, unknown>;
14
14
  /** What `useBetterAuth()` returns. */
15
15
  export interface UseBetterAuthReturn {
16
- /** `true` until the session is known and no prefetched token covers the gap. */
16
+ /** `true` until the session is known and no prefetched token covers the gap, or while a token from another user is replaced. */
17
17
  isLoading: ComputedRef<boolean>;
18
- /** `true` when Better Auth reports a session, or a token is cached while it reloads. */
18
+ /** `true` when Better Auth reports a session, or the prefetched token covers the session loading. */
19
19
  isAuthenticated: ComputedRef<boolean>;
20
20
  /** Convex's token fetcher: the Better Auth JWT for the current session, or `null`. */
21
21
  fetchAccessToken: AuthTokenFetcher;
@@ -25,8 +25,8 @@ export interface UseBetterAuthReturn {
25
25
  session: BetterAuthSession;
26
26
  /** The current user, or `null` when signed out / still loading. */
27
27
  user: ComputedRef<BetterAuthUser | null>;
28
- /** The session id (or user id); changes when the signed-in identity changes. */
29
- authVersion: ComputedRef<string | null>;
28
+ /** The session id (or user id), `null` when signed out, `undefined` until the session settles. */
29
+ authVersion: ComputedRef<string | null | undefined>;
30
30
  }
31
31
  /**
32
32
  * The Better Auth service for the Vue/Nuxt runtime.
@@ -49,3 +49,9 @@ export interface UseBetterAuthReturn {
49
49
  * @public
50
50
  */
51
51
  export declare function useBetterAuth(initialToken?: string | null): UseBetterAuthReturn;
52
+ /**
53
+ * Forget what the Better Auth integration keeps for the app's lifetime: the
54
+ * browser's token cache and its session subscription. Published from
55
+ * `nuxt-convex-module/testing`; call it between tests that sign in.
56
+ */
57
+ export declare function resetBetterAuthState(): void;