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:
natreex
2025-11-19 22:01:24 -05:00
parent f85c2d2269
commit 9e51cc93a8
20 changed files with 961 additions and 484 deletions

View File

@@ -34,6 +34,7 @@ import {AIUsageContext} from "@/context/AIUsageContext";
import OfflineProvider from "@/context/OfflineProvider";
import OfflineContext from "@/context/OfflineContext";
import OfflinePinSetup from "@/components/offline/OfflinePinSetup";
import OfflinePinVerify from "@/components/offline/OfflinePinVerify";
const messagesMap = {
fr: frMessages,
@@ -72,6 +73,7 @@ function ScribeContent() {
const [isTermsAccepted, setIsTermsAccepted] = useState<boolean>(false);
const [homeStepsGuide, setHomeStepsGuide] = useState<boolean>(false);
const [showPinSetup, setShowPinSetup] = useState<boolean>(false);
const [showPinVerify, setShowPinVerify] = useState<boolean>(false);
const homeSteps: GuideStep[] = [
{
@@ -188,6 +190,52 @@ function ScribeContent() {
checkPinSetup();
}, [session.isConnected]); // Run when session connection status changes
async function handlePinVerifySuccess(userId: string): Promise<void> {
console.log('[OfflinePin] PIN verified successfully for user:', userId);
try {
// Initialize database with user's encryption key
if (window.electron) {
const encryptionKey:string|null = await window.electron.getUserEncryptionKey(userId);
if (encryptionKey) {
await window.electron.dbInitialize(userId, encryptionKey);
// Load user from local DB
const localUser = await window.electron.invoke('db:user:info');
if (localUser && localUser.success) {
// Use local data and continue in offline mode
setSession({
isConnected: true,
user: localUser.data,
accessToken: 'offline', // Special offline token
});
setShowPinVerify(false);
setCurrentCredits(localUser.data.creditsBalance || 0);
setAmountSpent(localUser.data.aiUsage || 0);
console.log('[OfflinePin] Running in offline mode');
} else {
errorMessage(t("homePage.errors.localDataError"));
if (window.electron) {
//window.electron.logout();
}
}
} else {
errorMessage(t("homePage.errors.encryptionKeyError"));
if (window.electron) {
//window.electron.logout();
}
}
}
} catch (error) {
console.error('[OfflinePin] Error initializing offline mode:', error);
errorMessage(t("homePage.errors.offlineModeError"));
if (window.electron) {
//window.electron.logout();
}
}
}
async function handleHomeTour(): Promise<void> {
try {
const response: boolean = await System.authPostToServer<boolean>('logs/tour', {
@@ -211,7 +259,6 @@ function ScribeContent() {
}
async function checkAuthentification(): Promise<void> {
// Essayer de récupérer le token depuis electron-store en priorité
let token: string | null = null;
if (typeof window !== 'undefined' && window.electron) {
@@ -222,11 +269,6 @@ function ScribeContent() {
}
}
// Fallback sur les cookies si pas d'Electron
if (!token) {
token = System.getCookie('token');
}
if (token) {
try {
const user: UserProps = await System.authGetQueryToServer<UserProps>('user/infos', token, locale);
@@ -239,6 +281,8 @@ function ScribeContent() {
}
return;
}
console.log('user: ' , user);
// Initialize user in Electron (sets userId and creates/gets encryption key)
if (window.electron && user.id) {
@@ -306,35 +350,19 @@ function ScribeContent() {
}
} catch (e: unknown) {
console.log('[Auth] Server error, checking offline mode...');
// Check if we can use offline mode
if (window.electron) {
try {
// Check offline mode status
const offlineStatus = await window.electron.invoke('offline:mode:get');
// If offline mode is enabled and we have local data
if (offlineStatus.enabled && offlineStatus.hasPin) {
console.log('[Auth] Offline mode enabled, loading local user data');
// Try to load user from local DB
try {
const localUser = await window.electron.invoke('db:user:info');
if (localUser && localUser.success) {
// Use local data
setSession({
isConnected: true,
user: localUser.data,
accessToken: 'offline', // Special offline token
});
setIsLoading(false);
// Show offline mode notification
console.log('[Auth] Running in offline mode');
return;
}
} catch (dbError) {
console.error('[Auth] Failed to load local user:', dbError);
const offlineStatus = await window.electron.offlineModeGet();
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
console.log('[Auth] Server unreachable but PIN configured, showing PIN verification');
setShowPinVerify(true);
setIsLoading(false);
return;
} else {
if (window.electron) {
await window.electron.removeToken();
window.electron.logout();
}
}
} catch (offlineError) {
@@ -342,23 +370,28 @@ function ScribeContent() {
}
}
// If not in offline mode or failed to load local data, show error and logout
// If not in offline mode or no PIN configured, show error and logout
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t("homePage.errors.authenticationError"));
}
// Token invalide/erreur auth, supprimer et logout
if (window.electron) {
await window.electron.removeToken();
window.electron.logout();
}
}
} else {
// Pas de token - en Electron cela ne devrait jamais arriver
// car main.ts vérifie le token avant d'ouvrir mainWindow
// Si on arrive ici, c'est une erreur - fermer et ouvrir login
if (window.electron) {
try {
const offlineStatus = await window.electron.offlineModeGet();
if (offlineStatus.hasPin && offlineStatus.lastUserId) {
console.log('[Auth] No token but PIN configured, showing PIN verification for offline mode');
setShowPinVerify(true);
setIsLoading(false);
return;
}
} catch (error) {
console.error('[Auth] Error checking offline mode:', error);
}
window.electron.logout();
}
}
@@ -475,6 +508,16 @@ function ScribeContent() {
/>
)
}
{
showPinVerify && window.electron && (
<OfflinePinVerify
onSuccess={handlePinVerifySuccess}
onCancel={():void => {
//window.electron.logout();
}}
/>
)
}
</AIUsageContext.Provider>
</ChapterContext.Provider>
</BookContext.Provider>