better-convex-nuxt 0.2.7 → 0.2.9
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/module.d.mts +25 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -1
- package/dist/runtime/composables/useAuthClient.d.ts +16 -1
- package/dist/runtime/composables/useConvexAction.js +1 -1
- package/dist/runtime/composables/useConvexAuth.d.ts +33 -2
- package/dist/runtime/composables/useConvexAuth.js +58 -3
- package/dist/runtime/composables/useConvexFileUpload.js +1 -1
- package/dist/runtime/composables/useConvexMutation.js +1 -1
- package/dist/runtime/composables/useConvexPaginatedQuery.js +4 -2
- package/dist/runtime/composables/useConvexQuery.js +4 -2
- package/dist/runtime/devtools/auth-proxy-registry.d.ts +8 -0
- package/dist/runtime/devtools/auth-proxy-registry.js +5 -0
- package/dist/runtime/devtools/mutation-registry.d.ts +3 -0
- package/dist/runtime/devtools/mutation-registry.js +5 -0
- package/dist/runtime/devtools/query-registry.d.ts +3 -0
- package/dist/runtime/devtools/query-registry.js +5 -0
- package/dist/runtime/devtools/ui/dist/200.html +1 -1
- package/dist/runtime/devtools/ui/dist/404.html +1 -1
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/latest.json +1 -1
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/df213a49-93a4-44f1-863e-5dc7de19c884.json +1 -0
- package/dist/runtime/devtools/ui/dist/index.html +1 -1
- package/dist/runtime/plugin.client.js +193 -38
- package/dist/runtime/plugin.server.js +38 -6
- package/dist/runtime/server/api/auth/[...].js +1 -1
- package/dist/runtime/types.d.ts +8 -4
- package/dist/runtime/utils/convex-cache.d.ts +15 -2
- package/dist/runtime/utils/convex-cache.js +1 -3
- package/dist/types.d.mts +1 -1
- package/package.json +1 -1
- package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/bb1b6fab-3dad-4ebb-bedd-8a7e8492b357.json +0 -1
package/dist/module.d.mts
CHANGED
|
@@ -44,6 +44,24 @@ interface QueryDefaults {
|
|
|
44
44
|
*/
|
|
45
45
|
public?: boolean;
|
|
46
46
|
}
|
|
47
|
+
interface ConvexDebugOptions {
|
|
48
|
+
/**
|
|
49
|
+
* Enable detailed auth flow logs on both client and server plugins.
|
|
50
|
+
* Requires `logging: 'debug'` to print verbose phase logs.
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
authFlow?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Enable detailed auth flow logs on the client plugin only.
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
58
|
+
clientAuthFlow?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Enable detailed auth flow logs on the server plugin only.
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
serverAuthFlow?: boolean;
|
|
64
|
+
}
|
|
47
65
|
interface ModuleOptions {
|
|
48
66
|
/** Convex deployment URL (WebSocket) - e.g., https://your-app.convex.cloud */
|
|
49
67
|
url?: string;
|
|
@@ -99,6 +117,12 @@ interface ModuleOptions {
|
|
|
99
117
|
* @default false
|
|
100
118
|
*/
|
|
101
119
|
logging?: LogLevel;
|
|
120
|
+
/**
|
|
121
|
+
* Optional debug channels for runtime plugins.
|
|
122
|
+
* Use this to enable high-verbosity trace logs without changing regular logger behavior.
|
|
123
|
+
* @default { authFlow: false, clientAuthFlow: false, serverAuthFlow: false }
|
|
124
|
+
*/
|
|
125
|
+
debug?: ConvexDebugOptions;
|
|
102
126
|
/**
|
|
103
127
|
* SSR auth token caching configuration (opt-in).
|
|
104
128
|
* Caches Convex JWT tokens server-side to reduce TTFB on subsequent requests.
|
|
@@ -148,4 +172,4 @@ interface ModuleOptions {
|
|
|
148
172
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
149
173
|
|
|
150
174
|
export { _default as default };
|
|
151
|
-
export type { AuthCacheOptions, ModuleOptions, QueryDefaults };
|
|
175
|
+
export type { AuthCacheOptions, ConvexDebugOptions, ModuleOptions, QueryDefaults };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -35,6 +35,11 @@ const module$1 = defineNuxtModule({
|
|
|
35
35
|
skipAuthRoutes: [],
|
|
36
36
|
permissions: false,
|
|
37
37
|
logging: false,
|
|
38
|
+
debug: {
|
|
39
|
+
authFlow: false,
|
|
40
|
+
clientAuthFlow: false,
|
|
41
|
+
serverAuthFlow: false
|
|
42
|
+
},
|
|
38
43
|
authCache: {
|
|
39
44
|
enabled: false,
|
|
40
45
|
ttl: 900
|
|
@@ -72,6 +77,11 @@ const module$1 = defineNuxtModule({
|
|
|
72
77
|
trustedOrigins: options.trustedOrigins ?? [],
|
|
73
78
|
skipAuthRoutes: options.skipAuthRoutes ?? [],
|
|
74
79
|
logging: options.logging ?? false,
|
|
80
|
+
debug: {
|
|
81
|
+
authFlow: options.debug?.authFlow ?? false,
|
|
82
|
+
clientAuthFlow: options.debug?.clientAuthFlow ?? false,
|
|
83
|
+
serverAuthFlow: options.debug?.serverAuthFlow ?? false
|
|
84
|
+
},
|
|
75
85
|
authCache: {
|
|
76
86
|
enabled: options.authCache?.enabled ?? false,
|
|
77
87
|
ttl: options.authCache?.ttl ?? 900
|
|
@@ -91,7 +101,7 @@ const module$1 = defineNuxtModule({
|
|
|
91
101
|
mode: "server"
|
|
92
102
|
});
|
|
93
103
|
addPlugin(resolver.resolve("./runtime/plugin.client"));
|
|
94
|
-
if (isAuthEnabled
|
|
104
|
+
if (isAuthEnabled) {
|
|
95
105
|
addServerHandler({
|
|
96
106
|
route: `${authRoute}/**`,
|
|
97
107
|
handler: resolver.resolve("./runtime/server/api/auth/[...]")
|
|
@@ -12,6 +12,19 @@ type AuthClient = ReturnType<typeof createAuthClient>;
|
|
|
12
12
|
* but this is safe because auth operations (sign in, sign out) should only
|
|
13
13
|
* be triggered by user interactions which only happen on the client.
|
|
14
14
|
*
|
|
15
|
+
* ## Important: useSession() Cost
|
|
16
|
+
*
|
|
17
|
+
* Calling `authClient.useSession()` will trigger an additional `/api/auth/get-session`
|
|
18
|
+
* API call, which results in ~2 extra Convex database queries per page load.
|
|
19
|
+
* This is because Better Auth's useSession() fetches session data independently.
|
|
20
|
+
*
|
|
21
|
+
* **Recommended alternatives:**
|
|
22
|
+
* - Use `useConvexAuth()` for reading auth state (token, user, isAuthenticated)
|
|
23
|
+
* - Use `useConvexAuth().signOut()` for logging out (clears both Better Auth and Convex state)
|
|
24
|
+
*
|
|
25
|
+
* Only use `authClient.useSession()` if you specifically need Better Auth's
|
|
26
|
+
* reactive session features and are okay with the extra API call.
|
|
27
|
+
*
|
|
15
28
|
* @example
|
|
16
29
|
* ```vue
|
|
17
30
|
* <script setup>
|
|
@@ -26,8 +39,10 @@ type AuthClient = ReturnType<typeof createAuthClient>;
|
|
|
26
39
|
* })
|
|
27
40
|
* }
|
|
28
41
|
*
|
|
42
|
+
* // For logout, prefer useConvexAuth().signOut() instead:
|
|
43
|
+
* const { signOut } = useConvexAuth()
|
|
29
44
|
* async function logout() {
|
|
30
|
-
* await
|
|
45
|
+
* await signOut() // Clears both Better Auth AND Convex state
|
|
31
46
|
* }
|
|
32
47
|
* </script>
|
|
33
48
|
* ```
|
|
@@ -13,6 +13,7 @@ export function useConvexAction(action) {
|
|
|
13
13
|
const logLevel = getLogLevel(config.public.convex ?? {});
|
|
14
14
|
const logger = createLogger(logLevel);
|
|
15
15
|
const fnName = getFunctionName(action);
|
|
16
|
+
const client = useConvex();
|
|
16
17
|
const _status = ref("idle");
|
|
17
18
|
const error = ref(null);
|
|
18
19
|
const data = ref(void 0);
|
|
@@ -24,7 +25,6 @@ export function useConvexAction(action) {
|
|
|
24
25
|
data.value = void 0;
|
|
25
26
|
};
|
|
26
27
|
const execute = async (args) => {
|
|
27
|
-
const client = useConvex();
|
|
28
28
|
const startTime = Date.now();
|
|
29
29
|
if (!client) {
|
|
30
30
|
const err = new Error("ConvexClient not available - actions only work on client side");
|
|
@@ -12,12 +12,20 @@ export type { ConvexUser } from '../utils/types.js';
|
|
|
12
12
|
* @example
|
|
13
13
|
* ```vue
|
|
14
14
|
* <script setup>
|
|
15
|
-
* const { user, isAuthenticated, isPending } = useConvexAuth()
|
|
15
|
+
* const { user, isAuthenticated, isPending, signOut } = useConvexAuth()
|
|
16
|
+
*
|
|
17
|
+
* async function handleLogout() {
|
|
18
|
+
* await signOut()
|
|
19
|
+
* navigateTo('/login')
|
|
20
|
+
* }
|
|
16
21
|
* </script>
|
|
17
22
|
*
|
|
18
23
|
* <template>
|
|
19
24
|
* <div v-if="isPending">Loading...</div>
|
|
20
|
-
* <div v-else-if="isAuthenticated">
|
|
25
|
+
* <div v-else-if="isAuthenticated">
|
|
26
|
+
* Welcome, {{ user?.name }}
|
|
27
|
+
* <button @click="handleLogout">Sign out</button>
|
|
28
|
+
* </div>
|
|
21
29
|
* <div v-else>Please log in</div>
|
|
22
30
|
* </template>
|
|
23
31
|
* ```
|
|
@@ -49,4 +57,27 @@ export declare function useConvexAuth(): {
|
|
|
49
57
|
isPending: Readonly<import("vue").Ref<boolean, boolean>>;
|
|
50
58
|
/** Auth error message if authentication failed (e.g., 401/403) */
|
|
51
59
|
authError: Readonly<import("vue").Ref<string | null, string | null>>;
|
|
60
|
+
/**
|
|
61
|
+
* Signs out the user from both Better Auth and Convex.
|
|
62
|
+
* Clears local state immediately and calls Better Auth's signOut().
|
|
63
|
+
*/
|
|
64
|
+
signOut: () => Promise<{
|
|
65
|
+
data: {
|
|
66
|
+
success: boolean;
|
|
67
|
+
};
|
|
68
|
+
error: null;
|
|
69
|
+
} | {
|
|
70
|
+
data: null;
|
|
71
|
+
error: {
|
|
72
|
+
code?: string | undefined | undefined;
|
|
73
|
+
message?: string | undefined | undefined;
|
|
74
|
+
status: number;
|
|
75
|
+
statusText: string;
|
|
76
|
+
};
|
|
77
|
+
} | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Force refresh Convex auth state after login.
|
|
80
|
+
* Triggers fresh token fetch and updates reactive state.
|
|
81
|
+
*/
|
|
82
|
+
refreshAuth: () => Promise<void>;
|
|
52
83
|
};
|
|
@@ -1,10 +1,55 @@
|
|
|
1
|
-
import { useState, computed, readonly } from "#imports";
|
|
1
|
+
import { useState, computed, readonly, useNuxtApp, watch } from "#imports";
|
|
2
2
|
export function useConvexAuth() {
|
|
3
|
+
const nuxtApp = useNuxtApp();
|
|
3
4
|
const token = useState("convex:token", () => null);
|
|
4
5
|
const user = useState("convex:user", () => null);
|
|
5
|
-
const pending = useState("convex:pending", () =>
|
|
6
|
+
const pending = useState("convex:pending", () => true);
|
|
6
7
|
const authError = useState("convex:authError", () => null);
|
|
7
8
|
const isAuthenticated = computed(() => !!token.value && !!user.value);
|
|
9
|
+
const signOut = async () => {
|
|
10
|
+
const authClient = nuxtApp.$auth;
|
|
11
|
+
token.value = null;
|
|
12
|
+
user.value = null;
|
|
13
|
+
authError.value = null;
|
|
14
|
+
if (authClient) {
|
|
15
|
+
try {
|
|
16
|
+
return await authClient.signOut();
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.warn("signOut request failed:", e);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
};
|
|
24
|
+
const refreshAuth = async () => {
|
|
25
|
+
pending.value = true;
|
|
26
|
+
authError.value = null;
|
|
27
|
+
const refreshSignal = useState("convex:refreshSignal", () => 0);
|
|
28
|
+
refreshSignal.value++;
|
|
29
|
+
await new Promise((resolve) => {
|
|
30
|
+
let resolved = false;
|
|
31
|
+
let stopWatcher = null;
|
|
32
|
+
const cleanup = () => {
|
|
33
|
+
if (!resolved) {
|
|
34
|
+
resolved = true;
|
|
35
|
+
if (stopWatcher) stopWatcher();
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
stopWatcher = watch(
|
|
40
|
+
[token, authError],
|
|
41
|
+
([newToken, newError]) => {
|
|
42
|
+
if (newToken) {
|
|
43
|
+
cleanup();
|
|
44
|
+
} else if (newError && resolved === false) {
|
|
45
|
+
cleanup();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
setTimeout(cleanup, 5e3);
|
|
50
|
+
});
|
|
51
|
+
pending.value = false;
|
|
52
|
+
};
|
|
8
53
|
return {
|
|
9
54
|
/** The JWT token for Convex authentication (readonly) */
|
|
10
55
|
token: readonly(token),
|
|
@@ -15,6 +60,16 @@ export function useConvexAuth() {
|
|
|
15
60
|
/** Whether an auth operation is pending */
|
|
16
61
|
isPending: readonly(pending),
|
|
17
62
|
/** Auth error message if authentication failed (e.g., 401/403) */
|
|
18
|
-
authError: readonly(authError)
|
|
63
|
+
authError: readonly(authError),
|
|
64
|
+
/**
|
|
65
|
+
* Signs out the user from both Better Auth and Convex.
|
|
66
|
+
* Clears local state immediately and calls Better Auth's signOut().
|
|
67
|
+
*/
|
|
68
|
+
signOut,
|
|
69
|
+
/**
|
|
70
|
+
* Force refresh Convex auth state after login.
|
|
71
|
+
* Triggers fresh token fetch and updates reactive state.
|
|
72
|
+
*/
|
|
73
|
+
refreshAuth
|
|
19
74
|
};
|
|
20
75
|
}
|
|
@@ -9,6 +9,7 @@ export function useConvexFileUpload(generateUploadUrlMutation, options) {
|
|
|
9
9
|
const logLevel = getLogLevel(config.public.convex ?? {});
|
|
10
10
|
const logger = createLogger(logLevel);
|
|
11
11
|
const fnName = getFunctionName(generateUploadUrlMutation);
|
|
12
|
+
const client = useConvex();
|
|
12
13
|
const _status = ref("idle");
|
|
13
14
|
const error = ref(null);
|
|
14
15
|
const data = ref(void 0);
|
|
@@ -33,7 +34,6 @@ export function useConvexFileUpload(generateUploadUrlMutation, options) {
|
|
|
33
34
|
}
|
|
34
35
|
});
|
|
35
36
|
const upload = async (file, mutationArgs) => {
|
|
36
|
-
const client = useConvex();
|
|
37
37
|
const startTime = Date.now();
|
|
38
38
|
if (!client) {
|
|
39
39
|
const err = new Error("ConvexClient not available - file uploads only work on client side");
|
|
@@ -20,6 +20,7 @@ export function useConvexMutation(mutation, options) {
|
|
|
20
20
|
const logger = createLogger(logLevel);
|
|
21
21
|
const fnName = getFunctionName(mutation);
|
|
22
22
|
const hasOptimisticUpdate = !!options?.optimisticUpdate;
|
|
23
|
+
const client = useConvex();
|
|
23
24
|
const _status = ref("idle");
|
|
24
25
|
const error = ref(null);
|
|
25
26
|
const data = ref(void 0);
|
|
@@ -31,7 +32,6 @@ export function useConvexMutation(mutation, options) {
|
|
|
31
32
|
data.value = void 0;
|
|
32
33
|
};
|
|
33
34
|
const mutate = async (args) => {
|
|
34
|
-
const client = useConvex();
|
|
35
35
|
const startTime = Date.now();
|
|
36
36
|
if (!client) {
|
|
37
37
|
const err = new Error("ConvexClient not available - mutations only work on client side");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useNuxtApp, useRuntimeConfig, useRequestEvent, useAsyncData } from "#imports";
|
|
1
|
+
import { useNuxtApp, useRuntimeConfig, useRequestEvent, useAsyncData, useState } from "#imports";
|
|
2
2
|
import {
|
|
3
3
|
ref,
|
|
4
4
|
computed,
|
|
@@ -75,6 +75,7 @@ export function useConvexPaginatedQuery(query, args, options) {
|
|
|
75
75
|
const isSkipped = computed(() => getArgs() === "skip");
|
|
76
76
|
const event = import.meta.server ? useRequestEvent() : null;
|
|
77
77
|
const cookieHeader = event?.headers.get("cookie") || "";
|
|
78
|
+
const cachedToken = useState("convex:token");
|
|
78
79
|
const currentPaginationId = ref(generatePaginationId());
|
|
79
80
|
const pages = shallowRef([]);
|
|
80
81
|
const globalError = ref(null);
|
|
@@ -108,7 +109,8 @@ export function useConvexPaginatedQuery(query, args, options) {
|
|
|
108
109
|
authToken = await fetchAuthToken({
|
|
109
110
|
isPublic,
|
|
110
111
|
cookieHeader,
|
|
111
|
-
siteUrl
|
|
112
|
+
siteUrl,
|
|
113
|
+
cachedToken
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
return executeQueryHttp(convexUrl, functionPath, fullArgs, authToken);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useNuxtApp, useRuntimeConfig, useRequestEvent, useAsyncData } from "#imports";
|
|
1
|
+
import { useNuxtApp, useRuntimeConfig, useRequestEvent, useAsyncData, useState } from "#imports";
|
|
2
2
|
import { computed, watch, triggerRef, onUnmounted, toValue, isRef, isReactive } from "vue";
|
|
3
3
|
import {
|
|
4
4
|
getFunctionName,
|
|
@@ -74,6 +74,7 @@ export function useConvexQuery(query, args, options) {
|
|
|
74
74
|
};
|
|
75
75
|
const event = import.meta.server ? useRequestEvent() : null;
|
|
76
76
|
const cookieHeader = event?.headers.get("cookie") || "";
|
|
77
|
+
const cachedToken = useState("convex:token");
|
|
77
78
|
const asyncData = useAsyncData(
|
|
78
79
|
cacheKey,
|
|
79
80
|
async () => {
|
|
@@ -90,7 +91,8 @@ export function useConvexQuery(query, args, options) {
|
|
|
90
91
|
const authToken = await fetchAuthToken({
|
|
91
92
|
isPublic,
|
|
92
93
|
cookieHeader,
|
|
93
|
-
siteUrl
|
|
94
|
+
siteUrl,
|
|
95
|
+
cachedToken
|
|
94
96
|
});
|
|
95
97
|
const result2 = await executeQueryHttp(convexUrl, fnName, currentArgs, authToken);
|
|
96
98
|
return applyTransform(result2);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry for tracking auth proxy requests in dev mode.
|
|
3
3
|
* This runs on the Nitro server and stores request data in global state.
|
|
4
|
+
*
|
|
5
|
+
* WARNING: This module uses globalThis state which IS shared across SSR requests.
|
|
6
|
+
* This is acceptable because:
|
|
7
|
+
* 1. It's only used in development mode for debugging
|
|
8
|
+
* 2. It only stores non-sensitive timing/stats data
|
|
9
|
+
* 3. The data is intentionally aggregated across requests for the DevTools panel
|
|
10
|
+
*
|
|
11
|
+
* Do NOT use this pattern for user-specific data in production.
|
|
4
12
|
*/
|
|
5
13
|
import type { AuthProxyRequest, AuthProxyStats } from './types.js';
|
|
6
14
|
declare global {
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Mutation registry for DevTools integration.
|
|
3
3
|
* Tracks in-flight and completed mutations with timeline data.
|
|
4
4
|
* In-memory only, max 50 entries with LRU eviction.
|
|
5
|
+
*
|
|
6
|
+
* This module is client-only. Importing on the server will throw an error
|
|
7
|
+
* to prevent SSR state leakage between requests.
|
|
5
8
|
*/
|
|
6
9
|
import type { MutationEntry } from './types.js';
|
|
7
10
|
type RegistryCallback = (entries: MutationEntry[]) => void;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
if (import.meta.server) {
|
|
2
|
+
throw new Error(
|
|
3
|
+
"[better-convex-nuxt] DevTools mutation-registry must not be imported on server. This would cause state leakage between SSR requests."
|
|
4
|
+
);
|
|
5
|
+
}
|
|
1
6
|
const MAX_ENTRIES = 50;
|
|
2
7
|
const mutationRegistry = /* @__PURE__ */ new Map();
|
|
3
8
|
const subscribers = /* @__PURE__ */ new Set();
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Query registry for DevTools integration.
|
|
3
3
|
* Tracks active queries with metadata for visualization.
|
|
4
|
+
*
|
|
5
|
+
* This module is client-only. Importing on the server will throw an error
|
|
6
|
+
* to prevent SSR state leakage between requests.
|
|
4
7
|
*/
|
|
5
8
|
export type QueryStatus = 'pending' | 'success' | 'error' | 'idle';
|
|
6
9
|
export type DataSource = 'ssr' | 'websocket' | 'cache';
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
if (import.meta.server) {
|
|
2
|
+
throw new Error(
|
|
3
|
+
"[better-convex-nuxt] DevTools query-registry must not be imported on server. This would cause state leakage between SSR requests."
|
|
4
|
+
);
|
|
5
|
+
}
|
|
1
6
|
const queryRegistry = /* @__PURE__ */ new Map();
|
|
2
7
|
const subscribers = /* @__PURE__ */ new Set();
|
|
3
8
|
function notifySubscribers() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"
|
|
1
|
+
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"df213a49-93a4-44f1-863e-5dc7de19c884",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1771229435100,false]</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"
|
|
1
|
+
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"df213a49-93a4-44f1-863e-5dc7de19c884",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1771229435101,false]</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"df213a49-93a4-44f1-863e-5dc7de19c884","timestamp":1771229432599}
|
package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/df213a49-93a4-44f1-863e-5dc7de19c884.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"df213a49-93a4-44f1-863e-5dc7de19c884","timestamp":1771229432599,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"
|
|
1
|
+
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Convex DevTools</title><link rel="stylesheet" href="/__convex_devtools__/_nuxt/entry.BiOLMZBG.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__convex_devtools__/_nuxt/BhO0ov6K.js"><script type="module" src="/__convex_devtools__/_nuxt/BhO0ov6K.js" crossorigin></script><script id="unhead:payload" type="application/json">{"title":"Convex DevTools"}</script></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__convex_devtools__",buildId:"df213a49-93a4-44f1-863e-5dc7de19c884",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1771229435101,false]</script></body></html>
|
|
@@ -11,7 +11,28 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
11
11
|
const logLevel = getLogLevel(config.public.convex ?? {});
|
|
12
12
|
const logger = createLogger(logLevel);
|
|
13
13
|
const endInit = logger.time("plugin:init (client)");
|
|
14
|
-
|
|
14
|
+
const debugConfig = config.public.convex?.debug;
|
|
15
|
+
const enableClientAuthTrace = logLevel === "debug" && (debugConfig?.authFlow === true || debugConfig?.clientAuthFlow === true);
|
|
16
|
+
const rawAuthLog = logger.auth.bind(logger);
|
|
17
|
+
logger.auth = (event) => {
|
|
18
|
+
rawAuthLog(event);
|
|
19
|
+
if (enableClientAuthTrace) {
|
|
20
|
+
console.log("[BCN_AUTH][client]", {
|
|
21
|
+
phase: event.phase,
|
|
22
|
+
outcome: event.outcome,
|
|
23
|
+
...event.details,
|
|
24
|
+
error: event.error ? event.error.message : null
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const convexAuthTraceId = useState(
|
|
29
|
+
"convex:authTraceId",
|
|
30
|
+
() => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
|
|
31
|
+
);
|
|
32
|
+
if (nuxtApp._convexInitialized) {
|
|
33
|
+
logger.debug("plugin:init (client) skipped; already initialized", { traceId: convexAuthTraceId.value });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
15
36
|
nuxtApp._convexInitialized = true;
|
|
16
37
|
const convexUrl = config.public.convex?.url;
|
|
17
38
|
const isAuthEnabled = config.public.convex?.auth;
|
|
@@ -27,7 +48,24 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
27
48
|
const convexAuthError = useState("convex:authError");
|
|
28
49
|
const client = new ConvexClient(convexUrl);
|
|
29
50
|
let authClient = null;
|
|
30
|
-
const convexPending = useState("convex:pending", () =>
|
|
51
|
+
const convexPending = useState("convex:pending", () => true);
|
|
52
|
+
let hasResolvedInitialAuth = false;
|
|
53
|
+
const resolveInitialAuth = () => {
|
|
54
|
+
if (!hasResolvedInitialAuth) {
|
|
55
|
+
hasResolvedInitialAuth = true;
|
|
56
|
+
convexPending.value = false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const refreshSignal = useState("convex:refreshSignal", () => 0);
|
|
60
|
+
logger.auth({
|
|
61
|
+
phase: "client-init",
|
|
62
|
+
outcome: "success",
|
|
63
|
+
details: {
|
|
64
|
+
traceId: convexAuthTraceId.value,
|
|
65
|
+
serverRendered: Boolean(nuxtApp.payload?.serverRendered),
|
|
66
|
+
authEnabled: Boolean(isAuthEnabled)
|
|
67
|
+
}
|
|
68
|
+
});
|
|
31
69
|
if (isAuthEnabled && siteUrl) {
|
|
32
70
|
const rawAuthRoute = config.public.convex?.authRoute || "/api/auth";
|
|
33
71
|
const authRoute = (rawAuthRoute.startsWith("/") ? rawAuthRoute : `/${rawAuthRoute}`).replace(/\/+$/, "");
|
|
@@ -43,37 +81,118 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
43
81
|
const NULL_TOKEN_CACHE_MS = 5e3;
|
|
44
82
|
const skipRoutes = config.public.convex?.skipAuthRoutes || [];
|
|
45
83
|
const router = useRouter();
|
|
46
|
-
let currentAuthOperation = null;
|
|
47
84
|
const fetchToken = async ({
|
|
48
85
|
forceRefreshToken,
|
|
49
86
|
signal
|
|
50
87
|
}) => {
|
|
51
|
-
if (signal?.aborted) return null;
|
|
52
88
|
const route = router.currentRoute.value;
|
|
89
|
+
const routePath = route.path;
|
|
90
|
+
logger.auth({
|
|
91
|
+
phase: "client-fetchToken:start",
|
|
92
|
+
outcome: "success",
|
|
93
|
+
details: {
|
|
94
|
+
traceId: convexAuthTraceId.value,
|
|
95
|
+
path: routePath,
|
|
96
|
+
forceRefreshToken,
|
|
97
|
+
hasHydratedToken: Boolean(convexToken.value),
|
|
98
|
+
hasHydratedUser: Boolean(convexUser.value)
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
if (signal?.aborted) {
|
|
102
|
+
logger.auth({
|
|
103
|
+
phase: "client-fetchToken:abort",
|
|
104
|
+
outcome: "skip",
|
|
105
|
+
details: { traceId: convexAuthTraceId.value, reason: "signal-aborted-before-start", path: routePath }
|
|
106
|
+
});
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
53
109
|
if (route.meta?.skipConvexAuth === true) {
|
|
110
|
+
logger.auth({
|
|
111
|
+
phase: "client-fetchToken:skip",
|
|
112
|
+
outcome: "skip",
|
|
113
|
+
details: { traceId: convexAuthTraceId.value, reason: "page-meta-skip", path: routePath }
|
|
114
|
+
});
|
|
115
|
+
resolveInitialAuth();
|
|
54
116
|
return null;
|
|
55
117
|
}
|
|
56
118
|
if (matchesSkipRoute(route.path, skipRoutes)) {
|
|
119
|
+
logger.auth({
|
|
120
|
+
phase: "client-fetchToken:skip",
|
|
121
|
+
outcome: "skip",
|
|
122
|
+
details: { traceId: convexAuthTraceId.value, reason: "skip-auth-route", path: routePath }
|
|
123
|
+
});
|
|
124
|
+
resolveInitialAuth();
|
|
57
125
|
return null;
|
|
58
126
|
}
|
|
59
127
|
if (convexToken.value && !forceRefreshToken) {
|
|
60
128
|
lastTokenValidation = Date.now();
|
|
129
|
+
logger.auth({
|
|
130
|
+
phase: "client-fetchToken:cache",
|
|
131
|
+
outcome: "success",
|
|
132
|
+
details: { traceId: convexAuthTraceId.value, source: "hydrated-token", path: routePath }
|
|
133
|
+
});
|
|
134
|
+
resolveInitialAuth();
|
|
61
135
|
return convexToken.value;
|
|
62
136
|
}
|
|
63
137
|
const timeSinceValidation = Date.now() - lastTokenValidation;
|
|
64
138
|
if (convexToken.value && forceRefreshToken && timeSinceValidation < TOKEN_CACHE_MS) {
|
|
139
|
+
logger.auth({
|
|
140
|
+
phase: "client-fetchToken:cache",
|
|
141
|
+
outcome: "success",
|
|
142
|
+
details: {
|
|
143
|
+
traceId: convexAuthTraceId.value,
|
|
144
|
+
source: "recent-token-cache",
|
|
145
|
+
ageMs: timeSinceValidation,
|
|
146
|
+
path: routePath
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
resolveInitialAuth();
|
|
65
150
|
return convexToken.value;
|
|
66
151
|
}
|
|
67
152
|
const wasServerRendered = !!nuxtApp.payload?.serverRendered;
|
|
68
153
|
if (wasServerRendered && !convexToken.value && !convexUser.value && !forceRefreshToken) {
|
|
154
|
+
logger.auth({
|
|
155
|
+
phase: "client-fetchToken:skip",
|
|
156
|
+
outcome: "skip",
|
|
157
|
+
details: {
|
|
158
|
+
traceId: convexAuthTraceId.value,
|
|
159
|
+
reason: "ssr-rendered-no-hydrated-session",
|
|
160
|
+
path: routePath
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
resolveInitialAuth();
|
|
69
164
|
return null;
|
|
70
165
|
}
|
|
71
|
-
|
|
166
|
+
const timeSinceNullCheck = Date.now() - lastNullTokenCheck;
|
|
167
|
+
if (!convexToken.value && timeSinceNullCheck < NULL_TOKEN_CACHE_MS) {
|
|
168
|
+
logger.auth({
|
|
169
|
+
phase: "client-fetchToken:cache",
|
|
170
|
+
outcome: "miss",
|
|
171
|
+
details: {
|
|
172
|
+
traceId: convexAuthTraceId.value,
|
|
173
|
+
source: "negative-cache",
|
|
174
|
+
ageMs: timeSinceNullCheck,
|
|
175
|
+
path: routePath
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
resolveInitialAuth();
|
|
72
179
|
return null;
|
|
73
180
|
}
|
|
74
181
|
try {
|
|
182
|
+
logger.auth({
|
|
183
|
+
phase: "client-fetchToken:request",
|
|
184
|
+
outcome: "success",
|
|
185
|
+
details: { traceId: convexAuthTraceId.value, endpoint: `${authRoute}/convex/token`, path: routePath }
|
|
186
|
+
});
|
|
75
187
|
const response = await authClient.convex.token();
|
|
76
|
-
if (signal?.aborted)
|
|
188
|
+
if (signal?.aborted) {
|
|
189
|
+
logger.auth({
|
|
190
|
+
phase: "client-fetchToken:abort",
|
|
191
|
+
outcome: "skip",
|
|
192
|
+
details: { traceId: convexAuthTraceId.value, reason: "signal-aborted-after-request", path: routePath }
|
|
193
|
+
});
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
77
196
|
if (response.error || !response.data?.token) {
|
|
78
197
|
convexToken.value = null;
|
|
79
198
|
convexUser.value = null;
|
|
@@ -82,6 +201,16 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
82
201
|
convexAuthError.value = errorMsg;
|
|
83
202
|
}
|
|
84
203
|
lastNullTokenCheck = Date.now();
|
|
204
|
+
logger.auth({
|
|
205
|
+
phase: "client-fetchToken:response",
|
|
206
|
+
outcome: "miss",
|
|
207
|
+
details: {
|
|
208
|
+
traceId: convexAuthTraceId.value,
|
|
209
|
+
path: routePath,
|
|
210
|
+
hasError: Boolean(response.error)
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
resolveInitialAuth();
|
|
85
214
|
return null;
|
|
86
215
|
}
|
|
87
216
|
const token = response.data.token;
|
|
@@ -91,49 +220,68 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
91
220
|
if (!convexUser.value) {
|
|
92
221
|
convexUser.value = decodeUserFromJwt(token);
|
|
93
222
|
}
|
|
223
|
+
logger.auth({
|
|
224
|
+
phase: "client-fetchToken:response",
|
|
225
|
+
outcome: "success",
|
|
226
|
+
details: {
|
|
227
|
+
traceId: convexAuthTraceId.value,
|
|
228
|
+
path: routePath,
|
|
229
|
+
userHydrated: Boolean(convexUser.value)
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
resolveInitialAuth();
|
|
94
233
|
return token;
|
|
95
234
|
} catch (e) {
|
|
96
|
-
if (signal?.aborted)
|
|
235
|
+
if (signal?.aborted) {
|
|
236
|
+
logger.auth({
|
|
237
|
+
phase: "client-fetchToken:abort",
|
|
238
|
+
outcome: "skip",
|
|
239
|
+
details: { traceId: convexAuthTraceId.value, reason: "signal-aborted-after-error", path: routePath }
|
|
240
|
+
});
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
97
243
|
convexToken.value = null;
|
|
98
244
|
convexUser.value = null;
|
|
99
245
|
convexAuthError.value = e instanceof Error ? e.message : "Authentication request failed";
|
|
100
246
|
lastNullTokenCheck = Date.now();
|
|
247
|
+
logger.auth({
|
|
248
|
+
phase: "client-fetchToken:response",
|
|
249
|
+
outcome: "error",
|
|
250
|
+
details: { traceId: convexAuthTraceId.value, path: routePath },
|
|
251
|
+
error: e instanceof Error ? e : new Error("Authentication request failed")
|
|
252
|
+
});
|
|
253
|
+
resolveInitialAuth();
|
|
101
254
|
return null;
|
|
102
255
|
}
|
|
103
256
|
};
|
|
104
257
|
client.setAuth(fetchToken, (isAuthenticated) => {
|
|
105
|
-
logger.
|
|
258
|
+
logger.auth({
|
|
259
|
+
phase: "client-setAuth",
|
|
260
|
+
outcome: "success",
|
|
261
|
+
details: {
|
|
262
|
+
traceId: convexAuthTraceId.value,
|
|
263
|
+
state: isAuthenticated ? "authenticated" : "unauthenticated",
|
|
264
|
+
hasToken: Boolean(convexToken.value),
|
|
265
|
+
hasUser: Boolean(convexUser.value)
|
|
266
|
+
}
|
|
267
|
+
});
|
|
106
268
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const hadSession = !!oldSessionData;
|
|
115
|
-
const hasSession = !!sessionData;
|
|
116
|
-
if (hasSession && !hadSession) {
|
|
117
|
-
convexPending.value = true;
|
|
118
|
-
try {
|
|
119
|
-
lastNullTokenCheck = 0;
|
|
120
|
-
lastTokenValidation = 0;
|
|
121
|
-
await fetchToken({ forceRefreshToken: true, signal });
|
|
122
|
-
logger.auth({ phase: "login", outcome: "success" });
|
|
123
|
-
} finally {
|
|
124
|
-
if (!signal.aborted) {
|
|
125
|
-
convexPending.value = false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
} else if (!hasSession && hadSession) {
|
|
129
|
-
convexPending.value = false;
|
|
130
|
-
convexToken.value = null;
|
|
131
|
-
convexUser.value = null;
|
|
132
|
-
convexAuthError.value = null;
|
|
133
|
-
logger.auth({ phase: "logout", outcome: "success" });
|
|
269
|
+
watch(refreshSignal, () => {
|
|
270
|
+
logger.auth({
|
|
271
|
+
phase: "client-refresh",
|
|
272
|
+
outcome: "success",
|
|
273
|
+
details: {
|
|
274
|
+
traceId: convexAuthTraceId.value,
|
|
275
|
+
refreshSignal: refreshSignal.value
|
|
134
276
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
277
|
+
});
|
|
278
|
+
lastTokenValidation = 0;
|
|
279
|
+
lastNullTokenCheck = 0;
|
|
280
|
+
client.setAuth(fetchToken, (_isAuthenticated) => {
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
convexPending.value = false;
|
|
137
285
|
}
|
|
138
286
|
nuxtApp.provide("convex", client);
|
|
139
287
|
if (authClient) {
|
|
@@ -151,7 +299,14 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
151
299
|
if (convexToken.value) {
|
|
152
300
|
logger.auth({ phase: "hydrate", outcome: "success", details: { source: "ssr" } });
|
|
153
301
|
} else if (isAuthEnabled) {
|
|
154
|
-
logger.
|
|
302
|
+
logger.auth({
|
|
303
|
+
phase: "hydrate",
|
|
304
|
+
outcome: "miss",
|
|
305
|
+
details: {
|
|
306
|
+
traceId: convexAuthTraceId.value,
|
|
307
|
+
source: "client-init"
|
|
308
|
+
}
|
|
309
|
+
});
|
|
155
310
|
} else {
|
|
156
311
|
logger.debug("Client initialized (auth disabled)");
|
|
157
312
|
}
|
|
@@ -21,6 +21,20 @@ export default defineNuxtPlugin(async () => {
|
|
|
21
21
|
const logLevel = getLogLevel(config.public.convex ?? {});
|
|
22
22
|
const logger = createLogger(logLevel);
|
|
23
23
|
const endInit = logger.time("plugin:init (server)");
|
|
24
|
+
const debugConfig = config.public.convex?.debug;
|
|
25
|
+
const enableServerAuthTrace = logLevel === "debug" && (debugConfig?.authFlow === true || debugConfig?.serverAuthFlow === true);
|
|
26
|
+
const rawAuthLog = logger.auth.bind(logger);
|
|
27
|
+
logger.auth = (event2) => {
|
|
28
|
+
rawAuthLog(event2);
|
|
29
|
+
if (enableServerAuthTrace) {
|
|
30
|
+
console.log("[BCN_AUTH][server]", {
|
|
31
|
+
phase: event2.phase,
|
|
32
|
+
outcome: event2.outcome,
|
|
33
|
+
...event2.details,
|
|
34
|
+
error: event2.error ? event2.error.message : null
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
24
38
|
const isAuthEnabled = config.public.convex?.auth;
|
|
25
39
|
if (!isAuthEnabled) {
|
|
26
40
|
endInit();
|
|
@@ -32,6 +46,9 @@ export default defineNuxtPlugin(async () => {
|
|
|
32
46
|
logger.auth({ phase: "init", outcome: "error", error: new Error("No request event available") });
|
|
33
47
|
return;
|
|
34
48
|
}
|
|
49
|
+
const requestPath = event.path || event.node.req.url || "(unknown)";
|
|
50
|
+
const requestMethod = event.method || "GET";
|
|
51
|
+
const requestId = crypto.randomUUID();
|
|
35
52
|
const siteUrl = config.public.convex?.siteUrl;
|
|
36
53
|
if (!siteUrl) {
|
|
37
54
|
endInit();
|
|
@@ -39,7 +56,17 @@ export default defineNuxtPlugin(async () => {
|
|
|
39
56
|
return;
|
|
40
57
|
}
|
|
41
58
|
const logAuth = (phase, outcome, details, error) => {
|
|
42
|
-
logger.auth({
|
|
59
|
+
logger.auth({
|
|
60
|
+
phase,
|
|
61
|
+
outcome,
|
|
62
|
+
details: {
|
|
63
|
+
requestId,
|
|
64
|
+
method: requestMethod,
|
|
65
|
+
path: requestPath,
|
|
66
|
+
...details
|
|
67
|
+
},
|
|
68
|
+
error
|
|
69
|
+
});
|
|
43
70
|
};
|
|
44
71
|
const convexToken = useState("convex:token", () => null);
|
|
45
72
|
const convexUser = useState("convex:user", () => null);
|
|
@@ -48,14 +75,20 @@ export default defineNuxtPlugin(async () => {
|
|
|
48
75
|
const waterfallStart = trackWaterfall ? Date.now() : 0;
|
|
49
76
|
const phases = [];
|
|
50
77
|
let cacheHit = false;
|
|
78
|
+
const authCacheConfig = config.public.convex?.authCache;
|
|
51
79
|
const sessionCheckStart = trackWaterfall ? Date.now() : 0;
|
|
52
80
|
const cookieHeader = event.headers.get("cookie");
|
|
53
81
|
const sessionToken = getCookie(cookieHeader, SECURE_SESSION_COOKIE_NAME) || getCookie(cookieHeader, SESSION_COOKIE_NAME);
|
|
82
|
+
logAuth("server-init", "success", {
|
|
83
|
+
hasCookieHeader: Boolean(cookieHeader),
|
|
84
|
+
hasSessionToken: Boolean(sessionToken),
|
|
85
|
+
cacheEnabled: Boolean(authCacheConfig?.enabled)
|
|
86
|
+
});
|
|
54
87
|
if (!cookieHeader || !sessionToken) {
|
|
55
88
|
if (trackWaterfall) {
|
|
56
89
|
phases.push(buildPhase("session-check", sessionCheckStart, waterfallStart, "miss", "No session cookie"));
|
|
57
90
|
convexAuthWaterfall.value = {
|
|
58
|
-
requestId
|
|
91
|
+
requestId,
|
|
59
92
|
timestamp: waterfallStart,
|
|
60
93
|
phases,
|
|
61
94
|
totalDuration: Date.now() - waterfallStart,
|
|
@@ -70,7 +103,6 @@ export default defineNuxtPlugin(async () => {
|
|
|
70
103
|
if (trackWaterfall) {
|
|
71
104
|
phases.push(buildPhase("session-check", sessionCheckStart, waterfallStart, "success", "Cookie found"));
|
|
72
105
|
}
|
|
73
|
-
const authCacheConfig = config.public.convex?.authCache;
|
|
74
106
|
try {
|
|
75
107
|
let token = null;
|
|
76
108
|
if (authCacheConfig?.enabled && sessionToken) {
|
|
@@ -89,7 +121,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
89
121
|
buildPhase("jwt-decode", decodeStart, waterfallStart, convexUser.value ? "success" : "error")
|
|
90
122
|
);
|
|
91
123
|
convexAuthWaterfall.value = {
|
|
92
|
-
requestId
|
|
124
|
+
requestId,
|
|
93
125
|
timestamp: waterfallStart,
|
|
94
126
|
phases,
|
|
95
127
|
totalDuration: Date.now() - waterfallStart,
|
|
@@ -161,7 +193,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
161
193
|
}
|
|
162
194
|
if (trackWaterfall) {
|
|
163
195
|
convexAuthWaterfall.value = {
|
|
164
|
-
requestId
|
|
196
|
+
requestId,
|
|
165
197
|
timestamp: waterfallStart,
|
|
166
198
|
phases,
|
|
167
199
|
totalDuration: Date.now() - waterfallStart,
|
|
@@ -174,7 +206,7 @@ export default defineNuxtPlugin(async () => {
|
|
|
174
206
|
convexUser.value = null;
|
|
175
207
|
if (trackWaterfall) {
|
|
176
208
|
convexAuthWaterfall.value = {
|
|
177
|
-
requestId
|
|
209
|
+
requestId,
|
|
178
210
|
timestamp: waterfallStart,
|
|
179
211
|
phases,
|
|
180
212
|
totalDuration: Date.now() - waterfallStart,
|
|
@@ -34,13 +34,13 @@ export default defineEventHandler(async (event) => {
|
|
|
34
34
|
const authRoute = (rawAuthRoute.startsWith("/") ? rawAuthRoute : `/${rawAuthRoute}`).replace(/\/+$/, "");
|
|
35
35
|
const startTime = import.meta.dev ? Date.now() : 0;
|
|
36
36
|
const requestId = import.meta.dev ? crypto.randomUUID() : "";
|
|
37
|
+
const requestUrl = getRequestURL(event);
|
|
37
38
|
if (!siteUrl) {
|
|
38
39
|
throw createError({
|
|
39
40
|
statusCode: 500,
|
|
40
41
|
message: "Convex site URL not configured"
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
const requestUrl = getRequestURL(event);
|
|
44
44
|
const authRoutePattern = new RegExp(`^${authRoute.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
45
45
|
const path = requestUrl.pathname.replace(authRoutePattern, "") || "/";
|
|
46
46
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { createAuthClient } from 'better-auth/vue'
|
|
2
2
|
import type { ConvexClient } from 'convex/browser'
|
|
3
|
+
import type { LogLevel } from './utils/logger'
|
|
3
4
|
|
|
4
5
|
type AuthClient = ReturnType<typeof createAuthClient>
|
|
5
6
|
|
|
@@ -13,10 +14,13 @@ export interface ConvexPublicRuntimeConfig {
|
|
|
13
14
|
siteUrl?: string
|
|
14
15
|
/** Routes that should skip auth checks */
|
|
15
16
|
skipAuthRoutes?: string[]
|
|
16
|
-
/** Logging
|
|
17
|
-
logging?:
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/** Logging level */
|
|
18
|
+
logging?: LogLevel
|
|
19
|
+
/** Optional debug channels for high-verbosity traces */
|
|
20
|
+
debug?: {
|
|
21
|
+
authFlow?: boolean
|
|
22
|
+
clientAuthFlow?: boolean
|
|
23
|
+
serverAuthFlow?: boolean
|
|
20
24
|
}
|
|
21
25
|
/** Index signature for compatibility with Record<string, unknown> */
|
|
22
26
|
[key: string]: unknown
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { useNuxtApp } from '#app';
|
|
2
2
|
export { type QueryStatus, parseConvexResponse, computeQueryStatus, getFunctionName, hashArgs, getQueryKey, } from './convex-shared.js';
|
|
3
3
|
type NuxtApp = ReturnType<typeof useNuxtApp>;
|
|
4
4
|
/**
|
|
@@ -20,20 +20,33 @@ export interface FetchAuthTokenOptions {
|
|
|
20
20
|
cookieHeader: string;
|
|
21
21
|
/** Site URL for auth endpoint */
|
|
22
22
|
siteUrl: string | undefined;
|
|
23
|
+
/** Cached token state (must be obtained at setup time via useState) */
|
|
24
|
+
cachedToken: {
|
|
25
|
+
value: string | null;
|
|
26
|
+
};
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* Fetch auth token for SSR queries.
|
|
26
|
-
* Uses caching via
|
|
30
|
+
* Uses caching via the provided cachedToken ref to avoid redundant fetches.
|
|
31
|
+
*
|
|
32
|
+
* IMPORTANT: The cachedToken parameter must be obtained at component setup time
|
|
33
|
+
* using useState('convex:token') before being passed to this function.
|
|
34
|
+
* Calling useState inside an async function loses Vue context and will fail.
|
|
27
35
|
*
|
|
28
36
|
* @param options - Auth token fetch options
|
|
29
37
|
* @returns The auth token if available, undefined otherwise
|
|
30
38
|
*
|
|
31
39
|
* @example
|
|
32
40
|
* ```ts
|
|
41
|
+
* // At setup time (synchronous):
|
|
42
|
+
* const cachedToken = useState<string | null>('convex:token')
|
|
43
|
+
*
|
|
44
|
+
* // Later, in async context:
|
|
33
45
|
* const authToken = await fetchAuthToken({
|
|
34
46
|
* isPublic: false,
|
|
35
47
|
* cookieHeader: event?.headers.get('cookie') || '',
|
|
36
48
|
* siteUrl: config.public.convex?.siteUrl,
|
|
49
|
+
* cachedToken,
|
|
37
50
|
* })
|
|
38
51
|
* ```
|
|
39
52
|
*/
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useState } from "#app";
|
|
2
1
|
export {
|
|
3
2
|
parseConvexResponse,
|
|
4
3
|
computeQueryStatus,
|
|
@@ -8,14 +7,13 @@ export {
|
|
|
8
7
|
} from "./convex-shared.js";
|
|
9
8
|
const subscriptionRegistry = /* @__PURE__ */ new WeakMap();
|
|
10
9
|
export async function fetchAuthToken(options) {
|
|
11
|
-
const { isPublic, cookieHeader, siteUrl } = options;
|
|
10
|
+
const { isPublic, cookieHeader, siteUrl, cachedToken } = options;
|
|
12
11
|
if (isPublic) {
|
|
13
12
|
return void 0;
|
|
14
13
|
}
|
|
15
14
|
if (!cookieHeader.includes("better-auth.session_token")) {
|
|
16
15
|
return void 0;
|
|
17
16
|
}
|
|
18
|
-
const cachedToken = useState("convex:token");
|
|
19
17
|
if (cachedToken.value) {
|
|
20
18
|
return cachedToken.value;
|
|
21
19
|
}
|
package/dist/types.d.mts
CHANGED
|
@@ -2,4 +2,4 @@ export { type LogLevel } from '../dist/runtime/utils/logger.js'
|
|
|
2
2
|
|
|
3
3
|
export { default } from './module.mjs'
|
|
4
4
|
|
|
5
|
-
export { type AuthCacheOptions, type ModuleOptions, type QueryDefaults } from './module.mjs'
|
|
5
|
+
export { type AuthCacheOptions, type ConvexDebugOptions, type ModuleOptions, type QueryDefaults } from './module.mjs'
|
package/package.json
CHANGED
package/dist/runtime/devtools/ui/dist/_nuxt/builds/meta/bb1b6fab-3dad-4ebb-bedd-8a7e8492b357.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"bb1b6fab-3dad-4ebb-bedd-8a7e8492b357","timestamp":1769017708682,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|