Merge branch 'main' into feature/firebase

This commit is contained in:
samtiz 2025-07-09 17:51:58 +09:00
commit bf344268e7
10 changed files with 240 additions and 48 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "pickle-glass", "name": "pickle-glass",
"version": "0.2.2", "version": "0.2.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "pickle-glass", "name": "pickle-glass",
"version": "0.2.2", "version": "0.2.3",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {

View File

@ -1,7 +1,9 @@
{ {
"name": "pickle-glass", "name": "pickle-glass",
"productName": "Glass", "productName": "Glass",
"version": "0.2.2",
"version": "0.2.3",
"description": "Cl*ely for Free", "description": "Cl*ely for Free",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {

View File

@ -6,7 +6,8 @@ const LATEST_SCHEMA = {
{ name: 'email', type: 'TEXT NOT NULL' }, { name: 'email', type: 'TEXT NOT NULL' },
{ name: 'created_at', type: 'INTEGER' }, { name: 'created_at', type: 'INTEGER' },
{ name: 'api_key', type: 'TEXT' }, { name: 'api_key', type: 'TEXT' },
{ name: 'provider', type: 'TEXT DEFAULT \'openai\'' } { name: 'provider', type: 'TEXT DEFAULT \'openai\'' },
{ name: 'auto_update_enabled', type: 'INTEGER DEFAULT 1' }
] ]
}, },
sessions: { sessions: {

View File

@ -41,63 +41,71 @@ class AuthService {
// Initialize immediately for the default local user on startup. // Initialize immediately for the default local user on startup.
// This ensures the key is ready before any login/logout state change. // This ensures the key is ready before any login/logout state change.
encryptionService.initializeKey(this.currentUserId); encryptionService.initializeKey(this.currentUserId);
this.initializationPromise = null;
} }
initialize() { initialize() {
if (this.isInitialized) return; if (this.isInitialized) return this.initializationPromise;
const auth = getFirebaseAuth(); this.initializationPromise = new Promise((resolve) => {
onAuthStateChanged(auth, async (user) => { const auth = getFirebaseAuth();
const previousUser = this.currentUser; onAuthStateChanged(auth, async (user) => {
const previousUser = this.currentUser;
if (user) { if (user) {
// User signed IN // User signed IN
console.log(`[AuthService] Firebase user signed in:`, user.uid); console.log(`[AuthService] Firebase user signed in:`, user.uid);
this.currentUser = user; this.currentUser = user;
this.currentUserId = user.uid; this.currentUserId = user.uid;
this.currentUserMode = 'firebase'; this.currentUserMode = 'firebase';
// ** Initialize encryption key for the logged-in user ** // ** Initialize encryption key for the logged-in user **
await encryptionService.initializeKey(user.uid); await encryptionService.initializeKey(user.uid);
// Start background task to fetch and save virtual key // Start background task to fetch and save virtual key
(async () => { (async () => {
try { try {
const idToken = await user.getIdToken(true); const idToken = await user.getIdToken(true);
const virtualKey = await getVirtualKeyByEmail(user.email, idToken); const virtualKey = await getVirtualKeyByEmail(user.email, idToken);
if (global.modelStateService) { if (global.modelStateService) {
global.modelStateService.setFirebaseVirtualKey(virtualKey); global.modelStateService.setFirebaseVirtualKey(virtualKey);
}
console.log(`[AuthService] BG: Virtual key for ${user.email} has been processed.`);
} catch (error) {
console.error('[AuthService] BG: Failed to fetch or save virtual key:', error);
} }
console.log(`[AuthService] BG: Virtual key for ${user.email} has been processed.`); })();
} catch (error) { } else {
console.error('[AuthService] BG: Failed to fetch or save virtual key:', error); // User signed OUT
console.log(`[AuthService] No Firebase user.`);
if (previousUser) {
console.log(`[AuthService] Clearing API key for logged-out user: ${previousUser.uid}`);
if (global.modelStateService) {
global.modelStateService.setFirebaseVirtualKey(null);
}
} }
})(); this.currentUser = null;
this.currentUserId = 'default_user';
this.currentUserMode = 'local';
} else { // ** Initialize encryption key for the default/local user **
// User signed OUT await encryptionService.initializeKey(this.currentUserId);
console.log(`[AuthService] No Firebase user.`);
if (previousUser) {
console.log(`[AuthService] Clearing API key for logged-out user: ${previousUser.uid}`);
if (global.modelStateService) {
global.modelStateService.setFirebaseVirtualKey(null);
}
} }
this.currentUser = null; this.broadcastUserState();
this.currentUserId = 'default_user';
this.currentUserMode = 'local';
// ** Initialize encryption key for the default/local user ** if (!this.isInitialized) {
await encryptionService.initializeKey(this.currentUserId); this.isInitialized = true;
} console.log('[AuthService] Initialized and resolved initialization promise.');
this.broadcastUserState(); resolve();
}
});
}); });
this.isInitialized = true; return this.initializationPromise;
console.log('[AuthService] Initialized and attached to Firebase Auth state.');
} }
async signInWithCustomToken(token) { async signInWithCustomToken(token) {

View File

@ -456,6 +456,8 @@ export class SettingsView extends LitElement {
presets: { type: Array, state: true }, presets: { type: Array, state: true },
selectedPreset: { type: Object, state: true }, selectedPreset: { type: Object, state: true },
showPresets: { type: Boolean, state: true }, showPresets: { type: Boolean, state: true },
autoUpdateEnabled: { type: Boolean, state: true },
autoUpdateLoading: { type: Boolean, state: true },
}; };
//////// after_modelStateService //////// //////// after_modelStateService ////////
@ -479,10 +481,48 @@ export class SettingsView extends LitElement {
this.selectedPreset = null; this.selectedPreset = null;
this.showPresets = false; this.showPresets = false;
this.handleUsePicklesKey = this.handleUsePicklesKey.bind(this) this.handleUsePicklesKey = this.handleUsePicklesKey.bind(this)
this.autoUpdateEnabled = true;
this.autoUpdateLoading = true;
this.loadInitialData(); this.loadInitialData();
//////// after_modelStateService //////// //////// after_modelStateService ////////
} }
async loadAutoUpdateSetting() {
if (!window.require) return;
const { ipcRenderer } = window.require('electron');
this.autoUpdateLoading = true;
try {
const enabled = await ipcRenderer.invoke('settings:get-auto-update');
this.autoUpdateEnabled = enabled;
console.log('Auto-update setting loaded:', enabled);
} catch (e) {
console.error('Error loading auto-update setting:', e);
this.autoUpdateEnabled = true; // fallback
}
this.autoUpdateLoading = false;
this.requestUpdate();
}
async handleToggleAutoUpdate() {
if (!window.require || this.autoUpdateLoading) return;
const { ipcRenderer } = window.require('electron');
this.autoUpdateLoading = true;
this.requestUpdate();
try {
const newValue = !this.autoUpdateEnabled;
const result = await ipcRenderer.invoke('settings:set-auto-update', newValue);
if (result && result.success) {
this.autoUpdateEnabled = newValue;
} else {
console.error('Failed to update auto-update setting');
}
} catch (e) {
console.error('Error toggling auto-update:', e);
}
this.autoUpdateLoading = false;
this.requestUpdate();
}
//////// after_modelStateService //////// //////// after_modelStateService ////////
async loadInitialData() { async loadInitialData() {
if (!window.require) return; if (!window.require) return;
@ -617,6 +657,7 @@ export class SettingsView extends LitElement {
this.setupEventListeners(); this.setupEventListeners();
this.setupIpcListeners(); this.setupIpcListeners();
this.setupWindowResize(); this.setupWindowResize();
this.loadAutoUpdateSetting();
} }
disconnectedCallback() { disconnectedCallback() {
@ -648,6 +689,7 @@ export class SettingsView extends LitElement {
} else { } else {
this.firebaseUser = null; this.firebaseUser = null;
} }
this.loadAutoUpdateSetting();
this.requestUpdate(); this.requestUpdate();
}; };
@ -1161,6 +1203,9 @@ export class SettingsView extends LitElement {
<button class="settings-button full-width" @click=${this.handlePersonalize}> <button class="settings-button full-width" @click=${this.handlePersonalize}>
<span>Personalize / Meeting Notes</span> <span>Personalize / Meeting Notes</span>
</button> </button>
<button class="settings-button full-width" @click=${this.handleToggleAutoUpdate} ?disabled=${this.autoUpdateLoading}>
<span>Automatic Updates: ${this.autoUpdateEnabled ? 'On' : 'Off'}</span>
</button>
<div class="move-buttons"> <div class="move-buttons">
<button class="settings-button half-width" @click=${this.handleMoveLeft}> <button class="settings-button half-width" @click=${this.handleMoveLeft}>

View File

@ -85,10 +85,53 @@ async function deletePreset(id, uid) {
return { changes: 1 }; return { changes: 1 };
} }
async function getAutoUpdate(uid) {
// Assume users are stored in a "users" collection, and auto_update_enabled is a field
const userDocRef = doc(getFirestore(), 'users', uid);
try {
const userSnap = await getDoc(userDocRef);
if (userSnap.exists()) {
const data = userSnap.data();
if (typeof data.auto_update_enabled !== 'undefined') {
console.log('Firebase: Auto update setting found:', data.auto_update_enabled);
return !!data.auto_update_enabled;
} else {
// Field does not exist, just return default
return true;
}
} else {
// User doc does not exist, just return default
return true;
}
} catch (error) {
console.error('Firebase: Error getting auto_update_enabled setting:', error);
return true; // fallback to enabled
}
}
async function setAutoUpdate(uid, isEnabled) {
const userDocRef = doc(getFirestore(), 'users', uid);
try {
const userSnap = await getDoc(userDocRef);
if (userSnap.exists()) {
await updateDoc(userDocRef, { auto_update_enabled: !!isEnabled });
}
// If user doc does not exist, do nothing (no creation)
return { success: true };
} catch (error) {
console.error('Firebase: Error setting auto-update:', error);
return { success: false, error: error.message };
}
}
module.exports = { module.exports = {
getPresets, getPresets,
getPresetTemplates, getPresetTemplates,
createPreset, createPreset,
updatePreset, updatePreset,
deletePreset, deletePreset,
getAutoUpdate,
setAutoUpdate,
}; };

View File

@ -34,6 +34,16 @@ const settingsRepositoryAdapter = {
const uid = authService.getCurrentUserId(); const uid = authService.getCurrentUserId();
return getBaseRepository().deletePreset(id, uid); return getBaseRepository().deletePreset(id, uid);
}, },
getAutoUpdate: () => {
const uid = authService.getCurrentUserId();
return getBaseRepository().getAutoUpdate(uid);
},
setAutoUpdate: (isEnabled) => {
const uid = authService.getCurrentUserId();
return getBaseRepository().setAutoUpdate(uid, isEnabled);
},
}; };
module.exports = settingsRepositoryAdapter; module.exports = settingsRepositoryAdapter;

