nuxt-convex-module 0.12.3 → 0.13.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.
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/COXexnyN.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.DsZySRVW.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/COXexnyN.js"><script type="module" src="/__nuxt-convex-module/_nuxt/COXexnyN.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/BPtiIkWa.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"bfa6df52-caf6-46de-a611-bdf9d975bd3f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1793468157767,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-convex-module/_nuxt/CtC_CmOY.js"}}</script><link rel="stylesheet" href="/__nuxt-convex-module/_nuxt/entry.jOWrhpYd.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/CtC_CmOY.js"><script type="module" src="/__nuxt-convex-module/_nuxt/CtC_CmOY.js" crossorigin></script><link rel="prefetch" as="script" crossorigin href="/__nuxt-convex-module/_nuxt/C-J9qZjr.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-convex-module",buildId:"ad777e0e-9095-4e0e-9b9b-8010fe4e34c6",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794085598149,false]</script></body></html>
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "compatibility": {
6
6
  "nuxt": ">=4.6.0"
7
7
  },
8
- "version": "0.12.3",
8
+ "version": "0.13.0",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -95,7 +95,7 @@ function integrationWarnings(flags, isInstalled) {
95
95
  }
96
96
  if (flags.polar && !isInstalled("@polar-sh/checkout")) {
97
97
  warnings.push(
98
- "`@convex-dev/polar` is installed but its peer `@polar-sh/checkout` is not \u2014 `<CheckoutLink>` imports it and the build fails the moment the component is used. Run `npm install @polar-sh/checkout@~0.4.0`."
98
+ "`@convex-dev/polar` is installed but its peer `@polar-sh/checkout` is not \u2014 `<CheckoutLink>` imports it and the build fails the moment the component is used. Run `npm install @polar-sh/checkout@~0.6.0`."
99
99
  );
100
100
  }
101
101
  return warnings;
