Compare commits

...

39 Commits

Author SHA1 Message Date
af78165aee feat: add auto-redirect to pairing after block sync and update button text
**Motivations :**
- Passer automatiquement au pairing après 3 secondes une fois la synchronisation terminée
- Améliorer le texte du bouton pour indiquer qu'on va au pairing

**Modifications :**
- Ajouter redirection automatique après 3 secondes quand synchronisation terminée (blocs déjà synchronisés ou scan complété)
- Changer le texte du bouton de "Terminer la synchronisation" à "Aller au pairing"
- Mettre à jour le statut pour indiquer la redirection automatique

**Pages affectées :**
- src/pages/block-sync/block-sync.html (texte du bouton)
- src/pages/block-sync/block-sync.ts (redirection automatique après 3 secondes)
2025-10-29 17:09:10 +01:00
0d3163b5b2 fix: uniformize block-sync page style and redirect to pairing
**Motivations :**
- Uniformiser le style de la page block-sync avec les autres pages de setup
- Corriger la redirection pour aller vers la page de pairing au lieu de checkStorageStateAndNavigate

**Modifications :**
- Uniformiser le style HTML/CSS de block-sync.html avec birthday-setup et wallet-setup
- Changer la redirection du bouton pour aller vers /src/pages/home/home.html
- Utiliser le même fond dégradé et la même carte blanche centrée

**Pages affectées :**
- src/pages/block-sync/block-sync.html (style uniformisé)
- src/pages/block-sync/block-sync.ts (redirection vers pairing)
2025-10-29 17:05:56 +01:00
0ea661b766 fix: improve memory management for WebAssembly initialization
**Motivations :**
- WebAssembly échoue même avec 40% de mémoire utilisée, nécessite au moins 150MB disponibles
- Ajouter une vérification mémoire plus stricte avant d'importer WebAssembly
- Améliorer le nettoyage mémoire dans wallet-setup avant l'initialisation

**Modifications :**
- Ajouter vérification mémoire avant import WebAssembly dans service.ts init()
- Vérifier que plus de 150MB sont disponibles ou moins de 85% utilisés
- Améliorer nettoyage mémoire dans wallet-setup si >75% utilisé
- Lancer erreur explicite si mémoire insuffisante avec détails

**Pages affectées :**
- src/services/service.ts (vérification mémoire avant import WebAssembly)
- src/pages/wallet-setup/wallet-setup.ts (nettoyage mémoire amélioré)
2025-10-29 16:56:41 +01:00
43a5fadbc8 fix: reorder prerequisites check before Services initialization
**Motivations :**
- Éviter d'initialiser WebAssembly si les prérequis ne sont pas remplis
- Réduire la consommation mémoire en vérifiant d'abord les prérequis légers
- Éviter le router d'initialiser Services lors de la vérification du pairing

**Modifications :**
- Déplacer la vérification PBKDF2 AVANT l'initialisation de Services dans wallet-setup.ts
- Supprimer l'initialisation de Services dans router.ts lors de la vérification du pairing
- Router redirige maintenant directement vers home sans vérifier le pairing

**Pages affectées :**
- src/router.ts (supprime Services.getInstance() lors de la vérification du pairing)
- src/pages/wallet-setup/wallet-setup.ts (vérifie prérequis avant Services)
2025-10-29 16:50:17 +01:00
1f9100e3fe refactor: remove commented interface in storage.service.ts
**Motivations :**
- Supprimer le code mort (interface commentée)

**Modifications :**
- Supprimer l'interface TestResponse commentée dans storage.service.ts

**Pages affectées :**
- src/services/storage.service.ts (supprime interface commentée)
2025-10-29 16:48:10 +01:00
393b75c03b fix: restore services initialization in block-sync and remove remaining commented imports
**Motivations :**
- Corriger l'erreur de compilation dans block-sync.ts où services n'était plus défini
- Supprimer les derniers imports commentés restants

**Modifications :**
- Restaurer l'initialisation de Services dans block-sync.ts après les vérifications de prérequis
- Supprimer les imports commentés dans pairing.service.ts et iframe-pairing.service.ts

