import Routing from '../../services/modal.service'; 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 { navigate, registerAllListeners } from '../../router'; // Unused imports 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; } } // Home page loading spinner functions function showHomeLoadingSpinner(message: string = 'Loading...') { // Remove existing spinner if any hideHomeLoadingSpinner(); // Create spinner overlay const overlay = document.createElement('div'); overlay.id = 'home-loading-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 9998; backdrop-filter: blur(3px); `; // Create spinner content const spinnerContent = document.createElement('div'); spinnerContent.style.cssText = ` background: rgba(255, 255, 255, 0.95); border-radius: 12px; padding: 30px; text-align: center; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); border: 1px solid rgba(255, 255, 255, 0.2); max-width: 350px; width: 90%; `; // Create spinner const spinner = document.createElement('div'); spinner.style.cssText = ` width: 40px; height: 40px; border: 3px solid #f3f3f3; border-top: 3px solid #3a506b; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 15px auto; `; // Create message const messageEl = document.createElement('div'); messageEl.textContent = message; messageEl.style.cssText = ` font-size: 14px; color: #3a506b; font-weight: 500; `; // Add CSS animation if not already present if (!document.getElementById('home-spinner-styles')) { const style = document.createElement('style'); style.id = 'home-spinner-styles'; style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); } // Assemble spinner spinnerContent.appendChild(spinner); spinnerContent.appendChild(messageEl); overlay.appendChild(spinnerContent); // Add to document document.body.appendChild(overlay); } function hideHomeLoadingSpinner() { const overlay = document.getElementById('home-loading-overlay'); if (overlay) { overlay.remove(); } } export async function initHomePage(): Promise { console.log('INIT-HOME'); // Show loading spinner during home page initialization showHomeLoadingSpinner('Initializing pairing interface...'); // 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 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(); console.log('πŸ”§ Getting device address...'); const spAddress = await service.getDeviceAddress(); console.log('πŸ”§ Generating create button...'); generateCreateBtn(); console.log('πŸ”§ Displaying emojis...'); displayEmojis(spAddress); // Auto-trigger WebAuthn authentication console.log('πŸ” Auto-triggering WebAuthn authentication...'); await handleMainPairing(); // Hide loading spinner after initialization console.log('πŸ”§ Hiding loading spinner...'); hideHomeLoadingSpinner(); console.log('βœ… Home page initialization completed'); } catch (error) { console.error('❌ Error initializing home page:', error); hideHomeLoadingSpinner(); throw error; } } //// Modal export async function openModal(myAddress: string, receiverAddress: string) { const router = await Routing.getInstance(); router.openLoginModal(myAddress, receiverAddress); } // const service = await Services.getInstance() // service.setNotification() function scanDevice() { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const scannerImg = container.querySelector('#scanner') as HTMLElement; if (scannerImg) scannerImg.style.display = 'none'; const scannerQrCode = container.querySelector('.qr-code-scanner') as HTMLElement; if (scannerQrCode) scannerQrCode.style.display = 'block'; const scanButton = container?.querySelector('#scan-btn') as HTMLElement; if (scanButton) scanButton.style.display = 'none'; // QR scanner functionality removed } async function populateMemberSelect() { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement; if (!memberSelect) { console.error('Could not find memberSelect element'); return; } const service = await Services.getInstance(); const members = await service.getAllMembersSorted(); for (const [processId, member] of Object.entries(members)) { // Use member variable console.log('Processing member:', member); const process = await service.getProcess(processId); let memberPublicName; if (process) { const publicMemberData = service.getPublicData(process); if (publicMemberData) { const extractedName = publicMemberData['memberPublicName']; if (extractedName !== undefined && extractedName !== null) { memberPublicName = extractedName; } } } if (!memberPublicName) { memberPublicName = 'Unnamed Member'; } // RΓ©cupΓ©rer les emojis pour ce processId const emojis = await addressToEmoji(processId); const option = document.createElement('option'); option.value = processId; option.textContent = `${memberPublicName} (${emojis})`; memberSelect.appendChild(option); } } (window as any).populateMemberSelect = populateMemberSelect; (window as any).scanDevice = scanDevice; // 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); }); } }); } } // Main Pairing Interface - Auto-triggered, no button needed export function setupMainPairing(): void { // No button setup needed since authentication is automatic console.log('πŸ” Main pairing setup - authentication will be automatic'); } async function handleMainPairing(): Promise { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; try { // Update UI if (mainStatus) { mainStatus.innerHTML = '
Authenticating with browser...'; } // Always trigger WebAuthn flow for authentication console.log('πŸ” Triggering WebAuthn authentication...'); if (mainStatus) { mainStatus.innerHTML = '
Authenticating with browser...'; } // Import and trigger WebAuthn directly const { secureCredentialsService } = await import('../../services/secure-credentials.service'); // Check if we have existing credentials const hasCredentials = await secureCredentialsService.hasCredentials(); if (hasCredentials) { console.log('πŸ”“ Existing credentials found, decrypting...'); if (mainStatus) { mainStatus.innerHTML = '
Decrypting existing credentials...'; } // This will trigger WebAuthn for decryption await secureCredentialsService.retrieveCredentials(''); if (mainStatus) { mainStatus.innerHTML = 'βœ… Credentials decrypted successfully'; } } else { console.log('πŸ” No existing credentials, creating new ones...'); if (mainStatus) { mainStatus.innerHTML = '
Creating new credentials...'; } // This will trigger WebAuthn for creation await secureCredentialsService.generateSecureCredentials(''); if (mainStatus) { mainStatus.innerHTML = 'βœ… New credentials created successfully'; } } // Now proceed with pairing process await prepareAndSendPairingTx(); } catch (error) { console.error('Pairing failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Authentication failed'; } } } // 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'; } } }