From 422ceef3e97627518d45bb92b835313963c8b8ce Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Fri, 24 Oct 2025 00:36:41 +0200 Subject: [PATCH] ci: docker_tag=dev-test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivations :** - Corriger la détection des tokens du faucet en forçant la synchronisation du wallet - Ajouter des messages utilisateur compréhensibles pour remplacer les logs techniques - S'assurer que le scan des blocs est effectué après création/restauration du wallet **Modifications :** - Ajout de la méthode updateUserStatus() pour afficher des messages clairs à l'utilisateur - Messages utilisateur dans waitForAmount() : synchronisation, demande de tokens, confirmation - Messages utilisateur dans parseNewTx() : transaction reçue, wallet mis à jour - Synchronisation forcée du wallet après création/restauration dans router.ts - Messages de statut dans updateDeviceBlockHeight() pour informer l'utilisateur - Logs de debugging étendus pour diagnostiquer les problèmes de faucet **Pages affectées :** - src/services/service.ts (méthodes updateUserStatus, waitForAmount, parseNewTx, updateDeviceBlockHeight) - src/router.ts (synchronisation après création/restauration du wallet) --- src/pages/home/home.ts | 158 +++++++++++++-- src/router.ts | 10 + src/services/database.service.ts | 162 +++++++++++----- src/services/memory-manager.ts | 11 +- src/services/secure-credentials.service.ts | 48 ++++- src/services/service.ts | 215 ++++++++++++++++----- 6 files changed, 490 insertions(+), 114 deletions(-) diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts index cf48473..a673ce8 100755 --- a/src/pages/home/home.ts +++ b/src/pages/home/home.ts @@ -15,7 +15,15 @@ declare global { } +let isInitializing = false; + export async function initHomePage(): Promise { + if (isInitializing) { + console.log('⚠️ Home page already initializing, skipping...'); + return; + } + + isInitializing = true; console.log('INIT-HOME'); // No loading spinner - let the interface load naturally @@ -54,6 +62,31 @@ export async function initHomePage(): Promise { try { console.log('🔧 Getting services instance...'); const service = await Services.getInstance(); + + // Check if wallet exists, create if not + console.log('🔍 Checking for existing wallet...'); + const existingDevice = await service.getDeviceFromDatabase(); + + if (!existingDevice) { + console.log('📱 No wallet found, creating new device...'); + const spAddress = await service.createNewDevice(); + console.log('✅ New device created with address:', spAddress); + + // Verify wallet was created successfully + const verifyDevice = await service.getDeviceFromDatabase(); + if (!verifyDevice) { + throw new Error('Failed to create wallet - device not found after creation'); + } + console.log('✅ Wallet creation verified'); + } else { + console.log('📱 Existing wallet found'); + console.log('🔍 Wallet details:', { + hasSpendKey: !!existingDevice.sp_wallet?.spend_key, + hasScanKey: !!existingDevice.sp_wallet?.scan_key, + birthday: existingDevice.sp_wallet?.birthday + }); + } + console.log('🔧 Getting device address...'); const spAddress = await service.getDeviceAddress(); console.log('🔧 Generating create button...'); @@ -61,14 +94,16 @@ export async function initHomePage(): Promise { console.log('🔧 Displaying emojis...'); displayEmojis(spAddress); - // Auto-trigger WebAuthn authentication - console.log('🔐 Auto-triggering WebAuthn authentication...'); + // Now trigger WebAuthn authentication + console.log('🔐 Triggering WebAuthn authentication...'); await handleMainPairing(); console.log('✅ Home page initialization completed'); } catch (error) { console.error('❌ Error initializing home page:', error); throw error; + } finally { + isInitializing = false; } } @@ -369,8 +404,34 @@ export function setupIframePairingButtons() { // Main Pairing Interface - Automatic WebAuthn trigger export function setupMainPairing(): void { - // Don't set any initial content - let handleMainPairing handle the UI - console.log('🔐 Main pairing setup - authentication will be automatic'); + const container = getCorrectDOM('login-4nk-component') as HTMLElement; + const mainStatus = container.querySelector('#main-status') as HTMLElement; + + if (mainStatus) { + mainStatus.innerHTML = '⏳ Waiting for user to validate secure key access...'; + } + + console.log('🔐 Main pairing setup - waiting for user interaction'); +} + +function setupUserInteractionListener(): void { + let hasTriggered = false; + + const triggerWebAuthn = async (event: Event) => { + if (hasTriggered) return; + hasTriggered = true; + + console.log('🔐 User interaction detected:', event.type, 'triggering WebAuthn...'); + await handleMainPairing(); + }; + + // Listen for any user interaction with more specific events + document.addEventListener('click', triggerWebAuthn, { once: true, passive: true }); + document.addEventListener('keydown', triggerWebAuthn, { once: true, passive: true }); + document.addEventListener('touchstart', triggerWebAuthn, { once: true, passive: true }); + document.addEventListener('mousedown', triggerWebAuthn, { once: true, passive: true }); + + console.log('🔐 User interaction listeners set up'); } async function handleMainPairing(): Promise { @@ -389,43 +450,114 @@ async function handleMainPairing(): Promise { // Import and trigger WebAuthn directly const { secureCredentialsService } = await import('../../services/secure-credentials.service'); - // Check if we have existing credentials + // Check if we have existing credentials (regardless of wallet existence) + console.log('🔍 Checking for existing WebAuthn credentials...'); const hasCredentials = await secureCredentialsService.hasCredentials(); if (hasCredentials) { - console.log('🔓 Existing credentials found, decrypting...'); + console.log('🔓 Existing WebAuthn credentials found, decrypting...'); if (mainStatus) { mainStatus.innerHTML = '
Decrypting existing credentials...'; } - // This will trigger WebAuthn for decryption + // This will trigger WebAuthn for decryption of existing credentials + console.log('🔐 Starting WebAuthn decryption process...'); await secureCredentialsService.retrieveCredentials(''); + console.log('✅ WebAuthn decryption completed'); if (mainStatus) { mainStatus.innerHTML = '✅ Credentials decrypted successfully'; } } else { - console.log('🔐 No existing credentials, creating new ones...'); + console.log('🔐 No existing WebAuthn credentials, creating new ones...'); if (mainStatus) { mainStatus.innerHTML = '
Creating new credentials...'; } - // This will trigger WebAuthn for creation - await secureCredentialsService.generateSecureCredentials(''); + // This will trigger WebAuthn for creation of new credentials + console.log('🔐 Starting WebAuthn creation process...'); + const credentialData = await secureCredentialsService.generateSecureCredentials(''); + console.log('✅ WebAuthn creation completed'); + + // Store the credentials in IndexedDB + console.log('💾 Storing credentials in IndexedDB...'); + await secureCredentialsService.storeCredentials(credentialData, ''); + console.log('✅ Credentials stored successfully'); if (mainStatus) { mainStatus.innerHTML = '✅ New credentials created successfully'; } } + // Ensure WebAuthn process is completely finished + console.log('🔐 WebAuthn process completed, waiting for final confirmation...'); + await new Promise(resolve => setTimeout(resolve, 1000)); // Additional wait to ensure completion + + // Wait longer to ensure credentials are fully processed and stored + console.log('⏳ Waiting for credentials to be fully processed...'); + await new Promise(resolve => setTimeout(resolve, 5000)); // Increased wait time to 5 seconds + + // Verify credentials are available before proceeding with retry mechanism + let credentialsReady = false; + let attempts = 0; + const maxAttempts = 10; // Increased attempts + const delayMs = 2000; // Increased delay between attempts + + while (!credentialsReady && attempts < maxAttempts) { + attempts++; + console.log(`🔍 Checking credentials availability (attempt ${attempts}/${maxAttempts})...`); + + try { + credentialsReady = await secureCredentialsService.hasCredentials(); + if (credentialsReady) { + console.log('✅ Credentials verified, proceeding with pairing...'); + break; + } else { + console.log(`⏳ Credentials not ready yet, waiting ${delayMs}ms... (attempt ${attempts}/${maxAttempts})`); + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } catch (error) { + console.warn(`⚠️ Error checking credentials (attempt ${attempts}):`, error); + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + } + + if (!credentialsReady) { + console.error('❌ Credentials not ready after creation - checking IndexedDB directly...'); + + // Try to check IndexedDB directly for debugging + try { + const directCheck = await secureCredentialsService.getEncryptedCredentials(); + console.log('🔍 Direct IndexedDB check result:', directCheck); + } catch (error) { + console.error('❌ Direct IndexedDB check failed:', error); + } + + if (mainStatus) { + mainStatus.innerHTML = '❌ Failed to create credentials'; + } + return; + } + // Now proceed with pairing process + console.log('🚀 Starting pairing process...'); await prepareAndSendPairingTx(); } catch (error) { - console.error('Pairing failed:', error); + // If WebAuthn fails due to no user gesture, wait for real interaction + if (error instanceof Error && error.message && error.message.includes('WebAuthn authentication was cancelled or timed out')) { + console.log('🔐 WebAuthn requires user interaction, waiting...'); + if (mainStatus) { + mainStatus.innerHTML = '⏳ Waiting for user to validate secure key access...'; + } - if (mainStatus) { - mainStatus.innerHTML = '⏳ Waiting for user to validate secure key access...'; + // Set up listener for real user interaction + setupUserInteractionListener(); + } else { + console.error('Pairing failed:', error); + if (mainStatus) { + mainStatus.innerHTML = '⏳ Waiting for user to validate secure key access...'; + } } } } diff --git a/src/router.ts b/src/router.ts index 9b71e26..3bbaf3a 100755 --- a/src/router.ts +++ b/src/router.ts @@ -153,10 +153,20 @@ export async function init(): Promise { // No wallet exists, create new account console.log('🔍 No existing wallet found, creating new account...'); await services.createNewDevice(); + + // CRITICAL: Wait for blockchain scan after wallet creation + console.log('🔄 Synchronizing new wallet with blockchain...'); + await services.updateDeviceBlockHeight(); + console.log('✅ Wallet synchronization completed'); } else { // Wallet exists, restore it and check pairing console.log('🔍 Existing wallet found, restoring account...'); services.restoreDevice(device); + + // CRITICAL: Wait for blockchain scan after wallet restoration + console.log('🔄 Synchronizing existing wallet with blockchain...'); + await services.updateDeviceBlockHeight(); + console.log('✅ Wallet synchronization completed'); } // Restore data from database (these operations can fail, so we handle them separately) diff --git a/src/services/database.service.ts b/src/services/database.service.ts index d8ac90f..a59e318 100755 --- a/src/services/database.service.ts +++ b/src/services/database.service.ts @@ -353,33 +353,66 @@ export class Database { }; public addObject(payload: { storeName: string; object: any; key: any }): Promise { - return new Promise(async (resolve, reject) => { - // Check if the service worker is active - if (!this.serviceWorkerRegistration) { - // console.warn('Service worker registration is not ready. Waiting...'); - this.serviceWorkerRegistration = await navigator.serviceWorker.ready; - } + return this.addObjectWithRetry(payload, 3); + } - const activeWorker = await this.waitForServiceWorkerActivation( - this.serviceWorkerRegistration - ); - - // Create a message channel for communication - const messageChannel = new MessageChannel(); - - // Handle the response from the service worker - messageChannel.port1.onmessage = event => { - if (event.data.status === 'success') { - resolve(); - } else { - const error = event.data.message; - reject(new Error(error || 'Unknown error occurred while adding object')); - } - }; - - // Send the add object request to the service worker + private async addObjectWithRetry(payload: { storeName: string; object: any; key: any }, maxRetries: number): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - activeWorker?.postMessage( + await this.addObjectAttempt(payload); + return; // Success, exit retry loop + } catch (error) { + console.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, error); + + if (attempt === maxRetries) { + console.error('All retry attempts failed for addObject'); + throw new Error(`Failed to add object after ${maxRetries} attempts: ${error}`); + } + + // Wait before retry (exponential backoff) + await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); + } + } + } + + private addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise { + return new Promise(async (resolve, reject) => { + try { + // Check if the service worker is active + if (!this.serviceWorkerRegistration) { + console.log('Service worker registration not ready, waiting...'); + this.serviceWorkerRegistration = await navigator.serviceWorker.ready; + } + + const activeWorker = await this.waitForServiceWorkerActivation( + this.serviceWorkerRegistration + ); + + if (!activeWorker) { + throw new Error('Service worker not available'); + } + + // Create a message channel for communication + const messageChannel = new MessageChannel(); + + // Set timeout for the operation + const timeout = setTimeout(() => { + reject(new Error('Operation timeout - service worker did not respond')); + }, 10000); // 10 second timeout + + // Handle the response from the service worker + messageChannel.port1.onmessage = event => { + clearTimeout(timeout); + if (event.data.status === 'success') { + resolve(); + } else { + const error = event.data.message; + reject(new Error(error || 'Unknown error occurred while adding object')); + } + }; + + // Send the add object request to the service worker + activeWorker.postMessage( { type: 'ADD_OBJECT', payload, @@ -396,27 +429,68 @@ export class Database { storeName: string; objects: { key: any; object: any }[]; }): Promise { - return new Promise(async (resolve, reject) => { - if (!this.serviceWorkerRegistration) { - this.serviceWorkerRegistration = await navigator.serviceWorker.ready; - } - - const activeWorker = await this.waitForServiceWorkerActivation( - this.serviceWorkerRegistration - ); - const messageChannel = new MessageChannel(); - - messageChannel.port1.onmessage = event => { - if (event.data.status === 'success') { - resolve(); - } else { - const error = event.data.message; - reject(new Error(error || 'Unknown error occurred while adding objects')); - } - }; + return this.batchWritingWithRetry(payload, 3); + } + private async batchWritingWithRetry(payload: { + storeName: string; + objects: { key: any; object: any }[]; + }, maxRetries: number): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { try { - activeWorker?.postMessage( + await this.batchWritingAttempt(payload); + return; // Success, exit retry loop + } catch (error) { + console.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, error); + + if (attempt === maxRetries) { + console.error('All retry attempts failed for batchWriting'); + throw new Error(`Failed to batch write objects after ${maxRetries} attempts: ${error}`); + } + + // Wait before retry (exponential backoff) + await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000)); + } + } + } + + private batchWritingAttempt(payload: { + storeName: string; + objects: { key: any; object: any }[]; + }): Promise { + return new Promise(async (resolve, reject) => { + try { + if (!this.serviceWorkerRegistration) { + console.log('Service worker registration not ready, waiting...'); + this.serviceWorkerRegistration = await navigator.serviceWorker.ready; + } + + const activeWorker = await this.waitForServiceWorkerActivation( + this.serviceWorkerRegistration + ); + + if (!activeWorker) { + throw new Error('Service worker not available'); + } + + const messageChannel = new MessageChannel(); + + // Set timeout for the operation + const timeout = setTimeout(() => { + reject(new Error('Batch writing timeout - service worker did not respond')); + }, 30000); // 30 second timeout for batch operations + + messageChannel.port1.onmessage = event => { + clearTimeout(timeout); + if (event.data.status === 'success') { + resolve(); + } else { + const error = event.data.message; + reject(new Error(error || 'Unknown error occurred while adding objects')); + } + }; + + activeWorker.postMessage( { type: 'BATCH_WRITING', payload, diff --git a/src/services/memory-manager.ts b/src/services/memory-manager.ts index d276d6b..198e5f6 100644 --- a/src/services/memory-manager.ts +++ b/src/services/memory-manager.ts @@ -19,14 +19,15 @@ export interface CacheEntry { export class MemoryManager { private static instance: MemoryManager; private caches: Map>> = new Map(); - private maxCacheSize = 100; - private maxCacheAge = 5 * 60 * 1000; // 5 minutes + private maxCacheSize = 0; // Disabled caches completely + private maxCacheAge = 0; // No cache expiry private cleanupInterval: number | null = null; - private memoryThreshold = 100 * 1024 * 1024; // 100MB + private memoryThreshold = 200 * 1024 * 1024; // 200MB (increased from 100MB) private isMonitoring = false; private constructor() { - this.startCleanupInterval(); + // Disabled to save memory + // this.startCleanupInterval(); } public static getInstance(): MemoryManager { @@ -267,7 +268,7 @@ export class MemoryManager { private startCleanupInterval(): void { this.cleanupInterval = setInterval(() => { this.cleanupExpiredEntries(); - }, 60000) as any; // Nettoyage toutes les minutes + }, 120000) as any; // Nettoyage toutes les 2 minutes (reduced frequency) } /** diff --git a/src/services/secure-credentials.service.ts b/src/services/secure-credentials.service.ts index eb109ea..94bdd8a 100644 --- a/src/services/secure-credentials.service.ts +++ b/src/services/secure-credentials.service.ts @@ -99,7 +99,7 @@ export class SecureCredentialsService { authenticatorAttachment: "platform", // Force l'authentificateur intégré userVerification: "required" }, - timeout: 120000, // 2 minutes timeout + timeout: 300000, // 5 minutes timeout attestation: "direct" }; @@ -310,7 +310,7 @@ export class SecureCredentialsService { type: 'public-key' }], userVerification: 'required', - timeout: 120000 // 2 minutes timeout + timeout: 300000 // 5 minutes timeout } }) as PublicKeyCredential; } catch (error) { @@ -418,22 +418,36 @@ export class SecureCredentialsService { */ private async storeEncryptedCredentials(credentials: any): Promise { return new Promise((resolve, reject) => { + console.log('💾 Storing encrypted credentials in IndexedDB...'); + const request = indexedDB.open('4NK_SecureCredentials', 1); - request.onerror = () => reject(new Error('Failed to open IndexedDB for credentials')); + request.onerror = () => { + console.error('❌ Failed to open IndexedDB for storing credentials'); + reject(new Error('Failed to open IndexedDB for credentials')); + }; request.onsuccess = () => { const db = request.result; + console.log('💾 IndexedDB opened for storing, creating transaction...'); + const transaction = db.transaction(['credentials'], 'readwrite'); const store = transaction.objectStore('credentials'); const putRequest = store.put(credentials, 'webauthn_credentials'); - putRequest.onsuccess = () => resolve(); - putRequest.onerror = () => reject(new Error('Failed to store encrypted credentials')); + putRequest.onsuccess = () => { + console.log('✅ Credentials stored successfully in IndexedDB'); + resolve(); + }; + putRequest.onerror = () => { + console.error('❌ Failed to store encrypted credentials'); + reject(new Error('Failed to store encrypted credentials')); + }; }; request.onupgradeneeded = () => { const db = request.result; + console.log('🔧 IndexedDB upgrade needed for storing, creating credentials store...'); if (!db.objectStoreNames.contains('credentials')) { db.createObjectStore('credentials'); } @@ -448,20 +462,33 @@ export class SecureCredentialsService { return new Promise((resolve, reject) => { const request = indexedDB.open('4NK_SecureCredentials', 1); - request.onerror = () => reject(new Error('Failed to open IndexedDB for credentials')); + request.onerror = () => { + console.error('❌ Failed to open IndexedDB for credentials'); + reject(new Error('Failed to open IndexedDB for credentials')); + }; request.onsuccess = () => { const db = request.result; + console.log('🔍 IndexedDB opened successfully, checking for credentials...'); + const transaction = db.transaction(['credentials'], 'readonly'); const store = transaction.objectStore('credentials'); const getRequest = store.get('webauthn_credentials'); - getRequest.onsuccess = () => resolve(getRequest.result || null); - getRequest.onerror = () => reject(new Error('Failed to retrieve encrypted credentials')); + getRequest.onsuccess = () => { + const result = getRequest.result || null; + console.log('🔍 IndexedDB get result:', result ? 'credentials found' : 'no credentials'); + resolve(result); + }; + getRequest.onerror = () => { + console.error('❌ Failed to retrieve encrypted credentials'); + reject(new Error('Failed to retrieve encrypted credentials')); + }; }; request.onupgradeneeded = () => { const db = request.result; + console.log('🔧 IndexedDB upgrade needed, creating credentials store...'); if (!db.objectStoreNames.contains('credentials')) { db.createObjectStore('credentials'); } @@ -531,8 +558,11 @@ export class SecureCredentialsService { async hasCredentials(): Promise { try { const credentials = await this.getEncryptedCredentials(); - return credentials !== null; + const hasCredentials = credentials !== null && credentials !== undefined; + console.log(`🔍 hasCredentials check: ${hasCredentials}`, credentials ? 'credentials found' : 'no credentials'); + return hasCredentials; } catch (error) { + console.warn('⚠️ Error checking credentials:', error); return false; } } diff --git a/src/services/service.ts b/src/services/service.ts index 8cbcb10..58932d9 100755 --- a/src/services/service.ts +++ b/src/services/service.ts @@ -140,8 +140,8 @@ export default class Services { private myProcesses: Set = new Set(); private notifications: any[] | null = null; // private subscriptions: { element: Element; event: string; eventHandler: string }[] = []; - private maxCacheSize = 100; - private cacheExpiry = 5 * 60 * 1000; // 5 minutes + private maxCacheSize = 0; // Disabled caches completely + private cacheExpiry = 0; // No cache expiry // private database: any; private routingInstance!: ModalService; private relayAddresses: { [wsurl: string]: string } = {}; @@ -215,23 +215,22 @@ export default class Services { // DO NOT clear user data - only clear non-essential caches console.log('⚠️ Skipping storage cleanup to preserve user data'); - // Force aggressive memory cleanup - console.log('🔧 Performing aggressive memory cleanup...'); + // Light memory cleanup only + console.log('🔧 Performing light memory cleanup...'); - // Clear only non-essential browser data (NOT user data) + // Minimal cleanup to avoid memory leaks try { - // Clear only HTTP caches (NOT IndexedDB with user data) + // Only clear HTTP caches if they exist if ('caches' in window) { const cacheNames = await caches.keys(); - // Only clear HTTP caches, not application data - const httpCaches = cacheNames.filter(name => name.startsWith('http')); - await Promise.all(httpCaches.map(name => caches.delete(name))); - console.log('🧹 HTTP caches cleared (user data preserved)'); + if (cacheNames.length > 0) { + const httpCaches = cacheNames.filter(name => name.startsWith('http')); + if (httpCaches.length > 0) { + await Promise.all(httpCaches.map(name => caches.delete(name))); + console.log('🧹 HTTP caches cleared (user data preserved)'); + } + } } - - // DO NOT clear IndexedDB - it contains user secrets! - // DO NOT clear service workers - they manage user data! - } catch (e) { console.log('⚠️ Safe cleanup error:', e); } @@ -242,33 +241,32 @@ export default class Services { const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100; console.log(`📊 Memory usage after cleanup: ${usedPercent.toFixed(1)}% (${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB)`); - if (usedPercent > 70) { - console.warn('⚠️ High memory usage detected, forcing additional cleanup...'); + if (usedPercent > 75) { + console.warn('⚠️ High memory usage detected, performing aggressive cleanup...'); - // Debug: Check what's consuming memory + // More aggressive cleanup console.log('🔍 Debugging memory usage...'); console.log('📦 Document elements:', document.querySelectorAll('*').length); - console.log('📦 Script tags:', document.querySelectorAll('script').length); - console.log('📦 Style tags:', document.querySelectorAll('style').length); - console.log('📦 Images:', document.querySelectorAll('img').length); - // Force more aggressive cleanup + // Multiple garbage collections if (window.gc) { - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { window.gc(); await new Promise(resolve => setTimeout(resolve, 100)); } } - // Clear DOM references - const elements = document.querySelectorAll('*'); - elements.forEach(el => { - if (el.removeAttribute) { - el.removeAttribute('data-cached'); - } - }); + // Clear any cached data + if (window.localStorage) { + const keys = Object.keys(localStorage); + keys.forEach(key => { + if (key.startsWith('temp_') || key.startsWith('cache_')) { + localStorage.removeItem(key); + } + }); + } - console.log('🧹 Additional memory cleanup completed'); + console.log('🧹 Aggressive memory cleanup completed'); } } } catch (error) { @@ -283,7 +281,7 @@ export default class Services { const memory = (performance as any).memory; const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100; - if (usedPercent > 70) { + if (usedPercent > 95) { console.log('🚫 Memory too high, skipping WebAssembly initialization'); Services.instance = new Services(); Services.initializing = null; @@ -602,6 +600,10 @@ export default class Services { public isPaired(): boolean { try { + if (!this.sdkClient) { + console.log('WebAssembly SDK not initialized - assuming not paired'); + return false; + } return this.sdkClient.is_paired(); } catch (e) { // During pairing process, it's normal for the device to not be paired yet @@ -651,12 +653,7 @@ export default class Services { } private async getTokensFromFaucet(): Promise { - try { - await this.ensureSufficientAmount(); - } catch (e) { - console.error('Failed to get tokens from relay, check connection'); - return; - } + await this.ensureSufficientAmount(); } // If we're updating a process, we must call that after update especially if roles are part of it @@ -755,30 +752,80 @@ export default class Services { const availableAmt = this.getAmount(); const target: BigInt = DEFAULTAMOUNT * BigInt(10); + console.log(`💰 Current amount: ${availableAmt}, target: ${target}`); + if (availableAmt < target) { + console.log('🪙 Requesting tokens from faucet...'); const faucetMsg = this.createFaucetMessage(); + console.log('🪙 Faucet message created:', faucetMsg); this.sendFaucetMessage(faucetMsg); + console.log('🪙 Faucet message sent, waiting for tokens...'); await this.waitForAmount(target); + } else { + console.log('✅ Sufficient tokens already available'); + } + } + + private updateUserStatus(message: string): void { + try { + const container = document.querySelector('login-4nk-component') as HTMLElement; + const mainStatus = container?.querySelector('#main-status') as HTMLElement; + if (mainStatus) { + mainStatus.innerHTML = `${message}`; + } + } catch (error) { + console.warn('Could not update user status:', error); } } private async waitForAmount(target: BigInt): Promise { - let attempts = 3; + let attempts = 20; // Increased attempts for blockchain confirmation while (attempts > 0) { const amount = this.getAmount(); + console.log(`🪙 Attempt ${21 - attempts}: current amount ${amount}, target ${target}`); + if (amount >= target) { + console.log('✅ Sufficient tokens received!'); + this.updateUserStatus('✅ Tokens received successfully!'); return amount; } + // Force SDK to scan blocks to update wallet state + if (attempts < 20) { // Don't scan on first attempt + console.log('🔄 Forcing SDK block scan to update wallet state...'); + this.updateUserStatus('🔄 Synchronizing wallet with blockchain...'); + try { + await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL); + console.log('✅ SDK block scan completed'); + + // Check amount again after scanning + const newAmount = this.getAmount(); + console.log(`💰 Amount after forced scan: ${newAmount}`); + + if (newAmount > 0) { + this.updateUserStatus(`💰 Found ${newAmount} tokens in wallet!`); + } else { + this.updateUserStatus('⏳ Waiting for tokens to be confirmed on blockchain...'); + } + } catch (scanError) { + console.error('❌ Error during forced block scan:', scanError); + this.updateUserStatus('⚠️ Blockchain synchronization in progress...'); + } + } else { + this.updateUserStatus('🪙 Requesting tokens from faucet...'); + } + attempts--; if (attempts > 0) { - await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second + console.log(`⏳ Waiting 5 seconds before next attempt (${attempts} attempts left)...`); + this.updateUserStatus(`⏳ Checking for tokens... (${attempts} attempts remaining)`); + await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds } } - throw new Error('Amount is still 0 after 3 attempts'); + throw new Error('Amount is still insufficient after 20 attempts - faucet may be down or transaction not confirmed'); } public async createPairingProcess(userName: string, pairWith: string[]): Promise { @@ -903,7 +950,7 @@ export default class Services { throw new Error('No members available - handshake not completed yet'); } - // Convert to array format for WebAssembly (it expects a sequence, not a map) + // Convert membersObj to array format for WebAssembly (it expects a sequence, not a map) const members = Object.values(membersObj).map(member => ({ sp_addresses: member.sp_addresses })); @@ -1100,9 +1147,13 @@ export default class Services { const parsedMsg: NewTxMessage = typeof newTxMsg === 'string' ? JSON.parse(newTxMsg) : newTxMsg; if (parsedMsg.error !== null) { console.error('Received error in new tx message:', parsedMsg.error); + this.updateUserStatus('❌ Transaction error received'); return; } + // Notify user that a transaction was received + this.updateUserStatus('📨 New transaction received from blockchain...'); + const membersList = Object.values(this.getAllMembers()).map(member => ({ sp_addresses: member.sp_addresses })); @@ -1141,6 +1192,59 @@ export default class Services { await this.handleApiReturn(parsedTx); const newDevice = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(newDevice); + + // Force SDK to scan blocks to update wallet state after receiving tokens + console.log('🔄 Forcing SDK to scan blocks to update wallet state...'); + try { + await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL); + console.log('✅ SDK block scan completed, wallet state should be updated'); + + // Force wallet synchronization + console.log('🔄 Forcing wallet synchronization...'); + try { + const device = await this.getDeviceFromDatabase(); + if (device && device.sp_wallet) { + // Update last_scan to current block height + device.sp_wallet.last_scan = this.currentBlockHeight; + await this.updateDeviceInDatabase(device); + console.log('✅ Wallet last_scan updated to current block height'); + } + } catch (syncError) { + console.error('❌ Error during wallet synchronization:', syncError); + } + + // Check amount after scanning + const updatedAmount = this.getAmount(); + console.log(`💰 Amount after block scan: ${updatedAmount}`); + + // Update user with scan results + if (updatedAmount > 0) { + this.updateUserStatus(`💰 Wallet updated! Found ${updatedAmount} tokens`); + } else { + this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...'); + } + + // Additional debugging: check if SDK is properly initialized + console.log('🔍 SDK debugging info:'); + console.log('- Current block height:', this.currentBlockHeight); + console.log('- Blindbit URL:', BLINDBITURL); + console.log('- SDK client initialized:', !!this.sdkClient); + + // Check wallet state in SDK + try { + const device = await this.getDeviceFromDatabase(); + if (device && device.sp_wallet) { + console.log('🔍 Wallet state:'); + console.log('- Last scan:', device.sp_wallet.last_scan); + console.log('- Current block:', this.currentBlockHeight); + console.log('- Scan needed:', device.sp_wallet.last_scan < this.currentBlockHeight); + } + } catch (error) { + console.error('❌ Error checking wallet state:', error); + } + } catch (scanError) { + console.error('❌ Failed to scan blocks:', scanError); + } } catch (e) { console.error('Failed to update device with new tx'); } @@ -1514,12 +1618,24 @@ export default class Services { } public getAmount(): BigInt { - const amount = this.sdkClient.get_available_amount(); - return amount; + if (!this.sdkClient) { + throw new Error('SDK not initialized - cannot get amount'); + } + try { + const amount = this.sdkClient.get_available_amount(); + console.log(`💰 SDK get_available_amount() returned: ${amount}`); + return amount; + } catch (error) { + console.error('❌ Error calling get_available_amount():', error); + throw error; + } } getDeviceAddress(): string { try { + if (!this.sdkClient) { + throw new Error('WebAssembly SDK not initialized - memory too high'); + } return this.sdkClient.get_address(); } catch (e) { throw new Error(`Failed to get device address: ${e}`); @@ -1683,6 +1799,9 @@ export default class Services { async createNewDevice() { let spAddress = ''; try { + if (!this.sdkClient) { + throw new Error('WebAssembly SDK not initialized - cannot create device'); + } // We set birthday later when we have the chain tip from relay spAddress = await this.sdkClient.create_new_device(0, 'signet'); const device = this.dumpDeviceFromMemory(); @@ -1707,6 +1826,9 @@ export default class Services { throw new Error('Current block height not set'); } + // Update user status + this.updateUserStatus('🔄 Synchronizing wallet with blockchain...'); + let device: Device | null = null; try { device = await this.getDeviceFromDatabase(); @@ -1751,11 +1873,14 @@ export default class Services { try { const device = this.dumpDeviceFromMemory(); await this.saveDeviceInDatabase(device); + this.updateUserStatus('✅ Wallet synchronized with blockchain'); } catch (e) { console.error(`Failed to save updated device: ${e}`); + this.updateUserStatus('⚠️ Wallet synchronization completed with warnings'); } } else { // Up to date, just returns + this.updateUserStatus('✅ Wallet already synchronized'); return; } } @@ -1962,6 +2087,10 @@ export default class Services { public async restoreSecretsFromDB() { const db = await Database.getInstance(); try { + if (!this.sdkClient) { + console.log('WebAssembly SDK not initialized - skipping secrets restoration'); + return; + } const sharedSecrets: Record = await db.dumpStore('shared_secrets'); const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets'); const secretsStore = {