**Pages affectées :**
- src/pages/block-sync/block-sync.ts (restauration de l'initialisation Services)
- src/services/pairing.service.ts (supprime imports commentés)
- src/services/iframe-pairing.service.ts (supprime import commenté)
2025-10-29 16:47:55 +01:00
3f7c3b1dbe fix: correct indentation in wallet-setup.ts 2025-10-29 16:47:31 +01:00
90bb585251 refactor: remove duplicated code and dead code
**Motivations :**
- Éliminer la duplication de code pour la vérification PBKDF2 key
- Éliminer la duplication de code pour la vérification du wallet avec retries
- Supprimer les imports commentés (code mort)
- Centraliser la logique de vérification des prérequis dans un utilitaire

**Modifications :**
- Créer src/utils/prerequisites.utils.ts avec checkPBKDF2Key() et checkWalletWithRetries()
- Remplacer toutes les occurrences dupliquées dans router.ts, home.ts, birthday-setup.ts, block-sync.ts, wallet-setup.ts
- Supprimer les imports commentés dans device-management.ts, pairing.service.ts, modal.service.ts
- Utiliser pbkdf2KeyResult.key au lieu de récupérer la clé plusieurs fois dans wallet-setup.ts

**Pages affectées :**
- src/utils/prerequisites.utils.ts (nouveau fichier utilitaire)
- src/router.ts (utilise checkPBKDF2Key)
- src/pages/home/home.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/birthday-setup/birthday-setup.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/block-sync/block-sync.ts (utilise checkPBKDF2Key, checkWalletWithRetries)
- src/pages/wallet-setup/wallet-setup.ts (utilise checkPBKDF2Key, pbkdf2KeyResult.key)
- src/services/service.ts (supprime imports commentés)
- src/components/device-management/device-management.ts (supprime import commenté)
- src/services/pairing.service.ts (supprime imports commentés)
- src/services/modal.service.ts (supprime import commenté)
2025-10-29 16:47:12 +01:00
f3cfeef11b feat: optimize router to start with security-setup without WebAssembly
**Motivations :**
- La page d'accueil doit rediriger automatiquement vers security-setup si aucune clé PBKDF2 n'est trouvée
- Éviter d'initialiser WebAssembly au démarrage pour une première visite
- Le processus doit avancer automatiquement au fur et à mesure des vérifications

**Modifications :**
- Modifier checkStorageStateAndNavigate() pour utiliser DeviceReaderService au lieu de Services
- Commencer par vérifier la clé PBKDF2 (étape la plus précoce)
- Ne pas initialiser Services.getInstance() au démarrage dans init()
- Ajouter la route block-sync dans routes
- Rediriger vers security-setup par défaut si aucune clé PBKDF2 trouvée

**Pages affectées :**
- src/router.ts (logique de redirection optimisée, route block-sync ajoutée)
2025-10-29 16:42:04 +01:00
4418f805ef fix: replace all BigInt with bigint type
**Motivations :**
- Uniformiser l'utilisation de bigint au lieu du type BigInt TypeScript
- Corriger les erreurs de comparaison entre bigint et BigInt

**Modifications :**
- Changer toutes les références BigInt en bigint
- Utiliser 0n au lieu de BigInt(0)
- Utiliser 10n au lieu de BigInt(10)

**Pages affectées :**
- src/services/service.ts (types BigInt -> bigint, constantes)
2025-10-29 16:38:46 +01:00
6f54d8ad51 fix: correct BigInt type and PasswordCredential annotations
**Motivations :**
- Corriger le type de retour de getAmount() pour utiliser bigint au lieu de BigInt
- Utiliser @ts-ignore au lieu de @ts-expect-error pour PasswordCredential API

**Modifications :**
- Changer getAmount() return type de BigInt à bigint
- Remplacer @ts-expect-error par @ts-ignore pour PasswordCredential

**Pages affectées :**
- src/services/service.ts (type de retour getAmount)
- src/services/secure-credentials.service.ts (annotations PasswordCredential)
2025-10-29 16:38:05 +01:00
aa95537254 fix: correct TypeScript and ESLint errors
**Motivations :**
- Corriger les erreurs TypeScript qui bloquaient la compilation
- Corriger les erreurs ESLint pour améliorer la qualité du code
- Utiliser les bons formats pour secureLogger.error

**Modifications :**
- Corriger les appels secureLogger.error pour passer error comme 2e paramètre
- Ajouter les imports manquants (Database dans security-mode.service.ts)
- Préfixer les variables non utilisées avec _ pour respecter ESLint
- Corriger les comparaisons BigInt (utiliser BigInt(0) au lieu de 0n)
- Ajouter @ts-expect-error pour PasswordCredential API expérimentale
- Corriger le paramètre services non utilisé dans router.ts

**Pages affectées :**
- src/services/service.ts (comparaisons BigInt, imports)
- src/services/security-mode.service.ts (import Database)
- src/services/secure-credentials.service.ts (secureLogger.error, PasswordCredential)
- src/services/credentials/encryption.service.ts (secureLogger.error, salt type)
- src/router.ts (paramètre _services)
- src/components/device-management/device-management.ts (variable _data)
- src/components/secure-credentials/secure-credentials.ts (variable _error)
- src/components/security-mode-selector/security-mode-selector.ts (paramètre _mode)
- src/components/login-modal/login-modal.js (window globals)
2025-10-29 16:37:28 +01:00
8e6756539d fix: replace dynamic imports with static imports in home.ts
**Motivations :**
- Les imports dynamiques causaient des erreurs de redéclaration TypeScript
- L'erreur 500 venait de ces erreurs de compilation
- Les imports statiques sont plus fiables avec Vite

**Modifications :**
- Ajouter les imports statiques de SecureCredentialsService et SecurityModeService en haut de home.ts
- Remplacer tous les imports dynamiques par des références aux imports statiques
- Supprimer les redéclarations de SecureCredentialsService qui causaient l'erreur TS2451

**Pages affectées :**
- src/pages/home/home.ts (imports statiques)
2025-10-29 16:29:20 +01:00
16d30d45dc chore: add lint-all.sh script for automated linting with --fix
**Motivations :**
- Créer un script shell pour linter tout le projet avec correction automatique
- Faciliter l'exécution du linting avec --fix

**Modifications :**
- Créer lint-all.sh qui appelle npm run lint (qui inclut déjà --fix)
- Rendre le script exécutable

**Pages affectées :**
- lint-all.sh (nouveau script)
2025-10-29 16:28:04 +01:00
d15ef53384 fix: replace dynamic imports with static imports in DeviceReaderService
**Motivations :**
- Les imports dynamiques peuvent causer des problèmes de compilation avec Vite
- Remplacer await import() par des imports statiques en haut du fichier
- Simplifier le code et améliorer la compatibilité avec Vite

**Modifications :**
- Remplacer les imports dynamiques par des imports statiques dans device-reader.service.ts
- Importer SecureCredentialsService et EncryptionService en haut du fichier
- Supprimer la variable workingMode non utilisée

**Pages affectées :**
- src/services/device-reader.service.ts (imports statiques)
2025-10-29 16:20:09 +01:00
149bdd26c9 fix: add caches to ESLint globals for Service Worker API 2025-10-29 16:12:31 +01:00
64476b639c chore: clean up and improve ESLint configuration
**Motivations :**
- Le projet a déjà ESLint mais la configuration était mixte (ancien et nouveau format)
- Améliorer la configuration pour gérer les Web Workers et les globals manquants
- Supprimer les fichiers de configuration obsolètes

**Modifications :**
- Supprimer .eslintrc.json (ancien format, ignoré par ESLint 9)
- Supprimer .eslintignore (remplacé par ignores dans eslint.config.js)
- Améliorer eslint.config.js avec les globals nécessaires (Web Workers, IndexedDB, etc.)
- Ajouter une configuration spécifique pour les fichiers worker.ts
- Les ignores sont maintenant centralisés dans eslint.config.js

**Pages affectées :**
- eslint.config.js (amélioration de la configuration)
- Suppression de .eslintrc.json et .eslintignore
2025-10-29 16:12:23 +01:00
102ee331db fix: syntax error in wallet-setup and use simplified Device type in DeviceReaderService
**Motivations :**
- Erreur de syntaxe dans wallet-setup.ts (try/catch mal formé)
- Import de Device depuis SDK peut causer des erreurs de compilation
- Utiliser un type simplifié pour éviter les dépendances lourdes

**Modifications :**
- Corriger l'indentation du try/catch dans wallet-setup.ts
- Remplacer l'import Device par une interface simplifiée dans DeviceReaderService
- Cette interface couvre les champs nécessaires sans dépendre du SDK complet

**Pages affectées :**
- src/pages/wallet-setup/wallet-setup.ts (correction syntaxe)
- src/services/device-reader.service.ts (type simplifié)
2025-10-29 16:04:59 +01:00
36adf1df12 refactor: extract device reading to lightweight service to avoid WebAssembly initialization
**Motivations :**
- home.ts utilise Services uniquement pour getDeviceFromDatabase() et getDeviceAddress()
- Services initialise WebAssembly qui peut causer des erreurs de mémoire
- Créer un service léger qui lit le device sans WebAssembly

**Modifications :**
- Créer DeviceReaderService qui lit le device depuis IndexedDB sans WebAssembly
- Extraire getDeviceFromDatabase() et getDeviceAddress() dans DeviceReaderService
- Modifier home.ts pour utiliser DeviceReaderService au lieu de Services
- DeviceReaderService déchiffre le device et extrait l'adresse depuis sp_wallet.address
- Supprimer l'import de Services dans home.ts

**Pages affectées :**
- src/services/device-reader.service.ts (nouveau service léger)
- src/pages/home/home.ts (utilise DeviceReaderService au lieu de Services)
2025-10-29 15:38:45 +01:00
9de7f1a5ed fix: prevent WebAssembly reinitialization on memory errors
**Motivations :**
- Les erreurs de mémoire causent des tentatives multiples d'initialisation WebAssembly
- Services.initializing reste défini après une erreur, causant de nouvelles tentatives
- Les pages réessayent indéfiniment même en cas d'erreur mémoire fatale

**Modifications :**
- Réinitialiser Services.initializing à null dans le catch pour éviter les tentatives multiples
- Détecter les erreurs de mémoire et arrêter immédiatement les tentatives
- Ajouter la détection d'erreurs de mémoire dans wallet-setup, birthday-setup et block-sync
- Afficher un message clair à l'utilisateur pour actualiser la page en cas d'erreur mémoire
- Améliorer les messages d'erreur pour indiquer que l'actualisation de la page est nécessaire

**Pages affectées :**
- src/services/service.ts (réinitialisation de initializing et détection mémoire)
- src/pages/wallet-setup/wallet-setup.ts (détection erreurs mémoire)
- src/pages/birthday-setup/birthday-setup.ts (détection erreurs mémoire)
- src/pages/block-sync/block-sync.ts (détection erreurs mémoire)
2025-10-29 15:28:37 +01:00
b3af85d3a0 fix: add prerequisite checks and real block sync to block-sync page
**Motivations :**
- La page block-sync ne vérifie pas les prérequis comme les autres pages
- La synchronisation est simulée au lieu d'être réelle
- Il manque les vérifications de PBKDF2, wallet et birthday

**Modifications :**
- Ajout des vérifications de prérequis (PBKDF2 key, wallet, birthday) dans block-sync.ts
- Remplacement de la synchronisation simulée par une synchronisation réelle via updateDeviceBlockHeight()
- Ajout de la connexion aux relais si chain_tip n'est pas disponible
- Ajout de vérifications de wallet avec retry pour gérer les problèmes de synchronisation
- Ajout de la gestion d'erreur avec redirection vers les pages appropriées selon le type d'erreur
- Amélioration de la gestion d'erreur avec messages clairs et redirections automatiques
- Déplacement de l'écouteur du bouton continuer avant le try pour être toujours disponible
- Ajout de vérifications de nullité pour les éléments DOM

**Pages affectées :**
- src/pages/block-sync/block-sync.ts (ajout des vérifications de prérequis et synchronisation réelle)
- src/pages/home/home.ts (ajout des vérifications de prérequis pour la page de pairing)
2025-10-29 15:21:19 +01:00
cd368cf667 fix: wait for handshake before checking chain_tip and add logs for debugging
**Motivations :**
- La vérification de chain_tip se fait trop tôt, avant que le handshake arrive
- Le code ne continue pas après updateDeviceBlockHeight(), besoin de logs pour comprendre pourquoi
- Ajouter des logs détaillés pour tracer le flux d'exécution

**Modifications :**
- Déplacer la vérification de chain_tip après l'attente du handshake dans birthday-setup.ts
- Améliorer la condition de vérification pour attendre chain_tip > 0
- Ajouter des logs détaillés à chaque étape dans birthday-setup.ts
- Ajouter un return explicite à la fin de updateDeviceBlockHeight() pour garantir qu'elle se termine correctement
- Ajouter des logs avant et après updateDeviceBlockHeight() pour tracer l'exécution

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (attente du handshake avant vérification)
- src/services/service.ts (return explicite et logs)
2025-10-29 15:10:38 +01:00
a52baf07e0 docs: fix inconsistencies in documentation files
**Motivations :**
- La documentation doit refléter l'état actuel du code
- Corriger les incohérences entre le code et la documentation
- Ajouter les messages manquants dans INTEGRATION.md

**Modifications :**
- CODE_ANALYSIS_REPORT.md : Mise à jour de la taille de service.ts (2275 -> 3265 lignes)
- CODE_ANALYSIS_REPORT.md : Correction de la description du cache (désactivé au lieu de non limité)
- PAIRING_SYSTEM_ANALYSIS.md : Mise à jour des recommandations pour refléter l'état actuel (corrections implémentées)
- INTEGRATION.md : Ajout des messages manquants (TEST_RESPONSE, LISTENING)

**Pages affectées :**
- CODE_ANALYSIS_REPORT.md
- docs/PAIRING_SYSTEM_ANALYSIS.md
- INTEGRATION.md
2025-10-29 13:41:38 +01:00
9b5e1b68b6 docs: update initialization flow documentation with verified checks
**Motivations :**
- La documentation doit refléter les améliorations récentes sur les vérifications réelles des logs
- Documenter le nouveau flux avec block-sync et les vérifications des prérequis
- Documenter le système de vérification réelle des logs pour faciliter la maintenance

**Modifications :**
- Ajout d'une section détaillée sur les vérifications des prérequis dans birthday-setup
- Documentation des vérifications réelles dans updateDeviceBlockHeight
- Documentation des vérifications réelles dans saveDeviceInDatabase
- Ajout de la section sur la redirection vers block-sync
- Mise à jour du diagramme de flux pour inclure block-sync et les vérifications
- Ajout d'une section complète sur le système de vérification réelle des logs
- Mise à jour de la gestion des erreurs avec les erreurs de vérification
- Ajout de points d'attention sur les vérifications réelles et block-sync

**Pages affectées :**
- docs/INITIALIZATION_FLOW.md (documentation complète mise à jour)
2025-10-29 13:33:50 +01:00
93ddfcbb76 fix: replace declarative logs with verified checks
**Motivations :**
- Les logs étaient déclaratifs (juste des messages sans vérification réelle)
- Il faut vérifier réellement que les opérations sont réussies avant de logger le succès
- Les logs doivent refléter la réalité et non juste des déclarations

**Modifications :**
- Ajout de vérifications réelles dans updateDeviceBlockHeight() pour vérifier que le device est bien restauré en mémoire et sauvegardé en base
- Ajout de vérification dans saveDeviceInDatabase() pour vérifier que le wallet est bien sauvegardé après l'opération
- Ajout de vérifications dans birthday-setup.ts pour vérifier que les relais sont connectés, que le handshake est reçu, et que le birthday est bien mis à jour
- Les logs de succès sont maintenant émis uniquement après vérification réelle des résultats
- Amélioration de la gestion des erreurs avec messages explicites si les vérifications échouent

**Pages affectées :**
- src/services/service.ts (updateDeviceBlockHeight et saveDeviceInDatabase avec vérifications réelles)
- src/pages/birthday-setup/birthday-setup.ts (vérifications réelles des prérequis et du birthday)
2025-10-29 13:29:07 +01:00
72cb8129c1 fix: use direct IndexedDB access in saveDeviceInDatabase and add debug logs
**Motivations :**
- saveDeviceInDatabase() n'utilise pas directement IndexedDB, ce qui peut causer des problèmes de synchronisation
- Il manque des logs pour déboguer pourquoi le wallet n'est pas sauvegardé après updateDeviceBlockHeight()
- Utiliser directement IndexedDB comme dans getDeviceFromDatabase() pour éviter les problèmes de service worker

**Modifications :**
- Remplacement de Database.getInstance() par un accès direct à IndexedDB dans saveDeviceInDatabase()
- Ajout de logs détaillés à chaque étape du processus de sauvegarde
- Utilisation de put() au lieu de delete() puis add() pour éviter les problèmes de timing
- Amélioration de la gestion des erreurs avec logs explicites pour chaque étape

**Pages affectées :**
- src/services/service.ts (saveDeviceInDatabase utilise maintenant IndexedDB directement)
2025-10-29 13:26:40 +01:00
c63fe48420 fix: encrypt device before saving in saveDeviceInDatabase
**Motivations :**
- saveDeviceInDatabase() sauvegardait le device en clair, écrasant le wallet chiffré
- wallet-setup.ts sauvegarde le device chiffré avec encrypted_device et encrypted_wallet
- Après updateDeviceBlockHeight(), le wallet était écrasé par un wallet en clair
- Violation de sécurité: le wallet ne doit jamais être stocké en clair

**Modifications :**
- Modification de saveDeviceInDatabase() pour chiffrer le device avant sauvegarde
- Récupération de la clé PBKDF2 pour chiffrer le device
- Préservation de encrypted_wallet lors de la sauvegarde
- Utilisation du même format que wallet-setup.ts (encrypted_device + encrypted_wallet)
- Amélioration de la gestion des erreurs avec logs explicites

**Pages affectées :**
- src/services/service.ts (saveDeviceInDatabase chiffre maintenant le device)
2025-10-29 13:13:52 +01:00
ad28b37903 fix: use correct methods to check PBKDF2 key in birthday-setup
**Motivations :**
- birthday-setup.ts utilisait getPBKDF2Key() qui n'existe pas dans SecureCredentialsService
- wallet-setup.ts trouve la clé PBKDF2 mais birthday-setup.ts ne la trouve pas
- Il faut utiliser les mêmes méthodes que wallet-setup.ts (hasPBKDF2Key + retrievePBKDF2Key)

**Modifications :**
- Remplacement de getPBKDF2Key() par hasPBKDF2Key() puis retrievePBKDF2Key()
- Utilisation de la même approche que wallet-setup.ts pour vérifier la clé PBKDF2
- Ajout du mode 'otp' à la liste des modes de sécurité pour correspondre à wallet-setup.ts
- Amélioration des messages de log pour indiquer explicitement la vérification dans le store pbkdf2keys

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (correction de la vérification PBKDF2)
2025-10-29 13:10:57 +01:00
aedfa09bbc fix: verify PBKDF2 key in pbkdf2keys store during prerequisites check
**Motivations :**
- La vérification des prérequis ne vérifiait pas réellement la présence de la clé PBKDF2 dans le store pbkdf2keys
- Il y avait une duplication de code pour la vérification de la clé PBKDF2
- Le mode de sécurité était recherché deux fois au lieu d'être réutilisé

**Modifications :**
- Déplacement de la vérification réelle de la clé PBKDF2 dans la section des prérequis
- Vérification explicite de la présence de la clé dans le store pbkdf2keys avant de continuer
- Stockage du mode de sécurité trouvé dans une variable au niveau supérieur pour réutilisation
- Suppression de la vérification dupliquée plus tard dans le code
- Amélioration des messages de log pour indiquer explicitement la vérification dans le store pbkdf2keys

**Pages affectées :**
- src/pages/wallet-setup/wallet-setup.ts (vérification des prérequis améliorée)
2025-10-29 12:42:13 +01:00
b0694eba22 fix: add retry mechanism for wallet retrieval in birthday-setup
**Motivations :**
- Le wallet disparaît parfois entre wallet-setup et birthday-setup à cause de problèmes de synchronisation
- Il faut ajouter une logique de retry pour gérer les problèmes de synchronisation de la base de données
- Donner plus de temps à la base de données pour synchroniser les données avant de rediriger

**Modifications :**
- Ajout d'une logique de retry dans birthday-setup.ts pour récupérer le wallet
- Attente de 500ms entre chaque tentative (jusqu'à 5 tentatives)
- Redirection vers wallet-setup seulement si le wallet n'est toujours pas trouvé après les retries
- Cette logique permet de gérer les problèmes de synchronisation de la base de données

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (logique de retry pour le wallet)
2025-10-29 12:35:42 +01:00
6f9baf6f56 fix: improve prerequisites verification with automatic redirection
**Motivations :**
- Chaque page doit vérifier ses prérequis et rediriger automatiquement si nécessaire
- Erreur avec SecureCredentialsService: 'is not a constructor'
- Amélioration de l'ordre de vérification des prérequis (PBKDF2 key d'abord, puis wallet)
- Redirection automatique vers la page appropriée si les prérequis ne sont pas remplis

**Modifications :**
- Correction de l'utilisation de SecureCredentialsService: utilisation de getInstance() au lieu de new
- Amélioration de la vérification des prérequis dans birthday-setup.ts avec redirection automatique
- Vérification du PBKDF2 key en premier (prérequis le plus basique)
- Redirection vers security-setup si le PBKDF2 key n'est pas trouvé
- Redirection vers wallet-setup si le wallet n'est pas trouvé
- Amélioration de wallet-setup.ts pour rediriger vers security-setup si aucune clé PBKDF2 n'est trouvée

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (vérification des prérequis améliorée)
- src/pages/wallet-setup/wallet-setup.ts (redirection vers security-setup)
2025-10-29 12:35:08 +01:00
bb5f70a48f feat: add prerequisites verification in birthday-setup
**Motivations :**
- Chaque page doit vérifier ses prérequis en base pour éviter les erreurs
- La page birthday-setup s'arrêtait sans wallet en base
- Il faut s'assurer que le wallet et le PBKDF2 key existent avant de continuer

**Modifications :**
- Ajout de la vérification des prérequis dans birthday-setup.ts
- Vérification que le wallet existe en base de données
- Vérification que le PBKDF2 key existe pour au moins un mode de sécurité
- Ajout de messages d'erreur explicites si les prérequis ne sont pas remplis
- Amélioration de la vérification des prérequis dans wallet-setup.ts

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (vérification des prérequis)
- src/pages/wallet-setup/wallet-setup.ts (amélioration de la vérification)
2025-10-29 08:42:36 +01:00
2ce11599e2 fix: use existing services instance in birthday-setup
**Motivations :**
- Chaque page créait une nouvelle instance des services, causant des problèmes de synchronisation
- Le wallet disparaissait entre les pages à cause de cette incohérence
- Il faut utiliser l'instance existante des services pour maintenir la cohérence

**Modifications :**
- Suppression de la boucle d'attente pour l'initialisation des services
- Utilisation directe de Services.getInstance() pour obtenir l'instance existante
- Simplification du code d'initialisation des services

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (utilisation de l'instance existante)
2025-10-28 23:33:35 +01:00
ce38e01037 fix: improve birthday-setup waiting mechanism and button text
**Motivations :**
- Le texte du bouton était toujours 'Terminer l'initialisation' au lieu de 'Synchroniser les blocs'
- La boucle d'attente de la hauteur de bloc était inefficace et atteignait le timeout
- Il faut une approche plus robuste pour attendre le handshake

**Modifications :**
- Changement du texte du bouton de 'Terminer l'initialisation' à 'Synchroniser les blocs'
- Remplacement de la boucle d'attente par une approche basée sur Promise avec timeout
- Augmentation du timeout à 15 secondes pour laisser plus de temps au handshake
- Amélioration de la logique d'attente de la hauteur de bloc

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.html (texte du bouton)
- src/pages/birthday-setup/birthday-setup.ts (mécanisme d'attente)
2025-10-28 23:32:53 +01:00
309e2902cf feat: redirect to block sync page after birthday setup
**Motivations :**
- La page birthday-setup s'arrêtait après la mise à jour de la date anniversaire
- Le processus devrait continuer vers une page de synchronisation des blocs
- Création d'une page dédiée pour la synchronisation des blocs

**Modifications :**
- Modification de birthday-setup.ts pour rediriger vers block-sync après la mise à jour de la date anniversaire
- Suppression du scan complet et de la synchronisation des processus de birthday-setup
- Création de la page block-sync.html avec interface de synchronisation
- Création de block-sync.ts avec logique de synchronisation des blocs
- Interface utilisateur avec détails de synchronisation et barre de progression

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (redirection vers block-sync)
- src/pages/block-sync/block-sync.html (nouvelle page)
- src/pages/block-sync/block-sync.ts (nouvelle logique)
2025-10-28 23:12:03 +01:00
1f6b622c1a fix: increase timeout for handshake and block height waiting
**Motivations :**
- Le handshake arrive après le timeout de 3 secondes dans waitForHandshakeMessage
- La boucle d'attente de la hauteur de bloc atteint 30 tentatives avant que le handshake soit traité
- Il faut augmenter les timeouts pour permettre au handshake d'arriver

**Modifications :**
- Augmentation du timeout de waitForHandshakeMessage de 3 à 10 secondes
- Augmentation du nombre de tentatives dans la boucle d'attente de 30 à 100 (10 secondes)
- Cela donne plus de temps au handshake pour arriver et définir la hauteur de bloc

**Pages affectées :**
- src/services/service.ts (timeout waitForHandshakeMessage)
- src/pages/birthday-setup/birthday-setup.ts (nombre de tentatives)
2025-10-28 17:45:37 +01:00
2f2088c8ea fix: resolve variable redeclaration error in birthday-setup
**Motivations :**
- Erreur de redéclaration des variables 'attempts' et 'maxAttempts' dans birthday-setup.ts
- Cela causait une erreur 500 lors du chargement du fichier TypeScript

**Modifications :**
- Renommage des variables dans la deuxième boucle d'attente pour éviter les conflits
- Utilisation de 'blockHeightAttempts' et 'blockHeightMaxAttempts' pour la boucle d'attente de la hauteur de bloc

**Pages affectées :**
- src/pages/birthday-setup/birthday-setup.ts (correction des variables redéclarées)
2025-10-28 13:43:34 +01:00
2fad2d507f fix: wait for block height before updating birthday
**Motivations :**
- L'erreur 'Current block height not set' se produit car updateDeviceBlockHeight est appelé avant que le handshake soit complètement traité
- Il faut attendre que this.currentBlockHeight soit défini avant de mettre à jour la date anniversaire

**Modifications :**
- Ajout de la méthode publique getCurrentBlockHeight() dans Services
- Modification de birthday-setup.ts pour attendre que la hauteur de bloc soit définie
- Ajout d'une boucle d'attente avec timeout pour s'assurer que le handshake est traité

**Pages affectées :**
- src/services/service.ts (getCurrentBlockHeight)
- src/pages/birthday-setup/birthday-setup.ts (attente de la hauteur de bloc)
2025-10-28 13:37:26 +01:00
c2bd615e88 fix: resolve 'Device not found' error in birthday-setup
**Motivations :**
- L'erreur 'Device not found' se produit lors de la mise à jour de la date anniversaire
- Le wallet est bien créé et sauvegardé mais n'est pas trouvé lors de la récupération
- Problème d'incohérence entre l'accès direct à IndexedDB et l'accès via service worker

**Modifications :**
- Ajout de logs de débogage dans getDeviceFromDatabase pour tracer le problème
- Modification de getDeviceFromDatabase pour utiliser directement IndexedDB au lieu du service worker
- Correction des erreurs TypeScript dans birthday-setup.ts et home.ts
- Correction des comparaisons BigInt dans service.ts
- Modification de birthday-setup.ts pour éviter l'utilisation de méthodes privées

**Pages affectées :**
- src/services/service.ts (getDeviceFromDatabase, updateDeviceBlockHeight)
- src/services/database.service.ts (getObject)
- src/pages/birthday-setup/birthday-setup.ts
- src/pages/home/home.ts
- src/components/security-mode-selector/security-mode-selector.ts
2025-10-28 13:30:45 +01:00
37 changed files with 2748 additions and 1075 deletions

7
.cursor/rules/init.mdc Normal file
View File

@ -0,0 +1,7 @@
---
alwaysApply: true
---
lire avec attention: docs/INITIALIZATION_FLOW.md
lire avec attention: docs/IA_agents/*
lire avec attention: docs/docs/*

26
.eslintignore Normal file
View File

@ -0,0 +1,26 @@
# 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

View File

@ -79,6 +79,8 @@ voir les fichiers README.md
* **Centralisation des logs :** Centraliser les logs dans les répertoires `logs/` des applications et dans le répertoire `logs/` du projet pour les logs hors applications (déploiement par exemple)
* **Système de logging :** Implémenter une gestion d'erreurs robuste et utiliser le système de logging Winston pour toutes les sorties (info, warn, error, debug, etc.).
* **Traçabilité :** Logger toutes les valeurs, états clés et retours d'API.
* **Données vérifiées :** Vérifiant que les logs reflètent des vérifications réelles et non des déclarations.
* **Log abondamment :** Log les informations et étapes ou états clés ainsi que les identifiants clés.
#### 🌐 Interactions Externes (BDD, API, Emails)
* **APIs externes :** Gérer les interactions avec les API de manière appropriée, en respectant les limites d'utilisation et en gérant les erreurs.

View File

@ -28,7 +28,7 @@ Après analyse complète du code au-delà du linting, j'ai identifié plusieurs
```
3. **Responsabilités mélangées** : Services font trop de choses
- `Services` : 2275 lignes, gère pairing, storage, websockets, UI
- `Services` : 3265 lignes, gère pairing, storage, websockets, UI
- `Database` : 619 lignes, gère storage + communication
### **✅ Solutions recommandées :**
@ -72,12 +72,14 @@ interface ProcessRepository {
### **❌ Goulots d'étranglement identifiés :**
#### **A. Gestion mémoire défaillante**
1. **Cache non limité** : `processesCache` grandit indéfiniment
1. **Cache désactivé** : `processesCache` existe mais est désactivé (`maxCacheSize = 0`)
```typescript
// ❌ Problème actuel
// ⚠️ État actuel
private processesCache: Record<string, Process> = {};
// Aucune limite, aucune expiration
private maxCacheSize = 0; // Disabled caches completely
private cacheExpiry = 0; // No cache expiry
```
**Note** : Le cache a été désactivé pour économiser la mémoire, mais cela peut impacter les performances pour les applications avec beaucoup de processus.
2. **Event listeners non nettoyés** : Fuites mémoire
```typescript

View File

@ -1,8 +1,8 @@
# Lecoffre
# IHM_CLIENT
voir les fichiers README.md
voir README.md
## Instructions for Claude
voir docs/INITIALIZATION_FLOW.md
### General
@ -79,6 +79,9 @@ voir les fichiers README.md
* **Centralisation des logs :** Centraliser les logs dans les répertoires `logs/` des applications et dans le répertoire `logs/` du projet pour les logs hors applications (déploiement par exemple)
* **Système de logging :** Implémenter une gestion d'erreurs robuste et utiliser le système de logging Winston pour toutes les sorties (info, warn, error, debug, etc.).
* **Traçabilité :** Logger toutes les valeurs, états clés et retours d'API.
* **Données vérifiées :** Vérifiant que les logs reflètent des vérifications réelles et non des déclarations.
* **Log abondamment :** Log les informations et étapes ou états clés ainsi que les identifiants clés.
#### 🌐 Interactions Externes (BDD, API, Emails)
* **APIs externes :** Gérer les interactions avec les API de manière appropriée, en respectant les limites d'utilisation et en gérant les erreurs.

View File

@ -1,473 +0,0 @@
# 🎯 Plan d'action concret - Améliorations du code
## 📋 **Résumé de l'analyse**
Après analyse approfondie du code au-delà du linting, j'ai identifié **4 axes d'amélioration majeurs** :
1. **🏗️ Architecture** : Anti-patterns (singletons, couplage fort)
2. **🚀 Performance** : Fuites mémoire, opérations bloquantes
3. **🔒 Sécurité** : Exposition de données sensibles, validation insuffisante
4. **🧪 Qualité** : Aucun test, pas de monitoring
## 🎯 **Plan d'action prioritaire**
### **🔥 PHASE 1 - CRITIQUE (Semaine 1-2)**
#### **A. Sécurisation immédiate**
```bash
# 1. Chiffrement des clés privées
- Implémenter SecureKeyManager
- Remplacer le stockage en clair
- Ajouter la rotation des clés
# 2. Sanitisation des logs
- Supprimer les données sensibles des logs
- Implémenter SecureLogger
- Ajouter des niveaux de log
# 3. Validation des entrées
- Valider tous les messages WebSocket
- Sanitiser les données utilisateur
- Ajouter des limites de taille
```
#### **B. Gestion mémoire urgente**
```bash
# 1. Limitation des caches
- Ajouter une limite au processesCache
- Implémenter l'expiration TTL
- Nettoyer les caches inutilisés
# 2. Nettoyage des event listeners
- Implémenter un système de cleanup
- Ajouter des AbortController
- Nettoyer les WebSockets
# 3. Optimisation des boucles
- Remplacer les boucles synchrones
- Utiliser des Web Workers
- Implémenter le debouncing
```
### **⚡ PHASE 2 - PERFORMANCE (Semaine 3-4)**
#### **A. Architecture modulaire**
```bash
# 1. Injection de dépendances
- Créer un ServiceContainer
- Remplacer les singletons
- Implémenter le pattern Repository
# 2. Séparation des responsabilités
- Diviser Services (2275 lignes)
- Créer des services spécialisés
- Implémenter des interfaces
# 3. Communication découplée
- Implémenter un EventBus
- Utiliser des messages asynchrones
- Ajouter la gestion d'erreurs
```
#### **B. Optimisations**
```bash
# 1. Encodage asynchrone
- Utiliser des Web Workers
- Implémenter le streaming
- Ajouter la compression
# 2. Lazy loading
- Charger les modules à la demande
- Implémenter le code splitting
- Optimiser les imports
# 3. Caching intelligent
- Implémenter un cache LRU
- Ajouter la prévalidation
- Utiliser IndexedDB efficacement
```
### **🧪 PHASE 3 - QUALITÉ (Semaine 5-6)**
#### **A. Tests complets**
```bash
# 1. Tests unitaires
- Couvrir tous les services
- Tester les cas d'erreur
- Ajouter des mocks
# 2. Tests d'intégration
- Tester les flux complets
- Valider les communications
- Tester les performances
# 3. Tests de sécurité
- Tester les validations
- Vérifier l'encryption
- Tester les limites
```
#### **B. Monitoring**
```bash
# 1. Métriques de performance
- Temps de réponse
- Utilisation mémoire
- Taux d'erreur
# 2. Health checks
- Vérifier la base de données
- Tester les WebSockets
- Monitorer les ressources
# 3. Alertes
- Seuils de performance
- Erreurs critiques
- Ressources limitées
```
## 🛠️ **Implémentation pratique**
### **1. Création des nouveaux services**
#### **A. SecureKeyManager**
```typescript
// src/services/secure-key-manager.ts
export class SecureKeyManager {
private keyStore: CryptoKey | null = null;
private salt: Uint8Array;
constructor() {
this.salt = crypto.getRandomValues(new Uint8Array(16));
}
async storePrivateKey(key: string, password: string): Promise<void> {
const derivedKey = await this.deriveKey(password);
const encryptedKey = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: crypto.getRandomValues(new Uint8Array(12)) },
derivedKey,
new TextEncoder().encode(key)
);
this.keyStore = encryptedKey;
}
async getPrivateKey(password: string): Promise<string | null> {
if (!this.keyStore) return null;
try {
const derivedKey = await this.deriveKey(password);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: this.keyStore.slice(0, 12) },
derivedKey,
this.keyStore.slice(12)
);
return new TextDecoder().decode(decrypted);
} catch {
return null;
}
}
private async deriveKey(password: string): Promise<CryptoKey> {
const keyMaterial = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt: this.salt, iterations: 100000, hash: 'SHA-256' },
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
}
```
#### **B. PerformanceMonitor**
```typescript
// src/services/performance-monitor.ts
export class PerformanceMonitor {
private metrics: Map<string, number[]> = new Map();
private observers: PerformanceObserver[] = [];
constructor() {
this.setupPerformanceObservers();
}
recordMetric(name: string, value: number): void {
if (!this.metrics.has(name)) {
this.metrics.set(name, []);
}
this.metrics.get(name)!.push(value);
// Garder seulement les 100 dernières valeurs
const values = this.metrics.get(name)!;
if (values.length > 100) {
values.shift();
}
}
getAverageMetric(name: string): number {
const values = this.metrics.get(name) || [];
return values.reduce((sum, val) => sum + val, 0) / values.length;
}
getMetrics(): Record<string, number> {
const result: Record<string, number> = {};
for (const [name, values] of this.metrics) {
result[name] = this.getAverageMetric(name);
}
return result;
}
private setupPerformanceObservers(): void {
// Observer les mesures de performance
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.recordMetric(entry.name, entry.duration);
}
});
observer.observe({ entryTypes: ['measure', 'navigation'] });
this.observers.push(observer);
}
}
```
#### **C. EventBus**
```typescript
// src/services/event-bus.ts
export class EventBus {
private listeners: Map<string, Function[]> = new Map();
on(event: string, callback: Function): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(callback);
}
off(event: string, callback: Function): void {
const callbacks = this.listeners.get(event);
if (callbacks) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
emit(event: string, data?: any): void {
const callbacks = this.listeners.get(event);
if (callbacks) {
callbacks.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in event listener for ${event}:`, error);
}
});
}
}
once(event: string, callback: Function): void {
const onceCallback = (data: any) => {
callback(data);
this.off(event, onceCallback);
};
this.on(event, onceCallback);
}
}
```
### **2. Refactoring des services existants**
#### **A. Services modulaire**
```typescript
// src/services/pairing.service.ts
export class PairingService {
constructor(
private deviceRepo: DeviceRepository,
private eventBus: EventBus,
private logger: Logger,
private secureKeyManager: SecureKeyManager
) {}
async createPairing(): Promise<PairingResult> {
try {
this.logger.info('Creating pairing process');
const device = await this.deviceRepo.getDevice();
if (!device) {
throw new Error('No device found');
}
const result = await this.sdkClient.createPairing();
this.eventBus.emit('pairing:created', result);
this.logger.info('Pairing created successfully');
return { success: true, data: result };
} catch (error) {
this.logger.error('Failed to create pairing', error);
this.eventBus.emit('pairing:error', error);
return { success: false, error };
}
}
}
```
#### **B. Repository pattern**
```typescript
// src/repositories/device.repository.ts
export class DeviceRepository {
constructor(private database: Database) {}
async getDevice(): Promise<Device | null> {
try {
const device = await this.database.get('devices', 'current');
return device ? this.deserializeDevice(device) : null;
} catch (error) {
console.error('Failed to get device:', error);
return null;
}
}
async saveDevice(device: Device): Promise<void> {
try {
const serialized = this.serializeDevice(device);
await this.database.put('devices', serialized, 'current');
} catch (error) {
console.error('Failed to save device:', error);
throw error;
}
}
private serializeDevice(device: Device): any {
// Sérialisation sécurisée
return {
...device,
// Ne pas exposer les clés privées
sp_wallet: {
...device.sp_wallet,
private_key: '[REDACTED]'
}
};
}
}
```
### **3. Tests et validation**
#### **A. Tests unitaires**
```typescript
// src/services/__tests__/pairing.service.test.ts
describe('PairingService', () => {
let pairingService: PairingService;
let mockDeviceRepo: jest.Mocked<DeviceRepository>;
let mockEventBus: jest.Mocked<EventBus>;
let mockLogger: jest.Mocked<Logger>;
beforeEach(() => {
mockDeviceRepo = createMockDeviceRepository();
mockEventBus = createMockEventBus();
mockLogger = createMockLogger();
pairingService = new PairingService(
mockDeviceRepo,
mockEventBus,
mockLogger,
mockSecureKeyManager
);
});
it('should create pairing successfully', async () => {
// Arrange
const mockDevice = createMockDevice();
mockDeviceRepo.getDevice.mockResolvedValue(mockDevice);
// Act
const result = await pairingService.createPairing();
// Assert
expect(result.success).toBe(true);
expect(mockEventBus.emit).toHaveBeenCalledWith('pairing:created', expect.any(Object));
expect(mockLogger.info).toHaveBeenCalledWith('Creating pairing process');
});
it('should handle device not found error', async () => {
// Arrange
mockDeviceRepo.getDevice.mockResolvedValue(null);
// Act
const result = await pairingService.createPairing();
// Assert
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(mockEventBus.emit).toHaveBeenCalledWith('pairing:error', expect.any(Error));
});
});
```
#### **B. Tests de performance**
```typescript
// src/tests/performance.test.ts
describe('Performance Tests', () => {
it('should handle large data encoding within time limit', async () => {
const largeData = generateLargeData(1024 * 1024); // 1MB
const startTime = performance.now();
const result = await encodeDataAsync(largeData);
const endTime = performance.now();
expect(endTime - startTime).toBeLessThan(5000); // 5 secondes max
expect(result).toBeDefined();
});
it('should not exceed memory limit', async () => {
const initialMemory = performance.memory?.usedJSHeapSize || 0;
// Simuler une charge importante
for (let i = 0; i < 1000; i++) {
await processLargeData();
}
const finalMemory = performance.memory?.usedJSHeapSize || 0;
const memoryIncrease = finalMemory - initialMemory;
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); // 50MB max
});
});
```
## 📊 **Métriques de succès**
### **Objectifs quantifiables :**
- **Performance** : Temps de réponse < 200ms
- **Mémoire** : Utilisation < 100MB
- **Sécurité** : 0 vulnérabilité critique
- **Qualité** : Couverture de tests > 80%
- **Maintenabilité** : Complexité cyclomatique < 10
### **Indicateurs de progression :**
- **Semaine 1** : Sécurité implémentée, logs sécurisés
- **Semaine 2** : Gestion mémoire optimisée, fuites corrigées
- **Semaine 3** : Architecture modulaire, injection de dépendances
- **Semaine 4** : Performance optimisée, encodage asynchrone
- **Semaine 5** : Tests unitaires, couverture > 80%
- **Semaine 6** : Monitoring complet, métriques en temps réel
## 🚀 **Bénéfices attendus**
1. **Performance** : 3x plus rapide, 50% moins de mémoire
2. **Sécurité** : Protection des données sensibles
3. **Maintenabilité** : Code modulaire et testable
4. **Évolutivité** : Architecture extensible
5. **Fiabilité** : Moins de bugs, plus de stabilité
---
**Prochaines étapes** : Commencer par la Phase 1 (sécurisation) qui est la plus critique pour la sécurité de l'application.

View File

@ -63,11 +63,15 @@ if (window.parent !== window) {
- `PAIRING_4WORDS_STATUS_UPDATE` : Mise à jour du statut
- `PAIRING_4WORDS_SUCCESS` : Pairing réussi
- `PAIRING_4WORDS_ERROR` : Erreur de pairing
- `TEST_RESPONSE` : Réponse à un message de test
- `LISTENING` : Notification que l'iframe écoute les messages
### Messages reçus du parent
- `TEST_MESSAGE` : Test de communication
- `PAIRING_4WORDS_CREATE` : Créer un pairing
- `PAIRING_4WORDS_JOIN` : Rejoindre avec 4 mots
- `LISTENING` : Notification que le parent écoute les messages
- `IFRAME_READY` : Notification que l'iframe est prête (envoyée par l'iframe elle-même)
## 🧪 Tests d'intégration

624
docs/INITIALIZATION_FLOW.md Normal file
View File

@ -0,0 +1,624 @@
# Documentation de l'Initialisation LeCoffre.io
## Vue d'ensemble
Le système LeCoffre.io suit un processus d'initialisation en plusieurs étapes pour créer et sécuriser un wallet Bitcoin. Ce document détaille chaque étape du processus, depuis le choix du mode de sécurité jusqu'au pairing réussi et à la récupération des processus.
## Architecture des Stores IndexedDB
### Stores utilisés :
- **`pbkdf2keys`** : Stockage des clés PBKDF2 chiffrées par mode de sécurité
- **`wallet`** : Stockage du wallet chiffré (device + wallet data)
- **`credentials`** : Stockage des credentials de pairing (utilisé uniquement après pairing)
- **`processes`** : Stockage des processus de communication
- **`labels`** : Stockage des labels de transactions
- **`shared_secrets`** : Stockage des secrets partagés
- **`unconfirmed_secrets`** : Stockage des secrets non confirmés
- **`diffs`** : Stockage des différences de synchronisation
- **`data`** : Stockage des données générales
## Flux d'Initialisation Complet
### 1. Démarrage de l'Application
**Fichier :** `src/router.ts``checkStorageStateAndNavigate()`
L'application vérifie l'état du storage pour déterminer l'étape suivante :
```typescript
// Logique de progression :
// - Si pairing → account
// - Si date anniversaire → pairing
// - Si wallet → birthday-setup
// - Si pbkdf2 → wallet-setup
// - Sinon → security-setup
```
**États possibles :**
1. **Appareil appairé** → Redirection vers `account`
2. **Date anniversaire configurée** → Redirection vers `home` (pairing)
3. **Wallet existe sans date anniversaire** → Redirection vers `birthday-setup`
4. **Clé PBKDF2 existe** → Redirection vers `wallet-setup`
5. **Aucune configuration** → Redirection vers `security-setup`
### 2. Configuration du Mode de Sécurité
**Fichier :** `src/pages/security-setup/security-setup.ts`
#### 2.1 Sélection du Mode
L'utilisateur choisit parmi les modes disponibles :
| Mode | Nom | Description | Niveau de Sécurité | Clé de Chiffrement PBKDF2 | Stockage de la Clé de Chiffrement |
|------|-----|-------------|-------------------|---------------------------|-----------------------------------|
| `proton-pass` | Proton Pass | Authentification biométrique via Proton Pass | High | Clé WebAuthn générée par le navigateur | Stockée dans le navigateur (WebAuthn credential) |
| `os` | Authentificateur OS | Authentification biométrique du système | High | Clé WebAuthn générée par le système | Stockée dans le système d'exploitation |
| `otp` | OTP | Code à usage unique (Google Authenticator, etc.) | High | Aucune (clé PBKDF2 stockée en clair) | Secret OTP stocké dans l'application OTP |
| `password` | Mot de passe | Chiffrement par mot de passe (non sauvegardé) | Low | Mot de passe utilisateur | Stocké dans le gestionnaire de mots de passe du navigateur |
| `none` | Aucune sécurité | Chiffrement avec clé en dur (non recommandé) | Critical | Clé en dur `4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE` | Intégrée dans le code (non sécurisé) |
#### 2.2 Génération de la Clé PBKDF2
**Fichier :** `src/services/secure-credentials.service.ts``generatePBKDF2Key()`
Pour chaque mode, une clé PBKDF2 est générée et stockée différemment :
##### Mode `proton-pass` et `os` (WebAuthn)
```typescript
// Stockage avec WebAuthn (authentification biométrique)
await webAuthnService.storeKeyWithWebAuthn(pbkdf2Key, securityMode);
```
- **Store :** `pbkdf2keys`
- **Clé :** `security_mode` (ex: "proton-pass")
- **Valeur :** Clé PBKDF2 chiffrée avec WebAuthn
- **Authentification :** Biométrique (empreinte, visage, etc.)
##### Mode `otp`
```typescript
// Génération du secret OTP
const otpSecret = await this.generateOTPSecret();
// Stockage de la clé PBKDF2 en clair
await this.storePBKDF2KeyInStore(pbkdf2Key, securityMode);
// Affichage du QR code
this.displayOTPQRCode(otpSecret);
```
- **Store :** `pbkdf2keys`
- **Clé :** `security_mode` ("otp")
- **Valeur :** Clé PBKDF2 en clair
- **Authentification :** Code OTP généré par l'application
##### Mode `password`
```typescript
// Demande du mot de passe utilisateur
const userPassword = await this.promptForPasswordWithBrowser();
// Chiffrement de la clé PBKDF2
const encryptedKey = await encryptionService.encrypt(pbkdf2Key, userPassword);
// Stockage chiffré
await this.storePBKDF2KeyInStore(encryptedKey, securityMode);
```
- **Store :** `pbkdf2keys`
- **Clé :** `security_mode` ("password")
- **Valeur :** Clé PBKDF2 chiffrée avec le mot de passe utilisateur
- **Authentification :** Mot de passe utilisateur (non sauvegardé)
##### Mode `none`
```typescript
// Clé de chiffrement en dur
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
// Chiffrement avec la clé en dur
const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey);
// Stockage chiffré
await this.storePBKDF2KeyInStore(encryptedKeyNone, securityMode);
```
- **Store :** `pbkdf2keys`
- **Clé :** `security_mode` ("none")
- **Valeur :** Clé PBKDF2 chiffrée avec clé en dur
- **Authentification :** Aucune (non sécurisé)
### 3. Création du Wallet
**Fichier :** `src/pages/wallet-setup/wallet-setup.ts`
#### 3.1 Récupération de la Clé PBKDF2
Le système teste tous les modes de sécurité pour trouver la clé PBKDF2 valide :
```typescript
const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass'];
for (const mode of allSecurityModes) {
const hasKey = await secureCredentialsService.hasPBKDF2Key(mode);
if (hasKey) {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
currentMode = mode;
break;
}
}
}
```
#### 3.2 Génération des Clés du Wallet
```typescript
// Génération des clés temporaires
const walletData = {
scan_sk: encryptionService.generateRandomKey(),
spend_key: encryptionService.generateRandomKey(),
network: 'signet',
state: 'birthday_waiting',
created_at: new Date().toISOString()
};
```
#### 3.3 Création du Device via SDK
```typescript
// Création du device avec birthday = 0
const spAddress = await services.sdkClient.create_new_device(0, 'signet');
// Génération forcée du wallet
const wallet = await services.sdkClient.dump_wallet();
```
#### 3.4 Stockage Chiffré du Wallet
```typescript
// Chiffrement du device
const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key);
// Chiffrement du wallet
const encryptedWallet = await encryptionService.encrypt(walletString, pbkdf2Key);
// Stockage dans le store wallet
const walletObject = {
pre_id: '1',
encrypted_device: encryptedDevice,
encrypted_wallet: encryptedWallet
};
```
**Store :** `wallet`
**Clé :** `pre_id` ("1")
**Valeur :** Objet contenant uniquement des données chiffrées
### 4. Configuration de la Date Anniversaire
**Fichier :** `src/pages/birthday-setup/birthday-setup.ts`
#### 4.1 Vérification des Prérequis
Avant de procéder, la page vérifie que tous les prérequis sont remplis :
```typescript
// Vérification de la clé PBKDF2 dans le store pbkdf2keys
const securityModes = ['none', 'otp', 'password', 'os', 'proton-pass'];
let pbkdf2KeyFound = false;
for (const mode of securityModes) {
const hasKey = await secureCredentials.hasPBKDF2Key(mode);
if (hasKey) {
const key = await secureCredentials.retrievePBKDF2Key(mode);
if (key) {
pbkdf2KeyFound = true;
break;
}
}
}
// Vérification du wallet en base de données (avec retry pour synchronisation)
let wallet = await services.getDeviceFromDatabase();
if (!wallet) {
// Retry jusqu'à 5 tentatives avec délai de 500ms
for (let attempt = 0; attempt < 5; attempt++) {
await new Promise(resolve => setTimeout(resolve, 500));
wallet = await services.getDeviceFromDatabase();
if (wallet) break;
}
}
// Vérification que le wallet contient les données attendues
if (wallet.sp_wallet && wallet.sp_wallet.birthday !== undefined) {
console.log('✅ Wallet found in database with birthday:', wallet.sp_wallet.birthday);
} else {
throw new Error('Wallet found but missing required data');
}
```
**Points importants :**
- Vérification réelle de la présence de la clé PBKDF2 dans le store `pbkdf2keys`
- Vérification réelle du wallet en base avec retry pour gérer les problèmes de synchronisation
- Validation que le wallet contient bien les données attendues (`sp_wallet`, `birthday`)
#### 4.2 Connexion aux Relais
```typescript
// Connexion aux relais Bitcoin
await services.connectAllRelays();
// Vérification que les relais sont connectés en vérifiant chain_tip
const currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight !== -1) {
console.log('✅ Relays connected successfully, chain_tip:', currentBlockHeight);
} else {
throw new Error('Relays connected but chain_tip not set');
}
// Vérification que le handshake a été reçu
if (currentBlockHeight > 0) {
console.log('✅ Communication handshake completed, chain_tip:', currentBlockHeight);
} else {
throw new Error('Handshake not received or chain_tip not set');
}
```
**Vérifications réelles :**
- Vérification que `chain_tip` est défini (valeur != -1)
- Vérification que `chain_tip` est positif (indique que le handshake a été reçu)
#### 4.3 Mise à Jour de la Date Anniversaire
**Fichier :** `src/services/service.ts``updateDeviceBlockHeight()`
```typescript
// Mise à jour du birthday du device
await services.updateDeviceBlockHeight();
```
**Processus interne avec vérifications réelles :**
1. **Restauration en mémoire** :
```typescript
this.sdkClient.restore_device(device);
// Vérification que le device a été restauré en mémoire
const restoredDevice = this.dumpDeviceFromMemory();
if (restoredDevice?.sp_wallet?.birthday === device.sp_wallet.birthday) {
console.log('✅ Device restored in memory with updated birthday:', device.sp_wallet.birthday);
} else {
throw new Error('Device restoration failed');
}
```
2. **Sauvegarde en base de données** :
```typescript
await this.saveDeviceInDatabase(device);
// Vérification que le device a été sauvegardé en base de données
const savedDevice = await this.getDeviceFromDatabase();
if (savedDevice?.sp_wallet?.birthday === device.sp_wallet.birthday) {
console.log('✅ Device saved to database with updated birthday:', device.sp_wallet.birthday);
} else {
throw new Error('Device save verification failed');
}
```
3. **Vérification du scan initial** :
```typescript
await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL);
// Vérification que le scan est terminé en vérifiant last_scan
const deviceAfterScan = this.dumpDeviceFromMemory();
if (deviceAfterScan?.sp_wallet?.last_scan === this.currentBlockHeight) {
console.log('✅ Initial scan completed for new wallet');
} else {
console.warn('⚠️ Initial scan may not be complete');
}
```
4. **Vérification finale** :
```typescript
// Sauvegarde finale avec last_scan mis à jour
await this.saveDeviceInDatabase(device);
// Vérification que le device a été sauvegardé avec last_scan mis à jour
const finalDevice = await this.getDeviceFromDatabase();
if (finalDevice?.sp_wallet?.last_scan === this.currentBlockHeight) {
console.log('✅ New wallet initial scan completed and saved');
} else {
throw new Error('Final save verification failed');
}
```
**Vérification dans birthday-setup.ts :**
```typescript
// Vérifier que le birthday a bien été mis à jour en récupérant le wallet depuis la base
const updatedWallet = await services.getDeviceFromDatabase();
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
console.log('✅ Birthday updated successfully:', updatedWallet.sp_wallet.birthday);
} else {
throw new Error('Birthday update verification failed');
}
```
#### 4.4 Sauvegarde du Device avec Vérification
**Fichier :** `src/services/service.ts``saveDeviceInDatabase()`
La méthode `saveDeviceInDatabase()` effectue maintenant une vérification réelle après la sauvegarde :
```typescript
// Sauvegarde du wallet chiffré
const putRequest = store.put(walletObject);
putRequest.onsuccess = () => {
console.log('✅ Device saved to database with encryption');
// La vérification se fera dans transaction.oncomplete
};
transaction.oncomplete = async () => {
// Vérifier que le wallet a bien été sauvegardé en le récupérant depuis la base
const verificationDb = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const verificationTx = verificationDb.transaction([walletStore], 'readonly');
const verifyRequest = verificationStore.get('1');
verifyRequest.onsuccess = () => {
const savedData = verifyRequest.result;
if (savedData && savedData.encrypted_device === encryptedDevice) {
console.log('✅ Verified: Device correctly saved in database');
resolve();
} else {
throw new Error('Device save verification failed');
}
};
};
```
**Points importants :**
- Vérification réelle après la transaction pour confirmer que les données sont bien sauvegardées
- Comparaison de `encrypted_device` pour s'assurer que les données sont correctes
- Logs de succès uniquement après vérification réelle
#### 4.5 Redirection vers la Synchronisation des Blocs
Après la mise à jour réussie du birthday, l'application redirige vers la page de synchronisation des blocs :
```typescript
// Redirection vers la page de synchronisation des blocs
window.location.href = '/src/pages/block-sync/block-sync.html';
```
**Page :** `src/pages/block-sync/block-sync.html`
- Interface dédiée pour la synchronisation des blocs
- Affichage de la progression de la synchronisation
- Gestion de la synchronisation initiale du wallet avec le réseau Bitcoin
### 5. Processus de Pairing
**Fichier :** `src/pages/home/home.ts``handleMainPairing()`
#### 5.1 Vérification du Mode de Sécurité
```typescript
const currentMode = await securityModeService.getCurrentMode();
if (!currentMode) {
// Afficher le sélecteur de mode de sécurité
await showSecurityModeSelector();
return;
}
```
#### 5.2 Authentification selon le Mode
##### Mode `proton-pass` et `os`
```typescript
// Authentification WebAuthn
const credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(32),
allowCredentials: [{
id: credentialId,
type: 'public-key'
}]
}
});
```
##### Mode `otp`
```typescript
// Demande du code OTP
const otpCode = await this.promptForOTPCode();
// Vérification du code OTP
const isValid = await this.verifyOTPCode(otpCode, otpSecret);
```
##### Mode `password`
```typescript
// Demande du mot de passe
const password = await this.promptForPassword();
// Déchiffrement de la clé PBKDF2
const pbkdf2Key = await encryptionService.decrypt(encryptedKey, password);
```
##### Mode `none`
```typescript
// Déchiffrement avec la clé en dur
const pbkdf2Key = await encryptionService.decrypt(encryptedKey, hardcodedKey);
```
#### 5.3 Génération des Credentials de Pairing
```typescript
// Génération des credentials sécurisés
const credentialData = await secureCredentialsService.generateSecureCredentials('4nk-secure-password');
// Stockage des credentials dans le store credentials
await secureCredentialsService.storeCredentials(credentialData, '');
// Récupération et déchiffrement des credentials
const retrievedCredentials = await secureCredentialsService.retrieveCredentials('');
```
#### 5.4 Création du Processus de Pairing
```typescript
// Création du processus via le SDK
const pairingResult = await services.createPairingProcess({
spendKey: retrievedCredentials.spendKey,
scanKey: retrievedCredentials.scanKey
});
```
### 6. Récupération des Processus
**Fichier :** `src/services/service.ts``restoreProcessesFromDB()`
#### 6.1 Synchronisation des Processus
```typescript
// Récupération des processus depuis la base de données
const processes = await processRepo.getAllProcesses();
// Synchronisation avec le réseau
for (const process of processes) {
await services.syncProcess(process);
}
```
#### 6.2 Mise à Jour de l'État de Pairing
```typescript
// Vérification de l'état de pairing
const isPaired = services.isPaired();
if (isPaired) {
// Redirection vers la page account
await navigate('account');
}
```
## Diagramme de Flux
```mermaid
graph TD
A[Démarrage Application] --> B{Vérification État Storage}
B -->|Aucune config| C[Security Setup]
B -->|PBKDF2 existe| D[Wallet Setup]
B -->|Wallet existe| E[Birthday Setup]
B -->|Birthday configuré| F[Block Sync]
B -->|Appareil appairé| G[Account]
C --> C1[Sélection Mode Sécurité]
C1 --> C2[Génération Clé PBKDF2]
C2 --> C3[Stockage selon Mode]
C3 --> D
D --> D1[Récupération Clé PBKDF2]
D1 --> D2[Création Device SDK]
D2 --> D3[Génération Wallet]
D3 --> D4[Stockage Chiffré avec Vérification]
D4 --> E
E --> E1[Vérification Prérequis]
E1 --> E2{Prérequis OK?}
E2 -->|Non| E3[Redirection vers Setup Précédent]
E2 -->|Oui| E4[Connexion Relais]
E4 --> E5[Vérification Handshake]
E5 --> E6[Mise à Jour Birthday]
E6 --> E7[Vérification Sauvegarde]
E7 --> E8[Vérification Birthday]
E8 --> F
F --> F1[Synchronisation Blocs]
F1 --> F2[Initialisation Services]
F2 --> F3[Scan des Blocs]
F3 --> F4[Mise à Jour last_scan]
F4 --> G
G --> G1[Pairing]
G1 --> G2[Authentification Mode]
G2 --> G3[Génération Credentials]
G3 --> G4[Création Processus Pairing]
G4 --> G5[Récupération Processus]
```
## Sécurité par Mode
### Mode `proton-pass` et `os`
- **Stockage :** Clé PBKDF2 chiffrée avec WebAuthn
- **Authentification :** Biométrique (empreinte, visage)
- **Sécurité :** Élevée (clé matérielle)
### Mode `otp`
- **Stockage :** Clé PBKDF2 en clair
- **Authentification :** Code OTP temporaire
- **Sécurité :** Élevée (authentification à deux facteurs)
### Mode `password`
- **Stockage :** Clé PBKDF2 chiffrée avec mot de passe utilisateur
- **Authentification :** Mot de passe utilisateur
- **Sécurité :** Faible (dépend de la force du mot de passe)
### Mode `none`
- **Stockage :** Clé PBKDF2 chiffrée avec clé en dur
- **Authentification :** Aucune
- **Sécurité :** Critique (non recommandé)
## Gestion des Erreurs
### Erreurs de Chiffrement
- **Clé PBKDF2 introuvable** → Redirection vers `security-setup`
- **Échec de déchiffrement** → Demande de réauthentification
- **Wallet corrompu** → Recréation du wallet
### Erreurs de Réseau
- **Connexion relais échouée** → Retry automatique
- **Synchronisation échouée** → Mode hors ligne
- **Pairing échoué** → Nouvelle tentative
### Erreurs d'Authentification
- **WebAuthn échoué** → Fallback vers autre mode
- **OTP invalide** → Nouvelle demande
- **Mot de passe incorrect** → Nouvelle tentative
### Erreurs de Vérification
- **Vérification des prérequis échouée** → Redirection vers l'étape appropriée
- **Vérification de sauvegarde échouée** → Retry de la sauvegarde avec logs détaillés
- **Vérification de restauration échouée** → Retry de la restauration avec logs détaillés
- **Vérification de handshake échouée** → Retry de la connexion avec logs détaillés
## Système de Vérification Réelle des Logs
Tous les logs de succès sont maintenant émis uniquement après vérification réelle des résultats. Cela permet de :
1. **Détecter les échecs silencieux** : Les opérations qui échouent sans erreur sont détectées par les vérifications
2. **Avoir des logs fiables** : Les logs reflètent la réalité et non juste des déclarations
3. **Faciliter le diagnostic** : Les logs indiquent précisément où et pourquoi un processus échoue
### Vérifications Implémentées
#### Dans `birthday-setup.ts`
- ✅ Vérification réelle de la présence de la clé PBKDF2 dans le store `pbkdf2keys`
- ✅ Vérification réelle du wallet en base avec retry pour gérer les problèmes de synchronisation
- ✅ Validation que le wallet contient bien les données attendues (`sp_wallet`, `birthday`)
- ✅ Vérification que les relais sont connectés en vérifiant `chain_tip`
- ✅ Vérification que le handshake a été reçu (`chain_tip > 0`)
- ✅ Vérification que le birthday a bien été mis à jour en récupérant le wallet depuis la base
#### Dans `updateDeviceBlockHeight()`
- ✅ Vérification que le device est restauré en mémoire en comparant le birthday
- ✅ Vérification que le device est sauvegardé en base en le récupérant après l'opération
- ✅ Vérification que le scan est terminé en vérifiant `last_scan`
- ✅ Vérification que la sauvegarde finale est réussie
#### Dans `saveDeviceInDatabase()`
- ✅ Vérification que le wallet est bien sauvegardé en le récupérant depuis la base après la transaction
- ✅ Comparaison de `encrypted_device` pour confirmer que les données sont correctes
- ✅ Logs de succès uniquement après vérification réelle
### Avantages
- **Fiabilité** : Les logs reflètent la réalité et non juste des déclarations
- **Diagnostic** : Facilite le diagnostic en cas de problème
- **Détection** : Détecte les échecs silencieux qui passeraient inaperçus
- **Traçabilité** : Chaque étape est vérifiée et tracée avec des logs détaillés
## Points d'Attention
1. **Ordre des modes testés** : `['none', 'otp', 'password', 'os', 'proton-pass']`
2. **Store `credentials`** : Utilisé uniquement après pairing
3. **Clé PBKDF2** : Toujours stockée dans `pbkdf2keys` avec `security_mode` comme clé
4. **Wallet** : Toujours stocké chiffré dans le store `wallet`
5. **Redirection automatique** : 3 secondes après création du wallet vers `birthday-setup`
6. **Vérifications réelles** : Tous les logs de succès sont émis uniquement après vérification réelle des résultats
7. **Block Sync** : Nouvelle page intermédiaire entre `birthday-setup` et `pairing` pour la synchronisation des blocs
8. **Prérequis** : Chaque page vérifie ses prérequis en base de données avant de procéder
9. **Synchronisation IndexedDB** : Utilisation directe d'IndexedDB pour éviter les problèmes de synchronisation avec le service worker
10. **Retry automatique** : Retry automatique jusqu'à 5 tentatives avec délai de 500ms pour les vérifications de wallet en base
Cette documentation couvre l'ensemble du processus d'initialisation du système LeCoffre.io, depuis la configuration de sécurité jusqu'au pairing réussi et à la récupération des processus.

View File

@ -265,10 +265,11 @@ La méthode `checkConnections` gère maintenant :
## Recommandations
### Corrections Prioritaires
1. **Corriger le flux du joiner** : Implémenter le même flux de confirmation que le créateur
2. **Unifier les processus** : Le joiner devrait rejoindre un processus existant, pas en créer un nouveau
3. **Synchronisation bidirectionnelle** : Les deux côtés doivent avoir le même niveau de validation
### Améliorations Futures
1. **Tests automatisés** : Implémenter des tests unitaires et d'intégration pour valider le pairing
2. **Monitoring** : Ajouter des métriques pour surveiller les performances du pairing
3. **Documentation** : Maintenir cette documentation à jour avec les évolutions
4. **Optimisation** : Analyser et optimiser les délais de retry si nécessaire
### Tests et Monitoring
1. **Tests** : Tester le pairing entre différents hosts avec les deux flux
@ -276,9 +277,4 @@ La méthode `checkConnections` gère maintenant :
3. **Performance** : Optimiser les délais de retry si nécessaire
4. **Documentation** : Maintenir cette documentation à jour avec les évolutions
### Actions Immédiates
1. **Analyser le flux du joiner** : Comprendre comment il devrait rejoindre un processus existant
2. **Implémenter la cohérence** : Appliquer le même flux de confirmation aux deux côtés
3. **Valider la synchronisation** : S'assurer que les deux côtés ont le même `pairing_process_commitment`
Cette analyse fournit une base solide pour comprendre et maintenir le système de pairing, mais révèle des incohérences importantes qui doivent être corrigées.
Cette analyse fournit une base solide pour comprendre et maintenir le système de pairing. Les corrections majeures ont été implémentées et le système est maintenant opérationnel avec un flux unifié pour le créateur et le joiner.

View File

@ -20,6 +20,9 @@ export default [
'navigator': 'readonly',
'crypto': 'readonly',
'setTimeout': 'readonly',
'clearTimeout': 'readonly',
'setInterval': 'readonly',
'clearInterval': 'readonly',
'alert': 'readonly',
'confirm': 'readonly',
'prompt': 'readonly',
@ -27,9 +30,21 @@ export default [
'localStorage': 'readonly',
'sessionStorage': 'readonly',
'indexedDB': 'readonly',
'IDBDatabase': 'readonly',
'IDBTransaction': 'readonly',
'IDBObjectStore': 'readonly',
'IDBRequest': 'readonly',
'customElements': 'readonly',
'requestAnimationFrame': 'readonly',
'setInterval': 'readonly'
'cancelAnimationFrame': 'readonly',
'performance': 'readonly',
'WebAssembly': 'readonly',
'btoa': 'readonly',
'atob': 'readonly',
'self': 'readonly',
'SharedWorker': 'readonly',
'Worker': 'readonly',
'caches': 'readonly'
}
},
plugins: {
@ -74,13 +89,35 @@ export default [
'no-await-in-loop': 'off' // Permettre await dans les boucles pour l'instant
}
},
{
files: ['**/*.worker.ts', '**/*.worker.js'],
languageOptions: {
globals: {
'self': 'readonly',
'postMessage': 'readonly',
'onmessage': 'readonly',
'importScripts': 'readonly',
'btoa': 'readonly',
'atob': 'readonly',
'crypto': 'readonly',
'console': 'readonly'
}
}
},
{
ignores: [
'dist/',
'node_modules/',
'*.js',
'!eslint.config.js',
'pkg/',
'vite.config.ts'
'vite.config.ts',
'test-browser/',
'logs/',
'coverage/',
'.nyc_output/',
'**/*.test.ts',
'**/*.spec.ts'
]
}
];

