import Services from '../../services/service'; import { addSubscription } from '../../utils/subscription.utils'; import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils'; import { getCorrectDOM } from '../../utils/html.utils'; import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing'; // Extend WindowEventMap to include custom events declare global { interface WindowEventMap { 'pairing-words-generated': CustomEvent; 'pairing-status-update': CustomEvent; 'pairing-success': CustomEvent; 'pairing-error': CustomEvent; } } 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 // Initialize iframe pairing, content menu, and communication only if in iframe if (window.parent !== window) { initIframePairing(); initContentMenu(); initIframeCommunication(); } // Set up iframe pairing button listeners setupIframePairingButtons(); // Set up main pairing interface (avec protection contre les appels multiples) if (!isMainPairingSetup) { setupMainPairing(); } // Set up account actions setupAccountActions(); const container = getCorrectDOM('login-4nk-component') as HTMLElement; container.querySelectorAll('.tab').forEach(tab => { addSubscription(tab, 'click', () => { container.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); container .querySelectorAll('.tab-content') .forEach(content => content.classList.remove('active')); container .querySelector(`#${tab.getAttribute('data-tab') as string}`) ?.classList.add('active'); }); }); try { console.log('🔧 Getting services instance...'); const service = await Services.getInstance(); // D'abord vérifier la sécurité avant de créer le wallet console.log('🔐 Checking security configuration...'); const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const secureCredentialsService = SecureCredentialsService.getInstance(); const hasCredentials = await secureCredentialsService.hasCredentials(); if (!hasCredentials) { console.log('🔐 No security credentials found, user must configure security first...'); // Afficher le sélecteur de mode de sécurité await handleMainPairing(); return; } // 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 }); } // Trigger WebAuthn authentication console.log('🔐 Triggering WebAuthn authentication...'); await handleMainPairing(); // Attendre que les credentials soient réellement disponibles avant de continuer console.log('⏳ Waiting for credentials to be fully available...'); await waitForCredentialsAvailability(); console.log('✅ Credentials confirmed as available, proceeding...'); // 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.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 page:', error); throw error; } finally { isInitializing = false; } } // Initialize iframe pairing component let iframePairing: IframePairingComponent | null = null; export function initIframePairing() { if (!iframePairing) { iframePairing = new IframePairingComponent(); iframePairing.createHiddenIframe(); // Listen for pairing events window.addEventListener('pairing-words-generated', (event: Event) => { const customEvent = event as CustomEvent; console.log('✅ 4 words generated via iframe:', customEvent.detail.words); // Update the UI with the generated words const creatorWordsElement = document.querySelector('#creator-words'); if (creatorWordsElement) { creatorWordsElement.textContent = customEvent.detail.words; creatorWordsElement.className = 'words-content active'; } // Send message to parent if (window.parent !== window) { window.parent.postMessage( { type: 'PAIRING_4WORDS_WORDS_GENERATED', data: { words: customEvent.detail.words }, }, '*' ); } }); window.addEventListener('pairing-status-update', (event: Event) => { const customEvent = event as CustomEvent; console.log('📊 Pairing status update:', customEvent.detail.status); // Update status indicators const statusElement = document.querySelector(`#${customEvent.detail.type}-status span`); if (statusElement) { statusElement.textContent = customEvent.detail.status; } // Send message to parent if (window.parent !== window) { window.parent.postMessage( { type: 'PAIRING_4WORDS_STATUS_UPDATE', data: { status: customEvent.detail.status, type: customEvent.detail.type }, }, '*' ); } }); window.addEventListener('pairing-success', (event: Event) => { const customEvent = event as CustomEvent; console.log('✅ Pairing successful:', customEvent.detail.message); // Send message to parent if (window.parent !== window) { window.parent.postMessage( { type: 'PAIRING_4WORDS_SUCCESS', data: { message: customEvent.detail.message }, }, '*' ); } // Handle successful pairing setTimeout(() => { window.location.href = '/account'; }, 2000); }); window.addEventListener('pairing-error', (event: Event) => { const customEvent = event as CustomEvent; console.error('❌ Pairing error:', customEvent.detail.error); // Send message to parent if (window.parent !== window) { window.parent.postMessage( { type: 'PAIRING_4WORDS_ERROR', data: { error: customEvent.detail.error }, }, '*' ); } // Handle pairing error alert(`Pairing error: ${customEvent.detail.error}`); }); } } // Initialize content menu (only in iframe mode) export function initContentMenu() { // Only add menu buttons if we're in an iframe if (window.parent !== window) { // Add iframe mode class to body document.body.classList.add('iframe-mode'); // Add menu buttons to title container const titleContainer = document.querySelector('.title-container'); if (titleContainer) { const menuHtml = `
`; titleContainer.insertAdjacentHTML('beforeend', menuHtml); } const menuButtons = document.querySelectorAll('.menu-btn'); menuButtons.forEach(button => { button.addEventListener('click', () => { // Remove active class from all buttons menuButtons.forEach(btn => btn.classList.remove('active')); // Add active class to clicked button button.classList.add('active'); const page = button.getAttribute('data-page'); console.log(`Menu clicked: ${page}`); // Send message to parent window window.parent.postMessage( { type: 'MENU_NAVIGATION', data: { page }, }, '*' ); }); }); } } // Initialize iframe communication export function initIframeCommunication() { // Listen for messages from parent window window.addEventListener('message', event => { // Filter out browser extension messages first if ( event.data.source === 'react-devtools-content-script' || event.data.hello === true || !event.data.type || event.data.type.startsWith('Pass::') || event.data.type === 'PassClientScriptReady' ) { return; // Ignore browser extension messages } // Security check - in production, verify event.origin console.log('📨 Received message from parent:', event.data); const { type, data } = event.data; switch (type) { case 'TEST_MESSAGE': console.log('🧪 Test message received:', data.message); // Send response back to parent if (window.parent !== window) { window.parent.postMessage( { type: 'TEST_RESPONSE', data: { response: 'Hello from 4NK iframe!' }, }, '*' ); } break; case 'PAIRING_4WORDS_CREATE': console.log('🔐 Parent requested pairing creation'); createPairingViaIframe(); break; case 'PAIRING_4WORDS_JOIN': console.log('🔗 Parent requested pairing join with words:', data.words); joinPairingViaIframe(data.words); break; case 'LISTENING': console.log('👂 Parent is listening for messages'); break; case 'IFRAME_READY': console.log('✅ Iframe is ready and initialized'); break; default: console.log('❓ Unknown message type from parent:', type); } }); // Notify parent that iframe is ready if (window.parent !== window) { window.parent.postMessage( { type: 'IFRAME_READY', data: { service: '4nk-pairing' }, }, '*' ); console.log('📡 Notified parent that iframe is ready'); } } // Enhanced pairing functions using iframe export async function createPairingViaIframe() { if (!iframePairing) { initIframePairing(); } try { await iframePairing!.createPairing(); } catch (error) { console.error('Error creating pairing via iframe:', error); alert(`Error creating pairing: ${(error as Error).message}`); } } export async function joinPairingViaIframe(words: string) { if (!iframePairing) { initIframePairing(); } try { await iframePairing!.joinPairing(words); } catch (error) { console.error('Error joining pairing via iframe:', error); alert(`Error joining pairing: ${(error as Error).message}`); } } // Set up button listeners for iframe pairing export function setupIframePairingButtons() { // Create button listener const createButton = document.getElementById('createButton'); if (createButton) { createButton.addEventListener('click', async () => { console.log('🔐 Create button clicked - using iframe pairing'); await createPairingViaIframe(); }); } // Join button listener const joinButton = document.getElementById('joinButton'); const wordsInput = document.getElementById('wordsInput') as HTMLInputElement; if (joinButton && wordsInput) { // Enable join button when words are entered wordsInput.addEventListener('input', () => { const words = wordsInput.value.trim(); (joinButton as HTMLButtonElement).disabled = !words; }); joinButton.addEventListener('click', async () => { const words = wordsInput.value.trim(); if (words) { console.log('🔗 Join button clicked - using iframe pairing with words:', words); await joinPairingViaIframe(words); } }); } // Copy words button listener const copyWordsBtn = document.getElementById('copyWordsBtn'); if (copyWordsBtn) { copyWordsBtn.addEventListener('click', () => { const creatorWordsElement = document.querySelector('#creator-words'); if (creatorWordsElement && creatorWordsElement.textContent) { navigator.clipboard .writeText(creatorWordsElement.textContent) .then(() => { console.log('✅ Words copied to clipboard'); // Show feedback const originalText = copyWordsBtn.textContent; copyWordsBtn.textContent = '✅ Copied!'; setTimeout(() => { copyWordsBtn.textContent = originalText; }, 2000); }) .catch(err => { console.error('Failed to copy words:', err); }); } }); } } // Variable pour éviter les appels multiples à setupMainPairing let isMainPairingSetup = false; // Main Pairing Interface - Automatic WebAuthn trigger export function setupMainPairing(): void { // Protection contre les appels multiples if (isMainPairingSetup) { console.log('🔐 Main pairing already setup, skipping...'); return; } isMainPairingSetup = true; 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'); } /** * Affiche le sélecteur de mode de sécurisation */ async function showSecurityModeSelector(): Promise { return new Promise((resolve) => { // Créer le conteneur pour le sélecteur const selectorContainer = document.createElement('div'); selectorContainer.id = 'security-mode-selector-container'; selectorContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10000; backdrop-filter: blur(5px); `; // Créer le contenu du sélecteur const selectorContent = document.createElement('div'); selectorContent.style.cssText = ` background: white; border-radius: 12px; padding: 30px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); `; selectorContent.innerHTML = `

