nuxt-convex-module 0.12.2 → 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.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -2
- package/dist/runtime/better-auth/nuxt/server.js +7 -3
- 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/plugin.server.js +6 -1
- package/dist/runtime/better-auth/vue/token-store.d.ts +1 -0
- package/dist/runtime/better-auth/vue/token-store.js +38 -0
- package/dist/runtime/better-auth/vue/use-better-auth.d.ts +4 -4
- package/dist/runtime/better-auth/vue/use-better-auth.js +89 -46
- 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/config.d.ts +7 -1
- package/dist/runtime/nuxt/config.js +9 -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/dist/runtime/vue/client.js +13 -0
- package/dist/runtime/vue/plugin.d.ts +2 -1
- package/dist/runtime/vue/plugin.js +2 -0
- 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.d.mts
CHANGED
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"));
|
|
@@ -402,6 +402,7 @@ const module$1 = defineNuxtModule({
|
|
|
402
402
|
for (const message of diagnostics.errors) logger.error(message);
|
|
403
403
|
for (const message of diagnostics.warnings) logger.warn(message);
|
|
404
404
|
nuxt.options.runtimeConfig.public.convex.loginPath = diagnostics.loginPath;
|
|
405
|
+
nuxt.options.runtimeConfig.public.convex.authRoute = diagnostics.authRoute;
|
|
405
406
|
registerAuthClientAlias(resolver, nuxt, options);
|
|
406
407
|
registerConvexAliases(nuxt);
|
|
407
408
|
registerConvexTypeFallback(nuxt);
|
|
@@ -485,6 +486,13 @@ function registerIntegrations(resolver, nuxt, options, autoImports, authRoute) {
|
|
|
485
486
|
}
|
|
486
487
|
return flags;
|
|
487
488
|
}
|
|
489
|
+
const DEV_SECURITY_DEFAULTS = {
|
|
490
|
+
rateLimiter: false,
|
|
491
|
+
headers: {
|
|
492
|
+
contentSecurityPolicy: { "upgrade-insecure-requests": false },
|
|
493
|
+
strictTransportSecurity: false
|
|
494
|
+
}
|
|
495
|
+
};
|
|
488
496
|
function readSecurityOption(nuxt) {
|
|
489
497
|
const opts = nuxt.options;
|
|
490
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 = {
|
|
@@ -82,10 +84,12 @@ const getArgsAndOptions = (args, token) => {
|
|
|
82
84
|
return [args[0], { token }];
|
|
83
85
|
};
|
|
84
86
|
export function convexAuth(event, opts) {
|
|
87
|
+
const config = getConvexRuntimeConfig(event);
|
|
85
88
|
const siteUrl = parseConvexSiteUrl(
|
|
86
|
-
opts?.convexSiteUrl
|
|
89
|
+
opts?.convexSiteUrl || config.siteUrl || process.env.NUXT_PUBLIC_CONVEX_SITE_URL
|
|
87
90
|
);
|
|
88
|
-
const
|
|
91
|
+
const basePath = opts?.basePath ?? config.authRoute;
|
|
92
|
+
const cacheKey = `__nuxtConvexToken:${siteUrl}|${basePath ?? ""}`;
|
|
89
93
|
const tokenCache = event.context;
|
|
90
94
|
const cachedGetToken = ({ forceRefresh } = {}) => {
|
|
91
95
|
const cached = tokenCache[cacheKey];
|
|
@@ -94,7 +98,7 @@ export function convexAuth(event, opts) {
|
|
|
94
98
|
mutableHeaders.delete("content-length");
|
|
95
99
|
mutableHeaders.delete("transfer-encoding");
|
|
96
100
|
mutableHeaders.set("accept-encoding", "identity");
|
|
97
|
-
const promise = getToken(siteUrl, mutableHeaders, { ...opts, forceRefresh });
|
|
101
|
+
const promise = getToken(siteUrl, mutableHeaders, { ...opts, basePath, forceRefresh });
|
|
98
102
|
tokenCache[cacheKey] = promise;
|
|
99
103
|
return promise;
|
|
100
104
|
};
|
|
@@ -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';
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { computed } from "vue";
|
|
2
2
|
import { defineNuxtPlugin, useRuntimeConfig, useState, useRequestEvent, useResponseHeader } from "#app";
|
|
3
3
|
import { ConvexVueClient, ConvexClientKey } from "../../vue/client.js";
|
|
4
|
-
import { CONVEX_INITIAL_TOKEN_KEY } from "../../nuxt/config.js";
|
|
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
8
|
export async function prefetchAuthToken(event, initialToken, cacheControl) {
|
|
8
9
|
try {
|
|
9
10
|
const token = await convexAuth(event).getToken();
|
|
@@ -19,10 +20,13 @@ export default defineNuxtPlugin({
|
|
|
19
20
|
name: "nuxt-convex-module:better-auth:server",
|
|
20
21
|
async setup(nuxtApp) {
|
|
21
22
|
const initialToken = useState(CONVEX_INITIAL_TOKEN_KEY, () => null);
|
|
23
|
+
const tokenStore = createBetterAuthTokenStore();
|
|
24
|
+
nuxtApp.vueApp.provide(BetterAuthTokenStoreKey, tokenStore);
|
|
22
25
|
const url = useRuntimeConfig().public.convex.url;
|
|
23
26
|
const ssrClient = url ? new ConvexVueClient(url) : void 0;
|
|
24
27
|
if (ssrClient) {
|
|
25
28
|
nuxtApp.vueApp.provide(ConvexClientKey, ssrClient);
|
|
29
|
+
closeAfterRender(nuxtApp, ssrClient);
|
|
26
30
|
}
|
|
27
31
|
const ssrAuthState = {
|
|
28
32
|
isLoading: computed(() => true),
|
|
@@ -33,6 +37,7 @@ export default defineNuxtPlugin({
|
|
|
33
37
|
const event = useRequestEvent();
|
|
34
38
|
if (event && !import.meta.prerender) {
|
|
35
39
|
await prefetchAuthToken(event, initialToken, useResponseHeader("Cache-Control"));
|
|
40
|
+
seedInitialToken(tokenStore, initialToken.value);
|
|
36
41
|
}
|
|
37
42
|
}
|
|
38
43
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
export function createBetterAuthTokenStore() {
|
|
3
|
+
return {
|
|
4
|
+
cachedToken: ref(null),
|
|
5
|
+
cachedTokenVersion: null,
|
|
6
|
+
pendingToken: null,
|
|
7
|
+
pendingTokenVersion: null,
|
|
8
|
+
initialTokenUsed: false,
|
|
9
|
+
initialTokenSessionId: void 0
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export const BetterAuthTokenStoreKey = Symbol("BetterAuthTokenStore");
|
|
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
|
+
};
|
|
26
|
+
export function seedInitialToken(store, initialToken) {
|
|
27
|
+
if (store.initialTokenUsed || !initialToken) return;
|
|
28
|
+
store.cachedToken.value = initialToken;
|
|
29
|
+
store.cachedTokenVersion = PENDING_AUTH_VERSION;
|
|
30
|
+
store.initialTokenSessionId = getTokenSessionId(initialToken);
|
|
31
|
+
store.initialTokenUsed = true;
|
|
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,67 +1,110 @@
|
|
|
1
|
-
import { computed,
|
|
1
|
+
import { computed, effectScope, hasInjectionContext, inject, shallowRef, triggerRef, watch } from "vue";
|
|
2
2
|
import { authClient } from "#convex/auth-client";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { BetterAuthTokenStoreKey, clearToken, createBetterAuthTokenStore, PENDING_AUTH_VERSION, readTokenClaims, seedInitialToken } from "./token-store.js";
|
|
4
|
+
const browserStore = createBetterAuthTokenStore();
|
|
5
|
+
function useTokenStore() {
|
|
6
|
+
if (!import.meta.server) return browserStore;
|
|
7
|
+
return (hasInjectionContext() ? inject(BetterAuthTokenStoreKey, null) : null) ?? createBetterAuthTokenStore();
|
|
8
|
+
}
|
|
9
|
+
const serverSession = () => shallowRef({
|
|
10
|
+
data: null,
|
|
11
|
+
error: null,
|
|
12
|
+
isPending: true,
|
|
13
|
+
isRefetching: false,
|
|
14
|
+
refetch: async () => {
|
|
15
15
|
}
|
|
16
|
+
});
|
|
17
|
+
let browserSession;
|
|
18
|
+
let browserSessionScope;
|
|
19
|
+
const useClientSession = () => {
|
|
20
|
+
if (import.meta.server) return serverSession();
|
|
21
|
+
browserSessionScope ??= effectScope(true);
|
|
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
|
+
});
|
|
29
|
+
};
|
|
30
|
+
export function useBetterAuth(initialToken) {
|
|
31
|
+
const store = useTokenStore();
|
|
32
|
+
seedInitialToken(store, initialToken);
|
|
33
|
+
const { cachedToken } = store;
|
|
16
34
|
const session = useClientSession();
|
|
17
35
|
const client = authClient;
|
|
18
36
|
const authVersion = computed(() => {
|
|
19
37
|
const data = session.value.data;
|
|
20
|
-
|
|
38
|
+
const id = data?.session?.id ?? data?.user?.id ?? null;
|
|
39
|
+
return id === null && session.value.isPending ? void 0 : id;
|
|
21
40
|
});
|
|
22
|
-
const currentAuthVersion = () => authVersion.value
|
|
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;
|
|
48
|
+
});
|
|
49
|
+
const isServerTokenActive = () => session.value.isPending && cachedToken.value !== null && store.cachedTokenVersion === PENDING_AUTH_VERSION;
|
|
23
50
|
const fetchAccessToken = async ({
|
|
24
51
|
forceRefreshToken = false
|
|
25
52
|
} = {}) => {
|
|
26
53
|
const version = currentAuthVersion();
|
|
27
54
|
if (version === null) {
|
|
28
|
-
|
|
29
|
-
cachedTokenVersion = null;
|
|
30
|
-
pendingToken = null;
|
|
31
|
-
pendingTokenVersion = null;
|
|
55
|
+
clearToken(store);
|
|
32
56
|
return null;
|
|
33
57
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
58
|
+
if (store.cachedTokenVersion === PENDING_AUTH_VERSION && store.initialTokenSessionId === version) {
|
|
59
|
+
store.cachedTokenVersion = version;
|
|
36
60
|
}
|
|
37
|
-
if (!forceRefreshToken
|
|
38
|
-
|
|
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
|
+
}
|
|
39
68
|
}
|
|
40
|
-
|
|
41
|
-
|
|
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
|
+
};
|
|
79
|
+
store.pendingTokenVersion = version;
|
|
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
|
+
}
|
|
42
85
|
const token = data?.token || null;
|
|
43
|
-
|
|
44
|
-
|
|
86
|
+
if (isLatest()) {
|
|
87
|
+
store.cachedTokenVersion = version;
|
|
88
|
+
cachedToken.value = token;
|
|
89
|
+
triggerRef(cachedToken);
|
|
90
|
+
}
|
|
45
91
|
return token;
|
|
46
|
-
}).catch(() => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
pendingToken = null;
|
|
52
|
-
pendingTokenVersion = null;
|
|
92
|
+
}).catch(afterFailure).finally(() => {
|
|
93
|
+
if (isLatest()) {
|
|
94
|
+
store.pendingToken = null;
|
|
95
|
+
store.pendingTokenVersion = null;
|
|
96
|
+
}
|
|
53
97
|
});
|
|
54
|
-
return
|
|
98
|
+
return pending;
|
|
55
99
|
};
|
|
56
100
|
return {
|
|
57
|
-
isLoading: computed(() => session.value.isPending && !
|
|
101
|
+
isLoading: computed(() => session.value.isPending && !isServerTokenActive() || holdsAnotherSessionsToken.value),
|
|
58
102
|
// PARITY: D-03
|
|
59
|
-
// Diverges from upstream's `Boolean(session?.session) ||
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// 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.
|
|
63
106
|
isAuthenticated: computed(
|
|
64
|
-
() => !!session.value.data ||
|
|
107
|
+
() => !!session.value.data || isServerTokenActive()
|
|
65
108
|
),
|
|
66
109
|
fetchAccessToken,
|
|
67
110
|
// PARITY: A-06 — Vue-only service extensions.
|
|
@@ -75,9 +118,9 @@ export function useBetterAuth(initialToken) {
|
|
|
75
118
|
};
|
|
76
119
|
}
|
|
77
120
|
export function __resetUseBetterAuthForTests() {
|
|
78
|
-
cachedToken.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
121
|
+
Object.assign(browserStore, createBetterAuthTokenStore(), { cachedToken: browserStore.cachedToken });
|
|
122
|
+
browserStore.cachedToken.value = null;
|
|
123
|
+
browserSessionScope?.stop();
|
|
124
|
+
browserSessionScope = void 0;
|
|
125
|
+
browserSession = void 0;
|
|
83
126
|
}
|
|
@@ -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";
|
|
@@ -8,11 +8,17 @@ export declare const CONVEX_INITIAL_TOKEN_KEY = "convex:initialToken";
|
|
|
8
8
|
/**
|
|
9
9
|
* The module's runtime config, tolerating a missing Nuxt context (a plain
|
|
10
10
|
* server import outside a request) by returning an empty object.
|
|
11
|
+
*
|
|
12
|
+
* Given a server route's event, it reads that request's config: an
|
|
13
|
+
* environment variable set after the server started (by a Nitro plugin, say)
|
|
14
|
+
* reaches it, where the config read without one was fixed at startup.
|
|
11
15
|
*/
|
|
12
|
-
export declare function getConvexRuntimeConfig(): {
|
|
16
|
+
export declare function getConvexRuntimeConfig(event?: unknown): {
|
|
13
17
|
url: string | undefined;
|
|
14
18
|
siteUrl: string | undefined;
|
|
19
|
+
authRoute: string | undefined;
|
|
15
20
|
} | {
|
|
16
21
|
url?: undefined;
|
|
17
22
|
siteUrl?: undefined;
|
|
23
|
+
authRoute?: undefined;
|
|
18
24
|
};
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "#imports";
|
|
2
2
|
export const CONVEX_INITIAL_TOKEN_KEY = "convex:initialToken";
|
|
3
|
-
export function
|
|
3
|
+
export function closeAfterRender(nuxtApp, client) {
|
|
4
|
+
const close = () => void client.close();
|
|
5
|
+
nuxtApp.hook("app:rendered", close);
|
|
6
|
+
nuxtApp.hook("app:error", close);
|
|
7
|
+
}
|
|
8
|
+
export function getConvexRuntimeConfig(event) {
|
|
4
9
|
try {
|
|
5
|
-
const config = useRuntimeConfig();
|
|
10
|
+
const config = useRuntimeConfig(event);
|
|
6
11
|
return {
|
|
7
12
|
url: config.public?.convex?.url,
|
|
8
|
-
siteUrl: config.public?.convex?.siteUrl
|
|
13
|
+
siteUrl: config.public?.convex?.siteUrl,
|
|
14
|
+
authRoute: config.public?.convex?.authRoute
|
|
9
15
|
};
|
|
10
16
|
} catch {
|
|
11
17
|
return {};
|
|
@@ -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
|
};
|