13
lint-all.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
# Script pour linter tout le projet et corriger automatiquement les erreurs
set -e
echo "🔍 Running ESLint with --fix option..."
npm run lint
echo ""
echo "✅ Linting completed with auto-fix!"

View File

@ -1,4 +1,3 @@
// import { getCorrectDOM } from '../../utils/html.utils'; // Unused import
import Services from '../../services/service';
import { addressToWords } from '../../utils/sp-address.utils';
@ -649,8 +648,8 @@ export class DeviceManagementComponent extends HTMLElement {
if (file) {
try {
const text = await file.text();
const data = JSON.parse(text);
// Use data variable
const _data = JSON.parse(text);
// Data parsed but not used yet (for future use)
// Import the account data
if (window.importJSON) {

View File

@ -9,5 +9,7 @@ export async function closeLoginModal() {
router.closeLoginModal();
}
/* eslint-disable no-undef */
window.confirmLogin = confirmLogin;
window.closeLoginModal = closeLoginModal;
/* eslint-enable no-undef */

View File

@ -232,7 +232,7 @@ export class SecureCredentialsComponent {
try {
await this.updateUI();
this.showMessage('Credentials actualisés', 'success');
} catch (error) {
} catch (_error) {
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;
@ -222,7 +222,7 @@ export class SecurityModeSelector {
this.container.addEventListener('click', (e) => {
const option = (e.target as HTMLElement).closest('.security-option');
if (option) {
this.selectMode(option.dataset.mode as SecurityMode);
this.selectMode((option as HTMLElement).dataset.mode as SecurityMode);
}
});

View File

@ -112,7 +112,7 @@
<div class="progress-bar" id="progressBar"></div>
</div>
<button class="continue-btn" id="continueBtn" disabled>Terminer l'initialisation</button>
<button class="continue-btn" id="continueBtn" disabled>Synchroniser les blocs</button>
</div>
<script type="module" src="./birthday-setup.ts"></script>

View File

@ -3,6 +3,8 @@
* Mise à jour de la date anniversaire et scan des blocs
*/
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
document.addEventListener('DOMContentLoaded', async () => {
console.log('🎂 Birthday setup page loaded');
@ -26,61 +28,135 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!Services) {
throw new Error('Services class not found in default export');
}
console.log('🔄 Waiting for services to be ready...');
// Attendre que les services soient initialisés
let attempts = 0;
const maxAttempts = 30;
const delayMs = 2000;
console.log('🔄 Getting existing services instance...');
// Utiliser l'instance existante des services avec gestion des erreurs de mémoire
let services;
while (attempts < maxAttempts) {
try {
console.log(`🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...`);
services = await Services.getInstance();
console.log('✅ Services initialized successfully');
break;
console.log('✅ Services instance obtained successfully');
} catch (error) {
console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error.message);
attempts++;
if (attempts >= maxAttempts) {
throw new Error(`Services failed to initialize after ${maxAttempts} attempts.`);
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
console.error('🚫 Memory error detected');
updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error');
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
}
await new Promise(resolve => setTimeout(resolve, delayMs));
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) {
console.log('⚠️ PBKDF2 key not found in pbkdf2keys store, redirecting to security-setup...');
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) {
console.log('⚠️ Wallet still not found after retries, redirecting to wallet-setup...');
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) {
console.log('✅ Wallet found in database with birthday:', wallet.sp_wallet.birthday);
} 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) {
console.log('✅ All prerequisites verified');
} else {
throw new Error('Prerequisites verification failed');
}
// Connexion aux relais
await services.connectAllRelays();
console.log('✅ Relays connected successfully');
// Étape 2: Mise à jour de la date anniversaire
updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
// 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 les relais soient prêts
await services.getRelayReadyPromise();
console.log('✅ Communication handshake completed');
// 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) {
console.log(`✅ Block height set from handshake: ${blockHeight}`);
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) {
console.log('✅ Relays connected successfully, chain_tip:', currentBlockHeight);
console.log('✅ Communication handshake completed, chain_tip:', currentBlockHeight);
} else {
throw new Error('Handshake not received or chain_tip not set');
}
// Mettre à jour la date anniversaire du wallet
await services.updateDeviceBlockHeight();
console.log('✅ Birthday updated successfully');
// Étape 3: Scan des blocs
updateStatus('🔍 Scan des blocs en cours...', 'loading');
updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
updateProgress(60);
console.log('🔄 Calling updateDeviceBlockHeight()...');
await services.updateDeviceBlockHeight();
console.log('✅ updateDeviceBlockHeight() completed successfully');
// Effectuer le scan initial des blocs
await services.ensureCompleteInitialScan();
console.log('✅ Initial block scan completed');
// 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);
console.log('🔄 Verifying birthday update...');
const updatedWallet = await services.getDeviceFromDatabase();
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
console.log('✅ Birthday updated successfully:', updatedWallet.sp_wallet.birthday);
} else {
console.error('❌ Birthday update verification failed:', {
birthday: updatedWallet?.sp_wallet?.birthday,
hasSpWallet: !!updatedWallet?.sp_wallet
});
throw new Error(`Birthday update verification failed: expected birthday > 0, got ${updatedWallet?.sp_wallet?.birthday}`);
}
// Étape 4: Synchronisation des processus
updateStatus('🔄 Synchronisation des processus...', 'loading');
updateProgress(80);
// Redirection vers la page de synchronisation des blocs
updateStatus('🔄 Redirection vers la synchronisation des blocs...', 'loading');
updateProgress(100);
// Restaurer les processus depuis la base de données
await services.restoreProcessesFromDB();
console.log('✅ Process synchronization completed');
console.log('🎉 Birthday setup completed successfully - redirecting to block sync');
console.log('📍 Target URL: /src/pages/block-sync/block-sync.html');
console.log('⏰ Redirecting in 1 second...');
// Rediriger vers la page de synchronisation des blocs
setTimeout(() => {
console.log('🔄 Executing redirect now...');
window.location.href = '/src/pages/block-sync/block-sync.html';
}, 1000);
} catch (error) {
console.error('❌ Services not available:', error);
@ -88,22 +164,13 @@ document.addEventListener('DOMContentLoaded', async () => {
throw error;
}
// Étape 5: Finalisation
updateStatus('✅ Configuration terminée avec succès!', 'success');
updateProgress(100);
// Activer le bouton continuer
continueBtn.disabled = false;
console.log('🎉 Birthday setup completed successfully');
} catch (error) {
console.error('❌ Error during birthday setup:', error);
updateStatus('❌ Erreur lors de la configuration de la date anniversaire', 'error');
}
// Gestion du bouton continuer
continueBtn.addEventListener('click', () => {
continueBtn.addEventListener('click', async () => {
console.log('🏠 Redirecting to main application...');
// Rediriger vers l'application principale
console.log('🎂 Birthday setup completed, checking storage state...');

View File

@ -0,0 +1,189 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Synchronisation des Blocs - LeCoffre</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: white;
border-radius: 12px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
max-width: 600px;
width: 100%;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
}
.status {
text-align: center;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.status.loading {
background: #e3f2fd;
color: #1976d2;
}
.status.success {
background: #e8f5e8;
color: #2e7d32;
}
.status.error {
background: #ffebee;
color: #c62828;
}
.progress {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin: 20px 0;
}
.progress-bar {
height: 100%;
background: #667eea;
width: 0%;
transition: width 0.3s ease;
}
.sync-details {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
text-align: left;
}
.sync-details h3 {
margin-top: 0;
color: #495057;
text-align: center;
margin-bottom: 15px;
}
.sync-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid #e9ecef;
}
.sync-item:last-child {
border-bottom: none;
}
.sync-status {
font-weight: bold;
}
.sync-status.pending {
color: #ffc107;
}
.sync-status.completed {
color: #28a745;
}
.sync-status.error {
color: #dc3545;
}
.continue-btn {
width: 100%;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
padding: 15px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
margin-top: 20px;
transition: background 0.3s ease;
}
.continue-btn:hover:not(:disabled) {
background: #5a6fd8;
}
.continue-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>🔄 Synchronisation des Blocs</h1>
<p class="subtitle">Synchronisation avec le réseau Bitcoin pour récupérer l'historique des transactions</p>
<div class="status loading" id="status">
🔄 Initialisation de la synchronisation...
</div>
<div class="progress">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="sync-details">
<h3>📊 Détails de la synchronisation</h3>
<div class="sync-item">
<span>Hauteur de bloc actuelle:</span>
<span id="currentBlock" class="sync-status pending">En attente...</span>
</div>
<div class="sync-item">
<span>Date anniversaire:</span>
<span id="birthday" class="sync-status pending">En attente...</span>
</div>
<div class="sync-item">
<span>Blocs à scanner:</span>
<span id="blocksToScan" class="sync-status pending">En attente...</span>
</div>
<div class="sync-item">
<span>Blocs scannés:</span>
<span id="blocksScanned" class="sync-status pending">0</span>
</div>
<div class="sync-item">
<span>Transactions trouvées:</span>
<span id="transactionsFound" class="sync-status pending">0</span>
</div>
</div>
<button id="continueBtn" class="continue-btn" disabled>
Aller au pairing
</button>
</div>
<script type="module" src="./block-sync.ts"></script>
</body>
</html>

View File

@ -0,0 +1,271 @@
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
document.addEventListener("DOMContentLoaded", async () => {
console.log("🔄 Block sync page loaded");
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 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 () => {
console.log('🔗 Redirecting to pairing page...');
// Rediriger vers la page de pairing
window.location.href = '/src/pages/home/home.html';
});
}
try {
// Étape 1: Vérification des prérequis
updateStatus('🔍 Vérification des prérequis...', 'loading');
updateProgress(10);
// Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique)
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
console.log('⚠️ PBKDF2 key not found in pbkdf2keys store, redirecting to security-setup...');
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) {
console.log('⚠️ Wallet still not found after retries, redirecting to wallet-setup...');
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) {
console.log('✅ Wallet found in database with birthday:', wallet.sp_wallet.birthday);
} else {
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
}
// Étape 2: Initialisation des services
updateStatus('🔄 Initialisation des services...', 'loading');
updateProgress(20);
const { default: Services } = await import('../../services/service');
if (!Services) {
throw new Error('Services class not found in default export');
}
console.log('🔄 Waiting for services to be ready...');
let attempts = 0;
const maxAttempts = 30;
const delayMs = 2000;
let services;
while (attempts < maxAttempts) {
try {
console.log(`🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...`);
services = await Services.getInstance();
console.log('✅ Services initialized successfully');
break;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, errorMessage);
// Si c'est une erreur de mémoire, arrêter immédiatement
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
console.error('🚫 Memory error detected - stopping retry attempts');
updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error');
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
}
attempts++;
if (attempts >= maxAttempts) {
throw new Error(`Services failed to initialize after ${maxAttempts} attempts.`);
}
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
if (!services) {
throw new Error('Services not initialized');
}
// Vérifier que le birthday est configuré (> 0)
if (!wallet.sp_wallet.birthday || wallet.sp_wallet.birthday === 0) {
console.log('⚠️ Birthday not configured (birthday = 0), redirecting to birthday-setup...');
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 1000);
return;
}
console.log('✅ All prerequisites verified for block sync');
// Étape 3: Connexion aux relais si nécessaire
updateStatus('🔗 Connexion aux relais...', 'loading');
updateProgress(40);
let currentBlockHeight = services.getCurrentBlockHeight();
if (currentBlockHeight === -1 || currentBlockHeight === 0) {
console.log('⚠️ Block height not available, connecting to relays...');
await services.connectAllRelays();
// 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) {
console.log(`✅ Block height set from handshake: ${blockHeight}`);
currentBlockHeight = blockHeight;
clearTimeout(timeout);
resolve();
} else {
setTimeout(checkBlockHeight, 100);
}
};
checkBlockHeight();
});
}
// Étape 4: Récupération des informations de synchronisation
updateStatus('📊 Récupération des informations de synchronisation...', 'loading');
updateProgress(50);
const birthday = wallet.sp_wallet.birthday;
const lastScan = wallet.sp_wallet.last_scan || birthday;
const blocksToScan = currentBlockHeight - lastScan;
// Mettre à jour l'interface avec les informations
updateSyncItem('currentBlock', currentBlockHeight.toString(), 'completed');
updateSyncItem('birthday', birthday.toString(), 'completed');
updateSyncItem('blocksToScan', blocksToScan.toString(), 'pending');
console.log(`📊 Sync info: current=${currentBlockHeight}, birthday=${birthday}, lastScan=${lastScan}, toScan=${blocksToScan}`);
// Vérifier si une synchronisation est nécessaire
if (blocksToScan <= 0) {
console.log('✅ Wallet already synchronized');
updateStatus('✅ Wallet déjà synchronisé!', 'success');
updateProgress(100);
updateSyncItem('blocksScanned', lastScan.toString(), 'completed');
updateSyncItem('blocksToScan', '0', 'completed');
// Redirection automatique après 3 secondes
updateStatus('✅ Redirection automatique vers le pairing dans 3 secondes...', 'success');
continueBtn.disabled = false;
setTimeout(() => {
console.log('🔗 Auto-redirecting to pairing page...');
window.location.href = '/src/pages/home/home.html';
}, 3000);
return;
}
// Étape 5: Synchronisation réelle des blocs
updateStatus('🔍 Synchronisation des blocs en cours...', 'loading');
updateProgress(60);
console.log(`🔄 Starting real block scan from ${lastScan} to ${currentBlockHeight}...`);
// Utiliser updateDeviceBlockHeight qui gère la synchronisation si nécessaire
// Cette méthode vérifie si last_scan < currentBlockHeight et synchronise si nécessaire
try {
await services.updateDeviceBlockHeight();
console.log('✅ Block scan completed successfully');
// Vérifier que la mise à jour a été sauvegardée
const finalWallet = await services.getDeviceFromDatabase();
if (finalWallet?.sp_wallet?.last_scan) {
const finalLastScan = finalWallet.sp_wallet.last_scan;
console.log('✅ Wallet updated with last_scan:', finalLastScan);
// Finalisation
updateStatus('✅ Synchronisation terminée avec succès!', 'success');
updateProgress(100);
updateSyncItem('blocksScanned', finalLastScan.toString(), 'completed');
updateSyncItem('blocksToScan', blocksToScan.toString(), 'completed');
// Activer le bouton continuer
if (continueBtn) {
continueBtn.disabled = false;
}
console.log('🎉 Block sync completed successfully');
// Redirection automatique après 3 secondes
updateStatus('✅ Redirection automatique vers le pairing dans 3 secondes...', 'success');
setTimeout(() => {
console.log('🔗 Auto-redirecting to pairing page...');
window.location.href = '/src/pages/home/home.html';
}, 3000);
} else {
throw new Error('Failed to verify wallet update - last_scan not found');
}
} catch (error) {
console.error('❌ Error during block scan:', error);
throw error;
}
} catch (error) {
console.error('❌ Error during block sync:', error);
const errorMessage = (error as Error).message;
updateStatus(`❌ Erreur: ${errorMessage}`, 'error');
// Si l'erreur est liée aux prérequis, rediriger vers la page appropriée
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
console.log('⚠️ Security error detected, redirecting to security-setup...');
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 2000);
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
console.log('⚠️ Wallet error detected, redirecting to wallet-setup...');
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 2000);
} else if (errorMessage.includes('birthday')) {
console.log('⚠️ Birthday error detected, redirecting to birthday-setup...');
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 2000);
} else {
// Erreur générale, afficher et permettre de réessayer
updateSyncItem('currentBlock', 'Erreur', 'error');
updateSyncItem('birthday', 'Erreur', 'error');
updateSyncItem('blocksToScan', 'Erreur', 'error');
}
}
});

