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:
parent
82b3b27ab6
commit
802a77b568
@ -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 -->
|
||||||
|
|||||||
@ -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>';
|
||||||
|
}
|
||||||
|
if (mainPairingButton) {
|
||||||
|
mainPairingButton.disabled = true;
|
||||||
|
mainPairingButton.textContent = 'Authenticating...';
|
||||||
|
}
|
||||||
|
|
||||||
// Hide all flows
|
// Check if we have existing credentials
|
||||||
if (modeSelection) modeSelection.style.display = 'none';
|
const service = await Services.getInstance();
|
||||||
if (creatorFlow) creatorFlow.style.display = 'none';
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
if (joinerFlow) joinerFlow.style.display = 'none';
|
const hasCredentials = await secureCredentialsService.hasCredentials();
|
||||||
|
|
||||||
// Show selected flow
|
if (hasCredentials) {
|
||||||
switch (mode) {
|
// Existing pairing - decrypt credentials
|
||||||
case 'selection':
|
console.log('🔓 Existing credentials found, decrypting...');
|
||||||
if (modeSelection) modeSelection.style.display = 'block';
|
if (mainStatus) {
|
||||||
break;
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
||||||
case 'creator':
|
}
|
||||||
if (creatorFlow) creatorFlow.style.display = 'block';
|
|
||||||
break;
|
await secureCredentialsService.retrieveCredentials(''); // Empty password for WebAuthn
|
||||||
case 'joiner':
|
|
||||||
if (joinerFlow) joinerFlow.style.display = 'block';
|
if (mainStatus) {
|
||||||
break;
|
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';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user