Compare commits
11 Commits
8af1fd055d
...
422ceef3e9
| Author | SHA1 | Date | |
|---|---|---|---|
| 422ceef3e9 | |||
| f46f82be7a | |||
| 82f8fc4303 | |||
| 1a4a751485 | |||
| 9dd81d5f06 | |||
| 8057ff5b2c | |||
| 535bcf5314 | |||
| 9b3af0b5ea | |||
| 4f8e43ed87 | |||
| 5def07797e | |||
| e393a4f615 |
45
src/4nk.css
45
src/4nk.css
@ -1230,6 +1230,45 @@ select[data-multi-select-plugin] {
|
|||||||
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Authentication Button Styles */
|
||||||
|
.auth-container {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-button {
|
||||||
|
background: linear-gradient(135deg, #007bff, #0056b3);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 16px 32px;
|
||||||
|
border-radius: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
|
||||||
|
margin: 20px 0;
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-button:hover {
|
||||||
|
background: linear-gradient(135deg, #0056b3, #004085);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-hint {
|
||||||
|
color: #6c757d;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 10px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Design for Mode Selection */
|
/* Responsive Design for Mode Selection */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.mode-buttons {
|
.mode-buttons {
|
||||||
@ -1244,4 +1283,10 @@ select[data-multi-select-plugin] {
|
|||||||
position: static;
|
position: static;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-button {
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 14px 28px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,7 @@
|
|||||||
|
|
||||||
<div class="status-container">
|
<div class="status-container">
|
||||||
<div class="status-indicator" id="main-status">
|
<div class="status-indicator" id="main-status">
|
||||||
<div class="spinner"></div>
|
<!-- Content will be set by JavaScript -->
|
||||||
<span>Initializing secure pairing...</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -20,12 +19,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loading State -->
|
|
||||||
<div id="loading-flow" class="card pairing-card">
|
|
||||||
<div class="loading-container">
|
|
||||||
<div class="spinner large"></div>
|
|
||||||
<h2>Initializing...</h2>
|
|
||||||
<p>Setting up secure pairing</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
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, prepareAndSendPairingTx } 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 { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing';
|
import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing';
|
||||||
|
|
||||||
// Extend WindowEventMap to include custom events
|
// Extend WindowEventMap to include custom events
|
||||||
@ -16,97 +14,19 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Home page loading spinner functions
|
|
||||||
function showHomeLoadingSpinner(message: string = 'Loading...') {
|
|
||||||
// Remove existing spinner if any
|
|
||||||
hideHomeLoadingSpinner();
|
|
||||||
|
|
||||||
// Create spinner overlay
|
let isInitializing = false;
|
||||||
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> {
|
export async function initHomePage(): Promise<void> {
|
||||||
|
if (isInitializing) {
|
||||||
|
console.log('⚠️ Home page already initializing, skipping...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitializing = true;
|
||||||
console.log('INIT-HOME');
|
console.log('INIT-HOME');
|
||||||
|
|
||||||
// Show loading spinner during home page initialization
|
// No loading spinner - let the interface load naturally
|
||||||
showHomeLoadingSpinner('Initializing pairing interface...');
|
|
||||||
|
|
||||||
// Initialize iframe pairing, content menu, and communication only if in iframe
|
// Initialize iframe pairing, content menu, and communication only if in iframe
|
||||||
if (window.parent !== window) {
|
if (window.parent !== window) {
|
||||||
@ -142,6 +62,31 @@ export async function initHomePage(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
console.log('🔧 Getting services instance...');
|
console.log('🔧 Getting services instance...');
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
|
|
||||||
|
// Check if wallet exists, create if not
|
||||||
|
console.log('🔍 Checking for existing wallet...');
|
||||||
|
const existingDevice = await service.getDeviceFromDatabase();
|
||||||
|
|
||||||
|
if (!existingDevice) {
|
||||||
|
console.log('📱 No wallet found, creating new device...');
|
||||||
|
const spAddress = await service.createNewDevice();
|
||||||
|
console.log('✅ New device created with address:', spAddress);
|
||||||
|
|
||||||
|
// Verify wallet was created successfully
|
||||||
|
const verifyDevice = await service.getDeviceFromDatabase();
|
||||||
|
if (!verifyDevice) {
|
||||||
|
throw new Error('Failed to create wallet - device not found after creation');
|
||||||
|
}
|
||||||
|
console.log('✅ Wallet creation verified');
|
||||||
|
} else {
|
||||||
|
console.log('📱 Existing wallet found');
|
||||||
|
console.log('🔍 Wallet details:', {
|
||||||
|
hasSpendKey: !!existingDevice.sp_wallet?.spend_key,
|
||||||
|
hasScanKey: !!existingDevice.sp_wallet?.scan_key,
|
||||||
|
birthday: existingDevice.sp_wallet?.birthday
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🔧 Getting device address...');
|
console.log('🔧 Getting device address...');
|
||||||
const spAddress = await service.getDeviceAddress();
|
const spAddress = await service.getDeviceAddress();
|
||||||
console.log('🔧 Generating create button...');
|
console.log('🔧 Generating create button...');
|
||||||
@ -149,86 +94,19 @@ export async function initHomePage(): Promise<void> {
|
|||||||
console.log('🔧 Displaying emojis...');
|
console.log('🔧 Displaying emojis...');
|
||||||
displayEmojis(spAddress);
|
displayEmojis(spAddress);
|
||||||
|
|
||||||
// Auto-trigger WebAuthn authentication
|
// Now trigger WebAuthn authentication
|
||||||
console.log('🔐 Auto-triggering WebAuthn authentication...');
|
console.log('🔐 Triggering WebAuthn authentication...');
|
||||||
await handleMainPairing();
|
await handleMainPairing();
|
||||||
|
|
||||||
// Hide loading spinner after initialization
|
|
||||||
console.log('🔧 Hiding loading spinner...');
|
|
||||||
hideHomeLoadingSpinner();
|
|
||||||
console.log('✅ Home page initialization completed');
|
console.log('✅ Home page initialization completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error initializing home page:', error);
|
console.error('❌ Error initializing home page:', error);
|
||||||
hideHomeLoadingSpinner();
|
|
||||||
throw error;
|
throw error;
|
||||||
|
} finally {
|
||||||
|
isInitializing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//// 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
|
// Initialize iframe pairing component
|
||||||
let iframePairing: IframePairingComponent | null = null;
|
let iframePairing: IframePairingComponent | null = null;
|
||||||
@ -524,10 +402,36 @@ export function setupIframePairingButtons() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main Pairing Interface - Auto-triggered, no button needed
|
// Main Pairing Interface - Automatic WebAuthn trigger
|
||||||
export function setupMainPairing(): void {
|
export function setupMainPairing(): void {
|
||||||
// No button setup needed since authentication is automatic
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
console.log('🔐 Main pairing setup - authentication will be automatic');
|
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||||
|
|
||||||
|
if (mainStatus) {
|
||||||
|
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔐 Main pairing setup - waiting for user interaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupUserInteractionListener(): void {
|
||||||
|
let hasTriggered = false;
|
||||||
|
|
||||||
|
const triggerWebAuthn = async (event: Event) => {
|
||||||
|
if (hasTriggered) return;
|
||||||
|
hasTriggered = true;
|
||||||
|
|
||||||
|
console.log('🔐 User interaction detected:', event.type, 'triggering WebAuthn...');
|
||||||
|
await handleMainPairing();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for any user interaction with more specific events
|
||||||
|
document.addEventListener('click', triggerWebAuthn, { once: true, passive: true });
|
||||||
|
document.addEventListener('keydown', triggerWebAuthn, { once: true, passive: true });
|
||||||
|
document.addEventListener('touchstart', triggerWebAuthn, { once: true, passive: true });
|
||||||
|
document.addEventListener('mousedown', triggerWebAuthn, { once: true, passive: true });
|
||||||
|
|
||||||
|
console.log('🔐 User interaction listeners set up');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMainPairing(): Promise<void> {
|
async function handleMainPairing(): Promise<void> {
|
||||||
@ -535,57 +439,125 @@ async function handleMainPairing(): Promise<void> {
|
|||||||
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
const mainStatus = container.querySelector('#main-status') as HTMLElement;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update UI
|
// Update UI to show authentication in progress
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always trigger WebAuthn flow for authentication
|
// Always trigger WebAuthn flow for authentication
|
||||||
console.log('🔐 Triggering WebAuthn authentication...');
|
console.log('🔐 Triggering WebAuthn authentication...');
|
||||||
if (mainStatus) {
|
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Authenticating with browser...</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import and trigger WebAuthn directly
|
// Import and trigger WebAuthn directly
|
||||||
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
|
|
||||||
// Check if we have existing credentials
|
// Check if we have existing credentials (regardless of wallet existence)
|
||||||
|
console.log('🔍 Checking for existing WebAuthn credentials...');
|
||||||
const hasCredentials = await secureCredentialsService.hasCredentials();
|
const hasCredentials = await secureCredentialsService.hasCredentials();
|
||||||
|
|
||||||
if (hasCredentials) {
|
if (hasCredentials) {
|
||||||
console.log('🔓 Existing credentials found, decrypting...');
|
console.log('🔓 Existing WebAuthn credentials found, decrypting...');
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Decrypting existing credentials...</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will trigger WebAuthn for decryption
|
// This will trigger WebAuthn for decryption of existing credentials
|
||||||
|
console.log('🔐 Starting WebAuthn decryption process...');
|
||||||
await secureCredentialsService.retrieveCredentials('');
|
await secureCredentialsService.retrieveCredentials('');
|
||||||
|
console.log('✅ WebAuthn decryption completed');
|
||||||
|
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
|
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ Credentials decrypted successfully</span>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('🔐 No existing credentials, creating new ones...');
|
console.log('🔐 No existing WebAuthn credentials, creating new ones...');
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new credentials...</span>';
|
mainStatus.innerHTML = '<div class="spinner"></div><span>Creating new credentials...</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will trigger WebAuthn for creation
|
// This will trigger WebAuthn for creation of new credentials
|
||||||
await secureCredentialsService.generateSecureCredentials('');
|
console.log('🔐 Starting WebAuthn creation process...');
|
||||||
|
const credentialData = await secureCredentialsService.generateSecureCredentials('');
|
||||||
|
console.log('✅ WebAuthn creation completed');
|
||||||
|
|
||||||
|
// Store the credentials in IndexedDB
|
||||||
|
console.log('💾 Storing credentials in IndexedDB...');
|
||||||
|
await secureCredentialsService.storeCredentials(credentialData, '');
|
||||||
|
console.log('✅ Credentials stored successfully');
|
||||||
|
|
||||||
if (mainStatus) {
|
if (mainStatus) {
|
||||||
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
|
mainStatus.innerHTML = '<span style="color: var(--success-color)">✅ New credentials created successfully</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure WebAuthn process is completely finished
|
||||||
|
console.log('🔐 WebAuthn process completed, waiting for final confirmation...');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional wait to ensure completion
|
||||||
|
|
||||||
|
// Wait longer to ensure credentials are fully processed and stored
|
||||||
|
console.log('⏳ Waiting for credentials to be fully processed...');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000)); // Increased wait time to 5 seconds
|
||||||
|
|
||||||
|
// Verify credentials are available before proceeding with retry mechanism
|
||||||
|
let credentialsReady = false;
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 10; // Increased attempts
|
||||||
|
const delayMs = 2000; // Increased delay between attempts
|
||||||
|
|
||||||
|
while (!credentialsReady && attempts < maxAttempts) {
|
||||||
|
attempts++;
|
||||||
|
console.log(`🔍 Checking credentials availability (attempt ${attempts}/${maxAttempts})...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
credentialsReady = await secureCredentialsService.hasCredentials();
|
||||||
|
if (credentialsReady) {
|
||||||
|
console.log('✅ Credentials verified, proceeding with pairing...');
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
console.log(`⏳ Credentials not ready yet, waiting ${delayMs}ms... (attempt ${attempts}/${maxAttempts})`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`⚠️ Error checking credentials (attempt ${attempts}):`, error);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!credentialsReady) {
|
||||||
|
console.error('❌ Credentials not ready after creation - checking IndexedDB directly...');
|
||||||
|
|
||||||
|
// Try to check IndexedDB directly for debugging
|
||||||
|
try {
|
||||||
|
const directCheck = await secureCredentialsService.getEncryptedCredentials();
|
||||||
|
console.log('🔍 Direct IndexedDB check result:', directCheck);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Direct IndexedDB check failed:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainStatus) {
|
||||||
|
mainStatus.innerHTML = '<span style="color: var(--error-color)">❌ Failed to create credentials</span>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Now proceed with pairing process
|
// Now proceed with pairing process
|
||||||
|
console.log('🚀 Starting pairing process...');
|
||||||
await prepareAndSendPairingTx();
|
await prepareAndSendPairingTx();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Pairing failed:', error);
|
// If WebAuthn fails due to no user gesture, wait for real interaction
|
||||||
|
if (error instanceof Error && error.message && error.message.includes('WebAuthn authentication was cancelled or timed out')) {
|
||||||
|
console.log('🔐 WebAuthn requires user interaction, waiting...');
|
||||||
|
if (mainStatus) {
|
||||||
|
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
if (mainStatus) {
|
// Set up listener for real user interaction
|
||||||
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
|
setupUserInteractionListener();
|
||||||
|
} else {
|
||||||
|
console.error('Pairing failed:', error);
|
||||||
|
if (mainStatus) {
|
||||||
|
mainStatus.innerHTML = '<span style="color: var(--info-color)">⏳ Waiting for user to validate secure key access...</span>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -644,7 +616,7 @@ async function handleDeleteAccount(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get services
|
// Get services
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../../services/secure-credentials.service');
|
||||||
|
|
||||||
// Delete all credentials
|
// Delete all credentials
|
||||||
|
|||||||
@ -153,10 +153,20 @@ export async function init(): Promise<void> {
|
|||||||
// No wallet exists, create new account
|
// No wallet exists, create new account
|
||||||
console.log('🔍 No existing wallet found, creating new account...');
|
console.log('🔍 No existing wallet found, creating new account...');
|
||||||
await services.createNewDevice();
|
await services.createNewDevice();
|
||||||
|
|
||||||
|
// CRITICAL: Wait for blockchain scan after wallet creation
|
||||||
|
console.log('🔄 Synchronizing new wallet with blockchain...');
|
||||||
|
await services.updateDeviceBlockHeight();
|
||||||
|
console.log('✅ Wallet synchronization completed');
|
||||||
} else {
|
} else {
|
||||||
// Wallet exists, restore it and check pairing
|
// Wallet exists, restore it and check pairing
|
||||||
console.log('🔍 Existing wallet found, restoring account...');
|
console.log('🔍 Existing wallet found, restoring account...');
|
||||||
services.restoreDevice(device);
|
services.restoreDevice(device);
|
||||||
|
|
||||||
|
// CRITICAL: Wait for blockchain scan after wallet restoration
|
||||||
|
console.log('🔄 Synchronizing existing wallet with blockchain...');
|
||||||
|
await services.updateDeviceBlockHeight();
|
||||||
|
console.log('✅ Wallet synchronization completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore data from database (these operations can fail, so we handle them separately)
|
// Restore data from database (these operations can fail, so we handle them separately)
|
||||||
|
|||||||
@ -353,33 +353,66 @@ export class Database {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return this.addObjectWithRetry(payload, 3);
|
||||||
// Check if the service worker is active
|
}
|
||||||
if (!this.serviceWorkerRegistration) {
|
|
||||||
// console.warn('Service worker registration is not ready. Waiting...');
|
|
||||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
private async addObjectWithRetry(payload: { storeName: string; object: any; key: any }, maxRetries: number): Promise<void> {
|
||||||
this.serviceWorkerRegistration
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
);
|
|
||||||
|
|
||||||
// Create a message channel for communication
|
|
||||||
const messageChannel = new MessageChannel();
|
|
||||||
|
|
||||||
// Handle the response from the service worker
|
|
||||||
messageChannel.port1.onmessage = event => {
|
|
||||||
if (event.data.status === 'success') {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
const error = event.data.message;
|
|
||||||
reject(new Error(error || 'Unknown error occurred while adding object'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the add object request to the service worker
|
|
||||||
try {
|
try {
|
||||||
activeWorker?.postMessage(
|
await this.addObjectAttempt(payload);
|
||||||
|
return; // Success, exit retry loop
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, error);
|
||||||
|
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
console.error('All retry attempts failed for addObject');
|
||||||
|
throw new Error(`Failed to add object after ${maxRetries} attempts: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before retry (exponential backoff)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// Check if the service worker is active
|
||||||
|
if (!this.serviceWorkerRegistration) {
|
||||||
|
console.log('Service worker registration not ready, waiting...');
|
||||||
|
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||||
|
this.serviceWorkerRegistration
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!activeWorker) {
|
||||||
|
throw new Error('Service worker not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a message channel for communication
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
// Set timeout for the operation
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('Operation timeout - service worker did not respond'));
|
||||||
|
}, 10000); // 10 second timeout
|
||||||
|
|
||||||
|
// Handle the response from the service worker
|
||||||
|
messageChannel.port1.onmessage = event => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (event.data.status === 'success') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const error = event.data.message;
|
||||||
|
reject(new Error(error || 'Unknown error occurred while adding object'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the add object request to the service worker
|
||||||
|
activeWorker.postMessage(
|
||||||
{
|
{
|
||||||
type: 'ADD_OBJECT',
|
type: 'ADD_OBJECT',
|
||||||
payload,
|
payload,
|
||||||
@ -396,27 +429,68 @@ export class Database {
|
|||||||
storeName: string;
|
storeName: string;
|
||||||
objects: { key: any; object: any }[];
|
objects: { key: any; object: any }[];
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return this.batchWritingWithRetry(payload, 3);
|
||||||
if (!this.serviceWorkerRegistration) {
|
}
|
||||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
|
||||||
this.serviceWorkerRegistration
|
|
||||||
);
|
|
||||||
const messageChannel = new MessageChannel();
|
|
||||||
|
|
||||||
messageChannel.port1.onmessage = event => {
|
|
||||||
if (event.data.status === 'success') {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
const error = event.data.message;
|
|
||||||
reject(new Error(error || 'Unknown error occurred while adding objects'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
private async batchWritingWithRetry(payload: {
|
||||||
|
storeName: string;
|
||||||
|
objects: { key: any; object: any }[];
|
||||||
|
}, maxRetries: number): Promise<void> {
|
||||||
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
activeWorker?.postMessage(
|
await this.batchWritingAttempt(payload);
|
||||||
|
return; // Success, exit retry loop
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, error);
|
||||||
|
|
||||||
|
if (attempt === maxRetries) {
|
||||||
|
console.error('All retry attempts failed for batchWriting');
|
||||||
|
throw new Error(`Failed to batch write objects after ${maxRetries} attempts: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before retry (exponential backoff)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private batchWritingAttempt(payload: {
|
||||||
|
storeName: string;
|
||||||
|
objects: { key: any; object: any }[];
|
||||||
|
}): Promise<void> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (!this.serviceWorkerRegistration) {
|
||||||
|
console.log('Service worker registration not ready, waiting...');
|
||||||
|
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||||
|
this.serviceWorkerRegistration
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!activeWorker) {
|
||||||
|
throw new Error('Service worker not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
// Set timeout for the operation
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject(new Error('Batch writing timeout - service worker did not respond'));
|
||||||
|
}, 30000); // 30 second timeout for batch operations
|
||||||
|
|
||||||
|
messageChannel.port1.onmessage = event => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (event.data.status === 'success') {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const error = event.data.message;
|
||||||
|
reject(new Error(error || 'Unknown error occurred while adding objects'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
activeWorker.postMessage(
|
||||||
{
|
{
|
||||||
type: 'BATCH_WRITING',
|
type: 'BATCH_WRITING',
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@ -19,14 +19,15 @@ export interface CacheEntry<T> {
|
|||||||
export class MemoryManager {
|
export class MemoryManager {
|
||||||
private static instance: MemoryManager;
|
private static instance: MemoryManager;
|
||||||
private caches: Map<string, Map<string, CacheEntry<any>>> = new Map();
|
private caches: Map<string, Map<string, CacheEntry<any>>> = new Map();
|
||||||
private maxCacheSize = 100;
|
private maxCacheSize = 0; // Disabled caches completely
|
||||||
private maxCacheAge = 5 * 60 * 1000; // 5 minutes
|
private maxCacheAge = 0; // No cache expiry
|
||||||
private cleanupInterval: number | null = null;
|
private cleanupInterval: number | null = null;
|
||||||
private memoryThreshold = 100 * 1024 * 1024; // 100MB
|
private memoryThreshold = 200 * 1024 * 1024; // 200MB (increased from 100MB)
|
||||||
private isMonitoring = false;
|
private isMonitoring = false;
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.startCleanupInterval();
|
// Disabled to save memory
|
||||||
|
// this.startCleanupInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getInstance(): MemoryManager {
|
public static getInstance(): MemoryManager {
|
||||||
@ -267,7 +268,7 @@ export class MemoryManager {
|
|||||||
private startCleanupInterval(): void {
|
private startCleanupInterval(): void {
|
||||||
this.cleanupInterval = setInterval(() => {
|
this.cleanupInterval = setInterval(() => {
|
||||||
this.cleanupExpiredEntries();
|
this.cleanupExpiredEntries();
|
||||||
}, 60000) as any; // Nettoyage toutes les minutes
|
}, 120000) as any; // Nettoyage toutes les 2 minutes (reduced frequency)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -99,16 +99,32 @@ export class SecureCredentialsService {
|
|||||||
authenticatorAttachment: "platform", // Force l'authentificateur intégré
|
authenticatorAttachment: "platform", // Force l'authentificateur intégré
|
||||||
userVerification: "required"
|
userVerification: "required"
|
||||||
},
|
},
|
||||||
timeout: 60000,
|
timeout: 300000, // 5 minutes timeout
|
||||||
attestation: "direct"
|
attestation: "direct"
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('🔐 Requesting WebAuthn credential creation for encryption key...');
|
console.log('🔐 Requesting WebAuthn credential creation for encryption key...');
|
||||||
|
|
||||||
// Créer le credential WebAuthn
|
// Créer le credential WebAuthn avec gestion d'erreur robuste
|
||||||
const credential = await navigator.credentials.create({
|
let credential: PublicKeyCredential;
|
||||||
publicKey: publicKeyCredentialCreationOptions
|
try {
|
||||||
}) as PublicKeyCredential;
|
credential = await navigator.credentials.create({
|
||||||
|
publicKey: publicKeyCredentialCreationOptions
|
||||||
|
}) as PublicKeyCredential;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'NotAllowedError') {
|
||||||
|
throw new Error('WebAuthn authentication was cancelled or timed out. Please try again and complete the authentication when prompted.');
|
||||||
|
} else if (error.name === 'NotSupportedError') {
|
||||||
|
throw new Error('WebAuthn is not supported in this browser. Please use a modern browser with WebAuthn support.');
|
||||||
|
} else if (error.name === 'SecurityError') {
|
||||||
|
throw new Error('WebAuthn security error. Please ensure you are using HTTPS and try again.');
|
||||||
|
} else {
|
||||||
|
throw new Error(`WebAuthn error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
throw new Error('WebAuthn credential creation failed');
|
throw new Error('WebAuthn credential creation failed');
|
||||||
@ -283,18 +299,34 @@ export class SecureCredentialsService {
|
|||||||
throw new Error('WebAuthn not supported for decryption');
|
throw new Error('WebAuthn not supported for decryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Demander l'authentification WebAuthn
|
// Demander l'authentification WebAuthn avec gestion d'erreur robuste
|
||||||
const credential = await navigator.credentials.get({
|
let credential: PublicKeyCredential;
|
||||||
publicKey: {
|
try {
|
||||||
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
credential = await navigator.credentials.get({
|
||||||
allowCredentials: [{
|
publicKey: {
|
||||||
id: new TextEncoder().encode(credentialId),
|
challenge: crypto.getRandomValues(new Uint8Array(32)),
|
||||||
type: 'public-key'
|
allowCredentials: [{
|
||||||
}],
|
id: new TextEncoder().encode(credentialId),
|
||||||
userVerification: 'required',
|
type: 'public-key'
|
||||||
timeout: 60000
|
}],
|
||||||
|
userVerification: 'required',
|
||||||
|
timeout: 300000 // 5 minutes timeout
|
||||||
|
}
|
||||||
|
}) as PublicKeyCredential;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (error.name === 'NotAllowedError') {
|
||||||
|
throw new Error('WebAuthn authentication was cancelled or timed out. Please try again and complete the authentication when prompted.');
|
||||||
|
} else if (error.name === 'NotSupportedError') {
|
||||||
|
throw new Error('WebAuthn is not supported in this browser. Please use a modern browser with WebAuthn support.');
|
||||||
|
} else if (error.name === 'SecurityError') {
|
||||||
|
throw new Error('WebAuthn security error. Please ensure you are using HTTPS and try again.');
|
||||||
|
} else {
|
||||||
|
throw new Error(`WebAuthn decryption error: ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}) as PublicKeyCredential;
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
throw new Error('WebAuthn authentication failed');
|
throw new Error('WebAuthn authentication failed');
|
||||||
@ -386,22 +418,36 @@ export class SecureCredentialsService {
|
|||||||
*/
|
*/
|
||||||
private async storeEncryptedCredentials(credentials: any): Promise<void> {
|
private async storeEncryptedCredentials(credentials: any): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log('💾 Storing encrypted credentials in IndexedDB...');
|
||||||
|
|
||||||
const request = indexedDB.open('4NK_SecureCredentials', 1);
|
const request = indexedDB.open('4NK_SecureCredentials', 1);
|
||||||
|
|
||||||
request.onerror = () => reject(new Error('Failed to open IndexedDB for credentials'));
|
request.onerror = () => {
|
||||||
|
console.error('❌ Failed to open IndexedDB for storing credentials');
|
||||||
|
reject(new Error('Failed to open IndexedDB for credentials'));
|
||||||
|
};
|
||||||
|
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
|
console.log('💾 IndexedDB opened for storing, creating transaction...');
|
||||||
|
|
||||||
const transaction = db.transaction(['credentials'], 'readwrite');
|
const transaction = db.transaction(['credentials'], 'readwrite');
|
||||||
const store = transaction.objectStore('credentials');
|
const store = transaction.objectStore('credentials');
|
||||||
|
|
||||||
const putRequest = store.put(credentials, 'webauthn_credentials');
|
const putRequest = store.put(credentials, 'webauthn_credentials');
|
||||||
putRequest.onsuccess = () => resolve();
|
putRequest.onsuccess = () => {
|
||||||
putRequest.onerror = () => reject(new Error('Failed to store encrypted credentials'));
|
console.log('✅ Credentials stored successfully in IndexedDB');
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
putRequest.onerror = () => {
|
||||||
|
console.error('❌ Failed to store encrypted credentials');
|
||||||
|
reject(new Error('Failed to store encrypted credentials'));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onupgradeneeded = () => {
|
request.onupgradeneeded = () => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
|
console.log('🔧 IndexedDB upgrade needed for storing, creating credentials store...');
|
||||||
if (!db.objectStoreNames.contains('credentials')) {
|
if (!db.objectStoreNames.contains('credentials')) {
|
||||||
db.createObjectStore('credentials');
|
db.createObjectStore('credentials');
|
||||||
}
|
}
|
||||||
@ -416,20 +462,33 @@ export class SecureCredentialsService {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const request = indexedDB.open('4NK_SecureCredentials', 1);
|
const request = indexedDB.open('4NK_SecureCredentials', 1);
|
||||||
|
|
||||||
request.onerror = () => reject(new Error('Failed to open IndexedDB for credentials'));
|
request.onerror = () => {
|
||||||
|
console.error('❌ Failed to open IndexedDB for credentials');
|
||||||
|
reject(new Error('Failed to open IndexedDB for credentials'));
|
||||||
|
};
|
||||||
|
|
||||||
request.onsuccess = () => {
|
request.onsuccess = () => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
|
console.log('🔍 IndexedDB opened successfully, checking for credentials...');
|
||||||
|
|
||||||
const transaction = db.transaction(['credentials'], 'readonly');
|
const transaction = db.transaction(['credentials'], 'readonly');
|
||||||
const store = transaction.objectStore('credentials');
|
const store = transaction.objectStore('credentials');
|
||||||
|
|
||||||
const getRequest = store.get('webauthn_credentials');
|
const getRequest = store.get('webauthn_credentials');
|
||||||
getRequest.onsuccess = () => resolve(getRequest.result || null);
|
getRequest.onsuccess = () => {
|
||||||
getRequest.onerror = () => reject(new Error('Failed to retrieve encrypted credentials'));
|
const result = getRequest.result || null;
|
||||||
|
console.log('🔍 IndexedDB get result:', result ? 'credentials found' : 'no credentials');
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
getRequest.onerror = () => {
|
||||||
|
console.error('❌ Failed to retrieve encrypted credentials');
|
||||||
|
reject(new Error('Failed to retrieve encrypted credentials'));
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onupgradeneeded = () => {
|
request.onupgradeneeded = () => {
|
||||||
const db = request.result;
|
const db = request.result;
|
||||||
|
console.log('🔧 IndexedDB upgrade needed, creating credentials store...');
|
||||||
if (!db.objectStoreNames.contains('credentials')) {
|
if (!db.objectStoreNames.contains('credentials')) {
|
||||||
db.createObjectStore('credentials');
|
db.createObjectStore('credentials');
|
||||||
}
|
}
|
||||||
@ -499,8 +558,11 @@ export class SecureCredentialsService {
|
|||||||
async hasCredentials(): Promise<boolean> {
|
async hasCredentials(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const credentials = await this.getEncryptedCredentials();
|
const credentials = await this.getEncryptedCredentials();
|
||||||
return credentials !== null;
|
const hasCredentials = credentials !== null && credentials !== undefined;
|
||||||
|
console.log(`🔍 hasCredentials check: ${hasCredentials}`, credentials ? 'credentials found' : 'no credentials');
|
||||||
|
return hasCredentials;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.warn('⚠️ Error checking credentials:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,8 +140,8 @@ export default class Services {
|
|||||||
private myProcesses: Set<string> = new Set();
|
private myProcesses: Set<string> = new Set();
|
||||||
private notifications: any[] | null = null;
|
private notifications: any[] | null = null;
|
||||||
// private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
|
// private subscriptions: { element: Element; event: string; eventHandler: string }[] = [];
|
||||||
private maxCacheSize = 100;
|
private maxCacheSize = 0; // Disabled caches completely
|
||||||
private cacheExpiry = 5 * 60 * 1000; // 5 minutes
|
private cacheExpiry = 0; // No cache expiry
|
||||||
// private database: any;
|
// private database: any;
|
||||||
private routingInstance!: ModalService;
|
private routingInstance!: ModalService;
|
||||||
private relayAddresses: { [wsurl: string]: string } = {};
|
private relayAddresses: { [wsurl: string]: string } = {};
|
||||||
@ -215,23 +215,22 @@ export default class Services {
|
|||||||
// DO NOT clear user data - only clear non-essential caches
|
// DO NOT clear user data - only clear non-essential caches
|
||||||
console.log('⚠️ Skipping storage cleanup to preserve user data');
|
console.log('⚠️ Skipping storage cleanup to preserve user data');
|
||||||
|
|
||||||
// Force aggressive memory cleanup
|
// Light memory cleanup only
|
||||||
console.log('🔧 Performing aggressive memory cleanup...');
|
console.log('🔧 Performing light memory cleanup...');
|
||||||
|
|
||||||
// Clear only non-essential browser data (NOT user data)
|
// Minimal cleanup to avoid memory leaks
|
||||||
try {
|
try {
|
||||||
// Clear only HTTP caches (NOT IndexedDB with user data)
|
// Only clear HTTP caches if they exist
|
||||||
if ('caches' in window) {
|
if ('caches' in window) {
|
||||||
const cacheNames = await caches.keys();
|
const cacheNames = await caches.keys();
|
||||||
// Only clear HTTP caches, not application data
|
if (cacheNames.length > 0) {
|
||||||
const httpCaches = cacheNames.filter(name => name.startsWith('http'));
|
const httpCaches = cacheNames.filter(name => name.startsWith('http'));
|
||||||
await Promise.all(httpCaches.map(name => caches.delete(name)));
|
if (httpCaches.length > 0) {
|
||||||
console.log('🧹 HTTP caches cleared (user data preserved)');
|
await Promise.all(httpCaches.map(name => caches.delete(name)));
|
||||||
|
console.log('🧹 HTTP caches cleared (user data preserved)');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT clear IndexedDB - it contains user secrets!
|
|
||||||
// DO NOT clear service workers - they manage user data!
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('⚠️ Safe cleanup error:', e);
|
console.log('⚠️ Safe cleanup error:', e);
|
||||||
}
|
}
|
||||||
@ -242,33 +241,32 @@ export default class Services {
|
|||||||
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
|
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
|
||||||
console.log(`📊 Memory usage after cleanup: ${usedPercent.toFixed(1)}% (${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB)`);
|
console.log(`📊 Memory usage after cleanup: ${usedPercent.toFixed(1)}% (${(memory.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB)`);
|
||||||
|
|
||||||
if (usedPercent > 70) {
|
if (usedPercent > 75) {
|
||||||
console.warn('⚠️ High memory usage detected, forcing additional cleanup...');
|
console.warn('⚠️ High memory usage detected, performing aggressive cleanup...');
|
||||||
|
|
||||||
// Debug: Check what's consuming memory
|
// More aggressive cleanup
|
||||||
console.log('🔍 Debugging memory usage...');
|
console.log('🔍 Debugging memory usage...');
|
||||||
console.log('📦 Document elements:', document.querySelectorAll('*').length);
|
console.log('📦 Document elements:', document.querySelectorAll('*').length);
|
||||||
console.log('📦 Script tags:', document.querySelectorAll('script').length);
|
|
||||||
console.log('📦 Style tags:', document.querySelectorAll('style').length);
|
|
||||||
console.log('📦 Images:', document.querySelectorAll('img').length);
|
|
||||||
|
|
||||||
// Force more aggressive cleanup
|
// Multiple garbage collections
|
||||||
if (window.gc) {
|
if (window.gc) {
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
window.gc();
|
window.gc();
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear DOM references
|
// Clear any cached data
|
||||||
const elements = document.querySelectorAll('*');
|
if (window.localStorage) {
|
||||||
elements.forEach(el => {
|
const keys = Object.keys(localStorage);
|
||||||
if (el.removeAttribute) {
|
keys.forEach(key => {
|
||||||
el.removeAttribute('data-cached');
|
if (key.startsWith('temp_') || key.startsWith('cache_')) {
|
||||||
}
|
localStorage.removeItem(key);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log('🧹 Additional memory cleanup completed');
|
console.log('🧹 Aggressive memory cleanup completed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -283,7 +281,7 @@ export default class Services {
|
|||||||
const memory = (performance as any).memory;
|
const memory = (performance as any).memory;
|
||||||
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
|
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
|
||||||
|
|
||||||
if (usedPercent > 70) {
|
if (usedPercent > 95) {
|
||||||
console.log('🚫 Memory too high, skipping WebAssembly initialization');
|
console.log('🚫 Memory too high, skipping WebAssembly initialization');
|
||||||
Services.instance = new Services();
|
Services.instance = new Services();
|
||||||
Services.initializing = null;
|
Services.initializing = null;
|
||||||
@ -602,6 +600,10 @@ export default class Services {
|
|||||||
|
|
||||||
public isPaired(): boolean {
|
public isPaired(): boolean {
|
||||||
try {
|
try {
|
||||||
|
if (!this.sdkClient) {
|
||||||
|
console.log('WebAssembly SDK not initialized - assuming not paired');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return this.sdkClient.is_paired();
|
return this.sdkClient.is_paired();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// During pairing process, it's normal for the device to not be paired yet
|
// During pairing process, it's normal for the device to not be paired yet
|
||||||
@ -651,12 +653,7 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getTokensFromFaucet(): Promise<void> {
|
private async getTokensFromFaucet(): Promise<void> {
|
||||||
try {
|
await this.ensureSufficientAmount();
|
||||||
await this.ensureSufficientAmount();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to get tokens from relay, check connection');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're updating a process, we must call that after update especially if roles are part of it
|
// If we're updating a process, we must call that after update especially if roles are part of it
|
||||||
@ -755,30 +752,80 @@ export default class Services {
|
|||||||
const availableAmt = this.getAmount();
|
const availableAmt = this.getAmount();
|
||||||
const target: BigInt = DEFAULTAMOUNT * BigInt(10);
|
const target: BigInt = DEFAULTAMOUNT * BigInt(10);
|
||||||
|
|
||||||
|
console.log(`💰 Current amount: ${availableAmt}, target: ${target}`);
|
||||||
|
|
||||||
if (availableAmt < target) {
|
if (availableAmt < target) {
|
||||||
|
console.log('🪙 Requesting tokens from faucet...');
|
||||||
const faucetMsg = this.createFaucetMessage();
|
const faucetMsg = this.createFaucetMessage();
|
||||||
|
console.log('🪙 Faucet message created:', faucetMsg);
|
||||||
this.sendFaucetMessage(faucetMsg);
|
this.sendFaucetMessage(faucetMsg);
|
||||||
|
console.log('🪙 Faucet message sent, waiting for tokens...');
|
||||||
|
|
||||||
await this.waitForAmount(target);
|
await this.waitForAmount(target);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Sufficient tokens already available');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUserStatus(message: string): void {
|
||||||
|
try {
|
||||||
|
const container = document.querySelector('login-4nk-component') as HTMLElement;
|
||||||
|
const mainStatus = container?.querySelector('#main-status') as HTMLElement;
|
||||||
|
if (mainStatus) {
|
||||||
|
mainStatus.innerHTML = `<span style="color: var(--info-color)">${message}</span>`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not update user status:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForAmount(target: BigInt): Promise<BigInt> {
|
private async waitForAmount(target: BigInt): Promise<BigInt> {
|
||||||
let attempts = 3;
|
let attempts = 20; // Increased attempts for blockchain confirmation
|
||||||
|
|
||||||
while (attempts > 0) {
|
while (attempts > 0) {
|
||||||
const amount = this.getAmount();
|
const amount = this.getAmount();
|
||||||
|
console.log(`🪙 Attempt ${21 - attempts}: current amount ${amount}, target ${target}`);
|
||||||
|
|
||||||
if (amount >= target) {
|
if (amount >= target) {
|
||||||
|
console.log('✅ Sufficient tokens received!');
|
||||||
|
this.updateUserStatus('✅ Tokens received successfully!');
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Force SDK to scan blocks to update wallet state
|
||||||
|
if (attempts < 20) { // Don't scan on first attempt
|
||||||
|
console.log('🔄 Forcing SDK block scan to update wallet state...');
|
||||||
|
this.updateUserStatus('🔄 Synchronizing wallet with blockchain...');
|
||||||
|
try {
|
||||||
|
await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL);
|
||||||
|
console.log('✅ SDK block scan completed');
|
||||||
|
|
||||||
|
// Check amount again after scanning
|
||||||
|
const newAmount = this.getAmount();
|
||||||
|
console.log(`💰 Amount after forced scan: ${newAmount}`);
|
||||||
|
|
||||||
|
if (newAmount > 0) {
|
||||||
|
this.updateUserStatus(`💰 Found ${newAmount} tokens in wallet!`);
|
||||||
|
} else {
|
||||||
|
this.updateUserStatus('⏳ Waiting for tokens to be confirmed on blockchain...');
|
||||||
|
}
|
||||||
|
} catch (scanError) {
|
||||||
|
console.error('❌ Error during forced block scan:', scanError);
|
||||||
|
this.updateUserStatus('⚠️ Blockchain synchronization in progress...');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.updateUserStatus('🪙 Requesting tokens from faucet...');
|
||||||
|
}
|
||||||
|
|
||||||
attempts--;
|
attempts--;
|
||||||
if (attempts > 0) {
|
if (attempts > 0) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait for 1 second
|
console.log(`⏳ Waiting 5 seconds before next attempt (${attempts} attempts left)...`);
|
||||||
|
this.updateUserStatus(`⏳ Checking for tokens... (${attempts} attempts remaining)`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000)); // Wait for 5 seconds
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Amount is still 0 after 3 attempts');
|
throw new Error('Amount is still insufficient after 20 attempts - faucet may be down or transaction not confirmed');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn> {
|
public async createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn> {
|
||||||
@ -897,9 +944,18 @@ export default class Services {
|
|||||||
console.log('🔍 DEBUG: Members type:', typeof membersObj);
|
console.log('🔍 DEBUG: Members type:', typeof membersObj);
|
||||||
console.log('🔍 DEBUG: Members keys:', Object.keys(membersObj));
|
console.log('🔍 DEBUG: Members keys:', Object.keys(membersObj));
|
||||||
|
|
||||||
// Convert object to array for WebAssembly
|
// Check if membersList is empty
|
||||||
const members = Object.values(membersObj);
|
if (!membersObj || Object.keys(membersObj).length === 0) {
|
||||||
|
console.warn('⚠️ No members available for create_new_process, waiting for handshake...');
|
||||||
|
throw new Error('No members available - handshake not completed yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert membersObj to array format for WebAssembly (it expects a sequence, not a map)
|
||||||
|
const members = Object.values(membersObj).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
console.log('🔍 DEBUG: Members array length:', members.length);
|
console.log('🔍 DEBUG: Members array length:', members.length);
|
||||||
|
console.log('🔍 DEBUG: Members array sample:', members.slice(0, 3));
|
||||||
|
|
||||||
const result = this.sdkClient.create_new_process(
|
const result = this.sdkClient.create_new_process(
|
||||||
encodedPrivateData,
|
encodedPrivateData,
|
||||||
@ -943,12 +999,15 @@ export default class Services {
|
|||||||
...this.sdkClient.encode_binary(publicSplitData.binaryData),
|
...this.sdkClient.encode_binary(publicSplitData.binaryData),
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
const members = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
const result = this.sdkClient.update_process(
|
const result = this.sdkClient.update_process(
|
||||||
process,
|
process,
|
||||||
encodedPrivateData,
|
encodedPrivateData,
|
||||||
roles,
|
roles,
|
||||||
encodedPublicData,
|
encodedPublicData,
|
||||||
Object.values(this.getAllMembers())
|
members
|
||||||
);
|
);
|
||||||
if (result.updated_process) {
|
if (result.updated_process) {
|
||||||
await this.checkConnections(result.updated_process.current_process);
|
await this.checkConnections(result.updated_process.current_process);
|
||||||
@ -969,7 +1028,10 @@ export default class Services {
|
|||||||
await this.checkConnections(process);
|
await this.checkConnections(process);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return this.sdkClient.create_update_message(process, stateId, Object.values(this.getAllMembers()));
|
const members = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
|
return this.sdkClient.create_update_message(process, stateId, members);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to create prd update: ${e}`);
|
throw new Error(`Failed to create prd update: ${e}`);
|
||||||
}
|
}
|
||||||
@ -981,7 +1043,10 @@ export default class Services {
|
|||||||
throw new Error('Unknown process');
|
throw new Error('Unknown process');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return this.sdkClient.create_response_prd(process, stateId, Object.values(this.getAllMembers()));
|
const members = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
|
return this.sdkClient.create_response_prd(process, stateId, members);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to create response prd: ${e}`);
|
throw new Error(`Failed to create response prd: ${e}`);
|
||||||
}
|
}
|
||||||
@ -993,7 +1058,10 @@ export default class Services {
|
|||||||
throw new Error('Failed to get process from db');
|
throw new Error('Failed to get process from db');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = this.sdkClient.validate_state(process, stateId, Object.values(this.getAllMembers()));
|
const members = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
|
const result = this.sdkClient.validate_state(process, stateId, members);
|
||||||
if (result.updated_process) {
|
if (result.updated_process) {
|
||||||
await this.checkConnections(result.updated_process.current_process);
|
await this.checkConnections(result.updated_process.current_process);
|
||||||
return result;
|
return result;
|
||||||
@ -1049,7 +1117,9 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async parseCipher(message: string) {
|
async parseCipher(message: string) {
|
||||||
const membersList = Object.values(this.getAllMembers());
|
const membersList = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
const processes = await this.getProcesses();
|
const processes = await this.getProcesses();
|
||||||
try {
|
try {
|
||||||
// console.log('parsing new cipher');
|
// console.log('parsing new cipher');
|
||||||
@ -1077,10 +1147,16 @@ export default class Services {
|
|||||||
const parsedMsg: NewTxMessage = typeof newTxMsg === 'string' ? JSON.parse(newTxMsg) : newTxMsg;
|
const parsedMsg: NewTxMessage = typeof newTxMsg === 'string' ? JSON.parse(newTxMsg) : newTxMsg;
|
||||||
if (parsedMsg.error !== null) {
|
if (parsedMsg.error !== null) {
|
||||||
console.error('Received error in new tx message:', parsedMsg.error);
|
console.error('Received error in new tx message:', parsedMsg.error);
|
||||||
|
this.updateUserStatus('❌ Transaction error received');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const membersList = Object.values(this.getAllMembers());
|
// Notify user that a transaction was received
|
||||||
|
this.updateUserStatus('📨 New transaction received from blockchain...');
|
||||||
|
|
||||||
|
const membersList = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
try {
|
try {
|
||||||
// Does the transaction spend the tip of a process?
|
// Does the transaction spend the tip of a process?
|
||||||
const prevouts = this.sdkClient.get_prevouts(parsedMsg.transaction);
|
const prevouts = this.sdkClient.get_prevouts(parsedMsg.transaction);
|
||||||
@ -1116,6 +1192,59 @@ export default class Services {
|
|||||||
await this.handleApiReturn(parsedTx);
|
await this.handleApiReturn(parsedTx);
|
||||||
const newDevice = this.dumpDeviceFromMemory();
|
const newDevice = this.dumpDeviceFromMemory();
|
||||||
await this.saveDeviceInDatabase(newDevice);
|
await this.saveDeviceInDatabase(newDevice);
|
||||||
|
|
||||||
|
// Force SDK to scan blocks to update wallet state after receiving tokens
|
||||||
|
console.log('🔄 Forcing SDK to scan blocks to update wallet state...');
|
||||||
|
try {
|
||||||
|
await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL);
|
||||||
|
console.log('✅ SDK block scan completed, wallet state should be updated');
|
||||||
|
|
||||||
|
// Force wallet synchronization
|
||||||
|
console.log('🔄 Forcing wallet synchronization...');
|
||||||
|
try {
|
||||||
|
const device = await this.getDeviceFromDatabase();
|
||||||
|
if (device && device.sp_wallet) {
|
||||||
|
// Update last_scan to current block height
|
||||||
|
device.sp_wallet.last_scan = this.currentBlockHeight;
|
||||||
|
await this.updateDeviceInDatabase(device);
|
||||||
|
console.log('✅ Wallet last_scan updated to current block height');
|
||||||
|
}
|
||||||
|
} catch (syncError) {
|
||||||
|
console.error('❌ Error during wallet synchronization:', syncError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check amount after scanning
|
||||||
|
const updatedAmount = this.getAmount();
|
||||||
|
console.log(`💰 Amount after block scan: ${updatedAmount}`);
|
||||||
|
|
||||||
|
// Update user with scan results
|
||||||
|
if (updatedAmount > 0) {
|
||||||
|
this.updateUserStatus(`💰 Wallet updated! Found ${updatedAmount} tokens`);
|
||||||
|
} else {
|
||||||
|
this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional debugging: check if SDK is properly initialized
|
||||||
|
console.log('🔍 SDK debugging info:');
|
||||||
|
console.log('- Current block height:', this.currentBlockHeight);
|
||||||
|
console.log('- Blindbit URL:', BLINDBITURL);
|
||||||
|
console.log('- SDK client initialized:', !!this.sdkClient);
|
||||||
|
|
||||||
|
// Check wallet state in SDK
|
||||||
|
try {
|
||||||
|
const device = await this.getDeviceFromDatabase();
|
||||||
|
if (device && device.sp_wallet) {
|
||||||
|
console.log('🔍 Wallet state:');
|
||||||
|
console.log('- Last scan:', device.sp_wallet.last_scan);
|
||||||
|
console.log('- Current block:', this.currentBlockHeight);
|
||||||
|
console.log('- Scan needed:', device.sp_wallet.last_scan < this.currentBlockHeight);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error checking wallet state:', error);
|
||||||
|
}
|
||||||
|
} catch (scanError) {
|
||||||
|
console.error('❌ Failed to scan blocks:', scanError);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to update device with new tx');
|
console.error('Failed to update device with new tx');
|
||||||
}
|
}
|
||||||
@ -1489,12 +1618,24 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getAmount(): BigInt {
|
public getAmount(): BigInt {
|
||||||
const amount = this.sdkClient.get_available_amount();
|
if (!this.sdkClient) {
|
||||||
return amount;
|
throw new Error('SDK not initialized - cannot get amount');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const amount = this.sdkClient.get_available_amount();
|
||||||
|
console.log(`💰 SDK get_available_amount() returned: ${amount}`);
|
||||||
|
return amount;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error calling get_available_amount():', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceAddress(): string {
|
getDeviceAddress(): string {
|
||||||
try {
|
try {
|
||||||
|
if (!this.sdkClient) {
|
||||||
|
throw new Error('WebAssembly SDK not initialized - memory too high');
|
||||||
|
}
|
||||||
return this.sdkClient.get_address();
|
return this.sdkClient.get_address();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to get device address: ${e}`);
|
throw new Error(`Failed to get device address: ${e}`);
|
||||||
@ -1658,6 +1799,9 @@ export default class Services {
|
|||||||
async createNewDevice() {
|
async createNewDevice() {
|
||||||
let spAddress = '';
|
let spAddress = '';
|
||||||
try {
|
try {
|
||||||
|
if (!this.sdkClient) {
|
||||||
|
throw new Error('WebAssembly SDK not initialized - cannot create device');
|
||||||
|
}
|
||||||
// We set birthday later when we have the chain tip from relay
|
// We set birthday later when we have the chain tip from relay
|
||||||
spAddress = await this.sdkClient.create_new_device(0, 'signet');
|
spAddress = await this.sdkClient.create_new_device(0, 'signet');
|
||||||
const device = this.dumpDeviceFromMemory();
|
const device = this.dumpDeviceFromMemory();
|
||||||
@ -1682,6 +1826,9 @@ export default class Services {
|
|||||||
throw new Error('Current block height not set');
|
throw new Error('Current block height not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update user status
|
||||||
|
this.updateUserStatus('🔄 Synchronizing wallet with blockchain...');
|
||||||
|
|
||||||
let device: Device | null = null;
|
let device: Device | null = null;
|
||||||
try {
|
try {
|
||||||
device = await this.getDeviceFromDatabase();
|
device = await this.getDeviceFromDatabase();
|
||||||
@ -1726,11 +1873,14 @@ export default class Services {
|
|||||||
try {
|
try {
|
||||||
const device = this.dumpDeviceFromMemory();
|
const device = this.dumpDeviceFromMemory();
|
||||||
await this.saveDeviceInDatabase(device);
|
await this.saveDeviceInDatabase(device);
|
||||||
|
this.updateUserStatus('✅ Wallet synchronized with blockchain');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`Failed to save updated device: ${e}`);
|
console.error(`Failed to save updated device: ${e}`);
|
||||||
|
this.updateUserStatus('⚠️ Wallet synchronization completed with warnings');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Up to date, just returns
|
// Up to date, just returns
|
||||||
|
this.updateUserStatus('✅ Wallet already synchronized');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1937,6 +2087,10 @@ export default class Services {
|
|||||||
public async restoreSecretsFromDB() {
|
public async restoreSecretsFromDB() {
|
||||||
const db = await Database.getInstance();
|
const db = await Database.getInstance();
|
||||||
try {
|
try {
|
||||||
|
if (!this.sdkClient) {
|
||||||
|
console.log('WebAssembly SDK not initialized - skipping secrets restoration');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sharedSecrets: Record<string, string> = await db.dumpStore('shared_secrets');
|
const sharedSecrets: Record<string, string> = await db.dumpStore('shared_secrets');
|
||||||
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets');
|
const unconfirmedSecrets = await db.dumpStore('unconfirmed_secrets');
|
||||||
const secretsStore = {
|
const secretsStore = {
|
||||||
@ -2178,7 +2332,7 @@ export default class Services {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a flag to prevent processing the same handshake multiple times
|
// Add a flag to prevent processing the same handshake multiple times
|
||||||
const handshakeKey = `${url}_${Date.now()}`;
|
const handshakeKey = `${url}_${JSON.stringify(handshakeMsg.processes_list)}`;
|
||||||
if (this.processedHandshakes && this.processedHandshakes.has(handshakeKey)) {
|
if (this.processedHandshakes && this.processedHandshakes.has(handshakeKey)) {
|
||||||
console.debug('Handshake already processed for', url);
|
console.debug('Handshake already processed for', url);
|
||||||
return;
|
return;
|
||||||
@ -2479,7 +2633,9 @@ export default class Services {
|
|||||||
roles: Record<string, RoleDefinition>[]
|
roles: Record<string, RoleDefinition>[]
|
||||||
) {
|
) {
|
||||||
console.log('Requesting data from peers');
|
console.log('Requesting data from peers');
|
||||||
const membersList = Object.values(this.getAllMembers());
|
const membersList = Object.values(this.getAllMembers()).map(member => ({
|
||||||
|
sp_addresses: member.sp_addresses
|
||||||
|
}));
|
||||||
try {
|
try {
|
||||||
// Convert objects to strings for WASM compatibility
|
// Convert objects to strings for WASM compatibility
|
||||||
const rolesString = JSON.stringify(roles);
|
const rolesString = JSON.stringify(roles);
|
||||||
|
|||||||
@ -2542,35 +2542,20 @@ async function onCreateButtonClick() {
|
|||||||
console.log('🔍 DEBUG: protocol:', window.location.protocol);
|
console.log('🔍 DEBUG: protocol:', window.location.protocol);
|
||||||
|
|
||||||
const { secureCredentialsService } = await import('../services/secure-credentials.service');
|
const { secureCredentialsService } = await import('../services/secure-credentials.service');
|
||||||
updateCreatorStatus('🔐 Click to authenticate with browser...');
|
updateCreatorStatus('🔐 Authenticating with browser...');
|
||||||
|
|
||||||
// Force user interaction before WebAuthn
|
// Auto-trigger WebAuthn authentication
|
||||||
console.log('🔍 DEBUG: Waiting for user interaction...');
|
console.log('🔍 DEBUG: Auto-triggering WebAuthn authentication...');
|
||||||
|
|
||||||
// Create a button that requires user click
|
try {
|
||||||
const authButton = document.createElement('button');
|
// This should trigger the browser popup automatically
|
||||||
authButton.textContent = '🔐 Authenticate with Browser';
|
await secureCredentialsService.generateSecureCredentials('4nk-pairing-password');
|
||||||
authButton.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:9999;padding:20px;font-size:18px;background:#007bff;color:white;border:none;border-radius:8px;cursor:pointer;';
|
console.log('✅ WebAuthn credentials obtained');
|
||||||
|
updateCreatorStatus('✅ Browser authentication successful');
|
||||||
// Show button and wait for click
|
} catch (error) {
|
||||||
document.body.appendChild(authButton);
|
console.error('❌ WebAuthn failed:', error);
|
||||||
|
updateCreatorStatus('❌ Browser authentication failed');
|
||||||
await new Promise<void>((resolve) => {
|
}
|
||||||
authButton.onclick = async () => {
|
|
||||||
document.body.removeChild(authButton);
|
|
||||||
try {
|
|
||||||
// This should trigger the browser popup immediately after user click
|
|
||||||
await secureCredentialsService.generateSecureCredentials('4nk-pairing-password');
|
|
||||||
console.log('✅ WebAuthn credentials obtained');
|
|
||||||
updateCreatorStatus('✅ Browser authentication successful');
|
|
||||||
resolve();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('❌ WebAuthn failed:', error);
|
|
||||||
updateCreatorStatus('❌ Browser authentication failed');
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ WebAuthn failed, continuing with fallback:', error);
|
console.warn('⚠️ WebAuthn failed, continuing with fallback:', error);
|
||||||
updateCreatorStatus('⚠️ Using fallback authentication');
|
updateCreatorStatus('⚠️ Using fallback authentication');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user