@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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/module.d.mts +37 -1
  3. package/dist/module.json +2 -2
  4. package/dist/module.mjs +883 -313
  5. package/dist/runtime/app/components/BetterAuthState.vue +1 -0
  6. package/dist/runtime/app/composables/useAction.d.ts +2 -0
  7. package/dist/runtime/app/composables/useAction.js +6 -0
  8. package/dist/runtime/app/composables/useAuthAsyncData.d.ts +6 -0
  9. package/dist/runtime/app/composables/useAuthAsyncData.js +16 -0
  10. package/dist/runtime/app/composables/useAuthClientAction.d.ts +5 -0
  11. package/dist/runtime/app/composables/useAuthClientAction.js +15 -0
  12. package/dist/runtime/app/composables/useAuthRequestFetch.d.ts +11 -0
  13. package/dist/runtime/app/composables/useAuthRequestFetch.js +4 -0
  14. package/dist/runtime/app/composables/useSignIn.d.ts +16 -0
  15. package/dist/runtime/app/composables/useSignIn.js +8 -0
  16. package/dist/runtime/app/composables/useSignUp.d.ts +5 -0
  17. package/dist/runtime/app/composables/useSignUp.js +8 -0
  18. package/dist/runtime/app/composables/useUserSession.d.ts +14 -12
  19. package/dist/runtime/app/composables/useUserSession.js +237 -22
  20. package/dist/runtime/app/internal/auth-action-error.d.ts +3 -0
  21. package/dist/runtime/app/internal/auth-action-error.js +35 -0
  22. package/dist/runtime/app/internal/auth-action-handles.d.ts +18 -0
  23. package/dist/runtime/app/internal/auth-action-handles.js +107 -0
  24. package/dist/runtime/app/middleware/auth.global.js +73 -12
  25. package/dist/runtime/app/pages/__better-auth-devtools.vue +4 -10
  26. package/dist/runtime/app/plugins/session.client.js +1 -2
  27. package/dist/runtime/config.d.ts +64 -9
  28. package/dist/runtime/config.js +7 -2
  29. package/dist/runtime/server/api/_better-auth/accounts.get.js +3 -2
  30. package/dist/runtime/server/api/_better-auth/config.get.d.ts +17 -11
  31. package/dist/runtime/server/api/_better-auth/config.get.js +17 -5
  32. package/dist/runtime/server/api/_better-auth/sessions.delete.js +3 -2
  33. package/dist/runtime/server/api/_better-auth/sessions.get.d.ts +3 -1
  34. package/dist/runtime/server/api/_better-auth/sessions.get.js +3 -2
  35. package/dist/runtime/server/api/_better-auth/users.get.js +3 -2
  36. package/dist/runtime/server/api/auth/[...all].js +1 -1
  37. package/dist/runtime/server/middleware/route-access.js +1 -0
  38. package/dist/runtime/server/tsconfig.json +9 -1
  39. package/dist/runtime/server/utils/auth.d.ts +5 -7
  40. package/dist/runtime/server/utils/auth.js +197 -17
  41. package/dist/runtime/server/utils/custom-secondary-storage.d.ts +6 -0
  42. package/dist/runtime/server/utils/custom-secondary-storage.js +8 -0
  43. package/dist/runtime/server/utils/session.d.ts +4 -8
  44. package/dist/runtime/server/utils/session.js +43 -4
  45. package/dist/runtime/server/virtual-modules.d.ts +22 -0
  46. package/dist/runtime/types/augment.d.ts +18 -2
  47. package/dist/runtime/types.d.ts +12 -13
  48. package/dist/types.d.mts +1 -1
  49. package/package.json +42 -43
@@ -1,4 +1,5 @@
1
1
  <script setup>
2
+ import { useUserSession } from "#imports";
2
3
  const { loggedIn, user, session, signOut, ready } = useUserSession();
3
4
  </script>
4
5
 
@@ -0,0 +1,2 @@
1
+ import type { UserAuthActionHandle } from '../internal/auth-action-handles.js';
2
+ export declare function useAction<TArgs extends unknown[], TResult>(runner: (...args: TArgs) => Promise<TResult>): UserAuthActionHandle<TArgs, TResult>;
@@ -0,0 +1,6 @@
1
+ import { createActionHandle } from "../internal/auth-action-handles.js";
2
+ export function useAction(runner) {
3
+ if (typeof runner !== "function")
4
+ throw new TypeError("useAction(runner) requires an async function");
5
+ return createActionHandle(() => runner);
6
+ }
@@ -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,4 @@
1
+ import { useRequestFetch } from "#imports";
2
+ export function useAuthRequestFetch() {
3
+ return useRequestFetch();
4
+ }
@@ -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 declare function useUserSession(): {
6
- client: any;
7
- session: any;
8
- user: any;
9
- loggedIn: any;
10
- ready: any;
11
- signIn: any;
12
- signUp: any;
13
- signOut: (options?: SignOutOptions) => Promise<any>;
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 { createAppAuthClient } from "#auth/client";
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 client = import.meta.client ? getClient(runtimeConfig.public.siteUrl || requestURL.origin) : null;
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
- user.value = { ...user.value, ...updates };
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 (import.meta.client && client) {
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 [data, options] = args;
74
- if (data?.fetchOptions?.onSuccess) {
75
- return method({ ...data, fetchOptions: { ...data.fetchOptions, onSuccess: wrapOnSuccess(data.fetchOptions.onSuccess) } }, options);
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 (options?.onSuccess) {
78
- return method(data, { ...options, onSuccess: wrapOnSuccess(options.onSuccess) });
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
- return wrapAuthMethod((...args) => targetRecord[prop](...args));
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 (import.meta.server) {
111
- if (!authReady.value)
112
- authReady.value = true;
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
- const response = await client.signOut();
351
+ await client.signOut();
142
352
  clearSession();
143
- if (options?.onSuccess)
353
+ if (options?.onSuccess) {
144
354
  await options.onSuccess();
145
- return response;
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,3 @@
1
+ import type { AuthActionError } from '../../types.js';
2
+ export declare const DEFAULT_AUTH_ACTION_ERROR_MESSAGE = "Request failed. Please try again.";
3
+ export declare function normalizeAuthActionError(error: unknown): AuthActionError;
@@ -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
+ }