fix dependency

This commit is contained in:
jhyang0 2025-07-07 16:38:37 +09:00
parent 86e903ec4b
commit 427be2a293
13 changed files with 477 additions and 659 deletions

1
.npmrc
View File

@ -1,3 +1,2 @@
better-sqlite3:ignore-scripts=true better-sqlite3:ignore-scripts=true
electron-deeplink:ignore-scripts=true
sharp:ignore-scripts=true sharp:ignore-scripts=true

View File

@ -10,6 +10,7 @@
"package": "npm run build:renderer && electron-forge package", "package": "npm run build:renderer && electron-forge package",
"make": "npm run build:renderer && electron-forge make", "make": "npm run build:renderer && electron-forge make",
"build": "npm run build:renderer && electron-builder --config electron-builder.yml --publish never", "build": "npm run build:renderer && electron-builder --config electron-builder.yml --publish never",
"build:win": "npm run build:renderer && electron-builder --win --x64 --publish never",
"publish": "npm run build:renderer && electron-builder --config electron-builder.yml --publish always", "publish": "npm run build:renderer && electron-builder --config electron-builder.yml --publish always",
"lint": "eslint --ext .ts,.tsx,.js .", "lint": "eslint --ext .ts,.tsx,.js .",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
@ -35,7 +36,7 @@
"better-sqlite3": "^9.4.3", "better-sqlite3": "^9.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.0.0", "dotenv": "^17.0.0",
"electron-deeplink": "^1.0.10",
"electron-squirrel-startup": "^1.0.1", "electron-squirrel-startup": "^1.0.1",
"electron-store": "^8.2.0", "electron-store": "^8.2.0",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
@ -47,7 +48,6 @@
"openai": "^4.70.0", "openai": "^4.70.0",
"react-hot-toast": "^2.5.2", "react-hot-toast": "^2.5.2",
"sharp": "^0.34.2", "sharp": "^0.34.2",
"sqlite3": "^5.1.7",
"validator": "^13.11.0", "validator": "^13.11.0",
"wait-on": "^8.0.3", "wait-on": "^8.0.3",
"ws": "^8.18.0" "ws": "^8.18.0"
@ -69,8 +69,6 @@
"esbuild": "^0.25.5" "esbuild": "^0.25.5"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-x64": "^0.34.2",
"@img/sharp-libvips-darwin-x64": "^1.1.0",
"electron-liquid-glass": "^1.0.1" "electron-liquid-glass": "^1.0.1"
} }
} }

View File

