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(); 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); // 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 export function setupMainPairing(): void { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; if (mainPairingButton) { mainPairingButton.addEventListener('click', async () => { await handleMainPairing(); }); } } async function handleMainPairing(): Promise { const container = getCorrectDOM('login-4nk-component') as HTMLElement; const mainStatus = container.querySelector('#main-status') as HTMLElement; const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement; try { // Update UI if (mainStatus) { mainStatus.innerHTML = '
Authenticating with browser...'; } if (mainPairingButton) { mainPairingButton.disabled = true; mainPairingButton.textContent = 'Authenticating...'; } // Check if we have existing credentials const service = await Services.getInstance(); const { secureCredentialsService } = await import('../../services/secure-credentials.service'); const hasCredentials = await secureCredentialsService.hasCredentials(); if (hasCredentials) { // Existing pairing - decrypt credentials console.log('πŸ”“ Existing credentials found, decrypting...'); if (mainStatus) { mainStatus.innerHTML = '
Decrypting existing credentials...'; } await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn if (mainStatus) { mainStatus.innerHTML = 'βœ… Credentials decrypted successfully'; } } else { // No existing pairing - create new one console.log('πŸ” No existing credentials, creating new pairing...'); if (mainStatus) { mainStatus.innerHTML = '
Creating new secure pairing...'; } // This will trigger the WebAuthn flow and create new credentials await prepareAndSendPairingTx(); if (mainStatus) { mainStatus.innerHTML = 'βœ… New pairing created successfully'; } } // Re-enable button if (mainPairingButton) { mainPairingButton.disabled = false; mainPairingButton.textContent = 'Authenticate with Browser'; } } catch (error) { console.error('Pairing failed:', error); if (mainStatus) { mainStatus.innerHTML = '❌ Authentication failed'; } if (mainPairingButton) { mainPairingButton.disabled = false; mainPairingButton.textContent = 'Authenticate with Browser'; } } }