ci: docker_tag=dev-test

Motivations:
- Fix 502 en prod via build multi-pages et routes stables
- Stabilize IndexedDB (création stores) et faucet obligatoire
- Déploiement robuste: port 3004 garanti et logs

Modifications:
- Vite: inputs multi-pages, alias npm deploy:front
- Router/redirects: chemins
- Pages setup: URLs corrigées, suppression log faucet disabled
- DB: bump version à 5, upgrade crée stores manquants
- Script: scripts/deploy_front.sh (kill 3004, clean, build, start bg)
- Lint: perf monitor param non utilisé, warnings corrigés

Page affectées:
- vite.config.ts, src/router.ts
- src/pages/* (home, pairing, block-sync, wallet-setup, security-setup)
- src/services/* (database-config, performance-monitor)
- package.json, scripts/deploy_front.sh
This commit is contained in:
NicolasCantu 2025-10-30 11:46:04 +01:00
parent 25e0272959
commit bd1762ee0c
41 changed files with 1220 additions and 674 deletions

View File

@ -118,7 +118,6 @@ voir les fichiers README.md
#### 🚀 Déploiement
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
* **Lancement :** ne lance aucun déploiement sans demander avant

View File

@ -0,0 +1,360 @@
---
alwaysApply: true
---
# IHM_CLIENT - Analyse du Projet
## Vue d'ensemble
Application client Web5 pour l'écosystème 4NK permettant la gestion sécurisée des appareils, le pairing et les signatures de documents.
## Architecture Technique
### Stack Technologique
- **Frontend**: TypeScript, Vite, HTML5, CSS3
- **SDK**: Rust compilé en WebAssembly
- **Storage**: IndexedDB, Service Workers
- **Communication**: WebSockets, PostMessage API
- **Construction**: Vite avec plugins WASM et top-level await
### Configuration
- **Port**: 3004
- **URL**: https://dev3.4nkweb.com
- **Proxy**: `/storage` → https://dev3.4nkweb.com/storage
- **Base URL**: Variables d'environnement VITE_BASEURL, VITE_BOOTSTRAPURL, VITE_STORAGEURL, VITE_BLINDBITURL
### Structure du Projet
```
ihm_client_dev3/
├── src/
│ ├── components/ # Composants UI réutilisables
│ │ ├── account-nav/
│ │ ├── device-management/
│ │ ├── iframe-pairing/
│ │ ├── login-modal/
│ │ ├── secure-credentials/
│ │ ├── security-mode-selector/
│ │ └── validation-modal/
│ ├── pages/ # Pages de l'application
│ │ ├── account/
│ │ ├── birthday-setup/
│ │ ├── block-sync/
│ │ ├── home/
│ │ ├── pairing/
│ │ ├── security-setup/
│ │ └── wallet-setup/
│ ├── services/ # Services métier
│ │ ├── service.ts # Service principal (singleton)
│ │ ├── database.service.ts
│ │ ├── secure-credentials.service.ts
│ │ ├── security-mode.service.ts
│ │ ├── pairing.service.ts
│ │ ├── iframe-pairing.service.ts
│ │ └── websocket-manager.ts
│ ├── repositories/ # Accès aux données
│ │ ├── device.repository.ts
│ │ └── process.repository.ts
│ ├── models/ # Types et interfaces
│ ├── utils/ # Utilitaires
│ ├── service-workers/ # Workers pour opérations async
│ ├── router.ts # Router principal
│ └── main.ts # Point d'entrée
├── pkg/ # SDK WebAssembly compilé
├── docs/ # Documentation
├── IA_agents/ # Documentation pour IA
├── test-browser/ # Tests Playwright
└── logs/ # Logs centralisés
```
## Architecture Fonctionnelle
### Flux d'Initialisation
1. **Security Setup** → Configuration du mode de sécurité
2. **Wallet Setup** → Création du wallet Bitcoin
3. **Birthday Setup** → Configuration de la date anniversaire
4. **Block Sync** → Synchronisation initiale des blocs
5. **Pairing** → Appairage de l'appareil
6. **Account** → Interface principale
### Système de Modes de Sécurité
| Mode | Nom | Niveau | Stockage Clé PBKDF2 |
|------|-----|--------|---------------------|
| `proton-pass` | Proton Pass | High | WebAuthn du navigateur |
| `os` | OS Authenticator | High | WebAuthn du système |
| `otp` | OTP | High | En clair |
| `password` | Mot de passe | Low | Chiffré avec mot de passe |
| `none` | Aucune | Critical | Clé en dur (déconseillé) |
### Base de Données IndexedDB
**Stores:**
- `pbkdf2keys` : Clés PBKDF2 chiffrées par mode de sécurité
- `wallet` : Wallet chiffré (device + wallet data)
- `credentials` : Credentials de pairing (après pairing)
- `env` : Variables d'environnement internes
- `processes` : Processus de communication
- `labels` : Labels de transactions
- `shared_secrets` : Secrets partagés
- `unconfirmed_secrets` : Secrets non confirmés
- `diffs` : Différences de synchronisation
- `data` : Données générales
### Système de Processus
Le système de processus est générique et réutilisable pour créer des "contrats" entre des membres avec niveaux d'accès différents.
**Concepts:**
- **Process**: Contrat décentralisé entre plusieurs membres
- **State**: État du processus avec données publiques/privées
- **Roles**: Définition des permissions par rôle
- **Members**: Participants identifiés par pairing process ID
**Données:**
- **Public**: Accessibles à tous, portées automatiquement
- **Private**: Chiffrées via PCD commitments, distribuées aux membres autorisés
- **Roles**: Quorum et champs accessibles par rôle
**Cycle de vie:**
1. Création → `createProcess()`
2. Mise à jour → `updateProcess()`
3. Synchronisation PRD → `createPrdUpdate()`
4. Validation → `approveChange()`
5. Commit blockchain → État immuable
6. Accès → `getPublicData()` / `decryptAttribute()`
### Système de Pairing
**But**: Créer une identité numérique vérifiable pour MFA entre appareils
**Caractéristiques:**
- Un wallet peut être appairé à plusieurs appareils
- Processus blockchain avec états commités
- Synchronisation via relais
- Contrôle via 4 mots
**Flux Créateur:**
1. `createPairingProcess()` avec son adresse
2. `generateQRCode()` pour le joiner
3. `waitForJoinerAndUpdateProcess()`
4. `waitForPairingCommitment()`
5. `confirmPairing()`
**Flux Joiner:**
1. `discoverAndJoinPairingProcess()` via QR code
2. `waitForPairingCommitment()`
3. `confirmPairing()`
## Services Principaux
### Services (Singleton)
**Initialisation:**
- WebAssembly SDK
- WebSocket connections
- Database restoration
- Process listening
**Principales méthodes:**
- `getDeviceFromDatabase()` : Récupération du device
- `createPairingProcess()` : Création de processus de pairing
- `createProcess()` : Création de processus générique
- `updateProcess()` : Mise à jour de processus
- `getProcess()` : Récupération de processus
- `checkConnections()` : Vérification des connexions entre membres
- `decryptAttribute()` : Déchiffrement d'attribut privé
- `getPublicData()` : Récupération des données publiques
### Database Service
**Gestion IndexedDB:**
- Singleton avec service worker
- Stores configurables via `storeDefinitions`
- Transactions asynchrones
- Cache management
### Secure Credentials Service
**Gestion des credentials:**
- Génération et stockage de credentials de pairing
- Récupération avec déchiffrement
- Support WebAuthn pour modes haute sécurité
### Security Mode Service
**Gestion des modes:**
- Détection du mode actuel
- Stockage dans IndexedDB
- Authentification selon mode
## Logging et Erreurs
### SecureLogger
**Système centralisé:**
- Niveaux: DEBUG, INFO, WARN, ERROR
- Sanitisation automatique des données sensibles
- Contexte enrichi avec composant et métadonnées
- Formatage cohérent
**Bonnes pratiques:**
- Utiliser `secureLogger` au lieu de `console.*`
- Toujours spécifier le composant
- Ajouter métadonnées utiles
- Messages clairs et concis
- Vérifications réelles avant logs de succès
**Patterns:**
- 🔍 DEBUG : Informations de débogage
- ✅ INFO : Succès et initialisations
- ⚠️ WARN : Avertissements non critiques
- ❌ ERROR : Erreurs critiques
## Router
### Navigation
**Logique de progression:**
1. Si pairing → account
2. Si date anniversaire → pairing
3. Si wallet → birthday-setup
4. Si pbkdf2 → wallet-setup
5. Sinon → security-setup
**Routes principales:**
- `/security-setup` : Configuration sécurité
- `/wallet-setup` : Création wallet
- `/birthday-setup` : Configuration birthday
- `/block-sync` : Synchronisation blocs
- `/pairing` : Appairage
- `/account` : Interface principale
- `/home` : Page d'accueil avec login
### Message Handlers
Gestion des messages PostMessage pour communication iframe:
- `REQUEST_LINK` : Liaison avec site externe
- `CREATE_PAIRING` : Création de pairing
- `GET_PROCESSES` : Récupération des processus
- `CREATE_PROCESS` : Création de processus
- `UPDATE_PROCESS` : Mise à jour de processus
- `VALIDATE_STATE` : Validation d'état
- Etc.
## WebSocket
**Gestion:**
- Connexions multiples (BOOTSTRAP, STORAGE, BLINDBIT)
- Handshake automatique
- Retry automatique en cas de déconnexion
- Event bus pour messages
**Messages:**
- HandshakeMessage : Synchronisation initiale
- NewTxMessage : Nouvelles transactions
- Process updates : Mises à jour de processus
## Dépendances
**Principales:**
- `axios` : Requêtes HTTP
- `jose` : JWT et chiffrement
- `jsonwebtoken` : Tokens JWT
- `pdf-lib` : Manipulation PDF
- `sweetalert2` : Modals UI
**SDK WebAssembly:**
- `pkg/sdk_client.js` : SDK principal
- `pkg/sdk_client_bg.wasm` : Binaire WebAssembly
- Types TypeScript générés
## Tests
**Structure:**
- `test-browser/` : Tests Playwright
- Tests pour chaque parcours utilisateur
- Intégration avec mocked data
## User Stories
34 stories définies couvrant:
- Login (adresse device, QR code)
- Process management
- Account management
- Pairing
- Wallet
- Chat
- Signatures
## Configuration TypeScript
**Strict Mode:**
- `strict: true`
- `noImplicitAny: true`
- `noImplicitReturns: true`
- `forceConsistentCasingInFileNames: true`
**Modules:**
- `module: ESNext`
- `target: ESNext`
- `lib: ["DOM", "DOM.Iterable", "ESNext", "webworker"]`
- `isolatedModules: true`
## Points Critiques
### Sécurité
- Clés PBKDF2 toujours chiffrées (sauf mode OTP)
- Wallet toujours chiffré en base
- WebAuthn pour modes haute sécurité
- Sanitisation automatique des logs
### Performance
- Cache désactivé (`maxCacheSize = 0`)
- Memory management avec retry
- Lazy loading des composants
- Service workers pour opérations async
### Robustesse
- Retry automatique pour opérations critiques
- Vérifications réelles avant logs de succès
- Fallback et navigation automatique
- Gestion des erreurs IndexedDB
## Documentation
**Fichiers clés:**
- `docs/INITIALIZATION_FLOW.md` : Flux d'initialisation détaillé
- `docs/PROCESS_SYSTEM_ARCHITECTURE.md` : Architecture des processus
- `docs/PAIRING_SYSTEM_ANALYSIS.md` : Analyse du pairing
- `docs/LOGGING_GUIDELINES.md` : Guide de logging
- `IA_agents/all.md` : Règles pour IA
- `README.md` : Vue d'ensemble
## Développement
**Scripts:**
- `npm run start` : Serveur de développement
- `npm run build` : Build de production
- `npm run quality` : Vérification qualité
- `npm run lint` : Linting
- `npm run type-check` : Vérification TypeScript
**Prérequis:**
- Node.js 18+
- Rust (pour SDK)
- npm ou yarn
## Points d'Attention
1. **Ordre des modes testés**: `['none', 'otp', 'password', 'os', 'proton-pass']`
2. **Store credentials**: Utilisé uniquement après pairing
3. **Redirection automatique**: 3s après création wallet vers birthday-setup
4. **Synchronisation IndexedDB**: Utilisation directe pour éviter problèmes service worker
5. **Retry automatique**: Jusqu'à 5 tentatives pour vérifications wallet
6. **Vérifications réelles**: Logs de succès uniquement après vérification
7. **WebAssembly memory**: Monitoring et cleanup automatique
8. **Singleton patterns**: Services, Database, ModalService
9. **Message handlers**: Tous nécessitent validation token
10. **Process lifecycle**: Toujours vérifier état committé avant manipulation

View File

@ -1,26 +0,0 @@
# Dependencies
node_modules/
dist/
pkg/
# Build outputs
*.js
*.d.ts
!eslint.config.js
# Config files
vite.config.ts
tsconfig.json
tsconfig.build.json
# Test files
**/*.test.ts
**/*.spec.ts
# Generated files
coverage/
.nyc_output/
# Logs
logs/
*.log

2
.gitignore vendored
View File

@ -2,7 +2,6 @@
# 🦀 Rust
# ----------------------------
target/
pkg/
Cargo.lock
*.rs.bk
**/*.rlib
@ -30,7 +29,6 @@ pnpm-lock.yaml
# ----------------------------
# 🧱 IDE / Éditeurs
# ----------------------------
.vscode/
.idea/
*.iml
*.suo