@@ -383,7 +383,7 @@ const module$1 = defineNuxtModule({
383
383
  // with no extra entry on the consumer's side. The Convex-aware CSP is applied
384
384
  // at runtime (see `registerSecurity`), so module order is irrelevant. Nothing
385
385
  // is declared otherwise: kit fails the build resolving an absent package.
386
- moduleDependencies: (nuxt) => resolveSecurityState(nuxt, readSecurityOption(nuxt)).enabled ? { "nuxt-security": {} } : {},
386
+ moduleDependencies: (nuxt) => resolveSecurityState(nuxt, readSecurityOption(nuxt)).enabled ? { "nuxt-security": nuxt.options.dev ? { defaults: DEV_SECURITY_DEFAULTS } : {} } : {},
387
387
  async setup(options, nuxt) {
388
388
  const resolver = createResolver(import.meta.url);
389
389
  nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
@@ -486,6 +486,13 @@ function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
486
486
  }
487
487
  return flags;
488
488
  }
489
+ const DEV_SECURITY_DEFAULTS = {
490
+ rateLimiter: false,
491
+ headers: {
492
+ contentSecurityPolicy: { "upgrade-insecure-requests": false },
493
+ strictTransportSecurity: false
494
+ }
495
+ };
489
496
  function readSecurityOption(nuxt) {
490
497
  const opts = nuxt.options;
491
498
  return opts.convex?.security;
@@ -44,6 +44,8 @@ const handler = async (request, siteUrl) => {
44
44
  headers.delete("connection");
45
45
  headers.set("accept-encoding", "application/json");
46
46
  headers.set("host", new URL(siteUrl).host);
47
+ headers.delete("x-forwarded-host");
48
+ headers.delete("x-forwarded-proto");
47
49
  headers.set("x-better-auth-forwarded-host", requestUrl.host);
48
50
  headers.set("x-better-auth-forwarded-proto", requestUrl.protocol.replace(/:$/, ""));
49
51
  const init = {
@@ -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 { 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 = typeof 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
  *
@@ -7,7 +7,7 @@
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
9
  export { usePreloadedAuthQuery } from './hydration.js';
10
- export { consumeCrossDomainOneTimeToken, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
10
+ export { consumeCrossDomainOneTimeToken, type AuthClientWithCrossDomain, type ConsumeCrossDomainOneTimeTokenOptions } from './cross-domain.js';
11
11
  export { resolveAuthRedirect } from './redirect.js';
12
12
  export { AuthBoundary } from './auth-boundary.js';
13
13
  export { convexClient, crossDomainClient } from '@convex-dev/better-auth/client/plugins';
@@ -5,14 +5,34 @@ 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");
12
13
  export const PENDING_AUTH_VERSION = "__pending__";
14
+ export function readTokenClaims(token) {
15
+ try {
16
+ const payload = token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
17
+ return JSON.parse(atob(payload)) ?? void 0;
18
+ } catch {
19
+ return void 0;
20
+ }
21
+ }
22
+ const getTokenSessionId = (token) => {
23
+ const sessionId = readTokenClaims(token)?.sessionId;
24
+ return typeof sessionId === "string" ? sessionId : void 0;
25
+ };
13
26
  export function seedInitialToken(store, initialToken) {
14
27
  if (store.initialTokenUsed || !initialToken) return;
15
28
  store.cachedToken.value = initialToken;
16
29
  store.cachedTokenVersion = PENDING_AUTH_VERSION;
30
+ store.initialTokenSessionId = getTokenSessionId(initialToken);
17
31
  store.initialTokenUsed = true;
18
32
  }
33
+ export function clearToken(store) {
34
+ store.cachedToken.value = null;
35
+ store.cachedTokenVersion = null;
36
+ store.pendingToken = null;
37
+ store.pendingTokenVersion = null;
38
+ }
@@ -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.
@@ -1,6 +1,6 @@
1
- import { computed, effectScope, hasInjectionContext, inject, shallowRef } from "vue";
1
+ import { computed, effectScope, hasInjectionContext, inject, shallowRef, triggerRef, watch } from "vue";
2
2
  import { authClient } from "#convex/auth-client";
3
- import { BetterAuthTokenStoreKey, createBetterAuthTokenStore, PENDING_AUTH_VERSION, seedInitialToken } from "./token-store.js";
3
+ import { BetterAuthTokenStoreKey, clearToken, createBetterAuthTokenStore, PENDING_AUTH_VERSION, readTokenClaims, seedInitialToken } from "./token-store.js";
4
4
  const browserStore = createBetterAuthTokenStore();
5
5
  function useTokenStore() {
6
6
  if (!import.meta.server) return browserStore;
@@ -19,7 +19,13 @@ let browserSessionScope;
19
19
  const useClientSession = () => {
20
20
  if (import.meta.server) return serverSession();
21
21
  browserSessionScope ??= effectScope(true);
22
- return browserSession ??= browserSessionScope.run(() => authClient.useSession());
22
+ return browserSession ??= browserSessionScope.run(() => {
23
+ const session = authClient.useSession();
24
+ watch(session, ({ data, isPending }) => {
25
+ if (!data && !isPending) clearToken(browserStore);
26
+ }, { flush: "sync" });
27
+ return session;
28
+ });
23
29
  };
24
30
  export function useBetterAuth(initialToken) {
25
31
  const store = useTokenStore();
@@ -29,51 +35,76 @@ export function useBetterAuth(initialToken) {
29
35
  const client = authClient;
30
36
  const authVersion = computed(() => {
31
37
  const data = session.value.data;
32
- return data?.session?.id ?? data?.user?.id ?? null;
38
+ const id = data?.session?.id ?? data?.user?.id ?? null;
39
+ return id === null && session.value.isPending ? void 0 : id;
40
+ });
41
+ const currentAuthVersion = () => authVersion.value === void 0 ? PENDING_AUTH_VERSION : authVersion.value;
42
+ const holdsAnotherSessionsToken = computed(() => {
43
+ const data = session.value.data;
44
+ if (!data || !cachedToken.value || store.cachedTokenVersion !== PENDING_AUTH_VERSION) return false;
45
+ if (store.initialTokenSessionId && data.session?.id) return store.initialTokenSessionId !== data.session.id;
46
+ const subject = readTokenClaims(cachedToken.value)?.sub;
47
+ return !!data.user?.id && subject !== void 0 && subject !== data.user.id;
33
48
  });
34
- const currentAuthVersion = () => authVersion.value ?? (session.value.isPending ? PENDING_AUTH_VERSION : null);
49
+ const isServerTokenActive = () => session.value.isPending && cachedToken.value !== null && store.cachedTokenVersion === PENDING_AUTH_VERSION;
35
50
  const fetchAccessToken = async ({
36
51
  forceRefreshToken = false
37
52
  } = {}) => {
38
53
  const version = currentAuthVersion();
39
54
  if (version === null) {
40
- cachedToken.value = null;
41
- store.cachedTokenVersion = null;
42
- store.pendingToken = null;
43
- store.pendingTokenVersion = null;
55
+ clearToken(store);
44
56
  return null;
45
57
  }
46
- if (cachedToken.value && !forceRefreshToken && store.cachedTokenVersion === version) {
47
- return cachedToken.value;
58
+ if (store.cachedTokenVersion === PENDING_AUTH_VERSION && store.initialTokenSessionId === version) {
59
+ store.cachedTokenVersion = version;
48
60
  }
49
- if (!forceRefreshToken && store.pendingToken && store.pendingTokenVersion === version) {
50
- return store.pendingToken;
61
+ if (!forceRefreshToken) {
62
+ if (store.pendingToken && store.pendingTokenVersion === version) {
63
+ return store.pendingToken;
64
+ }
65
+ if (cachedToken.value && store.cachedTokenVersion === version) {
66
+ return cachedToken.value;
67
+ }
51
68
  }
69
+ const afterFailure = () => {
70
+ if (cachedToken.value && !holdsAnotherSessionsToken.value && (store.cachedTokenVersion === version || store.cachedTokenVersion === PENDING_AUTH_VERSION)) {
71
+ return cachedToken.value;
72
+ }
73
+ if (isLatest()) {
74
+ cachedToken.value = null;
75
+ store.cachedTokenVersion = version;
76
+ }
77
+ return null;
78
+ };
52
79
  store.pendingTokenVersion = version;
53
- const pending = store.pendingToken = client.convex.token({ fetchOptions: { throw: false } }).then(({ data }) => {
80
+ const isLatest = () => store.pendingToken === pending;
81
+ const pending = store.pendingToken = client.convex.token({ fetchOptions: { throw: false } }).then(({ data, error }) => {
82
+ if (error && error.status !== 401) {
83
+ return afterFailure();
84
+ }
54
85
  const token = data?.token || null;
55
- cachedToken.value = token;
56
- store.cachedTokenVersion = version;
86
+ if (isLatest()) {
87
+ store.cachedTokenVersion = version;
88
+ cachedToken.value = token;
89
+ triggerRef(cachedToken);
90
+ }
57
91
  return token;
58
- }).catch(() => {
59
- cachedToken.value = null;
60
- store.cachedTokenVersion = version;
61
- return null;
62
- }).finally(() => {
63
- store.pendingToken = null;
64
- store.pendingTokenVersion = null;
92
+ }).catch(afterFailure).finally(() => {
93
+ if (isLatest()) {
94
+ store.pendingToken = null;
95
+ store.pendingTokenVersion = null;
96
+ }
65
97
  });
66
98
  return pending;
67
99
  };
68
100
  return {
69
- isLoading: computed(() => session.value.isPending && !cachedToken.value),
101
+ isLoading: computed(() => session.value.isPending && !isServerTokenActive() || holdsAnotherSessionsToken.value),
70
102
  // PARITY: D-03
71
- // Diverges from upstream's `Boolean(session?.session) || cachedToken !== null`:
72
- // a settled signed-out session must read unauthenticated immediately (Vue has
73
- // no re-render to run upstream's cache-clearing effect first), and Better Auth
74
- // session data without a nested `session` still counts as signed in.
103
+ // Diverges from upstream's `Boolean(session?.session) || isSsrTokenActive`:
104
+ // Better Auth session data without a nested `session` still counts as
105
+ // signed in.
75
106
  isAuthenticated: computed(
76
- () => !!session.value.data || session.value.isPending && cachedToken.value !== null
107
+ () => !!session.value.data || isServerTokenActive()
77
108
  ),
78
109
  fetchAccessToken,
79
110
  // PARITY: A-06 — Vue-only service extensions.
@@ -32,7 +32,7 @@ export interface AsyncPaginatedQueryData<Item> {
32
32
  status: ComputedRef<PaginationStatus>;
33
33
  /** `true` while no page — server or live — is available yet, or more rows are loading. */
34
34
  isLoading: ComputedRef<boolean>;
35
- /** The SSR fetch error or the live subscription's, `null` when none. Never thrown. */
35
+ /** The live subscription's error, or the SSR fetch error until the live query has a page; `null` when none. Never thrown. */
36
36
  error: ComputedRef<Error | null>;
37
37
  /**
38
38
  * Fetch `numItems` more rows. Takes effect once the live subscription has
@@ -70,9 +70,11 @@ export function useAsyncPaginatedQuery(query, args, options) {
70
70
  return "LoadingFirstPage";
71
71
  });
72
72
  const isLoading = computed(() => status.value === "LoadingFirstPage" || status.value === "LoadingMore");
73
- const error = computed(
74
- () => liveState.value?.error ?? clientError.value ?? asyncData.error.value ?? null
75
- );
73
+ const error = computed(() => {
74
+ const liveError = liveState.value?.error ?? clientError.value;
75
+ if (liveError) return liveError;
76
+ return liveHasPage.value ? null : asyncData.error.value ?? null;
77
+ });
76
78
  const loadMore = (numItems) => {
77
79
  live?.loadMore(numItems);
78
80
  };
@@ -66,7 +66,10 @@ export interface AsyncQueryReturn<T> extends PromiseLike<AsyncQueryData<T>> {
66
66
  * nothing has loaded (or while skipped).
67
67
  */
68
68
  data: ComputedRef<T | undefined>;
69
- /** The initial-fetch or live-subscription error, `null` when none. */
69
+ /**
70
+ * The initial-fetch or live-subscription error, `null` when none. A failed
71
+ * initial fetch stops counting once the live subscription has a value.
72
+ */
70
73
  error: ComputedRef<Error | null>;
71
74
  /**
72
75
  * Initial-fetch status. Live pushes don't churn it: once data exists it
@@ -75,9 +75,12 @@ export function useAsyncQuery(query, args, options = {}) {
75
75
  const data = computed(
76
76
  () => liveValue.value !== void 0 ? liveValue.value : payloadValue.value
77
77
  );
78
- const error = computed(
79
- () => liveError.value ?? asyncData.error.value ?? null
80
- );
78
+ const error = computed(() => {
79
+ if (liveError.value) {
80
+ return liveError.value;
81
+ }
82
+ return liveValue.value !== void 0 ? null : asyncData.error.value ?? null;
83
+ });
81
84
  const status = computed(() => {
82
85
  if (liveError.value) {
83
86
  return "error";
@@ -37,7 +37,8 @@ export declare function convexResourceSrc(url?: string, siteUrl?: string): strin
37
37
  *
38
38
  * For each directive we keep any value the user already set and *append* the
39
39
  * Convex origins, so consumers extend (add their own third parties) rather than
40
- * fight the defaults, and the Convex origins are always present. A user who sets
40
+ * fight the defaults, and the Convex origins are always present. That covers a
41
+ * directive the user left to `default-src`, whose places carry over. A user who sets
41
42
  * `contentSecurityPolicy: false` (CSP disabled) is left untouched, as is a rule
42
43
  * set with nothing to add (no Convex URL).
43
44
  */
@@ -39,8 +39,14 @@ export function applyConvexSecurityDefaults(rules, connectSrc, resourceSrc) {
39
39
  tightenDirective(csp, "media-src", ["'self'"], resourceSrc);
40
40
  }
41
41
  function tightenDirective(csp, name, baseline, additions) {
42
- if (additions.length === 0) return;
43
- const current = csp[name];
44
- const existing = Array.isArray(current) ? current : typeof current === "string" ? current.split(/\s+/).filter(Boolean) : baseline;
42
+ if (additions.length === 0 || csp[name] === false) return;
43
+ const existing = sourcesOf(csp[name]) ?? [...baseline, ...inheritedSources(csp)];
45
44
  csp[name] = uniq([...existing, ...additions]);
46
45
  }
46
+ function sourcesOf(value) {
47
+ if (Array.isArray(value)) return value;
48
+ return typeof value === "string" ? value.split(/\s+/).filter(Boolean) : void 0;
49
+ }
50
+ function inheritedSources(csp) {
51
+ return (sourcesOf(csp["default-src"]) ?? []).filter((source) => source === "'self'" || !source.startsWith("'"));
52
+ }
@@ -66,7 +66,8 @@ export interface ConvexAuthProviderOptions {
66
66
  * Alternative re-authentication trigger for providers that hand out a
67
67
  * stable fetcher function: when this value changes, auth state transitions
68
68
  * back to loading, `clearAuth` runs, and `fetchAccessToken` is called
69
- * again against the new auth context.
69
+ * again against the new auth context. `undefined` means the provider does
70
+ * not know the context yet, so the first value it learns is no change.
70
71
  */
71
72
  authVersion?: MaybeRefOrGetter<unknown>;
72
73
  };
@@ -1,4 +1,4 @@
1
- import { computed, effectScope, inject, provide, ref, toValue, unref, watch, watchEffect } from "vue";
1
+ import { computed, effectScope, inject, nextTick, provide, ref, toValue, unref, watch } from "vue";
2
2
  export const ConvexAuthStateKey = Symbol("ConvexAuthState");
3
3
  export function useConvexAuth() {
4
4
  const authContext = inject(ConvexAuthStateKey);
@@ -9,6 +9,27 @@ export function useConvexAuth() {
9
9
  }
10
10
  return authContext;
11
11
  }
12
+ const pendingClears = /* @__PURE__ */ new WeakMap();
13
+ function clearAuthLast(client, ended) {
14
+ const pending = pendingClears.get(client);
15
+ if (pending) {
16
+ if (pending.start === ended) {
17
+ pending.start = null;
18
+ }
19
+ return;
20
+ }
21
+ const clear = { start: null };
22
+ pendingClears.set(client, clear);
23
+ void nextTick(() => {
24
+ pendingClears.delete(client);
25
+ try {
26
+ client.clearAuth();
27
+ } catch {
28
+ return;
29
+ }
30
+ clear.start?.();
31
+ });
32
+ }
12
33
  export function provideConvexAuth(options) {
13
34
  const authState = createConvexAuthState(options);
14
35
  provide(ConvexAuthStateKey, authState);
@@ -43,16 +64,37 @@ export function createConvexAuthState(options, scope) {
43
64
  },
44
65
  { immediate: true }
45
66
  );
46
- watchEffect((onCleanup) => {
47
- if (import.meta.server) {
48
- return;
67
+ const authContext = ref(0);
68
+ watch(
69
+ () => toValue(authVersion),
70
+ (_version, previous) => {
71
+ if (previous !== void 0) authContext.value++;
49
72
  }
50
- let isThisEffectRelevant = true;
51
- void toValue(authProviderLoading);
52
- void toValue(authVersion);
53
- const fetchToken = unref(fetchAccessToken);
54
- if (toValue(authProviderAuthenticated)) {
55
- client.setAuth(
73
+ );
74
+ watch(
75
+ [
76
+ // The loading flag is here only so auth re-registers when it flips,
77
+ // mirroring upstream's effect deps. It must not gate `setAuth`: a
78
+ // provider may report a cached session (authenticated) while still
79
+ // revalidating (loading), and upstream authenticates the client
80
+ // immediately in that window.
81
+ () => toValue(authProviderLoading),
82
+ authContext,
83
+ // Unwrapping here tracks the ref, so a new fetcher identity
84
+ // re-registers auth: upstream's effect re-running when
85
+ // `fetchAccessToken` changes between renders.
86
+ () => unref(fetchAccessToken),
87
+ () => toValue(authProviderAuthenticated)
88
+ ],
89
+ ([, , fetchToken, authenticated], _previous, onCleanup) => {
90
+ if (import.meta.server) {
91
+ return;
92
+ }
93
+ if (!authenticated) {
94
+ return;
95
+ }
96
+ let isThisEffectRelevant = true;
97
+ const start = () => client.setAuth(
56
98
  fetchToken,
57
99
  (convexReportsIsAuthenticated) => {
58
100
  if (isThisEffectRelevant) {
@@ -67,14 +109,21 @@ export function createConvexAuthState(options, scope) {
67
109
  }
68
110
  }
69
111
  );
112
+ const pending = pendingClears.get(client);
113
+ if (pending) {
114
+ pending.start = start;
115
+ } else {
116
+ start();
117
+ }
70
118
  onCleanup(() => {
71
119
  isThisEffectRelevant = false;
72
- client.clearAuth();
73
120
  isConvexAuthenticated.value = null;
74
121
  isRefreshing.value = false;
122
+ clearAuthLast(client, start);
75
123
  });
76
- }
77
- });
124
+ },
125
+ { immediate: true }
126
+ );
78
127
  };
79
128
  if (scope) {
80
129
  scope.run(register);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "nuxt-convex-module",
4
- "version": "0.12.3",
4
+ "version": "0.13.0",
5
5
  "description": "Convex backend integration with live queries, mutations, actions, pagination, file storage and SSR, auto-imported and typed against your deployment. The same Vue client runs without Nuxt; Better Auth, Clerk, Auth0 and Polar are opt-in.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -157,10 +157,10 @@
157
157
  "peerDependencies": {
158
158
  "@auth0/auth0-vue": "^2.0.0",
159
159
  "@clerk/vue": "^2.0.0",
160
- "@convex-dev/better-auth": ">=0.12.0 <0.13.0",
160
+ "@convex-dev/better-auth": ">=0.13.0 <0.14.0",
161
161
  "@convex-dev/polar": ">=0.9.0 <0.10.0",
162
- "@polar-sh/checkout": "^0.4.0",
163
- "better-auth": "~1.6.11",
162
+ "@polar-sh/checkout": "^0.6.0",
163
+ "better-auth": "~1.7.16",
164
164
  "convex": "^1.46.0",
165
165
  "nuxt-security": "^2.6.0",
166
166
  "vue": "^3.5.0"
@@ -191,10 +191,10 @@
191
191
  "devDependencies": {
192
192
  "@arethetypeswrong/cli": "^0.18.5",
193
193
  "@auth0/auth0-vue": "^2.11.0",
194
- "@clerk/vue": "2.5.16",
194
+ "@clerk/vue": "2.5.19",
195
195
  "@commitlint/cli": "^21.2.4",
196
196
  "@commitlint/config-conventional": "^21.2.4",
197
- "@convex-dev/better-auth": "^0.12.6",
197
+ "@convex-dev/better-auth": "^0.13.0",
198
198
  "@convex-dev/polar": "^0.9.2",
199
199
  "@nuxt/devtools": "^4.0.0",
200
200
  "@nuxt/devtools-kit": "^4.0.0",
@@ -203,25 +203,25 @@
203
203
  "@nuxt/module-builder": "^1.0.3",
204
204
  "@nuxt/schema": "^4.6.3",
205
205
  "@nuxt/test-utils": "^4.3.4",
206
- "@polar-sh/checkout": "^0.4.3",
206
+ "@polar-sh/checkout": "^0.6.0",
207
207
  "@types/node": "^26.6.5",
208
208
  "@types/ws": "^8.18.2",
209
209
  "@unhead/vue": "^3.4.2",
210
210
  "@vitejs/devtools-kit": "^0.8.0",
211
211
  "@vitest/coverage-v8": "^5.0.5",
212
212
  "@vue/test-utils": "^2.5.1",
213
- "better-auth": "~1.6.33",
213
+ "better-auth": "~1.7.18",
214
214
  "changelogen": "^0.6.2",
215
- "convex": "^1.48.0",
215
+ "convex": "^1.49.0",
216
216
  "eslint": "^10.13.0",
217
- "fallow": "3.37.0",
217
+ "fallow": "3.39.0",
218
218
  "happy-dom": "^21.0.4",
219
219
  "installed-check": "^11.0.0",
220
220
  "nuxt": "^4.6.3",
221
221
  "nuxt-security": "^2.6.0",
222
- "pkg-pr-new": "^0.0.92",
222
+ "pkg-pr-new": "^0.0.93",
223
223
  "playwright-core": "^1.64.1",
224
- "portless": "0.15.12",
224
+ "portless": "0.16.0",
225
225
  "publint": "^0.3.24",
226
226
  "taze": "^21.3.0",
227
227
  "typedoc": "^0.28.20",
@@ -1 +0,0 @@
1
- import{t as e}from"./BPtiIkWa.js";var t=Symbol.for(`devframe:browser-agent-registry`),{tools:n,listeners:r}=globalThis[t]??={tools:new Map,listeners:new Set};function i(){return[...n.values()]}function a(e){return r.add(e),()=>r.delete(e)}var o=`devframe:client-id`,s;function c(t=globalThis.window){try{let n=t?.sessionStorage;if(n){let t=n.getItem(o);return t||(t=e(),n.setItem(o,t)),t}}catch{}return s??=e(),s}function l(e){e.client.register({name:`devframe:agent:invoke-client-tool`,type:`action`,jsonSerializable:!0,handler:async(e,t)=>{let n=i().find(t=>t.id===e);if(!n)throw Error(`[devframe/agent] browser tool "${e}" not found`);return await n.invoke(t)}});let t=!1,n=!1,r=0,o=()=>{t||n||(t=!0,queueMicrotask(async()=>{if(t=!1,n)return;let a=i().map(({invoke:e,...t})=>t);(a.length!==0||r!==0)&&(r=a.length,await e.callOptional(`devframe:agent:sync-client-tools`,c(),a).catch(()=>{}))}))},s=a(o),l=e.events.on(`connection:status`,e=>{e===`connected`&&o()});return o(),()=>{n=!0,s(),l()}}export{l as setupBrowserAgentRpcBridge};