Files
ERitors-Scribe-Desktop/electron/ipc/offline.ipc.ts
natreex 9e51cc93a8 Remove SyncService and introduce context-based offline mode and state management
- Delete `SyncService` and its associated bidirectional synchronization logic.
- Add multiple context providers (`OfflineProvider`, `AlertProvider`, `LangContext`, `UserContext`, `SessionContext`, `WorldContext`, `SettingBookContext`) for contextual state management.
- Implement `SecureStorage` for OS-level secure data encryption and replace dependency on `SyncService` synchronization.
- Update localization files (`en.json`, `fr.json`) with offline mode and error-related strings.
2025-11-19 22:01:24 -05:00

150 lines
4.1 KiB
TypeScript

import { ipcMain } from 'electron';
import { createHandler } from '../database/LocalSystem.js';
import * as bcrypt from 'bcrypt';
import { getSecureStorage } from '../storage/SecureStorage.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);
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');