8
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
]
}

59
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,59 @@
{
// ESLint Configuration
"eslint.enable": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"eslint.workingDirectories": ["."],
"eslint.options": {
"overrideConfigFile": "eslint.config.js"
},
// TypeScript Configuration
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.tsserver.maxTsServerMemory": 4096,
// Problems Panel Configuration
"problems.showCurrentInStatus": true,
"problems.autoReveal": true,
// File Associations
"files.associations": {
"*.ts": "typescript",
"*.tsx": "typescriptreact"
},
// Editor Configuration
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
// TypeScript Specific
"typescript.suggest.autoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
// Exclude patterns for file watcher
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/pkg/**": true,
"**/logs/**": true,
"**/.git/**": true
},
// Search exclusion
"search.exclude": {
"**/node_modules": true,
"**/dist": true,
"**/pkg": true,
"**/logs": true,
"**/.git": true
}
}

View File

@ -94,7 +94,6 @@ voir les fichiers README.md
#### 🚀 Déploiement
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
* **Lancement :** ne lance aucun déploiement sans demander avant

View File

@ -95,7 +95,6 @@ voir docs/INITIALIZATION_FLOW.md
#### 🚀 Déploiement
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
* **Lancement :** ne lance aucun déploiement sans demander avant

View File

@ -8,11 +8,11 @@
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev3/pkg ../sdk_client --target bundler --dev",
"start": "vite --host 0.0.0.0",
"build": "tsc && vite build",
"deploy": "sudo cp -r dist/* /var/www/html/",
"deploy:front": "./scripts/deploy_front.sh",
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\"",
"build:dist": "tsc -p tsconfig.build.json",
"lint": "eslint src/ --ext .ts,.tsx --fix",
"lint:check": "eslint src/ --ext .ts,.tsx",
"lint": "eslint src/ --fix",
"lint:check": "eslint src/",
"type-check": "tsc --noEmit",
"quality": "npm run prettify",
"quality:strict": "npm run type-check && npm run lint:check && npm run prettify",

68
scripts/deploy_front.sh Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LOG_DIR="$PROJECT_ROOT/logs"
PID_FILE="$LOG_DIR/ihm_client_dev3.front.pid"
LOG_FILE="$LOG_DIR/ihm_client_dev3.front.log"
PORT=3004
mkdir -p "$LOG_DIR"
echo "[deploy] Ensuring nothing listens on :$PORT..."
if ss -ltnp | grep -q ":$PORT"; then
# Extract PID(s) for the port
PIDS=$(ss -ltnp | awk -v p=":$PORT" '$0 ~ p {print $NF}' | sed -E 's/.*pid=([0-9]+).*/\1/' | sort -u)
for PID in $PIDS; do
if [[ "$PID" =~ ^[0-9]+$ ]]; then
echo "[deploy] Killing PID $PID on port $PORT"
kill -TERM "$PID" || true
# Wait up to 10s for process to exit
for i in {1..10}; do
if ! ps -p "$PID" >/dev/null 2>&1; then break; fi
sleep 1
done
if ps -p "$PID" >/dev/null 2>&1; then
echo "[deploy] Force killing PID $PID"
kill -KILL "$PID" || true
fi
fi
done
fi
echo "[deploy] Cleaning Vite caches and previous dist..."
rm -rf "$PROJECT_ROOT/node_modules/.vite" "$PROJECT_ROOT/.vite" "$PROJECT_ROOT/dist"
echo "[deploy] Building production bundle..."
cd "$PROJECT_ROOT"
npm run build
echo "[deploy] Starting Vite dev server on :$PORT (non-bloquant) ..."
if [[ -f "$PID_FILE" ]]; then
# Clean stale PID file if any
OLD_PID=$(cat "$PID_FILE" || true)
if [[ -n "${OLD_PID}" ]] && ps -p "$OLD_PID" >/dev/null 2>&1; then
echo "[deploy] Previous PID $OLD_PID still running, terminating"
kill -TERM "$OLD_PID" || true
fi
rm -f "$PID_FILE"
fi
nohup npm run start >"$LOG_FILE" 2>&1 &
NEW_PID=$!
echo "$NEW_PID" > "$PID_FILE"
echo "[deploy] Launched PID $NEW_PID. Tail logs: tail -f $LOG_FILE"
echo "[deploy] Verifying port $PORT availability..."
for i in {1..10}; do
if ss -ltnp | grep -q ":$PORT"; then
echo "[deploy] OK: port $PORT is listening."
exit 0
fi
sleep 1
done
echo "[deploy] ERROR: port $PORT not listening after start. Check $LOG_FILE"
exit 1

View File

@ -232,7 +232,7 @@ export class SecureCredentialsComponent {
try {
await this.updateUI();
this.showMessage('Credentials actualisés', 'success');
} catch (_error) {
} catch {
this.showMessage('Erreur lors de l\'actualisation', 'error');
}
}

View File

