refactor: Simplify pairing interface with direct WebAuthn flow

**Motivations :**
- Simplify user experience with single authentication flow
- Remove confusing mode selection interface
- Hide 4 words display (will be used later in interface)
- Direct WebAuthn authentication for both new and existing pairings

**Modifications :**
- Replaced mode selection with single pairing interface
- Added logic to detect existing credentials vs new pairing
- Removed 4 words display from pairing process
- Simplified HTML structure with single main interface
- Updated JavaScript logic for direct WebAuthn flow

**Pages affectées :**
- src/pages/home/home.html - Simplified to single interface
- src/pages/home/home.ts - Added direct WebAuthn flow logic
- src/utils/sp-address.utils.ts - Removed 4 words display
This commit is contained in:
NicolasCantu 2025-10-23 20:18:05 +02:00
parent 82b3b27ab6
commit 802a77b568
3 changed files with 78 additions and 128 deletions

View File

@ -1,85 +1,21 @@
<div class="pairing-container"> <div class="pairing-container">
<!-- Mode Selection --> <!-- Main Pairing Interface -->
<div id="mode-selection" class="card pairing-card"> <div id="main-pairing" class="card pairing-card">
<div class="card-header"> <div class="card-header">
<h2>🔐 4NK Pairing</h2> <h2>🔐 4NK Pairing</h2>
<p class="card-description">Choose your role in the pairing process</p> <p class="card-description">Secure device pairing with WebAuthn authentication</p>
</div>
<div class="mode-buttons">
<button id="creator-mode-btn" class="mode-btn primary-btn">
<div class="mode-icon">🔐</div>
<div class="mode-content">
<h3>Create New Pairing</h3>
<p>Generate 4 words to share with another device</p>
</div>
</button>
<button id="joiner-mode-btn" class="mode-btn secondary-btn">
<div class="mode-icon">🔗</div>
<div class="mode-content">
<h3>Join Existing Pairing</h3>
<p>Enter 4 words from another device</p>
</div>
</button>
</div>
</div>
<!-- Creator Flow -->
<div id="creator-flow" class="card pairing-card" style="display: none">
<div class="card-header">
<h2>🔐 Create New Pairing</h2>
<button id="back-to-mode-creator" class="back-btn">← Back to Mode Selection</button>
</div> </div>
<div class="pairing-request"></div> <div class="pairing-request"></div>
<div class="words-display-container">
<div class="words-label">Share these 4 words with the other device:</div>
<div class="words-content" id="creator-words"></div>
<button class="copy-btn" id="copyWordsBtn">📋 Copy Words</button>
</div>
<div class="status-container"> <div class="status-container">
<div class="status-indicator" id="creator-status"> <div class="status-indicator" id="main-status">
<div class="spinner"></div> <div class="spinner"></div>
<span>Creating pairing process...</span> <span>Initializing secure pairing...</span>
</div> </div>
</div> </div>
<button id="createButton" class="primary-btn">Create Pairing</button> <button id="mainPairingButton" class="primary-btn">Authenticate with Browser</button>
</div>
<!-- Joiner Flow -->
<div id="joiner-flow" class="card pairing-card" style="display: none">
<div class="card-header">
<h2>🔗 Join Existing Pairing</h2>
<p class="card-description">Enter the 4 words from the creator device</p>
<button id="back-to-mode-joiner" class="back-btn">← Back to Mode Selection</button>
</div>
<div class="input-container">
<label for="wordsInput" class="input-label">4 Words:</label>
<input
type="text"
id="wordsInput"
placeholder="Enter 4 words (e.g., abandon ability able about)"
class="words-input"
autocomplete="off"
spellcheck="false"
/>
<div class="input-hint">Separate words with spaces</div>
</div>
<div class="words-display" id="words-display-2"></div>
<div class="status-container">
<div class="status-indicator" id="joiner-status">
<span>Ready to join</span>
</div>
</div>
<button id="joinButton" class="primary-btn" disabled>Join Pairing</button>
</div> </div>
<!-- Loading State --> <!-- Loading State -->

View File