View File

@ -90,10 +90,57 @@ function deletePreset(id, uid) {
} }
} }
function getAutoUpdate(uid) {
const db = sqliteClient.getDb();
const targetUid = uid;
try {
const row = db.prepare('SELECT auto_update_enabled FROM users WHERE uid = ?').get(targetUid);
if (row) {
console.log('SQLite: Auto update setting found:', row.auto_update_enabled);
return row.auto_update_enabled !== 0;
} else {
// User doesn't exist, create them with default settings
const now = Math.floor(Date.now() / 1000);
const stmt = db.prepare(
'INSERT OR REPLACE INTO users (uid, display_name, email, created_at, auto_update_enabled) VALUES (?, ?, ?, ?, ?)');
stmt.run(targetUid, 'User', 'user@example.com', now, 1);
return true; // default to enabled
}
} catch (error) {
console.error('SQLite: Error getting auto_update_enabled setting:', error);
return true; // fallback to enabled
}
}
function setAutoUpdate(uid, isEnabled) {
const db = sqliteClient.getDb();
const targetUid = uid || sqliteClient.defaultUserId;
try {
const result = db.prepare('UPDATE users SET auto_update_enabled = ? WHERE uid = ?').run(isEnabled ? 1 : 0, targetUid);
// If no rows were updated, the user might not exist, so create them
if (result.changes === 0) {
const now = Math.floor(Date.now() / 1000);
const stmt = db.prepare('INSERT OR REPLACE INTO users (uid, display_name, email, created_at, auto_update_enabled) VALUES (?, ?, ?, ?, ?)');
stmt.run(targetUid, 'User', 'user@example.com', now, isEnabled ? 1 : 0);
}
return { success: true };
} catch (error) {
console.error('SQLite: Error setting auto-update:', error);
throw error;
}
}
module.exports = { module.exports = {
getPresets, getPresets,
getPresetTemplates, getPresetTemplates,
createPreset, createPreset,
updatePreset, updatePreset,
deletePreset deletePreset,
getAutoUpdate,
setAutoUpdate
}; };