@ -17,12 +17,12 @@ export interface SecurityModeConfig {
export class SecurityModeSelector {
private container: HTMLElement;
private selectedMode: SecurityMode | null = null;
private onModeSelected: (_mode: SecurityMode) => void;
private onModeSelected: (mode: SecurityMode) => void;
private onCancel: () => void;
constructor(
container: HTMLElement,
onModeSelected: (_mode: SecurityMode) => void,
onModeSelected: (mode: SecurityMode) => void,
onCancel: () => void
) {
this.container = container;

View File

@ -22,6 +22,7 @@ export interface INotification {
path?: string;
}
/* eslint-disable no-unused-vars */
export enum MessageType {
// Establish connection and keep alive
LISTENING = 'LISTENING',
@ -75,3 +76,4 @@ export enum MessageType {
PAIRING_4WORDS_SUCCESS = 'PAIRING_4WORDS_SUCCESS',
PAIRING_4WORDS_ERROR = 'PAIRING_4WORDS_ERROR',
}
/* eslint-enable no-unused-vars */

View File

@ -5,201 +5,237 @@
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
import { secureLogger } from '../services/secure-logger';
import { secureLogger } from '../../services/secure-logger';
let isInitializing = false;
document.addEventListener('DOMContentLoaded', async () => {
if (isInitializing) {
secureLogger.warn('⚠️ Birthday setup page already initializing, skipping...', { component: 'BirthdaySetup' });
return;
}
// Type definition for update functions - parameters are template names
/* eslint-disable no-unused-vars */
interface UpdateFunctions {
updateStatus: (message: string, type: 'loading' | 'success' | 'error') => void;
updateProgress: (percent: number) => void;
}
/* eslint-enable no-unused-vars */
isInitializing = true;
secureLogger.info('🎂 Birthday setup page loaded', { component: 'BirthdaySetup' });
secureLogger.debug(`Current URL: ${window.location.href}`, { component: 'BirthdaySetup' });
secureLogger.debug(`Referrer: ${document.referrer}`, { component: 'BirthdaySetup' });
async function initializeServices() {
secureLogger.info('🔄 Importing services...', { component: 'BirthdaySetup' });
const serviceModule = await import('../../services/service');
secureLogger.debug(`Service module imported: ${Object.keys(serviceModule)}`, {
component: 'BirthdaySetup',
});
const status = document.getElementById('status') as HTMLDivElement;
const progressBar = document.getElementById('progressBar') as HTMLDivElement;
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
const Services = serviceModule.default;
if (!Services) {
throw new Error('Services class not found in default export');
}
try {
// Étape 1: Connexion aux relais
updateStatus('🌐 Connexion aux relais...', 'loading');
updateProgress(20);
try {
secureLogger.info('🔄 Importing services...', { component: 'BirthdaySetup' });
const serviceModule = await import('../../services/service');
secureLogger.debug(`Service module imported: ${Object.keys(serviceModule)}`, { component: 'BirthdaySetup' });
// La classe Services est exportée par défaut
const Services = serviceModule.default;
if (!Services) {
throw new Error('Services class not found in default export');
}
secureLogger.info('🔄 Getting existing services instance...', { component: 'BirthdaySetup' });
// Utiliser l'instance existante des services avec gestion des erreurs de mémoire
let services;
try {
services = await Services.getInstance();
secureLogger.info('✅ Services instance obtained successfully', { component: 'BirthdaySetup' });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
secureLogger.error('🚫 Memory error detected', { component: 'BirthdaySetup' });
updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error');
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
}
throw error;
}
// Vérifier les prérequis en base de données
updateStatus('🔍 Vérification des prérequis...', 'loading');
updateProgress(20);
// Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique)
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
secureLogger.warn('⚠️ PBKDF2 key not found in pbkdf2keys store, redirecting to security-setup...', { component: 'BirthdaySetup' });
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 1000);
return;
}
// Vérifier que le wallet existe en base (avec plusieurs tentatives pour gérer les problèmes de synchronisation)
const wallet = await checkWalletWithRetries();
if (!wallet) {
secureLogger.warn('⚠️ Wallet still not found after retries, redirecting to wallet-setup...', { component: 'BirthdaySetup' });
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 1000);
return;
}
// Vérifier que le wallet contient bien les données attendues
if (wallet.sp_wallet?.birthday !== undefined) {
secureLogger.info(`Wallet found in database with birthday: ${wallet.sp_wallet.birthday}`, { component: 'BirthdaySetup' });
} else {
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
}
// Vérifier que tous les prérequis sont remplis
const prerequisitesOk = wallet && pbkdf2KeyResult;
if (prerequisitesOk) {
secureLogger.info('✅ All prerequisites verified', { component: 'BirthdaySetup' });
} else {
throw new Error('Prerequisites verification failed');
}
// Connexion aux relais
await services.connectAllRelays();
// Attendre que la hauteur de bloc soit définie via le handshake
updateStatus('⏳ Attente de la synchronisation avec le réseau...', 'loading');
updateProgress(40);
// Attendre que le handshake arrive et que chain_tip soit défini
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for block height from handshake'));
}, 15000); // 15 secondes de timeout
const checkBlockHeight = () => {
const blockHeight = services.getCurrentBlockHeight();
if (blockHeight !== -1 && blockHeight > 0) {
secureLogger.info('✅ Block height set from handshake: ${blockHeight}', { component: 'BirthdaySetup' });
clearTimeout(timeout);
resolve();
} else {
setTimeout(checkBlockHeight, 100);
}
};
checkBlockHeight();
});
// Vérifier que les relais sont connectés et que le handshake a été reçu
const currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight !== -1 && currentBlockHeight > 0) {
secureLogger.info(`Relays connected successfully, chain_tip: ${currentBlockHeight}`, { component: 'BirthdaySetup' });
secureLogger.info(`Communication handshake completed, chain_tip: ${currentBlockHeight}`, { component: 'BirthdaySetup' });
} else {
throw new Error('Handshake not received or chain_tip not set');
}
// Mettre à jour la date anniversaire du wallet
updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
updateProgress(60);
secureLogger.info('🔄 Calling updateDeviceBlockHeight()...', { component: 'BirthdaySetup' });
await services.updateDeviceBlockHeight();
secureLogger.info('✅ updateDeviceBlockHeight() completed successfully', { component: 'BirthdaySetup' });
// Vérifier que le birthday a bien été mis à jour en récupérant le wallet depuis la base
updateStatus('🔍 Vérification de la mise à jour...', 'loading');
updateProgress(70);
secureLogger.info('🔄 Verifying birthday update...', { component: 'BirthdaySetup' });
const updatedWallet = await services.getDeviceFromDatabase();
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
secureLogger.info(`Birthday updated successfully: ${updatedWallet.sp_wallet.birthday}`, { component: 'BirthdaySetup' });
} else {
secureLogger.error('Birthday update verification failed', new Error('Verification failed'), {
component: 'BirthdaySetup',
data: {
birthday: updatedWallet?.sp_wallet?.birthday,
hasSpWallet: !!updatedWallet?.sp_wallet
}
});
throw new Error(`Birthday update verification failed: expected birthday > 0, got ${updatedWallet?.sp_wallet?.birthday}`);
}
// Redirection vers la page de synchronisation des blocs
updateStatus('🔄 Redirection vers la synchronisation des blocs...', 'loading');
updateProgress(100);
secureLogger.info('🎉 Birthday setup completed successfully - redirecting to block sync', { component: 'BirthdaySetup' });
secureLogger.info('📍 Target URL: /src/pages/block-sync/block-sync.html', { component: 'BirthdaySetup' });
secureLogger.info('⏰ Redirecting in 1 second...', { component: 'BirthdaySetup' });
// Rediriger vers la page de synchronisation des blocs
setTimeout(() => {
secureLogger.info('🔄 Executing redirect now...', { component: 'BirthdaySetup' });
window.location.href = '/src/pages/block-sync/block-sync.html';
}, 1000);
} catch (error) {
secureLogger.error('Services not available', error as Error, { component: 'BirthdaySetup' });
updateStatus('❌ Erreur: Services non disponibles', 'error');
throw error;
}
} catch (error) {
secureLogger.error('Error during birthday setup', error as Error, { component: 'BirthdaySetup' });
updateStatus('❌ Erreur lors de la configuration de la date anniversaire', 'error');
} finally {
isInitializing = false;
}
// Gestion du bouton continuer
continueBtn.addEventListener('click', async () => {
secureLogger.info('🏠 Redirecting to main application...', { component: 'BirthdaySetup' });
// Rediriger vers l'application principale
secureLogger.debug('🎂 Birthday setup completed, checking storage state...', { component: 'BirthdaySetup' });
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
try {
const services = await Services.getInstance();
secureLogger.info('✅ Services instance obtained successfully', {
component: 'BirthdaySetup',
});
return services;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
secureLogger.error('🚫 Memory error detected', { component: 'BirthdaySetup' });
throw new Error(
'WebAssembly initialization failed due to insufficient memory. Please refresh the page.'
);
}
throw error;
}
}
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
status.textContent = message;
status.className = `status ${type}`;
function redirectToSetup(page: string, updateFunctions: UpdateFunctions): void {
secureLogger.warn(`Redirecting to ${page}...`, { component: 'BirthdaySetup' });
updateFunctions.updateStatus(`⚠️ Redirection vers ${page}...`, 'loading');
setTimeout(() => {
window.location.href = `/src/pages/${page}/${page}.html`;
}, 1000);
}
async function verifyPrerequisites(updateFunctions: UpdateFunctions) {
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
redirectToSetup('security-setup', updateFunctions);
return null;
}
const wallet = await checkWalletWithRetries();
if (!wallet) {
redirectToSetup('wallet-setup', updateFunctions);
return null;
}
if (wallet.sp_wallet?.birthday === undefined) {
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
}
secureLogger.info(`Wallet found in database with birthday: ${wallet.sp_wallet.birthday}`, {
component: 'BirthdaySetup',
});
return { wallet, pbkdf2KeyResult };
}
async function waitForBlockHeight(services: any, updateFunctions: UpdateFunctions) {
updateFunctions.updateStatus('⏳ Attente de la synchronisation avec le réseau...', 'loading');
updateFunctions.updateProgress(40);
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Timeout waiting for block height from handshake'));
}, 15000);
const checkBlockHeight = () => {
const blockHeight = services.getCurrentBlockHeight();
if (blockHeight !== -1 && blockHeight > 0) {
secureLogger.info(`✅ Block height set from handshake: ${blockHeight}`, {
component: 'BirthdaySetup',
});
clearTimeout(timeout);
resolve();
} else {
setTimeout(checkBlockHeight, 100);
}
};
checkBlockHeight();
});
const currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight !== -1 && currentBlockHeight > 0) {
secureLogger.info(`Relays connected successfully, chain_tip: ${currentBlockHeight}`, {
component: 'BirthdaySetup',
});
} else {
throw new Error('Handshake not received or chain_tip not set');
}
}
async function updateBirthday(services: any, updateFunctions: UpdateFunctions) {
updateFunctions.updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
updateFunctions.updateProgress(60);
secureLogger.info('🔄 Calling updateDeviceBlockHeight()...', { component: 'BirthdaySetup' });
await services.updateDeviceBlockHeight();
secureLogger.info('✅ updateDeviceBlockHeight() completed successfully', {
component: 'BirthdaySetup',
});
}
async function verifyBirthdayUpdate(services: any, updateFunctions: UpdateFunctions) {
updateFunctions.updateStatus('🔍 Vérification de la mise à jour...', 'loading');
updateFunctions.updateProgress(70);
secureLogger.info('🔄 Verifying birthday update...', { component: 'BirthdaySetup' });
const updatedWallet = await services.getDeviceFromDatabase();
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
secureLogger.info(`Birthday updated successfully: ${updatedWallet.sp_wallet.birthday}`, {
component: 'BirthdaySetup',
});
} else {
secureLogger.error('Birthday update verification failed', new Error('Verification failed'), {
component: 'BirthdaySetup',
data: {
birthday: updatedWallet?.sp_wallet?.birthday,
hasSpWallet: !!updatedWallet?.sp_wallet,
},
});
throw new Error(
`Birthday update verification failed: expected birthday > 0, got ${updatedWallet?.sp_wallet?.birthday}`
);
}
}
function redirectToBlockSync(updateFunctions: UpdateFunctions) {
updateFunctions.updateStatus('🔄 Redirection vers la synchronisation des blocs...', 'loading');
updateFunctions.updateProgress(100);
secureLogger.info('🎉 Birthday setup completed successfully - redirecting to block sync', {
component: 'BirthdaySetup',
});
setTimeout(() => {
secureLogger.info('🔄 Executing redirect now...', { component: 'BirthdaySetup' });
window.location.href = '/src/pages/block-sync/block-sync.html';
}, 1000);
}
document.addEventListener('DOMContentLoaded', async () => {
if (isInitializing) {
secureLogger.warn('⚠️ Birthday setup page already initializing, skipping...', {
component: 'BirthdaySetup',
});
return;
}
isInitializing = true;
secureLogger.info('🎂 Birthday setup page loaded', { component: 'BirthdaySetup' });
secureLogger.debug(`Current URL: ${window.location.href}`, { component: 'BirthdaySetup' });
secureLogger.debug(`Referrer: ${document.referrer}`, { component: 'BirthdaySetup' });
const status = document.getElementById('status') as HTMLDivElement;
const progressBar = document.getElementById('progressBar') as HTMLDivElement;
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
status.textContent = message;
status.className = `status ${type}`;
}
function updateProgress(percent: number) {
progressBar.style.width = `${percent}%`;
}
const updateFunctions: UpdateFunctions = { updateStatus, updateProgress };
try {
updateStatus('🌐 Connexion aux relais...', 'loading');
updateProgress(20);
let services;
try {
services = await initializeServices();
} catch (error) {
secureLogger.error('Services not available', error as Error, { component: 'BirthdaySetup' });
updateStatus('❌ Erreur: Services non disponibles', 'error');
throw error;
}
function updateProgress(percent: number) {
progressBar.style.width = `${percent}%`;
updateStatus('🔍 Vérification des prérequis...', 'loading');
updateProgress(20);
const prerequisites = await verifyPrerequisites(updateFunctions);
if (!prerequisites) {
return; // Already redirected
}
secureLogger.info('✅ All prerequisites verified', { component: 'BirthdaySetup' });
await services.connectAllRelays();
await waitForBlockHeight(services, updateFunctions);
await updateBirthday(services, updateFunctions);
await verifyBirthdayUpdate(services, updateFunctions);
redirectToBlockSync(updateFunctions);
} catch (error) {
secureLogger.error('Error during birthday setup', error as Error, {
component: 'BirthdaySetup',
});
updateStatus('❌ Erreur lors de la configuration de la date anniversaire', 'error');
} finally {
isInitializing = false;
}
// Gestion du bouton continuer
continueBtn.addEventListener('click', async () => {
secureLogger.info('🏠 Redirecting to main application...', { component: 'BirthdaySetup' });
// Rediriger vers l'application principale
secureLogger.debug('🎂 Birthday setup completed, checking storage state...', {
component: 'BirthdaySetup',
});
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
});
});

View File