View File

@ -1,8 +1,11 @@
import Services from '../../services/service';
import { DeviceReaderService } from '../../services/device-reader.service';
import { SecureCredentialsService } from '../../services/secure-credentials.service';
import { SecurityModeService } from '../../services/security-mode.service';
import { addSubscription } from '../../utils/subscription.utils';
import { displayEmojis, generateCreateBtn, addressToEmoji, 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';
// Extend WindowEventMap to include custom events
declare global {
@ -24,7 +27,52 @@ export async function initHomePage(): Promise<void> {
}
isInitializing = true;
console.log('INIT-HOME');
console.log('🏠 Home/Pairing page loaded');
// Vérifier les prérequis en base de données
console.log('🔍 Verifying prerequisites...');
try {
console.log('🔧 Getting device reader service...');
const deviceReader = DeviceReaderService.getInstance();
// Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique) dans le store pbkdf2keys
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
console.log('⚠️ PBKDF2 key not found in pbkdf2keys store, redirecting to security-setup...');
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) {
console.log('⚠️ Wallet still not found after retries, redirecting to wallet-setup...');
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) {
console.log('✅ Wallet found in database with birthday:', wallet.sp_wallet.birthday);
} else {
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
}
// Vérifier que le birthday est configuré (> 0)
if (!wallet.sp_wallet.birthday || wallet.sp_wallet.birthday === 0) {
console.log('⚠️ Birthday not configured (birthday = 0), redirecting to birthday-setup...');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 1000);
return;
}
console.log('✅ All prerequisites verified for pairing page');
// No loading spinner - let the interface load naturally
@ -61,50 +109,30 @@ export async function initHomePage(): Promise<void> {
});
});
try {
console.log('🔧 Getting services instance...');
const service = await Services.getInstance();
// D'abord vérifier la sécurité avant de créer le wallet
console.log('🔐 Checking security configuration...');
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
// Les prérequis sont vérifiés, le wallet existe et le birthday est configuré
// Maintenant vérifier si les credentials de pairing existent
console.log('🔐 Checking pairing credentials...');
const secureCredentialsService = SecureCredentialsService.getInstance();
const hasCredentials = await secureCredentialsService.hasCredentials();
if (!hasCredentials) {
console.log('🔐 No security credentials found, user must configure security first...');
// Afficher le sélecteur de mode de sécurité
console.log('🔐 No pairing credentials found, user must authenticate...');
// Afficher le sélecteur de mode de sécurité si nécessaire ou déclencher l'authentification
await handleMainPairing();
return;
}
// Check if wallet exists, create if not
console.log('🔍 Checking for existing wallet...');
const existingDevice = await service.getDeviceFromDatabase();
if (!existingDevice) {
console.log('📱 No wallet found, creating new device...');
const spAddress = await service.createNewDevice();
console.log('✅ New device created with address:', spAddress);
// Verify wallet was created successfully
const verifyDevice = await service.getDeviceFromDatabase();
if (!verifyDevice) {
throw new Error('Failed to create wallet - device not found after creation');
}
console.log('✅ Wallet creation verified');
} else {
console.log('📱 Existing wallet found');
// Wallet existe et credentials existent, procéder avec le pairing
console.log('✅ Wallet and credentials verified, proceeding with pairing...');
console.log('🔍 Wallet details:', {
hasSpendKey: !!existingDevice.sp_wallet?.spend_key,
hasScanKey: !!existingDevice.sp_wallet?.scan_key,
birthday: existingDevice.sp_wallet?.birthday
hasSpendKey: !!wallet.sp_wallet?.spend_key,
hasScanKey: !!wallet.sp_wallet?.scan_key,
birthday: wallet.sp_wallet?.birthday
});
}
// Trigger WebAuthn authentication
console.log('🔐 Triggering WebAuthn authentication...');
// Trigger WebAuthn authentication pour déchiffrer les credentials existants
console.log('🔐 Triggering WebAuthn authentication to decrypt credentials...');
await handleMainPairing();
// Attendre que les credentials soient réellement disponibles avant de continuer
@ -116,14 +144,17 @@ export async function initHomePage(): Promise<void> {
console.log('🔧 Getting device address...');
try {
const spAddress = await service.getDeviceAddress();
const spAddress = await deviceReader.getDeviceAddress();
if (!spAddress) {
throw new Error('Device address not found');
}
console.log('🔧 Generating create button...');
generateCreateBtn();
console.log('🔧 Displaying emojis...');
displayEmojis(spAddress);
} catch (error) {
console.error('❌ Failed to get device address:', error);
if (error.message.includes('Wallet keys not available')) {
if ((error as Error).message.includes('Wallet keys not available')) {
console.error('❌ Wallet keys not available - authentication failed');
throw new Error('Authentication failed - wallet keys not available');
}
@ -132,7 +163,34 @@ export async function initHomePage(): Promise<void> {
console.log('✅ Home page initialization completed');
} catch (error) {
console.error('❌ Error initializing home page:', error);
console.error('❌ Error initializing home/pairing page:', error);
// Afficher un message d'erreur à l'utilisateur
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
const mainStatus = container.querySelector('#main-status') as HTMLElement;
if (mainStatus) {
mainStatus.innerHTML = `<span style="color: var(--error-color)">❌ Error: ${(error as Error).message}</span>`;
}
// Si l'erreur est liée aux prérequis, rediriger vers la page appropriée
const errorMessage = (error as Error).message;
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
console.log('⚠️ Security error detected, redirecting to security-setup...');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 2000);
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
console.log('⚠️ Wallet error detected, redirecting to wallet-setup...');
setTimeout(() => {
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
}, 2000);
} else if (errorMessage.includes('birthday')) {
console.log('⚠️ Birthday error detected, redirecting to birthday-setup...');
setTimeout(() => {
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 2000);
}
throw error;
} finally {
isInitializing = false;
@ -587,10 +645,11 @@ async function showSecurityModeSelector(): Promise<void> {
// Sélection d'un mode
options.forEach(option => {
const htmlOption = option as HTMLElement;
option.addEventListener('click', () => {
options.forEach(opt => opt.style.borderColor = '#e1e8ed');
option.style.borderColor = '#27ae60';
option.style.background = '#f8fff8';
options.forEach(opt => (opt as HTMLElement).style.borderColor = '#e1e8ed');
htmlOption.style.borderColor = '#27ae60';
htmlOption.style.background = '#f8fff8';
selectedMode = option.getAttribute('data-mode');
confirmBtn.disabled = false;
confirmBtn.style.opacity = '1';
@ -598,13 +657,13 @@ async function showSecurityModeSelector(): Promise<void> {
// Effet hover
option.addEventListener('mouseenter', () => {
if (option.style.borderColor !== '#27ae60') {
option.style.borderColor = '#3498db';
if (htmlOption.style.borderColor !== '#27ae60') {
htmlOption.style.borderColor = '#3498db';
}
});
option.addEventListener('mouseleave', () => {
if (option.style.borderColor !== '#27ae60') {
option.style.borderColor = '#e1e8ed';
if (htmlOption.style.borderColor !== '#27ae60') {
htmlOption.style.borderColor = '#e1e8ed';
}
});
});
@ -615,7 +674,6 @@ async function showSecurityModeSelector(): Promise<void> {
console.log(`🔐 Security mode selected: ${selectedMode}`);
// Vérifier si le mode nécessite une confirmation
const { SecurityModeService } = await import('../../services/security-mode.service');
const securityModeService = SecurityModeService.getInstance();
const modeConfig = securityModeService.getSecurityModeConfig(selectedMode as any);
@ -789,7 +847,6 @@ async function waitForCredentialsAvailability(): Promise<void> {
isWaitingForCredentials = true;
try {
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
let attempts = 0;
@ -883,7 +940,6 @@ async function handleMainPairing(): Promise<void> {
}
// Import and trigger authentication with selected mode
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Check if we have existing credentials (regardless of wallet existence)
@ -1105,9 +1161,7 @@ async function handleDeleteAccount(): Promise<void> {
mainStatus.innerHTML = '<div class="spinner"></div><span>Deleting account and all data...</span>';
}
// Get services
const service = await Services.getInstance();
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
// Delete credentials
const secureCredentialsService = SecureCredentialsService.getInstance();
// Delete all credentials

View File

@ -88,10 +88,9 @@ document.addEventListener('DOMContentLoaded', async () => {
const pbkdf2Key = await secureCredentialsService.generatePBKDF2Key(selectedMode);
console.log('✅ PBKDF2 key generated and stored securely');
// Rediriger vers la page de génération du wallet
console.log('🔐 Security setup completed, checking storage state...');
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
// Rediriger directement vers la page de génération du wallet
console.log('🔐 Security setup completed, redirecting to wallet-setup...');
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
} catch (error) {
console.error('❌ PBKDF2 key generation failed:', error);

View File

@ -4,6 +4,7 @@
*/
import { DATABASE_CONFIG } from '../../services/database-config';
import { checkPBKDF2Key } from '../../utils/prerequisites.utils';
document.addEventListener('DOMContentLoaded', async () => {
console.log('💰 Wallet setup page loaded');
@ -47,10 +48,80 @@ document.addEventListener('DOMContentLoaded', async () => {
}
try {
// Étape 1: Initialisation des services
updateStatus('🔄 Initialisation des services...', 'loading');
// Étape 1: Vérifier les prérequis AVANT d'initialiser Services
updateStatus('🔍 Vérification des prérequis...', 'loading');
updateProgress(10);
// CRITICAL: wallet-setup ne doit PAS générer de credentials
// Les credentials et clé PBKDF2 doivent être créés dans security-setup
// On vérifie que la clé PBKDF2 existe dans le store pbkdf2keys
console.log('🔐 Checking for existing PBKDF2 key in pbkdf2keys store...');
const pbkdf2KeyResult = await checkPBKDF2Key();
if (!pbkdf2KeyResult) {
console.log('⚠️ No PBKDF2 key found in pbkdf2keys store, redirecting to security-setup...');
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
setTimeout(() => {
window.location.href = '/src/pages/security-setup/security-setup.html';
}, 1000);
return;
}
const currentMode = pbkdf2KeyResult.mode;
console.log('✅ Prerequisites verified: PBKDF2 key found in pbkdf2keys store for mode:', currentMode);
// Étape 2: Initialisation des services (uniquement si les prérequis sont OK)
updateStatus('🔄 Initialisation des services...', 'loading');
updateProgress(20);
// Vérifier la mémoire avant d'initialiser WebAssembly
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;
console.log(`📊 Current memory usage: ${usedPercent.toFixed(1)}% (${usedMB.toFixed(1)}MB / ${limitMB.toFixed(1)}MB)`);
// Si la mémoire est très élevée (>75%), tenter un nettoyage agressif
if (usedPercent > 75) {
console.warn('⚠️ High memory usage detected, attempting aggressive cleanup...');
updateStatus('🧹 Nettoyage de la mémoire en cours...', 'loading');
// Nettoyage agressif
if (window.gc) {
for (let i = 0; i < 3; i++) {
window.gc();
await new Promise(resolve => setTimeout(resolve, 100));
}
}
// Nettoyer les caches HTTP
if ('caches' in window) {
try {
const cacheNames = await caches.keys();
await Promise.all(cacheNames.map(name => caches.delete(name)));
console.log('🧹 Caches cleared');
} catch (e) {
console.warn('⚠️ Cache cleanup error:', e);
}
}
// Vérifier la mémoire après nettoyage
const memoryAfter = (performance as any).memory;
const usedPercentAfter = (memoryAfter.usedJSHeapSize / memoryAfter.jsHeapSizeLimit) * 100;
console.log(`📊 Memory after cleanup: ${usedPercentAfter.toFixed(1)}% (${(memoryAfter.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB)`);
// Si toujours >90% après nettoyage, avertir l'utilisateur
if (usedPercentAfter > 90) {
console.error('❌ Memory still too high after cleanup');
updateStatus('⚠️ Mémoire trop élevée. Fermez les autres onglets et actualisez la page.', 'error');
alert('⚠️ Mémoire insuffisante détectée.\n\nVeuillez :\n- Fermer les autres onglets du navigateur\n- Actualiser cette page\n\nSi le problème persiste, redémarrez le navigateur.');
return;
}
}
}
let services: any; // Déclarer services au niveau supérieur
try {
@ -78,7 +149,15 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log('✅ Services initialized successfully');
break;
} catch (error) {
console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error instanceof Error ? error.message : String(error));
const errorMessage = error instanceof Error ? error.message : String(error);
console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, errorMessage);
// Si c'est une erreur de mémoire, arrêter immédiatement
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
console.error('🚫 Memory error detected - stopping retry attempts');
updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error');
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
}
// Diagnostic plus détaillé
if (attempts === 5) {
@ -101,24 +180,6 @@ document.addEventListener('DOMContentLoaded', async () => {
throw error;
}
// Étape 2: Génération des credentials sécurisés
updateStatus('🔐 Génération des clés de sécurité...', 'loading');
updateProgress(30);
try {
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Vérifier si des credentials existent déjà
// CRITICAL: wallet-setup ne doit PAS générer de credentials
// Les credentials et clé PBKDF2 doivent être créés dans security-setup
// On vérifie juste que la clé PBKDF2 existe
console.log('🔐 Checking for existing PBKDF2 key...');
} catch (error) {
// Erreur lors de la vérification, on continuera pour tester les clés PBKDF2
console.log(' Could not check credentials status, will test PBKDF2 keys directly');
}
// Étape 3: Sauvegarde du wallet avec état birthday_waiting
updateStatus('💰 Sauvegarde du portefeuille...', 'loading');
updateProgress(60);
@ -126,49 +187,8 @@ document.addEventListener('DOMContentLoaded', async () => {
try {
console.log('🔐 Sauvegarde du wallet avec état birthday_waiting...');
// DÉTECTER le mode de sécurité via la clé PBKDF2 disponible
// Le mode de sécurité est identifié par la clé PBKDF2 qui fonctionne
const { SecureCredentialsService } = await import('../../services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// TEST: Log credentialId from sessionStorage
const storedCredentialId = sessionStorage.getItem('webauthn_credential_id');
if (storedCredentialId) {
console.log('🔐 TEST: credentialId from sessionStorage in wallet-setup:', storedCredentialId);
} else {
console.log('🔐 TEST: No credentialId found in sessionStorage');
}
console.log('🔍 Testing all security modes to find a PBKDF2 key...');
// Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['otp', 'password', 'none', 'os', 'proton-pass'];
let currentMode: string | null = null;
for (const mode of allSecurityModes) {
console.log(`🔍 Testing security mode: ${mode}`);
try {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
currentMode = mode;
console.log(`✅ PBKDF2 key found for security mode: ${mode}`);
break;
}
} catch (error) {
console.log(`⚠️ No PBKDF2 key found for mode ${mode}`);
}
}
// CRITICAL: Si aucune clé PBKDF2 n'est disponible, arrêter immédiatement
if (!currentMode) {
console.error('❌ CRITICAL: No PBKDF2 key found for any security mode.');
console.error('❌ Cannot proceed with wallet creation without encryption key.');
updateStatus('❌ Erreur: Aucune clé de chiffrement trouvée. Veuillez configurer la sécurité.', 'error');
throw new Error('CRITICAL: No PBKDF2 key found. Cannot create wallet without encryption key.');
}
// Le mode de sécurité a déjà été trouvé dans la vérification des prérequis
// currentMode est déjà défini et vérifié
console.log('🔐 Using security mode for wallet encryption:', currentMode);
// Générer un wallet temporaire avec état birthday_waiting
@ -190,7 +210,7 @@ document.addEventListener('DOMContentLoaded', async () => {
// Récupérer la clé PBKDF2 existante pour le mode détecté
// IMPORTANT: Ne PAS générer de nouvelle clé, utiliser celle qui existe
console.log('🔐 Retrieving existing PBKDF2 key for security mode:', currentMode);
const pbkdf2Key = await secureCredentialsService.retrievePBKDF2Key(currentMode as any);
const pbkdf2Key = pbkdf2KeyResult.key;
if (!pbkdf2Key) {
console.error('❌ CRITICAL: Failed to retrieve PBKDF2 key for mode:', currentMode);
updateStatus('❌ Erreur: Impossible de récupérer la clé de chiffrement.', 'error');
@ -284,15 +304,13 @@ document.addEventListener('DOMContentLoaded', async () => {
const walletObject = {
pre_id: '1',
encrypted_device: encryptedDevice, // Device complètement chiffré
encrypted_wallet: encryptedWallet, // Wallet chiffré
security_mode: currentMode // Mode de sécurité utilisé
encrypted_wallet: encryptedWallet // Wallet chiffré
};
console.log('🔍 Attempting to save encrypted wallet object');
console.log('🔐 Object contains only encrypted data:', {
hasEncryptedDevice: !!walletObject.encrypted_device,
hasEncryptedWallet: !!walletObject.encrypted_wallet,
securityMode: walletObject.security_mode,
// Ne pas logger le contenu chiffré
});
@ -334,7 +352,6 @@ document.addEventListener('DOMContentLoaded', async () => {
hasPreId: !!verificationRequest.result.pre_id,
hasEncryptedDevice: !!verificationRequest.result.encrypted_device,
hasEncryptedWallet: !!verificationRequest.result.encrypted_wallet,
hasSecurityMode: !!verificationRequest.result.security_mode,
hasDeviceInClear: !!verificationRequest.result.device // DEVRAIT ÊTRE NULL
} : 'null');
@ -393,14 +410,13 @@ document.addEventListener('DOMContentLoaded', async () => {
console.log('🔍 Wallet contains only encrypted data:', {
hasEncryptedDevice: !!finalVerification.encrypted_device,
hasEncryptedWallet: !!finalVerification.encrypted_wallet,
securityMode: finalVerification.security_mode,
hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE
});
// TEST: Déchiffrer le wallet pour valider que ça fonctionne
console.log('🔐 TEST: Attempting to decrypt wallet to validate encryption...');
try {
const pbkdf2KeyTest = await secureCredentialsService.retrievePBKDF2Key(currentMode as any);
const pbkdf2KeyTest = pbkdf2KeyResult.key;
if (!pbkdf2KeyTest) {
console.error('❌ TEST: Failed to retrieve PBKDF2 key for decryption test');
} else {
@ -449,7 +465,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
// Étape 4: Finalisation
updateStatus('✅ Wallet sauvegardé avec succès!', 'success');
updateStatus('✅ Wallet sauvegardé avec succès! Redirection automatique dans 3 secondes...', 'success');
updateProgress(100);
console.log('🎉 Wallet setup completed successfully - wallet saved with birthday_waiting state');
@ -457,6 +473,13 @@ document.addEventListener('DOMContentLoaded', async () => {
// Activer le bouton continuer
continueBtn.disabled = false;
console.log('✅ Continue button enabled');
// Redirection automatique après 3 secondes si l'utilisateur ne clique pas
setTimeout(() => {
console.log('🔄 Auto-redirecting to birthday setup after timeout...');
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
}, 3000);
} catch (error) {
console.error('❌ Error during wallet setup:', error);
@ -464,10 +487,10 @@ document.addEventListener('DOMContentLoaded', async () => {
}
// Gestion du bouton continuer
continueBtn.addEventListener('click', () => {
console.log('🔗 Redirecting to pairing page...');
console.log('💰 Wallet setup completed, checking storage state...');
const { checkStorageStateAndNavigate } = await import('../../router');
await checkStorageStateAndNavigate();
continueBtn.addEventListener('click', async () => {
console.log('🔗 Redirecting to birthday setup...');
console.log('💰 Wallet setup completed, redirecting to birthday configuration...');
// Rediriger directement vers la page de configuration de la date anniversaire
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
});
});

View File

@ -1,15 +1,14 @@
// CSS is loaded via HTML link tag
/*import { initChat } from '../src/pages/chat/chat';*/
import Database from './services/database.service';
import Services from './services/service';
import TokenService from './services/token';
import { cleanSubscriptions } from './utils/subscription.utils';
import { LoginComponent } from './pages/home/home-component';
// import { prepareAndSendPairingTx } from './utils/sp-address.utils'; // Unused import
import ModalService from './services/modal.service';
import { MessageType } from './models/process.model';
import { splitPrivateData, isValid32ByteHex } from './utils/service.utils';
import { MerkleProofResult } from 'pkg/sdk_client';
import { checkPBKDF2Key } from './utils/prerequisites.utils';
const routes: { [key: string]: string } = {
home: '/src/pages/home/home.html',
@ -20,12 +19,14 @@ const routes: { [key: string]: string } = {
'security-setup': '/src/pages/security-setup/security-setup.html',
'wallet-setup': '/src/pages/wallet-setup/wallet-setup.html',
'birthday-setup': '/src/pages/birthday-setup/birthday-setup.html',
'block-sync': '/src/pages/block-sync/block-sync.html',
};
export let currentRoute = '';
/**
* Vérifie l'état du storage et détermine quelle page afficher selon l'état actuel
* Version légère qui n'initialise pas WebAssembly pour la première visite
* Logique de progression :
* - Si pairing account
* - Si date anniversaire pairing
@ -37,61 +38,41 @@ export async function checkStorageStateAndNavigate(): Promise<void> {
console.log('🔍 Checking storage state to determine next step...');
try {
// Vérifier l'état du pairing
const services = await Services.getInstance();
const isPaired = services.isPaired();
// Utiliser DeviceReaderService pour éviter d'initialiser WebAssembly
const { DeviceReaderService } = await import('./services/device-reader.service');
const deviceReader = DeviceReaderService.getInstance();
if (isPaired) {
console.log('✅ Device is paired, navigating to account page');
await navigate('account');
return;
}
// Vérifier si une clé PBKDF2 existe (vérification la plus légère)
const pbkdf2KeyResult = await checkPBKDF2Key();
// Vérifier si la date anniversaire est configurée (wallet avec birthday > 0)
const device = await services.getDeviceFromDatabase();
if (device && device.sp_wallet && device.sp_wallet.birthday > 0) {
console.log('🎂 Birthday is configured, navigating to pairing');
await navigate('home'); // Pour l'instant, rediriger vers home pour le pairing
if (!pbkdf2KeyResult) {
// Aucune clé PBKDF2 trouvée, commencer par la configuration de sécurité
console.log('🔐 No PBKDF2 key found, navigating to security-setup');
await navigate('security-setup');
return;
}
// Vérifier si le wallet existe (même avec birthday = 0)
if (device && device.sp_wallet) {
console.log('💰 Wallet exists but birthday not set, navigating to birthday-setup');
await navigate('birthday-setup');
return;
}
// Vérifier si une clé PBKDF2 existe
const { SecureCredentialsService } = await import('./services/secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
const allSecurityModes: Array<'otp' | 'password' | 'none' | 'os' | 'proton-pass'> =
['otp', 'password', 'none', 'os', 'proton-pass'];
let hasPBKDF2Key = false;
for (const mode of allSecurityModes) {
try {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
hasPBKDF2Key = true;
console.log(`🔐 PBKDF2 key found for mode: ${mode}`);
break;
}
} catch (error) {
// Mode non disponible, continuer
}
}
if (hasPBKDF2Key) {
console.log('🔐 PBKDF2 key exists, navigating to wallet-setup');
const device = await deviceReader.getDeviceFromDatabase();
if (!device?.sp_wallet) {
console.log('💰 Wallet does not exist, navigating to wallet-setup');
await navigate('wallet-setup');
return;
}
// Aucune clé PBKDF2 trouvée, commencer par la configuration de sécurité
console.log('🔐 No PBKDF2 key found, navigating to security-setup');
await navigate('security-setup');
// Vérifier si la date anniversaire est configurée (wallet avec birthday > 0)
if (device.sp_wallet.birthday && device.sp_wallet.birthday > 0) {
console.log('🎂 Birthday is configured, navigating to pairing');
// Ne pas vérifier le pairing ici pour éviter d'initialiser WebAssembly
// La page home.ts vérifiera le pairing si nécessaire
await navigate('home');
return;
}
// Wallet existe mais birthday pas configuré
console.log('💰 Wallet exists but birthday not set, navigating to birthday-setup');
await navigate('birthday-setup');
return;
} catch (error) {
console.error('❌ Error checking storage state:', error);
@ -129,6 +110,13 @@ async function handleLocation(path: string) {
console.log('📍 Current route set to:', currentRoute);
console.log('📍 Route HTML:', routeHtml);
// Pour les pages de setup, rediriger directement vers la page HTML
if (path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup' || path === 'block-sync') {
console.log('📍 Processing setup route:', path);
window.location.href = routeHtml;
return;
}
const content = document.getElementById('containerId');
console.log('📍 Container element found:', !!content);
if (content) {
@ -157,12 +145,6 @@ async function handleLocation(path: string) {
} catch (error) {
console.error('❌ Failed to initialize home page:', error);
}
} else if (path === 'security-setup' || path === 'wallet-setup' || path === 'birthday-setup') {
console.log('📍 Processing setup route:', path);
// Pour les pages de setup, rediriger directement vers la page HTML
window.location.href = routeHtml;
return;
} else {
console.log('📍 Processing other route:', path);
const html = await fetch(routeHtml).then(data => data.text());
@ -224,10 +206,8 @@ export async function init(): Promise<void> {
try {
console.log('🚀 Starting application initialization...');
// Initialiser les services de base
console.log('🔧 Initializing basic services...');
const services = await Services.getInstance();
(window as any).myService = services;
// Initialiser uniquement la base de données (sans WebAssembly)
console.log('🔧 Initializing database...');
const db = await Database.getInstance();
// Register service worker
@ -235,6 +215,7 @@ export async function init(): Promise<void> {
await db.registerServiceWorker('/src/service-workers/database.worker.js');
// Vérifier l'état du storage et naviguer vers la page appropriée
// Cette fonction est maintenant légère et ne nécessite pas WebAssembly
console.log('🔍 Checking storage state and navigating to appropriate page...');
await checkStorageStateAndNavigate();
@ -1254,7 +1235,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
/**
* ÉTAPE 5: Handshake
*/
async function performHandshake(services: any): Promise<void> {
async function performHandshake(_services: any): Promise<void> {
console.log('🤝 Performing handshake...');
try {

View File

@ -90,10 +90,9 @@ export class EncryptionService {
return btoa(String.fromCharCode(...combined));
} catch (error) {
secureLogger.error('Failed to encrypt with password', {
secureLogger.error('Failed to encrypt with password', error as Error, {
component: 'EncryptionService',
operation: 'encryptWithPassword',
error: error instanceof Error ? error.message : String(error)
operation: 'encryptWithPassword'
});
throw error;
}
@ -206,10 +205,9 @@ export class EncryptionService {
return new TextDecoder().decode(decrypted);
} catch (error) {
secureLogger.error('Failed to decrypt with password base64', {
secureLogger.error('Failed to decrypt with password base64', error as Error, {
component: 'EncryptionService',
operation: 'decryptWithPasswordBase64',
error: error instanceof Error ? error.message : String(error)
operation: 'decryptWithPasswordBase64'
});
throw error;
}
@ -236,7 +234,7 @@ export class EncryptionService {
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
salt: new Uint8Array(salt),
iterations: iterations,
hash: 'SHA-256'
},

View File

@ -1,5 +1,6 @@
/**
* StorageService - Gestion du stockage des credentials
* StorageService - Gestion du stockage des credentials de pairing
* Utilisé uniquement pour stocker les credentials après le processus de pairing
*/
import { secureLogger } from '../secure-logger';
import { CredentialData } from './types';
@ -8,7 +9,7 @@ import { DATABASE_CONFIG } from '../database-config';
export class StorageService {
private static instance: StorageService;
private dbName = DATABASE_CONFIG.name;
private storeName = DATABASE_CONFIG.stores.credentials.name; // Store séparé pour les clés PBKDF2
private storeName = DATABASE_CONFIG.stores.credentials.name; // Store pour les credentials de pairing
private dbVersion = DATABASE_CONFIG.version;
private constructor() {}
@ -20,178 +21,14 @@ export class StorageService {
return StorageService.instance;
}
/**
* Stocke une clé en clair (non recommandé)
*/
async storePlainKey(key: string): Promise<void> {
try {
secureLogger.warn('Storing key in plain text (not recommended)', {
component: 'StorageService',
operation: 'storePlainKey'
});
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.put(key, 'plain-pbkdf2-key');
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
secureLogger.info('Plain key stored successfully', {
component: 'StorageService',
operation: 'storePlainKey'
});
} catch (error) {
secureLogger.error('Failed to store plain key', {
component: 'StorageService',
operation: 'storePlainKey',
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Récupère une clé en clair depuis IndexedDB
*/
async retrievePlainKey(): Promise<string | null> {
try {
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const result = await new Promise<string | null>((resolve, reject) => {
const request = store.get('plain-pbkdf2-key');
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
return result;
} catch (error) {
secureLogger.error('Failed to retrieve plain key', {
component: 'StorageService',
operation: 'retrievePlainKey',
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Récupère une clé chiffrée depuis IndexedDB
*/
async retrieveEncryptedKey(): Promise<string | null> {
try {
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const result = await new Promise<string | null>((resolve, reject) => {
const request = store.get('encrypted-pbkdf2-key');
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
return result;
} catch (error) {
secureLogger.error('Failed to retrieve encrypted key', {
component: 'StorageService',
operation: 'retrieveEncryptedKey',
error: error instanceof Error ? error.message : String(error)
});
return null;
}
}
/**
* Stocke une clé chiffrée dans IndexedDB
*/
async storeEncryptedKey(encryptedKey: string, securityMode?: string): Promise<void> {
try {
secureLogger.info('Storing encrypted key', {
component: 'StorageService',
operation: 'storeEncryptedKey'
});
const db = await this.openDatabase();
const transaction = db.transaction([this.storeName], 'readwrite');
const store = transaction.objectStore(this.storeName);
await new Promise<void>((resolve, reject) => {
const request = store.put(encryptedKey, 'encrypted-pbkdf2-key');
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
secureLogger.info('Encrypted key stored successfully', {
component: 'StorageService',
operation: 'storeEncryptedKey'
});
} catch (error) {
secureLogger.error('Failed to store encrypted key', {
component: 'StorageService',
operation: 'storeEncryptedKey',
error: error instanceof Error ? error.message : String(error)
});
throw error;
}
}
/**
* Ouvre la base de données IndexedDB
*/
private async openDatabase(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
const error = new Error('Database open failed');
secureLogger.error('Failed to open IndexedDB', error, {
component: 'StorageService',
operation: 'openDatabase'
});
reject(request.error);
};
request.onsuccess = () => {
secureLogger.info('IndexedDB opened successfully', {
component: 'StorageService',
operation: 'openDatabase'
});
resolve(request.result);
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
console.log(`🔄 StorageService: Database upgrade needed for ${this.dbName} version ${this.dbVersion}`);
// Créer tous les stores définis dans DATABASE_CONFIG
Object.values(DATABASE_CONFIG.stores).forEach(storeConfig => {
if (!db.objectStoreNames.contains(storeConfig.name)) {
const options: IDBObjectStoreParameters = {};
if (storeConfig.keyPath) {
options.keyPath = storeConfig.keyPath;
}
if ('autoIncrement' in storeConfig && storeConfig.autoIncrement) {
options.autoIncrement = true;
}
const store = db.createObjectStore(storeConfig.name, options);
// Créer les index
storeConfig.indices.forEach(indexConfig => {
store.createIndex(indexConfig.name, indexConfig.keyPath, { unique: indexConfig.unique || false });
});
console.log(`✅ Created store: ${storeConfig.name}`);
}
});
};
});
// Utiliser la fonction centralisée openDatabase
const { openDatabase } = await import('../database-config');
return openDatabase();
}
/**
@ -266,8 +103,7 @@ export class StorageService {
async hasCredentials(): Promise<boolean> {
try {
const credentials = await this.getCredentials();
return credentials !== null &&
credentials.spendKey !== undefined &&
return credentials?.spendKey !== undefined &&
credentials.scanKey !== undefined;
} catch (error) {
secureLogger.error('Failed to check credentials existence', error as Error, {

View File

@ -59,7 +59,7 @@ export class WebAuthnService {
*/
async detectAvailableAuthenticators(): Promise<boolean> {
try {
if (!navigator.credentials || !navigator.credentials.create) {
if (!navigator.credentials?.create) {
return false;
}
@ -105,7 +105,7 @@ export class WebAuthnService {
// Vérifier la disponibilité sans faire d'appel réel à WebAuthn
// Juste vérifier que les APIs sont disponibles
if (!navigator.credentials || !navigator.credentials.create) {
if (!navigator.credentials?.create) {
console.log('❌ WebAuthn credentials API not available');
return false;
}
@ -266,11 +266,8 @@ export class WebAuthnService {
await new Promise<void>((resolve, reject) => {
// Utiliser le securityMode comme clé d'enregistrement
// NE PAS stocker credentialId avec la clé chiffrée
const request = store.put({
encryptedKey, // Clé PBKDF2 chiffrée avec WebAuthn
timestamp: Date.now()
}, securityMode);
// Stocker directement la clé chiffrée pour cohérence avec les autres modes
const request = store.put(encryptedKey, securityMode);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
@ -287,6 +284,28 @@ export class WebAuthnService {
}
/**
* Vérifie silencieusement si une clé PBKDF2 est stockée pour un mode de sécurité
*/
async hasStoredKey(securityMode: SecurityMode): Promise<boolean> {
try {
const db = await this.openDatabase();
const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readonly');
const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name);
const result = await new Promise<any>((resolve, reject) => {
const request = store.get(securityMode);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
return result && typeof result === 'string';
} catch (error) {
console.log(`🔍 No PBKDF2 key found for security mode: ${securityMode}`);
return false;
}
}
/**
* Récupère et déchiffre la clé PBKDF2 avec WebAuthn
*/
@ -303,7 +322,7 @@ export class WebAuthnService {
request.onerror = () => reject(request.error);
});
if (!result || !result.encryptedKey) {
if (!result || typeof result !== 'string') {
console.log(`🔍 No PBKDF2 key found for security mode: ${securityMode}`);
return null;
}
@ -336,7 +355,7 @@ export class WebAuthnService {
// Déchiffrer la clé avec le credentialId WebAuthn
console.log('🔐 Decrypting PBKDF2 key with credentialId:', credentialId);
const encrypted = atob(result.encryptedKey);
const encrypted = atob(result);
const combined = new Uint8Array(encrypted.length);
for (let i = 0; i < encrypted.length; i++) {
combined[i] = encrypted.charCodeAt(i);

View File

@ -79,7 +79,7 @@ export class Database {
Object.values(this.storeDefinitions).forEach(({ name, options, indices }) => {
if (!db.objectStoreNames.contains(name)) {
let store = db.createObjectStore(name, options as IDBObjectStoreParameters);
const store = db.createObjectStore(name, options as IDBObjectStoreParameters);
indices.forEach(({ name, keyPath, options }) => {
store.createIndex(name, keyPath, options);
@ -116,7 +116,7 @@ export class Database {
}
public async registerServiceWorker(path: string) {
if (!('serviceWorker' in navigator)) return; // Ensure service workers are supported
if (!('serviceWorker' in navigator)) {return;} // Ensure service workers are supported
console.log('registering worker at', path);
try {
@ -268,7 +268,7 @@ export class Database {
private async handleDownloadList(downloadList: string[]): Promise<void> {
// Download the missing data
let requestedStateId: string[] = [];
const requestedStateId: string[] = [];
const service = await Services.getInstance();
for (const hash of downloadList) {
const diff = await service.getDiffByValue(hash);
@ -321,7 +321,7 @@ export class Database {
} else if (data.type === 'TO_DOWNLOAD') {
console.log(`Received missing data ${data}`);
// Download the missing data
let requestedStateId: string[] = [];
const requestedStateId: string[] = [];
for (const hash of data.data) {
try {
const valueBytes = await service.fetchValueFromStorage(hash);
@ -510,14 +510,31 @@ export class Database {
}
public async getObject(storeName: string, key: string): Promise<any | null> {
const db = await this.getDb();
console.log(`🔍 DEBUG: Database.getObject - storeName: ${storeName}, key: ${key}`);
// Utiliser directement IndexedDB au lieu du service worker pour éviter les problèmes de synchronisation
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
console.log(`🔍 DEBUG: Database.getObject - db obtained directly, 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}`);
const result = await new Promise((resolve, reject) => {
const getRequest = store.get(key);
getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => reject(getRequest.error);
getRequest.onsuccess = () => {
console.log(`🔍 DEBUG: Database.getObject - getRequest success, result:`, getRequest.result);
resolve(getRequest.result);
};
getRequest.onerror = () => {
console.log(`🔍 DEBUG: Database.getObject - getRequest error:`, getRequest.error);
reject(getRequest.error);
};
});
console.log(`🔍 DEBUG: Database.getObject - final result:`, result);
return result ?? null; // Convert undefined to null
}

View File

@ -0,0 +1,145 @@
/**
* Service léger pour lire le device depuis la base de données
* Sans nécessiter l'initialisation de WebAssembly
*/
import { DATABASE_CONFIG } from './database-config';
import { SecureCredentialsService } from './secure-credentials.service';
import { EncryptionService } from './encryption.service';
// Type simplifié pour éviter les problèmes d'import du SDK
export interface Device {
sp_wallet?: {
address?: string;
birthday?: number;
last_scan?: number;
spend_key?: any;
scan_key?: any;
[key: string]: any;
};
sp_client?: any;
[key: string]: any;
}
export class DeviceReaderService {
private static instance: DeviceReaderService | null = null;
private constructor() {}
public static getInstance(): DeviceReaderService {
if (!DeviceReaderService.instance) {
DeviceReaderService.instance = new DeviceReaderService();
}
return DeviceReaderService.instance;
}
/**
* Récupère le device depuis la base de données et le déchiffre
* Version légère sans nécessiter WebAssembly
*/
async getDeviceFromDatabase(): Promise<Device | null> {
console.log('🔍 DeviceReaderService: Reading device from database...');
// Utiliser directement IndexedDB
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const walletStore = DATABASE_CONFIG.stores.wallet.name;
try {
const dbRes = await new Promise<any>((resolve, reject) => {
const tx = db.transaction(walletStore, 'readonly');
const store = tx.objectStore(walletStore);
const getRequest = store.get('1');
getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => reject(getRequest.error);
});
if (!dbRes) {
console.log('🔍 DeviceReaderService: No device found in database');
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...');
// Get the PBKDF2 key based on security mode
const secureCredentialsService = SecureCredentialsService.getInstance();
// Get all security modes to find which one works
const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass'];
let pbkdf2Key: string | null = null;
for (const mode of allSecurityModes) {
try {
const hasKey = await secureCredentialsService.hasPBKDF2Key(mode as any);
if (hasKey) {
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
if (key) {
pbkdf2Key = key;
console.log(`✅ DeviceReaderService: PBKDF2 key found for mode: ${mode}`);
break;
}
}
} catch (e) {
// Continue to next mode
console.log(`⚠️ DeviceReaderService: No PBKDF2 key for mode ${mode}`);
}
}
if (!pbkdf2Key) {
console.error('❌ DeviceReaderService: Failed to retrieve PBKDF2 key for decryption');
throw new Error('PBKDF2 key not found - cannot decrypt device');
}
// Decrypt the device
const encryptionService = EncryptionService.getInstance();
const decryptedDeviceString = await encryptionService.decrypt(
dbRes['encrypted_device'],
pbkdf2Key
);
const device: Device = JSON.parse(decryptedDeviceString);
console.log('✅ DeviceReaderService: Device decrypted successfully');
return device;
} else {
// Old plain format - return as is (should not happen in production)
console.warn('⚠️ DeviceReaderService: Device found in plain format (old format)');
return dbRes as Device;
}
} catch (error) {
console.error('❌ DeviceReaderService: Error reading device:', error);
throw error;
}
}
/**
* Récupère l'adresse du device depuis la base de données
* Version légère sans nécessiter WebAssembly
*/
async getDeviceAddress(): Promise<string | null> {
try {
const device = await this.getDeviceFromDatabase();
if (!device) {
return null;
}
// L'adresse est stockée dans device.sp_wallet.address
const address = device.sp_wallet?.address;
if (address) {
console.log('✅ DeviceReaderService: Device address retrieved:', address);
return address;
}
console.warn('⚠️ DeviceReaderService: Device found but no address in sp_wallet.address');
return null;
} catch (error) {
console.error('❌ DeviceReaderService: Error getting device address:', error);
throw error;
}
}
}

View File

@ -1,7 +1,6 @@
import { MessageType } from '../models/process.model';
import Services from './service';
import {
// generateWordsDisplay, // Unused import
discoverAndJoinPairingProcessWithWords,
prepareAndSendPairingTx,
} from '../utils/sp-address.utils';

View File

@ -3,7 +3,6 @@ import modalScript from '../components/login-modal/login-modal.js?raw';
import validationModalStyle from '../components/validation-modal/validation-modal.css?raw';
import Services from './service';
import { navigate } from '../router';
// import { init } from '../router'; // Unused import
import { addressToEmoji } from '../utils/sp-address.utils';
import { RoleDefinition } from 'pkg/sdk_client';
import { initValidationModal } from '../components/validation-modal/validation-modal';

View File

@ -2,12 +2,10 @@
* PairingService - Service spécialisé pour le pairing
* Gère la logique métier du pairing sans couplage direct
*/
// import { Device } from '../../pkg/sdk_client';
import { DeviceRepository } from '../repositories/device.repository';
import { ProcessRepository } from '../repositories/process.repository';
import { eventBus } from './event-bus';
import { secureLogger } from './secure-logger';
// import { secureKeyManager } from './secure-key-manager';
import { SecureCredentialsService } from './secure-credentials.service';
import { CredentialData } from './credentials/types';

View File

@ -94,34 +94,135 @@ export class SecureCredentialsService {
}
}
/**
* Vérifie silencieusement si une clé PBKDF2 existe pour un mode de sécurité
* sans déclencher d'interactions utilisateur (comme les fenêtres du navigateur)
*/
async hasPBKDF2Key(securityMode: SecurityMode): Promise<boolean> {
try {
switch (securityMode) {
case 'proton-pass':
case 'os':
// Pour WebAuthn, vérifier si une clé chiffrée existe
const { WebAuthnService } = await import('./credentials/webauthn.service');
const webAuthnService = WebAuthnService.getInstance();
return await webAuthnService.hasStoredKey(securityMode);
case 'otp':
// Vérifier si une clé en clair existe dans pbkdf2keys
const plainKey = await this.getPBKDF2KeyFromStore(securityMode);
return plainKey !== null;
case 'password':
// Vérifier silencieusement si une clé chiffrée existe dans pbkdf2keys
// sans déclencher l'API Credential Management
const encryptedPasswordData = await this.getPBKDF2KeyFromStore(securityMode);
return encryptedPasswordData !== null;
case 'none':
// Vérifier si une clé chiffrée avec la clé en dur existe dans pbkdf2keys
const encryptedData = await this.getPBKDF2KeyFromStore(securityMode);
return encryptedData !== null;
default:
return false;
}
} catch (error) {
secureLogger.error('Failed to check PBKDF2 key existence', error as Error, {
component: 'SecureCredentialsService',
operation: 'hasPBKDF2Key'
});
return false;
}
}
/**
* Récupère une clé PBKDF2 chiffrée depuis le store pbkdf2keys
*/
private async getPBKDF2KeyFromStore(securityMode: SecurityMode): Promise<string | null> {
try {
const { DATABASE_CONFIG, openDatabase } = await import('./database-config');
const db = await openDatabase();
const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readonly');
const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name);
const result = await new Promise<string | null>((resolve, reject) => {
const request = store.get(securityMode);
request.onsuccess = () => resolve(request.result || null);
request.onerror = () => reject(request.error);
});
return result;
} catch (error) {
secureLogger.error('Failed to retrieve PBKDF2 key from pbkdf2keys store', error as Error, {
component: 'SecureCredentialsService',
operation: 'getPBKDF2KeyFromStore'
});
return null;
}
}
/**
* Stocke une clé PBKDF2 chiffrée dans le store pbkdf2keys
*/
private async storePBKDF2KeyInStore(encryptedKey: string, securityMode: SecurityMode): Promise<void> {
try {
const { DATABASE_CONFIG, openDatabase } = await import('./database-config');
const db = await openDatabase();
const transaction = db.transaction([DATABASE_CONFIG.stores.pbkdf2keys.name], 'readwrite');
const store = transaction.objectStore(DATABASE_CONFIG.stores.pbkdf2keys.name);
await new Promise<void>((resolve, reject) => {
const request = store.put(encryptedKey, securityMode);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
secureLogger.info('PBKDF2 key stored in pbkdf2keys store', {
component: 'SecureCredentialsService',
operation: 'storePBKDF2KeyInStore',
securityMode
});
} catch (error) {
secureLogger.error('Failed to store PBKDF2 key in pbkdf2keys store', error as Error, {
component: 'SecureCredentialsService',
operation: 'storePBKDF2KeyInStore'
});
throw error;
}
}
/**
* Récupère une clé PBKDF2 existante selon le mode de sécurité
*/
async retrievePBKDF2Key(securityMode: SecurityMode): Promise<string | null> {
try {
const { StorageService } = await import('./credentials/storage.service');
const { DATABASE_CONFIG, openDatabase } = await import('./database-config');
const { WebAuthnService } = await import('./credentials/webauthn.service');
const storageService = StorageService.getInstance();
const webAuthnService = WebAuthnService.getInstance();
switch (securityMode) {
case 'proton-pass':
case 'os':
// Récupérer la clé chiffrée avec WebAuthn
// Récupérer la clé chiffrée avec WebAuthn depuis pbkdf2keys
return await webAuthnService.retrieveKeyWithWebAuthn(securityMode);
case 'otp':
// Récupérer la clé en clair (l'OTP protège l'accès)
return await storageService.retrievePlainKey();
// Récupérer la clé en clair depuis pbkdf2keys
return await this.getPBKDF2KeyFromStore(securityMode);
case 'password':
// Récupérer la clé chiffrée avec mot de passe
return await storageService.retrieveEncryptedKey();
// Récupérer la clé chiffrée avec mot de passe depuis pbkdf2keys
const encryptedPasswordData = await this.getPBKDF2KeyFromStore(securityMode);
if (encryptedPasswordData) {
return await this.decryptPBKDF2KeyWithPassword(encryptedPasswordData);
}
return null;
case 'none':
// Récupérer la clé chiffrée avec la clé en dur
const encryptedData = await storageService.retrieveEncryptedKey();
// Récupérer la clé chiffrée avec la clé en dur depuis pbkdf2keys
const encryptedData = await this.getPBKDF2KeyFromStore(securityMode);
if (encryptedData) {
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
@ -134,10 +235,9 @@ export class SecureCredentialsService {
return null;
}
} catch (error) {
secureLogger.error('Failed to retrieve PBKDF2 key', {
secureLogger.error('Failed to retrieve PBKDF2 key', error as Error, {
component: 'SecureCredentialsService',
operation: 'retrievePBKDF2Key',
error: error instanceof Error ? error.message : String(error)
operation: 'retrievePBKDF2Key'
});
return null;
}
@ -157,11 +257,9 @@ export class SecureCredentialsService {
// Import dynamique des services
const { EncryptionService } = await import('./encryption.service');
const { WebAuthnService } = await import('./credentials/webauthn.service');
const { StorageService } = await import('./credentials/storage.service');
const encryptionService = EncryptionService.getInstance();
const webAuthnService = WebAuthnService.getInstance();
const storageService = StorageService.getInstance();
// Essayer d'abord de récupérer une clé existante
const existingKey = await this.retrievePBKDF2Key(securityMode);
@ -188,18 +286,18 @@ export class SecureCredentialsService {
console.log('🔐 Setting up OTP authentication for PBKDF2 key...');
const otpSecret = await this.generateOTPSecret();
console.log('🔐 OTP Secret generated:', otpSecret);
// Stocker la clé PBKDF2 en clair (l'OTP protège l'accès, pas le stockage)
await storageService.storePlainKey(pbkdf2Key);
// Stocker la clé PBKDF2 en clair dans pbkdf2keys (l'OTP protège l'accès, pas le stockage)
await this.storePBKDF2KeyInStore(pbkdf2Key, securityMode);
// Afficher le QR code pour l'utilisateur
this.displayOTPQRCode(otpSecret);
break;
case 'password':
// Demander un mot de passe à l'utilisateur et chiffrer la clé
console.log('🔐 Storing PBKDF2 key with password encryption...');
const userPassword = await this.promptForPassword();
// Utiliser l'API Credential Management du navigateur
console.log('🔐 Storing PBKDF2 key with browser password manager...');
const userPassword = await this.promptForPasswordWithBrowser();
const encryptedKey = await encryptionService.encrypt(pbkdf2Key, userPassword);
await storageService.storeEncryptedKey(encryptedKey, securityMode);
await this.storePBKDF2KeyInStore(encryptedKey, securityMode);
break;
case 'none':
@ -207,7 +305,7 @@ export class SecureCredentialsService {
console.log('⚠️ Storing PBKDF2 key with hardcoded encryption (not recommended)...');
const hardcodedKey = '4NK_DEFAULT_ENCRYPTION_KEY_NOT_SECURE';
const encryptedKeyNone = await encryptionService.encrypt(pbkdf2Key, hardcodedKey);
await storageService.storeEncryptedKey(encryptedKeyNone, securityMode);
await this.storePBKDF2KeyInStore(encryptedKeyNone, securityMode);
break;
default:
@ -223,10 +321,9 @@ export class SecureCredentialsService {
return pbkdf2Key;
} catch (error) {
secureLogger.error('Failed to generate PBKDF2 key', {
secureLogger.error('Failed to generate PBKDF2 key', error as Error, {
component: 'SecureCredentialsService',
operation: 'generatePBKDF2Key',
error: error instanceof Error ? error.message : String(error)
operation: 'generatePBKDF2Key'
});
throw error;
}
@ -390,7 +487,7 @@ export class SecureCredentialsService {
* Génère des credentials en clair
*/
private async generatePlainCredentials(
password: string,
_password: string,
_options: CredentialOptions = {}
): Promise<CredentialData> {
try {
@ -473,7 +570,7 @@ export class SecureCredentialsService {
*/
private async decryptWithWebAuthn(
credentials: CredentialData,
password: string
_password: string
): Promise<CredentialData> {
try {
const currentMode = await this.securityModeService.getCurrentMode();
@ -545,7 +642,7 @@ export class SecureCredentialsService {
/**
* Stocke des credentials
*/
async storeCredentials(credentials: CredentialData, password: string): Promise<void> {
async storeCredentials(credentials: CredentialData, _password: string): Promise<void> {
try {
// Import dynamique du service de stockage
const { StorageService } = await import('./credentials/storage.service');
@ -611,7 +708,7 @@ export class SecureCredentialsService {
/**
* Valide un code OTP
*/
async validateOTPCode(secret: string, code: string): Promise<boolean> {
async validateOTPCode(_secret: string, code: string): Promise<boolean> {
try {
// Implémentation simplifiée de validation OTP
// Dans une implémentation complète, on utiliserait une bibliothèque comme speakeasy
@ -736,13 +833,305 @@ QR Code URL: ${qrUrl}`);
*/
private async promptForPassword(): Promise<string> {
return new Promise((resolve, reject) => {
const password = prompt('Entrez un mot de passe pour chiffrer la clé PBKDF2:');
if (password) {
resolve(password);
} else {
// Créer une interface utilisateur pour saisir le mot de passe
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
max-width: 400px;
width: 90%;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; color: #333;">🔐 Mot de passe de sécurité</h3>
<p style="margin: 0 0 15px 0; color: #666; font-size: 14px;">
Entrez un mot de passe fort pour chiffrer votre clé PBKDF2.<br>
<strong>Attention :</strong> Ce mot de passe ne sera pas sauvegardé et ne pourra pas être récupéré !
</p>
<input type="password" id="passwordInput" placeholder="Mot de passe"
style="width: 100%; padding: 12px; border: 2px solid #e1e5e9; border-radius: 6px; margin-bottom: 15px; font-size: 16px;">
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button id="cancelBtn" style="padding: 10px 20px; border: 1px solid #ccc; background: white; border-radius: 6px; cursor: pointer;">
Annuler
</button>
<button id="confirmBtn" style="padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer;">
Confirmer
</button>
</div>
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
const passwordInput = dialog.querySelector('#passwordInput') as HTMLInputElement;
const cancelBtn = dialog.querySelector('#cancelBtn') as HTMLButtonElement;
const confirmBtn = dialog.querySelector('#confirmBtn') as HTMLButtonElement;
// Focus sur l'input
passwordInput.focus();
// Gestion des événements
let cleanup = () => {
document.body.removeChild(modal);
};
cancelBtn.addEventListener('click', () => {
cleanup();
reject(new Error('Password prompt cancelled'));
});
confirmBtn.addEventListener('click', () => {
const password = passwordInput.value.trim();
if (password.length < 8) {
alert('Le mot de passe doit contenir au moins 8 caractères');
passwordInput.focus();
return;
}
cleanup();
resolve(password);
});
// Gestion de la touche Entrée
passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
confirmBtn.click();
}
});
// Gestion de la touche Échap
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
cleanup();
reject(new Error('Password prompt cancelled'));
}
};
document.addEventListener('keydown', handleEscape);
// Nettoyer l'event listener quand le modal est fermé
const originalCleanup = cleanup;
cleanup = () => {
document.removeEventListener('keydown', handleEscape);
originalCleanup();
};
});
}
/**
* Demande un mot de passe à l'utilisateur en utilisant l'API Credential Management du navigateur
*/
private async promptForPasswordWithBrowser(): Promise<string> {
// Vérifier si l'API Credential Management est disponible
if (!navigator.credentials) {
console.warn('⚠️ Credential Management API not available, falling back to modal');
return this.promptForPassword();
}
try {
// Essayer de récupérer un mot de passe existant
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
const existingCredential = await navigator.credentials.get({
password: true,
mediation: 'optional'
} as any);
if (existingCredential?.type === 'password') {
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
const passwordCredential = existingCredential as any;
console.log('🔐 Retrieved existing password from browser');
return passwordCredential.password;
}
} catch (error) {
console.log('🔐 No existing password found, will create new one');
}
// Si aucun mot de passe existant, créer un nouveau
return new Promise((resolve, reject) => {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
max-width: 400px;
width: 90%;
`;
dialog.innerHTML = `
<h3 style="margin: 0 0 20px 0; color: #333;">🔐 Mot de passe de sécurité</h3>
<p style="margin: 0 0 15px 0; color: #666; font-size: 14px;">
Entrez un mot de passe fort pour chiffrer votre clé PBKDF2.<br>
<strong>Le navigateur vous proposera de sauvegarder ce mot de passe.</strong>
</p>
<input type="password" id="passwordInput" placeholder="Mot de passe"
style="width: 100%; padding: 12px; border: 2px solid #e1e5e9; border-radius: 6px; margin-bottom: 15px; font-size: 16px;">
<div style="display: flex; gap: 10px; justify-content: flex-end;">
<button id="cancelBtn" style="padding: 10px 20px; border: 1px solid #ccc; background: white; border-radius: 6px; cursor: pointer;">
Annuler
</button>
<button id="confirmBtn" style="padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 6px; cursor: pointer;">
Confirmer
</button>
</div>
`;
modal.appendChild(dialog);
document.body.appendChild(modal);
const passwordInput = dialog.querySelector('#passwordInput') as HTMLInputElement;
const cancelBtn = dialog.querySelector('#cancelBtn') as HTMLButtonElement;
const confirmBtn = dialog.querySelector('#confirmBtn') as HTMLButtonElement;
passwordInput.focus();
let cleanup = () => {
document.body.removeChild(modal);
};
cancelBtn.addEventListener('click', () => {
cleanup();
reject(new Error('Password prompt cancelled'));
});
confirmBtn.addEventListener('click', async () => {
const password = passwordInput.value.trim();
if (password.length < 8) {
alert('Le mot de passe doit contenir au moins 8 caractères');
passwordInput.focus();
return;
}
try {
// Sauvegarder le mot de passe dans le gestionnaire de mots de passe du navigateur
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
if (typeof PasswordCredential !== 'undefined' && navigator.credentials && navigator.credentials.create) {
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
const credential = new PasswordCredential({
id: '4nk-pbkdf2-password',
password: password,
name: '4NK PBKDF2 Password',
iconURL: '/favicon.ico'
});
await navigator.credentials.store(credential);
console.log('🔐 Password saved to browser password manager');
}
} catch (error) {
console.warn('⚠️ Failed to save password to browser:', error);
// Continuer même si la sauvegarde échoue
}
cleanup();
resolve(password);
});
// Gestion de la touche Entrée
passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
confirmBtn.click();
}
});
// Gestion de la touche Échap
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
cleanup();
reject(new Error('Password prompt cancelled'));
}
};
document.addEventListener('keydown', handleEscape);
// Nettoyer l'event listener quand le modal est fermé
const originalCleanup = cleanup;
cleanup = () => {
document.removeEventListener('keydown', handleEscape);
originalCleanup();
};
});
}
/**
* Déchiffre une clé PBKDF2 avec le mot de passe du navigateur
*/
async decryptPBKDF2KeyWithPassword(encryptedKey: string): Promise<string | null> {
try {
// Récupérer le mot de passe depuis le gestionnaire de mots de passe du navigateur
const password = await this.getPasswordFromBrowser();
if (!password) {
console.warn('⚠️ No password found in browser, falling back to manual input');
return null;
}
// Déchiffrer avec le mot de passe
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
return await encryptionService.decrypt(encryptedKey, password);
} catch (error) {
secureLogger.error('Failed to decrypt PBKDF2 key with password', error as Error, {
component: 'SecureCredentialsService',
operation: 'decryptPBKDF2KeyWithPassword'
});
return null;
}
}
/**
* Récupère le mot de passe depuis le gestionnaire de mots de passe du navigateur
*/
private async getPasswordFromBrowser(): Promise<string | null> {
// Vérifier si l'API Credential Management est disponible
if (!navigator.credentials) {
console.warn('⚠️ Credential Management API not available');
return null;
}
try {
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
const credential = await navigator.credentials.get({
password: true,
mediation: 'optional'
} as any);
if (credential?.type === 'password') {
// @ts-ignore - PasswordCredential API may not be in TypeScript definitions
const passwordCredential = credential as any;
console.log('🔐 Retrieved password from browser password manager');
return passwordCredential.password;
}
} catch (error) {
console.log('🔐 No password found in browser password manager');
}
return null;
}
/**
@ -762,14 +1151,14 @@ QR Code URL: ${qrUrl}`);
return { isValid: false, score: 0, feedback };
}
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
if (password.length >= 16) score += 1;
if (password.length >= 8) {score += 1;}
if (password.length >= 12) {score += 1;}
if (password.length >= 16) {score += 1;}
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
if (/[0-9]/.test(password)) score += 1;
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
if (/[a-z]/.test(password)) {score += 1;}
if (/[A-Z]/.test(password)) {score += 1;}
if (/[0-9]/.test(password)) {score += 1;}
if (/[^a-zA-Z0-9]/.test(password)) {score += 1;}
// Feedback détaillé
if (score < 3) {

View File

@ -5,6 +5,7 @@
*/
import { secureLogger } from './secure-logger';
import Database from './database.service';
export type SecurityMode = 'proton-pass' | 'os' | 'otp' | '2fa' | 'password' | 'none';

View File

@ -1,9 +1,6 @@
// import { INotification } from '~/models/notification.model'; // Unused import
// import { IProcess } from '~/models/process.model'; // Unused import
import { initWebsocket, sendMessage } from '../websockets';
import { memoryManager } from './memory-manager';
import { secureLogger } from './secure-logger';
// import { secureKeyManager } from './secure-key-manager';
import {
ApiReturn,
Device,
@ -20,9 +17,7 @@ import {
} from '../../pkg/sdk_client';
import ModalService from './modal.service';
import Database from './database.service';
// import { navigate } from '../router'; // Unused import
import { storeData, retrieveData } from './storage.service';
// import { testData } from './storage.service'; // Unused import
import { BackUp } from '../models/backup.model';
import { DATABASE_CONFIG } from './database-config';
@ -314,13 +309,16 @@ export default class Services {
if ((performance as any).memory) {
const memory = (performance as any).memory;
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
const availableMB = (memory.jsHeapSizeLimit - memory.usedJSHeapSize) / 1024 / 1024;
if (usedPercent > 98) {
console.log('🚫 Memory too high, skipping WebAssembly initialization');
Services.instance = new Services();
console.log(`📊 Memory check before WebAssembly: ${usedPercent.toFixed(1)}% used, ${availableMB.toFixed(1)}MB available`);
// WebAssembly nécessite généralement au moins 100-200MB de mémoire disponible
// Si moins de 150MB disponibles ou plus de 85% utilisé, ne pas initialiser
if (usedPercent > 85 || availableMB < 150) {
console.error(`🚫 Memory insufficient for WebAssembly: ${usedPercent.toFixed(1)}% used, ${availableMB.toFixed(1)}MB available`);
Services.initializing = null;
console.log('✅ Services initialized without WebAssembly');
return Services.instance;
throw new Error(`Insufficient memory for WebAssembly initialization. Current usage: ${usedPercent.toFixed(1)}%, Available: ${availableMB.toFixed(1)}MB. Please close other tabs and refresh.`);
}
}
@ -331,6 +329,16 @@ export default class Services {
} catch (error) {
console.error('❌ Service initialization failed:', error);
// Réinitialiser initializing pour permettre une nouvelle tentative après un délai
Services.initializing = null;
// Si c'est une erreur de mémoire, ne pas réessayer immédiatement
const errorMessage = (error as Error).message || String(error);
if (errorMessage.includes('Out of memory') || errorMessage.includes('memory')) {
console.error('🚫 Memory error detected - cannot retry immediately');
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
}
throw error;
}
@ -342,6 +350,22 @@ export default class Services {
public async init(): Promise<void> {
this.notifications = this.getNotifications();
// Vérifier la mémoire avant d'importer WebAssembly
if ((performance as any).memory) {
const memory = (performance as any).memory;
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
const availableMB = (memory.jsHeapSizeLimit - memory.usedJSHeapSize) / 1024 / 1024;
console.log(`📊 Memory check before WebAssembly import: ${usedPercent.toFixed(1)}% used, ${availableMB.toFixed(1)}MB available`);
// WebAssembly nécessite au moins 150MB de mémoire disponible
if (usedPercent > 85 || availableMB < 150) {
console.error(`🚫 Memory insufficient for WebAssembly import: ${usedPercent.toFixed(1)}% used, ${availableMB.toFixed(1)}MB available`);
throw new Error(`Insufficient memory for WebAssembly. Current usage: ${usedPercent.toFixed(1)}%, Available: ${availableMB.toFixed(1)}MB. Please close other tabs and refresh.`);
}
}
this.sdkClient = await import('../../pkg/sdk_client');
this.sdkClient.setup();
for (const wsurl of Object.values(BOOTSTRAPURL)) {
@ -541,7 +565,7 @@ export default class Services {
// Wait for at least one handshake message if we have connections
if (connectedUrls.length > 0) {
try {
await this.waitForHandshakeMessage();
await this.waitForHandshakeMessage(10000); // Augmenter le timeout à 10 secondes
console.log(`✅ Handshake received from at least one relay`);
} catch (error) {
console.warn(
@ -784,7 +808,7 @@ export default class Services {
private async ensureSufficientAmount(): Promise<void> {
const availableAmt = this.getAmount();
const target: BigInt = DEFAULTAMOUNT * BigInt(10);
const target: bigint = DEFAULTAMOUNT * 10n;
console.log(`💰 Current amount: ${availableAmt}, target: ${target}`);
@ -840,7 +864,7 @@ export default class Services {
}
}
private async waitForAmount(target: BigInt): Promise<BigInt> {
private async waitForAmount(target: bigint): Promise<bigint> {
let attempts = 20; // Increased attempts for blockchain confirmation
while (attempts > 0) {
@ -867,7 +891,7 @@ export default class Services {
const newAmount = this.getAmount();
console.log(`💰 Amount after transaction scan: ${newAmount}`);
if (newAmount > 0) {
if (newAmount > 0n) {
this.updateUserStatus(`💰 Found ${newAmount} tokens in wallet!`);
} else {
this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...');
@ -1293,7 +1317,7 @@ export default class Services {
// Update last_scan to current block height
device.sp_wallet.last_scan = this.currentBlockHeight;
await this.updateDeviceInDatabase(device);
await this.saveDeviceInDatabase(device);
console.log('✅ Wallet last_scan updated to current block height');
} else {
console.log('🔄 Using safe scan blocks...');
@ -1318,7 +1342,7 @@ export default class Services {
console.log(`💰 Amount after block scan: ${updatedAmount}`);
// Update user with scan results
if (updatedAmount > 0) {
if (updatedAmount > 0n) {
this.updateUserStatus(`💰 Wallet updated! Found ${updatedAmount} tokens`);
} else {
this.updateUserStatus('⏳ Transaction processed, waiting for confirmation...');
@ -1735,7 +1759,7 @@ export default class Services {
}
}
public getAmount(): BigInt {
public getAmount(): bigint {
if (!this.sdkClient) {
throw new Error('SDK not initialized - cannot get amount');
}
@ -1785,6 +1809,10 @@ export default class Services {
}
}
getCurrentBlockHeight(): number {
return this.currentBlockHeight;
}
public dumpDeviceFromMemory(): Device {
try {
return this.sdkClient.dump_device();
@ -1811,29 +1839,189 @@ export default class Services {
}
async saveDeviceInDatabase(device: Device): Promise<void> {
const db = await Database.getInstance();
const walletStore = DATABASE_CONFIG.stores.wallet.name;
console.log('🔐 saveDeviceInDatabase called - starting encryption process...');
try {
const prevDevice = await this.getDeviceFromDatabase();
if (prevDevice) {
await db.deleteObject(walletStore, '1');
// Récupérer la clé PBKDF2 pour chiffrer le device
console.log('🔐 Retrieving PBKDF2 key for device encryption...');
const { SecureCredentialsService } = await import('./secure-credentials.service');
const secureCredentialsService = SecureCredentialsService.getInstance();
// Trouver le mode de sécurité qui fonctionne
const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass'];
let pbkdf2Key: string | null = null;
let workingMode: string | null = null;
for (const mode of allSecurityModes) {
try {
const hasKey = await secureCredentialsService.hasPBKDF2Key(mode as any);
if (hasKey) {
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
if (key) {
pbkdf2Key = key;
workingMode = mode;
console.log(`✅ PBKDF2 key found for mode: ${mode}`);
break;
}
await db.addObject({
storeName: walletStore,
object: { pre_id: '1', device },
key: null,
}
} catch (e) {
// Continue to next mode
console.log(`⚠️ No PBKDF2 key for mode ${mode}`);
}
}
if (!pbkdf2Key) {
throw new Error('Failed to retrieve PBKDF2 key - cannot encrypt device');
}
console.log('🔐 Encrypting device with PBKDF2 key...');
// Chiffrer le device
const { EncryptionService } = await import('./encryption.service');
const encryptionService = EncryptionService.getInstance();
const deviceString = JSON.stringify(device);
const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key);
console.log('✅ Device encrypted successfully');
// Récupérer le wallet existant pour préserver encrypted_wallet
console.log('🔍 Retrieving existing wallet to preserve encrypted_wallet...');
let encryptedWallet: string | undefined = undefined;
// Récupérer le wallet chiffré depuis la base de données directement
const dbTemp = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const walletStoreName = DATABASE_CONFIG.stores.wallet.name;
const walletData = await new Promise<any>((resolve, reject) => {
const tx = dbTemp.transaction(walletStoreName, 'readonly');
const store = tx.objectStore(walletStoreName);
const getRequest = store.get('1');
getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => reject(getRequest.error);
});
if (walletData?.encrypted_wallet) {
encryptedWallet = walletData.encrypted_wallet;
console.log('✅ encrypted_wallet preserved');
} else {
console.log('⚠️ No existing encrypted_wallet to preserve');
}
// Sauvegarder avec le format chiffré
console.log('💾 Saving encrypted device to database...');
// Utiliser directement IndexedDB au lieu du service Database pour éviter les problèmes de service worker
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const walletStore = DATABASE_CONFIG.stores.wallet.name;
// Sauvegarder le wallet chiffré directement avec IndexedDB
await new Promise<void>((resolve, reject) => {
const transaction = db.transaction([walletStore], 'readwrite');
const store = transaction.objectStore(walletStore);
// Sauvegarder le nouveau wallet chiffré (put écrase automatiquement si existe)
const walletObject: any = {
pre_id: '1',
encrypted_device: encryptedDevice
};
// Préserver encrypted_wallet s'il existe
if (encryptedWallet) {
walletObject.encrypted_wallet = encryptedWallet;
}
// Définir les handlers de transaction avant le put
transaction.oncomplete = async () => {
console.log('✅ Transaction completed for wallet save');
// Vérifier que le wallet a bien été sauvegardé en le récupérant depuis la base
try {
const verificationDb = await new Promise<IDBDatabase>((resolveDb, rejectDb) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolveDb(request.result);
request.onerror = () => rejectDb(request.error);
});
const verificationTx = verificationDb.transaction([walletStore], 'readonly');
const verificationStore = verificationTx.objectStore(walletStore);
const verifyRequest = verificationStore.get('1');
await new Promise<void>((resolveVerify, rejectVerify) => {
verifyRequest.onsuccess = () => {
const savedData = verifyRequest.result;
if (savedData?.encrypted_device === encryptedDevice) {
console.log('✅ Verified: Device correctly saved in database');
resolveVerify();
} else {
console.error('❌ Verification failed: Device not found or encrypted data mismatch');
rejectVerify(new Error('Device save verification failed'));
}
};
verifyRequest.onerror = () => {
console.error('❌ Verification failed: Could not retrieve saved device', verifyRequest.error);
rejectVerify(verifyRequest.error);
};
});
resolve();
} catch (verifyError) {
reject(verifyError);
}
};
transaction.onerror = () => {
console.error('❌ Transaction failed:', transaction.error);
reject(transaction.error);
};
const putRequest = store.put(walletObject);
putRequest.onsuccess = () => {
console.log('✅ Device saved to database with encryption');
// La vérification se fera dans transaction.oncomplete
};
putRequest.onerror = () => {
console.error('❌ Failed to save wallet:', putRequest.error);
reject(putRequest.error);
};
});
} catch (e) {
console.error(e);
console.error('❌ Error saving device to database:', e);
throw e;
}
}
async getDeviceFromDatabase(): Promise<Device | null> {
const db = await Database.getInstance();
console.log('🔍 DEBUG: getDeviceFromDatabase - attempting to get wallet with key "1"');
// Utiliser directement IndexedDB au lieu du service Database pour éviter les problèmes de service worker
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
const walletStore = DATABASE_CONFIG.stores.wallet.name;
console.log('🔍 DEBUG: getDeviceFromDatabase - db opened directly, objectStoreNames:', Array.from(db.objectStoreNames));
try {
const dbRes = await db.getObject(walletStore, '1');
const dbRes = await new Promise<any>((resolve, reject) => {
const tx = db.transaction(walletStore, 'readonly');
const store = tx.objectStore(walletStore);
const getRequest = store.get('1');
getRequest.onsuccess = () => resolve(getRequest.result);
getRequest.onerror = () => reject(getRequest.error);
});
console.log('🔍 DEBUG: getDeviceFromDatabase - db.getObject result:', dbRes);
if (!dbRes) {
console.log('🔍 DEBUG: getDeviceFromDatabase - no data found for key "1"');
return null;
}
@ -1847,18 +2035,24 @@ export default class Services {
const secureCredentialsService = SecureCredentialsService.getInstance();
// Get all security modes to find which one works
const allSecurityModes = ['otp', 'password', 'none', 'os', 'proton-pass'];
// Mettre 'none' en premier pour éviter d'ouvrir la fenêtre du navigateur
const allSecurityModes = ['none', 'otp', 'password', 'os', 'proton-pass'];
let pbkdf2Key: string | null = null;
let workingMode: string | null = null;
for (const mode of allSecurityModes) {
try {
// Vérifier d'abord silencieusement si une clé existe
const hasKey = await secureCredentialsService.hasPBKDF2Key(mode as any);
if (hasKey) {
// Si une clé existe, essayer de la récupérer
const key = await secureCredentialsService.retrievePBKDF2Key(mode as any);
if (key) {
pbkdf2Key = key;
workingMode = mode;
break;
}
}
} catch (e) {
// Continue to next mode
}
@ -2013,7 +2207,7 @@ export default class Services {
has_spend_key: !!device.sp_wallet?.spend_key,
has_scan_key: !!device.sp_wallet?.scan_key,
birthday: device.sp_wallet?.birthday,
sp_address: device.sp_address
sp_address: device.sp_wallet?.address
});
await this.saveDeviceInDatabase(device);
@ -2132,21 +2326,51 @@ export default class Services {
try {
// First set the updated device in memory
this.sdkClient.restore_device(device);
console.log('✅ Device restored in memory with updated birthday');
// Vérifier que le device a été restauré en mémoire
const restoredDevice = this.dumpDeviceFromMemory();
if (restoredDevice?.sp_wallet?.birthday === device.sp_wallet.birthday) {
console.log('✅ Device restored in memory with updated birthday:', device.sp_wallet.birthday);
} else {
throw new Error(`Device restoration failed: expected birthday ${device.sp_wallet.birthday}, got ${restoredDevice?.sp_wallet?.birthday}`);
}
// Then save it to database
await this.saveDeviceInDatabase(device);
console.log('✅ Device saved to database with updated birthday');
// Vérifier que le device a été sauvegardé en base de données
const savedDevice = await this.getDeviceFromDatabase();
if (savedDevice?.sp_wallet?.birthday === device.sp_wallet.birthday) {
console.log('✅ Device saved to database with updated birthday:', device.sp_wallet.birthday);
} else {
throw new Error(`Device save verification failed: expected birthday ${device.sp_wallet.birthday}, got ${savedDevice?.sp_wallet?.birthday}`);
}
// For new wallets, perform initial scan to catch any existing transactions
console.log(`🔄 Performing initial scan for new wallet from block ${device.sp_wallet.birthday} to ${this.currentBlockHeight}...`);
await this.sdkClient.scan_blocks(this.currentBlockHeight, BLINDBITURL);
// Vérifier que le scan est terminé en vérifiant last_scan
const deviceAfterScan = this.dumpDeviceFromMemory();
if (deviceAfterScan?.sp_wallet?.last_scan === this.currentBlockHeight) {
console.log('✅ Initial scan completed for new wallet');
} else {
console.warn(`⚠️ Initial scan may not be complete: expected last_scan ${this.currentBlockHeight}, got ${deviceAfterScan?.sp_wallet?.last_scan}`);
}
// Update last_scan to current block height
device.sp_wallet.last_scan = this.currentBlockHeight;
await this.saveDeviceInDatabase(device);
console.log('✅ New wallet initial scan completed');
// Vérifier que le device a été sauvegardé avec last_scan mis à jour
const finalDevice = await this.getDeviceFromDatabase();
if (finalDevice?.sp_wallet?.last_scan === this.currentBlockHeight) {
console.log('✅ New wallet initial scan completed and saved');
} else {
throw new Error(`Final save verification failed: expected last_scan ${this.currentBlockHeight}, got ${finalDevice?.sp_wallet?.last_scan}`);
}
console.log('✅ updateDeviceBlockHeight completed successfully for new device');
return;
} catch (e) {
throw new Error(`Failed to save updated device: ${e}`);
}

View File

@ -106,11 +106,6 @@ export async function retrieveData(servers: string[], key: string): Promise<Arra
return null;
}
// interface TestResponse { // Unused interface
// key: string;
// value: boolean;
// }
export async function testData(url: string, _key: string): Promise<boolean | null> {
try {
const response = await axios.get(url);

View File

@ -0,0 +1,83 @@
/**
* Utilitaires pour vérifier les prérequis de l'application
* Centralise la logique de vérification pour éviter la duplication
*/
import { SecureCredentialsService } from '../services/secure-credentials.service';
import { DeviceReaderService } from '../services/device-reader.service';
export type SecurityMode = 'none' | 'otp' | 'password' | 'os' | 'proton-pass';
const ALL_SECURITY_MODES: SecurityMode[] = ['none', 'otp', 'password', 'os', 'proton-pass'];
/**
* Vérifie si une clé PBKDF2 existe dans le store pbkdf2keys
* @returns La clé PBKDF2 trouvée et le mode de sécurité associé, ou null si aucune clé n'est trouvée
*/
export async function checkPBKDF2Key(): Promise<{ key: string; mode: SecurityMode } | null> {
const secureCredentialsService = SecureCredentialsService.getInstance();
for (const mode of ALL_SECURITY_MODES) {
try {
const hasKey = await secureCredentialsService.hasPBKDF2Key(mode);
if (hasKey) {
const key = await secureCredentialsService.retrievePBKDF2Key(mode);
if (key) {
console.log(`✅ PBKDF2 key found in pbkdf2keys store for security mode: ${mode}`);
return { key, mode };
}
}
} catch (error) {
// Continue to next mode
console.log(`⚠️ No PBKDF2 key found in pbkdf2keys store for mode ${mode}`);
}
}
return null;
}
/**
* Vérifie si le wallet existe en base de données avec retries pour gérer la synchronisation
* @param maxAttempts Nombre maximum de tentatives
* @param delayMs Délai entre les tentatives en millisecondes
* @returns Le wallet trouvé ou null si non trouvé après toutes les tentatives
*/
export async function checkWalletWithRetries(
maxAttempts: number = 5,
delayMs: number = 500
): Promise<any | null> {
const deviceReader = DeviceReaderService.getInstance();
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const wallet = await deviceReader.getDeviceFromDatabase();
if (wallet) {
if (attempt > 0) {
console.log(`✅ Wallet found after ${attempt + 1} attempts`);
}
return wallet;
}
if (attempt < maxAttempts - 1) {
console.log(`⚠️ Wallet not found, waiting for database synchronization (attempt ${attempt + 1}/${maxAttempts})...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
console.log(`⚠️ Wallet still not found after ${maxAttempts} attempts`);
return null;
}
/**
* Vérifie tous les prérequis nécessaires pour une page
* @returns Objet avec les résultats des vérifications
*/
export async function checkAllPrerequisites(): Promise<{
pbkdf2Key: { key: string; mode: SecurityMode } | null;
wallet: any | null;
}> {
const pbkdf2Key = await checkPBKDF2Key();
const wallet = pbkdf2Key ? await checkWalletWithRetries() : null;
return { pbkdf2Key, wallet };
}

144
test-birthday-setup.html Normal file
View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Birthday Setup</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.log {
background: #f5f5f5;
border: 1px solid #ddd;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
max-height: 400px;
overflow-y: auto;
}
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover {
background: #0056b3;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Test Birthday Setup - Debug Device Not Found</h1>
<div>
<button onclick="testWalletCreation()">1. Créer Wallet</button>
<button onclick="testDeviceRetrieval()">2. Récupérer Device</button>
<button onclick="testBirthdaySetup()">3. Test Birthday Setup</button>
<button onclick="clearLogs()">Effacer Logs</button>
</div>
<div id="logs" class="log"></div>
<script type="module">
import Services from './src/services/service.js';
import { DATABASE_CONFIG } from './src/services/database-config.js';
const logs = document.getElementById('logs');
function log(message) {
const timestamp = new Date().toLocaleTimeString();
logs.textContent += `[${timestamp}] ${message}\n`;
logs.scrollTop = logs.scrollHeight;
console.log(message);
}
function clearLogs() {
logs.textContent = '';
}
window.clearLogs = clearLogs;
async function testWalletCreation() {
log('🔄 Test: Création du wallet...');
try {
// Simuler la création du wallet comme dans wallet-setup.ts
const services = await Services.getInstance();
log('✅ Services initialisés');
// Créer un device avec le SDK
const device = services.sdkClient.create_device(0);
log('✅ Device créé avec le SDK');
// Sauvegarder le device
await services.saveDeviceInDatabase(device);
log('✅ Device sauvegardé dans la base de données');
log('🎉 Test de création du wallet réussi!');
} catch (error) {
log(`❌ Erreur lors de la création du wallet: ${error.message}`);
}
}
async function testDeviceRetrieval() {
log('🔄 Test: Récupération du device...');
try {
const services = await Services.getInstance();
log('✅ Services initialisés');
const device = await services.getDeviceFromDatabase();
if (device) {
log('✅ Device récupéré avec succès');
log(`🔍 Device details: ${JSON.stringify({
hasSpWallet: !!device.sp_wallet,
birthday: device.sp_wallet?.birthday,
address: device.sp_wallet?.address
}, null, 2)}`);
} else {
log('❌ Aucun device trouvé dans la base de données');
}
} catch (error) {
log(`❌ Erreur lors de la récupération du device: ${error.message}`);
}
}
async function testBirthdaySetup() {
log('🔄 Test: Birthday Setup complet...');
try {
const services = await Services.getInstance();
log('✅ Services initialisés');
// Connexion aux relais
await services.connectAllRelays();
log('✅ Relays connectés');
// Mettre à jour la date anniversaire
await services.updateDeviceBlockHeight();
log('✅ Birthday updated successfully');
log('🎉 Test de birthday setup réussi!');
} catch (error) {
log(`❌ Erreur lors du birthday setup: ${error.message}`);
}
}
window.testWalletCreation = testWalletCreation;
window.testDeviceRetrieval = testDeviceRetrieval;
window.testBirthdaySetup = testBirthdaySetup;
log('🚀 Test page chargée. Cliquez sur les boutons pour tester.');
</script>
</body>
</html>