@ -1,7 +1,7 @@
import Routing from '../../services/modal.service'; import Routing from '../../services/modal.service';
import Services from '../../services/service'; import Services from '../../services/service';
import { addSubscription } from '../../utils/subscription.utils'; import { addSubscription } from '../../utils/subscription.utils';
import { displayEmojis, generateCreateBtn, addressToEmoji } from '../../utils/sp-address.utils'; import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
import { getCorrectDOM } from '../../utils/html.utils'; import { getCorrectDOM } from '../../utils/html.utils';
// import { navigate, registerAllListeners } from '../../router'; // Unused imports // import { navigate, registerAllListeners } from '../../router'; // Unused imports
import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing'; import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing';
@ -118,8 +118,8 @@ export async function initHomePage(): Promise<void> {
// Set up iframe pairing button listeners // Set up iframe pairing button listeners
setupIframePairingButtons(); setupIframePairingButtons();
// Set up mode selection // Set up main pairing interface
setupModeSelection(); setupMainPairing();
const container = getCorrectDOM('login-4nk-component') as HTMLElement; const container = getCorrectDOM('login-4nk-component') as HTMLElement;
container.querySelectorAll('.tab').forEach(tab => { container.querySelectorAll('.tab').forEach(tab => {
@ -517,65 +517,83 @@ export function setupIframePairingButtons() {
} }
} }
// Mode Selection Functions // Main Pairing Interface
export function setupModeSelection(): void { export function setupMainPairing(): void {
const container = getCorrectDOM('login-4nk-component') as HTMLElement; const container = getCorrectDOM('login-4nk-component') as HTMLElement;
// Mode selection buttons const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
const creatorModeBtn = container.querySelector('#creator-mode-btn') as HTMLButtonElement; const mainStatus = container.querySelector('#main-status') as HTMLElement;
const joinerModeBtn = container.querySelector('#joiner-mode-btn') as HTMLButtonElement;
// Back buttons if (mainPairingButton) {
const backToModeCreator = container.querySelector('#back-to-mode-creator') as HTMLButtonElement; mainPairingButton.addEventListener('click', async () => {
const backToModeJoiner = container.querySelector('#back-to-mode-joiner') as HTMLButtonElement; await handleMainPairing();
if (creatorModeBtn) {
creatorModeBtn.addEventListener('click', () => {
showMode('creator');
});
}
if (joinerModeBtn) {
joinerModeBtn.addEventListener('click', () => {
showMode('joiner');
});
}
if (backToModeCreator) {
backToModeCreator.addEventListener('click', () => {
showMode('selection');
});
}
if (backToModeJoiner) {
backToModeJoiner.addEventListener('click', () => {
showMode('selection');
}); });
} }
} }
function showMode(mode: 'selection' | 'creator' | 'joiner'): void { async function handleMainPairing(): Promise<void> {
const container = getCorrectDOM('login-4nk-component') as HTMLElement; const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const mainStatus = container.querySelector('#main-status') as HTMLElement;
const mainPairingButton = container.querySelector('#mainPairingButton') as HTMLButtonElement;
const modeSelection = container.querySelector('#mode-selection') as HTMLElement; try {
const creatorFlow = container.querySelector('#creator-flow') as HTMLElement; // Update UI
const joinerFlow = container.querySelector('#joiner-flow') as HTMLElement; if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
// Hide all flows }
if (modeSelection) modeSelection.style.display = 'none'; if (mainPairingButton) {
if (creatorFlow) creatorFlow.style.display = 'none'; mainPairingButton.disabled = true;
if (joinerFlow) joinerFlow.style.display = 'none'; mainPairingButton.textContent = 'Authenticating...';
}
// Show selected flow
switch (mode) { // Check if we have existing credentials
case 'selection': const service = await Services.getInstance();
if (modeSelection) modeSelection.style.display = 'block'; const { secureCredentialsService } = await import('../../services/secure-credentials.service');
break; const hasCredentials = await secureCredentialsService.hasCredentials();
case 'creator':
if (creatorFlow) creatorFlow.style.display = 'block'; if (hasCredentials) {
break; // Existing pairing - decrypt credentials
case 'joiner': console.log('🔓 Existing credentials found, decrypting...');
if (joinerFlow) joinerFlow.style.display = 'block'; if (mainStatus) {
break; mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
}
await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
}
} else {
// No existing pairing - create new one
console.log('🔐 No existing credentials, creating new pairing...');
if (mainStatus) {
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new secure pairing...</span>';
}
// This will trigger the WebAuthn flow and create new credentials
await prepareAndSendPairingTx();
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New pairing created successfully</span>';
}
}
// Re-enable button
if (mainPairingButton) {
mainPairingButton.disabled = false;
mainPairingButton.textContent = 'Authenticate with Browser';
}
} catch (error) {
console.error('Pairing failed:', error);
if (mainStatus) {
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Authentication failed</span>';
}
if (mainPairingButton) {
mainPairingButton.disabled = false;
mainPairingButton.textContent = 'Authenticate with Browser';
}
} }
} }

View File

@ -2743,10 +2743,6 @@ export async function prepareAndSendPairingTx(): Promise<void> {
// Update UI with creator address // Update UI with creator address
updateCreatorStatus(`Creator address: ${creatorAddress}`); updateCreatorStatus(`Creator address: ${creatorAddress}`);
// Generate 4 words representation for the joiner immediately
console.log(`🔍 DEBUG: Generating 4 words representation for joiner...`);
await generateWordsDisplay(creatorAddress);
// Secure credentials already initialized in the click handler // Secure credentials already initialized in the click handler
// Create pairing process with creator's address // Create pairing process with creator's address