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 { addSubscription } from '../../utils/subscription.utils';
import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils'; import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
import { getCorrectDOM } from '../../utils/html.utils'; import { getCorrectDOM } from '../../utils/html.utils';
@ -30,8 +30,8 @@ export async function initHomePage(): Promise<void> {
console.log('🔍 Verifying prerequisites...'); console.log('🔍 Verifying prerequisites...');
try { try {
console.log('🔧 Getting services instance...'); console.log('🔧 Getting device reader service...');
const service = await Services.getInstance(); const deviceReader = DeviceReaderService.getInstance();
// Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique) dans le store pbkdf2keys // 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 { SecureCredentialsService } = await import('../../services/secure-credentials.service');
@ -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) // 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) { if (!wallet) {
console.log('⚠️ Wallet not found, waiting for database synchronization...'); console.log('⚠️ Wallet not found, waiting for database synchronization...');
// Attendre un peu pour la synchronisation de la base de données // Attendre un peu pour la synchronisation de la base de données
for (let attempt = 0; attempt < 5; attempt++) { for (let attempt = 0; attempt < 5; attempt++) {
await new Promise(resolve => setTimeout(resolve, 500)); await new Promise(resolve => setTimeout(resolve, 500));
wallet = await service.getDeviceFromDatabase(); wallet = await deviceReader.getDeviceFromDatabase();
if (wallet) { if (wallet) {
console.log(`✅ Wallet found after ${attempt + 1} attempts`); console.log(`✅ Wallet found after ${attempt + 1} attempts`);
break; break;
@ -177,20 +177,23 @@ export async function initHomePage(): Promise<void> {
// After WebAuthn, get device address and setup UI // After WebAuthn, get device address and setup UI
console.log('🔧 Getting device address...'); console.log('🔧 Getting device address...');
try { try {
const spAddress = await service.getDeviceAddress(); const spAddress = await deviceReader.getDeviceAddress();
console.log('🔧 Generating create button...'); if (!spAddress) {
generateCreateBtn(); throw new Error('Device address not found');
console.log('🔧 Displaying emojis...'); }
displayEmojis(spAddress); console.log('🔧 Generating create button...');
} catch (error) { generateCreateBtn();
console.error('❌ Failed to get device address:', error); console.log('🔧 Displaying emojis...');
if ((error as Error).message.includes('Wallet keys not available')) { displayEmojis(spAddress);
console.error('❌ Wallet keys not available - authentication failed'); } catch (error) {
throw new Error('Authentication failed - wallet keys not available'); console.error('❌ Failed to get device address:', error);
} if ((error as Error).message.includes('Wallet keys not available')) {
throw error; 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'); console.log('✅ Home page initialization completed');
} catch (error) { } catch (error) {
@ -1195,8 +1198,7 @@ async function handleDeleteAccount(): Promise<void> {
mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>'; mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>';
} }
// Get services // Delete credentials
const service = await Services.getInstance();
const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance(); 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;
}
}
}