This commit is contained in:
290
app/composables/useAuth.ts
Normal file
290
app/composables/useAuth.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import {
|
||||
signInWithEmailAndPassword,
|
||||
signInWithPopup,
|
||||
GoogleAuthProvider,
|
||||
signOut as firebaseSignOut,
|
||||
onAuthStateChanged,
|
||||
getAuth,
|
||||
createUserWithEmailAndPassword,
|
||||
} from "firebase/auth";
|
||||
import { getApp, getApps, initializeApp } from "firebase/app";
|
||||
import type { User } from "firebase/auth";
|
||||
|
||||
export const useAuth = () => {
|
||||
const user = useState<User | null>("auth-user", () => null);
|
||||
const isLoading = useState<boolean>("auth-loading", () => true);
|
||||
const error = useState<string | null>("auth-error", () => null);
|
||||
const firebaseReady = useState<boolean>("firebase-ready", () => false);
|
||||
const listenerRegistered = useState<boolean>(
|
||||
"auth-listener-registered",
|
||||
() => false
|
||||
);
|
||||
const backendUser = useState<Record<string, any> | null>(
|
||||
"auth-backend-user",
|
||||
() => null
|
||||
);
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
const ensureFirebaseApp = () => {
|
||||
if (!process.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!firebaseReady.value) {
|
||||
const firebaseConfig = {
|
||||
apiKey: config.public.firebaseApiKey,
|
||||
authDomain: config.public.firebaseAuthDomain,
|
||||
projectId: config.public.firebaseProjectId,
|
||||
storageBucket: config.public.firebaseStorageBucket,
|
||||
messagingSenderId: config.public.firebaseMessagingSenderId,
|
||||
appId: config.public.firebaseAppId,
|
||||
...(config.public.firebaseMeasurementId
|
||||
? { measurementId: config.public.firebaseMeasurementId }
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (getApps().length === 0) {
|
||||
initializeApp(firebaseConfig);
|
||||
}
|
||||
|
||||
firebaseReady.value = true;
|
||||
}
|
||||
|
||||
try {
|
||||
return getApp();
|
||||
} catch (err) {
|
||||
console.error("[useAuth] Failed to get Firebase app instance:", err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getAuthInstance = () => {
|
||||
const app = ensureFirebaseApp();
|
||||
return app ? getAuth(app) : null;
|
||||
};
|
||||
|
||||
const authenticateWithBackend = async (idToken: string) => {
|
||||
try {
|
||||
const response = await $fetch<{
|
||||
token?: string;
|
||||
user?: Record<string, any> | null;
|
||||
}>("/auth/login", {
|
||||
baseURL: config.public.backendUrl,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: {
|
||||
idToken,
|
||||
},
|
||||
});
|
||||
|
||||
backendUser.value = response?.user ?? null;
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.error("Backend authentication failed:", err);
|
||||
backendUser.value = null;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const syncBackendWithToken = async (tokenProvider: () => Promise<string>) => {
|
||||
try {
|
||||
const idToken = await tokenProvider();
|
||||
await authenticateWithBackend(idToken);
|
||||
} catch (err) {
|
||||
console.warn("[useAuth] Failed to sync backend session", err);
|
||||
}
|
||||
};
|
||||
|
||||
const registerListener = () => {
|
||||
if (!process.client || listenerRegistered.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const auth = getAuthInstance();
|
||||
if (auth) {
|
||||
const existingUser = auth.currentUser;
|
||||
if (existingUser && !user.value) {
|
||||
user.value = existingUser;
|
||||
isLoading.value = false;
|
||||
syncBackendWithToken(() => existingUser.getIdToken());
|
||||
}
|
||||
|
||||
listenerRegistered.value = true;
|
||||
onAuthStateChanged(auth, (firebaseUser) => {
|
||||
user.value = firebaseUser;
|
||||
isLoading.value = false;
|
||||
if (firebaseUser) {
|
||||
syncBackendWithToken(() => firebaseUser.getIdToken());
|
||||
} else {
|
||||
backendUser.value = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[useAuth] Failed to initialize auth listener:", err);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const initAuth = () => {
|
||||
registerListener();
|
||||
};
|
||||
|
||||
// Eagerly register listener when composable is used in client context
|
||||
if (process.client) {
|
||||
registerListener();
|
||||
}
|
||||
|
||||
// Sign in with email and password
|
||||
const signInWithEmail = async (email: string, password: string) => {
|
||||
try {
|
||||
error.value = null;
|
||||
isLoading.value = true;
|
||||
|
||||
const auth = getAuthInstance();
|
||||
if (!auth) {
|
||||
throw new Error("Firebase not initialized");
|
||||
}
|
||||
|
||||
const userCredential = await signInWithEmailAndPassword(
|
||||
auth,
|
||||
email,
|
||||
password
|
||||
);
|
||||
const idToken = await userCredential.user.getIdToken();
|
||||
|
||||
try {
|
||||
await authenticateWithBackend(idToken);
|
||||
} catch (backendErr) {
|
||||
console.warn(
|
||||
"[useAuth] Backend authentication failed after email login:",
|
||||
backendErr
|
||||
);
|
||||
}
|
||||
|
||||
user.value = userCredential.user;
|
||||
registerListener();
|
||||
|
||||
return userCredential.user;
|
||||
} catch (err: any) {
|
||||
error.value = err.message;
|
||||
throw err;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Sign in with Google
|
||||
const signInWithGoogle = async () => {
|
||||
try {
|
||||
error.value = null;
|
||||
isLoading.value = true;
|
||||
|
||||
const auth = getAuthInstance();
|
||||
if (!auth) {
|
||||
throw new Error("Firebase not initialized");
|
||||
}
|
||||
|
||||
const provider = new GoogleAuthProvider();
|
||||
const userCredential = await signInWithPopup(auth, provider);
|
||||
const idToken = await userCredential.user.getIdToken();
|
||||
|
||||
try {
|
||||
await authenticateWithBackend(idToken);
|
||||
} catch (backendErr) {
|
||||
console.warn(
|
||||
"[useAuth] Backend authentication failed after Google login:",
|
||||
backendErr
|
||||
);
|
||||
}
|
||||
|
||||
user.value = userCredential.user;
|
||||
registerListener();
|
||||
|
||||
return userCredential.user;
|
||||
} catch (err: any) {
|
||||
error.value = err.message;
|
||||
throw err;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const registerWithEmail = async (email: string, password: string) => {
|
||||
try {
|
||||
error.value = null;
|
||||
isLoading.value = true;
|
||||
|
||||
const auth = getAuthInstance();
|
||||
if (!auth) {
|
||||
throw new Error("Firebase not initialized");
|
||||
}
|
||||
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
email,
|
||||
password
|
||||
);
|
||||
const idToken = await userCredential.user.getIdToken();
|
||||
|
||||
try {
|
||||
await authenticateWithBackend(idToken);
|
||||
} catch (backendErr) {
|
||||
console.warn(
|
||||
"[useAuth] Backend authentication failed after registration:",
|
||||
backendErr
|
||||
);
|
||||
}
|
||||
|
||||
user.value = userCredential.user;
|
||||
registerListener();
|
||||
|
||||
return userCredential.user;
|
||||
} catch (err: any) {
|
||||
error.value = err.message;
|
||||
throw err;
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Sign out
|
||||
const signOut = async () => {
|
||||
try {
|
||||
const auth = getAuthInstance();
|
||||
if (!auth) {
|
||||
throw new Error("Firebase not initialized");
|
||||
}
|
||||
|
||||
await firebaseSignOut(auth);
|
||||
user.value = null;
|
||||
backendUser.value = null;
|
||||
} catch (err: any) {
|
||||
error.value = err.message;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
// Get current user's ID token
|
||||
const getIdToken = async () => {
|
||||
if (!user.value) return null;
|
||||
return await user.value.getIdToken();
|
||||
};
|
||||
|
||||
return {
|
||||
user: readonly(user),
|
||||
isLoading: readonly(isLoading),
|
||||
error: readonly(error),
|
||||
backendUser: readonly(backendUser),
|
||||
initAuth,
|
||||
signInWithEmail,
|
||||
signInWithGoogle,
|
||||
registerWithEmail,
|
||||
signOut,
|
||||
getIdToken,
|
||||
};
|
||||
};
|
||||
1
app/composables/useFirebaseStorage.ts
Normal file
1
app/composables/useFirebaseStorage.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
1
app/composables/useLoginModal.ts
Normal file
1
app/composables/useLoginModal.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const useLoginModal = () => useState<boolean>('login-modal-open', () => false);
|
||||
5
app/composables/useSlipmatDesigner.ts
Normal file
5
app/composables/useSlipmatDesigner.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { computed, ref, shallowRef, watch } from "vue";
|
||||
|
||||
// import type { fabric as FabricNamespace } from "fabric";
|
||||
|
||||
export * from "../../composables/useSlipmatDesigner";
|
||||
Reference in New Issue
Block a user