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.
This commit is contained in:
150
electron/ipc/offline.ipc.ts
Normal file
150
electron/ipc/offline.ipc.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
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');
|
||||
Reference in New Issue
Block a user