@ -2,40 +2,34 @@ const sqliteClient = require('../../services/sqliteClient');
function getPresets(uid) { function getPresets(uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = ` const query = `
SELECT * FROM prompt_presets SELECT * FROM prompt_presets
WHERE uid = ? OR is_default = 1 WHERE uid = ? OR is_default = 1
ORDER BY is_default DESC, title ASC ORDER BY is_default DESC, title ASC
`; `;
db.all(query, [uid], (err, rows) => {
if (err) { try {
return db.prepare(query).all(uid);
} catch (err) {
console.error('SQLite: Failed to get presets:', err); console.error('SQLite: Failed to get presets:', err);
reject(err); throw err;
} else {
resolve(rows);
} }
});
});
} }
function getPresetTemplates() { function getPresetTemplates() {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = ` const query = `
SELECT * FROM prompt_presets SELECT * FROM prompt_presets
WHERE is_default = 1 WHERE is_default = 1
ORDER BY title ASC ORDER BY title ASC
`; `;
db.all(query, [], (err, rows) => {
if (err) { try {
return db.prepare(query).all();
} catch (err) {
console.error('SQLite: Failed to get preset templates:', err); console.error('SQLite: Failed to get preset templates:', err);
reject(err); throw err;
} else {
resolve(rows);
} }
});
});
} }
function create({ uid, title, prompt }) { function create({ uid, title, prompt }) {
@ -44,38 +38,42 @@ function create({ uid, title, prompt }) {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state) VALUES (?, ?, ?, ?, 0, ?, 'dirty')`; const query = `INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state) VALUES (?, ?, ?, ?, 0, ?, 'dirty')`;
return new Promise((resolve, reject) => { try {
db.run(query, [presetId, uid, title, prompt, now], function(err) { db.prepare(query).run(presetId, uid, title, prompt, now);
if (err) reject(err); return { id: presetId };
else resolve({ id: presetId }); } catch (err) {
}); throw err;
}); }
} }
function update(id, { title, prompt }, uid) { function update(id, { title, prompt }, uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
const query = `UPDATE prompt_presets SET title = ?, prompt = ?, sync_state = 'dirty' WHERE id = ? AND uid = ? AND is_default = 0`; const query = `UPDATE prompt_presets SET title = ?, prompt = ?, sync_state = 'dirty' WHERE id = ? AND uid = ? AND is_default = 0`;
return new Promise((resolve, reject) => { try {
db.run(query, [title, prompt, id, uid], function(err) { const result = db.prepare(query).run(title, prompt, id, uid);
if (err) reject(err); if (result.changes === 0) {
else if (this.changes === 0) reject(new Error("Preset not found or permission denied.")); throw new Error("Preset not found or permission denied.");
else resolve({ changes: this.changes }); }
}); return { changes: result.changes };
}); } catch (err) {
throw err;
}
} }
function del(id, uid) { function del(id, uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
const query = `DELETE FROM prompt_presets WHERE id = ? AND uid = ? AND is_default = 0`; const query = `DELETE FROM prompt_presets WHERE id = ? AND uid = ? AND is_default = 0`;
return new Promise((resolve, reject) => { try {
db.run(query, [id, uid], function(err) { const result = db.prepare(query).run(id, uid);
if (err) reject(err); if (result.changes === 0) {
else if (this.changes === 0) reject(new Error("Preset not found or permission denied.")); throw new Error("Preset not found or permission denied.");
else resolve({ changes: this.changes }); }
}); return { changes: result.changes };
}); } catch (err) {
throw err;
}
} }
module.exports = { module.exports = {

View File

@ -2,124 +2,79 @@ const sqliteClient = require('../../services/sqliteClient');
function getById(id) { function getById(id) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { return db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
db.get('SELECT * FROM sessions WHERE id = ?', [id], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
} }
function create(uid, type = 'ask') { function create(uid, type = 'ask') {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const sessionId = require('crypto').randomUUID(); const sessionId = require('crypto').randomUUID();
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `INSERT INTO sessions (id, uid, title, session_type, started_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`; const query = `INSERT INTO sessions (id, uid, title, session_type, started_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`;
db.run(query, [sessionId, uid, `Session @ ${new Date().toLocaleTimeString()}`, type, now, now], function(err) { try {
if (err) { db.prepare(query).run(sessionId, uid, `Session @ ${new Date().toLocaleTimeString()}`, type, now, now);
console.error('SQLite: Failed to create session:', err);
reject(err);
} else {
console.log(`SQLite: Created session ${sessionId} for user ${uid} (type: ${type})`); console.log(`SQLite: Created session ${sessionId} for user ${uid} (type: ${type})`);
resolve(sessionId); return sessionId;
} catch (err) {
console.error('SQLite: Failed to create session:', err);
throw err;
} }
});
});
} }
function getAllByUserId(uid) { function getAllByUserId(uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = "SELECT id, uid, title, session_type, started_at, ended_at, sync_state, updated_at FROM sessions WHERE uid = ? ORDER BY started_at DESC"; const query = "SELECT id, uid, title, session_type, started_at, ended_at, sync_state, updated_at FROM sessions WHERE uid = ? ORDER BY started_at DESC";
db.all(query, [uid], (err, rows) => { return db.prepare(query).all(uid);
if (err) reject(err);
else resolve(rows);
});
});
} }
function updateTitle(id, title) { function updateTitle(id, title) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { const result = db.prepare('UPDATE sessions SET title = ? WHERE id = ?').run(title, id);
db.run('UPDATE sessions SET title = ? WHERE id = ?', [title, id], function(err) { return { changes: result.changes };
if (err) reject(err);
else resolve({ changes: this.changes });
});
});
} }
function deleteWithRelatedData(id) { function deleteWithRelatedData(id) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { const transaction = db.transaction(() => {
db.serialize(() => { db.prepare("DELETE FROM transcripts WHERE session_id = ?").run(id);
db.run("BEGIN TRANSACTION;"); db.prepare("DELETE FROM ai_messages WHERE session_id = ?").run(id);
const queries = [ db.prepare("DELETE FROM summaries WHERE session_id = ?").run(id);
"DELETE FROM transcripts WHERE session_id = ?", db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
"DELETE FROM ai_messages WHERE session_id = ?", });
"DELETE FROM summaries WHERE session_id = ?",
"DELETE FROM sessions WHERE id = ?" try {
]; transaction();
queries.forEach(query => { return { success: true };
db.run(query, [id], (err) => { } catch (err) {
if (err) { throw err;
db.run("ROLLBACK;");
return reject(err);
} }
});
});
db.run("COMMIT;", (err) => {
if (err) {
db.run("ROLLBACK;");
return reject(err);
}
resolve({ success: true });
});
});
});
} }
function end(id) { function end(id) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE id = ?`; const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE id = ?`;
db.run(query, [now, now, id], function(err) { const result = db.prepare(query).run(now, now, id);
if (err) reject(err); return { changes: result.changes };
else resolve({ changes: this.changes });
});
});
} }
function updateType(id, type) { function updateType(id, type) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = 'UPDATE sessions SET session_type = ?, updated_at = ? WHERE id = ?'; const query = 'UPDATE sessions SET session_type = ?, updated_at = ? WHERE id = ?';
db.run(query, [type, now, id], function(err) { const result = db.prepare(query).run(type, now, id);
if (err) { return { changes: result.changes };
reject(err);
} else {
resolve({ changes: this.changes });
}
});
});
} }
function touch(id) { function touch(id) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = 'UPDATE sessions SET updated_at = ? WHERE id = ?'; const query = 'UPDATE sessions SET updated_at = ? WHERE id = ?';
db.run(query, [now, id], function(err) { const result = db.prepare(query).run(now, id);
if (err) reject(err); return { changes: result.changes };
else resolve({ changes: this.changes });
});
});
} }
async function getOrCreateActive(uid, requestedType = 'ask') { function getOrCreateActive(uid, requestedType = 'ask') {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
// 1. Look for ANY active session for the user (ended_at IS NULL). // 1. Look for ANY active session for the user (ended_at IS NULL).
@ -131,12 +86,7 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
LIMIT 1 LIMIT 1
`; `;
const activeSession = await new Promise((resolve, reject) => { const activeSession = db.prepare(findQuery).get(uid);
db.get(findQuery, [uid], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
if (activeSession) { if (activeSession) {
// An active session exists. // An active session exists.
@ -144,12 +94,12 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
// 2. Promotion Logic: If it's an 'ask' session and we need 'listen', promote it. // 2. Promotion Logic: If it's an 'ask' session and we need 'listen', promote it.
if (activeSession.session_type === 'ask' && requestedType === 'listen') { if (activeSession.session_type === 'ask' && requestedType === 'listen') {
await updateType(activeSession.id, 'listen'); updateType(activeSession.id, 'listen');
console.log(`[Repo] Promoted session ${activeSession.id} to 'listen' type.`); console.log(`[Repo] Promoted session ${activeSession.id} to 'listen' type.`);
} }
// 3. Touch the session and return its ID. // 3. Touch the session and return its ID.
await touch(activeSession.id); touch(activeSession.id);
return activeSession.id; return activeSession.id;
} else { } else {
// 4. No active session found, create a new one. // 4. No active session found, create a new one.
@ -160,19 +110,17 @@ async function getOrCreateActive(uid, requestedType = 'ask') {
function endAllActiveSessions() { function endAllActiveSessions() {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL`; const query = `UPDATE sessions SET ended_at = ?, updated_at = ? WHERE ended_at IS NULL`;
db.run(query, [now, now], function(err) {
if (err) { try {
const result = db.prepare(query).run(now, now);
console.log(`[Repo] Ended ${result.changes} active session(s).`);
return { changes: result.changes };
} catch (err) {
console.error('SQLite: Failed to end all active sessions:', err); console.error('SQLite: Failed to end all active sessions:', err);
reject(err); throw err;
} else {
console.log(`[Repo] Ended ${this.changes} active session(s).`);
resolve({ changes: this.changes });
} }
});
});
} }
module.exports = { module.exports = {

View File

@ -13,66 +13,44 @@ function findOrCreate(user) {
email=excluded.email email=excluded.email
`; `;
return new Promise((resolve, reject) => { try {
db.run(query, [uid, displayName, email, now], (err) => { db.prepare(query).run(uid, displayName, email, now);
if (err) { return getById(uid);
} catch (err) {
console.error('SQLite: Failed to find or create user:', err); console.error('SQLite: Failed to find or create user:', err);
return reject(err); throw err;
} }
getById(uid).then(resolve).catch(reject);
});
});
} }
function getById(uid) { function getById(uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { return db.prepare('SELECT * FROM users WHERE uid = ?').get(uid);
db.get('SELECT * FROM users WHERE uid = ?', [uid], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
} }
function saveApiKey(apiKey, uid, provider = 'openai') { function saveApiKey(apiKey, uid, provider = 'openai') {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { try {
db.run( const result = db.prepare('UPDATE users SET api_key = ?, provider = ? WHERE uid = ?').run(apiKey, provider, uid);
'UPDATE users SET api_key = ?, provider = ? WHERE uid = ?',
[apiKey, provider, uid],
function(err) {
if (err) {
console.error('SQLite: Failed to save API key:', err);
reject(err);
} else {
console.log(`SQLite: API key saved for user ${uid} with provider ${provider}.`); console.log(`SQLite: API key saved for user ${uid} with provider ${provider}.`);
resolve({ changes: this.changes }); return { changes: result.changes };
} catch (err) {
console.error('SQLite: Failed to save API key:', err);
throw err;
} }
}
);
});
} }
function update({ uid, displayName }) { function update({ uid, displayName }) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => { const result = db.prepare('UPDATE users SET display_name = ? WHERE uid = ?').run(displayName, uid);
db.run('UPDATE users SET display_name = ? WHERE uid = ?', [displayName, uid], function(err) { return { changes: result.changes };
if (err) reject(err);
else resolve({ changes: this.changes });
});
});
} }
function deleteById(uid) { function deleteById(uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const userSessions = db.prepare('SELECT id FROM sessions WHERE uid = ?').all(uid); const userSessions = db.prepare('SELECT id FROM sessions WHERE uid = ?').all(uid);
const sessionIds = userSessions.map(s => s.id); const sessionIds = userSessions.map(s => s.id);
db.serialize(() => { const transaction = db.transaction(() => {
db.run("BEGIN TRANSACTION;");
try {
if (sessionIds.length > 0) { if (sessionIds.length > 0) {
const placeholders = sessionIds.map(() => '?').join(','); const placeholders = sessionIds.map(() => '?').join(',');
db.prepare(`DELETE FROM transcripts WHERE session_id IN (${placeholders})`).run(...sessionIds); db.prepare(`DELETE FROM transcripts WHERE session_id IN (${placeholders})`).run(...sessionIds);
@ -82,20 +60,14 @@ function deleteById(uid) {
} }
db.prepare('DELETE FROM prompt_presets WHERE uid = ? AND is_default = 0').run(uid); db.prepare('DELETE FROM prompt_presets WHERE uid = ? AND is_default = 0').run(uid);
db.prepare('DELETE FROM users WHERE uid = ?').run(uid); db.prepare('DELETE FROM users WHERE uid = ?').run(uid);
});
db.run("COMMIT;", (err) => { try {
if (err) { transaction();
db.run("ROLLBACK;"); return { success: true };
return reject(err);
}
resolve({ success: true });
});
} catch (err) { } catch (err) {
db.run("ROLLBACK;"); throw err;
reject(err);
} }
});
});
} }
module.exports = { module.exports = {

View File

@ -123,7 +123,7 @@ class AuthService {
const userState = this.getCurrentUser(); const userState = this.getCurrentUser();
console.log('[AuthService] Broadcasting user state change:', userState); console.log('[AuthService] Broadcasting user state change:', userState);
BrowserWindow.getAllWindows().forEach(win => { BrowserWindow.getAllWindows().forEach(win => {
if (win && !win.isDestroyed()) { if (win && !win.isDestroyed() && win.webContents && !win.webContents.isDestroyed()) {
win.webContents.send('user-state-changed', userState); win.webContents.send('user-state-changed', userState);
} }
}); });

View File

@ -1,4 +1,4 @@
const sqlite3 = require('sqlite3').verbose(); const Database = require('better-sqlite3');
const path = require('path'); const path = require('path');
const LATEST_SCHEMA = require('../config/schema'); const LATEST_SCHEMA = require('../config/schema');
@ -10,28 +10,20 @@ class SQLiteClient {
} }
connect(dbPath) { connect(dbPath) {
return new Promise((resolve, reject) => {
if (this.db) { if (this.db) {
console.log('[SQLiteClient] Already connected.'); console.log('[SQLiteClient] Already connected.');
return resolve(); return;
} }
try {
this.dbPath = dbPath; this.dbPath = dbPath;
this.db = new sqlite3.Database(this.dbPath, (err) => { this.db = new Database(this.dbPath);
if (err) { this.db.pragma('journal_mode = WAL');
console.error('[SQLiteClient] Could not connect to database', err);
return reject(err);
}
console.log('[SQLiteClient] Connected successfully to:', this.dbPath); console.log('[SQLiteClient] Connected successfully to:', this.dbPath);
} catch (err) {
this.db.run('PRAGMA journal_mode = WAL;', (err) => { console.error('[SQLiteClient] Could not connect to database', err);
if (err) { throw err;
return reject(err);
} }
resolve();
});
});
});
} }
getDb() { getDb() {
@ -41,51 +33,39 @@ class SQLiteClient {
return this.db; return this.db;
} }
async synchronizeSchema() { synchronizeSchema() {
console.log('[DB Sync] Starting schema synchronization...'); console.log('[DB Sync] Starting schema synchronization...');
const tablesInDb = await this.getTablesFromDb(); const tablesInDb = this.getTablesFromDb();
for (const tableName of Object.keys(LATEST_SCHEMA)) { for (const tableName of Object.keys(LATEST_SCHEMA)) {
const tableSchema = LATEST_SCHEMA[tableName]; const tableSchema = LATEST_SCHEMA[tableName];
if (!tablesInDb.includes(tableName)) { if (!tablesInDb.includes(tableName)) {
// Table doesn't exist, create it // Table doesn't exist, create it
await this.createTable(tableName, tableSchema); this.createTable(tableName, tableSchema);
} else { } else {
// Table exists, check for missing columns // Table exists, check for missing columns
await this.updateTable(tableName, tableSchema); this.updateTable(tableName, tableSchema);
} }
} }
console.log('[DB Sync] Schema synchronization finished.'); console.log('[DB Sync] Schema synchronization finished.');
} }
async getTablesFromDb() { getTablesFromDb() {
return new Promise((resolve, reject) => { const tables = this.db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
this.db.all("SELECT name FROM sqlite_master WHERE type='table'", (err, tables) => { return tables.map(t => t.name);
if (err) return reject(err);
resolve(tables.map(t => t.name));
});
});
} }
async createTable(tableName, tableSchema) { createTable(tableName, tableSchema) {
return new Promise((resolve, reject) => {
const columnDefs = tableSchema.columns.map(col => `"${col.name}" ${col.type}`).join(', '); const columnDefs = tableSchema.columns.map(col => `"${col.name}" ${col.type}`).join(', ');
const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columnDefs})`; const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columnDefs})`;
console.log(`[DB Sync] Creating table: ${tableName}`); console.log(`[DB Sync] Creating table: ${tableName}`);
this.db.run(query, (err) => { this.db.prepare(query).run();
if (err) return reject(err);
resolve();
});
});
} }
async updateTable(tableName, tableSchema) { updateTable(tableName, tableSchema) {
return new Promise((resolve, reject) => { const existingColumns = this.db.prepare(`PRAGMA table_info("${tableName}")`).all();
this.db.all(`PRAGMA table_info("${tableName}")`, async (err, existingColumns) => {
if (err) return reject(err);
const existingColumnNames = existingColumns.map(c => c.name); const existingColumnNames = existingColumns.map(c => c.name);
const columnsToAdd = tableSchema.columns.filter(col => !existingColumnNames.includes(col.name)); const columnsToAdd = tableSchema.columns.filter(col => !existingColumnNames.includes(col.name));
@ -93,28 +73,16 @@ class SQLiteClient {
console.log(`[DB Sync] Updating table: ${tableName}. Adding columns: ${columnsToAdd.map(c=>c.name).join(', ')}`); console.log(`[DB Sync] Updating table: ${tableName}. Adding columns: ${columnsToAdd.map(c=>c.name).join(', ')}`);
for (const column of columnsToAdd) { for (const column of columnsToAdd) {
const addColumnQuery = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${column.type}`; const addColumnQuery = `ALTER TABLE "${tableName}" ADD COLUMN "${column.name}" ${column.type}`;
try { this.db.prepare(addColumnQuery).run();
await this.runQuery(addColumnQuery);
} catch (alterErr) {
return reject(alterErr);
} }
} }
} }
resolve();
});
});
}
async runQuery(query, params = []) { runQuery(query, params = []) {
return new Promise((resolve, reject) => { return this.db.prepare(query).run(params);
this.db.run(query, params, function(err) {
if (err) return reject(err);
resolve(this);
});
});
} }
async cleanupEmptySessions() { cleanupEmptySessions() {
console.log('[DB Cleanup] Checking for empty sessions...'); console.log('[DB Cleanup] Checking for empty sessions...');
const query = ` const query = `
SELECT s.id FROM sessions s SELECT s.id FROM sessions s
@ -124,16 +92,11 @@ class SQLiteClient {
WHERE t.id IS NULL AND a.id IS NULL AND su.session_id IS NULL WHERE t.id IS NULL AND a.id IS NULL AND su.session_id IS NULL
`; `;
return new Promise((resolve, reject) => { const rows = this.db.prepare(query).all();
this.db.all(query, [], (err, rows) => {
if (err) {
console.error('[DB Cleanup] Error finding empty sessions:', err);
return reject(err);
}
if (rows.length === 0) { if (rows.length === 0) {
console.log('[DB Cleanup] No empty sessions found.'); console.log('[DB Cleanup] No empty sessions found.');
return resolve(); return;
} }
const idsToDelete = rows.map(r => r.id); const idsToDelete = rows.map(r => r.id);
@ -141,36 +104,23 @@ class SQLiteClient {
const deleteQuery = `DELETE FROM sessions WHERE id IN (${placeholders})`; const deleteQuery = `DELETE FROM sessions WHERE id IN (${placeholders})`;
console.log(`[DB Cleanup] Found ${idsToDelete.length} empty sessions. Deleting...`); console.log(`[DB Cleanup] Found ${idsToDelete.length} empty sessions. Deleting...`);
this.db.run(deleteQuery, idsToDelete, function(deleteErr) { const result = this.db.prepare(deleteQuery).run(idsToDelete);
if (deleteErr) { console.log(`[DB Cleanup] Successfully deleted ${result.changes} empty sessions.`);
console.error('[DB Cleanup] Error deleting empty sessions:', deleteErr);
return reject(deleteErr);
}
console.log(`[DB Cleanup] Successfully deleted ${this.changes} empty sessions.`);
resolve();
});
});
});
} }
async initTables() { initTables() {
await this.synchronizeSchema(); this.synchronizeSchema();
await this.initDefaultData(); this.initDefaultData();
} }
async initDefaultData() { initDefaultData() {
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const initUserQuery = ` const initUserQuery = `
INSERT OR IGNORE INTO users (uid, display_name, email, created_at) INSERT OR IGNORE INTO users (uid, display_name, email, created_at)
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
`; `;
this.db.run(initUserQuery, [this.defaultUserId, 'Default User', 'contact@pickle.com', now], (err) => { this.db.prepare(initUserQuery).run(this.defaultUserId, 'Default User', 'contact@pickle.com', now);
if (err) {
console.error('Failed to initialize default user:', err);
return reject(err);
}
const defaultPresets = [ const defaultPresets = [
['school', 'School', 'You are a school and lecture assistant. Your goal is to help the user, a student, understand academic material and answer questions.\n\nWhenever a question appears on the user\'s screen or is asked aloud, you provide a direct, step-by-step answer, showing all necessary reasoning or calculations.\n\nIf the user is watching a lecture or working through new material, you offer concise explanations of key concepts and clarify definitions as they come up.', 1], ['school', 'School', 'You are a school and lecture assistant. Your goal is to help the user, a student, understand academic material and answer questions.\n\nWhenever a question appears on the user\'s screen or is asked aloud, you provide a direct, step-by-step answer, showing all necessary reasoning or calculations.\n\nIf the user is watching a lecture or working through new material, you offer concise explanations of key concepts and clarify definitions as they come up.', 1],
@ -189,27 +139,18 @@ class SQLiteClient {
stmt.run(preset[0], this.defaultUserId, preset[1], preset[2], preset[3], now); stmt.run(preset[0], this.defaultUserId, preset[1], preset[2], preset[3], now);
} }
stmt.finalize((err) => {
if (err) {
console.error('Failed to finalize preset statement:', err);
return reject(err);
}
console.log('Default data initialized.'); console.log('Default data initialized.');
resolve();
});
});
});
} }
async markPermissionsAsCompleted() { markPermissionsAsCompleted() {
return this.query( return this.query(
'INSERT OR REPLACE INTO system_settings (key, value) VALUES (?, ?)', 'INSERT OR REPLACE INTO system_settings (key, value) VALUES (?, ?)',
['permissions_completed', 'true'] ['permissions_completed', 'true']
); );
} }
async checkPermissionsCompleted() { checkPermissionsCompleted() {
const result = await this.query( const result = this.query(
'SELECT value FROM system_settings WHERE key = ?', 'SELECT value FROM system_settings WHERE key = ?',
['permissions_completed'] ['permissions_completed']
); );
@ -218,43 +159,32 @@ class SQLiteClient {
close() { close() {
if (this.db) { if (this.db) {
this.db.close((err) => { try {
if (err) { this.db.close();
console.error('SQLite connection close failed:', err);
} else {
console.log('SQLite connection closed.'); console.log('SQLite connection closed.');
} catch (err) {
console.error('SQLite connection close failed:', err);
} }
});
this.db = null; this.db = null;
} }
} }
async query(sql, params = []) { query(sql, params = []) {
return new Promise((resolve, reject) => {
if (!this.db) { if (!this.db) {
return reject(new Error('Database not connected')); throw new Error('Database not connected');
} }
try {
if (sql.toUpperCase().startsWith('SELECT')) { if (sql.toUpperCase().startsWith('SELECT')) {
this.db.all(sql, params, (err, rows) => { return this.db.prepare(sql).all(params);
if (err) { } else {
const result = this.db.prepare(sql).run(params);
return { changes: result.changes, lastID: result.lastID };
}
} catch (err) {
console.error('Query error:', err); console.error('Query error:', err);
reject(err); throw err;
} else {
resolve(rows);
} }
});
} else {
this.db.run(sql, params, function(err) {
if (err) {
console.error('Query error:', err);
reject(err);
} else {
resolve({ changes: this.changes, lastID: this.lastID });
}
});
}
});
} }
} }

View File

@ -1,7 +1,6 @@
const { BrowserWindow, globalShortcut, ipcMain, screen, app, shell, desktopCapturer } = require('electron'); const { BrowserWindow, globalShortcut, ipcMain, screen, app, shell, desktopCapturer } = require('electron');
const WindowLayoutManager = require('./windowLayoutManager'); const WindowLayoutManager = require('./windowLayoutManager');
const SmoothMovementManager = require('./smoothMovementManager'); const SmoothMovementManager = require('./smoothMovementManager');
const liquidGlass = require('electron-liquid-glass');
const path = require('node:path'); const path = require('node:path');
const fs = require('node:fs'); const fs = require('node:fs');
const os = require('os'); const os = require('os');
@ -15,6 +14,7 @@ const fetch = require('node-fetch');
/* ────────────────[ GLASS BYPASS ]─────────────── */ /* ────────────────[ GLASS BYPASS ]─────────────── */
let liquidGlass;
const isLiquidGlassSupported = () => { const isLiquidGlassSupported = () => {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
return false; return false;
@ -23,7 +23,15 @@ const isLiquidGlassSupported = () => {
// return majorVersion >= 25; // macOS 26+ (Darwin 25+) // return majorVersion >= 25; // macOS 26+ (Darwin 25+)
return majorVersion >= 26; // See you soon! return majorVersion >= 26; // See you soon!
}; };
const shouldUseLiquidGlass = isLiquidGlassSupported(); let shouldUseLiquidGlass = isLiquidGlassSupported();
if (shouldUseLiquidGlass) {
try {
liquidGlass = require('electron-liquid-glass');
} catch (e) {
console.warn('Could not load optional dependency "electron-liquid-glass". The feature will be disabled.');
shouldUseLiquidGlass = false;
}
}
/* ────────────────[ GLASS BYPASS ]─────────────── */ /* ────────────────[ GLASS BYPASS ]─────────────── */
let isContentProtectionOn = true; let isContentProtectionOn = true;

View File

@ -2,31 +2,23 @@ const sqliteClient = require('../../../common/services/sqliteClient');
function addAiMessage({ sessionId, role, content, model = 'gpt-4.1' }) { function addAiMessage({ sessionId, role, content, model = 'gpt-4.1' }) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const messageId = require('crypto').randomUUID(); const messageId = require('crypto').randomUUID();
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `INSERT INTO ai_messages (id, session_id, sent_at, role, content, model, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`; const query = `INSERT INTO ai_messages (id, session_id, sent_at, role, content, model, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`;
db.run(query, [messageId, sessionId, now, role, content, model, now], function(err) {
if (err) { try {
db.prepare(query).run(messageId, sessionId, now, role, content, model, now);
return { id: messageId };
} catch (err) {
console.error('SQLite: Failed to add AI message:', err); console.error('SQLite: Failed to add AI message:', err);
reject(err); throw err;
} }
else {
resolve({ id: messageId });
}
});
});
} }
function getAllAiMessagesBySessionId(sessionId) { function getAllAiMessagesBySessionId(sessionId) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = "SELECT * FROM ai_messages WHERE session_id = ? ORDER BY sent_at ASC"; const query = "SELECT * FROM ai_messages WHERE session_id = ? ORDER BY sent_at ASC";
db.all(query, [sessionId], (err, rows) => { return db.prepare(query).all(sessionId);
if (err) reject(err);
else resolve(rows);
});
});
} }
module.exports = { module.exports = {

View File

@ -2,33 +2,23 @@ const sqliteClient = require('../../../../common/services/sqliteClient');
function addTranscript({ sessionId, speaker, text }) { function addTranscript({ sessionId, speaker, text }) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const transcriptId = require('crypto').randomUUID(); const transcriptId = require('crypto').randomUUID();
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = `INSERT INTO transcripts (id, session_id, start_at, speaker, text, created_at) VALUES (?, ?, ?, ?, ?, ?)`; const query = `INSERT INTO transcripts (id, session_id, start_at, speaker, text, created_at) VALUES (?, ?, ?, ?, ?, ?)`;
db.run(query, [transcriptId, sessionId, now, speaker, text, now], function(err) {
if (err) { try {
db.prepare(query).run(transcriptId, sessionId, now, speaker, text, now);
return { id: transcriptId };
} catch (err) {
console.error('Error adding transcript:', err); console.error('Error adding transcript:', err);
reject(err); throw err;
} else {
resolve({ id: transcriptId });
} }
});
});
} }
function getAllTranscriptsBySessionId(sessionId) { function getAllTranscriptsBySessionId(sessionId) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = "SELECT * FROM transcripts WHERE session_id = ? ORDER BY start_at ASC"; const query = "SELECT * FROM transcripts WHERE session_id = ? ORDER BY start_at ASC";
db.all(query, [sessionId], (err, rows) => { return db.prepare(query).all(sessionId);
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
} }
module.exports = { module.exports = {

View File

@ -2,7 +2,6 @@ const sqliteClient = require('../../../../common/services/sqliteClient');
function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) { function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model = 'gpt-4.1' }) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = ` const query = `
INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at) INSERT INTO summaries (session_id, generated_at, model, text, tldr, bullet_json, action_json, updated_at)
@ -16,29 +15,20 @@ function saveSummary({ sessionId, tldr, text, bullet_json, action_json, model =
action_json=excluded.action_json, action_json=excluded.action_json,
updated_at=excluded.updated_at updated_at=excluded.updated_at
`; `;
db.run(query, [sessionId, now, model, text, tldr, bullet_json, action_json, now], function(err) {
if (err) { try {
const result = db.prepare(query).run(sessionId, now, model, text, tldr, bullet_json, action_json, now);
return { changes: result.changes };
} catch (err) {
console.error('Error saving summary:', err); console.error('Error saving summary:', err);
reject(err); throw err;
} else {
resolve({ changes: this.changes });
} }
});
});
} }
function getSummaryBySessionId(sessionId) { function getSummaryBySessionId(sessionId) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = "SELECT * FROM summaries WHERE session_id = ?"; const query = "SELECT * FROM summaries WHERE session_id = ?";
db.get(query, [sessionId], (err, row) => { return db.prepare(query).get(sessionId) || null;
if (err) {
reject(err);
} else {
resolve(row || null);
}
});
});
} }
module.exports = { module.exports = {

View File

@ -2,102 +2,92 @@ const sqliteClient = require('../../../common/services/sqliteClient');
function getPresets(uid) { function getPresets(uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = ` const query = `
SELECT * FROM prompt_presets SELECT * FROM prompt_presets
WHERE uid = ? OR is_default = 1 WHERE uid = ? OR is_default = 1
ORDER BY is_default DESC, title ASC ORDER BY is_default DESC, title ASC
`; `;
db.all(query, [uid], (err, rows) => {
if (err) { try {
return db.prepare(query).all(uid) || [];
} catch (err) {
console.error('SQLite: Failed to get presets:', err); console.error('SQLite: Failed to get presets:', err);
reject(err); throw err;
} else {
resolve(rows || []);
} }
});
});
} }
function getPresetTemplates() { function getPresetTemplates() {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = ` const query = `
SELECT * FROM prompt_presets SELECT * FROM prompt_presets
WHERE is_default = 1 WHERE is_default = 1
ORDER BY title ASC ORDER BY title ASC
`; `;
db.all(query, [], (err, rows) => {
if (err) { try {
return db.prepare(query).all() || [];
} catch (err) {
console.error('SQLite: Failed to get preset templates:', err); console.error('SQLite: Failed to get preset templates:', err);
reject(err); throw err;
} else {
resolve(rows || []);
} }
});
});
} }
function createPreset({ uid, title, prompt }) { function createPreset({ uid, title, prompt }) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const id = require('crypto').randomUUID(); const id = require('crypto').randomUUID();
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = ` const query = `
INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state) INSERT INTO prompt_presets (id, uid, title, prompt, is_default, created_at, sync_state)
VALUES (?, ?, ?, ?, 0, ?, 'dirty') VALUES (?, ?, ?, ?, 0, ?, 'dirty')
`; `;
db.run(query, [id, uid, title, prompt, now], function(err) {
if (err) { try {
db.prepare(query).run(id, uid, title, prompt, now);
return { id };
} catch (err) {
console.error('SQLite: Failed to create preset:', err); console.error('SQLite: Failed to create preset:', err);
reject(err); throw err;
} else {
resolve({ id });
} }
});
});
} }
function updatePreset(id, { title, prompt }, uid) { function updatePreset(id, { title, prompt }, uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const query = ` const query = `
UPDATE prompt_presets UPDATE prompt_presets
SET title = ?, prompt = ?, sync_state = 'dirty', updated_at = ? SET title = ?, prompt = ?, sync_state = 'dirty', updated_at = ?
WHERE id = ? AND uid = ? AND is_default = 0 WHERE id = ? AND uid = ? AND is_default = 0
`; `;
db.run(query, [title, prompt, now, id, uid], function(err) {
if (err) { try {
console.error('SQLite: Failed to update preset:', err); const result = db.prepare(query).run(title, prompt, now, id, uid);
reject(err); if (result.changes === 0) {
} else if (this.changes === 0) { throw new Error('Preset not found, is default, or permission denied');
reject(new Error('Preset not found, is default, or permission denied')); }
} else { return { changes: result.changes };
resolve({ changes: this.changes }); } catch (err) {
console.error('SQLite: Failed to update preset:', err);
throw err;
} }
});
});
} }
function deletePreset(id, uid) { function deletePreset(id, uid) {
const db = sqliteClient.getDb(); const db = sqliteClient.getDb();
return new Promise((resolve, reject) => {
const query = ` const query = `
DELETE FROM prompt_presets DELETE FROM prompt_presets
WHERE id = ? AND uid = ? AND is_default = 0 WHERE id = ? AND uid = ? AND is_default = 0
`; `;
db.run(query, [id, uid], function(err) {
if (err) { try {
console.error('SQLite: Failed to delete preset:', err); const result = db.prepare(query).run(id, uid);
reject(err); if (result.changes === 0) {
} else if (this.changes === 0) { throw new Error('Preset not found, is default, or permission denied');
reject(new Error('Preset not found, is default, or permission denied')); }
} else { return { changes: result.changes };
resolve({ changes: this.changes }); } catch (err) {
console.error('SQLite: Failed to delete preset:', err);
throw err;
} }
});
});
} }
module.exports = { module.exports = {

View File

@ -18,7 +18,6 @@ const { initializeFirebase } = require('./common/services/firebaseClient');
const databaseInitializer = require('./common/services/databaseInitializer'); const databaseInitializer = require('./common/services/databaseInitializer');
const authService = require('./common/services/authService'); const authService = require('./common/services/authService');
const path = require('node:path'); const path = require('node:path');
const { Deeplink } = require('electron-deeplink');
const express = require('express'); const express = require('express');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
@ -33,72 +32,86 @@ let WEB_PORT = 3000;
const listenService = new ListenService(); const listenService = new ListenService();
// Make listenService globally accessible so other modules (e.g., windowManager, askService) can reuse the same instance // Make listenService globally accessible so other modules (e.g., windowManager, askService) can reuse the same instance
global.listenService = listenService; global.listenService = listenService;
let deeplink = null; // Initialize as null
let pendingDeepLinkUrl = null; // Store any deep link that arrives before initialization
function createMainWindows() { // Native deep link handling - cross-platform compatible
createWindows(); let pendingDeepLinkUrl = null;
const { windowPool } = require('./electron/windowManager'); function setupProtocolHandling() {
const headerWindow = windowPool.get('header'); // Protocol registration - must be done before app is ready
if (!app.isDefaultProtocolClient('pickleglass')) {
app.setAsDefaultProtocolClient('pickleglass');
console.log('[Protocol] Set as default protocol client for pickleglass://');
}
// Initialize deeplink after windows are created // Handle protocol URLs on Windows/Linux
if (!deeplink && headerWindow) { app.on('second-instance', (event, commandLine, workingDirectory) => {
try { // Focus existing window first
deeplink = new Deeplink({ focusMainWindow();
app,
mainWindow: headerWindow, // Look for protocol URL in command line arguments
protocol: 'pickleglass', const protocolUrl = commandLine.find(arg => arg.startsWith('pickleglass://'));
isDev: !app.isPackaged, if (protocolUrl) {
debugLogging: true console.log('[Protocol] Received URL from second instance:', protocolUrl);
handleCustomUrl(protocolUrl);
}
}); });
deeplink.on('received', (url) => { // Handle protocol URLs on macOS
console.log('[deeplink] received:', url); app.on('open-url', (event, url) => {
event.preventDefault();
console.log('[Protocol] Received URL via open-url:', url);
if (!url || !url.startsWith('pickleglass://')) {
console.warn('[Protocol] Invalid URL format:', url);
return;
}
if (app.isReady()) {
handleCustomUrl(url); handleCustomUrl(url);
} else {
pendingDeepLinkUrl = url;
console.log('[Protocol] App not ready, storing URL for later');
}
}); });
console.log('[deeplink] Initialized with main window');
// Handle any pending deep link
if (pendingDeepLinkUrl) {
console.log('[deeplink] Processing pending deep link:', pendingDeepLinkUrl);
handleCustomUrl(pendingDeepLinkUrl);
pendingDeepLinkUrl = null;
}
} catch (error) {
console.error('[deeplink] Failed to initialize deep link:', error);
deeplink = null;
}
}
} }
function focusMainWindow() {
const { windowPool } = require('./electron/windowManager');
if (windowPool) {
const header = windowPool.get('header');
if (header && !header.isDestroyed()) {
if (header.isMinimized()) header.restore();
header.focus();
return true;
}
}
// Fallback: focus any available window
const windows = BrowserWindow.getAllWindows();
if (windows.length > 0) {
const mainWindow = windows[0];
if (!mainWindow.isDestroyed()) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
return true;
}
}
return false;
}
// Setup protocol handling before app.whenReady()
setupProtocolHandling();
app.whenReady().then(async () => { app.whenReady().then(async () => {
// Single instance lock - must be first
const gotTheLock = app.requestSingleInstanceLock(); const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) { if (!gotTheLock) {
app.quit(); app.quit();
return; return;
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
const { windowPool } = require('./electron/windowManager');
if (windowPool) {
const header = windowPool.get('header');
if (header) {
if (header.isMinimized()) header.restore();
header.focus();
return;
}
}
const windows = BrowserWindow.getAllWindows();
if (windows.length > 0) {
const mainWindow = windows[0];
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
} }
// Initialize core services
initializeFirebase(); initializeFirebase();
databaseInitializer.initialize() databaseInitializer.initialize()
@ -121,9 +134,16 @@ app.whenReady().then(async () => {
WEB_PORT = await startWebStack(); WEB_PORT = await startWebStack();
console.log('Web front-end listening on', WEB_PORT); console.log('Web front-end listening on', WEB_PORT);
createMainWindows(); createWindows();
initAutoUpdater(); initAutoUpdater();
// Process any pending deep link after everything is initialized
if (pendingDeepLinkUrl) {
console.log('[Protocol] Processing pending URL:', pendingDeepLinkUrl);
handleCustomUrl(pendingDeepLinkUrl);
pendingDeepLinkUrl = null;
}
}); });
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
@ -142,34 +162,17 @@ app.on('before-quit', async () => {
app.on('activate', () => { app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) { if (BrowserWindow.getAllWindows().length === 0) {
createMainWindows(); createWindows();
} }
}); });
// Add macOS native deep link handling as fallback
app.on('open-url', (event, url) => {
event.preventDefault();
console.log('[app] open-url received:', url);
if (!deeplink) {
// Store the URL if deeplink isn't ready yet
pendingDeepLinkUrl = url;
console.log('[app] Deep link stored for later processing');
} else {
handleCustomUrl(url);
}
});
// Ensure app can handle the protocol
app.setAsDefaultProtocolClient('pickleglass');
function setupGeneralIpcHandlers() { function setupGeneralIpcHandlers() {
const userRepository = require('./common/repositories/user'); const userRepository = require('./common/repositories/user');
const presetRepository = require('./common/repositories/preset'); const presetRepository = require('./common/repositories/preset');
ipcMain.handle('save-api-key', async (event, apiKey) => { ipcMain.handle('save-api-key', (event, apiKey) => {
try { try {
await userRepository.saveApiKey(apiKey, authService.getCurrentUserId()); userRepository.saveApiKey(apiKey, authService.getCurrentUserId());
BrowserWindow.getAllWindows().forEach(win => { BrowserWindow.getAllWindows().forEach(win => {
win.webContents.send('api-key-updated'); win.webContents.send('api-key-updated');
}); });
@ -180,12 +183,12 @@ function setupGeneralIpcHandlers() {
} }
}); });
ipcMain.handle('get-user-presets', async () => { ipcMain.handle('get-user-presets', () => {
return await presetRepository.getPresets(authService.getCurrentUserId()); return presetRepository.getPresets(authService.getCurrentUserId());
}); });
ipcMain.handle('get-preset-templates', async () => { ipcMain.handle('get-preset-templates', () => {
return await presetRepository.getPresetTemplates(); return presetRepository.getPresetTemplates();
}); });
ipcMain.handle('start-firebase-auth', async () => { ipcMain.handle('start-firebase-auth', async () => {
@ -204,7 +207,7 @@ function setupGeneralIpcHandlers() {
return process.env.pickleglass_WEB_URL || 'http://localhost:3000'; return process.env.pickleglass_WEB_URL || 'http://localhost:3000';
}); });
ipcMain.handle('get-current-user', async () => { ipcMain.handle('get-current-user', () => {
return authService.getCurrentUser(); return authService.getCurrentUser();
}); });
@ -220,72 +223,72 @@ function setupWebDataHandlers() {
const userRepository = require('./common/repositories/user'); const userRepository = require('./common/repositories/user');
const presetRepository = require('./common/repositories/preset'); const presetRepository = require('./common/repositories/preset');
const handleRequest = async (channel, responseChannel, payload) => { const handleRequest = (channel, responseChannel, payload) => {
let result; let result;
const currentUserId = authService.getCurrentUserId(); const currentUserId = authService.getCurrentUserId();
try { try {
switch (channel) { switch (channel) {
// SESSION // SESSION
case 'get-sessions': case 'get-sessions':
result = await sessionRepository.getAllByUserId(currentUserId); result = sessionRepository.getAllByUserId(currentUserId);
break; break;
case 'get-session-details': case 'get-session-details':
const session = await sessionRepository.getById(payload); const session = sessionRepository.getById(payload);
if (!session) { if (!session) {
result = null; result = null;
break; break;
} }
const transcripts = await sttRepository.getAllTranscriptsBySessionId(payload); const transcripts = sttRepository.getAllTranscriptsBySessionId(payload);
const ai_messages = await askRepository.getAllAiMessagesBySessionId(payload); const ai_messages = askRepository.getAllAiMessagesBySessionId(payload);
const summary = await summaryRepository.getSummaryBySessionId(payload); const summary = summaryRepository.getSummaryBySessionId(payload);
result = { session, transcripts, ai_messages, summary }; result = { session, transcripts, ai_messages, summary };
break; break;
case 'delete-session': case 'delete-session':
result = await sessionRepository.deleteWithRelatedData(payload); result = sessionRepository.deleteWithRelatedData(payload);
break; break;
case 'create-session': case 'create-session':
const id = await sessionRepository.create(currentUserId, 'ask'); const id = sessionRepository.create(currentUserId, 'ask');
if (payload.title) { if (payload.title) {
await sessionRepository.updateTitle(id, payload.title); sessionRepository.updateTitle(id, payload.title);
} }
result = { id }; result = { id };
break; break;
// USER // USER
case 'get-user-profile': case 'get-user-profile':
result = await userRepository.getById(currentUserId); result = userRepository.getById(currentUserId);
break; break;
case 'update-user-profile': case 'update-user-profile':
result = await userRepository.update({ uid: currentUserId, ...payload }); result = userRepository.update({ uid: currentUserId, ...payload });
break; break;
case 'find-or-create-user': case 'find-or-create-user':
result = await userRepository.findOrCreate(payload); result = userRepository.findOrCreate(payload);
break; break;
case 'save-api-key': case 'save-api-key':
result = await userRepository.saveApiKey(payload, currentUserId); result = userRepository.saveApiKey(payload, currentUserId);
break; break;
case 'check-api-key-status': case 'check-api-key-status':
const user = await userRepository.getById(currentUserId); const user = userRepository.getById(currentUserId);
result = { hasApiKey: !!user?.api_key && user.api_key.length > 0 }; result = { hasApiKey: !!user?.api_key && user.api_key.length > 0 };
break; break;
case 'delete-account': case 'delete-account':
result = await userRepository.deleteById(currentUserId); result = userRepository.deleteById(currentUserId);
break; break;
// PRESET // PRESET
case 'get-presets': case 'get-presets':
result = await presetRepository.getPresets(currentUserId); result = presetRepository.getPresets(currentUserId);
break; break;
case 'create-preset': case 'create-preset':
result = await presetRepository.create({ ...payload, uid: currentUserId }); result = presetRepository.create({ ...payload, uid: currentUserId });
settingsService.notifyPresetUpdate('created', result.id, payload.title); settingsService.notifyPresetUpdate('created', result.id, payload.title);
break; break;
case 'update-preset': case 'update-preset':
result = await presetRepository.update(payload.id, payload.data, currentUserId); result = presetRepository.update(payload.id, payload.data, currentUserId);
settingsService.notifyPresetUpdate('updated', payload.id, payload.data.title); settingsService.notifyPresetUpdate('updated', payload.id, payload.data.title);
break; break;
case 'delete-preset': case 'delete-preset':
result = await presetRepository.delete(payload, currentUserId); result = presetRepository.delete(payload, currentUserId);
settingsService.notifyPresetUpdate('deleted', payload); settingsService.notifyPresetUpdate('deleted', payload);
break; break;
@ -295,13 +298,13 @@ function setupWebDataHandlers() {
const batchResult = {}; const batchResult = {};
if (includes.includes('profile')) { if (includes.includes('profile')) {
batchResult.profile = await userRepository.getById(currentUserId); batchResult.profile = userRepository.getById(currentUserId);
} }
if (includes.includes('presets')) { if (includes.includes('presets')) {
batchResult.presets = await presetRepository.getPresets(currentUserId); batchResult.presets = presetRepository.getPresets(currentUserId);
} }
if (includes.includes('sessions')) { if (includes.includes('sessions')) {
batchResult.sessions = await sessionRepository.getAllByUserId(currentUserId); batchResult.sessions = sessionRepository.getAllByUserId(currentUserId);
} }
result = batchResult; result = batchResult;
break; break;
@ -392,7 +395,7 @@ async function handleFirebaseAuthCallback(params) {
}; };
// 1. Sync user data to local DB // 1. Sync user data to local DB
await userRepository.findOrCreate(firebaseUser); userRepository.findOrCreate(firebaseUser);
console.log('[Auth] User data synced with local DB.'); console.log('[Auth] User data synced with local DB.');
// 2. Sign in using the authService in the main process // 2. Sign in using the authService in the main process