feat: add better provider support, icons, regen, and a lot more
This commit is contained in:
+169
-51
@@ -1,66 +1,184 @@
|
||||
import type { BetterAuthClientOptions, InferSessionFromClient, InferUserFromClient } from 'better-auth/client';
|
||||
import { authClient } from '~~/lib/auth-client';
|
||||
import type { User, Session } from 'better-auth';
|
||||
import type { Result } from '~~/types/result';
|
||||
import { Ok, Err } from '~~/types/result';
|
||||
import { createAuthClient } from 'better-auth/vue';
|
||||
|
||||
export enum AuthError {
|
||||
NotAuthenticated = 'NOT_AUTHENTICATED',
|
||||
SignInFailed = 'SIGN_IN_FAILED',
|
||||
SignUpFailed = 'SIGN_UP_FAILED',
|
||||
SignOutFailed = 'SIGN_OUT_FAILED',
|
||||
NetworkError = 'NETWORK_ERROR',
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
user: User | null;
|
||||
session: Session | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export const useAuth = () => {
|
||||
const session = useState<InferSessionFromClient<BetterAuthClientOptions> | null>('auth:session', () => null);
|
||||
const user = useState<InferUserFromClient<BetterAuthClientOptions> | null>('auth:user', () => null);
|
||||
const sessionFetching = import.meta.server ? ref(false) : useState('auth:sessionFetching', () => false);
|
||||
const url = useRequestURL();
|
||||
const headers = useRequestHeaders();
|
||||
const client = createAuthClient({
|
||||
baseURL: url.origin,
|
||||
fetchOptions: {
|
||||
headers
|
||||
}
|
||||
});
|
||||
|
||||
const fetchSession = async () => {
|
||||
if (sessionFetching.value) {
|
||||
console.log('already fetching session');
|
||||
return;
|
||||
const state = useState<AuthState>('auth:state', () => ({
|
||||
user: null,
|
||||
session: null,
|
||||
isLoading: false,
|
||||
}));
|
||||
|
||||
const isAuthenticated = computed(() => !!state.value.session);
|
||||
const userId = computed(() => state.value.user?.id ?? null);
|
||||
|
||||
const fetchSession = async (): Promise<Result<{ session: Session | null; user: User | null }, AuthError>> => {
|
||||
state.value.isLoading = true;
|
||||
|
||||
try {
|
||||
const { data } = await client.getSession();
|
||||
|
||||
if (data) {
|
||||
state.value.session = data.session;
|
||||
state.value.user = data.user;
|
||||
return Ok({ session: data.session, user: data.user });
|
||||
}
|
||||
|
||||
state.value.session = null;
|
||||
state.value.user = null;
|
||||
return Ok({ session: null, user: null });
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch session:', error);
|
||||
return Err(AuthError.NetworkError);
|
||||
} finally {
|
||||
state.value.isLoading = false;
|
||||
}
|
||||
sessionFetching.value = true;
|
||||
let data: {
|
||||
session: InferSessionFromClient<BetterAuthClientOptions>;
|
||||
user: InferUserFromClient<BetterAuthClientOptions>;
|
||||
} | null = null;
|
||||
if (import.meta.server) {
|
||||
data =
|
||||
(
|
||||
await useFetch<{
|
||||
session: InferSessionFromClient<BetterAuthClientOptions>;
|
||||
user: InferUserFromClient<BetterAuthClientOptions>;
|
||||
}>('/api/auth/get-session')
|
||||
).data.value ?? null;
|
||||
} else {
|
||||
data = (await authClient.getSession()).data;
|
||||
}
|
||||
session.value = data?.session || null;
|
||||
user.value = data?.user || null;
|
||||
sessionFetching.value = false;
|
||||
return data;
|
||||
};
|
||||
|
||||
if (import.meta.client) {
|
||||
authClient.$store.listen('$sessionSignal', async (signal) => {
|
||||
if (!signal) return;
|
||||
await fetchSession();
|
||||
const signIn = async (
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<Result<{ user: User; token: string }, { error: AuthError, data?: any }>> => {
|
||||
state.value.isLoading = true;
|
||||
|
||||
if (!session.value) return;
|
||||
try {
|
||||
const { data, error } = await client.signIn.email({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Sign in failed:', error);
|
||||
return Err({ error: AuthError.SignInFailed, data: error });
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return Err({ error: AuthError.SignInFailed });
|
||||
}
|
||||
|
||||
state.value.user = data.user;
|
||||
|
||||
const triplit = useTriplitClient();
|
||||
if ('updateOptions' in triplit) {
|
||||
triplit.updateOptions({
|
||||
token: session.value.token,
|
||||
});
|
||||
if ('startSession' in triplit && data.token) {
|
||||
await triplit.startSession(data.token);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearNuxtData();
|
||||
|
||||
return Ok({ user: data.user, token: data.token });
|
||||
} catch (err) {
|
||||
console.error('Sign in error:', err);
|
||||
return Err({ error: AuthError.NetworkError });
|
||||
} finally {
|
||||
state.value.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const signUp = async (
|
||||
email: string,
|
||||
password: string,
|
||||
name: string
|
||||
): Promise<Result<{ user: User; token: string }, { error: AuthError, data?: any }>> => {
|
||||
state.value.isLoading = true;
|
||||
|
||||
try {
|
||||
const { data, error } = await client.signUp.email({
|
||||
email,
|
||||
password,
|
||||
name,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
console.error('Sign up failed:', error);
|
||||
return Err({ error: AuthError.SignUpFailed, data: error });
|
||||
}
|
||||
|
||||
if (!data || !data.token) {
|
||||
return Err({ error: AuthError.SignUpFailed });
|
||||
}
|
||||
|
||||
state.value.user = data.user;
|
||||
|
||||
const triplit = useTriplitClient();
|
||||
if ('startSession' in triplit) {
|
||||
await triplit.startSession(data.token);
|
||||
}
|
||||
|
||||
clearNuxtData();
|
||||
|
||||
return Ok({ user: data.user, token: data.token });
|
||||
} catch (err) {
|
||||
console.error('Sign up error:', err);
|
||||
return Err({ error: AuthError.NetworkError });
|
||||
} finally {
|
||||
state.value.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = async (): Promise<Result<void, AuthError>> => {
|
||||
state.value.isLoading = true;
|
||||
|
||||
try {
|
||||
const { error } = await client.signOut();
|
||||
|
||||
if (error) {
|
||||
console.error('Sign out failed:', error);
|
||||
return Err(AuthError.SignOutFailed);
|
||||
}
|
||||
|
||||
state.value.user = null;
|
||||
state.value.session = null;
|
||||
|
||||
const triplit = useTriplitClient();
|
||||
if ('disconnect' in triplit) {
|
||||
triplit.disconnect();
|
||||
}
|
||||
|
||||
clearNuxtData();
|
||||
|
||||
return Ok(undefined);
|
||||
} catch (err) {
|
||||
console.error('Sign out error:', err);
|
||||
return Err(AuthError.NetworkError);
|
||||
} finally {
|
||||
state.value.isLoading = false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
session,
|
||||
user,
|
||||
loggedIn: computed(() => !!session.value),
|
||||
signIn: authClient.signIn,
|
||||
signUp: authClient.signUp,
|
||||
async signOut() {
|
||||
await authClient.signOut();
|
||||
session.value = null;
|
||||
user.value = null;
|
||||
return navigateTo('/auth/login');
|
||||
},
|
||||
client,
|
||||
user: computed(() => state.value.user),
|
||||
session: computed(() => state.value.session),
|
||||
isLoading: computed(() => state.value.isLoading),
|
||||
isAuthenticated,
|
||||
userId,
|
||||
fetchSession,
|
||||
signIn,
|
||||
signUp,
|
||||
signOut,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user