- Add `OfflineContext` to manage offline state and interactions within components. - Refactor session logic in `ScribeControllerBar` and `page.tsx` to handle offline scenarios (e.g., check connectivity before enabling GPT features). - Enhance offline PIN setup and verification with better flow and error messaging. - Optimize database IPC handlers to initialize and sync data in offline mode. - Refactor code to clean up redundant logs and ensure stricter typings. - Improve consistency and structure in handling online and offline operations for smoother user experience.
162 lines
4.6 KiB
TypeScript
162 lines
4.6 KiB
TypeScript
import { ipcMain } from 'electron';
|
|
import { createHandler } from '../database/LocalSystem.js';
|
|
import * as bcrypt from 'bcrypt';
|
|
import { getSecureStorage } from '../storage/SecureStorage.js';
|
|
import { getDatabaseService } from '../database/database.service.js';
|
|
|
|
interface SetPinData {
|
|
pin: string;
|
|
}
|
|
|
|
interface VerifyPinData {
|
|
pin: string;
|
|
}
|
|
|
|
interface OfflineModeData {
|
|
enabled: boolean;
|
|
syncInterval?: number; // days
|
|
}
|
|
|
|
ipcMain.handle('offline:pin:set', async (_event, data: SetPinData) => {
|
|
try {
|
|
const storage = getSecureStorage();
|
|
const userId = storage.get<string>('userId');
|
|
|
|
if (!userId) {
|
|
return { success: false, error: 'No user logged in' };
|
|
}
|
|
|
|
// Hash the PIN
|
|
const hashedPin = await bcrypt.hash(data.pin, 10);
|
|
|
|
// Store hashed PIN
|
|
storage.set(`pin-${userId}`, hashedPin);
|
|
storage.save();
|
|
|
|
console.log('[Offline] PIN set for user');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[Offline] Error setting PIN:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
}
|
|
});
|
|
|
|
// Verify PIN for offline access
|
|
ipcMain.handle('offline:pin:verify', async (_event, data: VerifyPinData) => {
|
|
try {
|
|
const storage = getSecureStorage();
|
|
|
|
// Try to get last known userId
|
|
const lastUserId = storage.get<string>('lastUserId');
|
|
if (!lastUserId) {
|
|
return { success: false, error: 'No offline account found' };
|
|
}
|
|
|
|
const hashedPin = storage.get<string>(`pin-${lastUserId}`);
|
|
if (!hashedPin) {
|
|
return { success: false, error: 'No PIN configured' };
|
|
}
|
|
|
|
// Verify PIN
|
|
const isValid = await bcrypt.compare(data.pin, hashedPin);
|
|
|
|
if (isValid) {
|
|
// Set userId for session
|
|
storage.set('userId', lastUserId);
|
|
|
|
// Initialize database for offline use
|
|
const encryptionKey = storage.get<string>(`encryptionKey-${lastUserId}`);
|
|
if (encryptionKey) {
|
|
const db = getDatabaseService();
|
|
db.initialize(lastUserId, encryptionKey);
|
|
} else {
|
|
console.error('[Offline] No encryption key found for user');
|
|
return { success: false, error: 'No encryption key found' };
|
|
}
|
|
|
|
console.log('[Offline] PIN verified, user authenticated locally');
|
|
return {
|
|
success: true,
|
|
userId: lastUserId
|
|
};
|
|
}
|
|
|
|
return { success: false, error: 'Invalid PIN' };
|
|
} catch (error) {
|
|
console.error('[Offline] Error verifying PIN:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
}
|
|
});
|
|
|
|
// Set offline mode preference
|
|
ipcMain.handle('offline:mode:set', (_event, data: OfflineModeData) => {
|
|
try {
|
|
const storage = getSecureStorage();
|
|
storage.set('offlineMode', data.enabled);
|
|
|
|
if (data.syncInterval) {
|
|
storage.set('syncInterval', data.syncInterval);
|
|
}
|
|
|
|
storage.save();
|
|
console.log('[Offline] Mode set to:', data.enabled);
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('[Offline] Error setting mode:', error);
|
|
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
}
|
|
});
|
|
|
|
// Get offline mode status
|
|
ipcMain.handle('offline:mode:get', () => {
|
|
try {
|
|
const storage = getSecureStorage();
|
|
const offlineMode = storage.get<boolean>('offlineMode', false);
|
|
const syncInterval = storage.get<number>('syncInterval', 30);
|
|
const lastUserId = storage.get<string>('lastUserId');
|
|
const hasPin = lastUserId ? !!storage.get<string>(`pin-${lastUserId}`) : false;
|
|
|
|
return {
|
|
enabled: offlineMode,
|
|
syncInterval,
|
|
hasPin,
|
|
lastUserId
|
|
};
|
|
} catch (error) {
|
|
console.error('[Offline] Error getting mode:', error);
|
|
return {
|
|
enabled: false,
|
|
syncInterval: 30,
|
|
hasPin: false
|
|
};
|
|
}
|
|
});
|
|
|
|
// Check if should sync
|
|
ipcMain.handle('offline:sync:check', () => {
|
|
try {
|
|
const storage = getSecureStorage();
|
|
const lastSync = storage.get<string>('lastSync');
|
|
const syncInterval = storage.get<number>('syncInterval', 30) || 30;
|
|
|
|
if (!lastSync) {
|
|
return { shouldSync: true };
|
|
}
|
|
|
|
const daysSinceSync = Math.floor(
|
|
(Date.now() - new Date(lastSync).getTime()) / (1000 * 60 * 60 * 24)
|
|
);
|
|
|
|
return {
|
|
shouldSync: daysSinceSync >= syncInterval,
|
|
daysSinceSync,
|
|
syncInterval
|
|
};
|
|
} catch (error) {
|
|
console.error('[Offline] Error checking sync:', error);
|
|
return { shouldSync: false };
|
|
}
|
|
});
|
|
|
|
console.log('[IPC] Offline handlers registered'); |