glass/src/common/services/authService.js
2025-07-07 03:47:12 +09:00

179 lines
6.8 KiB
JavaScript

const { onAuthStateChanged, signInWithCustomToken, signOut } = require('firebase/auth');
const { BrowserWindow } = require('electron');
const { getFirebaseAuth } = require('./firebaseClient');
const userRepository = require('../repositories/user');
const fetch = require('node-fetch');
async function getVirtualKeyByEmail(email, idToken) {
if (!idToken) {
throw new Error('Firebase ID token is required for virtual key request');
}
const resp = await fetch('https://serverless-api-sf3o.vercel.app/api/virtual_key', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${idToken}`,
},
body: JSON.stringify({ email: email.trim().toLowerCase() }),
redirect: 'follow',
});
const json = await resp.json().catch(() => ({}));
if (!resp.ok) {
console.error('[VK] API request failed:', json.message || 'Unknown error');
throw new Error(json.message || `HTTP ${resp.status}: Virtual key request failed`);
}
const vKey = json?.data?.virtualKey || json?.data?.virtual_key || json?.data?.newVKey?.slug;
if (!vKey) throw new Error('virtual key missing in response');
return vKey;
}
class AuthService {
constructor() {
this.currentUserId = 'default_user';
this.currentUserMode = 'local'; // 'local' or 'firebase'
this.currentUser = null;
this.hasApiKey = false; // Add a flag for API key status
this.isInitialized = false;
}
initialize() {
if (this.isInitialized) return;
const auth = getFirebaseAuth();
onAuthStateChanged(auth, async (user) => {
const previousUser = this.currentUser;
if (user) {
// User signed IN
console.log(`[AuthService] Firebase user signed in:`, user.uid);
this.currentUser = user;
this.currentUserId = user.uid;
this.currentUserMode = 'firebase';
this.hasApiKey = false; // Optimistically assume no key yet
// Broadcast immediately to make UI feel responsive
this.broadcastUserState();
// Start background task to fetch and save virtual key
(async () => {
try {
const idToken = await user.getIdToken(true);
const virtualKey = await getVirtualKeyByEmail(user.email, idToken);
await userRepository.saveApiKey(virtualKey, user.uid, 'openai');
console.log(`[AuthService] BG: Virtual key for ${user.email} has been saved.`);
// Now update the key status, which will trigger another broadcast
await this.updateApiKeyStatus();
} catch (error) {
console.error('[AuthService] BG: Failed to fetch or save virtual key:', error);
}
})();
} else {
// User signed OUT
console.log(`[AuthService] Firebase user signed out.`);
if (previousUser) {
console.log(`[AuthService] Clearing API key for logged-out user: ${previousUser.uid}`);
await userRepository.saveApiKey(null, previousUser.uid);
}
this.currentUser = null;
this.currentUserId = 'default_user';
this.currentUserMode = 'local';
// Update API key status (e.g., if a local key for default_user exists)
// This will also broadcast the final logged-out state.
await this.updateApiKeyStatus();
}
});
// Check for initial API key state
this.updateApiKeyStatus();
this.isInitialized = true;
console.log('[AuthService] Initialized and attached to Firebase Auth state.');
}
async signInWithCustomToken(token) {
const auth = getFirebaseAuth();
try {
const userCredential = await signInWithCustomToken(auth, token);
console.log(`[AuthService] Successfully signed in with custom token for user:`, userCredential.user.uid);
// onAuthStateChanged will handle the state update and broadcast
} catch (error) {
console.error('[AuthService] Error signing in with custom token:', error);
throw error; // Re-throw to be handled by the caller
}
}
async signOut() {
const auth = getFirebaseAuth();
try {
await signOut(auth);
console.log('[AuthService] User sign-out initiated successfully.');
// onAuthStateChanged will handle the state update and broadcast,
// which will also re-evaluate the API key status.
} catch (error) {
console.error('[AuthService] Error signing out:', error);
}
}
broadcastUserState() {
const userState = this.getCurrentUser();
console.log('[AuthService] Broadcasting user state change:', userState);
BrowserWindow.getAllWindows().forEach(win => {
if (win && !win.isDestroyed()) {
win.webContents.send('user-state-changed', userState);
}
});
}
/**
* Updates the internal API key status from the repository and broadcasts if changed.
*/
async updateApiKeyStatus() {
try {
const user = await userRepository.getById(this.currentUserId);
const newStatus = !!(user && user.api_key);
if (this.hasApiKey !== newStatus) {
console.log(`[AuthService] API key status changed to: ${newStatus}`);
this.hasApiKey = newStatus;
this.broadcastUserState();
}
} catch (error) {
console.error('[AuthService] Error checking API key status:', error);
this.hasApiKey = false;
}
}
getCurrentUserId() {
return this.currentUserId;
}
getCurrentUser() {
const isLoggedIn = !!(this.currentUserMode === 'firebase' && this.currentUser);
if (isLoggedIn) {
return {
uid: this.currentUser.uid,
email: this.currentUser.email,
displayName: this.currentUser.displayName,
mode: 'firebase',
isLoggedIn: true,
hasApiKey: this.hasApiKey // Always true for firebase users, but good practice
};
}
return {
uid: this.currentUserId, // returns 'default_user'
email: 'contact@pickle.com',
displayName: 'Default User',
mode: 'local',
isLoggedIn: false,
hasApiKey: this.hasApiKey
};
}
}
const authService = new AuthService();
module.exports = authService;