refactor: extract device reading to lightweight service to avoid WebAssembly initialization

**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)
This commit is contained in:
NicolasCantu 2025-10-29 15:38:45 +01:00
parent 9de7f1a5ed
commit 36adf1df12
3 changed files with 166 additions and 29 deletions

View File

@ -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<void> {
// 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<void> {
}
// 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<void> {
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<void> {
// 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 = `<span style="color: var(--error-color)">❌ Error: ${(error as Error).message}</span>`;
}
// 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<void> {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 2000);
}
throw error;
} finally {
isInitializing = false;
@ -1195,8 +1198,7 @@ async function handleDeleteAccount(): Promise<void> {
mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>';
}
// Get services
const service = await Services.getInstance();
// Delete credentials
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();

View File

@ -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<Device | null> {
console.log('🔍 DeviceReaderService: Reading device from database...');
// Utiliser directement IndexedDB
const db = await new Promise<IDBDatabase>((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<any>((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<string | null> {
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;
}
}
}

View File

@ -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;
}