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.
- package/dist/devtools-client/_nuxt/C-J9qZjr.js +3 -0
- package/dist/devtools-client/_nuxt/CB1p6iYX.js +1 -0
- package/dist/devtools-client/_nuxt/{COXexnyN.js → CtC_CmOY.js} +2 -2
- package/dist/devtools-client/_nuxt/{entry.DsZySRVW.css → entry.jOWrhpYd.css} +1 -1
- package/dist/devtools-client/index.html +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +9 -2
- package/dist/runtime/better-auth/nuxt/server.js +2 -0
- package/dist/runtime/better-auth/vue/client.d.ts +3 -3
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +26 -0
- package/dist/runtime/better-auth/vue/index.d.ts +1 -1
- package/dist/runtime/better-auth/vue/token-store.js +21 -1
- package/dist/runtime/better-auth/vue/use-better-auth.d.ts +4 -4
- package/dist/runtime/better-auth/vue/use-better-auth.js +60 -29
- package/dist/runtime/nuxt/composables/use-async-paginated-query.d.ts +1 -1
- package/dist/runtime/nuxt/composables/use-async-paginated-query.js +5 -3
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +4 -1
- package/dist/runtime/nuxt/composables/use-async-query.js +6 -3
- package/dist/runtime/nuxt/csp.d.ts +2 -1
- package/dist/runtime/nuxt/csp.js +9 -3
- package/dist/runtime/vue/auth/index.d.ts +2 -1
- package/dist/runtime/vue/auth/index.js +62 -13
- package/package.json +12 -12
- package/dist/devtools-client/_nuxt/AqohFEu1.js +0 -1
- package/dist/devtools-client/_nuxt/BPtiIkWa.js +0 -3
|
@@ -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/
|
|
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
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.
|
|
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 {
|
|
3
|
-
export declare const authClient:
|
|
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
|
|
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)
|
|
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(() =>
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
41
|
-
store.cachedTokenVersion = null;
|
|
42
|
-
store.pendingToken = null;
|
|
43
|
-
store.pendingTokenVersion = null;
|
|
55
|
+
clearToken(store);
|
|
44
56
|
return null;
|
|
45
57
|
}
|
|
46
|
-
if (
|
|
47
|
-
|
|
58
|
+
if (store.cachedTokenVersion === PENDING_AUTH_VERSION && store.initialTokenSessionId === version) {
|
|
59
|
+
store.cachedTokenVersion = version;
|
|
48
60
|
}
|
|
49
|
-
if (!forceRefreshToken
|
|
50
|
-
|
|
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
|
|
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
|
-
|
|
56
|
-
|
|
86
|
+
if (isLatest()) {
|
|
87
|
+
store.cachedTokenVersion = version;
|
|
88
|
+
cachedToken.value = token;
|
|
89
|
+
triggerRef(cachedToken);
|
|
90
|
+
}
|
|
57
91
|
return token;
|
|
58
|
-
}).catch(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 && !
|
|
101
|
+
isLoading: computed(() => session.value.isPending && !isServerTokenActive() || holdsAnotherSessionsToken.value),
|
|
70
102
|
// PARITY: D-03
|
|
71
|
-
// Diverges from upstream's `Boolean(session?.session) ||
|
|
72
|
-
//
|
|
73
|
-
//
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
(
|
|
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.
|
|
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
|
*/
|
package/dist/runtime/nuxt/csp.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
const authContext = ref(0);
|
|
68
|
+
watch(
|
|
69
|
+
() => toValue(authVersion),
|
|
70
|
+
(_version, previous) => {
|
|
71
|
+
if (previous !== void 0) authContext.value++;
|
|
49
72
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
163
|
-
"better-auth": "~1.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
213
|
+
"better-auth": "~1.7.18",
|
|
214
214
|
"changelogen": "^0.6.2",
|
|
215
|
-
"convex": "^1.
|
|
215
|
+
"convex": "^1.49.0",
|
|
216
216
|
"eslint": "^10.13.0",
|
|
217
|
-
"fallow": "3.
|
|
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.
|
|
222
|
+
"pkg-pr-new": "^0.0.93",
|
|
223
223
|
"playwright-core": "^1.64.1",
|
|
224
|
-
"portless": "0.
|
|
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};
|