View File

@ -364,6 +364,25 @@ async function updateContentProtection(enabled) {
} }
} }
async function getAutoUpdateSetting() {
try {
return settingsRepository.getAutoUpdate();
} catch (error) {
console.error('[SettingsService] Error getting auto update setting:', error);
return true; // Fallback to enabled
}
}
async function setAutoUpdateSetting(isEnabled) {
try {
await settingsRepository.setAutoUpdate(isEnabled);
return { success: true };
} catch (error) {
console.error('[SettingsService] Error setting auto update setting:', error);
return { success: false, error: error.message };
}
}
function initialize() { function initialize() {
// cleanup // cleanup
windowNotificationManager.cleanup(); windowNotificationManager.cleanup();
@ -410,6 +429,15 @@ function initialize() {
return await updateContentProtection(enabled); return await updateContentProtection(enabled);
}); });
ipcMain.handle('settings:get-auto-update', async () => {
return await getAutoUpdateSetting();
});
ipcMain.handle('settings:set-auto-update', async (event, isEnabled) => {
console.log('[SettingsService] Setting auto update setting:', isEnabled);
return await setAutoUpdateSetting(isEnabled);
});
console.log('[SettingsService] Initialized and ready.'); console.log('[SettingsService] Initialized and ready.');
} }
@ -440,4 +468,5 @@ module.exports = {
saveApiKey, saveApiKey,
removeApiKey, removeApiKey,
updateContentProtection, updateContentProtection,
getAutoUpdateSetting,
}; };

