Add offline mode support with PIN configuration and management

- Introduce `OfflinePinSetup` component for users to configure secure offline access.
- Add new `AIUsageContext` and extend `OfflineProvider` for offline-related state management.
- Implement offline login functionality in `electron/main.ts` with PIN verification and fallback support.
- Enhance IPC handlers to manage offline mode data, PIN setup, and synchronization.
- Update localization files (`en.json`, `fr.json`) with offline mode and PIN-related strings.
- Add `bcrypt` and `@types/bcrypt` dependencies for secure PIN hashing and validation.
- Refactor login and session management to handle offline mode scenarios with improved error handling and flow.
This commit is contained in:
natreex
2025-11-19 19:58:55 -05:00
parent dde4683c38
commit f85c2d2269
10 changed files with 290 additions and 9 deletions

View File

@@ -33,6 +33,7 @@ import {LangContext} from "@/context/LangContext";
import {AIUsageContext} from "@/context/AIUsageContext";
import OfflineProvider from "@/context/OfflineProvider";
import OfflineContext from "@/context/OfflineContext";
import OfflinePinSetup from "@/components/offline/OfflinePinSetup";
const messagesMap = {
fr: frMessages,
@@ -70,6 +71,7 @@ function ScribeContent() {
const [isTermsAccepted, setIsTermsAccepted] = useState<boolean>(false);
const [homeStepsGuide, setHomeStepsGuide] = useState<boolean>(false);
const [showPinSetup, setShowPinSetup] = useState<boolean>(false);
const homeSteps: GuideStep[] = [
{
@@ -160,6 +162,31 @@ function ScribeContent() {
getLastChapter().then();
}
}, [currentBook]);
// Check for PIN setup after successful connection
useEffect(() => {
async function checkPinSetup() {
if (session.isConnected && window.electron) {
try {
const offlineStatus = await window.electron.offlineModeGet();
console.log('[Page] Session connected, offline status:', offlineStatus);
if (!offlineStatus.hasPin) {
console.log('[Page] No PIN configured, will show setup dialog');
// Show PIN setup dialog after a short delay
setTimeout(() => {
console.log('[Page] Showing PIN setup dialog');
setShowPinSetup(true);
}, 2000); // 2 seconds delay after page load
}
} catch (error) {
console.error('[Page] Error checking offline mode:', error);
}
}
}
checkPinSetup();
}, [session.isConnected]); // Run when session connection status changes
async function handleHomeTour(): Promise<void> {
try {
@@ -221,6 +248,23 @@ function ScribeContent() {
console.error('[Page] Failed to initialize user:', initResult.error);
} else {
console.log('[Page] User initialized successfully, key created:', initResult.keyCreated);
// Check if PIN is configured for offline mode
try {
const offlineStatus = await window.electron.offlineModeGet();
console.log('[Page] Offline status:', offlineStatus);
if (!offlineStatus.hasPin) {
// First login or no PIN configured yet
// Show PIN setup dialog after a short delay
console.log('[Page] No PIN configured, will show setup dialog');
setTimeout(() => {
console.log('[Page] Showing PIN setup dialog');
setShowPinSetup(true);
}, 2000); // 2 seconds delay after successful login
}
} catch (error) {
console.error('[Page] Error checking offline mode:', error);
}
}
} catch (error) {
console.error('[Page] Error initializing user:', error);
@@ -261,6 +305,44 @@ 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);
}
}
} catch (offlineError) {
console.error('[Auth] Error checking offline mode:', offlineError);
}
}
// If not in offline mode or failed to load local data, show error and logout
if (e instanceof Error) {
errorMessage(e.message);
} else {
@@ -381,6 +463,18 @@ function ScribeContent() {
{
!isTermsAccepted && <TermsOfUse onAccept={handleTermsAcceptance}/>
}
{
showPinSetup && window.electron && (
<OfflinePinSetup
showOnFirstLogin={true}
onClose={() => setShowPinSetup(false)}
onSuccess={() => {
setShowPinSetup(false);
console.log('[Page] PIN configured successfully');
}}
/>
)
}
</AIUsageContext.Provider>
</ChapterContext.Provider>
</BookContext.Provider>