use-convex 0.0.2
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 +2 -0
- package/README.md +321 -0
- package/dist/module.d.mts +97 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +232 -0
- package/dist/runtime/components/AuthLoading.d.vue.ts +13 -0
- package/dist/runtime/components/AuthLoading.vue +8 -0
- package/dist/runtime/components/AuthLoading.vue.d.ts +13 -0
- package/dist/runtime/components/AuthRefreshing.d.vue.ts +13 -0
- package/dist/runtime/components/AuthRefreshing.vue +8 -0
- package/dist/runtime/components/AuthRefreshing.vue.d.ts +13 -0
- package/dist/runtime/components/Authenticated.d.vue.ts +13 -0
- package/dist/runtime/components/Authenticated.vue +8 -0
- package/dist/runtime/components/Authenticated.vue.d.ts +13 -0
- package/dist/runtime/components/Unauthenticated.d.vue.ts +13 -0
- package/dist/runtime/components/Unauthenticated.vue +8 -0
- package/dist/runtime/components/Unauthenticated.vue.d.ts +13 -0
- package/dist/runtime/composables/prewarmQuery.d.ts +9 -0
- package/dist/runtime/composables/prewarmQuery.js +14 -0
- package/dist/runtime/composables/useAuth.d.ts +60 -0
- package/dist/runtime/composables/useAuth.js +426 -0
- package/dist/runtime/composables/useAuthToken.d.ts +24 -0
- package/dist/runtime/composables/useAuthToken.js +71 -0
- package/dist/runtime/composables/useConvex.d.ts +5 -0
- package/dist/runtime/composables/useConvex.js +10 -0
- package/dist/runtime/composables/useConvexAction.d.ts +13 -0
- package/dist/runtime/composables/useConvexAction.js +30 -0
- package/dist/runtime/composables/useConvexAuth.d.ts +51 -0
- package/dist/runtime/composables/useConvexAuth.js +96 -0
- package/dist/runtime/composables/useConvexConnectionState.d.ts +8 -0
- package/dist/runtime/composables/useConvexConnectionState.js +21 -0
- package/dist/runtime/composables/useConvexGate.d.ts +20 -0
- package/dist/runtime/composables/useConvexGate.js +21 -0
- package/dist/runtime/composables/useConvexMutation.d.ts +25 -0
- package/dist/runtime/composables/useConvexMutation.js +35 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.d.ts +43 -0
- package/dist/runtime/composables/useConvexPaginatedQuery.js +188 -0
- package/dist/runtime/composables/useConvexQueries.d.ts +27 -0
- package/dist/runtime/composables/useConvexQueries.js +84 -0
- package/dist/runtime/composables/useConvexQuery.d.ts +56 -0
- package/dist/runtime/composables/useConvexQuery.js +147 -0
- package/dist/runtime/plugin.auth.client.d.ts +8 -0
- package/dist/runtime/plugin.auth.client.js +41 -0
- package/dist/runtime/plugin.client.d.ts +7 -0
- package/dist/runtime/plugin.client.js +40 -0
- package/dist/runtime/plugin.devtools.client.d.ts +6 -0
- package/dist/runtime/plugin.devtools.client.js +16 -0
- package/dist/runtime/plugin.server.d.ts +7 -0
- package/dist/runtime/plugin.server.js +44 -0
- package/dist/runtime/server/api/convex/auth/session.d.ts +36 -0
- package/dist/runtime/server/api/convex/auth/session.js +95 -0
- package/dist/runtime/server/auth.d.ts +19 -0
- package/dist/runtime/server/auth.js +32 -0
- package/dist/runtime/server/authCookies.d.ts +9 -0
- package/dist/runtime/server/authCookies.js +86 -0
- package/dist/runtime/server/fetch.d.ts +43 -0
- package/dist/runtime/server/fetch.js +63 -0
- package/dist/runtime/server/routes/__convex_devtools.get.d.ts +6 -0
- package/dist/runtime/server/routes/__convex_devtools.get.js +32 -0
- package/dist/runtime/server/sameOrigin.d.ts +14 -0
- package/dist/runtime/server/sameOrigin.js +33 -0
- package/dist/runtime/utils/authCookie.d.ts +21 -0
- package/dist/runtime/utils/authCookie.js +47 -0
- package/dist/runtime/utils/authGate.d.ts +11 -0
- package/dist/runtime/utils/authGate.js +9 -0
- package/dist/runtime/utils/authMutex.d.ts +15 -0
- package/dist/runtime/utils/authMutex.js +47 -0
- package/dist/runtime/utils/authState.d.ts +31 -0
- package/dist/runtime/utils/authState.js +35 -0
- package/dist/runtime/utils/authStorage.d.ts +55 -0
- package/dist/runtime/utils/authStorage.js +85 -0
- package/dist/runtime/utils/context.d.ts +53 -0
- package/dist/runtime/utils/context.js +41 -0
- package/dist/runtime/utils/fetchToken.d.ts +9 -0
- package/dist/runtime/utils/fetchToken.js +6 -0
- package/dist/runtime/utils/http.d.ts +10 -0
- package/dist/runtime/utils/http.js +13 -0
- package/dist/runtime/utils/oauthRedirect.d.ts +5 -0
- package/dist/runtime/utils/oauthRedirect.js +12 -0
- package/dist/runtime/utils/overlay.d.ts +18 -0
- package/dist/runtime/utils/overlay.js +48 -0
- package/dist/runtime/utils/paginatedOptimistic.d.ts +37 -0
- package/dist/runtime/utils/paginatedOptimistic.js +178 -0
- package/dist/runtime/utils/queryKey.d.ts +8 -0
- package/dist/runtime/utils/queryKey.js +11 -0
- package/dist/types.d.mts +29 -0
- package/package.json +66 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
declare var __VLS_1: {};
|
|
2
|
+
type __VLS_Slots = {} & {
|
|
3
|
+
default?: (props: typeof __VLS_1) => any;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
6
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
7
|
+
declare const _default: typeof __VLS_export;
|
|
8
|
+
export default _default;
|
|
9
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
10
|
+
new (): {
|
|
11
|
+
$slots: S;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FunctionArgs, FunctionReference } from 'convex/server';
|
|
2
|
+
/**
|
|
3
|
+
* Start a live subscription early so the first `useConvexQuery` / screen
|
|
4
|
+
* paint can reuse warm client state (React `prewarmQuery` parity).
|
|
5
|
+
*
|
|
6
|
+
* Returns an unsubscribe function. When called during Vue `setup`, the
|
|
7
|
+
* subscription is also cleaned up with the current effect scope.
|
|
8
|
+
*/
|
|
9
|
+
export declare function prewarmQuery<Query extends FunctionReference<'query'>>(query: Query, args?: FunctionArgs<Query>): () => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { onScopeDispose } from "vue";
|
|
2
|
+
import { useConvex } from "./useConvex.js";
|
|
3
|
+
export function prewarmQuery(query, args = {}) {
|
|
4
|
+
const client = useConvex();
|
|
5
|
+
const unsubscribe = client.onUpdate(query, args, () => {
|
|
6
|
+
});
|
|
7
|
+
try {
|
|
8
|
+
onScopeDispose(() => {
|
|
9
|
+
unsubscribe();
|
|
10
|
+
});
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
return unsubscribe;
|
|
14
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type ComputedRef, type Ref } from 'vue';
|
|
2
|
+
export type AuthTokens = {
|
|
3
|
+
token: string;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
};
|
|
6
|
+
export type SignInResult = {
|
|
7
|
+
signingIn: boolean;
|
|
8
|
+
redirect?: URL;
|
|
9
|
+
};
|
|
10
|
+
export interface UseAuthReturn {
|
|
11
|
+
isLoading: ComputedRef<boolean>;
|
|
12
|
+
isAuthenticated: ComputedRef<boolean>;
|
|
13
|
+
isRefreshing: ComputedRef<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* JWT cookie is present. Use with `isAuthenticated` to keep an SSR-gated
|
|
16
|
+
* shell mounted while Convex confirms — do not live-subscribe on this alone.
|
|
17
|
+
*/
|
|
18
|
+
hasSsrSession: ComputedRef<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Mount the signed-in shell when the SSR cookie is present **or** Convex
|
|
21
|
+
* has confirmed auth. Prefer this over `isAuthenticated` alone.
|
|
22
|
+
* Live subscriptions must still gate on `isAuthenticated`.
|
|
23
|
+
*/
|
|
24
|
+
showAuthedUi: ComputedRef<boolean>;
|
|
25
|
+
error: Ref<string | null>;
|
|
26
|
+
pending: Ref<boolean>;
|
|
27
|
+
signIn: typeof signIn;
|
|
28
|
+
signOut: typeof signOut;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* First-party Convex Auth session state + actions.
|
|
32
|
+
*
|
|
33
|
+
* Only auto-imported when `convex.auth.provider === 'convex-auth'`.
|
|
34
|
+
* Wire-up happens in `plugin.auth.client` — call `signIn` / `signOut` from forms.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useAuth(): UseAuthReturn;
|
|
37
|
+
/**
|
|
38
|
+
* Sign in with a Convex Auth provider (password, OAuth, OTP, …).
|
|
39
|
+
* Mirrors `@convex-dev/auth/react` `useAuthActions().signIn`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function signIn(provider?: string, params?: FormData | Record<string, string>): Promise<SignInResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Sign out and clear local tokens + SSR JWT cookie.
|
|
44
|
+
*/
|
|
45
|
+
export declare function signOut(): Promise<void>;
|
|
46
|
+
/** @internal Used by the Convex Auth client plugin. */
|
|
47
|
+
export declare function hydrateAuthFromStorage(): void;
|
|
48
|
+
/** @internal Token fetcher for `useConvexAuth({ fetchToken })`. */
|
|
49
|
+
export declare function getAuthToken({ forceRefreshToken, }: {
|
|
50
|
+
forceRefreshToken: boolean;
|
|
51
|
+
}): Promise<string | null>;
|
|
52
|
+
/** @internal Sync check for OAuth `?code=` + stored verifier (no await). */
|
|
53
|
+
export declare function hasPendingOAuthCallback(): boolean;
|
|
54
|
+
/** @internal Used by the Convex Auth client plugin. */
|
|
55
|
+
export declare function consumeOAuthCodeFromUrl(): Promise<boolean>;
|
|
56
|
+
/** @internal Provider session flags for `useConvexAuth`. */
|
|
57
|
+
export declare function useAuthProviderState(): {
|
|
58
|
+
isLoading: ComputedRef<boolean>;
|
|
59
|
+
hasSession: ComputedRef<boolean>;
|
|
60
|
+
};
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
2
|
+
import { makeFunctionReference } from "convex/server";
|
|
3
|
+
import {
|
|
4
|
+
computed,
|
|
5
|
+
nextTick
|
|
6
|
+
} from "vue";
|
|
7
|
+
import {
|
|
8
|
+
useRuntimeConfig,
|
|
9
|
+
useState
|
|
10
|
+
} from "nuxt/app";
|
|
11
|
+
import { useConvexAuth } from "./useConvexAuth.js";
|
|
12
|
+
import { withRefreshMutex } from "../utils/authMutex.js";
|
|
13
|
+
import {
|
|
14
|
+
useAuthJwtCookie,
|
|
15
|
+
useAuthPresentCookie
|
|
16
|
+
} from "../utils/authCookie.js";
|
|
17
|
+
import {
|
|
18
|
+
flattenSignInParams,
|
|
19
|
+
isHttpOnlyAuth,
|
|
20
|
+
JWT_STORAGE_KEY,
|
|
21
|
+
readLocal,
|
|
22
|
+
REFRESH_TOKEN_STORAGE_KEY,
|
|
23
|
+
shouldConsumeOAuthCode,
|
|
24
|
+
storageKey,
|
|
25
|
+
VERIFIER_STORAGE_KEY,
|
|
26
|
+
writeLocal
|
|
27
|
+
} from "../utils/authStorage.js";
|
|
28
|
+
import { tryUseConvexContext } from "../utils/context.js";
|
|
29
|
+
import { parseOAuthRedirect } from "../utils/oauthRedirect.js";
|
|
30
|
+
const authSignIn = makeFunctionReference("auth:signIn");
|
|
31
|
+
const authSignOut = makeFunctionReference(
|
|
32
|
+
"auth:signOut"
|
|
33
|
+
);
|
|
34
|
+
const RETRY_BACKOFF = [500, 2e3];
|
|
35
|
+
const RETRY_JITTER = 100;
|
|
36
|
+
export function useAuth() {
|
|
37
|
+
const session = useAuthSession();
|
|
38
|
+
if (!tryUseConvexContext()) {
|
|
39
|
+
return {
|
|
40
|
+
isLoading: computed(() => session.isLoading.value),
|
|
41
|
+
isAuthenticated: computed(() => false),
|
|
42
|
+
isRefreshing: computed(() => false),
|
|
43
|
+
hasSsrSession: computed(() => false),
|
|
44
|
+
showAuthedUi: computed(() => false),
|
|
45
|
+
error: session.error,
|
|
46
|
+
pending: session.pending,
|
|
47
|
+
signIn,
|
|
48
|
+
signOut
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const convexAuth = useConvexAuth();
|
|
52
|
+
return {
|
|
53
|
+
isLoading: convexAuth.isLoading,
|
|
54
|
+
isAuthenticated: convexAuth.isAuthenticated,
|
|
55
|
+
isRefreshing: convexAuth.isRefreshing,
|
|
56
|
+
hasSsrSession: convexAuth.hasSsrSession,
|
|
57
|
+
showAuthedUi: convexAuth.showAuthedUi,
|
|
58
|
+
error: session.error,
|
|
59
|
+
pending: session.pending,
|
|
60
|
+
signIn,
|
|
61
|
+
signOut
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function signIn(provider, params) {
|
|
65
|
+
const session = useAuthSession();
|
|
66
|
+
const convexUrl = session.convexUrl;
|
|
67
|
+
if (!convexUrl) {
|
|
68
|
+
throw new Error("Convex URL is not configured");
|
|
69
|
+
}
|
|
70
|
+
session.pending.value = true;
|
|
71
|
+
session.error.value = null;
|
|
72
|
+
try {
|
|
73
|
+
const flatParams = flattenSignInParams(params);
|
|
74
|
+
const existingVerifier = readLocal(session.verifierKey.value) ?? void 0;
|
|
75
|
+
writeLocal(session.verifierKey.value, null);
|
|
76
|
+
const result = await callSignIn(convexUrl, {
|
|
77
|
+
provider,
|
|
78
|
+
params: flatParams,
|
|
79
|
+
verifier: existingVerifier
|
|
80
|
+
});
|
|
81
|
+
if (result.redirect !== void 0) {
|
|
82
|
+
const url = parseOAuthRedirect(result.redirect);
|
|
83
|
+
if (result.verifier) {
|
|
84
|
+
writeLocal(session.verifierKey.value, result.verifier);
|
|
85
|
+
}
|
|
86
|
+
if (import.meta.client) {
|
|
87
|
+
window.location.href = url.toString();
|
|
88
|
+
}
|
|
89
|
+
return { signingIn: false, redirect: url };
|
|
90
|
+
}
|
|
91
|
+
if (result.tokens !== void 0) {
|
|
92
|
+
await persistTokens(session, result.tokens);
|
|
93
|
+
return { signingIn: result.tokens !== null };
|
|
94
|
+
}
|
|
95
|
+
return { signingIn: false };
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
const message = cause instanceof Error ? cause.message : "Authentication failed";
|
|
98
|
+
session.error.value = message;
|
|
99
|
+
throw cause;
|
|
100
|
+
} finally {
|
|
101
|
+
session.pending.value = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export async function signOut() {
|
|
105
|
+
const session = useAuthSession();
|
|
106
|
+
const convexUrl = session.convexUrl;
|
|
107
|
+
session.pending.value = true;
|
|
108
|
+
session.error.value = null;
|
|
109
|
+
try {
|
|
110
|
+
if (convexUrl) {
|
|
111
|
+
const token = readLocal(session.jwtKey.value);
|
|
112
|
+
const http = new ConvexHttpClient(convexUrl);
|
|
113
|
+
if (token) {
|
|
114
|
+
http.setAuth(token);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await http.action(authSignOut, {});
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
await persistTokens(session, null);
|
|
122
|
+
} finally {
|
|
123
|
+
session.pending.value = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function hydrateAuthFromStorage() {
|
|
127
|
+
const session = useAuthSession();
|
|
128
|
+
if (!session.convexUrl) {
|
|
129
|
+
session.isLoading.value = false;
|
|
130
|
+
session.hasSession.value = false;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (session.httpOnly) {
|
|
134
|
+
const present = useAuthPresentCookie();
|
|
135
|
+
if (present?.value === "1") {
|
|
136
|
+
session.hasSession.value = true;
|
|
137
|
+
session.isLoading.value = false;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
session.isLoading.value = true;
|
|
141
|
+
void fetchAuthSession().then((result) => {
|
|
142
|
+
session.hasSession.value = result.hasSession;
|
|
143
|
+
session.isLoading.value = false;
|
|
144
|
+
if (result.hasSession) {
|
|
145
|
+
const marker = useAuthPresentCookie();
|
|
146
|
+
if (marker) {
|
|
147
|
+
marker.value = "1";
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const token = readLocal(session.jwtKey.value);
|
|
154
|
+
session.hasSession.value = token !== null;
|
|
155
|
+
session.isLoading.value = false;
|
|
156
|
+
if (token) {
|
|
157
|
+
setJwtCookie(token);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export async function getAuthToken({
|
|
161
|
+
forceRefreshToken
|
|
162
|
+
}) {
|
|
163
|
+
const session = useAuthSession();
|
|
164
|
+
const convexUrl = session.convexUrl;
|
|
165
|
+
if (!convexUrl) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
if (session.httpOnly) {
|
|
169
|
+
return withRefreshMutex(session.refreshKey.value, async () => {
|
|
170
|
+
if (!forceRefreshToken) {
|
|
171
|
+
const current = await fetchAuthSessionToken();
|
|
172
|
+
if (current.token) {
|
|
173
|
+
session.hasSession.value = true;
|
|
174
|
+
return current.token;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const refreshed = await refreshAuthSession();
|
|
178
|
+
if (!refreshed) {
|
|
179
|
+
await clearAuthSession();
|
|
180
|
+
session.hasSession.value = false;
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
session.hasSession.value = true;
|
|
184
|
+
return refreshed;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (!forceRefreshToken) {
|
|
188
|
+
return readLocal(session.jwtKey.value);
|
|
189
|
+
}
|
|
190
|
+
return withRefreshMutex(session.refreshKey.value, async () => {
|
|
191
|
+
const refreshToken = readLocal(session.refreshKey.value);
|
|
192
|
+
if (!refreshToken) {
|
|
193
|
+
persistTokens(session, null);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const result = await callSignInWithRetry(convexUrl, { refreshToken });
|
|
198
|
+
const tokens = result.tokens ?? null;
|
|
199
|
+
if (!tokens) {
|
|
200
|
+
persistTokens(session, null);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
persistTokens(session, tokens);
|
|
204
|
+
return tokens.token;
|
|
205
|
+
} catch {
|
|
206
|
+
persistTokens(session, null);
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export function hasPendingOAuthCallback() {
|
|
212
|
+
if (!import.meta.client) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
const session = useAuthSession();
|
|
216
|
+
if (!session.convexUrl) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const code = new URLSearchParams(window.location.search).get("code");
|
|
220
|
+
const verifier = readLocal(session.verifierKey.value);
|
|
221
|
+
return shouldConsumeOAuthCode({ code, verifier });
|
|
222
|
+
}
|
|
223
|
+
export async function consumeOAuthCodeFromUrl() {
|
|
224
|
+
if (!import.meta.client) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
const session = useAuthSession();
|
|
228
|
+
if (!session.convexUrl) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const code = new URLSearchParams(window.location.search).get("code");
|
|
232
|
+
const verifier = readLocal(session.verifierKey.value);
|
|
233
|
+
if (!shouldConsumeOAuthCode({ code, verifier })) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const url = new URL(window.location.href);
|
|
237
|
+
url.searchParams.delete("code");
|
|
238
|
+
window.history.replaceState({}, "", url.pathname + url.search + url.hash);
|
|
239
|
+
session.pending.value = true;
|
|
240
|
+
session.error.value = null;
|
|
241
|
+
session.isLoading.value = true;
|
|
242
|
+
try {
|
|
243
|
+
writeLocal(session.verifierKey.value, null);
|
|
244
|
+
const result = await callSignInWithRetry(session.convexUrl, {
|
|
245
|
+
params: { code },
|
|
246
|
+
verifier
|
|
247
|
+
});
|
|
248
|
+
await persistTokens(session, result.tokens ?? null);
|
|
249
|
+
return result.tokens != null;
|
|
250
|
+
} catch (cause) {
|
|
251
|
+
const message = cause instanceof Error ? cause.message : "OAuth callback failed";
|
|
252
|
+
session.error.value = message;
|
|
253
|
+
await persistTokens(session, null);
|
|
254
|
+
return false;
|
|
255
|
+
} finally {
|
|
256
|
+
session.pending.value = false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
export function useAuthProviderState() {
|
|
260
|
+
const session = useAuthSession();
|
|
261
|
+
return {
|
|
262
|
+
isLoading: computed(() => session.isLoading.value),
|
|
263
|
+
hasSession: computed(() => session.hasSession.value)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function useAuthSession() {
|
|
267
|
+
const config = useRuntimeConfig();
|
|
268
|
+
const convexConfig = config.public.convex;
|
|
269
|
+
const convexUrl = convexConfig?.url;
|
|
270
|
+
const httpOnly = isHttpOnlyAuth(convexConfig?.auth);
|
|
271
|
+
const isLoading = useState("convex-auth-loading", () => true);
|
|
272
|
+
const hasSession = useState("convex-auth-has-session", () => false);
|
|
273
|
+
const error = useState("convex-auth-error", () => null);
|
|
274
|
+
const pending = useState("convex-auth-pending", () => false);
|
|
275
|
+
const jwtKey = computed(
|
|
276
|
+
() => convexUrl ? storageKey(JWT_STORAGE_KEY, convexUrl) : JWT_STORAGE_KEY
|
|
277
|
+
);
|
|
278
|
+
const refreshKey = computed(
|
|
279
|
+
() => convexUrl ? storageKey(REFRESH_TOKEN_STORAGE_KEY, convexUrl) : REFRESH_TOKEN_STORAGE_KEY
|
|
280
|
+
);
|
|
281
|
+
const verifierKey = computed(
|
|
282
|
+
() => convexUrl ? storageKey(VERIFIER_STORAGE_KEY, convexUrl) : VERIFIER_STORAGE_KEY
|
|
283
|
+
);
|
|
284
|
+
return {
|
|
285
|
+
convexUrl,
|
|
286
|
+
httpOnly,
|
|
287
|
+
jwtKey,
|
|
288
|
+
refreshKey,
|
|
289
|
+
verifierKey,
|
|
290
|
+
isLoading,
|
|
291
|
+
hasSession,
|
|
292
|
+
error,
|
|
293
|
+
pending
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function setJwtCookie(token) {
|
|
297
|
+
const cookie = useAuthJwtCookie();
|
|
298
|
+
if (!cookie) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
cookie.value = token;
|
|
302
|
+
}
|
|
303
|
+
function setPresentCookie(value) {
|
|
304
|
+
const cookie = useAuthPresentCookie();
|
|
305
|
+
if (!cookie) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
cookie.value = value;
|
|
309
|
+
}
|
|
310
|
+
const AUTH_SESSION_PATH = "/api/convex/auth/session";
|
|
311
|
+
async function fetchAuthSession() {
|
|
312
|
+
try {
|
|
313
|
+
return await $fetch(AUTH_SESSION_PATH, {
|
|
314
|
+
method: "GET"
|
|
315
|
+
});
|
|
316
|
+
} catch {
|
|
317
|
+
return { hasSession: false };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async function fetchAuthSessionToken() {
|
|
321
|
+
try {
|
|
322
|
+
return await $fetch(
|
|
323
|
+
AUTH_SESSION_PATH,
|
|
324
|
+
{
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: { getToken: true }
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
} catch {
|
|
330
|
+
return { hasSession: false, token: null };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async function refreshAuthSession() {
|
|
334
|
+
try {
|
|
335
|
+
const result = await $fetch(AUTH_SESSION_PATH, {
|
|
336
|
+
method: "POST",
|
|
337
|
+
body: { refresh: true }
|
|
338
|
+
});
|
|
339
|
+
return result.token;
|
|
340
|
+
} catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function clearAuthSession() {
|
|
345
|
+
try {
|
|
346
|
+
await $fetch(AUTH_SESSION_PATH, { method: "DELETE" });
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
setPresentCookie(null);
|
|
350
|
+
}
|
|
351
|
+
async function writeHttpOnlySession(tokens) {
|
|
352
|
+
if (tokens === null) {
|
|
353
|
+
await clearAuthSession();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
await $fetch(AUTH_SESSION_PATH, {
|
|
357
|
+
method: "POST",
|
|
358
|
+
body: {
|
|
359
|
+
token: tokens.token,
|
|
360
|
+
refreshToken: tokens.refreshToken
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
setPresentCookie("1");
|
|
364
|
+
}
|
|
365
|
+
async function persistTokens(session, tokens) {
|
|
366
|
+
if (session.httpOnly) {
|
|
367
|
+
if (tokens === null) {
|
|
368
|
+
await writeHttpOnlySession(null);
|
|
369
|
+
writeLocal(session.jwtKey.value, null);
|
|
370
|
+
writeLocal(session.refreshKey.value, null);
|
|
371
|
+
session.hasSession.value = false;
|
|
372
|
+
session.isLoading.value = false;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
session.hasSession.value = true;
|
|
376
|
+
session.isLoading.value = false;
|
|
377
|
+
await writeHttpOnlySession(tokens);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (tokens === null) {
|
|
381
|
+
writeLocal(session.jwtKey.value, null);
|
|
382
|
+
writeLocal(session.refreshKey.value, null);
|
|
383
|
+
setJwtCookie(null);
|
|
384
|
+
session.hasSession.value = false;
|
|
385
|
+
session.isLoading.value = false;
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
writeLocal(session.jwtKey.value, tokens.token);
|
|
389
|
+
writeLocal(session.refreshKey.value, tokens.refreshToken);
|
|
390
|
+
session.hasSession.value = true;
|
|
391
|
+
session.isLoading.value = false;
|
|
392
|
+
nextTick(() => {
|
|
393
|
+
setJwtCookie(tokens.token);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
async function callSignIn(convexUrl, args) {
|
|
397
|
+
const http = new ConvexHttpClient(convexUrl);
|
|
398
|
+
return await http.action(authSignIn, args);
|
|
399
|
+
}
|
|
400
|
+
async function callSignInWithRetry(convexUrl, args) {
|
|
401
|
+
let lastError;
|
|
402
|
+
let retry = 0;
|
|
403
|
+
while (retry < RETRY_BACKOFF.length) {
|
|
404
|
+
try {
|
|
405
|
+
return await callSignIn(convexUrl, args);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
lastError = e;
|
|
408
|
+
if (!isNetworkError(e)) {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
const wait = RETRY_BACKOFF[retry] + RETRY_JITTER * Math.random();
|
|
412
|
+
retry++;
|
|
413
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
throw lastError;
|
|
417
|
+
}
|
|
418
|
+
function isNetworkError(error) {
|
|
419
|
+
if (error instanceof TypeError) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
if (error instanceof Error && /network|fetch|Failed to fetch/i.test(error.message)) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { navigateTo } from 'nuxt/app';
|
|
2
|
+
import { type ComputedRef } from 'vue';
|
|
3
|
+
/**
|
|
4
|
+
* Current JWT for authenticated HTTP calls (React `@convex-dev/auth`
|
|
5
|
+
* `useAuthToken` parity).
|
|
6
|
+
*
|
|
7
|
+
* Prefers `ConvexClient.getAuth()`, then the configured `fetchToken`
|
|
8
|
+
* (Convex Auth / BYO). Returns `null` when signed out or unavailable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useAuthToken(): ComputedRef<string | null>;
|
|
11
|
+
/**
|
|
12
|
+
* Nuxt route middleware helper — redirect when the user is signed out.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // middleware/auth.ts
|
|
17
|
+
* export default defineNuxtRouteMiddleware(() => {
|
|
18
|
+
* return requireConvexAuthMiddleware({ redirectTo: '/login' })
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function requireConvexAuthMiddleware(options?: {
|
|
23
|
+
redirectTo?: string;
|
|
24
|
+
}): ReturnType<typeof navigateTo> | void;
|