@onmax/nuxt-better-auth 0.0.2-alpha.3 → 0.0.2-alpha.30
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 +37 -1
- package/dist/module.json +2 -2
- package/dist/module.mjs +883 -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 +237 -22
- package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
- package/dist/runtime/app/internal/auth-action-error.js +35 -0
- package/dist/runtime/app/internal/auth-action-handles.d.ts +18 -0
- package/dist/runtime/app/internal/auth-action-handles.js +107 -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 +64 -9
- package/dist/runtime/config.js +7 -2
- 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 +42 -43
|
@@ -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,145 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { computed, nextTick, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
1
|
+
import createAppAuthClient from "#auth/client";
|
|
2
|
+
import { computed, navigateTo, nextTick, useNuxtApp, useRequestFetch, useRequestHeaders, useRequestURL, useRuntimeConfig, useState, watch } from "#imports";
|
|
3
|
+
import { normalizeAuthActionError } from "../internal/auth-action-error.js";
|
|
4
|
+
let _sessionSignalListenerBound = false;
|
|
3
5
|
let _client = null;
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return Boolean(value && typeof value === "object");
|
|
8
|
+
}
|
|
4
9
|
function getClient(baseURL) {
|
|
5
10
|
if (!_client)
|
|
6
11
|
_client = createAppAuthClient(baseURL);
|
|
7
12
|
return _client;
|
|
8
13
|
}
|
|
14
|
+
function getRuntimeFlags() {
|
|
15
|
+
const globalFlags = globalThis.__NUXT_BETTER_AUTH_TEST_FLAGS__;
|
|
16
|
+
if (globalFlags)
|
|
17
|
+
return globalFlags;
|
|
18
|
+
return { client: Boolean(import.meta.client), server: Boolean(import.meta.server) };
|
|
19
|
+
}
|
|
20
|
+
function ensureSessionSignalListener(client, onSignal) {
|
|
21
|
+
if (_sessionSignalListenerBound)
|
|
22
|
+
return;
|
|
23
|
+
const store = client.$store;
|
|
24
|
+
if (!isRecord(store))
|
|
25
|
+
return;
|
|
26
|
+
const listen = store.listen;
|
|
27
|
+
if (typeof listen !== "function")
|
|
28
|
+
return;
|
|
29
|
+
_sessionSignalListenerBound = true;
|
|
30
|
+
const listenFn = listen;
|
|
31
|
+
listenFn("$sessionSignal", async () => {
|
|
32
|
+
try {
|
|
33
|
+
await onSignal();
|
|
34
|
+
} catch {
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
9
38
|
export function useUserSession() {
|
|
39
|
+
const runtimeFlags = getRuntimeFlags();
|
|
10
40
|
const runtimeConfig = useRuntimeConfig();
|
|
11
41
|
const requestURL = useRequestURL();
|
|
12
|
-
const
|
|
42
|
+
const nuxtApp = useNuxtApp();
|
|
43
|
+
const client = runtimeFlags.client ? getClient(runtimeConfig.public.siteUrl || requestURL.origin) : null;
|
|
13
44
|
const session = useState("auth:session", () => null);
|
|
14
45
|
const user = useState("auth:user", () => null);
|
|
15
46
|
const authReady = useState("auth:ready", () => false);
|
|
47
|
+
const prerenderReadyResetQueued = useState("auth:prerender-ready-reset-queued", () => false);
|
|
48
|
+
const hydrationReconcileQueued = useState("auth:hydration-reconcile-queued", () => false);
|
|
16
49
|
const ready = computed(() => authReady.value);
|
|
17
50
|
const loggedIn = computed(() => Boolean(session.value && user.value));
|
|
51
|
+
const isPrerenderedPayload = computed(() => Boolean(nuxtApp.payload.prerenderedAt || nuxtApp.payload.isCached));
|
|
52
|
+
const isPrerenderHydrationEmptySnapshot = computed(() => {
|
|
53
|
+
if (!runtimeFlags.client)
|
|
54
|
+
return false;
|
|
55
|
+
if (!nuxtApp.isHydrating || !nuxtApp.payload.serverRendered || !isPrerenderedPayload.value)
|
|
56
|
+
return false;
|
|
57
|
+
return !session.value && !user.value;
|
|
58
|
+
});
|
|
59
|
+
const skipHydratedSsrGetSession = computed(() => {
|
|
60
|
+
const authConfig = runtimeConfig.public.auth;
|
|
61
|
+
return Boolean(authConfig?.session?.skipHydratedSsrGetSession);
|
|
62
|
+
});
|
|
63
|
+
const shouldSkipInitialClientSessionFetch = computed(() => {
|
|
64
|
+
if (!skipHydratedSsrGetSession.value)
|
|
65
|
+
return false;
|
|
66
|
+
if (!runtimeFlags.client)
|
|
67
|
+
return false;
|
|
68
|
+
if (!nuxtApp.payload.serverRendered)
|
|
69
|
+
return false;
|
|
70
|
+
if (isPrerenderedPayload.value)
|
|
71
|
+
return false;
|
|
72
|
+
return Boolean(session.value && user.value);
|
|
73
|
+
});
|
|
74
|
+
if (isPrerenderHydrationEmptySnapshot.value && authReady.value && !prerenderReadyResetQueued.value) {
|
|
75
|
+
prerenderReadyResetQueued.value = true;
|
|
76
|
+
nuxtApp.hook("app:suspense:resolve", () => {
|
|
77
|
+
try {
|
|
78
|
+
if (!session.value && !user.value && authReady.value)
|
|
79
|
+
authReady.value = false;
|
|
80
|
+
} finally {
|
|
81
|
+
prerenderReadyResetQueued.value = false;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (shouldSkipInitialClientSessionFetch.value && !authReady.value)
|
|
86
|
+
authReady.value = true;
|
|
18
87
|
function clearSession() {
|
|
19
88
|
session.value = null;
|
|
20
89
|
user.value = null;
|
|
21
90
|
}
|
|
22
|
-
function updateUser(updates) {
|
|
23
|
-
if (user.value)
|
|
24
|
-
|
|
91
|
+
async function updateUser(updates) {
|
|
92
|
+
if (!user.value)
|
|
93
|
+
return;
|
|
94
|
+
const previousUser = user.value;
|
|
95
|
+
user.value = { ...user.value, ...updates };
|
|
96
|
+
if (!client)
|
|
97
|
+
return;
|
|
98
|
+
try {
|
|
99
|
+
const clientWithUpdateUser = client;
|
|
100
|
+
const result = await clientWithUpdateUser.updateUser(updates);
|
|
101
|
+
if (result?.error) {
|
|
102
|
+
if (result.error instanceof Error)
|
|
103
|
+
throw result.error;
|
|
104
|
+
const normalizedError = normalizeAuthActionError(result.error);
|
|
105
|
+
throw new Error(normalizedError.message);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
user.value = previousUser;
|
|
109
|
+
if (!(error instanceof Error)) {
|
|
110
|
+
const normalizedError = normalizeAuthActionError(error);
|
|
111
|
+
throw new Error(normalizedError.message);
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
25
115
|
}
|
|
26
|
-
if (
|
|
116
|
+
if (runtimeFlags.client && client && !shouldSkipInitialClientSessionFetch.value) {
|
|
27
117
|
const clientSession = client.useSession();
|
|
28
118
|
watch(
|
|
29
119
|
() => clientSession.value,
|
|
30
120
|
(newSession) => {
|
|
121
|
+
const shouldWaitForPrerenderResolution = isPrerenderHydrationEmptySnapshot.value && !newSession?.data?.session && !newSession?.data?.user;
|
|
122
|
+
if (shouldWaitForPrerenderResolution)
|
|
123
|
+
return;
|
|
31
124
|
if (newSession?.data?.session && newSession?.data?.user) {
|
|
32
125
|
const { token: _, ...safeSession } = newSession.data.session;
|
|
33
126
|
session.value = safeSession;
|
|
34
127
|
user.value = newSession.data.user;
|
|
35
|
-
} else if (!newSession?.isPending) {
|
|
128
|
+
} else if (!newSession?.isPending && !newSession?.isRefetching) {
|
|
129
|
+
const isHydrationEmptySnapshot = nuxtApp.isHydrating && nuxtApp.payload.serverRendered && Boolean(session.value && user.value) && !newSession?.data?.session && !newSession?.data?.user;
|
|
130
|
+
if (isHydrationEmptySnapshot) {
|
|
131
|
+
if (!hydrationReconcileQueued.value) {
|
|
132
|
+
hydrationReconcileQueued.value = true;
|
|
133
|
+
nuxtApp.hook("app:mounted", async () => {
|
|
134
|
+
await fetchSession({ force: true });
|
|
135
|
+
hydrationReconcileQueued.value = false;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
36
140
|
clearSession();
|
|
37
141
|
}
|
|
38
|
-
if (!authReady.value && !newSession?.isPending)
|
|
142
|
+
if (!authReady.value && !newSession?.isPending && !newSession?.isRefetching)
|
|
39
143
|
authReady.value = true;
|
|
40
144
|
},
|
|
41
145
|
{ immediate: true, deep: true }
|
|
@@ -59,6 +163,43 @@ export function useUserSession() {
|
|
|
59
163
|
}, 5e3);
|
|
60
164
|
});
|
|
61
165
|
}
|
|
166
|
+
function isSafeLocalRedirect(redirect) {
|
|
167
|
+
if (typeof redirect !== "string")
|
|
168
|
+
return;
|
|
169
|
+
if (!redirect.startsWith("/") || redirect.startsWith("//"))
|
|
170
|
+
return;
|
|
171
|
+
return redirect;
|
|
172
|
+
}
|
|
173
|
+
function resolvePostAuthRedirect() {
|
|
174
|
+
const authConfig = runtimeConfig.public.auth;
|
|
175
|
+
const redirectQueryKey = authConfig?.redirectQueryKey ?? "redirect";
|
|
176
|
+
const queryRedirect = requestURL.searchParams?.get(redirectQueryKey);
|
|
177
|
+
const safeQueryRedirect = isSafeLocalRedirect(queryRedirect);
|
|
178
|
+
if (safeQueryRedirect)
|
|
179
|
+
return safeQueryRedirect;
|
|
180
|
+
return isSafeLocalRedirect(authConfig?.redirects?.authenticated);
|
|
181
|
+
}
|
|
182
|
+
function resolvePostAuthSuccessRedirect() {
|
|
183
|
+
const target = resolvePostAuthRedirect();
|
|
184
|
+
if (!target)
|
|
185
|
+
return;
|
|
186
|
+
return async () => {
|
|
187
|
+
await navigateTo(target);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function withFallbackSocialCallbackURL(data) {
|
|
191
|
+
const callbackURL = resolvePostAuthRedirect();
|
|
192
|
+
if (!callbackURL)
|
|
193
|
+
return data;
|
|
194
|
+
if (!isRecord(data))
|
|
195
|
+
return { callbackURL };
|
|
196
|
+
if (typeof data.callbackURL === "string")
|
|
197
|
+
return data;
|
|
198
|
+
return {
|
|
199
|
+
...data,
|
|
200
|
+
callbackURL
|
|
201
|
+
};
|
|
202
|
+
}
|
|
62
203
|
function wrapOnSuccess(cb) {
|
|
63
204
|
return async (ctx) => {
|
|
64
205
|
await fetchSession({ force: true });
|
|
@@ -68,14 +209,57 @@ export function useUserSession() {
|
|
|
68
209
|
await cb(ctx);
|
|
69
210
|
};
|
|
70
211
|
}
|
|
71
|
-
function wrapAuthMethod(method) {
|
|
212
|
+
function wrapAuthMethod(method, wrapOptions = {}) {
|
|
72
213
|
return (async (...args) => {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
214
|
+
const originalData = args[0];
|
|
215
|
+
const options = args[1];
|
|
216
|
+
const data = wrapOptions.transformData?.(originalData, options) ?? originalData;
|
|
217
|
+
const dataRecord = isRecord(data) ? data : void 0;
|
|
218
|
+
const optionsRecord = isRecord(options) ? options : void 0;
|
|
219
|
+
if (wrapOptions.shouldSkipSessionSync?.(data, options))
|
|
220
|
+
return method(data, options);
|
|
221
|
+
const fetchOptions = isRecord(dataRecord?.fetchOptions) ? dataRecord.fetchOptions : void 0;
|
|
222
|
+
const nestedOnSuccess = fetchOptions?.onSuccess;
|
|
223
|
+
const topLevelOnSuccess = optionsRecord?.onSuccess;
|
|
224
|
+
const fallbackOnSuccess = resolvePostAuthSuccessRedirect();
|
|
225
|
+
const wrappedFallbackOnSuccess = fallbackOnSuccess && wrapOnSuccess(async () => {
|
|
226
|
+
if (!loggedIn.value)
|
|
227
|
+
return;
|
|
228
|
+
await fallbackOnSuccess();
|
|
229
|
+
});
|
|
230
|
+
if (typeof nestedOnSuccess === "function") {
|
|
231
|
+
const nextData = {
|
|
232
|
+
...dataRecord,
|
|
233
|
+
fetchOptions: {
|
|
234
|
+
...fetchOptions,
|
|
235
|
+
onSuccess: wrapOnSuccess(nestedOnSuccess)
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
return method(nextData, options);
|
|
239
|
+
}
|
|
240
|
+
if (typeof topLevelOnSuccess === "function") {
|
|
241
|
+
const nextOptions = {
|
|
242
|
+
...optionsRecord,
|
|
243
|
+
onSuccess: wrapOnSuccess(topLevelOnSuccess)
|
|
244
|
+
};
|
|
245
|
+
return method(data, nextOptions);
|
|
76
246
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
247
|
+
if (wrappedFallbackOnSuccess) {
|
|
248
|
+
if (fetchOptions) {
|
|
249
|
+
const nextData = {
|
|
250
|
+
...dataRecord,
|
|
251
|
+
fetchOptions: {
|
|
252
|
+
...fetchOptions,
|
|
253
|
+
onSuccess: wrappedFallbackOnSuccess
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
return method(nextData, options);
|
|
257
|
+
}
|
|
258
|
+
const nextOptions = {
|
|
259
|
+
...optionsRecord,
|
|
260
|
+
onSuccess: wrappedFallbackOnSuccess
|
|
261
|
+
};
|
|
262
|
+
return method(data, nextOptions);
|
|
79
263
|
}
|
|
80
264
|
return method(data, options);
|
|
81
265
|
});
|
|
@@ -86,7 +270,15 @@ export function useUserSession() {
|
|
|
86
270
|
const method = targetRecord[prop];
|
|
87
271
|
if (typeof method !== "function")
|
|
88
272
|
return method;
|
|
89
|
-
|
|
273
|
+
const shouldSkipSessionSync = prop === "social" ? (data) => {
|
|
274
|
+
const socialData = isRecord(data) ? data : void 0;
|
|
275
|
+
return socialData?.disableRedirect !== true;
|
|
276
|
+
} : void 0;
|
|
277
|
+
const transformData = prop === "social" ? withFallbackSocialCallbackURL : void 0;
|
|
278
|
+
return wrapAuthMethod(
|
|
279
|
+
(...args) => targetRecord[prop](...args),
|
|
280
|
+
{ shouldSkipSessionSync, transformData }
|
|
281
|
+
);
|
|
90
282
|
}
|
|
91
283
|
}) : new Proxy({}, {
|
|
92
284
|
get: (_, prop) => {
|
|
@@ -107,9 +299,24 @@ export function useUserSession() {
|
|
|
107
299
|
}
|
|
108
300
|
});
|
|
109
301
|
async function fetchSession(options = {}) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
302
|
+
if (runtimeFlags.server) {
|
|
303
|
+
try {
|
|
304
|
+
const headers = options.headers || useRequestHeaders(["cookie"]);
|
|
305
|
+
const requestFetch = useRequestFetch();
|
|
306
|
+
const data = await requestFetch("/api/auth/get-session", { headers });
|
|
307
|
+
if (data?.session && data?.user) {
|
|
308
|
+
const { token: _, ...safeSession } = data.session;
|
|
309
|
+
session.value = safeSession;
|
|
310
|
+
user.value = data.user;
|
|
311
|
+
} else {
|
|
312
|
+
clearSession();
|
|
313
|
+
}
|
|
314
|
+
} catch {
|
|
315
|
+
clearSession();
|
|
316
|
+
} finally {
|
|
317
|
+
if (!authReady.value)
|
|
318
|
+
authReady.value = true;
|
|
319
|
+
}
|
|
113
320
|
return;
|
|
114
321
|
}
|
|
115
322
|
if (client) {
|
|
@@ -135,14 +342,22 @@ export function useUserSession() {
|
|
|
135
342
|
}
|
|
136
343
|
}
|
|
137
344
|
}
|
|
345
|
+
if (runtimeFlags.client && client && shouldSkipInitialClientSessionFetch.value) {
|
|
346
|
+
ensureSessionSignalListener(client, () => fetchSession({ force: true }));
|
|
347
|
+
}
|
|
138
348
|
async function signOut(options) {
|
|
139
349
|
if (!client)
|
|
140
350
|
throw new Error("signOut can only be called on client-side");
|
|
141
|
-
|
|
351
|
+
await client.signOut();
|
|
142
352
|
clearSession();
|
|
143
|
-
if (options?.onSuccess)
|
|
353
|
+
if (options?.onSuccess) {
|
|
144
354
|
await options.onSuccess();
|
|
145
|
-
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const authConfig = runtimeConfig.public.auth;
|
|
358
|
+
const logoutRedirect = authConfig?.redirects?.logout;
|
|
359
|
+
if (logoutRedirect)
|
|
360
|
+
await navigateTo(logoutRedirect);
|
|
146
361
|
}
|
|
147
362
|
return {
|
|
148
363
|
client,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const DEFAULT_AUTH_ACTION_ERROR_MESSAGE = "Request failed. Please try again.";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return Boolean(value && typeof value === "object");
|
|
4
|
+
}
|
|
5
|
+
function getMessage(value) {
|
|
6
|
+
if (value instanceof Error)
|
|
7
|
+
return value.message;
|
|
8
|
+
if (typeof value === "string")
|
|
9
|
+
return value;
|
|
10
|
+
if (isRecord(value) && typeof value.message === "string")
|
|
11
|
+
return value.message;
|
|
12
|
+
return DEFAULT_AUTH_ACTION_ERROR_MESSAGE;
|
|
13
|
+
}
|
|
14
|
+
function getCode(value) {
|
|
15
|
+
if (!isRecord(value))
|
|
16
|
+
return void 0;
|
|
17
|
+
return typeof value.code === "string" ? value.code : void 0;
|
|
18
|
+
}
|
|
19
|
+
function getStatus(value) {
|
|
20
|
+
if (!isRecord(value))
|
|
21
|
+
return void 0;
|
|
22
|
+
if (typeof value.status === "number")
|
|
23
|
+
return value.status;
|
|
24
|
+
if (typeof value.statusCode === "number")
|
|
25
|
+
return value.statusCode;
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
export function normalizeAuthActionError(error) {
|
|
29
|
+
return {
|
|
30
|
+
message: getMessage(error),
|
|
31
|
+
code: getCode(error),
|
|
32
|
+
status: getStatus(error),
|
|
33
|
+
raw: error
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -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,107 @@
|
|
|
1
|
+
import { ref } from "#imports";
|
|
2
|
+
import { normalizeAuthActionError } from "./auth-action-error.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return Boolean(value && typeof value === "object");
|
|
5
|
+
}
|
|
6
|
+
function isErrorResult(value) {
|
|
7
|
+
if (!isRecord(value))
|
|
8
|
+
return false;
|
|
9
|
+
if (!("error" in value))
|
|
10
|
+
return false;
|
|
11
|
+
return Boolean(value.error);
|
|
12
|
+
}
|
|
13
|
+
function isRedirectResult(value) {
|
|
14
|
+
if (!isRecord(value))
|
|
15
|
+
return false;
|
|
16
|
+
return value.redirect === true && typeof value.url === "string" && value.url.length > 0;
|
|
17
|
+
}
|
|
18
|
+
function getRedirectResult(value) {
|
|
19
|
+
if (isRedirectResult(value))
|
|
20
|
+
return value;
|
|
21
|
+
if (!isRecord(value))
|
|
22
|
+
return null;
|
|
23
|
+
const nested = value.data;
|
|
24
|
+
if (isRedirectResult(nested))
|
|
25
|
+
return nested;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const REDIRECT_PENDING_FALLBACK_MS = 1e4;
|
|
29
|
+
export function createActionHandle(getMethod, options = {}) {
|
|
30
|
+
const status = ref("idle");
|
|
31
|
+
const data = ref(null);
|
|
32
|
+
const error = ref(null);
|
|
33
|
+
let latestCallId = 0;
|
|
34
|
+
const run = async (...args) => {
|
|
35
|
+
const callId = ++latestCallId;
|
|
36
|
+
status.value = "pending";
|
|
37
|
+
data.value = null;
|
|
38
|
+
error.value = null;
|
|
39
|
+
try {
|
|
40
|
+
const result = await getMethod()(...args);
|
|
41
|
+
if (isErrorResult(result)) {
|
|
42
|
+
const normalizedError = normalizeAuthActionError(result.error);
|
|
43
|
+
if (callId === latestCallId) {
|
|
44
|
+
status.value = "error";
|
|
45
|
+
data.value = null;
|
|
46
|
+
error.value = normalizedError;
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (callId === latestCallId) {
|
|
51
|
+
if (options.keepPendingOnRedirect !== false) {
|
|
52
|
+
const redirectResult = getRedirectResult(result);
|
|
53
|
+
if (redirectResult) {
|
|
54
|
+
status.value = "pending";
|
|
55
|
+
data.value = result;
|
|
56
|
+
error.value = null;
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
if (callId !== latestCallId || status.value !== "pending")
|
|
59
|
+
return;
|
|
60
|
+
status.value = "success";
|
|
61
|
+
}, REDIRECT_PENDING_FALLBACK_MS);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
status.value = "success";
|
|
66
|
+
data.value = result;
|
|
67
|
+
error.value = null;
|
|
68
|
+
}
|
|
69
|
+
} catch (thrown) {
|
|
70
|
+
const normalizedError = normalizeAuthActionError(thrown);
|
|
71
|
+
if (callId === latestCallId) {
|
|
72
|
+
status.value = "error";
|
|
73
|
+
data.value = null;
|
|
74
|
+
error.value = normalizedError;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const execute = (async (...args) => {
|
|
79
|
+
await run(...args);
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
execute,
|
|
83
|
+
status,
|
|
84
|
+
data,
|
|
85
|
+
error
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function createActionHandles(getTarget, targetName) {
|
|
89
|
+
const handles = /* @__PURE__ */ new Map();
|
|
90
|
+
return new Proxy({}, {
|
|
91
|
+
get(_target, prop) {
|
|
92
|
+
if (prop === "then")
|
|
93
|
+
return void 0;
|
|
94
|
+
if (handles.has(prop))
|
|
95
|
+
return handles.get(prop);
|
|
96
|
+
const handle = createActionHandle(() => {
|
|
97
|
+
const target = getTarget();
|
|
98
|
+
const method = target[prop];
|
|
99
|
+
if (typeof method !== "function")
|
|
100
|
+
throw new TypeError(`${targetName}.${String(prop)}() is not a function`);
|
|
101
|
+
return method;
|
|
102
|
+
});
|
|
103
|
+
handles.set(prop, handle);
|
|
104
|
+
return handle;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|