View File

@ -26,6 +26,7 @@ const askService = require('./features/ask/askService');
const settingsService = require('./features/settings/settingsService'); const settingsService = require('./features/settings/settingsService');
const sessionRepository = require('./common/repositories/session'); const sessionRepository = require('./common/repositories/session');
const ModelStateService = require('./common/services/modelStateService'); const ModelStateService = require('./common/services/modelStateService');
const sqliteClient = require('./common/services/sqliteClient');
const eventBridge = new EventEmitter(); const eventBridge = new EventEmitter();
let WEB_PORT = 3000; let WEB_PORT = 3000;
@ -189,7 +190,7 @@ app.whenReady().then(async () => {
// Clean up zombie sessions from previous runs first // Clean up zombie sessions from previous runs first
sessionRepository.endAllActiveSessions(); sessionRepository.endAllActiveSessions();
authService.initialize(); await authService.initialize();
//////// after_modelStateService //////// //////// after_modelStateService ////////
modelStateService.initialize(); modelStateService.initialize();
@ -215,6 +216,7 @@ app.whenReady().then(async () => {
); );
} }
// initAutoUpdater should be called after auth is initialized
initAutoUpdater(); initAutoUpdater();
// Process any pending deep link after everything is initialized // Process any pending deep link after everything is initialized
@ -677,8 +679,13 @@ async function startWebStack() {
} }
// Auto-update initialization // Auto-update initialization
function initAutoUpdater() { async function initAutoUpdater() {
try { try {
const autoUpdateEnabled = await settingsService.getAutoUpdateSetting();
if (!autoUpdateEnabled) {
console.log('[AutoUpdater] Skipped because auto-updates are disabled in settings');
return;
}
// Skip auto-updater in development mode // Skip auto-updater in development mode
if (!app.isPackaged) { if (!app.isPackaged) {
console.log('[AutoUpdater] Skipped in development (app is not packaged)'); console.log('[AutoUpdater] Skipped in development (app is not packaged)');