@onmax/nuxt-better-auth 0.0.2-alpha.3 → 0.0.2-alpha.31
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/README.md +1 -1
- package/dist/module.d.mts +28 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +965 -313
- package/dist/runtime/app/components/BetterAuthState.vue +1 -0
- package/dist/runtime/app/composables/useAction.d.ts +2 -0
- package/dist/runtime/app/composables/useAction.js +6 -0
- package/dist/runtime/app/composables/useAuthAsyncData.d.ts +6 -0
- package/dist/runtime/app/composables/useAuthAsyncData.js +16 -0
- package/dist/runtime/app/composables/useAuthClientAction.d.ts +5 -0
- package/dist/runtime/app/composables/useAuthClientAction.js +15 -0
- package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +11 -0
- package/dist/runtime/app/composables/useAuthRequestFetch.js +4 -0
- package/dist/runtime/app/composables/useSignIn.d.ts +16 -0
- package/dist/runtime/app/composables/useSignIn.js +8 -0
- package/dist/runtime/app/composables/useSignUp.d.ts +5 -0
- package/dist/runtime/app/composables/useSignUp.js +8 -0
- package/dist/runtime/app/composables/useUserSession.d.ts +14 -12
- package/dist/runtime/app/composables/useUserSession.js +162 -67
- package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
- package/dist/runtime/app/internal/auth-action-error.js +33 -0
- package/dist/runtime/app/internal/auth-action-handles.d.ts +18 -0
- package/dist/runtime/app/internal/auth-action-handles.js +105 -0
- package/dist/runtime/app/internal/redirect-helpers.d.ts +4 -0
- package/dist/runtime/app/internal/redirect-helpers.js +37 -0
- package/dist/runtime/app/internal/session-fetch.d.ts +12 -0
- package/dist/runtime/app/internal/session-fetch.js +56 -0
- package/dist/runtime/app/internal/utils.d.ts +1 -0
- package/dist/runtime/app/internal/utils.js +3 -0
- package/dist/runtime/app/internal/wrap-auth-method.d.ts +15 -0
- package/dist/runtime/app/internal/wrap-auth-method.js +66 -0
- package/dist/runtime/app/middleware/auth.global.js +73 -12
- package/dist/runtime/app/pages/__better-auth-devtools.vue +4 -10
- package/dist/runtime/app/plugins/session.client.js +1 -2
- package/dist/runtime/config.d.ts +66 -11
- package/dist/runtime/config.js +9 -2
- package/dist/runtime/server/api/_better-auth/_schema.js +2 -1
- package/dist/runtime/server/api/_better-auth/accounts.get.js +3 -2
- package/dist/runtime/server/api/_better-auth/config.get.d.ts +17 -11
- package/dist/runtime/server/api/_better-auth/config.get.js +17 -5
- package/dist/runtime/server/api/_better-auth/sessions.delete.js +3 -2
- package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +3 -1
- package/dist/runtime/server/api/_better-auth/sessions.get.js +3 -2
- package/dist/runtime/server/api/_better-auth/users.get.js +3 -2
- package/dist/runtime/server/api/auth/[...all].js +1 -1
- package/dist/runtime/server/middleware/route-access.js +1 -0
- package/dist/runtime/server/tsconfig.json +9 -1
- package/dist/runtime/server/utils/auth.d.ts +5 -7
- package/dist/runtime/server/utils/auth.js +197 -17
- package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
- package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
- package/dist/runtime/server/utils/session.d.ts +4 -8
- package/dist/runtime/server/utils/session.js +43 -4
- package/dist/runtime/server/virtual-modules.d.ts +22 -0
- package/dist/runtime/types/augment.d.ts +18 -2
- package/dist/runtime/types.d.ts +12 -13
- package/dist/types.d.mts +1 -1
- package/package.json +32 -42
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AsyncDataOptions } from '#app';
|
|
2
|
+
import { useAuthRequestFetch } from './useAuthRequestFetch.js';
|
|
3
|
+
export interface UseAuthAsyncDataOptions<T> extends Omit<AsyncDataOptions<T | null>, 'default'> {
|
|
4
|
+
requireAuth?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function useAuthAsyncData<T>(key: string, fetcher: (requestFetch: ReturnType<typeof useAuthRequestFetch>) => Promise<T>, options?: UseAuthAsyncDataOptions<T>): any;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useAsyncData } from "#imports";
|
|
2
|
+
import { useAuthRequestFetch } from "./useAuthRequestFetch.js";
|
|
3
|
+
import { useUserSession } from "./useUserSession.js";
|
|
4
|
+
export function useAuthAsyncData(key, fetcher, options = {}) {
|
|
5
|
+
const requestFetch = useAuthRequestFetch();
|
|
6
|
+
const { loggedIn } = useUserSession();
|
|
7
|
+
const { requireAuth = true, ...asyncDataOptions } = options;
|
|
8
|
+
return useAsyncData(key, async () => {
|
|
9
|
+
if (requireAuth && !loggedIn.value)
|
|
10
|
+
return null;
|
|
11
|
+
return await fetcher(requestFetch);
|
|
12
|
+
}, {
|
|
13
|
+
default: () => null,
|
|
14
|
+
...asyncDataOptions
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UserAuthActionHandle } from '../internal/auth-action-handles.js';
|
|
2
|
+
import type { UseUserSessionReturn } from './useUserSession.js';
|
|
3
|
+
type AppAuthClient = NonNullable<UseUserSessionReturn['client']>;
|
|
4
|
+
export declare function useAuthClientAction<TArgs extends unknown[], TResult>(select: (client: AppAuthClient) => (...args: TArgs) => Promise<TResult>): UserAuthActionHandle<TArgs, TResult>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useUserSession } from "#imports";
|
|
2
|
+
import { useAction } from "./useAction.js";
|
|
3
|
+
export function useAuthClientAction(select) {
|
|
4
|
+
if (typeof select !== "function")
|
|
5
|
+
throw new TypeError("useAuthClientAction(select) requires a selector function");
|
|
6
|
+
return useAction(async (...args) => {
|
|
7
|
+
const { client } = useUserSession();
|
|
8
|
+
if (!client)
|
|
9
|
+
throw new Error("Auth client is unavailable. This action can only run on client-side.");
|
|
10
|
+
const method = select(client);
|
|
11
|
+
if (typeof method !== "function")
|
|
12
|
+
throw new TypeError("useAuthClientAction(select) must resolve to a function");
|
|
13
|
+
return method(...args);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AuthApiEndpointMethod, AuthApiEndpointPath, AuthApiEndpointResponse } from '#nuxt-better-auth';
|
|
2
|
+
import type { NitroFetchOptions } from 'nitropack/types';
|
|
3
|
+
import { useRequestFetch } from '#imports';
|
|
4
|
+
type AuthRequestFetchExtractedMethod<Options> = Options extends undefined ? 'get' : Lowercase<Extract<Exclude<Options extends {
|
|
5
|
+
method?: infer Method;
|
|
6
|
+
} ? Method : never, undefined>, string>> extends infer NormalizedMethod extends string ? NormalizedMethod : 'get';
|
|
7
|
+
type AuthRequestFetchMethodFromOptions<Path extends AuthApiEndpointPath, Options> = NitroFetchOptions<Path> extends Options ? 'get' : AuthRequestFetchExtractedMethod<Options>;
|
|
8
|
+
type AuthRequestFetchResolvedMethod<Path extends AuthApiEndpointPath, Options> = Extract<AuthRequestFetchMethodFromOptions<Path, Options>, AuthApiEndpointMethod<Path>> extends infer Method extends string ? Method : never;
|
|
9
|
+
type AuthRequestFetch = <Path extends AuthApiEndpointPath, Options extends NitroFetchOptions<Path> = NitroFetchOptions<Path>>(request: Path, opts?: Options) => Promise<AuthApiEndpointResponse<Path, Extract<AuthRequestFetchResolvedMethod<Path, Options>, AuthApiEndpointMethod<Path>>>>;
|
|
10
|
+
export declare function useAuthRequestFetch(): AuthRequestFetch & ReturnType<typeof useRequestFetch>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AppAuthClient, AuthSocialProviderRegistry } from '#nuxt-better-auth';
|
|
2
|
+
import type { ActionHandleFor } from '../internal/auth-action-handles.js';
|
|
3
|
+
type SignIn = NonNullable<AppAuthClient>['signIn'];
|
|
4
|
+
type AuthSocialProviderId = AuthSocialProviderRegistry extends {
|
|
5
|
+
ids: infer T;
|
|
6
|
+
} ? Extract<T, string> : never;
|
|
7
|
+
type TypedSocialMethod<Method> = Method extends (data: infer Data, ...rest: infer Rest) => Promise<infer Result> ? (data: Omit<Extract<Data, Record<string, unknown>>, 'provider'> & {
|
|
8
|
+
provider: AuthSocialProviderId;
|
|
9
|
+
}, ...rest: Rest) => Promise<Result> : Method;
|
|
10
|
+
type SignInWithTypedSocial = Omit<SignIn, 'social'> & (SignIn extends {
|
|
11
|
+
social: infer SocialMethod;
|
|
12
|
+
} ? {
|
|
13
|
+
social: TypedSocialMethod<SocialMethod>;
|
|
14
|
+
} : unknown);
|
|
15
|
+
export declare function useSignIn<MethodKey extends keyof SignInWithTypedSocial>(method: MethodKey): ActionHandleFor<SignInWithTypedSocial[MethodKey]>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useUserSession } from "#imports";
|
|
2
|
+
import { createActionHandles } from "../internal/auth-action-handles.js";
|
|
3
|
+
export function useSignIn(method) {
|
|
4
|
+
if (method === void 0 || method === null)
|
|
5
|
+
throw new TypeError("useSignIn(method) requires a sign-in method key");
|
|
6
|
+
const handles = createActionHandles(() => useUserSession().signIn, "signIn");
|
|
7
|
+
return handles[method];
|
|
8
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppAuthClient } from '#nuxt-better-auth';
|
|
2
|
+
import type { ActionHandleFor } from '../internal/auth-action-handles.js';
|
|
3
|
+
type SignUp = NonNullable<AppAuthClient>['signUp'];
|
|
4
|
+
export declare function useSignUp<MethodKey extends keyof SignUp>(method: MethodKey): ActionHandleFor<SignUp[MethodKey]>;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useUserSession } from "#imports";
|
|
2
|
+
import { createActionHandles } from "../internal/auth-action-handles.js";
|
|
3
|
+
export function useSignUp(method) {
|
|
4
|
+
if (method === void 0 || method === null)
|
|
5
|
+
throw new TypeError("useSignUp(method) requires a sign-up method key");
|
|
6
|
+
const handles = createActionHandles(() => useUserSession().signUp, "signUp");
|
|
7
|
+
return handles[method];
|
|
8
|
+
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import type { AuthUser } from '#nuxt-better-auth';
|
|
1
|
+
import type { AppAuthClient, AuthSession, AuthUser } from '#nuxt-better-auth';
|
|
2
|
+
import type { ComputedRef, Ref } from 'vue';
|
|
2
3
|
export interface SignOutOptions {
|
|
3
4
|
onSuccess?: () => void | Promise<void>;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
-
client:
|
|
7
|
-
session:
|
|
8
|
-
user:
|
|
9
|
-
loggedIn:
|
|
10
|
-
ready:
|
|
11
|
-
signIn:
|
|
12
|
-
signUp:
|
|
13
|
-
signOut: (options?: SignOutOptions) => Promise<
|
|
6
|
+
export interface UseUserSessionReturn {
|
|
7
|
+
client: AppAuthClient | null;
|
|
8
|
+
session: Ref<AuthSession | null>;
|
|
9
|
+
user: Ref<AuthUser | null>;
|
|
10
|
+
loggedIn: ComputedRef<boolean>;
|
|
11
|
+
ready: ComputedRef<boolean>;
|
|
12
|
+
signIn: NonNullable<AppAuthClient>['signIn'];
|
|
13
|
+
signUp: NonNullable<AppAuthClient>['signUp'];
|
|
14
|
+
signOut: (options?: SignOutOptions) => Promise<void>;
|
|
14
15
|
waitForSession: () => Promise<void>;
|
|
15
16
|
fetchSession: (options?: {
|
|
16
17
|
headers?: HeadersInit;
|
|
17
18
|
force?: boolean;
|
|
18
19
|
}) => Promise<void>;
|
|
19
|
-
updateUser: (updates: Partial<AuthUser>) => void
|
|
20
|
-
}
|
|
20
|
+
updateUser: (updates: Partial<AuthUser>) => Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export declare function useUserSession(): UseUserSessionReturn;
|
|
@@ -1,41 +1,152 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { computed, nextTick,
|
|
1
|
+
import createAppAuthClient from "#auth/client";
|
|
2
|
+
import { computed, navigateTo, nextTick, useNuxtApp, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
|
+
import { normalizeAuthActionError } from "../internal/auth-action-error.js";
|
|
4
|
+
import { resolvePostAuthSuccessRedirect, withFallbackSocialCallbackURL } from "../internal/redirect-helpers.js";
|
|
5
|
+
import { fetchSessionClient, fetchSessionServer, stripToken } from "../internal/session-fetch.js";
|
|
6
|
+
import { isRecord } from "../internal/utils.js";
|
|
7
|
+
import { wrapAuthMethod } from "../internal/wrap-auth-method.js";
|
|
8
|
+
let _sessionSignalListenerBound = false;
|
|
9
|
+
let _signOutPromise = null;
|
|
3
10
|
let _client = null;
|
|
4
11
|
function getClient(baseURL) {
|
|
5
12
|
if (!_client)
|
|
6
13
|
_client = createAppAuthClient(baseURL);
|
|
7
14
|
return _client;
|
|
8
15
|
}
|
|
16
|
+
function getRuntimeFlags() {
|
|
17
|
+
const globalFlags = globalThis.__NUXT_BETTER_AUTH_TEST_FLAGS__;
|
|
18
|
+
if (globalFlags)
|
|
19
|
+
return globalFlags;
|
|
20
|
+
return { client: Boolean(import.meta.client), server: Boolean(import.meta.server) };
|
|
21
|
+
}
|
|
22
|
+
function ensureSessionSignalListener(client, onSignal) {
|
|
23
|
+
if (_sessionSignalListenerBound)
|
|
24
|
+
return;
|
|
25
|
+
const store = client.$store;
|
|
26
|
+
if (!isRecord(store))
|
|
27
|
+
return;
|
|
28
|
+
const listen = store.listen;
|
|
29
|
+
if (typeof listen !== "function")
|
|
30
|
+
return;
|
|
31
|
+
_sessionSignalListenerBound = true;
|
|
32
|
+
const listenFn = listen;
|
|
33
|
+
listenFn("$sessionSignal", async () => {
|
|
34
|
+
try {
|
|
35
|
+
await onSignal();
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
9
40
|
export function useUserSession() {
|
|
41
|
+
const runtimeFlags = getRuntimeFlags();
|
|
10
42
|
const runtimeConfig = useRuntimeConfig();
|
|
11
43
|
const requestURL = useRequestURL();
|
|
12
|
-
const
|
|
44
|
+
const nuxtApp = useNuxtApp();
|
|
45
|
+
const client = runtimeFlags.client ? getClient(runtimeConfig.public.siteUrl || requestURL.origin) : null;
|
|
13
46
|
const session = useState("auth:session", () => null);
|
|
14
47
|
const user = useState("auth:user", () => null);
|
|
15
48
|
const authReady = useState("auth:ready", () => false);
|
|
49
|
+
const prerenderReadyResetQueued = useState("auth:prerender-ready-reset-queued", () => false);
|
|
50
|
+
const hydrationReconcileQueued = useState("auth:hydration-reconcile-queued", () => false);
|
|
16
51
|
const ready = computed(() => authReady.value);
|
|
17
52
|
const loggedIn = computed(() => Boolean(session.value && user.value));
|
|
53
|
+
const isPrerenderedPayload = computed(() => Boolean(nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached));
|
|
54
|
+
const isPrerenderHydrationEmptySnapshot = computed(() => {
|
|
55
|
+
if (!runtimeFlags.client)
|
|
56
|
+
return false;
|
|
57
|
+
if (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered || !isPrerenderedPayload.value)
|
|
58
|
+
return false;
|
|
59
|
+
return !session.value && !user.value;
|
|
60
|
+
});
|
|
61
|
+
const skipHydratedSsrGetSession = computed(() => {
|
|
62
|
+
const authConfig = runtimeConfig.public.auth;
|
|
63
|
+
return Boolean(authConfig?.session?.skipHydratedSsrGetSession);
|
|
64
|
+
});
|
|
65
|
+
const shouldSkipInitialClientSessionFetch = computed(() => {
|
|
66
|
+
if (!skipHydratedSsrGetSession.value)
|
|
67
|
+
return false;
|
|
68
|
+
if (!runtimeFlags.client)
|
|
69
|
+
return false;
|
|
70
|
+
if (!nuxtApp.payload.serverRendered)
|
|
71
|
+
return false;
|
|
72
|
+
if (isPrerenderedPayload.value)
|
|
73
|
+
return false;
|
|
74
|
+
return Boolean(session.value && user.value);
|
|
75
|
+
});
|
|
76
|
+
if (isPrerenderHydrationEmptySnapshot.value && authReady.value && !prerenderReadyResetQueued.value) {
|
|
77
|
+
prerenderReadyResetQueued.value = true;
|
|
78
|
+
nuxtApp.hook("app:suspense:resolve", () => {
|
|
79
|
+
try {
|
|
80
|
+
if (!session.value && !user.value && authReady.value)
|
|
81
|
+
authReady.value = false;
|
|
82
|
+
} finally {
|
|
83
|
+
prerenderReadyResetQueued.value = false;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (shouldSkipInitialClientSessionFetch.value && !authReady.value)
|
|
88
|
+
authReady.value = true;
|
|
18
89
|
function clearSession() {
|
|
19
90
|
session.value = null;
|
|
20
91
|
user.value = null;
|
|
21
92
|
}
|
|
22
|
-
function
|
|
23
|
-
if (
|
|
24
|
-
|
|
93
|
+
async function fetchSession(options = {}) {
|
|
94
|
+
if (runtimeFlags.server)
|
|
95
|
+
return fetchSessionServer(session, user, authReady, options);
|
|
96
|
+
if (client)
|
|
97
|
+
return fetchSessionClient(client, session, user, authReady, options);
|
|
98
|
+
}
|
|
99
|
+
async function updateUser(updates) {
|
|
100
|
+
if (!user.value)
|
|
101
|
+
return;
|
|
102
|
+
const previousUser = user.value;
|
|
103
|
+
user.value = { ...user.value, ...updates };
|
|
104
|
+
if (!client)
|
|
105
|
+
return;
|
|
106
|
+
try {
|
|
107
|
+
const clientWithUpdateUser = client;
|
|
108
|
+
const result = await clientWithUpdateUser.updateUser(updates);
|
|
109
|
+
if (result?.error) {
|
|
110
|
+
if (result.error instanceof Error)
|
|
111
|
+
throw result.error;
|
|
112
|
+
const normalizedError = normalizeAuthActionError(result.error);
|
|
113
|
+
throw new Error(normalizedError.message);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
user.value = previousUser;
|
|
117
|
+
if (!(error instanceof Error)) {
|
|
118
|
+
const normalizedError = normalizeAuthActionError(error);
|
|
119
|
+
throw new Error(normalizedError.message);
|
|
120
|
+
}
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
25
123
|
}
|
|
26
|
-
if (
|
|
124
|
+
if (runtimeFlags.client && client && !shouldSkipInitialClientSessionFetch.value) {
|
|
27
125
|
const clientSession = client.useSession();
|
|
28
126
|
watch(
|
|
29
127
|
() => clientSession.value,
|
|
30
128
|
(newSession) => {
|
|
129
|
+
const shouldWaitForPrerenderResolution = isPrerenderHydrationEmptySnapshot.value && !newSession?.data?.session && !newSession?.data?.user;
|
|
130
|
+
if (shouldWaitForPrerenderResolution)
|
|
131
|
+
return;
|
|
31
132
|
if (newSession?.data?.session && newSession?.data?.user) {
|
|
32
|
-
|
|
33
|
-
session.value = safeSession;
|
|
133
|
+
session.value = stripToken(newSession.data.session);
|
|
34
134
|
user.value = newSession.data.user;
|
|
35
|
-
} else if (!newSession?.isPending) {
|
|
135
|
+
} else if (!newSession?.isPending && !newSession?.isRefetching) {
|
|
136
|
+
const isHydrationEmptySnapshot = nuxtApp.isHydrating && nuxtApp.payload.serverRendered && Boolean(session.value && user.value) && !newSession?.data?.session && !newSession?.data?.user;
|
|
137
|
+
if (isHydrationEmptySnapshot) {
|
|
138
|
+
if (!hydrationReconcileQueued.value) {
|
|
139
|
+
hydrationReconcileQueued.value = true;
|
|
140
|
+
nuxtApp.hook("app:mounted", async () => {
|
|
141
|
+
await fetchSession({ force: true });
|
|
142
|
+
hydrationReconcileQueued.value = false;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
36
147
|
clearSession();
|
|
37
148
|
}
|
|
38
|
-
if (!authReady.value && !newSession?.isPending)
|
|
149
|
+
if (!authReady.value && !newSession?.isPending && !newSession?.isRefetching)
|
|
39
150
|
authReady.value = true;
|
|
40
151
|
},
|
|
41
152
|
{ immediate: true, deep: true }
|
|
@@ -59,34 +170,28 @@ export function useUserSession() {
|
|
|
59
170
|
}, 5e3);
|
|
60
171
|
});
|
|
61
172
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await cb(ctx);
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
function wrapAuthMethod(method) {
|
|
72
|
-
return (async (...args) => {
|
|
73
|
-
const [data, options] = args;
|
|
74
|
-
if (data?.fetchOptions?.onSuccess) {
|
|
75
|
-
return method({ ...data, fetchOptions: { ...data.fetchOptions, onSuccess: wrapOnSuccess(data.fetchOptions.onSuccess) } }, options);
|
|
76
|
-
}
|
|
77
|
-
if (options?.onSuccess) {
|
|
78
|
-
return method(data, { ...options, onSuccess: wrapOnSuccess(options.onSuccess) });
|
|
79
|
-
}
|
|
80
|
-
return method(data, options);
|
|
81
|
-
});
|
|
82
|
-
}
|
|
173
|
+
const wrapDeps = {
|
|
174
|
+
fetchSession,
|
|
175
|
+
loggedIn,
|
|
176
|
+
waitForSession,
|
|
177
|
+
resolvePostAuthSuccessRedirect: () => resolvePostAuthSuccessRedirect(requestURL)
|
|
178
|
+
};
|
|
83
179
|
const signIn = client?.signIn ? new Proxy(client.signIn, {
|
|
84
180
|
get(target, prop) {
|
|
85
181
|
const targetRecord = target;
|
|
86
182
|
const method = targetRecord[prop];
|
|
87
183
|
if (typeof method !== "function")
|
|
88
184
|
return method;
|
|
89
|
-
|
|
185
|
+
const shouldSkipSessionSync = prop === "social" ? (data) => {
|
|
186
|
+
const socialData = isRecord(data) ? data : void 0;
|
|
187
|
+
return socialData?.disableRedirect !== true;
|
|
188
|
+
} : void 0;
|
|
189
|
+
const transformData = prop === "social" ? (data) => withFallbackSocialCallbackURL(data, requestURL) : void 0;
|
|
190
|
+
return wrapAuthMethod(
|
|
191
|
+
(...args) => targetRecord[prop](...args),
|
|
192
|
+
wrapDeps,
|
|
193
|
+
{ shouldSkipSessionSync, transformData }
|
|
194
|
+
);
|
|
90
195
|
}
|
|
91
196
|
}) : new Proxy({}, {
|
|
92
197
|
get: (_, prop) => {
|
|
@@ -99,50 +204,40 @@ export function useUserSession() {
|
|
|
99
204
|
const method = targetRecord[prop];
|
|
100
205
|
if (typeof method !== "function")
|
|
101
206
|
return method;
|
|
102
|
-
return wrapAuthMethod((...args) => targetRecord[prop](...args));
|
|
207
|
+
return wrapAuthMethod((...args) => targetRecord[prop](...args), wrapDeps);
|
|
103
208
|
}
|
|
104
209
|
}) : new Proxy({}, {
|
|
105
210
|
get: (_, prop) => {
|
|
106
211
|
throw new Error(`signUp.${String(prop)}() can only be called on client-side`);
|
|
107
212
|
}
|
|
108
213
|
});
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (!authReady.value)
|
|
112
|
-
authReady.value = true;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (client) {
|
|
116
|
-
try {
|
|
117
|
-
const headers = options.headers || useRequestHeaders(["cookie"]);
|
|
118
|
-
const fetchOptions = headers ? { headers } : void 0;
|
|
119
|
-
const query = options.force ? { disableCookieCache: true } : void 0;
|
|
120
|
-
const result = await client.getSession({ query }, fetchOptions);
|
|
121
|
-
const data = result.data;
|
|
122
|
-
if (data?.session && data?.user) {
|
|
123
|
-
const { token: _, ...safeSession } = data.session;
|
|
124
|
-
session.value = safeSession;
|
|
125
|
-
user.value = data.user;
|
|
126
|
-
} else {
|
|
127
|
-
clearSession();
|
|
128
|
-
}
|
|
129
|
-
} catch (error) {
|
|
130
|
-
clearSession();
|
|
131
|
-
console.error("[nuxt-better-auth] Failed to fetch session:", error);
|
|
132
|
-
} finally {
|
|
133
|
-
if (!authReady.value)
|
|
134
|
-
authReady.value = true;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
214
|
+
if (runtimeFlags.client && client && shouldSkipInitialClientSessionFetch.value) {
|
|
215
|
+
ensureSessionSignalListener(client, () => fetchSession({ force: true }));
|
|
137
216
|
}
|
|
138
217
|
async function signOut(options) {
|
|
139
218
|
if (!client)
|
|
140
219
|
throw new Error("signOut can only be called on client-side");
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
220
|
+
if (_signOutPromise) {
|
|
221
|
+
await _signOutPromise;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
_signOutPromise = (async () => {
|
|
225
|
+
await client.signOut();
|
|
226
|
+
clearSession();
|
|
227
|
+
if (options?.onSuccess) {
|
|
228
|
+
await options.onSuccess();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const authConfig = runtimeConfig.public.auth;
|
|
232
|
+
const logoutRedirect = authConfig?.redirects?.logout;
|
|
233
|
+
if (logoutRedirect) {
|
|
234
|
+
await nextTick();
|
|
235
|
+
await navigateTo(logoutRedirect);
|
|
236
|
+
}
|
|
237
|
+
})().finally(() => {
|
|
238
|
+
_signOutPromise = null;
|
|
239
|
+
});
|
|
240
|
+
await _signOutPromise;
|
|
146
241
|
}
|
|
147
242
|
return {
|
|
148
243
|
client,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isRecord } from "./utils.js";
|
|
2
|
+
export const DEFAULT_AUTH_ACTION_ERROR_MESSAGE = "Request failed. Please try again.";
|
|
3
|
+
function getMessage(value) {
|
|
4
|
+
if (value instanceof Error)
|
|
5
|
+
return value.message;
|
|
6
|
+
if (typeof value === "string")
|
|
7
|
+
return value;
|
|
8
|
+
if (isRecord(value) && typeof value.message === "string")
|
|
9
|
+
return value.message;
|
|
10
|
+
return DEFAULT_AUTH_ACTION_ERROR_MESSAGE;
|
|
11
|
+
}
|
|
12
|
+
function getCode(value) {
|
|
13
|
+
if (!isRecord(value))
|
|
14
|
+
return void 0;
|
|
15
|
+
return typeof value.code === "string" ? value.code : void 0;
|
|
16
|
+
}
|
|
17
|
+
function getStatus(value) {
|
|
18
|
+
if (!isRecord(value))
|
|
19
|
+
return void 0;
|
|
20
|
+
if (typeof value.status === "number")
|
|
21
|
+
return value.status;
|
|
22
|
+
if (typeof value.statusCode === "number")
|
|
23
|
+
return value.statusCode;
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
export function normalizeAuthActionError(error) {
|
|
27
|
+
return {
|
|
28
|
+
message: getMessage(error),
|
|
29
|
+
code: getCode(error),
|
|
30
|
+
status: getStatus(error),
|
|
31
|
+
raw: error
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
import type { AuthActionError } from '../../types.js';
|
|
3
|
+
export type UserAuthActionStatus = 'idle' | 'pending' | 'success' | 'error';
|
|
4
|
+
export interface UserAuthActionHandle<TArgs extends unknown[], TResult> {
|
|
5
|
+
execute: (...args: TArgs) => Promise<void>;
|
|
6
|
+
status: Ref<UserAuthActionStatus>;
|
|
7
|
+
data: Ref<TResult | null>;
|
|
8
|
+
error: Ref<AuthActionError | null>;
|
|
9
|
+
}
|
|
10
|
+
export interface CreateActionHandleOptions {
|
|
11
|
+
keepPendingOnRedirect?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export type ActionHandleFor<T> = T extends (...args: infer A) => Promise<infer R> ? UserAuthActionHandle<A, R> : never;
|
|
14
|
+
export type ActionHandleMap<T> = {
|
|
15
|
+
[K in keyof T]: ActionHandleFor<T[K]>;
|
|
16
|
+
};
|
|
17
|
+
export declare function createActionHandle<TArgs extends unknown[], TResult>(getMethod: () => (...args: TArgs) => Promise<TResult>, options?: CreateActionHandleOptions): UserAuthActionHandle<TArgs, TResult>;
|
|
18
|
+
export declare function createActionHandles<T extends object>(getTarget: () => T, targetName: string): ActionHandleMap<T>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ref } from "#imports";
|
|
2
|
+
import { normalizeAuthActionError } from "./auth-action-error.js";
|
|
3
|
+
import { isRecord } from "./utils.js";
|
|
4
|
+
function isErrorResult(value) {
|
|
5
|
+
if (!isRecord(value))
|
|
6
|
+
return false;
|
|
7
|
+
if (!("error" in value))
|
|
8
|
+
return false;
|
|
9
|
+
return Boolean(value.error);
|
|
10
|
+
}
|
|
11
|
+
function isRedirectResult(value) {
|
|
12
|
+
if (!isRecord(value))
|
|
13
|
+
return false;
|
|
14
|
+
return value.redirect === true && typeof value.url === "string" && value.url.length > 0;
|
|
15
|
+
}
|
|
16
|
+
function getRedirectResult(value) {
|
|
17
|
+
if (isRedirectResult(value))
|
|
18
|
+
return value;
|
|
19
|
+
if (!isRecord(value))
|
|
20
|
+
return null;
|
|
21
|
+
const nested = value.data;
|
|
22
|
+
if (isRedirectResult(nested))
|
|
23
|
+
return nested;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const REDIRECT_PENDING_FALLBACK_MS = 1e4;
|
|
27
|
+
export function createActionHandle(getMethod, options = {}) {
|
|
28
|
+
const status = ref("idle");
|
|
29
|
+
const data = ref(null);
|
|
30
|
+
const error = ref(null);
|
|
31
|
+
let latestCallId = 0;
|
|
32
|
+
const run = async (...args) => {
|
|
33
|
+
const callId = ++latestCallId;
|
|
34
|
+
status.value = "pending";
|
|
35
|
+
data.value = null;
|
|
36
|
+
error.value = null;
|
|
37
|
+
try {
|
|
38
|
+
const result = await getMethod()(...args);
|
|
39
|
+
if (isErrorResult(result)) {
|
|
40
|
+
const normalizedError = normalizeAuthActionError(result.error);
|
|
41
|
+
if (callId === latestCallId) {
|
|
42
|
+
status.value = "error";
|
|
43
|
+
data.value = null;
|
|
44
|
+
error.value = normalizedError;
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (callId === latestCallId) {
|
|
49
|
+
if (options.keepPendingOnRedirect !== false) {
|
|
50
|
+
const redirectResult = getRedirectResult(result);
|
|
51
|
+
if (redirectResult) {
|
|
52
|
+
status.value = "pending";
|
|
53
|
+
data.value = result;
|
|
54
|
+
error.value = null;
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
if (callId !== latestCallId || status.value !== "pending")
|
|
57
|
+
return;
|
|
58
|
+
status.value = "success";
|
|
59
|
+
}, REDIRECT_PENDING_FALLBACK_MS);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
status.value = "success";
|
|
64
|
+
data.value = result;
|
|
65
|
+
error.value = null;
|
|
66
|
+
}
|
|
67
|
+
} catch (thrown) {
|
|
68
|
+
const normalizedError = normalizeAuthActionError(thrown);
|
|
69
|
+
if (callId === latestCallId) {
|
|
70
|
+
status.value = "error";
|
|
71
|
+
data.value = null;
|
|
72
|
+
error.value = normalizedError;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const execute = (async (...args) => {
|
|
77
|
+
await run(...args);
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
execute,
|
|
81
|
+
status,
|
|
82
|
+
data,
|
|
83
|
+
error
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function createActionHandles(getTarget, targetName) {
|
|
87
|
+
const handles = /* @__PURE__ */ new Map();
|
|
88
|
+
return new Proxy({}, {
|
|
89
|
+
get(_target, prop) {
|
|
90
|
+
if (prop === "then")
|
|
91
|
+
return void 0;
|
|
92
|
+
if (handles.has(prop))
|
|
93
|
+
return handles.get(prop);
|
|
94
|
+
const handle = createActionHandle(() => {
|
|
95
|
+
const target = getTarget();
|
|
96
|
+
const method = target[prop];
|
|
97
|
+
if (typeof method !== "function")
|
|
98
|
+
throw new TypeError(`${targetName}.${String(prop)}() is not a function`);
|
|
99
|
+
return method;
|
|
100
|
+
});
|
|
101
|
+
handles.set(prop, handle);
|
|
102
|
+
return handle;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function isSafeLocalRedirect(redirect: unknown): string | undefined;
|
|
2
|
+
export declare function resolvePostAuthRedirect(requestURL: URL): string | undefined;
|
|
3
|
+
export declare function resolvePostAuthSuccessRedirect(requestURL: URL): (() => Promise<void>) | undefined;
|
|
4
|
+
export declare function withFallbackSocialCallbackURL(data: unknown, requestURL: URL): unknown;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { navigateTo, useRuntimeConfig } from "#imports";
|
|
2
|
+
import { isRecord } from "./utils.js";
|
|
3
|
+
export function isSafeLocalRedirect(redirect) {
|
|
4
|
+
if (typeof redirect !== "string")
|
|
5
|
+
return;
|
|
6
|
+
if (!redirect.startsWith("/") || redirect.startsWith("//"))
|
|
7
|
+
return;
|
|
8
|
+
return redirect;
|
|
9
|
+
}
|
|
10
|
+
export function resolvePostAuthRedirect(requestURL) {
|
|
11
|
+
const runtimeConfig = useRuntimeConfig();
|
|
12
|
+
const authConfig = runtimeConfig.public.auth;
|
|
13
|
+
const redirectQueryKey = authConfig?.redirectQueryKey ?? "redirect";
|
|
14
|
+
const queryRedirect = requestURL.searchParams?.get(redirectQueryKey);
|
|
15
|
+
const safeQueryRedirect = isSafeLocalRedirect(queryRedirect);
|
|
16
|
+
if (safeQueryRedirect)
|
|
17
|
+
return safeQueryRedirect;
|
|
18
|
+
return isSafeLocalRedirect(authConfig?.redirects?.authenticated);
|
|
19
|
+
}
|
|
20
|
+
export function resolvePostAuthSuccessRedirect(requestURL) {
|
|
21
|
+
const target = resolvePostAuthRedirect(requestURL);
|
|
22
|
+
if (!target)
|
|
23
|
+
return;
|
|
24
|
+
return async () => {
|
|
25
|
+
await navigateTo(target);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function withFallbackSocialCallbackURL(data, requestURL) {
|
|
29
|
+
const callbackURL = resolvePostAuthRedirect(requestURL);
|
|
30
|
+
if (!callbackURL)
|
|
31
|
+
return data;
|
|
32
|
+
if (!isRecord(data))
|
|
33
|
+
return { callbackURL };
|
|
34
|
+
if (typeof data.callbackURL === "string")
|
|
35
|
+
return data;
|
|
36
|
+
return { ...data, callbackURL };
|
|
37
|
+
}
|