@ -1,243 +1,264 @@
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
import { secureLogger } from '../services/secure-logger';
import { secureLogger } from '../../services/secure-logger';
import Services from '../../services/service';
document.addEventListener("DOMContentLoaded", async () => {
secureLogger.info('🔄 Block sync page loaded', { component: 'BlockSync' });
document.addEventListener('DOMContentLoaded', async () => {
secureLogger.info('🔄 Block sync page loaded', { component: 'BlockSync' });
const status = document.getElementById("status") as HTMLElement;
const progressBar = document.getElementById("progressBar") as HTMLElement;
const continueBtn = document.getElementById("continueBtn") as HTMLButtonElement;
const status = document.getElementById('status') as HTMLElement;
const progressBar = document.getElementById('progressBar') as HTMLElement;
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
if (status) {
status.textContent = message;
status.className = `status ${type}`;
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
if (status) {
status.textContent = message;
status.className = `status ${type}`;
}
}
function updateProgress(percentage: number) {
if (progressBar) {
progressBar.style.width = `${percentage}%`;
}
}
function updateSyncItem(
elementId: string,
value: string,
status: 'pending' | 'completed' | 'error' = 'pending'
) {
const element = document.getElementById(elementId);
if (element) {
element.textContent = value;
element.className = `sync-status ${status}`;
}
}
// Gestion du bouton continuer (définie avant le try pour être toujours disponible)
if (continueBtn) {
continueBtn.addEventListener('click', async () => {
secureLogger.info('🔗 Redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
});
}
try {
// Vérifier les prérequis
secureLogger.debug('🔍 Verifying prerequisites...', { component: 'BlockSync' });
updateStatus('🔍 Vérification des prérequis...', 'loading');
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
secureLogger.warn('⚠️ PBKDF2 key not found, redirecting to security-setup...', {
component: 'BlockSync',
});
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 1000);
return;
}
const wallet = await checkWalletWithRetries();
if (!wallet) {
secureLogger.warn('⚠️ Wallet not found, redirecting to wallet-setup...', {
component: 'BlockSync',
});
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 1000);
return;
}
if (!wallet.sp_wallet?.birthday || wallet.sp_wallet.birthday === 0) {
secureLogger.warn('⚠️ Birthday not configured, redirecting to birthday-setup...', {
component: 'BlockSync',
});
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 1000);
return;
}
secureLogger.info('✅ All prerequisites verified for block sync', { component: 'BlockSync' });
updateStatus('✅ Prerequisites verified', 'success');
// Initialiser les services
secureLogger.info('🔄 Waiting for services to be ready...', { component: 'BlockSync' });
updateStatus('🔄 Initialisation des services...', 'loading');
let services: Services;
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
try {
secureLogger.info(
'🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...',
{ component: 'BlockSync' }
);
services = await Services.getInstance();
secureLogger.info('✅ Services initialized successfully', { component: 'BlockSync' });
break;
} catch (error) {
attempts++;
secureLogger.warn(
`Services initialization failed (attempt ${attempts}/${maxAttempts})`,
error as Error,
{ component: 'BlockSync' }
);
if (attempts >= maxAttempts) {
throw new Error('Failed to initialize services after maximum attempts');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
function updateProgress(percentage: number) {
if (progressBar) {
progressBar.style.width = `${percentage}%`;
if (!services!) {
throw new Error('Services not initialized');
}
// Vérifier si le wallet est déjà synchronisé
const currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight === -1) {
secureLogger.warn('⚠️ Block height not available, connecting to relays...', {
component: 'BlockSync',
});
updateStatus('⚠️ Connexion aux relays...', 'loading');
// Attendre que les services se connectent aux relays
await services.connectAllRelays();
// Attendre que la hauteur de bloc soit définie
await services.waitForBlockHeight();
}
const finalBlockHeight = services.getCurrentBlockHeight();
const birthday = wallet.sp_wallet.birthday;
const lastScan = wallet.sp_wallet.last_scan || 0;
const toScan = Math.max(0, finalBlockHeight - lastScan);
secureLogger.info(
'📊 Sync info: current=${finalBlockHeight}, birthday=${birthday}, lastScan=${lastScan}, toScan=${toScan}',
{ component: 'BlockSync' }
);
// Mettre à jour l'interface
updateSyncItem('currentBlock', finalBlockHeight.toString(), 'completed');
updateSyncItem('birthday', birthday.toString(), 'completed');
updateSyncItem('lastScan', lastScan.toString(), 'completed');
if (toScan === 0) {
secureLogger.info('✅ Wallet already synchronized', { component: 'BlockSync' });
updateStatus('✅ Wallet déjà synchronisé', 'success');
updateSyncItem('blocksToScan', '0', 'completed');
updateSyncItem('blocksScanned', '0', 'completed');
updateSyncItem('transactionsFound', '0', 'completed');
// Activer le bouton et rediriger automatiquement
if (continueBtn) {
continueBtn.disabled = false;
continueBtn.textContent = 'Aller au pairing';
}
// Auto-redirection après 3 secondes
setTimeout(() => {
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
}, 3000);
return;
}
// Afficher la barre de progression
updateProgress(0);
updateStatus('🔄 Synchronisation en cours...', 'loading');
updateSyncItem('blocksToScan', toScan.toString(), 'pending');
// Intercepter les messages de progression du scan
// Fonction pour intercepter les messages de progression
const originalConsoleLog = console.log;
console.log = (...args: any[]) => {
const message = args.join(' ');
if (message.includes('Scan progress:')) {
// Extraire les informations de progression
const progressMatch = message.match(/Scan progress: (\d+)\/(\d+) \((\d+)%\)/);
if (progressMatch) {
const currentBlock = parseInt(progressMatch[1]);
const totalBlocks = parseInt(progressMatch[2]);
const percentage = parseInt(progressMatch[3]);
// Mettre à jour l'interface avec les détails de progression
updateStatus(
`🔍 Synchronisation des blocs: ${currentBlock}/${totalBlocks} (${percentage}%)`,
'loading'
);
updateProgress(percentage);
// Mettre à jour les éléments de synchronisation
updateSyncItem('blocksScanned', currentBlock.toString(), 'pending');
updateSyncItem('blocksToScan', (totalBlocks - currentBlock).toString(), 'pending');
// Progress message available if needed: `Bloc ${currentBlock}/${totalBlocks} (${percentage}%)`
}
}
function updateSyncItem(elementId: string, value: string, status: 'pending' | 'completed' | 'error' = 'pending') {
const element = document.getElementById(elementId);
if (element) {
element.textContent = value;
element.className = `sync-status ${status}`;
}
}
// Gestion du bouton continuer (définie avant le try pour être toujours disponible)
if (continueBtn) {
continueBtn.addEventListener('click', async () => {
secureLogger.info('🔗 Redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
});
}
}
// Appeler la fonction console.log originale
originalConsoleLog.apply(console, args);
};
try {
// Vérifier les prérequis
secureLogger.debug('🔍 Verifying prerequisites...', { component: 'BlockSync' });
updateStatus('🔍 Vérification des prérequis...', 'loading');
// Effectuer la synchronisation
await services.updateDeviceBlockHeight();
secureLogger.info('✅ Block scan completed successfully', { component: 'BlockSync' });
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
secureLogger.warn('⚠️ PBKDF2 key not found, redirecting to security-setup...', { component: 'BlockSync' });
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 1000);
return;
}
// Restaurer la fonction console.log originale
console.log = originalConsoleLog;
const wallet = await checkWalletWithRetries();
if (!wallet) {
secureLogger.warn('⚠️ Wallet not found, redirecting to wallet-setup...', { component: 'BlockSync' });
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 1000);
return;
}
updateStatus('✅ Synchronisation terminée', 'success');
updateProgress(100);
updateSyncItem('blocksScanned', toScan.toString(), 'completed');
updateSyncItem('blocksToScan', '0', 'completed');
updateSyncItem('transactionsFound', '0', 'completed');
if (!wallet.sp_wallet?.birthday || wallet.sp_wallet.birthday === 0) {
secureLogger.warn('⚠️ Birthday not configured, redirecting to birthday-setup...', { component: 'BlockSync' });
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 1000);
return;
}
secureLogger.info('✅ All prerequisites verified for block sync', { component: 'BlockSync' });
updateStatus('✅ Prerequisites verified', 'success');
// Initialiser les services
secureLogger.info('🔄 Waiting for services to be ready...', { component: 'BlockSync' });
updateStatus('🔄 Initialisation des services...', 'loading');
let services: Services;
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
try {
secureLogger.info('🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...', { component: 'BlockSync' });
services = await Services.getInstance();
secureLogger.info('✅ Services initialized successfully', { component: 'BlockSync' });
break;
} catch (error) {
attempts++;
secureLogger.warn(`Services initialization failed (attempt ${attempts}/${maxAttempts})`, error as Error, { component: 'BlockSync' });
if (attempts >= maxAttempts) {
throw new Error('Failed to initialize services after maximum attempts');
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
if (!services!) {
throw new Error('Services not initialized');
}
// Vérifier si le wallet est déjà synchronisé
const currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight === -1) {
secureLogger.warn('⚠️ Block height not available, connecting to relays...', { component: 'BlockSync' });
updateStatus('⚠️ Connexion aux relays...', 'loading');
// Attendre que les services se connectent aux relays
await services.connectAllRelays();
// Attendre que la hauteur de bloc soit définie
await services.waitForBlockHeight();
}
const finalBlockHeight = services.getCurrentBlockHeight();
const birthday = wallet.sp_wallet.birthday;
const lastScan = wallet.sp_wallet.last_scan || 0;
const toScan = Math.max(0, finalBlockHeight - lastScan);
secureLogger.info('📊 Sync info: current=${finalBlockHeight}, birthday=${birthday}, lastScan=${lastScan}, toScan=${toScan}', { component: 'BlockSync' });
// Mettre à jour l'interface
updateSyncItem('currentBlock', finalBlockHeight.toString(), 'completed');
updateSyncItem('birthday', birthday.toString(), 'completed');
updateSyncItem('lastScan', lastScan.toString(), 'completed');
if (toScan === 0) {
secureLogger.info('✅ Wallet already synchronized', { component: 'BlockSync' });
updateStatus('✅ Wallet déjà synchronisé', 'success');
updateSyncItem('blocksToScan', '0', 'completed');
updateSyncItem('blocksScanned', '0', 'completed');
updateSyncItem('transactionsFound', '0', 'completed');
// Activer le bouton et rediriger automatiquement
if (continueBtn) {
continueBtn.disabled = false;
continueBtn.textContent = 'Aller au pairing';
}
// Auto-redirection après 3 secondes
setTimeout(() => {
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
}, 3000);
return;
}
// Afficher la barre de progression
updateProgress(0);
updateStatus('🔄 Synchronisation en cours...', 'loading');
updateSyncItem('blocksToScan', toScan.toString(), 'pending');
// Intercepter les messages de progression du scan
let scanProgressInterval: NodeJS.Timeout | null = null;
let lastProgressMessage = '';
// Fonction pour intercepter les messages de progression
const originalConsoleLog = console.log;
console.log = (...args: any[]) => {
const message = args.join(' ');
if (message.includes('Scan progress:')) {
// Extraire les informations de progression
const progressMatch = message.match(/Scan progress: (\d+)\/(\d+) \((\d+)%\)/);
if (progressMatch) {
const currentBlock = parseInt(progressMatch[1]);
const totalBlocks = parseInt(progressMatch[2]);
const percentage = parseInt(progressMatch[3]);
// Mettre à jour l'interface avec les détails de progression
updateStatus(`🔍 Synchronisation des blocs: ${currentBlock}/${totalBlocks} (${percentage}%)`, 'loading');
updateProgress(percentage);
// Mettre à jour les éléments de synchronisation
updateSyncItem('blocksScanned', currentBlock.toString(), 'pending');
updateSyncItem('blocksToScan', (totalBlocks - currentBlock).toString(), 'pending');
lastProgressMessage = `Bloc ${currentBlock}/${totalBlocks} (${percentage}%)`;
}
}
// Appeler la fonction console.log originale
originalConsoleLog.apply(console, args);
};
try {
// Effectuer la synchronisation
await services.updateDeviceBlockHeight();
secureLogger.info('✅ Block scan completed successfully', { component: 'BlockSync' });
// Restaurer la fonction console.log originale
console.log = originalConsoleLog;
updateStatus('✅ Synchronisation terminée', 'success');
updateProgress(100);
updateSyncItem('blocksScanned', toScan.toString(), 'completed');
updateSyncItem('blocksToScan', '0', 'completed');
updateSyncItem('transactionsFound', '0', 'completed');
// Activer le bouton et rediriger automatiquement
if (continueBtn) {
continueBtn.disabled = false;
continueBtn.textContent = 'Aller au pairing';
}
// Auto-redirection après 3 secondes
setTimeout(() => {
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
}, 3000);
} catch (error) {
// Restaurer la fonction console.log originale en cas d'erreur
console.log = originalConsoleLog;
secureLogger.error('Error during block scan', error as Error, { component: 'BlockSync' });
updateStatus(`❌ Erreur lors de la synchronisation: ${(error as Error).message}`, 'error');
updateSyncItem('blocksToScan', 'Erreur', 'error');
throw error;
}
// Activer le bouton et rediriger automatiquement
if (continueBtn) {
continueBtn.disabled = false;
continueBtn.textContent = 'Aller au pairing';
}
// Auto-redirection après 3 secondes
setTimeout(() => {
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
window.location.href = '/src/pages/pairing/pairing.html';
}, 3000);
} catch (error) {
secureLogger.error('Error in block sync page', error as Error, { component: 'BlockSync' });
updateStatus(`❌ Erreur: ${(error as Error).message}`, 'error');
// Rediriger vers la page appropriée selon l'erreur
const errorMessage = (error as Error).message;
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 2000);
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 2000);
} else if (errorMessage.includes('birthday')) {
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 2000);
}
// Restaurer la fonction console.log originale en cas d'erreur
console.log = originalConsoleLog;
secureLogger.error('Error during block scan', error as Error, { component: 'BlockSync' });
updateStatus(`❌ Erreur lors de la synchronisation: ${(error as Error).message}`, 'error');
updateSyncItem('blocksToScan', 'Erreur', 'error');
throw error;
}
});
} catch (error) {
secureLogger.error('Error in block sync page', error as Error, { component: 'BlockSync' });
updateStatus(`❌ Erreur: ${(error as Error).message}`, 'error');
// Rediriger vers la page appropriée selon l'erreur
const errorMessage = (error as Error).message;
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 2000);
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 2000);
} else if (errorMessage.includes('birthday')) {
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 2000);
}
}
});

View File

