ci: docker_tag=dev-test

**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)
This commit is contained in:
NicolasCantu 2025-10-24 00:36:41 +02:00
parent f46f82be7a
commit 422ceef3e9
6 changed files with 490 additions and 114 deletions

View File

@ -15,7 +15,15 @@ declare global {
}
let isInitializing = false;
export async function initHomePage(): Promise<void> {
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<void> {
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<void> {
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 = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
}
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<void> {
@ -389,43 +450,114 @@ async function handleMainPairing(): Promise<void> {
// 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 = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
}
// 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 = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
}
} else {
console.log('🔐 No existing credentials, creating new ones...');
console.log('🔐 No existing WebAuthn credentials, creating new ones...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new credentials...</span>';
}
// 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 = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
}
}
// 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 = '<span style="color: var(--error-color)">❌ Failed to create credentials</span>';
}
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 = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
}
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
// Set up listener for real user interaction
setupUserInteractionListener();
} else {
console.error('Pairing failed:', error);
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
}
}
}
}

View File

@ -153,10 +153,20 @@ export async function init(): Promise<void> {
// 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)

View File

@ -353,33 +353,66 @@ export class Database {
};
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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,

View File

@ -19,14 +19,15 @@ export interface CacheEntry<T> {
export class MemoryManager {
private static instance: MemoryManager;
private caches: Map<string, Map<string, CacheEntry<any>>> = 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)
}
/**

View File

@ -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<void> {
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<boolean> {
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;
}
}

View File

@ -140,8 +140,8 @@ export default class Services {
private myProcesses: Set<string> = 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<void> {
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 = `<span style="color: var(--info-color)">${message}</span>`;
}
} catch (error) {
console.warn('Could not update user status:', error);
}
}
private async waitForAmount(target: BigInt): Promise<BigInt> {
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<ApiReturn> {
@ -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<string, string> = await db.dumpStore('shared_secrets');
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets');
const secretsStore = {