🔐 Mode de Sécurisation

Choisissez comment vous souhaitez sécuriser vos clés privées :

🔒 Proton Pass Sécurisé
Utilise Proton Pass pour l'authentification biométrique
🖥️ Authentificateur OS Sécurisé
Utilise l'authentificateur intégré de votre système
🌐 Navigateur Moyennement sécurisé
Utilise les fonctionnalités de sécurité du navigateur
📱 Application 2FA ⚠️ Non sécurisé
Stockage en clair avec authentification 2FA
🔑 Mot de Passe ⚠️ Non sauvegardé
Chiffrement par mot de passe (non sauvegardé, non récupérable)
🚨 Aucune Sécurité DANGEREUX
Stockage en clair sans aucune protection
`; selectorContainer.appendChild(selectorContent); document.body.appendChild(selectorContainer); let selectedMode: string | null = null; // Gestion des événements const options = selectorContent.querySelectorAll('.security-option'); const confirmBtn = selectorContent.querySelector('#confirm-security-mode') as HTMLButtonElement; const cancelBtn = selectorContent.querySelector('#cancel-security-mode') as HTMLButtonElement; // Sélection d'un mode options.forEach(option => { option.addEventListener('click', () => { options.forEach(opt => opt.style.borderColor = '#e1e8ed'); option.style.borderColor = '#27ae60'; option.style.background = '#f8fff8'; selectedMode = option.getAttribute('data-mode'); confirmBtn.disabled = false; confirmBtn.style.opacity = '1'; }); // Effet hover option.addEventListener('mouseenter', () => { if (option.style.borderColor !== '#27ae60') { option.style.borderColor = '#3498db'; } }); option.addEventListener('mouseleave', () => { if (option.style.borderColor !== '#27ae60') { option.style.borderColor = '#e1e8ed'; } }); }); // Confirmation confirmBtn.addEventListener('click', async () => { if (selectedMode) { console.log(`🔐 Security mode selected: ${selectedMode}`); // Vérifier si le mode nécessite une confirmation const { SecurityModeService } = await import('../../services/security-mode.service'); const securityModeService = SecurityModeService.getInstance(); const modeConfig = securityModeService.getSecurityModeConfig(selectedMode as any); if (modeConfig.requiresConfirmation) { // Afficher une alerte de sécurité const confirmed = await showSecurityWarning(modeConfig); if (!confirmed) { return; // L'utilisateur a annulé } } // Définir le mode de sécurisation await securityModeService.setSecurityMode(selectedMode as any); // Fermer le sélecteur document.body.removeChild(selectorContainer); // Réinitialiser les flags pour permettre la relance isPairingInProgress = false; pairingAttempts = 0; // Relancer l'authentification avec le mode sélectionné await handleMainPairing(); resolve(); } }); // Annulation cancelBtn.addEventListener('click', () => { console.log('❌ Security mode selection cancelled'); document.body.removeChild(selectorContainer); resolve(); }); // Fermer avec Escape const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { document.body.removeChild(selectorContainer); document.removeEventListener('keydown', handleEscape); resolve(); } }; document.addEventListener('keydown', handleEscape); }); } /** * Affiche une alerte de sécurité pour les modes non recommandés */ async function showSecurityWarning(modeConfig: any): Promise { return new Promise((resolve) => { const warningContainer = document.createElement('div'); warningContainer.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 10001; backdrop-filter: blur(5px); `; const warningContent = document.createElement('div'); warningContent.style.cssText = ` background: white; border-radius: 12px; padding: 30px; max-width: 500px; width: 90%; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); `; warningContent.innerHTML = `

⚠️ Attention - Mode de Sécurisation Non Recommandé

Vous avez choisi : ${modeConfig.name}

${modeConfig.description}

⚠️ Risques identifiés :
    ${modeConfig.warnings.map((warning: string) => `
  • ${warning}
  • `).join('')}

Recommandation : ${modeConfig.securityLevel === 'low' ? 'Nous vous recommandons fortement de choisir un mode plus sécurisé comme Proton Pass ou l\'authentificateur OS.' : 'Ce mode présente des risques de sécurité élevés. Assurez-vous de comprendre les implications.' }

`; warningContainer.appendChild(warningContent); document.body.appendChild(warningContainer); const understandCheckbox = warningContent.querySelector('#understand-risks') as HTMLInputElement; const confirmBtn = warningContent.querySelector('#confirm-risky-mode') as HTMLButtonElement; const cancelBtn = warningContent.querySelector('#cancel-risky-mode') as HTMLButtonElement; // Gestion de la checkbox understandCheckbox.addEventListener('change', () => { confirmBtn.disabled = !understandCheckbox.checked; confirmBtn.style.opacity = understandCheckbox.checked ? '1' : '0.5'; }); // Confirmation confirmBtn.addEventListener('click', () => { document.body.removeChild(warningContainer); resolve(true); }); // Annulation cancelBtn.addEventListener('click', () => { document.body.removeChild(warningContainer); resolve(false); }); // Fermer avec Escape const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') { document.body.removeChild(warningContainer); document.removeEventListener('keydown', handleEscape); resolve(false); } }; document.addEventListener('keydown', handleEscape); }); } /** * Attend que les credentials soient réellement disponibles */ // Variable pour éviter les appels multiples à waitForCredentialsAvailability let isWaitingForCredentials = false; async function waitForCredentialsAvailability(): Promise { // Protection contre les appels multiples if (isWaitingForCredentials) { console.log('🔍 Already waiting for credentials, skipping...'); return; } isWaitingForCredentials = true; try { const { secureCredentialsService } = await import('../../services/secure-credentials.service'); let attempts = 0; const maxAttempts = 20; const delayMs = 1000; while (attempts < maxAttempts) { attempts++; console.log(`🔍 Vérification de la disponibilité des credentials (tentative ${attempts}/${maxAttempts})...`); try { // Vérifier que les credentials sont réellement disponibles et accessibles const credentials = await secureCredentialsService.retrieveCredentials(''); if (credentials && credentials.spendKey && credentials.scanKey) { console.log('✅ Credentials confirmés comme disponibles'); return; } else { throw new Error('Credentials incomplets'); } } catch (error) { console.warn(`⚠️ Credentials pas encore disponibles (tentative ${attempts}):`, error); if (attempts < maxAttempts) { console.log(`⏳ Attente de ${delayMs}ms avant la prochaine tentative...`); await new Promise(resolve => setTimeout(resolve, delayMs)); } } } throw new Error('Credentials non disponibles après toutes les tentatives'); } finally { isWaitingForCredentials = false; } } // Variables pour éviter les appels multiples let isPairingInProgress = false; let pairingAttempts = 0; const MAX_PAIRING_ATTEMPTS = 1; async function handleMainPairing(): Promise { // Protection renforcée contre les appels multiples if (isPairingInProgress) { console.log('🔐 Pairing already in progress, skipping...'); return; } if (pairingAttempts >= MAX_PAIRING_ATTEMPTS) { console.log('🔐 Maximum pairing attempts reached, skipping...'); return; } isPairingInProgress = true; pairingAttempts++; const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; try { // Vérifier si un mode de sécurisation est déjà sélectionné const { SecurityModeService } = await import('../../services/security-mode.service'); const securityModeService = SecurityModeService.getInstance(); let currentMode: string | null = null; try { currentMode = await securityModeService.getCurrentMode(); } catch (error) { // Ignorer les erreurs de base de données lors du premier lancement console.log('🔐 No security mode configured yet (first launch)'); currentMode = null; } if (!currentMode) { // Aucun mode sélectionné, afficher le sélecteur console.log('🔐 No security mode selected, showing selector...'); if (mainStatus) { mainStatus.innerHTML = '
🔐 Please select your security mode...'; } // Réinitialiser le flag avant d'afficher le sélecteur isPairingInProgress = false; await showSecurityModeSelector(); return; // La fonction sera rappelée après sélection du mode } // Mode sélectionné, continuer avec l'authentification console.log(`🔐 Using security mode: ${currentMode}`); if (mainStatus) { mainStatus.innerHTML = '
Authenticating with selected security mode...'; } // Import and trigger authentication with selected mode const { secureCredentialsService } = await import('../../services/secure-credentials.service'); // Check if we have existing credentials (regardless of wallet existence) console.log('🔍 Checking for existing credentials...'); const hasCredentials = await secureCredentialsService.hasCredentials(); if (hasCredentials) { console.log('🔓 Existing credentials found, decrypting...'); if (mainStatus) { mainStatus.innerHTML = '
Decrypting existing credentials...'; } try { // This will trigger authentication for decryption of existing credentials console.log('🔐 Starting credentials decryption process...'); const decryptedCredentials = await secureCredentialsService.retrieveCredentials(''); if (!decryptedCredentials) { throw new Error('Failed to decrypt existing credentials - no data returned'); } console.log('✅ Credentials decryption completed successfully'); if (mainStatus) { mainStatus.innerHTML = '✅ Credentials decrypted successfully'; } } catch (error) { console.error('❌ Credentials decryption failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Failed to decrypt credentials. Please try again.'; } throw error; // Arrêter le processus si le déchiffrement échoue } } else { console.log('🔐 No existing credentials, creating new ones...'); if (mainStatus) { mainStatus.innerHTML = '
🔐 Setting up secure authentication...'; } try { // This will trigger authentication for creation of new credentials console.log('🔐 Starting credentials creation process...'); if (mainStatus) { mainStatus.innerHTML = '
🔐 Creating secure credentials with your device...'; } const credentialData = await secureCredentialsService.generateSecureCredentials('4nk-secure-password'); if (!credentialData || !credentialData.spendKey || !credentialData.scanKey) { throw new Error('Failed to generate valid credentials - missing spendKey or scanKey'); } console.log('✅ Credentials creation completed successfully'); // Store the credentials in IndexedDB console.log('💾 Storing credentials in IndexedDB...'); if (mainStatus) { mainStatus.innerHTML = '
💾 Securing credentials...'; } await secureCredentialsService.storeCredentials(credentialData, ''); console.log('✅ Credentials stored successfully'); // Decrypt and make keys available to SDK console.log('🔓 Decrypting credentials for SDK access...'); if (mainStatus) { mainStatus.innerHTML = '
🔓 Making keys available...'; } const retrievedCredentials = await secureCredentialsService.retrieveCredentials(''); if (!retrievedCredentials) { throw new Error('Failed to retrieve stored credentials - decryption failed'); } console.log('✅ Credentials decrypted and available'); if (mainStatus) { mainStatus.innerHTML = '✅ Secure authentication ready'; } } catch (error) { console.error('❌ Credentials creation/encryption/decryption failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Failed to create/encrypt/decrypt credentials. Please try again.'; } throw error; // Arrêter le processus si la génération/chiffrement/déchiffrement échoue } } // 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 { // Vérifier que les credentials sont réellement disponibles const credentials = await secureCredentialsService.retrieveCredentials(''); if (!credentials || !credentials.spendKey || !credentials.scanKey) { throw new Error('Credentials not properly available'); } credentialsReady = true; console.log('✅ Credentials verified as available'); } catch (error) { console.warn(`⚠️ Credentials not ready on attempt ${attempts}:`, error); if (attempts < maxAttempts) { console.log(`⏳ Waiting ${delayMs}ms before next attempt...`); await new Promise(resolve => setTimeout(resolve, delayMs)); } } } // Si les credentials ne sont toujours pas disponibles après tous les essais, arrêter le processus if (!credentialsReady) { console.error('❌ Credentials not available after all attempts - stopping process'); if (mainStatus) { mainStatus.innerHTML = '❌ Authentication failed - credentials not available'; } throw new Error('Credentials not available after maximum retry attempts'); } console.log('✅ Credentials verified, proceeding with pairing...'); // Now proceed with pairing process console.log('🚀 Starting pairing process...'); if (mainStatus) { mainStatus.innerHTML = '
🚀 Starting secure pairing process...'; } // Attendre que les credentials soient réellement disponibles avant de continuer await waitForCredentialsAvailability(); await prepareAndSendPairingTx(); } catch (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...'; } // Set up listener for real user interaction setupUserInteractionListener(); } else { console.error('Pairing failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Pairing failed: ' + (error as Error).message + ''; } throw error; } } finally { // Réinitialiser les flags pour permettre de nouveaux appels isPairingInProgress = false; // Ne pas réinitialiser pairingAttempts ici pour éviter les boucles infinies } } // Account Actions export function setupAccountActions(): void { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const deleteAccountButton = container.querySelector('#deleteAccountButton') as HTMLButtonElement; if (deleteAccountButton) { deleteAccountButton.addEventListener('click', async () => { await handleDeleteAccount(); }); } } async function handleDeleteAccount(): Promise { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; // Confirmation dialog const confirmed = confirm( '⚠️ WARNING: This will permanently delete your account and all associated data.\n\n' + 'This action cannot be undone!\n\n' + 'Are you sure you want to delete your account?' ); if (!confirmed) { return; } // Double confirmation const doubleConfirmed = confirm( '🚨 FINAL WARNING: You are about to permanently delete your account.\n\n' + 'All your data, credentials, and pairings will be lost forever.\n\n' + 'Type "DELETE" to confirm (case sensitive):' ); if (doubleConfirmed) { const userInput = prompt('Type "DELETE" to confirm account deletion:'); if (userInput !== 'DELETE') { if (mainStatus) { mainStatus.innerHTML = '❌ Account deletion cancelled - confirmation text did not match'; } return; } } else { return; } try { if (mainStatus) { mainStatus.innerHTML = '
Deleting account and all data...'; } // Get services const service = await Services.getInstance(); const { secureCredentialsService } = await import('../../services/secure-credentials.service'); // Delete all credentials await secureCredentialsService.deleteCredentials(); // Clear all local storage localStorage.clear(); sessionStorage.clear(); // Clear IndexedDB if ('indexedDB' in window) { const databases = await indexedDB.databases(); for (const db of databases) { if (db.name) { indexedDB.deleteDatabase(db.name); } } } // Clear service worker caches if ('caches' in window) { const cacheNames = await caches.keys(); await Promise.all( cacheNames.map(cacheName => caches.delete(cacheName)) ); } if (mainStatus) { mainStatus.innerHTML = '✅ Account and all data deleted successfully'; } // Reload the page to start fresh setTimeout(() => { window.location.reload(); }, 2000); } catch (error) { console.error('Account deletion failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Failed to delete account'; } } }