- Replace `electron-store` with OS-level encrypted storage for secure token, userId, and language management in `LocalSystem` and `keyManager`. - Add `init-user` IPC handler to initialize user data and manage encryption keys. - Update login process to handle encrypted storage saving with fallback for macOS issues. - Add offline warning component to `login/page.tsx` to handle first-time sync requirements. - Remove `electron-store` and associated dependencies from `package.json` and `package-lock.json`.
106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import type { IpcMainInvokeEvent } from 'electron';
|
|
import { getSecureStorage } from '../storage/SecureStorage.js';
|
|
|
|
// ============================================================
|
|
// SESSION MANAGEMENT - Auto-inject userId and lang
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get userId from secure storage (OS-encrypted)
|
|
* Set during login via 'login-success' event
|
|
*/
|
|
function getUserIdFromSession(): string | null {
|
|
const storage = getSecureStorage();
|
|
return storage.get<string>('userId', null);
|
|
}
|
|
|
|
/**
|
|
* Get lang from secure storage
|
|
* Set via 'set-lang' handler, defaults to 'fr'
|
|
*/
|
|
function getLangFromSession(): 'fr' | 'en' {
|
|
const storage = getSecureStorage();
|
|
return storage.get<'fr' | 'en'>('userLang', 'fr') as 'fr' | 'en';
|
|
}
|
|
|
|
// ============================================================
|
|
// UNIVERSAL HANDLER - Like a Fastify route
|
|
// Automatically injects: userId, lang
|
|
// Optional body parameter (for GET, POST, PUT, DELETE)
|
|
// Generic return type (void, object, etc.)
|
|
// ============================================================
|
|
|
|
/**
|
|
* Universal IPC handler - works like a Fastify route
|
|
* Automatically injects: userId, lang from session
|
|
*
|
|
* @template TBody - Request body type (use void for no params)
|
|
* @template TReturn - Response type (use void for no return)
|
|
*
|
|
* @example
|
|
* // GET with no params
|
|
* ipcMain.handle('db:books:getAll',
|
|
* createHandler<void, BookProps[]>(
|
|
* async (userId, body, lang) => {
|
|
* return await Book.getBooks(userId, lang);
|
|
* }
|
|
* )
|
|
* );
|
|
* // Frontend: invoke('db:books:getAll')
|
|
*
|
|
* @example
|
|
* // GET with 1 param
|
|
* ipcMain.handle('db:book:get',
|
|
* createHandler<string, BookProps>(
|
|
* async (userId, bookId, lang) => {
|
|
* return await Book.getBook(bookId, userId, lang);
|
|
* }
|
|
* )
|
|
* );
|
|
* // Frontend: invoke('db:book:get', bookId)
|
|
*
|
|
* @example
|
|
* // POST with object body
|
|
* ipcMain.handle('db:book:create',
|
|
* createHandler<CreateBookData, string>(
|
|
* async (userId, data, lang) => {
|
|
* return await Book.addBook(userId, data, lang);
|
|
* }
|
|
* )
|
|
* );
|
|
* // Frontend: invoke('db:book:create', { title: '...', ... })
|
|
*
|
|
* @example
|
|
* // DELETE with void return
|
|
* ipcMain.handle('db:book:delete',
|
|
* createHandler<string, void>(
|
|
* async (userId, bookId, lang) => {
|
|
* await Book.deleteBook(bookId, userId, lang);
|
|
* }
|
|
* )
|
|
* );
|
|
* // Frontend: invoke('db:book:delete', bookId)
|
|
*/
|
|
export function createHandler<TBody = void, TReturn = void>(
|
|
handler: (userId: string, body: TBody, lang: 'fr' | 'en') => TReturn | Promise<TReturn>
|
|
): (event: IpcMainInvokeEvent, body?: TBody) => Promise<TReturn> {
|
|
return async function(event: IpcMainInvokeEvent, body?: TBody): Promise<TReturn> {
|
|
const userId = getUserIdFromSession();
|
|
const lang = getLangFromSession();
|
|
|
|
if (!userId) {
|
|
throw new Error('User not authenticated');
|
|
}
|
|
|
|
try {
|
|
return await handler(userId, body as TBody, lang);
|
|
} catch (error: unknown) {
|
|
if (error instanceof Error) {
|
|
console.error(`[DB] ${error.message}`);
|
|
throw error;
|
|
}
|
|
throw new Error('An unknown error occurred.');
|
|
}
|
|
};
|
|
}
|