NicolasCantu 06df2ff6c1 Add debug logs to router navigation
**Motivations :**
- Application initializes but doesn't display anything after service initialization
- Need to identify where navigation fails in the router flow

**Modifications :**
- Added debug logs to navigate() function to track path processing
- Added debug logs to handleLocation() function to track route handling
- Added debug logs to home route processing to track component creation
- Added debug logs to navigation completion in init() function

**Pages affectées :**
- src/router.ts
2025-10-23 21:13:53 +02:00

691 lines
21 KiB
TypeScript
Executable File

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<void> {
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 = `
<div class="content-menu">
<button class="menu-btn active" data-page="home">🏠 Home</button>
<button class="menu-btn" data-page="account">👤 Account</button>
<button class="menu-btn" data-page="settings">⚙️ Settings</button>
<button class="menu-btn" data-page="help">❓ Help</button>
</div>
`;
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<void> {
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const mainStatus = container.querySelector('#main-status') as HTMLElement;
try {
// Update UI
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
}
// Always trigger WebAuthn flow for authentication
console.log('🔐 Triggering WebAuthn authentication...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
}
// 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 = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
}
// This will trigger WebAuthn for decryption
await secureCredentialsService.retrieveCredentials('');
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
}
} else {
console.log('🔐 No existing 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('');
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
}
}
// Now proceed with pairing process
await prepareAndSendPairingTx();
} catch (error) {
console.error('Pairing failed:', error);
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed</span>';
}
}
}
// 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<void> {
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 = '<span style="color: var(--warning-color)">❌ Account deletion cancelled - confirmation text did not match</span>';
}
return;
}
} else {
return;
}
try {
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>';
}
// 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 = '<span style="color: var(--success-color)">✅ Account and all data deleted successfully</span>';
}
// Reload the page to start fresh
setTimeout(() => {
window.location.reload();
}, 2000);
} catch (error) {
console.error('Account deletion failed:', error);
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Failed to delete account</span>';
}
}
}