From 36adf1df120701d384256cceac24f2e9a4db6ee8 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Wed, 29 Oct 2025 15:38:45 +0100 Subject: [PATCH] refactor: extract device reading to lightweight service to avoid WebAssembly initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivations :** - home.ts utilise Services uniquement pour getDeviceFromDatabase() et getDeviceAddress() - Services initialise WebAssembly qui peut causer des erreurs de mémoire - Créer un service léger qui lit le device sans WebAssembly **Modifications :** - Créer DeviceReaderService qui lit le device depuis IndexedDB sans WebAssembly - Extraire getDeviceFromDatabase() et getDeviceAddress() dans DeviceReaderService - Modifier home.ts pour utiliser DeviceReaderService au lieu de Services - DeviceReaderService déchiffre le device et extrait l'adresse depuis sp_wallet.address - Supprimer l'import de Services dans home.ts **Pages affectées :** - src/services/device-reader.service.ts (nouveau service léger) - src/pages/home/home.ts (utilise DeviceReaderService au lieu de Services) --- src/pages/home/home.ts | 56 +++++------ src/services/device-reader.service.ts | 135 ++++++++++++++++++++++++++ src/services/service.ts | 4 +- 3 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 src/services/device-reader.service.ts diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index 825575e..c7e1907 100755 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -1,4 +1,4 @@ -import Services from '../../services/service'; +import { DeviceReaderService } from '../../services/device-reader.service'; import { addSubscription } from '../../utils/subscription.utils'; import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils'; import { getCorrectDOM } from '../../utils/html.utils'; @@ -28,11 +28,11 @@ export async function initHomePage(): Promise { // Vérifier les prérequis en base de données console.log('🔍 Verifying prerequisites...'); - + try { - console.log('🔧 Getting services instance...'); - const service = await Services.getInstance(); - + console.log('🔧 Getting device reader service...'); + const deviceReader = DeviceReaderService.getInstance(); + // Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique) dans le store pbkdf2keys const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const secureCredentials = SecureCredentialsService.getInstance(); @@ -67,13 +67,13 @@ export async function initHomePage(): Promise { } // Vérifier que le wallet existe en base (avec plusieurs tentatives pour gérer les problèmes de synchronisation) - let wallet = await service.getDeviceFromDatabase(); + let wallet = await deviceReader.getDeviceFromDatabase(); if (!wallet) { console.log('⚠️ Wallet not found, waiting for database synchronization...'); // Attendre un peu pour la synchronisation de la base de données for (let attempt = 0; attempt < 5; attempt++) { await new Promise(resolve => setTimeout(resolve, 500)); - wallet = await service.getDeviceFromDatabase(); + wallet = await deviceReader.getDeviceFromDatabase(); if (wallet) { console.log(`✅ Wallet found after ${attempt + 1} attempts`); break; @@ -88,7 +88,7 @@ export async function initHomePage(): Promise { return; } } - + // Vérifier que le wallet contient bien les données attendues if (wallet.sp_wallet && wallet.sp_wallet.birthday !== undefined) { console.log('✅ Wallet found in database with birthday:', wallet.sp_wallet.birthday); @@ -177,32 +177,35 @@ export async function initHomePage(): Promise { // After WebAuthn, get device address and setup UI console.log('🔧 Getting device address...'); - try { - const spAddress = await service.getDeviceAddress(); - console.log('🔧 Generating create button...'); - generateCreateBtn(); - console.log('🔧 Displaying emojis...'); - displayEmojis(spAddress); - } catch (error) { - console.error('❌ Failed to get device address:', error); - if ((error as Error).message.includes('Wallet keys not available')) { - console.error('❌ Wallet keys not available - authentication failed'); - throw new Error('Authentication failed - wallet keys not available'); - } - throw error; - } + try { + const spAddress = await deviceReader.getDeviceAddress(); + if (!spAddress) { + throw new Error('Device address not found'); + } + console.log('🔧 Generating create button...'); + generateCreateBtn(); + console.log('🔧 Displaying emojis...'); + displayEmojis(spAddress); + } catch (error) { + console.error('❌ Failed to get device address:', error); + if ((error as Error).message.includes('Wallet keys not available')) { + console.error('❌ Wallet keys not available - authentication failed'); + throw new Error('Authentication failed - wallet keys not available'); + } + throw error; + } console.log('✅ Home page initialization completed'); } catch (error) { console.error('❌ Error initializing home/pairing page:', error); - + // Afficher un message d'erreur à l'utilisateur const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; if (mainStatus) { mainStatus.innerHTML = `❌ Error: ${(error as Error).message}`; } - + // Si l'erreur est liée aux prérequis, rediriger vers la page appropriée const errorMessage = (error as Error).message; if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) { @@ -221,7 +224,7 @@ export async function initHomePage(): Promise { window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }, 2000); } - + throw error; } finally { isInitializing = false; @@ -1195,8 +1198,7 @@ async function handleDeleteAccount(): Promise { mainStatus.innerHTML = '
Deleting account and all data...'; } - // Get services - const service = await Services.getInstance(); + // Delete credentials const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const secureCredentialsService = SecureCredentialsService.getInstance(); diff --git a/src/services/device-reader.service.ts b/src/services/device-reader.service.ts new file mode 100644 index 0000000..0af8e7c --- /dev/null +++ b/src/services/device-reader.service.ts @@ -0,0 +1,135 @@ +/** + * Service léger pour lire le device depuis la base de données + * Sans nécessiter l'initialisation de WebAssembly + */ + +import { DATABASE_CONFIG } from './database-config'; +import { Device } from '../../pkg/sdk_client'; + +export class DeviceReaderService { + private static instance: DeviceReaderService | null = null; + + private constructor() {} + + public static getInstance(): DeviceReaderService { + if (!DeviceReaderService.instance) { + DeviceReaderService.instance = new DeviceReaderService(); + } + return DeviceReaderService.instance; + } + + /** + * Récupère le device depuis la base de données et le déchiffre + * Version légère sans nécessiter WebAssembly + */ + async getDeviceFromDatabase(): Promise { + console.log('🔍 DeviceReaderService: Reading device from database...'); + + // Utiliser directement IndexedDB + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); + + const walletStore = DATABASE_CONFIG.stores.wallet.name; + + try { + const dbRes = await new Promise((resolve, reject) => { + const tx = db.transaction(walletStore, 'readonly'); + const store = tx.objectStore(walletStore); + const getRequest = store.get('1'); + getRequest.onsuccess = () => resolve(getRequest.result); + getRequest.onerror = () => reject(getRequest.error); + }); + + if (!dbRes) { + console.log('🔍 DeviceReaderService: No device found in database'); + return null; + } + + // Check if data is encrypted (new format) or plain (old format) + if (dbRes['encrypted_device']) { + // New encrypted format - need to decrypt + console.log('🔐 DeviceReaderService: Device found in encrypted format, decrypting...'); + + // Get the PBKDF2 key based on security mode + const { SecureCredentialsService } = await import('./secure-credentials.service'); + const secureCredentialsService = SecureCredentialsService.getInstance(); + + // Get all security modes to find which one works + const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass']; + let pbkdf2Key: string | null = null; + let workingMode: string | null = null; + + for (const mode of allSecurityModes) { + try { + const hasKey = await secureCredentialsService.hasPBKDF2Key(mode as any); + if (hasKey) { + const key = await secureCredentialsService.retrievePBKDF2Key(mode as any); + if (key) { + pbkdf2Key = key; + workingMode = mode; + console.log(`✅ DeviceReaderService: PBKDF2 key found for mode: ${mode}`); + break; + } + } + } catch (e) { + // Continue to next mode + console.log(`⚠️ DeviceReaderService: No PBKDF2 key for mode ${mode}`); + } + } + + if (!pbkdf2Key) { + console.error('❌ DeviceReaderService: Failed to retrieve PBKDF2 key for decryption'); + throw new Error('PBKDF2 key not found - cannot decrypt device'); + } + + // Decrypt the device + const { EncryptionService } = await import('./encryption.service'); + const encryptionService = EncryptionService.getInstance(); + const decryptedDeviceString = await encryptionService.decrypt( + dbRes['encrypted_device'], + pbkdf2Key + ); + const device: Device = JSON.parse(decryptedDeviceString); + console.log('✅ DeviceReaderService: Device decrypted successfully'); + return device; + } else { + // Old plain format - return as is (should not happen in production) + console.warn('⚠️ DeviceReaderService: Device found in plain format (old format)'); + return dbRes as Device; + } + } catch (error) { + console.error('❌ DeviceReaderService: Error reading device:', error); + throw error; + } + } + + /** + * Récupère l'adresse du device depuis la base de données + * Version légère sans nécessiter WebAssembly + */ + async getDeviceAddress(): Promise { + try { + const device = await this.getDeviceFromDatabase(); + if (!device) { + return null; + } + + // L'adresse est stockée dans device.sp_wallet.address + const address = device.sp_wallet?.address; + if (address) { + console.log('✅ DeviceReaderService: Device address retrieved:', address); + return address; + } + + console.warn('⚠️ DeviceReaderService: Device found but no address in sp_wallet.address'); + return null; + } catch (error) { + console.error('❌ DeviceReaderService: Error getting device address:', error); + throw error; + } + } +} + diff --git a/src/services/service.ts b/src/services/service.ts index 2925734..49c23a8 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -333,14 +333,14 @@ export default class Services { console.error('❌ Service initialization failed:', error); // Réinitialiser initializing pour permettre une nouvelle tentative après un délai Services.initializing = null; - + // Si c'est une erreur de mémoire, ne pas réessayer immédiatement const errorMessage = (error as Error).message || String(error); if (errorMessage.includes('Out of memory') || errorMessage.includes('memory')) { console.error('🚫 Memory error detected - cannot retry immediately'); throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.'); } - + throw error; }