nuxt-convex-module 0.0.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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/chunks/index.mjs +64 -0
- package/dist/devtools-client/200.html +1 -0
- package/dist/devtools-client/404.html +1 -0
- package/dist/devtools-client/_nuxt/B29GGJ5n.js +1 -0
- package/dist/devtools-client/_nuxt/BSDEpQet.js +1 -0
- package/dist/devtools-client/_nuxt/BY23OQOo.js +1 -0
- package/dist/devtools-client/_nuxt/BZYRaL-G.js +1 -0
- package/dist/devtools-client/_nuxt/Byw6FxnC.js +1 -0
- package/dist/devtools-client/_nuxt/CJJ_hcZN.js +1 -0
- package/dist/devtools-client/_nuxt/CS6WMFOx.js +2 -0
- package/dist/devtools-client/_nuxt/CZbAGcNC.js +1 -0
- package/dist/devtools-client/_nuxt/Cr6_aklh.js +2 -0
- package/dist/devtools-client/_nuxt/DNfpyBVU.js +3 -0
- package/dist/devtools-client/_nuxt/DPWXUmcI.js +1 -0
- package/dist/devtools-client/_nuxt/builds/latest.json +1 -0
- package/dist/devtools-client/_nuxt/builds/meta/a7708498-bd09-4de2-b5ab-19b380293c77.json +1 -0
- package/dist/devtools-client/_nuxt/dy_xXcv9.js +1 -0
- package/dist/devtools-client/_nuxt/entry.DbTJhkHT.css +1 -0
- package/dist/devtools-client/_nuxt/error-404.CwGGO2Kz.css +1 -0
- package/dist/devtools-client/_nuxt/error-500.8ZJqCQm8.css +1 -0
- package/dist/devtools-client/_nuxt/ncgEdbEt.js +1 -0
- package/dist/devtools-client/_nuxt/queries.CE_dtt9-.css +1 -0
- package/dist/devtools-client/auth/index.html +1 -0
- package/dist/devtools-client/icon.svg +6 -0
- package/dist/devtools-client/index.html +1 -0
- package/dist/devtools-client/logs/index.html +1 -0
- package/dist/devtools-client/queries/index.html +1 -0
- package/dist/module.d.ts +189 -0
- package/dist/module.js +489 -0
- package/dist/module.json +13 -0
- package/dist/runtime/auth0/vue/index.d.ts +58 -0
- package/dist/runtime/auth0/vue/index.js +33 -0
- package/dist/runtime/better-auth/nuxt/middleware.d.ts +14 -0
- package/dist/runtime/better-auth/nuxt/middleware.js +39 -0
- package/dist/runtime/better-auth/nuxt/proxy.d.ts +6 -0
- package/dist/runtime/better-auth/nuxt/proxy.js +11 -0
- package/dist/runtime/better-auth/nuxt/server.d.ts +106 -0
- package/dist/runtime/better-auth/nuxt/server.js +123 -0
- package/dist/runtime/better-auth/vue/auth-boundary.d.ts +131 -0
- package/dist/runtime/better-auth/vue/auth-boundary.js +78 -0
- package/dist/runtime/better-auth/vue/client.d.ts +358 -0
- package/dist/runtime/better-auth/vue/client.js +5 -0
- package/dist/runtime/better-auth/vue/cross-domain.d.ts +39 -0
- package/dist/runtime/better-auth/vue/cross-domain.js +45 -0
- package/dist/runtime/better-auth/vue/hydration.d.ts +20 -0
- package/dist/runtime/better-auth/vue/hydration.js +52 -0
- package/dist/runtime/better-auth/vue/index.d.ts +12 -0
- package/dist/runtime/better-auth/vue/index.js +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.d.ts +6 -0
- package/dist/runtime/better-auth/vue/plugin.client.js +32 -0
- package/dist/runtime/better-auth/vue/plugin.server.d.ts +24 -0
- package/dist/runtime/better-auth/vue/plugin.server.js +42 -0
- package/dist/runtime/better-auth/vue/use-auth.d.ts +92 -0
- package/dist/runtime/better-auth/vue/use-auth.js +82 -0
- package/dist/runtime/clerk/vue/index.d.ts +81 -0
- package/dist/runtime/clerk/vue/index.js +49 -0
- package/dist/runtime/devtools/bridge.d.ts +12 -0
- package/dist/runtime/devtools/bridge.js +191 -0
- package/dist/runtime/devtools/plugin.client.d.ts +8 -0
- package/dist/runtime/devtools/plugin.client.js +34 -0
- package/dist/runtime/devtools/types.d.ts +78 -0
- package/dist/runtime/devtools/types.js +0 -0
- package/dist/runtime/nuxt/composables/use-async-query.d.ts +144 -0
- package/dist/runtime/nuxt/composables/use-async-query.js +104 -0
- package/dist/runtime/nuxt/config.d.ts +7 -0
- package/dist/runtime/nuxt/config.js +12 -0
- package/dist/runtime/nuxt/index.d.ts +136 -0
- package/dist/runtime/nuxt/index.js +91 -0
- package/dist/runtime/polar/vue/components.d.ts +184 -0
- package/dist/runtime/polar/vue/components.js +115 -0
- package/dist/runtime/vue/auth/helpers.d.ts +67 -0
- package/dist/runtime/vue/auth/helpers.js +50 -0
- package/dist/runtime/vue/auth/index.d.ts +125 -0
- package/dist/runtime/vue/auth/index.js +97 -0
- package/dist/runtime/vue/client.d.ts +305 -0
- package/dist/runtime/vue/client.js +382 -0
- package/dist/runtime/vue/composables/use-action.d.ts +62 -0
- package/dist/runtime/vue/composables/use-action.js +13 -0
- package/dist/runtime/vue/composables/use-connection-state.d.ts +30 -0
- package/dist/runtime/vue/composables/use-connection-state.js +14 -0
- package/dist/runtime/vue/composables/use-mutation.d.ts +76 -0
- package/dist/runtime/vue/composables/use-mutation.js +34 -0
- package/dist/runtime/vue/composables/use-paginated-query.d.ts +132 -0
- package/dist/runtime/vue/composables/use-paginated-query.js +513 -0
- package/dist/runtime/vue/composables/use-queries.d.ts +62 -0
- package/dist/runtime/vue/composables/use-queries.js +37 -0
- package/dist/runtime/vue/composables/use-query.d.ts +87 -0
- package/dist/runtime/vue/composables/use-query.js +53 -0
- package/dist/runtime/vue/composables/use-storage-url.d.ts +46 -0
- package/dist/runtime/vue/composables/use-storage-url.js +9 -0
- package/dist/runtime/vue/composables/use-subscription.d.ts +15 -0
- package/dist/runtime/vue/composables/use-subscription.js +33 -0
- package/dist/runtime/vue/composables/use-upload-queue.d.ts +117 -0
- package/dist/runtime/vue/composables/use-upload-queue.js +115 -0
- package/dist/runtime/vue/composables/use-upload.d.ts +134 -0
- package/dist/runtime/vue/composables/use-upload.js +114 -0
- package/dist/runtime/vue/hydration.d.ts +66 -0
- package/dist/runtime/vue/hydration.js +46 -0
- package/dist/runtime/vue/index.d.ts +94 -0
- package/dist/runtime/vue/index.js +42 -0
- package/dist/runtime/vue/plugin.d.ts +18 -0
- package/dist/runtime/vue/plugin.js +16 -0
- package/dist/runtime/vue/provide.d.ts +39 -0
- package/dist/runtime/vue/provide.js +15 -0
- package/dist/runtime/vue/queries-observer.d.ts +41 -0
- package/dist/runtime/vue/queries-observer.js +126 -0
- package/dist/types.d.ts +3 -0
- package/package.json +233 -0
package/dist/module.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nuxt-convex-module",
|
|
3
|
+
"configKey": "convex",
|
|
4
|
+
"docs": "https://github.com/qruto/nuxt-convex-module#readme",
|
|
5
|
+
"compatibility": {
|
|
6
|
+
"nuxt": ">=4.1.0"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.0.0",
|
|
9
|
+
"builder": {
|
|
10
|
+
"@nuxt/module-builder": "1.0.3",
|
|
11
|
+
"unbuild": "unknown"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type PropType } from 'vue';
|
|
2
|
+
import { type ConvexAuthState, type IConvexVueClient } from '../../vue/auth/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Options for {@link provideConvexAuthFromAuth0} / `<ConvexProviderWithAuth0>`.
|
|
5
|
+
*/
|
|
6
|
+
export interface ConvexProviderWithAuth0Options {
|
|
7
|
+
/** Convex client to authenticate. Defaults to the provided {@link useConvex} client. */
|
|
8
|
+
client?: IConvexVueClient;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The composable form of {@link ConvexProviderWithAuth0} — provides a
|
|
12
|
+
* {@link ConvexVueClient} authenticated with Auth0.
|
|
13
|
+
*
|
|
14
|
+
* It must be called in an app wrapped by a configured Auth0 plugin from
|
|
15
|
+
* `@auth0/auth0-vue`.
|
|
16
|
+
*
|
|
17
|
+
* See [Convex Auth0](https://docs.convex.dev/auth/auth0) on how to set up
|
|
18
|
+
* Convex with Auth0.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```vue
|
|
22
|
+
* <script setup lang="ts">
|
|
23
|
+
* import { provideConvexAuthFromAuth0 } from 'nuxt-convex-module/auth0/client'
|
|
24
|
+
* provideConvexAuthFromAuth0()
|
|
25
|
+
* </script>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
export declare function provideConvexAuthFromAuth0(options?: ConvexProviderWithAuth0Options): ConvexAuthState;
|
|
31
|
+
/**
|
|
32
|
+
* A wrapper Vue component which provides a {@link ConvexVueClient}
|
|
33
|
+
* authenticated with Auth0 — the component form of
|
|
34
|
+
* {@link provideConvexAuthFromAuth0}. Renders its default slot once Convex
|
|
35
|
+
* auth is wired.
|
|
36
|
+
*
|
|
37
|
+
* It must be wrapped by a configured Auth0 plugin from `@auth0/auth0-vue`.
|
|
38
|
+
*
|
|
39
|
+
* See [Convex Auth0](https://docs.convex.dev/auth/auth0) on how to set up
|
|
40
|
+
* Convex with Auth0.
|
|
41
|
+
*
|
|
42
|
+
* @public
|
|
43
|
+
*/
|
|
44
|
+
export declare const ConvexProviderWithAuth0: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
45
|
+
client: {
|
|
46
|
+
type: PropType<IConvexVueClient>;
|
|
47
|
+
default: undefined;
|
|
48
|
+
};
|
|
49
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
}>[] | undefined, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
52
|
+
client: {
|
|
53
|
+
type: PropType<IConvexVueClient>;
|
|
54
|
+
default: undefined;
|
|
55
|
+
};
|
|
56
|
+
}>> & Readonly<{}>, {
|
|
57
|
+
client: IConvexVueClient;
|
|
58
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useAuth0 } from "@auth0/auth0-vue";
|
|
2
|
+
import { defineComponent } from "vue";
|
|
3
|
+
import { provideConvexAuth } from "../../vue/auth/index.js";
|
|
4
|
+
import { useConvexOrThrow } from "../../vue/client.js";
|
|
5
|
+
export function provideConvexAuthFromAuth0(options = {}) {
|
|
6
|
+
const client = options.client ?? useConvexOrThrow("provideConvexAuthFromAuth0");
|
|
7
|
+
return provideConvexAuth({ client, useAuth: useAuthFromAuth0 });
|
|
8
|
+
}
|
|
9
|
+
export const ConvexProviderWithAuth0 = defineComponent({
|
|
10
|
+
name: "ConvexProviderWithAuth0",
|
|
11
|
+
props: {
|
|
12
|
+
client: { type: Object, default: void 0 }
|
|
13
|
+
},
|
|
14
|
+
setup(props, { slots }) {
|
|
15
|
+
provideConvexAuthFromAuth0({ client: props.client });
|
|
16
|
+
return () => slots.default?.();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
function useAuthFromAuth0() {
|
|
20
|
+
const { isLoading, isAuthenticated, getAccessTokenSilently } = useAuth0();
|
|
21
|
+
const fetchAccessToken = async ({ forceRefreshToken }) => {
|
|
22
|
+
try {
|
|
23
|
+
const response = await getAccessTokenSilently({
|
|
24
|
+
detailedResponse: true,
|
|
25
|
+
cacheMode: forceRefreshToken ? "off" : "on"
|
|
26
|
+
});
|
|
27
|
+
return response.id_token;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return { isLoading, isAuthenticated, fetchAccessToken };
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth route middleware — protects pages from unauthenticated access.
|
|
3
|
+
*
|
|
4
|
+
* Usage in page:
|
|
5
|
+
* ```vue
|
|
6
|
+
* <script setup>
|
|
7
|
+
* definePageMeta({ middleware: 'auth' })
|
|
8
|
+
* </script>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
/** Exported for unit tests — `import.meta.server` is compile-time. @internal */
|
|
12
|
+
export declare function serverGuard(path: string): Promise<string | false | void | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric | import("vue-router").NavigationFailure>;
|
|
13
|
+
declare const _default: import("nuxt/app").RouteMiddleware;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp, useRequestEvent } from "#app";
|
|
2
|
+
import { watch } from "vue";
|
|
3
|
+
import { useAuth } from "../vue/use-auth.js";
|
|
4
|
+
import { convexAuth } from "./server.js";
|
|
5
|
+
export async function serverGuard(path) {
|
|
6
|
+
const event = useRequestEvent();
|
|
7
|
+
if (!event) return;
|
|
8
|
+
const nuxtApp = useNuxtApp();
|
|
9
|
+
const authed = await convexAuth(event).isAuthenticated();
|
|
10
|
+
if (!authed && path !== "/login") {
|
|
11
|
+
return nuxtApp.runWithContext(() => navigateTo("/login"));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function waitForSession(isPending) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const stop = watch(
|
|
17
|
+
isPending,
|
|
18
|
+
(pending) => {
|
|
19
|
+
if (!pending) {
|
|
20
|
+
stop();
|
|
21
|
+
resolve();
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{ immediate: true }
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
29
|
+
if (import.meta.server) {
|
|
30
|
+
return serverGuard(to.path);
|
|
31
|
+
}
|
|
32
|
+
const { session } = useAuth();
|
|
33
|
+
if (session.value.isPending) {
|
|
34
|
+
await waitForSession(() => session.value.isPending);
|
|
35
|
+
}
|
|
36
|
+
if (!session.value.data && to.path !== "/login") {
|
|
37
|
+
return navigateTo("/login");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { convexAuth } from "./server.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const config = useRuntimeConfig();
|
|
6
|
+
const siteUrl = config.convex.siteUrl;
|
|
7
|
+
if (!siteUrl) {
|
|
8
|
+
throw new Error("[nuxt-convex-module] NUXT_PUBLIC_CONVEX_SITE_URL is not configured. Auth proxy cannot forward requests.");
|
|
9
|
+
}
|
|
10
|
+
return convexAuth(event, { convexSiteUrl: siteUrl }).handler();
|
|
11
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side Better Auth + Convex integration for Nuxt.
|
|
3
|
+
*
|
|
4
|
+
* Provides {@link convexAuth} for Nuxt server handlers.
|
|
5
|
+
*
|
|
6
|
+
* It accepts the H3
|
|
7
|
+
* {@link H3Event} that is available in every Nuxt server route, API handler
|
|
8
|
+
* and server middleware.
|
|
9
|
+
*
|
|
10
|
+
* @module better-auth/server
|
|
11
|
+
*/
|
|
12
|
+
import type { H3Event } from 'h3';
|
|
13
|
+
import type { GetTokenOptions } from '@convex-dev/better-auth/utils';
|
|
14
|
+
import type { FunctionReference, FunctionReturnType } from 'convex/server';
|
|
15
|
+
import type { Preloaded } from '../../vue/hydration.js';
|
|
16
|
+
type OptionalArgs<FuncRef extends FunctionReference<any, any>> = FuncRef['_args'] extends Record<string, never> ? [args?: Record<string, never>] : [args: FuncRef['_args']];
|
|
17
|
+
/**
|
|
18
|
+
* Options for {@link convexAuth}.
|
|
19
|
+
*
|
|
20
|
+
* Extends {@link GetTokenOptions} from `@convex-dev/better-auth/utils` with
|
|
21
|
+
* an optional override for the Convex site URL.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
*/
|
|
25
|
+
export type ConvexAuthOptions = GetTokenOptions & {
|
|
26
|
+
/**
|
|
27
|
+
* Accepted for drop-in compatibility with upstream `convexBetterAuthNextJs`
|
|
28
|
+
* option objects and ignored — upstream requires but never reads it either;
|
|
29
|
+
* the deployment URL comes from runtime config.
|
|
30
|
+
*/
|
|
31
|
+
convexUrl?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Override the Convex site URL. Defaults to `NUXT_PUBLIC_CONVEX_SITE_URL`.
|
|
34
|
+
*/
|
|
35
|
+
convexSiteUrl?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Per-request Better Auth + Convex helper returned by {@link convexAuth}.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export interface ConvexAuthService {
|
|
43
|
+
/** Convex JWT for the current user, or `undefined` when not authenticated. */
|
|
44
|
+
getToken: () => Promise<string | undefined>;
|
|
45
|
+
/** Proxy a Better Auth route to the configured Convex site URL. */
|
|
46
|
+
handler: () => Promise<Response>;
|
|
47
|
+
/** `true` when the current request has a valid Convex auth token. */
|
|
48
|
+
isAuthenticated: () => Promise<boolean>;
|
|
49
|
+
/** Preload a Convex query with the current user's auth token. */
|
|
50
|
+
preloadAuthQuery: <Query extends FunctionReference<'query'>>(query: Query, ...args: OptionalArgs<Query>) => Promise<Preloaded<Query>>;
|
|
51
|
+
/** Execute a Convex query with the current user's auth token. */
|
|
52
|
+
fetchAuthQuery: <Query extends FunctionReference<'query'>>(query: Query, ...args: OptionalArgs<Query>) => Promise<FunctionReturnType<Query>>;
|
|
53
|
+
/** Execute a Convex mutation with the current user's auth token. */
|
|
54
|
+
fetchAuthMutation: <Mutation extends FunctionReference<'mutation'>>(mutation: Mutation, ...args: OptionalArgs<Mutation>) => Promise<FunctionReturnType<Mutation>>;
|
|
55
|
+
/** Execute a Convex action with the current user's auth token. */
|
|
56
|
+
fetchAuthAction: <Action extends FunctionReference<'action'>>(action: Action, ...args: OptionalArgs<Action>) => Promise<FunctionReturnType<Action>>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a per-request Better Auth + Convex helper for Nuxt server code.
|
|
60
|
+
*
|
|
61
|
+
* Adapts the Better Auth + Convex server helper pattern to H3 events.
|
|
62
|
+
*
|
|
63
|
+
* Call once at the top of a server route / event handler and destructure the
|
|
64
|
+
* helpers you need. The auth token is fetched at most once per request — the
|
|
65
|
+
* cache lives on `event.context`, so every `convexAuth(event)` instance for
|
|
66
|
+
* the same request (server plugin, middleware, your route) shares one Better
|
|
67
|
+
* Auth round-trip. Mirrors upstream's `React.cache`-wrapped `getToken`.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* // server/api/profile.get.ts
|
|
72
|
+
* export default defineEventHandler(async (event) => {
|
|
73
|
+
* const { fetchAuthQuery, isAuthenticated } = convexAuth(event)
|
|
74
|
+
* if (!await isAuthenticated()) {
|
|
75
|
+
* throw createError({ statusCode: 401 })
|
|
76
|
+
* }
|
|
77
|
+
* return fetchAuthQuery(api.users.current)
|
|
78
|
+
* })
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* // pages/profile.vue — preloading data for SSR
|
|
84
|
+
* const { data } = await useAsyncData(async () => {
|
|
85
|
+
* const event = useRequestEvent()!
|
|
86
|
+
* return convexAuth(event).preloadAuthQuery(api.users.current)
|
|
87
|
+
* })
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @param event - The H3 event from a Nuxt server route or middleware.
|
|
91
|
+
* @param opts - Optional {@link ConvexAuthOptions}.
|
|
92
|
+
*
|
|
93
|
+
* @public
|
|
94
|
+
*/
|
|
95
|
+
export declare function convexAuth(event: H3Event, opts?: ConvexAuthOptions): ConvexAuthService;
|
|
96
|
+
/**
|
|
97
|
+
* Upstream-named alias of {@link convexAuth} — the mechanical rename target
|
|
98
|
+
* for `@convex-dev/better-auth/nextjs`'s `convexBetterAuthNextJs` (framework
|
|
99
|
+
* suffix `NextJs` → `Nuxt`). Unlike upstream's module-level factory (which
|
|
100
|
+
* reads request state ambiently via `next/headers`), Nitro has no ambient
|
|
101
|
+
* per-request context, so the H3 `event` is passed explicitly.
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export declare const convexBetterAuthNuxt: typeof convexAuth;
|
|
106
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getRequestHeaders, toWebRequest } from "h3";
|
|
2
|
+
import { getToken } from "@convex-dev/better-auth/utils";
|
|
3
|
+
import { getConvexRuntimeConfig } from "../../nuxt/config.js";
|
|
4
|
+
import { fetchAction, fetchMutation, fetchQuery, preloadQuery } from "../../nuxt/index.js";
|
|
5
|
+
const parseConvexSiteUrl = (url) => {
|
|
6
|
+
if (!url) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"NUXT_PUBLIC_CONVEX_SITE_URL is not set.\nThis is automatically set in the Convex deployment, but must be set in the Nuxt environment.\nFor local development, this can be set in the .env file, or via the `convex.siteUrl` option in nuxt.config."
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
if (url.endsWith(".convex.cloud")) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`NUXT_PUBLIC_CONVEX_SITE_URL should be set to your Convex Site URL, which ends in .convex.site.
|
|
14
|
+
Currently set to ${url}.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return url;
|
|
18
|
+
};
|
|
19
|
+
const handler = async (request, siteUrl) => {
|
|
20
|
+
const requestUrl = new URL(request.url);
|
|
21
|
+
const nextUrl = `${siteUrl}${requestUrl.pathname}${requestUrl.search}`;
|
|
22
|
+
const headers = new Headers(request.headers);
|
|
23
|
+
headers.delete("transfer-encoding");
|
|
24
|
+
headers.delete("content-length");
|
|
25
|
+
headers.delete("connection");
|
|
26
|
+
headers.set("accept-encoding", "application/json");
|
|
27
|
+
headers.set("host", new URL(siteUrl).host);
|
|
28
|
+
headers.set("x-forwarded-host", requestUrl.host);
|
|
29
|
+
headers.set("x-forwarded-proto", requestUrl.protocol.replace(/:$/, ""));
|
|
30
|
+
headers.set("x-better-auth-forwarded-host", requestUrl.host);
|
|
31
|
+
headers.set("x-better-auth-forwarded-proto", requestUrl.protocol.replace(/:$/, ""));
|
|
32
|
+
const init = {
|
|
33
|
+
headers,
|
|
34
|
+
method: request.method,
|
|
35
|
+
redirect: "manual"
|
|
36
|
+
};
|
|
37
|
+
if (request.method !== "GET" && request.method !== "HEAD") {
|
|
38
|
+
const body = await request.arrayBuffer();
|
|
39
|
+
if (body.byteLength > 0) {
|
|
40
|
+
init.body = body;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(nextUrl, init);
|
|
44
|
+
const responseHeaders = new Headers(response.headers);
|
|
45
|
+
responseHeaders.delete("content-encoding");
|
|
46
|
+
responseHeaders.delete("content-length");
|
|
47
|
+
return new Response(response.body, {
|
|
48
|
+
status: response.status,
|
|
49
|
+
statusText: response.statusText,
|
|
50
|
+
headers: responseHeaders
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const nuxtHandler = (event, siteUrl) => () => handler(toWebRequest(event), siteUrl);
|
|
54
|
+
const getArgsAndOptions = (args, token) => {
|
|
55
|
+
return [args[0], { token }];
|
|
56
|
+
};
|
|
57
|
+
export function convexAuth(event, opts) {
|
|
58
|
+
const siteUrl = parseConvexSiteUrl(
|
|
59
|
+
opts?.convexSiteUrl ?? getConvexRuntimeConfig().siteUrl ?? process.env.NUXT_PUBLIC_CONVEX_SITE_URL
|
|
60
|
+
);
|
|
61
|
+
const cacheKey = `__nuxtConvexToken:${siteUrl}|${opts?.basePath ?? ""}`;
|
|
62
|
+
const tokenCache = event.context;
|
|
63
|
+
const cachedGetToken = ({ forceRefresh } = {}) => {
|
|
64
|
+
const cached = tokenCache[cacheKey];
|
|
65
|
+
if (cached && !forceRefresh) return cached;
|
|
66
|
+
const headers = getRequestHeaders(event);
|
|
67
|
+
const mutableHeaders = new Headers(headers);
|
|
68
|
+
mutableHeaders.delete("content-length");
|
|
69
|
+
mutableHeaders.delete("transfer-encoding");
|
|
70
|
+
mutableHeaders.set("accept-encoding", "identity");
|
|
71
|
+
const promise = getToken(siteUrl, mutableHeaders, { ...opts, forceRefresh });
|
|
72
|
+
tokenCache[cacheKey] = promise;
|
|
73
|
+
return promise;
|
|
74
|
+
};
|
|
75
|
+
const callWithToken = async (fn) => {
|
|
76
|
+
const token = await cachedGetToken();
|
|
77
|
+
try {
|
|
78
|
+
return await fn(token?.token);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (!opts?.jwtCache?.enabled || token.isFresh || !opts.jwtCache.isAuthError(error)) {
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
const newToken = await cachedGetToken({ forceRefresh: true });
|
|
84
|
+
return await fn(newToken.token);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
getToken: async () => {
|
|
89
|
+
const token = await cachedGetToken();
|
|
90
|
+
return token.token;
|
|
91
|
+
},
|
|
92
|
+
handler: nuxtHandler(event, siteUrl),
|
|
93
|
+
isAuthenticated: async () => {
|
|
94
|
+
const token = await cachedGetToken();
|
|
95
|
+
return !!token.token;
|
|
96
|
+
},
|
|
97
|
+
preloadAuthQuery: async (query, ...args) => {
|
|
98
|
+
return callWithToken((token) => {
|
|
99
|
+
const argsAndOptions = getArgsAndOptions(args, token);
|
|
100
|
+
return preloadQuery(query, ...argsAndOptions);
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
fetchAuthQuery: async (query, ...args) => {
|
|
104
|
+
return callWithToken((token) => {
|
|
105
|
+
const argsAndOptions = getArgsAndOptions(args, token);
|
|
106
|
+
return fetchQuery(query, ...argsAndOptions);
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
fetchAuthMutation: async (mutation, ...args) => {
|
|
110
|
+
return callWithToken((token) => {
|
|
111
|
+
const argsAndOptions = getArgsAndOptions(args, token);
|
|
112
|
+
return fetchMutation(mutation, ...argsAndOptions);
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
fetchAuthAction: async (action, ...args) => {
|
|
116
|
+
return callWithToken((token) => {
|
|
117
|
+
const argsAndOptions = getArgsAndOptions(args, token);
|
|
118
|
+
return fetchAction(action, ...argsAndOptions);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export const convexBetterAuthNuxt = convexAuth;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { type PropType, type VNode } from 'vue';
|
|
2
|
+
import type { FunctionReference } from 'convex/server';
|
|
3
|
+
import type { EmptyObject } from 'convex-helpers';
|
|
4
|
+
import type { AuthClient } from '#convex/auth-client';
|
|
5
|
+
/**
|
|
6
|
+
* _Experimental_
|
|
7
|
+
*
|
|
8
|
+
* A wrapper Vue component which provides error handling for auth related errors.
|
|
9
|
+
* This is typically used to redirect the user to the login page when they are
|
|
10
|
+
* unauthenticated, and does so reactively based on the getAuthUserFn query.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```vue
|
|
14
|
+
* <!--
|
|
15
|
+
* convex/auth.ts:
|
|
16
|
+
* export const { getAuthUser } = authComponent.clientApi();
|
|
17
|
+
* -->
|
|
18
|
+
* <script setup lang="ts">
|
|
19
|
+
* import { AuthBoundary, authClient } from '#imports'
|
|
20
|
+
* import { api } from '#convex/api'
|
|
21
|
+
* import { isAuthError } from '~/utils/auth'
|
|
22
|
+
* </script>
|
|
23
|
+
*
|
|
24
|
+
* <template>
|
|
25
|
+
* <AuthBoundary
|
|
26
|
+
* :on-unauth="() => navigateTo('/sign-in')"
|
|
27
|
+
* :auth-client="authClient"
|
|
28
|
+
* :get-auth-user-fn="api.auth.getAuthUser"
|
|
29
|
+
* :is-auth-error="isAuthError"
|
|
30
|
+
* >
|
|
31
|
+
* <slot />
|
|
32
|
+
* </AuthBoundary>
|
|
33
|
+
* </template>
|
|
34
|
+
* ```
|
|
35
|
+
* @param props.onUnauth - Function to call when the user is
|
|
36
|
+
* unauthenticated. Typically a redirect to the login page.
|
|
37
|
+
* @param props.authClient - Better Auth authClient to use.
|
|
38
|
+
* @param props.renderFallback - Fallback component to render when the user is
|
|
39
|
+
* unauthenticated. Defaults to null. Generally not rendered as error handling
|
|
40
|
+
* is typically a redirect.
|
|
41
|
+
* @param props.getAuthUserFn - Reference to a Convex query that returns user.
|
|
42
|
+
* The component provides a query for this via `export const { getAuthUser } = authComponent.clientApi()`.
|
|
43
|
+
* @param props.isAuthError - Function to check if the error is auth related.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export declare const AuthBoundary: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
48
|
+
/**
|
|
49
|
+
* The function to call when the user is unauthenticated. Typically a redirect
|
|
50
|
+
* to the login page.
|
|
51
|
+
*/
|
|
52
|
+
onUnauth: {
|
|
53
|
+
type: PropType<() => void | Promise<void>>;
|
|
54
|
+
required: true;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The Better Auth authClient to use.
|
|
58
|
+
*/
|
|
59
|
+
authClient: {
|
|
60
|
+
type: PropType<AuthClient>;
|
|
61
|
+
required: true;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The fallback to render when the user is unauthenticated. Defaults to null.
|
|
65
|
+
* Generally not rendered as error handling is typically a redirect.
|
|
66
|
+
*/
|
|
67
|
+
renderFallback: {
|
|
68
|
+
type: PropType<() => VNode | null>;
|
|
69
|
+
default: () => null;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* The function to call to get the auth user.
|
|
73
|
+
*/
|
|
74
|
+
getAuthUserFn: {
|
|
75
|
+
type: PropType<FunctionReference<"query", "public", EmptyObject>>;
|
|
76
|
+
required: true;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* The function to call to check if the error is auth related.
|
|
80
|
+
*/
|
|
81
|
+
isAuthError: {
|
|
82
|
+
type: PropType<(error: unknown) => boolean>;
|
|
83
|
+
required: true;
|
|
84
|
+
};
|
|
85
|
+
}>, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
86
|
+
[key: string]: any;
|
|
87
|
+
}> | (VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
88
|
+
[key: string]: any;
|
|
89
|
+
}> | VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
90
|
+
[key: string]: any;
|
|
91
|
+
}>[] | undefined)[] | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
92
|
+
/**
|
|
93
|
+
* The function to call when the user is unauthenticated. Typically a redirect
|
|
94
|
+
* to the login page.
|
|
95
|
+
*/
|
|
96
|
+
onUnauth: {
|
|
97
|
+
type: PropType<() => void | Promise<void>>;
|
|
98
|
+
required: true;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The Better Auth authClient to use.
|
|
102
|
+
*/
|
|
103
|
+
authClient: {
|
|
104
|
+
type: PropType<AuthClient>;
|
|
105
|
+
required: true;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* The fallback to render when the user is unauthenticated. Defaults to null.
|
|
109
|
+
* Generally not rendered as error handling is typically a redirect.
|
|
110
|
+
*/
|
|
111
|
+
renderFallback: {
|
|
112
|
+
type: PropType<() => VNode | null>;
|
|
113
|
+
default: () => null;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* The function to call to get the auth user.
|
|
117
|
+
*/
|
|
118
|
+
getAuthUserFn: {
|
|
119
|
+
type: PropType<FunctionReference<"query", "public", EmptyObject>>;
|
|
120
|
+
required: true;
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The function to call to check if the error is auth related.
|
|
124
|
+
*/
|
|
125
|
+
isAuthError: {
|
|
126
|
+
type: PropType<(error: unknown) => boolean>;
|
|
127
|
+
required: true;
|
|
128
|
+
};
|
|
129
|
+
}>> & Readonly<{}>, {
|
|
130
|
+
renderFallback: () => VNode | null;
|
|
131
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { defineComponent, h, onErrorCaptured, ref, watch } from "vue";
|
|
2
|
+
import { useConvexAuth } from "../../vue/auth/index.js";
|
|
3
|
+
import { Authenticated } from "../../vue/auth/helpers.js";
|
|
4
|
+
import { useQuery } from "../../vue/composables/use-query.js";
|
|
5
|
+
const UserSubscription = defineComponent({
|
|
6
|
+
name: "UserSubscription",
|
|
7
|
+
props: {
|
|
8
|
+
getAuthUserFn: { type: Object, required: true }
|
|
9
|
+
},
|
|
10
|
+
setup(props) {
|
|
11
|
+
const user = useQuery(props.getAuthUserFn);
|
|
12
|
+
return () => {
|
|
13
|
+
void user.value;
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
export const AuthBoundary = defineComponent({
|
|
19
|
+
name: "AuthBoundary",
|
|
20
|
+
props: {
|
|
21
|
+
/**
|
|
22
|
+
* The function to call when the user is unauthenticated. Typically a redirect
|
|
23
|
+
* to the login page.
|
|
24
|
+
*/
|
|
25
|
+
onUnauth: { type: Function, required: true },
|
|
26
|
+
/**
|
|
27
|
+
* The Better Auth authClient to use.
|
|
28
|
+
*/
|
|
29
|
+
authClient: { type: Object, required: true },
|
|
30
|
+
/**
|
|
31
|
+
* The fallback to render when the user is unauthenticated. Defaults to null.
|
|
32
|
+
* Generally not rendered as error handling is typically a redirect.
|
|
33
|
+
*/
|
|
34
|
+
renderFallback: { type: Function, default: () => null },
|
|
35
|
+
/**
|
|
36
|
+
* The function to call to get the auth user.
|
|
37
|
+
*/
|
|
38
|
+
getAuthUserFn: {
|
|
39
|
+
type: Object,
|
|
40
|
+
required: true
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* The function to call to check if the error is auth related.
|
|
44
|
+
*/
|
|
45
|
+
isAuthError: { type: Function, required: true }
|
|
46
|
+
},
|
|
47
|
+
setup(props, { slots }) {
|
|
48
|
+
const { isAuthenticated, isLoading } = useConvexAuth();
|
|
49
|
+
const handleUnauth = async () => {
|
|
50
|
+
await props.authClient.getSession();
|
|
51
|
+
await props.onUnauth();
|
|
52
|
+
};
|
|
53
|
+
watch([isLoading, isAuthenticated], () => {
|
|
54
|
+
void (async () => {
|
|
55
|
+
if (!isLoading.value && !isAuthenticated.value) {
|
|
56
|
+
await handleUnauth();
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
}, { immediate: true });
|
|
60
|
+
const error = ref();
|
|
61
|
+
onErrorCaptured((err) => {
|
|
62
|
+
error.value = err;
|
|
63
|
+
if (props.isAuthError(err)) {
|
|
64
|
+
void handleUnauth();
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
if (error.value && props.isAuthError(error.value)) {
|
|
70
|
+
return props.renderFallback?.();
|
|
71
|
+
}
|
|
72
|
+
return [
|
|
73
|
+
h(Authenticated, () => h(UserSubscription, { getAuthUserFn: props.getAuthUserFn })),
|
|
74
|
+
slots.default?.()
|
|
75
|
+
];
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|