@ -4,7 +4,7 @@ import { SecurityModeService, SecurityMode } from '../../services/security-mode.
import { secureLogger } from '../../services/secure-logger';
import Services from '../../services/service';
import { addSubscription } from '../../utils/subscription.utils';
import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
import { displayEmojis, generateCreateBtn, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
import { getCorrectDOM } from '../../utils/html.utils';
import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing';
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
@ -646,7 +646,7 @@ async function handleMainPairing(): Promise<void> {
let currentMode: string | null = null;
try {
currentMode = await securityModeService.getCurrentMode();
} catch (error) {
} catch {
// Si aucun mode n'est configuré, rediriger vers security-setup (le mode devrait déjà être configuré avant d'arriver ici)
secureLogger.warn('⚠️ No security mode configured, redirecting to security-setup...', { component: 'HomePage' });
if (mainStatus) {
@ -763,8 +763,7 @@ async function handleMainPairing(): Promise<void> {
await secureCredentialsService.storeCredentials(credentialData, '');
secureLogger.info('✅ Credentials stored successfully', { component: 'HomePage' });
// Note: Faucet temporarily disabled due to relay configuration issue
secureLogger.info('🪙 Faucet temporarily disabled - relay needs Bitcoin Core wallet configuration', { component: 'HomePage' });
// Faucet flow remains enabled and required
// Decrypt and make keys available to SDK
secureLogger.info('🔓 Decrypting credentials for SDK access...', { component: 'HomePage' });

View File

@ -1,5 +1,5 @@
import { DeviceReaderService } from '../../services/device-reader.service';
import { secureLogger } from '../services/secure-logger';
import { secureLogger } from '../../services/secure-logger';
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
// Extend WindowEventMap to include custom events
@ -64,7 +64,7 @@ document.addEventListener('DOMContentLoaded', async () => {
try {
secureLogger.info('🔧 Getting device reader service...', { component: 'PairingPage' });
const deviceReader = DeviceReaderService.getInstance();
// const deviceReader = DeviceReaderService.getInstance(); // Not used yet
// Vérifier que le PBKDF2 key existe d'abord
const pbkdf2KeyResult = await checkPBKDF2Key();

View File

@ -86,7 +86,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Générer la clé PBKDF2 et la stocker selon le mode
// IMPORTANT: Cette opération doit être directe (pas de délai) pour que WebAuthn fonctionne
const pbkdf2Key = await secureCredentialsService.generatePBKDF2Key(selectedMode);
await secureCredentialsService.generatePBKDF2Key(selectedMode);
secureLogger.info('✅ PBKDF2 key generated and stored securely', { component: 'SecuritySetup' });
// Rediriger directement vers la page de génération du wallet

View File

@ -4,7 +4,7 @@
*/
import { DATABASE_CONFIG } from '../../services/database-config';
import { secureLogger } from '../services/secure-logger';
import { secureLogger } from '../../services/secure-logger';
import { checkPBKDF2Key } from '../../utils/prerequisites.utils';
document.addEventListener('DOMContentLoaded', async () => {
@ -23,30 +23,30 @@ document.addEventListener('DOMContentLoaded', async () => {
progressBar.style.width = `${percent}%`;
}
// Méthode pour sauvegarder directement en IndexedDB dans la base 4nk
async function saveCredentialsDirectly(credentials: any): Promise<void> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
// Méthode pour sauvegarder directement en IndexedDB dans la base 4nk (non utilisée pour l'instant)
// async function saveCredentialsDirectly(credentials: any): Promise<void> {
// return new Promise((resolve, reject) => {
// const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
const db = request.result;
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
// request.onerror = () => reject(request.error);
// request.onsuccess = () => {
// const db = request.result;
// const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
// const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
const putRequest = store.put(credentials, '/4nk/credentials');
putRequest.onsuccess = () => resolve();
putRequest.onerror = () => reject(putRequest.error);
};
// const putRequest = store.put(credentials, '/4nk/credentials');
// putRequest.onsuccess = () => resolve();
// putRequest.onerror = () => reject(putRequest.error);
// };
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
}
};
});
}
// request.onupgradeneeded = () => {
// const db = request.result;
// if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
// db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
// }
// };
// });
// }
try {
// Étape 1: Vérifier les prérequis AVANT d'initialiser Services
@ -110,10 +110,8 @@ document.addEventListener('DOMContentLoaded', async () => {
if ((performance as any).memory) {
const memory = (performance as any).memory;
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
const usedMB = memory.usedJSHeapSize / 1024 / 1024;
const limitMB = memory.jsHeapSizeLimit / 1024 / 1024;
secureLogger.info('📊 Current memory usage: ${usedPercent.toFixed(1)}% (${usedMB.toFixed(1)}MB / ${limitMB.toFixed(1)}MB)', { component: 'WalletSetup' });
secureLogger.info(`📊 Current memory usage: ${usedPercent.toFixed(1)}%`, { component: 'WalletSetup' });
// Si la mémoire est très élevée (>75%), tenter un nettoyage agressif
if (usedPercent > 75) {
@ -194,10 +192,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Diagnostic plus détaillé
if (attempts === 5) {
secureLogger.debug('🔍 Diagnostic: Checking memory usage...', { component: 'WalletSetup' });
if ((performance as any).memory) {
const memory = (performance as any).memory;
secureLogger.info('📊 Memory usage: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB / ${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB', { component: 'WalletSetup' });
}
// Memory check removed - not needed here
}
attempts++;
@ -279,7 +274,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Créer le store wallet seulement s'il n'existe pas
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
const store = db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
secureLogger.info('✅ Wallet store created with keyPath: ${DATABASE_CONFIG.stores.wallet.keyPath}', { component: 'WalletSetup' });
} else {
secureLogger.info('✅ Wallet store already exists', { component: 'WalletSetup' });
@ -312,7 +307,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Récupérer le device créé par le SDK
const device = services.dumpDeviceFromMemory();
secureLogger.debug('Device structure from SDK', {
secureLogger.debug('Device structure from SDK', {
component: 'WalletSetup',
data: {
hasSpWallet: !!device.sp_wallet,
@ -370,7 +365,7 @@ document.addEventListener('DOMContentLoaded', async () => {
};
transaction.onerror = () => {
secureLogger.error('Transaction failed', transaction.error as Error, { component: 'WalletSetup' });
secureLogger.error('Transaction error details', new Error('Transaction failed'), {
secureLogger.error('Transaction error details', new Error('Transaction failed'), {
component: 'WalletSetup',
data: {
error: transaction.error,
@ -389,7 +384,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const verificationRequest = store.get('1');
verificationRequest.onsuccess = () => {
secureLogger.debug('Verification result structure', {
secureLogger.debug('Verification result structure', {
component: 'WalletSetup',
data: verificationRequest.result ? {
hasPreId: !!verificationRequest.result.pre_id,
@ -451,7 +446,8 @@ document.addEventListener('DOMContentLoaded', async () => {
}
secureLogger.info('✅ Wallet saved exclusively in IndexedDB and verified', { component: 'WalletSetup' });
console.log('🔍 Wallet contains only encrypted data:', {
secureLogger.debug('🔍 Wallet contains only encrypted data:', {
component: 'WalletSetup',
hasEncryptedDevice: !!finalVerification.encrypted_device,
hasEncryptedWallet: !!finalVerification.encrypted_wallet,
hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE
@ -472,7 +468,8 @@ document.addEventListener('DOMContentLoaded', async () => {
pbkdf2KeyTest
);
const parsedWallet = JSON.parse(decryptedWallet);
console.log('✅ TEST: Wallet decrypted successfully:', {
secureLogger.debug('✅ TEST: Wallet decrypted successfully:', {
component: 'WalletSetup',
hasScanSk: !!parsedWallet.scan_sk,
hasSpendKey: !!parsedWallet.spend_key,
network: parsedWallet.network,
@ -486,7 +483,8 @@ document.addEventListener('DOMContentLoaded', async () => {
pbkdf2KeyTest
);
const parsedDevice = JSON.parse(decryptedDevice);
console.log('✅ TEST: Device decrypted successfully:', {
secureLogger.debug('✅ TEST: Device decrypted successfully:', {
component: 'WalletSetup',
hasSpWallet: !!parsedDevice.sp_wallet,
network: parsedDevice.network
});
@ -494,8 +492,7 @@ document.addEventListener('DOMContentLoaded', async () => {
secureLogger.info('✅ TEST: Full decryption test passed - wallet and device decrypt correctly', { component: 'WalletSetup' });
}
} catch (decryptError) {
console.error('❌ TEST: Decryption test failed:', decryptError);
secureLogger.error('❌ This indicates an issue with encryption/decryption logic', { component: 'WalletSetup' });
secureLogger.error('❌ TEST: Decryption test failed:', decryptError as Error, { component: 'WalletSetup' });
}
} else {
secureLogger.error('❌ Final wallet verification failed - wallet not found in IndexedDB', { component: 'WalletSetup' });
@ -503,7 +500,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
} catch (error) {
console.error('❌ Error during wallet save:', error);
secureLogger.error('❌ Error during wallet save:', error as Error, { component: 'WalletSetup' });
updateStatus('❌ Erreur: Échec de la sauvegarde du wallet', 'error');
throw error;
}
@ -520,13 +517,13 @@ document.addEventListener('DOMContentLoaded', async () => {
secureLogger.info('✅ Continue button enabled', { component: 'WalletSetup' });
// Redirection automatique après 3 secondes si l'utilisateur ne clique pas
setTimeout(() => {
setTimeout(() => {
secureLogger.info('🔄 Auto-redirecting to birthday setup after timeout...', { component: 'WalletSetup' });
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 3000);
} catch (error) {
console.error('❌ Error during wallet setup:', error);
secureLogger.error('❌ Error during wallet setup:', error as Error, { component: 'WalletSetup' });
updateStatus('❌ Erreur lors de la génération du wallet', 'error');
}

View File

@ -5,17 +5,19 @@
import { Device } from '../../pkg/sdk_client';
import { secureLogger } from '../services/secure-logger';
/* eslint-disable no-unused-vars */
export interface DeviceRepository {
getDevice(): Promise<Device | null>;
saveDevice(device: Device): Promise<void>;
saveDevice(_device: Device): Promise<void>;
deleteDevice(): Promise<void>;
hasDevice(): Promise<boolean>;
getDeviceAddress(): Promise<string | null>;
updateDevice(device: Partial<Device>): Promise<void>;
updateDevice(_device: Partial<Device>): Promise<void>;
}
/* eslint-enable no-unused-vars */
export class DeviceRepositoryImpl implements DeviceRepository {
constructor(private database: any) {}
constructor(private database: any) {} // database used via methods
async getDevice(): Promise<Device | null> {
try {

View File

@ -5,19 +5,21 @@
import { Process } from '../../pkg/sdk_client';
import { secureLogger } from '../services/secure-logger';
/* eslint-disable no-unused-vars */
export interface ProcessRepository {
getProcess(processId: string): Promise<Process | null>;
saveProcess(process: Process): Promise<void>;
deleteProcess(processId: string): Promise<void>;
getProcess(_processId: string): Promise<Process | null>;
saveProcess(_process: Process): Promise<void>;
deleteProcess(_processId: string): Promise<void>;
getProcesses(): Promise<Process[]>;
getMyProcesses(): Promise<string[]>;
addMyProcess(processId: string): Promise<void>;
removeMyProcess(processId: string): Promise<void>;
hasProcess(processId: string): Promise<boolean>;
addMyProcess(_processId: string): Promise<void>;
removeMyProcess(_processId: string): Promise<void>;
hasProcess(_processId: string): Promise<boolean>;
}
/* eslint-enable no-unused-vars */
export class ProcessRepositoryImpl implements ProcessRepository {
constructor(private database: any) {}
constructor(private database: any) {} // database used via methods
async getProcess(processId: string): Promise<Process | null> {
try {

View File

@ -124,7 +124,7 @@ async function handleLocation(path: string) {
if (path === 'home') {
secureLogger.info('🏠 Processing home route...', { component: 'Router' });
// Use LoginComponent
const loginComponent = LoginComponent;
// const loginComponent = LoginComponent; // Used as type, not variable
const container = document.querySelector('#containerId');
secureLogger.info('🏠 Container for home:', { component: 'Router', data: !!container });
const accountComponent = document.createElement('login-4nk-component');
@ -253,18 +253,9 @@ export async function registerAllListeners() {
);
};
const successResponse = (data: any, origin: string, messageId?: string) => {
// Use successResponse function
secureLogger.info('Success response:', { component: 'Router', data: data });
window.parent.postMessage(
{
type: MessageType.SUCCESS,
data: data,
messageId,
},
origin
);
};
// const successResponse = (data: any, origin: string, messageId?: string) => {
// // Not used anymore, response sent directly in handlers
// };
// --- Handler functions ---
const handleRequestLink = async (event: MessageEvent) => {
@ -658,7 +649,7 @@ export async function registerAllListeners() {
}
const processId = createProcessReturn.updated_process.process_id;
const process = createProcessReturn.updated_process.current_process;
const stateId = process.states[0].state_id;
// const stateId = process.states[0].state_id; // Not used
await services.handleApiReturn(createProcessReturn);
const res = {
@ -971,7 +962,7 @@ export async function registerAllListeners() {
let parsedMerkleProof: MerkleProofResult;
try {
parsedMerkleProof = JSON.parse(merkleProof);
} catch (e) {
} catch {
throw new Error('Provided merkleProof is not a valid json object');
}
@ -1185,6 +1176,7 @@ document.addEventListener('navigate', (e: Event) => {
* ÉTAPE 2: Gestion de la sécurité (clés de sécurité)
* Cette étape doit être la première et rien d'autre ne doit s'exécuter en parallèle
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function handleSecurityKeyManagement(): Promise<boolean> {
secureLogger.info('🔐 Starting security key management...', { component: 'Router' });
@ -1211,7 +1203,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
if (!hasCredentials) {
secureLogger.info('🔐 No security credentials found, redirecting to wallet setup...', { component: 'Router' });
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
window.location.href = '/wallet-setup.html';
return false;
} else {
secureLogger.info('🔐 Security credentials found, verifying access...', { component: 'Router' });
@ -1219,7 +1211,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
const credentials = await secureCredentialsService.retrieveCredentials('');
if (!credentials) {
secureLogger.error('❌ Failed to access security credentials', { component: 'Router' });
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
window.location.href = '/wallet-setup.html';
return false;
}
secureLogger.info('✅ Security credentials verified', { component: 'Router' });
@ -1228,7 +1220,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
} catch (error) {
secureLogger.error('❌ Security key management failed:', error, { component: 'Router' });
secureLogger.info('🔐 Redirecting to security setup...', { component: 'Router' });
window.location.href = '/src/pages/security-setup/security-setup.html';
window.location.href = '/security-setup.html';
return false;
}
}
@ -1236,6 +1228,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
/**
* ÉTAPE 5: Handshake
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function performHandshake(_services: any): Promise<void> {
secureLogger.info('🤝 Performing handshake...', { component: 'Router' });
@ -1252,6 +1245,7 @@ async function performHandshake(_services: any): Promise<void> {
/**
* ÉTAPE 6: Pairing
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function handlePairing(services: any): Promise<void> {
secureLogger.info('🔗 Handling device pairing...', { component: 'Router' });
@ -1276,6 +1270,7 @@ async function handlePairing(services: any): Promise<void> {
/**
* ÉTAPE 7: Écoute des processus
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function startProcessListening(services: any): Promise<void> {
secureLogger.info('👂 Starting process listening...', { component: 'Router' });

View File

@ -1,3 +1,4 @@
/* eslint-env browser, serviceworker */
const addResourcesToCache = async resources => {
const cache = await caches.open('v1');
await cache.addAll(resources);

View File

@ -1,3 +1,4 @@
/* eslint-env browser, serviceworker */
const EMPTY32BYTES = String('').padStart(64, '0');
self.addEventListener('install', event => {
@ -109,10 +110,10 @@ const DATABASE_CONFIG = {
async function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onerror = event => {
request.onerror = () => {
reject(request.error);
};
request.onsuccess = event => {
request.onsuccess = () => {
resolve(request.result);
};
request.onupgradeneeded = event => {
@ -180,6 +181,7 @@ async function getProcesses(processIds) {
return results.filter(result => result !== undefined);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function getAllDiffsNeedValidation() {
const db = await openDatabase();

View File

@ -58,7 +58,7 @@ export class AsyncEncoderService {
component: 'AsyncEncoderService',
operation: 'initializeWorker'
});
} catch (error) {
} catch {
secureLogger.warn('Failed to initialize encoder worker, falling back to sync encoding', {
component: 'AsyncEncoderService',
operation: 'initializeWorker'

View File

@ -3,7 +3,7 @@
*/
import { secureLogger } from '../secure-logger';
import { SecurityMode } from '../security-mode.service';
import { WebAuthnCredential, EncryptionResult } from './types';
import { WebAuthnCredential } from './types';
import { DATABASE_CONFIG } from '../database-config';
export class WebAuthnService {
@ -74,7 +74,7 @@ export class WebAuthnService {
}
// Vérifier si on est dans un contexte sécurisé (requis pour WebAuthn)
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
return false;
}
@ -93,33 +93,33 @@ export class WebAuthnService {
*/
async detectProtonPass(): Promise<boolean> {
try {
console.log('🔍 Detecting Proton Pass availability...');
secureLogger.debug('🔍 Detecting Proton Pass availability...', { component: 'WebAuthn' });
const available = await this.detectAvailableAuthenticators();
if (!available) {
console.log('❌ WebAuthn not available');
secureLogger.debug('❌ WebAuthn not available', { component: 'WebAuthn' });
return false;
}
console.log('✅ WebAuthn is available, checking Proton Pass support...');
secureLogger.debug('✅ WebAuthn is available, checking Proton Pass support...', { component: 'WebAuthn' });
// Vérifier la disponibilité sans faire d'appel réel à WebAuthn
// Juste vérifier que les APIs sont disponibles
if (!navigator.credentials?.create) {
console.log('❌ WebAuthn credentials API not available');
secureLogger.debug('❌ WebAuthn credentials API not available', { component: 'WebAuthn' });
return false;
}
// Vérifier si on est dans un contexte sécurisé (requis pour WebAuthn)
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
console.log('❌ WebAuthn requires HTTPS or localhost');
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
secureLogger.debug('❌ WebAuthn requires HTTPS or localhost', { component: 'WebAuthn' });
return false;
}
console.log('✅ Proton Pass should be available (basic checks passed)');
secureLogger.debug('✅ Proton Pass should be available (basic checks passed)', { component: 'WebAuthn' });
return true;
} catch (error) {
console.log('❌ Error detecting Proton Pass:', error);
secureLogger.error('❌ Error detecting Proton Pass:', error as Error, { component: 'WebAuthn' });
return false;
}
}

View File

@ -4,10 +4,11 @@
* Ce fichier centralise toutes les définitions de la base de données pour éviter
* les erreurs de version et d'incohérence entre les différents fichiers.
*/
import { secureLogger } from './secure-logger';
export const DATABASE_CONFIG = {
name: '4nk',
version: 4,
version: 5,
stores: {
wallet: {
name: 'wallet',
@ -75,18 +76,18 @@ export async function openDatabase(): Promise<IDBDatabase> {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onerror = () => {
console.error(`Failed to open database ${DATABASE_CONFIG.name}:`, request.error);
secureLogger.error(`Failed to open database ${DATABASE_CONFIG.name}`, request.error as Error, { component: 'DatabaseConfig' });
reject(request.error);
};
request.onsuccess = () => {
console.log(`Database ${DATABASE_CONFIG.name} opened successfully`);
secureLogger.info(`Database ${DATABASE_CONFIG.name} opened successfully`, { component: 'DatabaseConfig' });
resolve(request.result);
};
request.onupgradeneeded = () => {
const db = request.result;
console.log(`🔄 Database upgrade needed for ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}`);
secureLogger.info(`Database upgrade needed for ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}`, { component: 'DatabaseConfig' });
// Créer les stores manquants
Object.values(DATABASE_CONFIG.stores).forEach(storeConfig => {
@ -106,9 +107,9 @@ export async function openDatabase(): Promise<IDBDatabase> {
store.createIndex(indexConfig.name, indexConfig.keyPath, { unique: indexConfig.unique || false });
});
console.log(`Created store: ${storeConfig.name}`);
secureLogger.info(`Created store: ${storeConfig.name}`, { component: 'DatabaseConfig' });
} else {
console.log(`Store already exists: ${storeConfig.name}`);
secureLogger.info(`Store already exists: ${storeConfig.name}`, { component: 'DatabaseConfig' });
}
});
};

View File

@ -1,5 +1,6 @@
import Services from './service';
import { DATABASE_CONFIG } from './database-config';
import { secureLogger } from './secure-logger';
export class Database {
private static instance: Database;
@ -94,7 +95,7 @@ export class Database {
};
request.onerror = () => {
console.error('Database error:', request.error);
secureLogger.error('Database error:', request.error as Error, { component: 'Database' });
reject(request.error);
};
});
@ -117,7 +118,7 @@ export class Database {
public async registerServiceWorker(path: string) {
if (!('serviceWorker' in navigator)) {return;} // Ensure service workers are supported
console.log('registering worker at', path);
secureLogger.debug('Registering worker at', { component: 'Database', path });
try {
// Get existing service worker registrations
@ -127,7 +128,7 @@ export class Database {
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, {
type: 'module',
});
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
secureLogger.info('Service Worker registered with scope:', { component: 'Database', scope: this.serviceWorkerRegistration.scope });
// Show spinner during service worker initialization
this.showServiceWorkerSpinner('Initializing database service...');
@ -135,16 +136,16 @@ export class Database {
// One existing worker: update it (restart it) without unregistering.
this.serviceWorkerRegistration = registrations[0];
await this.serviceWorkerRegistration.update();
console.log('Service Worker updated');
secureLogger.info('Service Worker updated', { component: 'Database' });
} else {
// More than one existing worker: unregister them all and register a new one.
console.log('Multiple Service Worker(s) detected. Unregistering all...');
secureLogger.warn('Multiple Service Worker(s) detected. Unregistering all...', { component: 'Database' });
await Promise.all(registrations.map(reg => reg.unregister()));
console.log('All previous Service Workers unregistered.');
secureLogger.info('All previous Service Workers unregistered', { component: 'Database' });
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, {
type: 'module',
});
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
secureLogger.info('Service Worker registered with scope:', { component: 'Database', scope: this.serviceWorkerRegistration.scope });
// Show spinner during service worker initialization
this.showServiceWorkerSpinner('Initializing database service...');
@ -156,9 +157,9 @@ export class Database {
this.checkForUpdates(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Update timeout')), 5000)),
]);
console.log('✅ Service worker updates completed');
secureLogger.info('✅ Service worker updates completed', { component: 'Database' });
} catch (error) {
console.warn('⚠️ Service worker update failed or timed out:', error);
secureLogger.warn('⚠️ Service worker update failed or timed out:', { component: 'Database', error: String(error) });
// Continue anyway - don't block the initialization
}
@ -169,11 +170,11 @@ export class Database {
// Hide spinner once service worker is ready
this.hideServiceWorkerSpinner();
console.log('✅ Service worker initialization completed');
secureLogger.info('✅ Service worker initialization completed', { component: 'Database' });
// Set up a global message listener for responses from the service worker.
navigator.serviceWorker.addEventListener('message', async event => {
console.log('Received message from service worker:', event.data);
secureLogger.debug('Received message from service worker:', { component: 'Database', data: event.data });
await this.handleServiceWorkerMessage(event.data);
});
@ -193,13 +194,13 @@ export class Database {
}
}
} catch (error) {
console.warn('Service worker scan failed:', error);
secureLogger.warn('Service worker scan failed:', { component: 'Database', error: String(error) });
// Continue the interval even if one scan fails
}
}, 5000);
}, 10000); // Wait 10 seconds before starting the interval
} catch (error) {
console.error('Service Worker registration failed:', error);
secureLogger.error('Service Worker registration failed:', error as Error, { component: 'Database' });
this.hideServiceWorkerSpinner();
throw error; // Re-throw to be handled by the caller
}
@ -211,7 +212,7 @@ export class Database {
): Promise<ServiceWorker | null> {
return new Promise((resolve, _reject) => {
// Use reject parameter
console.log('Service worker activation promise created');
secureLogger.debug('Service worker activation promise created', { component: 'Database' });
if (registration.active) {
resolve(registration.active);
return;
@ -220,7 +221,7 @@ export class Database {
// Set a timeout to prevent infinite waiting
const timeout = setTimeout(() => {
navigator.serviceWorker.removeEventListener('controllerchange', listener);
console.warn('Service worker activation timeout');
secureLogger.warn('Service worker activation timeout', { component: 'Database' });
resolve(null); // Return null instead of rejecting to allow continuation
}, 15000); // 15 second timeout
@ -250,7 +251,7 @@ export class Database {
this.serviceWorkerRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
}
} catch (error) {
console.error('Error checking for service worker updates:', error);
secureLogger.error('Error checking for service worker updates:', error as Error, { component: 'Database' });
throw error; // Re-throw to be caught by the calling function
}
}
@ -262,7 +263,7 @@ export class Database {
await this.handleDownloadList(message.data);
break;
default:
console.warn('Unknown message type received from service worker:', message);
secureLogger.warn('Unknown message type received from service worker:', { component: 'Database', message });
}
}
@ -274,7 +275,7 @@ export class Database {
const diff = await service.getDiffByValue(hash);
if (!diff) {
// This should never happen
console.warn(`Missing a diff for hash ${hash}`);
secureLogger.warn(`Missing a diff for hash ${hash}`, { component: 'Database' });
continue;
}
const processId = diff.process_id;
@ -297,7 +298,7 @@ export class Database {
);
} else {
// We first request the data from managers
console.log('Request data from managers of the process');
secureLogger.debug('Request data from managers of the process', { component: 'Database' });
// get the diff from db
if (!requestedStateId.includes(stateId)) {
await service.requestDataFromPeers(processId, [stateId], [roles]);
@ -305,21 +306,20 @@ export class Database {
}
}
} catch (e) {
console.error(e);
secureLogger.error('Error downloading missing data:', e as Error, { component: 'Database' });
}
}
}
private handleAddObjectResponse = async (event: MessageEvent) => {
// Use event parameter
console.log('Add object response:', event);
secureLogger.debug('Add object response received', { component: 'Database' });
const data = event.data;
console.log('Received response from service worker (ADD_OBJECT):', data);
secureLogger.debug('Received response from service worker (ADD_OBJECT):', { component: 'Database', data });
const service = await Services.getInstance();
if (data.type === 'NOTIFICATIONS') {
service.setNotifications(data.data);
} else if (data.type === 'TO_DOWNLOAD') {
console.log(`Received missing data ${data}`);
secureLogger.debug(`Received missing data`, { component: 'Database', data });
// Download the missing data
const requestedStateId: string[] = [];
for (const hash of data.data) {
@ -331,7 +331,7 @@ export class Database {
await service.saveBlobToDb(hash, blob);
} else {
// We first request the data from managers
console.log('Request data from managers of the process');
secureLogger.debug('Request data from managers of the process', { component: 'Database' });
// get the diff from db
const diff = await service.getDiffByValue(hash);
if (diff === null) {
@ -346,16 +346,14 @@ export class Database {
}
}
} catch (e) {
console.error(e);
secureLogger.error('Error downloading missing data:', e as Error, { component: 'Database' });
}
}
}
};
private handleGetObjectResponse = (event: MessageEvent) => {
console.log('Received response from service worker (GET_OBJECT):', event.data);
// Use event parameter
console.log('Get object response event:', event);
secureLogger.debug('Received response from service worker (GET_OBJECT):', { component: 'Database', data: event.data });
};
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
@ -368,10 +366,10 @@ export class Database {
await this.addObjectAttempt(payload);
return; // Success, exit retry loop
} catch (error) {
console.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, error);
secureLogger.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, { component: 'Database', error: String(error) });
if (attempt === maxRetries) {
console.error('All retry attempts failed for addObject');
secureLogger.error('All retry attempts failed for addObject', { component: 'Database' });
throw new Error(`Failed to add object after ${maxRetries} attempts: ${error}`);
}
@ -381,23 +379,23 @@ export class Database {
}
}
private addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise<void> {
return new Promise(async (resolve, reject) => {
private async addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise<void> {
// Check if the service worker is active
if (!this.serviceWorkerRegistration) {
secureLogger.debug('Service worker registration not ready, waiting...', { component: 'Database' });
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
}
const activeWorker = await this.waitForServiceWorkerActivation(
this.serviceWorkerRegistration
);
if (!activeWorker) {
throw new Error('Service worker not available');
}
return new Promise((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();
@ -447,10 +445,10 @@ export class Database {
await this.batchWritingAttempt(payload);
return; // Success, exit retry loop
} catch (error) {
console.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, error);
secureLogger.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, { component: 'Database', error: String(error) });
if (attempt === maxRetries) {
console.error('All retry attempts failed for batchWriting');
secureLogger.error('All retry attempts failed for batchWriting', { component: 'Database' });
throw new Error(`Failed to batch write objects after ${maxRetries} attempts: ${error}`);
}
@ -464,21 +462,28 @@ export class Database {
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;
}
return this.batchWritingInternal(payload);
}
const activeWorker = await this.waitForServiceWorkerActivation(
this.serviceWorkerRegistration
);
private async batchWritingInternal(payload: {
storeName: string;
objects: { key: any; object: any }[];
}): Promise<void> {
try {
if (!this.serviceWorkerRegistration) {
secureLogger.debug('Service worker registration not ready, waiting...', { component: 'Database' });
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
}
if (!activeWorker) {
throw new Error('Service worker not available');
}
const activeWorker = await this.waitForServiceWorkerActivation(
this.serviceWorkerRegistration
);
if (!activeWorker) {
throw new Error('Service worker not available');
}
return new Promise((resolve, reject) => {
const messageChannel = new MessageChannel();
// Set timeout for the operation
@ -503,14 +508,14 @@ export class Database {
},
[messageChannel.port2]
);
} catch (error) {
reject(new Error(`Failed to send message to service worker: ${error}`));
}
});
});
} catch (error) {
throw new Error(`Failed to send message to service worker: ${error}`);
}
}
public async getObject(storeName: string, key: string): Promise<any | null> {
console.log(`🔍 DEBUG: Database.getObject - storeName: ${storeName}, key: ${key}`);
secureLogger.debug(`Database.getObject - storeName: ${storeName}, key: ${key}`, { component: 'Database' });
// Utiliser directement IndexedDB au lieu du service worker pour éviter les problèmes de synchronisation
const db = await new Promise<IDBDatabase>((resolve, reject) => {
@ -519,22 +524,22 @@ export class Database {
request.onerror = () => reject(request.error);
});
console.log(`🔍 DEBUG: Database.getObject - db obtained directly, objectStoreNames:`, Array.from(db.objectStoreNames));
secureLogger.debug(`Database.getObject - db obtained directly`, { component: 'Database', objectStoreNames: Array.from(db.objectStoreNames) });
const tx = db.transaction(storeName, 'readonly');
const store = tx.objectStore(storeName);
console.log(`🔍 DEBUG: Database.getObject - store opened: ${store.name}`);
secureLogger.debug(`Database.getObject - store opened: ${store.name}`, { component: 'Database' });
const result = await new Promise((resolve, reject) => {
const getRequest = store.get(key);
getRequest.onsuccess = () => {
console.log(`🔍 DEBUG: Database.getObject - getRequest success, result:`, getRequest.result);
secureLogger.debug(`Database.getObject - getRequest success`, { component: 'Database', hasResult: !!getRequest.result });
resolve(getRequest.result);
};
getRequest.onerror = () => {
console.log(`🔍 DEBUG: Database.getObject - getRequest error:`, getRequest.error);
secureLogger.error(`Database.getObject - getRequest error`, getRequest.error as Error, { component: 'Database' });
reject(getRequest.error);
};
});
console.log(`🔍 DEBUG: Database.getObject - final result:`, result);
secureLogger.debug(`Database.getObject - final result`, { component: 'Database', hasResult: !!result });
return result ?? null; // Convert undefined to null
}
@ -564,7 +569,7 @@ export class Database {
};
});
} catch (error) {
console.error('Error fetching data from IndexedDB:', error);
secureLogger.error('Error fetching data from IndexedDB:', error as Error, { component: 'Database' });
throw error;
}
}

View File

@ -6,6 +6,7 @@
import { DATABASE_CONFIG } from './database-config';
import { SecureCredentialsService } from './secure-credentials.service';
import { EncryptionService } from './encryption.service';
import { secureLogger } from './secure-logger';
// Type simplifié pour éviter les problèmes d'import du SDK
export interface Device {
@ -38,7 +39,7 @@ export class DeviceReaderService {
* Version légère sans nécessiter WebAssembly
*/
async getDeviceFromDatabase(): Promise<Device | null> {
console.log('🔍 DeviceReaderService: Reading device from database...');
secureLogger.debug('🔍 DeviceReaderService: Reading device from database...', { component: 'DeviceReader' });
// Utiliser directement IndexedDB
const db = await new Promise<IDBDatabase>((resolve, reject) => {
@ -59,14 +60,14 @@ export class DeviceReaderService {
});
if (!dbRes) {
console.log('🔍 DeviceReaderService: No device found in database');
secureLogger.debug('🔍 DeviceReaderService: No device found in database', { component: 'DeviceReader' });
return null;
}
// Check if data is encrypted (new format) or plain (old format)
if (dbRes['encrypted_device']) {
// New encrypted format - need to decrypt
console.log('🔐 DeviceReaderService: Device found in encrypted format, decrypting...');
secureLogger.debug('🔐 DeviceReaderService: Device found in encrypted format, decrypting...', { component: 'DeviceReader' });
// Get the PBKDF2 key based on security mode
const secureCredentialsService = SecureCredentialsService.getInstance();

View File

@ -2,6 +2,7 @@
* EventBus - Système de communication découplé
* Permet la communication entre services sans couplage direct
*/
import { secureLogger } from './secure-logger';
export interface EventData {
[key: string]: any;
}
@ -46,7 +47,7 @@ export class EventBus {
// Vérifier la limite d'écouteurs
if (eventListeners.length >= this.maxListeners) {
console.warn(`Maximum listeners (${this.maxListeners}) reached for event: ${event}`);
secureLogger.warn(`Maximum listeners (${this.maxListeners}) reached for event: ${event}`, { component: 'EventBus' });
return;
}
@ -106,7 +107,7 @@ export class EventBus {
try {
listener(data);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
secureLogger.error(`Error in event listener for ${event}:`, error as Error, { component: 'EventBus' });
}
});
}
@ -126,7 +127,7 @@ export class EventBus {
const result = listener(data);
return Promise.resolve(result);
} catch (error) {
console.error(`Error in async event listener for ${event}:`, error);
secureLogger.error(`Error in async event listener for ${event}:`, error as Error, { component: 'EventBus' });
return Promise.resolve();
}
});

View File

@ -4,6 +4,7 @@ import {
discoverAndJoinPairingProcessWithWords,
prepareAndSendPairingTx,
} from '../utils/sp-address.utils';
import { secureLogger } from './secure-logger';
export default class IframePairingService {
private static instance: IframePairingService;
@ -29,7 +30,7 @@ export default class IframePairingService {
// Detect if we're in an iframe
if (window.parent !== window) {
this.parentWindow = window.parent;
console.log('🔗 Iframe pairing service initialized');
secureLogger.info('🔗 Iframe pairing service initialized', { component: 'IframePairing' });
}
}
@ -49,14 +50,14 @@ export default class IframePairingService {
break;
}
} catch (error) {
console.error('Error handling iframe pairing message:', error);
secureLogger.error('Error handling iframe pairing message:', error as Error, { component: 'IframePairing' });
this.sendMessage(MessageType.PAIRING_4WORDS_ERROR, { error: (error as Error).message });
}
}
private async handleCreatePairing(_data: any) {
try {
console.log('🔐 Creating pairing process via iframe...');
secureLogger.info('🔐 Creating pairing process via iframe...', { component: 'IframePairing' });
this._isCreator = true;
// Update status

View File

@ -2,6 +2,7 @@
* MemoryManager - Gestion intelligente de la mémoire
* Surveille et optimise l'utilisation mémoire de l'application
*/
import { secureLogger } from './secure-logger';
export interface MemoryStats {
usedJSHeapSize: number;
totalJSHeapSize: number;
@ -188,7 +189,7 @@ export class MemoryManager {
* Effectue un nettoyage de mémoire
*/
private performMemoryCleanup(): void {
console.log('🧹 Performing memory cleanup...');
secureLogger.info('🧹 Performing memory cleanup...', { component: 'MemoryManager' });
// Nettoyer les caches expirés
this.cleanupExpiredEntries();
@ -277,7 +278,8 @@ export class MemoryManager {
private logMemoryStats(): void {
const stats = this.getMemoryStats();
if (stats) {
console.log('📊 Memory Stats:', {
secureLogger.debug('📊 Memory Stats', {
component: 'MemoryManager',
used: `${Math.round(stats.usedJSHeapSize / 1024 / 1024)}MB`,
total: `${Math.round(stats.totalJSHeapSize / 1024 / 1024)}MB`,
limit: `${Math.round(stats.jsHeapSizeLimit / 1024 / 1024)}MB`,
@ -298,8 +300,7 @@ export class MemoryManager {
const caches: Record<string, { size: number; entries: any[] }> = {};
this.caches.forEach((cache, name) => {
// Use cache variable
console.log('Cache:', cache);
secureLogger.debug(`Cache stats for ${name}`, { component: 'MemoryManager', cacheSize: cache.size });
caches[name] = this.getCacheStats(name);
});

View File

@ -7,6 +7,7 @@ import { addressToEmoji } from '../utils/sp-address.utils';
import { RoleDefinition } from 'pkg/sdk_client';
import { initValidationModal } from '../components/validation-modal/validation-modal';
import { interpolate } from '../utils/html.utils';
import { secureLogger } from './secure-logger';
interface ConfirmationModalOptions {
title: string;
@ -97,7 +98,7 @@ export default class ModalService {
throw new Error('Must have exactly 1 member');
}
console.log('MEMBERS:', members);
secureLogger.debug('Modal members list', { component: 'ModalService', membersCount: members.length });
// We take all the addresses except our own
const service = await Services.getInstance();
const localAddress = service.getDeviceAddress();
@ -126,7 +127,7 @@ export default class ModalService {
};
}
confirmLogin() {
console.log('=============> Confirm Login');
secureLogger.info('Confirm Login action triggered', { component: 'ModalService' });
}
async closeLoginModal() {
if (this.modal) {this.modal.style.display = 'none';}

View File

@ -27,8 +27,7 @@ export class PairingService {
private processRepo: ProcessRepository,
private sdkClient: any
) {
// Use parameters
console.log('Pairing service constructor:', { deviceRepo: this.deviceRepo, processRepo: this.processRepo, sdkClient: this.sdkClient });
secureLogger.debug('Pairing service constructor initialized', { component: 'PairingService' });
}
/**
@ -82,8 +81,6 @@ export class PairingService {
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
// Use errorMessage variable
console.log('Pairing error:', errorMessage);
secureLogger.error('Failed to create pairing process', error as Error, {
component: 'PairingService',
@ -131,8 +128,6 @@ export class PairingService {
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
// Use errorMessage variable
console.log('Pairing error:', errorMessage);
secureLogger.error('Failed to join pairing process', error as Error, {
component: 'PairingService',

View File

@ -2,6 +2,7 @@
* PerformanceMonitor - Surveillance des performances
* Mesure et optimise les performances de l'application
*/
import { secureLogger } from './secure-logger';
export interface PerformanceMetric {
name: string;
value: number;
@ -54,7 +55,7 @@ export class PerformanceMonitor {
if (this.isMonitoring) {return;}
this.isMonitoring = true;
console.log('📊 Performance monitoring started');
secureLogger.info('📊 Performance monitoring started', { component: 'PerformanceMonitor' });
}
/**
@ -64,15 +65,13 @@ export class PerformanceMonitor {
this.isMonitoring = false;
this.observers.forEach(observer => observer.disconnect());
this.observers = [];
console.log('📊 Performance monitoring stopped');
secureLogger.info('📊 Performance monitoring stopped', { component: 'PerformanceMonitor' });
}
/**
* Enregistre une métrique de performance
*/
recordMetric(name: string, value: number, unit: string = 'ms'): void {
// Use unit parameter
console.log('Performance metric unit:', unit);
recordMetric(name: string, value: number, _unit: string = 'ms'): void {
if (!this.isMonitoring) {return;}
if (!this.metrics.has(name)) {
@ -151,8 +150,6 @@ export class PerformanceMonitor {
const result: Record<string, PerformanceStats> = {};
this.metrics.forEach((values, name) => {
// Use values parameter
console.log('Performance metric values:', values);
const stats = this.getMetricStats(name);
if (stats) {
result[name] = stats;
@ -220,9 +217,9 @@ export class PerformanceMonitor {
if (!threshold) {return;}
if (value > threshold.critical) {
console.warn(`🚨 Critical performance threshold exceeded for ${name}: ${value}ms`);
secureLogger.warn(`🚨 Critical performance threshold exceeded for ${name}: ${value}ms`, { component: 'PerformanceMonitor', metric: name, value });
} else if (value > threshold.warning) {
console.warn(`⚠️ Performance warning for ${name}: ${value}ms`);
secureLogger.warn(`⚠️ Performance warning for ${name}: ${value}ms`, { component: 'PerformanceMonitor', metric: name, value });
}
}
@ -245,7 +242,7 @@ export class PerformanceMonitor {
measureObserver.observe({ entryTypes: ['measure'] });
this.observers.push(measureObserver);
} catch (error) {
console.warn('Failed to observe performance measures:', error);
secureLogger.warn('Failed to observe performance measures', { component: 'PerformanceMonitor', error: String(error) });
}
// Observer la navigation
@ -263,7 +260,7 @@ export class PerformanceMonitor {
navigationObserver.observe({ entryTypes: ['navigation'] });
this.observers.push(navigationObserver);
} catch (error) {
console.warn('Failed to observe navigation performance:', error);
secureLogger.warn('Failed to observe navigation performance', { component: 'PerformanceMonitor', error: String(error) });
}
}
@ -285,7 +282,7 @@ export class PerformanceMonitor {
performance.measure(name, startMark);
}
} catch (error) {
console.warn(`Failed to measure ${name}:`, error);
secureLogger.warn(`Failed to measure ${name}`, { component: 'PerformanceMonitor', metric: name, error: String(error) });
}
}
@ -294,8 +291,6 @@ export class PerformanceMonitor {
*/
cleanup(): void {
const cutoff = Date.now() - (24 * 60 * 60 * 1000); // 24 heures
// Use cutoff variable
console.log('Performance cleanup cutoff:', cutoff);
this.metrics.forEach((values, name) => {
// Garder seulement les 100 dernières valeurs

View File

@ -57,21 +57,33 @@ export class SecureLogger {
/**
* Log un avertissement
*/
warn(message: string, context?: LogContext): void {
this.log(LogLevel.WARN, message, context);
warn(message: string, contextOrError?: LogContext | Error | unknown, context?: LogContext): void {
// If we have 3 params, second is error, third is context
if (context) {
// Message, error, context case
this.log(LogLevel.WARN, message, context);
} else if (contextOrError && (contextOrError instanceof Error || typeof contextOrError !== 'object')) {
// Message, error case - convert to context if needed
this.log(LogLevel.WARN, message, undefined);
} else {
// Message, context case
this.log(LogLevel.WARN, message, contextOrError as LogContext);
}
}
/**
* Log une erreur
*/
error(message: string, error?: Error, context?: LogContext): void {
this.log(LogLevel.ERROR, message, context, error);
error(message: string, error?: Error | unknown, context?: LogContext): void {
// Convert unknown to Error if needed
const errorObj = error instanceof Error ? error : (error ? new Error(String(error)) : undefined);
this.log(LogLevel.ERROR, message, context, errorObj);
}
/**
* Log sécurisé avec sanitisation
*/
private log(level: LogLevel, message: string, context?: LogContext, error?: Error): void {
private log(level: LogLevel, message: string, context?: LogContext, error?: Error | undefined): void {
const sanitizedContext = this.sanitizeContext(context);
const sanitizedMessage = this.sanitizeMessage(message);

View File

@ -1303,7 +1303,7 @@ export default class Services {
}
}
} catch (e) {
secureLogger.debug('Error in operation', e as Error, { component: 'Service' });
secureLogger.debug('Error in operation', { component: 'Service', error: String(e) });
}
}
@ -1704,6 +1704,10 @@ export default class Services {
throw new Error(` Credential retrieval failed: ${credentialError}`);
}
if (!credentials) {
throw new Error('No credentials found');
}
secureLogger.debug(' Credentials to inject:', { component: 'Service', data: {
spendKey_length: credentials.spendKey?.length || 0,
scanKey_length: credentials.scanKey?.length || 0,
@ -1711,10 +1715,6 @@ export default class Services {
scanKey_type: typeof credentials.scanKey
} });
if (!credentials) {
throw new Error('No credentials found');
}
// Injecter les clés dans le device de la base de données avant restauration
secureLogger.info('🔧 Injecting keys into device from database...', { component: 'Service' });
try {

View File

@ -62,7 +62,7 @@ export class LogAnalyzer {
/**
* Suggère un contexte basé sur le nom du fichier et le message
*/
private suggestContext(file: string, message: string): string {
private suggestContext(file: string, _message: string): string {
const fileName = file.split('/').pop()?.replace('.ts', '') || '';
// Patterns pour identifier le composant

View File

@ -25,7 +25,7 @@ export class LogFixer {
// Remplacer console.log par secureLogger.info
fixedContent = fixedContent.replace(
/console\.log\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
(match, message) => {
(_match, message) => {
const level = this.determineLogLevel(message);
const context = this.determineContext(filePath, message);
return `secureLogger.${level}('${message}'${context})`;
@ -35,7 +35,7 @@ export class LogFixer {
// Remplacer console.warn par secureLogger.warn
fixedContent = fixedContent.replace(
/console\.warn\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
(match, message) => {
(_match, message) => {
const context = this.determineContext(filePath, message);
return `secureLogger.warn('${message}'${context})`;
}
@ -44,7 +44,7 @@ export class LogFixer {
// Remplacer console.error par secureLogger.error
fixedContent = fixedContent.replace(
/console\.error\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
(match, message) => {
(_match, message) => {
const context = this.determineContext(filePath, message);
return `secureLogger.error('${message}'${context})`;
}
@ -53,7 +53,7 @@ export class LogFixer {
// Remplacer console.info par secureLogger.info
fixedContent = fixedContent.replace(
/console\.info\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
(match, message) => {
(_match, message) => {
const context = this.determineContext(filePath, message);
return `secureLogger.info('${message}'${context})`;
}
@ -62,7 +62,7 @@ export class LogFixer {
// Remplacer console.debug par secureLogger.debug
fixedContent = fixedContent.replace(
/console\.debug\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
(match, message) => {
(_match, message) => {
const context = this.determineContext(filePath, message);
return `secureLogger.debug('${message}'${context})`;
}
@ -95,7 +95,7 @@ export class LogFixer {
/**
* Détermine le contexte basé sur le fichier et le message
*/
private static determineContext(filePath: string, message: string): string {
private static determineContext(filePath: string, _message: string): string {
const fileName = filePath.split('/').pop()?.replace('.ts', '') || '';
let component = 'Application';

View File

@ -2593,7 +2593,7 @@ async function onCreateButtonClick() {
}
// New function for joiner to discover and join existing pairing process using 4 words
export async function discoverAndJoinPairingProcessWithWords(words: string): Promise<void> {
export async function discoverAndJoinPairingProcessWithWords(_words: string): Promise<void> {
const service = await Services.getInstance();
try {
@ -2647,13 +2647,13 @@ export async function discoverAndJoinPairingProcessWithWords(words: string): Pro
throw new Error(`❌ Failed to discover pairing process after ${maxRetries} attempts`);
} catch (err) {
secureLogger.error('❌ Joiner discovery failed:', (err as Error, { component: 'SPAddressUtils' }).message);
secureLogger.error('❌ Joiner discovery failed:', err as Error, { component: 'SPAddressUtils' });
throw err;
}
}
// New function for joiner to discover and join existing pairing process
export async function discoverAndJoinPairingProcess(creatorAddress: string): Promise<void> {
export async function discoverAndJoinPairingProcess(_creatorAddress: string): Promise<void> {
const service = await Services.getInstance();
try {
@ -2707,7 +2707,7 @@ export async function discoverAndJoinPairingProcess(creatorAddress: string): Pro
throw new Error(`❌ Failed to discover pairing process after ${maxRetries} attempts`);
} catch (err) {
secureLogger.error('❌ Joiner discovery failed:', (err as Error, { component: 'SPAddressUtils' }).message);
secureLogger.error('❌ Joiner discovery failed:', err as Error, { component: 'SPAddressUtils' });
throw err;
}
}

View File

@ -1,5 +1,5 @@
import { defineConfig } from 'vite';
// import path from 'path';
import path from 'path';
// @ts-ignore - vite-plugin-wasm type definitions issue
import wasm from 'vite-plugin-wasm';
import topLevelAwait from 'vite-plugin-top-level-await';
@ -19,6 +19,18 @@ export default defineConfig({
target: 'esnext',
minify: true, // Enable minification to reduce size
rollupOptions: {
input: {
index: path.resolve(__dirname, 'index.html'),
'wallet-setup': path.resolve(__dirname, 'src/pages/wallet-setup/wallet-setup.html'),
'security-setup': path.resolve(__dirname, 'src/pages/security-setup/security-setup.html'),
'birthday-setup': path.resolve(__dirname, 'src/pages/birthday-setup/birthday-setup.html'),
'block-sync': path.resolve(__dirname, 'src/pages/block-sync/block-sync.html'),
'pairing': path.resolve(__dirname, 'src/pages/pairing/pairing.html'),
'home': path.resolve(__dirname, 'src/pages/home/home.html'),
'iframe-home': path.resolve(__dirname, 'src/pages/home/iframe-home.html'),
'account': path.resolve(__dirname, 'src/pages/account/account.html'),
'iframe-pairing': path.resolve(__dirname, 'src/pages/iframe-pairing.html'),
},
output: {
manualChunks: {
// Split WebAssembly into separate chunk