ci: docker_tag=dev-test
Motivations: - Fix 502 en prod via build multi-pages et routes stables - Stabilize IndexedDB (création stores) et faucet obligatoire - Déploiement robuste: port 3004 garanti et logs Modifications: - Vite: inputs multi-pages, alias npm deploy:front - Router/redirects: chemins - Pages setup: URLs corrigées, suppression log faucet disabled - DB: bump version à 5, upgrade crée stores manquants - Script: scripts/deploy_front.sh (kill 3004, clean, build, start bg) - Lint: perf monitor param non utilisé, warnings corrigés Page affectées: - vite.config.ts, src/router.ts - src/pages/* (home, pairing, block-sync, wallet-setup, security-setup) - src/services/* (database-config, performance-monitor) - package.json, scripts/deploy_front.sh
This commit is contained in:
parent
25e0272959
commit
bd1762ee0c
@ -118,7 +118,6 @@ voir les fichiers README.md
|
||||
#### 🚀 Déploiement
|
||||
|
||||
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
|
||||
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
|
||||
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
|
||||
* **Lancement :** ne lance aucun déploiement sans demander avant
|
||||
|
||||
|
||||
360
.cursor/rules/project-analysis.mdc
Normal file
360
.cursor/rules/project-analysis.mdc
Normal file
@ -0,0 +1,360 @@
|
||||
---
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# IHM_CLIENT - Analyse du Projet
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Application client Web5 pour l'écosystème 4NK permettant la gestion sécurisée des appareils, le pairing et les signatures de documents.
|
||||
|
||||
## Architecture Technique
|
||||
|
||||
### Stack Technologique
|
||||
- **Frontend**: TypeScript, Vite, HTML5, CSS3
|
||||
- **SDK**: Rust compilé en WebAssembly
|
||||
- **Storage**: IndexedDB, Service Workers
|
||||
- **Communication**: WebSockets, PostMessage API
|
||||
- **Construction**: Vite avec plugins WASM et top-level await
|
||||
|
||||
### Configuration
|
||||
- **Port**: 3004
|
||||
- **URL**: https://dev3.4nkweb.com
|
||||
- **Proxy**: `/storage` → https://dev3.4nkweb.com/storage
|
||||
- **Base URL**: Variables d'environnement VITE_BASEURL, VITE_BOOTSTRAPURL, VITE_STORAGEURL, VITE_BLINDBITURL
|
||||
|
||||
### Structure du Projet
|
||||
|
||||
```
|
||||
ihm_client_dev3/
|
||||
├── src/
|
||||
│ ├── components/ # Composants UI réutilisables
|
||||
│ │ ├── account-nav/
|
||||
│ │ ├── device-management/
|
||||
│ │ ├── iframe-pairing/
|
||||
│ │ ├── login-modal/
|
||||
│ │ ├── secure-credentials/
|
||||
│ │ ├── security-mode-selector/
|
||||
│ │ └── validation-modal/
|
||||
│ ├── pages/ # Pages de l'application
|
||||
│ │ ├── account/
|
||||
│ │ ├── birthday-setup/
|
||||
│ │ ├── block-sync/
|
||||
│ │ ├── home/
|
||||
│ │ ├── pairing/
|
||||
│ │ ├── security-setup/
|
||||
│ │ └── wallet-setup/
|
||||
│ ├── services/ # Services métier
|
||||
│ │ ├── service.ts # Service principal (singleton)
|
||||
│ │ ├── database.service.ts
|
||||
│ │ ├── secure-credentials.service.ts
|
||||
│ │ ├── security-mode.service.ts
|
||||
│ │ ├── pairing.service.ts
|
||||
│ │ ├── iframe-pairing.service.ts
|
||||
│ │ └── websocket-manager.ts
|
||||
│ ├── repositories/ # Accès aux données
|
||||
│ │ ├── device.repository.ts
|
||||
│ │ └── process.repository.ts
|
||||
│ ├── models/ # Types et interfaces
|
||||
│ ├── utils/ # Utilitaires
|
||||
│ ├── service-workers/ # Workers pour opérations async
|
||||
│ ├── router.ts # Router principal
|
||||
│ └── main.ts # Point d'entrée
|
||||
├── pkg/ # SDK WebAssembly compilé
|
||||
├── docs/ # Documentation
|
||||
├── IA_agents/ # Documentation pour IA
|
||||
├── test-browser/ # Tests Playwright
|
||||
└── logs/ # Logs centralisés
|
||||
```
|
||||
|
||||
## Architecture Fonctionnelle
|
||||
|
||||
### Flux d'Initialisation
|
||||
|
||||
1. **Security Setup** → Configuration du mode de sécurité
|
||||
2. **Wallet Setup** → Création du wallet Bitcoin
|
||||
3. **Birthday Setup** → Configuration de la date anniversaire
|
||||
4. **Block Sync** → Synchronisation initiale des blocs
|
||||
5. **Pairing** → Appairage de l'appareil
|
||||
6. **Account** → Interface principale
|
||||
|
||||
### Système de Modes de Sécurité
|
||||
|
||||
| Mode | Nom | Niveau | Stockage Clé PBKDF2 |
|
||||
|------|-----|--------|---------------------|
|
||||
| `proton-pass` | Proton Pass | High | WebAuthn du navigateur |
|
||||
| `os` | OS Authenticator | High | WebAuthn du système |
|
||||
| `otp` | OTP | High | En clair |
|
||||
| `password` | Mot de passe | Low | Chiffré avec mot de passe |
|
||||
| `none` | Aucune | Critical | Clé en dur (déconseillé) |
|
||||
|
||||
### Base de Données IndexedDB
|
||||
|
||||
**Stores:**
|
||||
- `pbkdf2keys` : Clés PBKDF2 chiffrées par mode de sécurité
|
||||
- `wallet` : Wallet chiffré (device + wallet data)
|
||||
- `credentials` : Credentials de pairing (après pairing)
|
||||
- `env` : Variables d'environnement internes
|
||||
- `processes` : Processus de communication
|
||||
- `labels` : Labels de transactions
|
||||
- `shared_secrets` : Secrets partagés
|
||||
- `unconfirmed_secrets` : Secrets non confirmés
|
||||
- `diffs` : Différences de synchronisation
|
||||
- `data` : Données générales
|
||||
|
||||
### Système de Processus
|
||||
|
||||
Le système de processus est générique et réutilisable pour créer des "contrats" entre des membres avec niveaux d'accès différents.
|
||||
|
||||
**Concepts:**
|
||||
- **Process**: Contrat décentralisé entre plusieurs membres
|
||||
- **State**: État du processus avec données publiques/privées
|
||||
- **Roles**: Définition des permissions par rôle
|
||||
- **Members**: Participants identifiés par pairing process ID
|
||||
|
||||
**Données:**
|
||||
- **Public**: Accessibles à tous, portées automatiquement
|
||||
- **Private**: Chiffrées via PCD commitments, distribuées aux membres autorisés
|
||||
- **Roles**: Quorum et champs accessibles par rôle
|
||||
|
||||
**Cycle de vie:**
|
||||
1. Création → `createProcess()`
|
||||
2. Mise à jour → `updateProcess()`
|
||||
3. Synchronisation PRD → `createPrdUpdate()`
|
||||
4. Validation → `approveChange()`
|
||||
5. Commit blockchain → État immuable
|
||||
6. Accès → `getPublicData()` / `decryptAttribute()`
|
||||
|
||||
### Système de Pairing
|
||||
|
||||
**But**: Créer une identité numérique vérifiable pour MFA entre appareils
|
||||
|
||||
**Caractéristiques:**
|
||||
- Un wallet peut être appairé à plusieurs appareils
|
||||
- Processus blockchain avec états commités
|
||||
- Synchronisation via relais
|
||||
- Contrôle via 4 mots
|
||||
|
||||
**Flux Créateur:**
|
||||
1. `createPairingProcess()` avec son adresse
|
||||
2. `generateQRCode()` pour le joiner
|
||||
3. `waitForJoinerAndUpdateProcess()`
|
||||
4. `waitForPairingCommitment()`
|
||||
5. `confirmPairing()`
|
||||
|
||||
**Flux Joiner:**
|
||||
1. `discoverAndJoinPairingProcess()` via QR code
|
||||
2. `waitForPairingCommitment()`
|
||||
3. `confirmPairing()`
|
||||
|
||||
## Services Principaux
|
||||
|
||||
### Services (Singleton)
|
||||
|
||||
**Initialisation:**
|
||||
- WebAssembly SDK
|
||||
- WebSocket connections
|
||||
- Database restoration
|
||||
- Process listening
|
||||
|
||||
**Principales méthodes:**
|
||||
- `getDeviceFromDatabase()` : Récupération du device
|
||||
- `createPairingProcess()` : Création de processus de pairing
|
||||
- `createProcess()` : Création de processus générique
|
||||
- `updateProcess()` : Mise à jour de processus
|
||||
- `getProcess()` : Récupération de processus
|
||||
- `checkConnections()` : Vérification des connexions entre membres
|
||||
- `decryptAttribute()` : Déchiffrement d'attribut privé
|
||||
- `getPublicData()` : Récupération des données publiques
|
||||
|
||||
### Database Service
|
||||
|
||||
**Gestion IndexedDB:**
|
||||
- Singleton avec service worker
|
||||
- Stores configurables via `storeDefinitions`
|
||||
- Transactions asynchrones
|
||||
- Cache management
|
||||
|
||||
### Secure Credentials Service
|
||||
|
||||
**Gestion des credentials:**
|
||||
- Génération et stockage de credentials de pairing
|
||||
- Récupération avec déchiffrement
|
||||
- Support WebAuthn pour modes haute sécurité
|
||||
|
||||
### Security Mode Service
|
||||
|
||||
**Gestion des modes:**
|
||||
- Détection du mode actuel
|
||||
- Stockage dans IndexedDB
|
||||
- Authentification selon mode
|
||||
|
||||
## Logging et Erreurs
|
||||
|
||||
### SecureLogger
|
||||
|
||||
**Système centralisé:**
|
||||
- Niveaux: DEBUG, INFO, WARN, ERROR
|
||||
- Sanitisation automatique des données sensibles
|
||||
- Contexte enrichi avec composant et métadonnées
|
||||
- Formatage cohérent
|
||||
|
||||
**Bonnes pratiques:**
|
||||
- Utiliser `secureLogger` au lieu de `console.*`
|
||||
- Toujours spécifier le composant
|
||||
- Ajouter métadonnées utiles
|
||||
- Messages clairs et concis
|
||||
- Vérifications réelles avant logs de succès
|
||||
|
||||
**Patterns:**
|
||||
- 🔍 DEBUG : Informations de débogage
|
||||
- ✅ INFO : Succès et initialisations
|
||||
- ⚠️ WARN : Avertissements non critiques
|
||||
- ❌ ERROR : Erreurs critiques
|
||||
|
||||
## Router
|
||||
|
||||
### Navigation
|
||||
|
||||
**Logique de progression:**
|
||||
1. Si pairing → account
|
||||
2. Si date anniversaire → pairing
|
||||
3. Si wallet → birthday-setup
|
||||
4. Si pbkdf2 → wallet-setup
|
||||
5. Sinon → security-setup
|
||||
|
||||
**Routes principales:**
|
||||
- `/security-setup` : Configuration sécurité
|
||||
- `/wallet-setup` : Création wallet
|
||||
- `/birthday-setup` : Configuration birthday
|
||||
- `/block-sync` : Synchronisation blocs
|
||||
- `/pairing` : Appairage
|
||||
- `/account` : Interface principale
|
||||
- `/home` : Page d'accueil avec login
|
||||
|
||||
### Message Handlers
|
||||
|
||||
Gestion des messages PostMessage pour communication iframe:
|
||||
- `REQUEST_LINK` : Liaison avec site externe
|
||||
- `CREATE_PAIRING` : Création de pairing
|
||||
- `GET_PROCESSES` : Récupération des processus
|
||||
- `CREATE_PROCESS` : Création de processus
|
||||
- `UPDATE_PROCESS` : Mise à jour de processus
|
||||
- `VALIDATE_STATE` : Validation d'état
|
||||
- Etc.
|
||||
|
||||
## WebSocket
|
||||
|
||||
**Gestion:**
|
||||
- Connexions multiples (BOOTSTRAP, STORAGE, BLINDBIT)
|
||||
- Handshake automatique
|
||||
- Retry automatique en cas de déconnexion
|
||||
- Event bus pour messages
|
||||
|
||||
**Messages:**
|
||||
- HandshakeMessage : Synchronisation initiale
|
||||
- NewTxMessage : Nouvelles transactions
|
||||
- Process updates : Mises à jour de processus
|
||||
|
||||
## Dépendances
|
||||
|
||||
**Principales:**
|
||||
- `axios` : Requêtes HTTP
|
||||
- `jose` : JWT et chiffrement
|
||||
- `jsonwebtoken` : Tokens JWT
|
||||
- `pdf-lib` : Manipulation PDF
|
||||
- `sweetalert2` : Modals UI
|
||||
|
||||
**SDK WebAssembly:**
|
||||
- `pkg/sdk_client.js` : SDK principal
|
||||
- `pkg/sdk_client_bg.wasm` : Binaire WebAssembly
|
||||
- Types TypeScript générés
|
||||
|
||||
## Tests
|
||||
|
||||
**Structure:**
|
||||
- `test-browser/` : Tests Playwright
|
||||
- Tests pour chaque parcours utilisateur
|
||||
- Intégration avec mocked data
|
||||
|
||||
## User Stories
|
||||
|
||||
34 stories définies couvrant:
|
||||
- Login (adresse device, QR code)
|
||||
- Process management
|
||||
- Account management
|
||||
- Pairing
|
||||
- Wallet
|
||||
- Chat
|
||||
- Signatures
|
||||
|
||||
## Configuration TypeScript
|
||||
|
||||
**Strict Mode:**
|
||||
- `strict: true`
|
||||
- `noImplicitAny: true`
|
||||
- `noImplicitReturns: true`
|
||||
- `forceConsistentCasingInFileNames: true`
|
||||
|
||||
**Modules:**
|
||||
- `module: ESNext`
|
||||
- `target: ESNext`
|
||||
- `lib: ["DOM", "DOM.Iterable", "ESNext", "webworker"]`
|
||||
- `isolatedModules: true`
|
||||
|
||||
## Points Critiques
|
||||
|
||||
### Sécurité
|
||||
- Clés PBKDF2 toujours chiffrées (sauf mode OTP)
|
||||
- Wallet toujours chiffré en base
|
||||
- WebAuthn pour modes haute sécurité
|
||||
- Sanitisation automatique des logs
|
||||
|
||||
### Performance
|
||||
- Cache désactivé (`maxCacheSize = 0`)
|
||||
- Memory management avec retry
|
||||
- Lazy loading des composants
|
||||
- Service workers pour opérations async
|
||||
|
||||
### Robustesse
|
||||
- Retry automatique pour opérations critiques
|
||||
- Vérifications réelles avant logs de succès
|
||||
- Fallback et navigation automatique
|
||||
- Gestion des erreurs IndexedDB
|
||||
|
||||
## Documentation
|
||||
|
||||
**Fichiers clés:**
|
||||
- `docs/INITIALIZATION_FLOW.md` : Flux d'initialisation détaillé
|
||||
- `docs/PROCESS_SYSTEM_ARCHITECTURE.md` : Architecture des processus
|
||||
- `docs/PAIRING_SYSTEM_ANALYSIS.md` : Analyse du pairing
|
||||
- `docs/LOGGING_GUIDELINES.md` : Guide de logging
|
||||
- `IA_agents/all.md` : Règles pour IA
|
||||
- `README.md` : Vue d'ensemble
|
||||
|
||||
## Développement
|
||||
|
||||
**Scripts:**
|
||||
- `npm run start` : Serveur de développement
|
||||
- `npm run build` : Build de production
|
||||
- `npm run quality` : Vérification qualité
|
||||
- `npm run lint` : Linting
|
||||
- `npm run type-check` : Vérification TypeScript
|
||||
|
||||
**Prérequis:**
|
||||
- Node.js 18+
|
||||
- Rust (pour SDK)
|
||||
- npm ou yarn
|
||||
|
||||
## Points d'Attention
|
||||
|
||||
1. **Ordre des modes testés**: `['none', 'otp', 'password', 'os', 'proton-pass']`
|
||||
2. **Store credentials**: Utilisé uniquement après pairing
|
||||
3. **Redirection automatique**: 3s après création wallet vers birthday-setup
|
||||
4. **Synchronisation IndexedDB**: Utilisation directe pour éviter problèmes service worker
|
||||
5. **Retry automatique**: Jusqu'à 5 tentatives pour vérifications wallet
|
||||
6. **Vérifications réelles**: Logs de succès uniquement après vérification
|
||||
7. **WebAssembly memory**: Monitoring et cleanup automatique
|
||||
8. **Singleton patterns**: Services, Database, ModalService
|
||||
9. **Message handlers**: Tous nécessitent validation token
|
||||
10. **Process lifecycle**: Toujours vérifier état committé avant manipulation
|
||||
@ -1,26 +0,0 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
dist/
|
||||
pkg/
|
||||
|
||||
# Build outputs
|
||||
*.js
|
||||
*.d.ts
|
||||
!eslint.config.js
|
||||
|
||||
# Config files
|
||||
vite.config.ts
|
||||
tsconfig.json
|
||||
tsconfig.build.json
|
||||
|
||||
# Test files
|
||||
**/*.test.ts
|
||||
**/*.spec.ts
|
||||
|
||||
# Generated files
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
# 🦀 Rust
|
||||
# ----------------------------
|
||||
target/
|
||||
pkg/
|
||||
Cargo.lock
|
||||
*.rs.bk
|
||||
**/*.rlib
|
||||
@ -30,7 +29,6 @@ pnpm-lock.yaml
|
||||
# ----------------------------
|
||||
# 🧱 IDE / Éditeurs
|
||||
# ----------------------------
|
||||
.vscode/
|
||||
.idea/
|
||||
*.iml
|
||||
*.suo
|
||||
|
||||
8
.vscode/extensions.json
vendored
Normal file
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"ms-vscode.vscode-typescript-next"
|
||||
]
|
||||
}
|
||||
|
||||
59
.vscode/settings.json
vendored
Normal file
59
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
// ESLint Configuration
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"eslint.workingDirectories": ["."],
|
||||
"eslint.options": {
|
||||
"overrideConfigFile": "eslint.config.js"
|
||||
},
|
||||
|
||||
// TypeScript Configuration
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"typescript.tsserver.maxTsServerMemory": 4096,
|
||||
|
||||
// Problems Panel Configuration
|
||||
"problems.showCurrentInStatus": true,
|
||||
"problems.autoReveal": true,
|
||||
|
||||
// File Associations
|
||||
"files.associations": {
|
||||
"*.ts": "typescript",
|
||||
"*.tsx": "typescriptreact"
|
||||
},
|
||||
|
||||
// Editor Configuration
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
|
||||
// TypeScript Specific
|
||||
"typescript.suggest.autoImports": true,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
|
||||
// Exclude patterns for file watcher
|
||||
"files.watcherExclude": {
|
||||
"**/node_modules/**": true,
|
||||
"**/dist/**": true,
|
||||
"**/pkg/**": true,
|
||||
"**/logs/**": true,
|
||||
"**/.git/**": true
|
||||
},
|
||||
|
||||
// Search exclusion
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
"**/dist": true,
|
||||
"**/pkg": true,
|
||||
"**/logs": true,
|
||||
"**/.git": true
|
||||
}
|
||||
}
|
||||
@ -94,7 +94,6 @@ voir les fichiers README.md
|
||||
#### 🚀 Déploiement
|
||||
|
||||
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
|
||||
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
|
||||
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
|
||||
* **Lancement :** ne lance aucun déploiement sans demander avant
|
||||
|
||||
|
||||
@ -95,7 +95,6 @@ voir docs/INITIALIZATION_FLOW.md
|
||||
#### 🚀 Déploiement
|
||||
|
||||
* **Préparation du déploiement :** Décrire et préparer le déploiement des correctifs et des évolutions.
|
||||
* **Script de déploiement :** le déploiment passe par `deploy-remote.sh`, ne masque pas la sortie (pas de 2>&1 pas exemple).
|
||||
* **Bilan de déloploiement :** ne fait pas de bilan de déploiement.
|
||||
* **Lancement :** ne lance aucun déploiement sans demander avant
|
||||
|
||||
|
||||
@ -8,11 +8,11 @@
|
||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev3/pkg ../sdk_client --target bundler --dev",
|
||||
"start": "vite --host 0.0.0.0",
|
||||
"build": "tsc && vite build",
|
||||
"deploy": "sudo cp -r dist/* /var/www/html/",
|
||||
"deploy:front": "./scripts/deploy_front.sh",
|
||||
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\"",
|
||||
"build:dist": "tsc -p tsconfig.build.json",
|
||||
"lint": "eslint src/ --ext .ts,.tsx --fix",
|
||||
"lint:check": "eslint src/ --ext .ts,.tsx",
|
||||
"lint": "eslint src/ --fix",
|
||||
"lint:check": "eslint src/",
|
||||
"type-check": "tsc --noEmit",
|
||||
"quality": "npm run prettify",
|
||||
"quality:strict": "npm run type-check && npm run lint:check && npm run prettify",
|
||||
|
||||
68
scripts/deploy_front.sh
Executable file
68
scripts/deploy_front.sh
Executable file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
LOG_DIR="$PROJECT_ROOT/logs"
|
||||
PID_FILE="$LOG_DIR/ihm_client_dev3.front.pid"
|
||||
LOG_FILE="$LOG_DIR/ihm_client_dev3.front.log"
|
||||
PORT=3004
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
echo "[deploy] Ensuring nothing listens on :$PORT..."
|
||||
if ss -ltnp | grep -q ":$PORT"; then
|
||||
# Extract PID(s) for the port
|
||||
PIDS=$(ss -ltnp | awk -v p=":$PORT" '$0 ~ p {print $NF}' | sed -E 's/.*pid=([0-9]+).*/\1/' | sort -u)
|
||||
for PID in $PIDS; do
|
||||
if [[ "$PID" =~ ^[0-9]+$ ]]; then
|
||||
echo "[deploy] Killing PID $PID on port $PORT"
|
||||
kill -TERM "$PID" || true
|
||||
# Wait up to 10s for process to exit
|
||||
for i in {1..10}; do
|
||||
if ! ps -p "$PID" >/dev/null 2>&1; then break; fi
|
||||
sleep 1
|
||||
done
|
||||
if ps -p "$PID" >/dev/null 2>&1; then
|
||||
echo "[deploy] Force killing PID $PID"
|
||||
kill -KILL "$PID" || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[deploy] Cleaning Vite caches and previous dist..."
|
||||
rm -rf "$PROJECT_ROOT/node_modules/.vite" "$PROJECT_ROOT/.vite" "$PROJECT_ROOT/dist"
|
||||
|
||||
echo "[deploy] Building production bundle..."
|
||||
cd "$PROJECT_ROOT"
|
||||
npm run build
|
||||
|
||||
echo "[deploy] Starting Vite dev server on :$PORT (non-bloquant) ..."
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
# Clean stale PID file if any
|
||||
OLD_PID=$(cat "$PID_FILE" || true)
|
||||
if [[ -n "${OLD_PID}" ]] && ps -p "$OLD_PID" >/dev/null 2>&1; then
|
||||
echo "[deploy] Previous PID $OLD_PID still running, terminating"
|
||||
kill -TERM "$OLD_PID" || true
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
|
||||
nohup npm run start >"$LOG_FILE" 2>&1 &
|
||||
NEW_PID=$!
|
||||
echo "$NEW_PID" > "$PID_FILE"
|
||||
|
||||
echo "[deploy] Launched PID $NEW_PID. Tail logs: tail -f $LOG_FILE"
|
||||
|
||||
echo "[deploy] Verifying port $PORT availability..."
|
||||
for i in {1..10}; do
|
||||
if ss -ltnp | grep -q ":$PORT"; then
|
||||
echo "[deploy] OK: port $PORT is listening."
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "[deploy] ERROR: port $PORT not listening after start. Check $LOG_FILE"
|
||||
exit 1
|
||||
@ -232,7 +232,7 @@ export class SecureCredentialsComponent {
|
||||
try {
|
||||
await this.updateUI();
|
||||
this.showMessage('Credentials actualisés', 'success');
|
||||
} catch (_error) {
|
||||
} catch {
|
||||
this.showMessage('Erreur lors de l\'actualisation', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -22,6 +22,7 @@ export interface INotification {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum MessageType {
|
||||
// Establish connection and keep alive
|
||||
LISTENING = 'LISTENING',
|
||||
@ -75,3 +76,4 @@ export enum MessageType {
|
||||
PAIRING_4WORDS_SUCCESS = 'PAIRING_4WORDS_SUCCESS',
|
||||
PAIRING_4WORDS_ERROR = 'PAIRING_4WORDS_ERROR',
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
@ -5,201 +5,237 @@
|
||||
|
||||
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
|
||||
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
import { secureLogger } from '../../services/secure-logger';
|
||||
let isInitializing = false;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (isInitializing) {
|
||||
secureLogger.warn('⚠️ Birthday setup page already initializing, skipping...', { component: 'BirthdaySetup' });
|
||||
return;
|
||||
}
|
||||
// Type definition for update functions - parameters are template names
|
||||
/* eslint-disable no-unused-vars */
|
||||
interface UpdateFunctions {
|
||||
updateStatus: (message: string, type: 'loading' | 'success' | 'error') => void;
|
||||
updateProgress: (percent: number) => void;
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
isInitializing = true;
|
||||
secureLogger.info('🎂 Birthday setup page loaded', { component: 'BirthdaySetup' });
|
||||
secureLogger.debug(`Current URL: ${window.location.href}`, { component: 'BirthdaySetup' });
|
||||
secureLogger.debug(`Referrer: ${document.referrer}`, { component: 'BirthdaySetup' });
|
||||
async function initializeServices() {
|
||||
secureLogger.info('🔄 Importing services...', { component: 'BirthdaySetup' });
|
||||
const serviceModule = await import('../../services/service');
|
||||
secureLogger.debug(`Service module imported: ${Object.keys(serviceModule)}`, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
|
||||
const status = document.getElementById('status') as HTMLDivElement;
|
||||
const progressBar = document.getElementById('progressBar') as HTMLDivElement;
|
||||
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
|
||||
const Services = serviceModule.default;
|
||||
if (!Services) {
|
||||
throw new Error('Services class not found in default export');
|
||||
}
|
||||
|
||||
try {
|
||||
// Étape 1: Connexion aux relais
|
||||
updateStatus('🌐 Connexion aux relais...', 'loading');
|
||||
updateProgress(20);
|
||||
|
||||
try {
|
||||
secureLogger.info('🔄 Importing services...', { component: 'BirthdaySetup' });
|
||||
const serviceModule = await import('../../services/service');
|
||||
secureLogger.debug(`Service module imported: ${Object.keys(serviceModule)}`, { component: 'BirthdaySetup' });
|
||||
|
||||
// La classe Services est exportée par défaut
|
||||
const Services = serviceModule.default;
|
||||
|
||||
if (!Services) {
|
||||
throw new Error('Services class not found in default export');
|
||||
}
|
||||
secureLogger.info('🔄 Getting existing services instance...', { component: 'BirthdaySetup' });
|
||||
|
||||
// Utiliser l'instance existante des services avec gestion des erreurs de mémoire
|
||||
let services;
|
||||
try {
|
||||
services = await Services.getInstance();
|
||||
secureLogger.info('✅ Services instance obtained successfully', { component: 'BirthdaySetup' });
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
|
||||
secureLogger.error('🚫 Memory error detected', { component: 'BirthdaySetup' });
|
||||
updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error');
|
||||
throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Vérifier les prérequis en base de données
|
||||
updateStatus('🔍 Vérification des prérequis...', 'loading');
|
||||
updateProgress(20);
|
||||
|
||||
// Vérifier que le PBKDF2 key existe d'abord (prérequis le plus basique)
|
||||
const pbkdf2KeyResult = await checkPBKDF2Key();
|
||||
if (!pbkdf2KeyResult) {
|
||||
secureLogger.warn('⚠️ PBKDF2 key not found in pbkdf2keys store, redirecting to security-setup...', { component: 'BirthdaySetup' });
|
||||
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que le wallet existe en base (avec plusieurs tentatives pour gérer les problèmes de synchronisation)
|
||||
const wallet = await checkWalletWithRetries();
|
||||
if (!wallet) {
|
||||
secureLogger.warn('⚠️ Wallet still not found after retries, redirecting to wallet-setup...', { component: 'BirthdaySetup' });
|
||||
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que le wallet contient bien les données attendues
|
||||
if (wallet.sp_wallet?.birthday !== undefined) {
|
||||
secureLogger.info(`Wallet found in database with birthday: ${wallet.sp_wallet.birthday}`, { component: 'BirthdaySetup' });
|
||||
} else {
|
||||
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
|
||||
}
|
||||
|
||||
// Vérifier que tous les prérequis sont remplis
|
||||
const prerequisitesOk = wallet && pbkdf2KeyResult;
|
||||
if (prerequisitesOk) {
|
||||
secureLogger.info('✅ All prerequisites verified', { component: 'BirthdaySetup' });
|
||||
} else {
|
||||
throw new Error('Prerequisites verification failed');
|
||||
}
|
||||
|
||||
// Connexion aux relais
|
||||
await services.connectAllRelays();
|
||||
|
||||
// Attendre que la hauteur de bloc soit définie via le handshake
|
||||
updateStatus('⏳ Attente de la synchronisation avec le réseau...', 'loading');
|
||||
updateProgress(40);
|
||||
|
||||
// Attendre que le handshake arrive et que chain_tip soit défini
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Timeout waiting for block height from handshake'));
|
||||
}, 15000); // 15 secondes de timeout
|
||||
|
||||
const checkBlockHeight = () => {
|
||||
const blockHeight = services.getCurrentBlockHeight();
|
||||
if (blockHeight !== -1 && blockHeight > 0) {
|
||||
secureLogger.info('✅ Block height set from handshake: ${blockHeight}', { component: 'BirthdaySetup' });
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkBlockHeight, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkBlockHeight();
|
||||
});
|
||||
|
||||
// Vérifier que les relais sont connectés et que le handshake a été reçu
|
||||
const currentBlockHeight = services.getCurrentBlockHeight();
|
||||
if (currentBlockHeight !== -1 && currentBlockHeight > 0) {
|
||||
secureLogger.info(`Relays connected successfully, chain_tip: ${currentBlockHeight}`, { component: 'BirthdaySetup' });
|
||||
secureLogger.info(`Communication handshake completed, chain_tip: ${currentBlockHeight}`, { component: 'BirthdaySetup' });
|
||||
} else {
|
||||
throw new Error('Handshake not received or chain_tip not set');
|
||||
}
|
||||
|
||||
// Mettre à jour la date anniversaire du wallet
|
||||
updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
|
||||
updateProgress(60);
|
||||
secureLogger.info('🔄 Calling updateDeviceBlockHeight()...', { component: 'BirthdaySetup' });
|
||||
await services.updateDeviceBlockHeight();
|
||||
secureLogger.info('✅ updateDeviceBlockHeight() completed successfully', { component: 'BirthdaySetup' });
|
||||
|
||||
// Vérifier que le birthday a bien été mis à jour en récupérant le wallet depuis la base
|
||||
updateStatus('🔍 Vérification de la mise à jour...', 'loading');
|
||||
updateProgress(70);
|
||||
secureLogger.info('🔄 Verifying birthday update...', { component: 'BirthdaySetup' });
|
||||
const updatedWallet = await services.getDeviceFromDatabase();
|
||||
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
|
||||
secureLogger.info(`Birthday updated successfully: ${updatedWallet.sp_wallet.birthday}`, { component: 'BirthdaySetup' });
|
||||
} else {
|
||||
secureLogger.error('Birthday update verification failed', new Error('Verification failed'), {
|
||||
component: 'BirthdaySetup',
|
||||
data: {
|
||||
birthday: updatedWallet?.sp_wallet?.birthday,
|
||||
hasSpWallet: !!updatedWallet?.sp_wallet
|
||||
}
|
||||
});
|
||||
throw new Error(`Birthday update verification failed: expected birthday > 0, got ${updatedWallet?.sp_wallet?.birthday}`);
|
||||
}
|
||||
|
||||
// Redirection vers la page de synchronisation des blocs
|
||||
updateStatus('🔄 Redirection vers la synchronisation des blocs...', 'loading');
|
||||
updateProgress(100);
|
||||
|
||||
secureLogger.info('🎉 Birthday setup completed successfully - redirecting to block sync', { component: 'BirthdaySetup' });
|
||||
secureLogger.info('📍 Target URL: /src/pages/block-sync/block-sync.html', { component: 'BirthdaySetup' });
|
||||
secureLogger.info('⏰ Redirecting in 1 second...', { component: 'BirthdaySetup' });
|
||||
|
||||
// Rediriger vers la page de synchronisation des blocs
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔄 Executing redirect now...', { component: 'BirthdaySetup' });
|
||||
window.location.href = '/src/pages/block-sync/block-sync.html';
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
secureLogger.error('Services not available', error as Error, { component: 'BirthdaySetup' });
|
||||
updateStatus('❌ Erreur: Services non disponibles', 'error');
|
||||
throw error;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
secureLogger.error('Error during birthday setup', error as Error, { component: 'BirthdaySetup' });
|
||||
updateStatus('❌ Erreur lors de la configuration de la date anniversaire', 'error');
|
||||
} finally {
|
||||
isInitializing = false;
|
||||
}
|
||||
|
||||
// Gestion du bouton continuer
|
||||
continueBtn.addEventListener('click', async () => {
|
||||
secureLogger.info('🏠 Redirecting to main application...', { component: 'BirthdaySetup' });
|
||||
// Rediriger vers l'application principale
|
||||
secureLogger.debug('🎂 Birthday setup completed, checking storage state...', { component: 'BirthdaySetup' });
|
||||
const { checkStorageStateAndNavigate } = await import('../../router');
|
||||
await checkStorageStateAndNavigate();
|
||||
try {
|
||||
const services = await Services.getInstance();
|
||||
secureLogger.info('✅ Services instance obtained successfully', {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
return services;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) {
|
||||
secureLogger.error('🚫 Memory error detected', { component: 'BirthdaySetup' });
|
||||
throw new Error(
|
||||
'WebAssembly initialization failed due to insufficient memory. Please refresh the page.'
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
function redirectToSetup(page: string, updateFunctions: UpdateFunctions): void {
|
||||
secureLogger.warn(`Redirecting to ${page}...`, { component: 'BirthdaySetup' });
|
||||
updateFunctions.updateStatus(`⚠️ Redirection vers ${page}...`, 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = `/src/pages/${page}/${page}.html`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function verifyPrerequisites(updateFunctions: UpdateFunctions) {
|
||||
const pbkdf2KeyResult = await checkPBKDF2Key();
|
||||
if (!pbkdf2KeyResult) {
|
||||
redirectToSetup('security-setup', updateFunctions);
|
||||
return null;
|
||||
}
|
||||
|
||||
const wallet = await checkWalletWithRetries();
|
||||
if (!wallet) {
|
||||
redirectToSetup('wallet-setup', updateFunctions);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (wallet.sp_wallet?.birthday === undefined) {
|
||||
throw new Error('Wallet found but missing required data (sp_wallet or birthday)');
|
||||
}
|
||||
|
||||
secureLogger.info(`Wallet found in database with birthday: ${wallet.sp_wallet.birthday}`, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
|
||||
return { wallet, pbkdf2KeyResult };
|
||||
}
|
||||
|
||||
async function waitForBlockHeight(services: any, updateFunctions: UpdateFunctions) {
|
||||
updateFunctions.updateStatus('⏳ Attente de la synchronisation avec le réseau...', 'loading');
|
||||
updateFunctions.updateProgress(40);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Timeout waiting for block height from handshake'));
|
||||
}, 15000);
|
||||
|
||||
const checkBlockHeight = () => {
|
||||
const blockHeight = services.getCurrentBlockHeight();
|
||||
if (blockHeight !== -1 && blockHeight > 0) {
|
||||
secureLogger.info(`✅ Block height set from handshake: ${blockHeight}`, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkBlockHeight, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkBlockHeight();
|
||||
});
|
||||
|
||||
const currentBlockHeight = services.getCurrentBlockHeight();
|
||||
if (currentBlockHeight !== -1 && currentBlockHeight > 0) {
|
||||
secureLogger.info(`Relays connected successfully, chain_tip: ${currentBlockHeight}`, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
} else {
|
||||
throw new Error('Handshake not received or chain_tip not set');
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBirthday(services: any, updateFunctions: UpdateFunctions) {
|
||||
updateFunctions.updateStatus('🎂 Mise à jour de la date anniversaire...', 'loading');
|
||||
updateFunctions.updateProgress(60);
|
||||
|
||||
secureLogger.info('🔄 Calling updateDeviceBlockHeight()...', { component: 'BirthdaySetup' });
|
||||
await services.updateDeviceBlockHeight();
|
||||
secureLogger.info('✅ updateDeviceBlockHeight() completed successfully', {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyBirthdayUpdate(services: any, updateFunctions: UpdateFunctions) {
|
||||
updateFunctions.updateStatus('🔍 Vérification de la mise à jour...', 'loading');
|
||||
updateFunctions.updateProgress(70);
|
||||
|
||||
secureLogger.info('🔄 Verifying birthday update...', { component: 'BirthdaySetup' });
|
||||
const updatedWallet = await services.getDeviceFromDatabase();
|
||||
|
||||
if (updatedWallet?.sp_wallet?.birthday && updatedWallet.sp_wallet.birthday > 0) {
|
||||
secureLogger.info(`Birthday updated successfully: ${updatedWallet.sp_wallet.birthday}`, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
} else {
|
||||
secureLogger.error('Birthday update verification failed', new Error('Verification failed'), {
|
||||
component: 'BirthdaySetup',
|
||||
data: {
|
||||
birthday: updatedWallet?.sp_wallet?.birthday,
|
||||
hasSpWallet: !!updatedWallet?.sp_wallet,
|
||||
},
|
||||
});
|
||||
throw new Error(
|
||||
`Birthday update verification failed: expected birthday > 0, got ${updatedWallet?.sp_wallet?.birthday}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function redirectToBlockSync(updateFunctions: UpdateFunctions) {
|
||||
updateFunctions.updateStatus('🔄 Redirection vers la synchronisation des blocs...', 'loading');
|
||||
updateFunctions.updateProgress(100);
|
||||
|
||||
secureLogger.info('🎉 Birthday setup completed successfully - redirecting to block sync', {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔄 Executing redirect now...', { component: 'BirthdaySetup' });
|
||||
window.location.href = '/src/pages/block-sync/block-sync.html';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
if (isInitializing) {
|
||||
secureLogger.warn('⚠️ Birthday setup page already initializing, skipping...', {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
isInitializing = true;
|
||||
secureLogger.info('🎂 Birthday setup page loaded', { component: 'BirthdaySetup' });
|
||||
secureLogger.debug(`Current URL: ${window.location.href}`, { component: 'BirthdaySetup' });
|
||||
secureLogger.debug(`Referrer: ${document.referrer}`, { component: 'BirthdaySetup' });
|
||||
|
||||
const status = document.getElementById('status') as HTMLDivElement;
|
||||
const progressBar = document.getElementById('progressBar') as HTMLDivElement;
|
||||
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
|
||||
|
||||
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
}
|
||||
|
||||
function updateProgress(percent: number) {
|
||||
progressBar.style.width = `${percent}%`;
|
||||
}
|
||||
|
||||
const updateFunctions: UpdateFunctions = { updateStatus, updateProgress };
|
||||
|
||||
try {
|
||||
updateStatus('🌐 Connexion aux relais...', 'loading');
|
||||
updateProgress(20);
|
||||
|
||||
let services;
|
||||
try {
|
||||
services = await initializeServices();
|
||||
} catch (error) {
|
||||
secureLogger.error('Services not available', error as Error, { component: 'BirthdaySetup' });
|
||||
updateStatus('❌ Erreur: Services non disponibles', 'error');
|
||||
throw error;
|
||||
}
|
||||
|
||||
function updateProgress(percent: number) {
|
||||
progressBar.style.width = `${percent}%`;
|
||||
updateStatus('🔍 Vérification des prérequis...', 'loading');
|
||||
updateProgress(20);
|
||||
|
||||
const prerequisites = await verifyPrerequisites(updateFunctions);
|
||||
if (!prerequisites) {
|
||||
return; // Already redirected
|
||||
}
|
||||
|
||||
secureLogger.info('✅ All prerequisites verified', { component: 'BirthdaySetup' });
|
||||
|
||||
await services.connectAllRelays();
|
||||
await waitForBlockHeight(services, updateFunctions);
|
||||
await updateBirthday(services, updateFunctions);
|
||||
await verifyBirthdayUpdate(services, updateFunctions);
|
||||
redirectToBlockSync(updateFunctions);
|
||||
} catch (error) {
|
||||
secureLogger.error('Error during birthday setup', error as Error, {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
updateStatus('❌ Erreur lors de la configuration de la date anniversaire', 'error');
|
||||
} finally {
|
||||
isInitializing = false;
|
||||
}
|
||||
|
||||
// Gestion du bouton continuer
|
||||
continueBtn.addEventListener('click', async () => {
|
||||
secureLogger.info('🏠 Redirecting to main application...', { component: 'BirthdaySetup' });
|
||||
// Rediriger vers l'application principale
|
||||
secureLogger.debug('🎂 Birthday setup completed, checking storage state...', {
|
||||
component: 'BirthdaySetup',
|
||||
});
|
||||
const { checkStorageStateAndNavigate } = await import('../../router');
|
||||
await checkStorageStateAndNavigate();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,243 +1,264 @@
|
||||
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
import { secureLogger } from '../../services/secure-logger';
|
||||
import Services from '../../services/service';
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
secureLogger.info('🔄 Block sync page loaded', { component: 'BlockSync' });
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
secureLogger.info('🔄 Block sync page loaded', { component: 'BlockSync' });
|
||||
|
||||
const status = document.getElementById("status") as HTMLElement;
|
||||
const progressBar = document.getElementById("progressBar") as HTMLElement;
|
||||
const continueBtn = document.getElementById("continueBtn") as HTMLButtonElement;
|
||||
const status = document.getElementById('status') as HTMLElement;
|
||||
const progressBar = document.getElementById('progressBar') as HTMLElement;
|
||||
const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement;
|
||||
|
||||
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
||||
if (status) {
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
function updateStatus(message: string, type: 'loading' | 'success' | 'error') {
|
||||
if (status) {
|
||||
status.textContent = message;
|
||||
status.className = `status ${type}`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgress(percentage: number) {
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${percentage}%`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSyncItem(
|
||||
elementId: string,
|
||||
value: string,
|
||||
status: 'pending' | 'completed' | 'error' = 'pending'
|
||||
) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
element.className = `sync-status ${status}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion du bouton continuer (définie avant le try pour être toujours disponible)
|
||||
if (continueBtn) {
|
||||
continueBtn.addEventListener('click', async () => {
|
||||
secureLogger.info('🔗 Redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier les prérequis
|
||||
secureLogger.debug('🔍 Verifying prerequisites...', { component: 'BlockSync' });
|
||||
updateStatus('🔍 Vérification des prérequis...', 'loading');
|
||||
|
||||
const pbkdf2KeyResult = await checkPBKDF2Key();
|
||||
if (!pbkdf2KeyResult) {
|
||||
secureLogger.warn('⚠️ PBKDF2 key not found, redirecting to security-setup...', {
|
||||
component: 'BlockSync',
|
||||
});
|
||||
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const wallet = await checkWalletWithRetries();
|
||||
if (!wallet) {
|
||||
secureLogger.warn('⚠️ Wallet not found, redirecting to wallet-setup...', {
|
||||
component: 'BlockSync',
|
||||
});
|
||||
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wallet.sp_wallet?.birthday || wallet.sp_wallet.birthday === 0) {
|
||||
secureLogger.warn('⚠️ Birthday not configured, redirecting to birthday-setup...', {
|
||||
component: 'BlockSync',
|
||||
});
|
||||
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
secureLogger.info('✅ All prerequisites verified for block sync', { component: 'BlockSync' });
|
||||
updateStatus('✅ Prerequisites verified', 'success');
|
||||
|
||||
// Initialiser les services
|
||||
secureLogger.info('🔄 Waiting for services to be ready...', { component: 'BlockSync' });
|
||||
updateStatus('🔄 Initialisation des services...', 'loading');
|
||||
|
||||
let services: Services;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
secureLogger.info(
|
||||
'🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...',
|
||||
{ component: 'BlockSync' }
|
||||
);
|
||||
services = await Services.getInstance();
|
||||
secureLogger.info('✅ Services initialized successfully', { component: 'BlockSync' });
|
||||
break;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
secureLogger.warn(
|
||||
`Services initialization failed (attempt ${attempts}/${maxAttempts})`,
|
||||
error as Error,
|
||||
{ component: 'BlockSync' }
|
||||
);
|
||||
if (attempts >= maxAttempts) {
|
||||
throw new Error('Failed to initialize services after maximum attempts');
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgress(percentage: number) {
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${percentage}%`;
|
||||
if (!services!) {
|
||||
throw new Error('Services not initialized');
|
||||
}
|
||||
|
||||
// Vérifier si le wallet est déjà synchronisé
|
||||
const currentBlockHeight = services.getCurrentBlockHeight();
|
||||
if (currentBlockHeight === -1) {
|
||||
secureLogger.warn('⚠️ Block height not available, connecting to relays...', {
|
||||
component: 'BlockSync',
|
||||
});
|
||||
updateStatus('⚠️ Connexion aux relays...', 'loading');
|
||||
|
||||
// Attendre que les services se connectent aux relays
|
||||
await services.connectAllRelays();
|
||||
|
||||
// Attendre que la hauteur de bloc soit définie
|
||||
await services.waitForBlockHeight();
|
||||
}
|
||||
|
||||
const finalBlockHeight = services.getCurrentBlockHeight();
|
||||
const birthday = wallet.sp_wallet.birthday;
|
||||
const lastScan = wallet.sp_wallet.last_scan || 0;
|
||||
const toScan = Math.max(0, finalBlockHeight - lastScan);
|
||||
|
||||
secureLogger.info(
|
||||
'📊 Sync info: current=${finalBlockHeight}, birthday=${birthday}, lastScan=${lastScan}, toScan=${toScan}',
|
||||
{ component: 'BlockSync' }
|
||||
);
|
||||
|
||||
// Mettre à jour l'interface
|
||||
updateSyncItem('currentBlock', finalBlockHeight.toString(), 'completed');
|
||||
updateSyncItem('birthday', birthday.toString(), 'completed');
|
||||
updateSyncItem('lastScan', lastScan.toString(), 'completed');
|
||||
|
||||
if (toScan === 0) {
|
||||
secureLogger.info('✅ Wallet already synchronized', { component: 'BlockSync' });
|
||||
updateStatus('✅ Wallet déjà synchronisé', 'success');
|
||||
updateSyncItem('blocksToScan', '0', 'completed');
|
||||
updateSyncItem('blocksScanned', '0', 'completed');
|
||||
updateSyncItem('transactionsFound', '0', 'completed');
|
||||
|
||||
// Activer le bouton et rediriger automatiquement
|
||||
if (continueBtn) {
|
||||
continueBtn.disabled = false;
|
||||
continueBtn.textContent = 'Aller au pairing';
|
||||
}
|
||||
|
||||
// Auto-redirection après 3 secondes
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher la barre de progression
|
||||
updateProgress(0);
|
||||
updateStatus('🔄 Synchronisation en cours...', 'loading');
|
||||
updateSyncItem('blocksToScan', toScan.toString(), 'pending');
|
||||
|
||||
// Intercepter les messages de progression du scan
|
||||
|
||||
// Fonction pour intercepter les messages de progression
|
||||
const originalConsoleLog = console.log;
|
||||
console.log = (...args: any[]) => {
|
||||
const message = args.join(' ');
|
||||
if (message.includes('Scan progress:')) {
|
||||
// Extraire les informations de progression
|
||||
const progressMatch = message.match(/Scan progress: (\d+)\/(\d+) \((\d+)%\)/);
|
||||
if (progressMatch) {
|
||||
const currentBlock = parseInt(progressMatch[1]);
|
||||
const totalBlocks = parseInt(progressMatch[2]);
|
||||
const percentage = parseInt(progressMatch[3]);
|
||||
|
||||
// Mettre à jour l'interface avec les détails de progression
|
||||
updateStatus(
|
||||
`🔍 Synchronisation des blocs: ${currentBlock}/${totalBlocks} (${percentage}%)`,
|
||||
'loading'
|
||||
);
|
||||
updateProgress(percentage);
|
||||
|
||||
// Mettre à jour les éléments de synchronisation
|
||||
updateSyncItem('blocksScanned', currentBlock.toString(), 'pending');
|
||||
updateSyncItem('blocksToScan', (totalBlocks - currentBlock).toString(), 'pending');
|
||||
|
||||
// Progress message available if needed: `Bloc ${currentBlock}/${totalBlocks} (${percentage}%)`
|
||||
}
|
||||
}
|
||||
|
||||
function updateSyncItem(elementId: string, value: string, status: 'pending' | 'completed' | 'error' = 'pending') {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.textContent = value;
|
||||
element.className = `sync-status ${status}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion du bouton continuer (définie avant le try pour être toujours disponible)
|
||||
if (continueBtn) {
|
||||
continueBtn.addEventListener('click', async () => {
|
||||
secureLogger.info('🔗 Redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
});
|
||||
}
|
||||
}
|
||||
// Appeler la fonction console.log originale
|
||||
originalConsoleLog.apply(console, args);
|
||||
};
|
||||
|
||||
try {
|
||||
// Vérifier les prérequis
|
||||
secureLogger.debug('🔍 Verifying prerequisites...', { component: 'BlockSync' });
|
||||
updateStatus('🔍 Vérification des prérequis...', 'loading');
|
||||
// Effectuer la synchronisation
|
||||
await services.updateDeviceBlockHeight();
|
||||
secureLogger.info('✅ Block scan completed successfully', { component: 'BlockSync' });
|
||||
|
||||
const pbkdf2KeyResult = await checkPBKDF2Key();
|
||||
if (!pbkdf2KeyResult) {
|
||||
secureLogger.warn('⚠️ PBKDF2 key not found, redirecting to security-setup...', { component: 'BlockSync' });
|
||||
updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
// Restaurer la fonction console.log originale
|
||||
console.log = originalConsoleLog;
|
||||
|
||||
const wallet = await checkWalletWithRetries();
|
||||
if (!wallet) {
|
||||
secureLogger.warn('⚠️ Wallet not found, redirecting to wallet-setup...', { component: 'BlockSync' });
|
||||
updateStatus('⚠️ Redirection vers la configuration du wallet...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
updateStatus('✅ Synchronisation terminée', 'success');
|
||||
updateProgress(100);
|
||||
updateSyncItem('blocksScanned', toScan.toString(), 'completed');
|
||||
updateSyncItem('blocksToScan', '0', 'completed');
|
||||
updateSyncItem('transactionsFound', '0', 'completed');
|
||||
|
||||
if (!wallet.sp_wallet?.birthday || wallet.sp_wallet.birthday === 0) {
|
||||
secureLogger.warn('⚠️ Birthday not configured, redirecting to birthday-setup...', { component: 'BlockSync' });
|
||||
updateStatus('⚠️ Redirection vers la configuration de la date anniversaire...', 'loading');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
secureLogger.info('✅ All prerequisites verified for block sync', { component: 'BlockSync' });
|
||||
updateStatus('✅ Prerequisites verified', 'success');
|
||||
|
||||
// Initialiser les services
|
||||
secureLogger.info('🔄 Waiting for services to be ready...', { component: 'BlockSync' });
|
||||
updateStatus('🔄 Initialisation des services...', 'loading');
|
||||
|
||||
let services: Services;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
secureLogger.info('🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...', { component: 'BlockSync' });
|
||||
services = await Services.getInstance();
|
||||
secureLogger.info('✅ Services initialized successfully', { component: 'BlockSync' });
|
||||
break;
|
||||
} catch (error) {
|
||||
attempts++;
|
||||
secureLogger.warn(`Services initialization failed (attempt ${attempts}/${maxAttempts})`, error as Error, { component: 'BlockSync' });
|
||||
if (attempts >= maxAttempts) {
|
||||
throw new Error('Failed to initialize services after maximum attempts');
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (!services!) {
|
||||
throw new Error('Services not initialized');
|
||||
}
|
||||
|
||||
// Vérifier si le wallet est déjà synchronisé
|
||||
const currentBlockHeight = services.getCurrentBlockHeight();
|
||||
if (currentBlockHeight === -1) {
|
||||
secureLogger.warn('⚠️ Block height not available, connecting to relays...', { component: 'BlockSync' });
|
||||
updateStatus('⚠️ Connexion aux relays...', 'loading');
|
||||
|
||||
// Attendre que les services se connectent aux relays
|
||||
await services.connectAllRelays();
|
||||
|
||||
// Attendre que la hauteur de bloc soit définie
|
||||
await services.waitForBlockHeight();
|
||||
}
|
||||
|
||||
const finalBlockHeight = services.getCurrentBlockHeight();
|
||||
const birthday = wallet.sp_wallet.birthday;
|
||||
const lastScan = wallet.sp_wallet.last_scan || 0;
|
||||
const toScan = Math.max(0, finalBlockHeight - lastScan);
|
||||
|
||||
secureLogger.info('📊 Sync info: current=${finalBlockHeight}, birthday=${birthday}, lastScan=${lastScan}, toScan=${toScan}', { component: 'BlockSync' });
|
||||
|
||||
// Mettre à jour l'interface
|
||||
updateSyncItem('currentBlock', finalBlockHeight.toString(), 'completed');
|
||||
updateSyncItem('birthday', birthday.toString(), 'completed');
|
||||
updateSyncItem('lastScan', lastScan.toString(), 'completed');
|
||||
|
||||
if (toScan === 0) {
|
||||
secureLogger.info('✅ Wallet already synchronized', { component: 'BlockSync' });
|
||||
updateStatus('✅ Wallet déjà synchronisé', 'success');
|
||||
updateSyncItem('blocksToScan', '0', 'completed');
|
||||
updateSyncItem('blocksScanned', '0', 'completed');
|
||||
updateSyncItem('transactionsFound', '0', 'completed');
|
||||
|
||||
// Activer le bouton et rediriger automatiquement
|
||||
if (continueBtn) {
|
||||
continueBtn.disabled = false;
|
||||
continueBtn.textContent = 'Aller au pairing';
|
||||
}
|
||||
|
||||
// Auto-redirection après 3 secondes
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher la barre de progression
|
||||
updateProgress(0);
|
||||
updateStatus('🔄 Synchronisation en cours...', 'loading');
|
||||
updateSyncItem('blocksToScan', toScan.toString(), 'pending');
|
||||
|
||||
// Intercepter les messages de progression du scan
|
||||
let scanProgressInterval: NodeJS.Timeout | null = null;
|
||||
let lastProgressMessage = '';
|
||||
|
||||
// Fonction pour intercepter les messages de progression
|
||||
const originalConsoleLog = console.log;
|
||||
console.log = (...args: any[]) => {
|
||||
const message = args.join(' ');
|
||||
if (message.includes('Scan progress:')) {
|
||||
// Extraire les informations de progression
|
||||
const progressMatch = message.match(/Scan progress: (\d+)\/(\d+) \((\d+)%\)/);
|
||||
if (progressMatch) {
|
||||
const currentBlock = parseInt(progressMatch[1]);
|
||||
const totalBlocks = parseInt(progressMatch[2]);
|
||||
const percentage = parseInt(progressMatch[3]);
|
||||
|
||||
// Mettre à jour l'interface avec les détails de progression
|
||||
updateStatus(`🔍 Synchronisation des blocs: ${currentBlock}/${totalBlocks} (${percentage}%)`, 'loading');
|
||||
updateProgress(percentage);
|
||||
|
||||
// Mettre à jour les éléments de synchronisation
|
||||
updateSyncItem('blocksScanned', currentBlock.toString(), 'pending');
|
||||
updateSyncItem('blocksToScan', (totalBlocks - currentBlock).toString(), 'pending');
|
||||
|
||||
lastProgressMessage = `Bloc ${currentBlock}/${totalBlocks} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
// Appeler la fonction console.log originale
|
||||
originalConsoleLog.apply(console, args);
|
||||
};
|
||||
|
||||
try {
|
||||
// Effectuer la synchronisation
|
||||
await services.updateDeviceBlockHeight();
|
||||
secureLogger.info('✅ Block scan completed successfully', { component: 'BlockSync' });
|
||||
|
||||
// Restaurer la fonction console.log originale
|
||||
console.log = originalConsoleLog;
|
||||
|
||||
updateStatus('✅ Synchronisation terminée', 'success');
|
||||
updateProgress(100);
|
||||
updateSyncItem('blocksScanned', toScan.toString(), 'completed');
|
||||
updateSyncItem('blocksToScan', '0', 'completed');
|
||||
updateSyncItem('transactionsFound', '0', 'completed');
|
||||
|
||||
// Activer le bouton et rediriger automatiquement
|
||||
if (continueBtn) {
|
||||
continueBtn.disabled = false;
|
||||
continueBtn.textContent = 'Aller au pairing';
|
||||
}
|
||||
|
||||
// Auto-redirection après 3 secondes
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
// Restaurer la fonction console.log originale en cas d'erreur
|
||||
console.log = originalConsoleLog;
|
||||
secureLogger.error('Error during block scan', error as Error, { component: 'BlockSync' });
|
||||
updateStatus(`❌ Erreur lors de la synchronisation: ${(error as Error).message}`, 'error');
|
||||
updateSyncItem('blocksToScan', 'Erreur', 'error');
|
||||
throw error;
|
||||
}
|
||||
// Activer le bouton et rediriger automatiquement
|
||||
if (continueBtn) {
|
||||
continueBtn.disabled = false;
|
||||
continueBtn.textContent = 'Aller au pairing';
|
||||
}
|
||||
|
||||
// Auto-redirection après 3 secondes
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔗 Auto-redirecting to pairing page...', { component: 'BlockSync' });
|
||||
window.location.href = '/src/pages/pairing/pairing.html';
|
||||
}, 3000);
|
||||
} catch (error) {
|
||||
secureLogger.error('Error in block sync page', error as Error, { component: 'BlockSync' });
|
||||
updateStatus(`❌ Erreur: ${(error as Error).message}`, 'error');
|
||||
|
||||
// Rediriger vers la page appropriée selon l'erreur
|
||||
const errorMessage = (error as Error).message;
|
||||
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
}, 2000);
|
||||
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
}, 2000);
|
||||
} else if (errorMessage.includes('birthday')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||
}, 2000);
|
||||
}
|
||||
// Restaurer la fonction console.log originale en cas d'erreur
|
||||
console.log = originalConsoleLog;
|
||||
secureLogger.error('Error during block scan', error as Error, { component: 'BlockSync' });
|
||||
updateStatus(`❌ Erreur lors de la synchronisation: ${(error as Error).message}`, 'error');
|
||||
updateSyncItem('blocksToScan', 'Erreur', 'error');
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
secureLogger.error('Error in block sync page', error as Error, { component: 'BlockSync' });
|
||||
updateStatus(`❌ Erreur: ${(error as Error).message}`, 'error');
|
||||
|
||||
// Rediriger vers la page appropriée selon l'erreur
|
||||
const errorMessage = (error as Error).message;
|
||||
if (errorMessage.includes('PBKDF2') || errorMessage.includes('security')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
}, 2000);
|
||||
} else if (errorMessage.includes('wallet') || errorMessage.includes('device')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
}, 2000);
|
||||
} else if (errorMessage.includes('birthday')) {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,7 +4,7 @@ import { SecurityModeService, SecurityMode } from '../../services/security-mode.
|
||||
import { secureLogger } from '../../services/secure-logger';
|
||||
import Services from '../../services/service';
|
||||
import { addSubscription } from '../../utils/subscription.utils';
|
||||
import { displayEmojis, generateCreateBtn, addressToEmoji, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
|
||||
import { displayEmojis, generateCreateBtn, prepareAndSendPairingTx } from '../../utils/sp-address.utils';
|
||||
import { getCorrectDOM } from '../../utils/html.utils';
|
||||
import { IframePairingComponent } from '../../components/iframe-pairing/iframe-pairing';
|
||||
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
|
||||
@ -646,7 +646,7 @@ async function handleMainPairing(): Promise<void> {
|
||||
let currentMode: string | null = null;
|
||||
try {
|
||||
currentMode = await securityModeService.getCurrentMode();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Si aucun mode n'est configuré, rediriger vers security-setup (le mode devrait déjà être configuré avant d'arriver ici)
|
||||
secureLogger.warn('⚠️ No security mode configured, redirecting to security-setup...', { component: 'HomePage' });
|
||||
if (mainStatus) {
|
||||
@ -763,8 +763,7 @@ async function handleMainPairing(): Promise<void> {
|
||||
await secureCredentialsService.storeCredentials(credentialData, '');
|
||||
secureLogger.info('✅ Credentials stored successfully', { component: 'HomePage' });
|
||||
|
||||
// Note: Faucet temporarily disabled due to relay configuration issue
|
||||
secureLogger.info('🪙 Faucet temporarily disabled - relay needs Bitcoin Core wallet configuration', { component: 'HomePage' });
|
||||
// Faucet flow remains enabled and required
|
||||
|
||||
// Decrypt and make keys available to SDK
|
||||
secureLogger.info('🔓 Decrypting credentials for SDK access...', { component: 'HomePage' });
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { DeviceReaderService } from '../../services/device-reader.service';
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
import { secureLogger } from '../../services/secure-logger';
|
||||
import { checkPBKDF2Key, checkWalletWithRetries } from '../../utils/prerequisites.utils';
|
||||
|
||||
// Extend WindowEventMap to include custom events
|
||||
@ -64,7 +64,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
try {
|
||||
secureLogger.info('🔧 Getting device reader service...', { component: 'PairingPage' });
|
||||
const deviceReader = DeviceReaderService.getInstance();
|
||||
// const deviceReader = DeviceReaderService.getInstance(); // Not used yet
|
||||
|
||||
// Vérifier que le PBKDF2 key existe d'abord
|
||||
const pbkdf2KeyResult = await checkPBKDF2Key();
|
||||
|
||||
@ -86,7 +86,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// Générer la clé PBKDF2 et la stocker selon le mode
|
||||
// IMPORTANT: Cette opération doit être directe (pas de délai) pour que WebAuthn fonctionne
|
||||
const pbkdf2Key = await secureCredentialsService.generatePBKDF2Key(selectedMode);
|
||||
await secureCredentialsService.generatePBKDF2Key(selectedMode);
|
||||
secureLogger.info('✅ PBKDF2 key generated and stored securely', { component: 'SecuritySetup' });
|
||||
|
||||
// Rediriger directement vers la page de génération du wallet
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { DATABASE_CONFIG } from '../../services/database-config';
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
import { secureLogger } from '../../services/secure-logger';
|
||||
import { checkPBKDF2Key } from '../../utils/prerequisites.utils';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
@ -23,30 +23,30 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
progressBar.style.width = `${percent}%`;
|
||||
}
|
||||
|
||||
// Méthode pour sauvegarder directement en IndexedDB dans la base 4nk
|
||||
async function saveCredentialsDirectly(credentials: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
|
||||
// Méthode pour sauvegarder directement en IndexedDB dans la base 4nk (non utilisée pour l'instant)
|
||||
// async function saveCredentialsDirectly(credentials: any): Promise<void> {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
const db = request.result;
|
||||
const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
|
||||
const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
|
||||
// request.onerror = () => reject(request.error);
|
||||
// request.onsuccess = () => {
|
||||
// const db = request.result;
|
||||
// const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite');
|
||||
// const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name);
|
||||
|
||||
const putRequest = store.put(credentials, '/4nk/credentials');
|
||||
putRequest.onsuccess = () => resolve();
|
||||
putRequest.onerror = () => reject(putRequest.error);
|
||||
};
|
||||
// const putRequest = store.put(credentials, '/4nk/credentials');
|
||||
// putRequest.onsuccess = () => resolve();
|
||||
// putRequest.onerror = () => reject(putRequest.error);
|
||||
// };
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result;
|
||||
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
|
||||
db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
// request.onupgradeneeded = () => {
|
||||
// const db = request.result;
|
||||
// if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
|
||||
// db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
|
||||
// }
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
|
||||
try {
|
||||
// Étape 1: Vérifier les prérequis AVANT d'initialiser Services
|
||||
@ -110,10 +110,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
if ((performance as any).memory) {
|
||||
const memory = (performance as any).memory;
|
||||
const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100;
|
||||
const usedMB = memory.usedJSHeapSize / 1024 / 1024;
|
||||
const limitMB = memory.jsHeapSizeLimit / 1024 / 1024;
|
||||
|
||||
secureLogger.info('📊 Current memory usage: ${usedPercent.toFixed(1)}% (${usedMB.toFixed(1)}MB / ${limitMB.toFixed(1)}MB)', { component: 'WalletSetup' });
|
||||
secureLogger.info(`📊 Current memory usage: ${usedPercent.toFixed(1)}%`, { component: 'WalletSetup' });
|
||||
|
||||
// Si la mémoire est très élevée (>75%), tenter un nettoyage agressif
|
||||
if (usedPercent > 75) {
|
||||
@ -194,10 +192,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Diagnostic plus détaillé
|
||||
if (attempts === 5) {
|
||||
secureLogger.debug('🔍 Diagnostic: Checking memory usage...', { component: 'WalletSetup' });
|
||||
if ((performance as any).memory) {
|
||||
const memory = (performance as any).memory;
|
||||
secureLogger.info('📊 Memory usage: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB / ${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB', { component: 'WalletSetup' });
|
||||
}
|
||||
// Memory check removed - not needed here
|
||||
}
|
||||
|
||||
attempts++;
|
||||
@ -279,7 +274,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// Créer le store wallet seulement s'il n'existe pas
|
||||
if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) {
|
||||
const store = db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
|
||||
db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string });
|
||||
secureLogger.info('✅ Wallet store created with keyPath: ${DATABASE_CONFIG.stores.wallet.keyPath}', { component: 'WalletSetup' });
|
||||
} else {
|
||||
secureLogger.info('✅ Wallet store already exists', { component: 'WalletSetup' });
|
||||
@ -312,7 +307,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
// Récupérer le device créé par le SDK
|
||||
const device = services.dumpDeviceFromMemory();
|
||||
secureLogger.debug('Device structure from SDK', {
|
||||
secureLogger.debug('Device structure from SDK', {
|
||||
component: 'WalletSetup',
|
||||
data: {
|
||||
hasSpWallet: !!device.sp_wallet,
|
||||
@ -370,7 +365,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
};
|
||||
transaction.onerror = () => {
|
||||
secureLogger.error('Transaction failed', transaction.error as Error, { component: 'WalletSetup' });
|
||||
secureLogger.error('Transaction error details', new Error('Transaction failed'), {
|
||||
secureLogger.error('Transaction error details', new Error('Transaction failed'), {
|
||||
component: 'WalletSetup',
|
||||
data: {
|
||||
error: transaction.error,
|
||||
@ -389,7 +384,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
const verificationRequest = store.get('1');
|
||||
verificationRequest.onsuccess = () => {
|
||||
secureLogger.debug('Verification result structure', {
|
||||
secureLogger.debug('Verification result structure', {
|
||||
component: 'WalletSetup',
|
||||
data: verificationRequest.result ? {
|
||||
hasPreId: !!verificationRequest.result.pre_id,
|
||||
@ -451,7 +446,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
secureLogger.info('✅ Wallet saved exclusively in IndexedDB and verified', { component: 'WalletSetup' });
|
||||
console.log('🔍 Wallet contains only encrypted data:', {
|
||||
secureLogger.debug('🔍 Wallet contains only encrypted data:', {
|
||||
component: 'WalletSetup',
|
||||
hasEncryptedDevice: !!finalVerification.encrypted_device,
|
||||
hasEncryptedWallet: !!finalVerification.encrypted_wallet,
|
||||
hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE
|
||||
@ -472,7 +468,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
pbkdf2KeyTest
|
||||
);
|
||||
const parsedWallet = JSON.parse(decryptedWallet);
|
||||
console.log('✅ TEST: Wallet decrypted successfully:', {
|
||||
secureLogger.debug('✅ TEST: Wallet decrypted successfully:', {
|
||||
component: 'WalletSetup',
|
||||
hasScanSk: !!parsedWallet.scan_sk,
|
||||
hasSpendKey: !!parsedWallet.spend_key,
|
||||
network: parsedWallet.network,
|
||||
@ -486,7 +483,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
pbkdf2KeyTest
|
||||
);
|
||||
const parsedDevice = JSON.parse(decryptedDevice);
|
||||
console.log('✅ TEST: Device decrypted successfully:', {
|
||||
secureLogger.debug('✅ TEST: Device decrypted successfully:', {
|
||||
component: 'WalletSetup',
|
||||
hasSpWallet: !!parsedDevice.sp_wallet,
|
||||
network: parsedDevice.network
|
||||
});
|
||||
@ -494,8 +492,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
secureLogger.info('✅ TEST: Full decryption test passed - wallet and device decrypt correctly', { component: 'WalletSetup' });
|
||||
}
|
||||
} catch (decryptError) {
|
||||
console.error('❌ TEST: Decryption test failed:', decryptError);
|
||||
secureLogger.error('❌ This indicates an issue with encryption/decryption logic', { component: 'WalletSetup' });
|
||||
secureLogger.error('❌ TEST: Decryption test failed:', decryptError as Error, { component: 'WalletSetup' });
|
||||
}
|
||||
} else {
|
||||
secureLogger.error('❌ Final wallet verification failed - wallet not found in IndexedDB', { component: 'WalletSetup' });
|
||||
@ -503,7 +500,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error during wallet save:', error);
|
||||
secureLogger.error('❌ Error during wallet save:', error as Error, { component: 'WalletSetup' });
|
||||
updateStatus('❌ Erreur: Échec de la sauvegarde du wallet', 'error');
|
||||
throw error;
|
||||
}
|
||||
@ -520,13 +517,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
secureLogger.info('✅ Continue button enabled', { component: 'WalletSetup' });
|
||||
|
||||
// Redirection automatique après 3 secondes si l'utilisateur ne clique pas
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
secureLogger.info('🔄 Auto-redirecting to birthday setup after timeout...', { component: 'WalletSetup' });
|
||||
window.location.href = '/src/pages/birthday-setup/birthday-setup.html';
|
||||
}, 3000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error during wallet setup:', error);
|
||||
secureLogger.error('❌ Error during wallet setup:', error as Error, { component: 'WalletSetup' });
|
||||
updateStatus('❌ Erreur lors de la génération du wallet', 'error');
|
||||
}
|
||||
|
||||
|
||||
@ -5,17 +5,19 @@
|
||||
import { Device } from '../../pkg/sdk_client';
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export interface DeviceRepository {
|
||||
getDevice(): Promise<Device | null>;
|
||||
saveDevice(device: Device): Promise<void>;
|
||||
saveDevice(_device: Device): Promise<void>;
|
||||
deleteDevice(): Promise<void>;
|
||||
hasDevice(): Promise<boolean>;
|
||||
getDeviceAddress(): Promise<string | null>;
|
||||
updateDevice(device: Partial<Device>): Promise<void>;
|
||||
updateDevice(_device: Partial<Device>): Promise<void>;
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
export class DeviceRepositoryImpl implements DeviceRepository {
|
||||
constructor(private database: any) {}
|
||||
constructor(private database: any) {} // database used via methods
|
||||
|
||||
async getDevice(): Promise<Device | null> {
|
||||
try {
|
||||
|
||||
@ -5,19 +5,21 @@
|
||||
import { Process } from '../../pkg/sdk_client';
|
||||
import { secureLogger } from '../services/secure-logger';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export interface ProcessRepository {
|
||||
getProcess(processId: string): Promise<Process | null>;
|
||||
saveProcess(process: Process): Promise<void>;
|
||||
deleteProcess(processId: string): Promise<void>;
|
||||
getProcess(_processId: string): Promise<Process | null>;
|
||||
saveProcess(_process: Process): Promise<void>;
|
||||
deleteProcess(_processId: string): Promise<void>;
|
||||
getProcesses(): Promise<Process[]>;
|
||||
getMyProcesses(): Promise<string[]>;
|
||||
addMyProcess(processId: string): Promise<void>;
|
||||
removeMyProcess(processId: string): Promise<void>;
|
||||
hasProcess(processId: string): Promise<boolean>;
|
||||
addMyProcess(_processId: string): Promise<void>;
|
||||
removeMyProcess(_processId: string): Promise<void>;
|
||||
hasProcess(_processId: string): Promise<boolean>;
|
||||
}
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
export class ProcessRepositoryImpl implements ProcessRepository {
|
||||
constructor(private database: any) {}
|
||||
constructor(private database: any) {} // database used via methods
|
||||
|
||||
async getProcess(processId: string): Promise<Process | null> {
|
||||
try {
|
||||
|
||||
@ -124,7 +124,7 @@ async function handleLocation(path: string) {
|
||||
if (path === 'home') {
|
||||
secureLogger.info('🏠 Processing home route...', { component: 'Router' });
|
||||
// Use LoginComponent
|
||||
const loginComponent = LoginComponent;
|
||||
// const loginComponent = LoginComponent; // Used as type, not variable
|
||||
const container = document.querySelector('#containerId');
|
||||
secureLogger.info('🏠 Container for home:', { component: 'Router', data: !!container });
|
||||
const accountComponent = document.createElement('login-4nk-component');
|
||||
@ -253,18 +253,9 @@ export async function registerAllListeners() {
|
||||
);
|
||||
};
|
||||
|
||||
const successResponse = (data: any, origin: string, messageId?: string) => {
|
||||
// Use successResponse function
|
||||
secureLogger.info('Success response:', { component: 'Router', data: data });
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: MessageType.SUCCESS,
|
||||
data: data,
|
||||
messageId,
|
||||
},
|
||||
origin
|
||||
);
|
||||
};
|
||||
// const successResponse = (data: any, origin: string, messageId?: string) => {
|
||||
// // Not used anymore, response sent directly in handlers
|
||||
// };
|
||||
|
||||
// --- Handler functions ---
|
||||
const handleRequestLink = async (event: MessageEvent) => {
|
||||
@ -658,7 +649,7 @@ export async function registerAllListeners() {
|
||||
}
|
||||
const processId = createProcessReturn.updated_process.process_id;
|
||||
const process = createProcessReturn.updated_process.current_process;
|
||||
const stateId = process.states[0].state_id;
|
||||
// const stateId = process.states[0].state_id; // Not used
|
||||
await services.handleApiReturn(createProcessReturn);
|
||||
|
||||
const res = {
|
||||
@ -971,7 +962,7 @@ export async function registerAllListeners() {
|
||||
let parsedMerkleProof: MerkleProofResult;
|
||||
try {
|
||||
parsedMerkleProof = JSON.parse(merkleProof);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
throw new Error('Provided merkleProof is not a valid json object');
|
||||
}
|
||||
|
||||
@ -1185,6 +1176,7 @@ document.addEventListener('navigate', (e: Event) => {
|
||||
* ÉTAPE 2: Gestion de la sécurité (clés de sécurité)
|
||||
* Cette étape doit être la première et rien d'autre ne doit s'exécuter en parallèle
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function handleSecurityKeyManagement(): Promise<boolean> {
|
||||
secureLogger.info('🔐 Starting security key management...', { component: 'Router' });
|
||||
|
||||
@ -1211,7 +1203,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
|
||||
|
||||
if (!hasCredentials) {
|
||||
secureLogger.info('🔐 No security credentials found, redirecting to wallet setup...', { component: 'Router' });
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
window.location.href = '/wallet-setup.html';
|
||||
return false;
|
||||
} else {
|
||||
secureLogger.info('🔐 Security credentials found, verifying access...', { component: 'Router' });
|
||||
@ -1219,7 +1211,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
|
||||
const credentials = await secureCredentialsService.retrieveCredentials('');
|
||||
if (!credentials) {
|
||||
secureLogger.error('❌ Failed to access security credentials', { component: 'Router' });
|
||||
window.location.href = '/src/pages/wallet-setup/wallet-setup.html';
|
||||
window.location.href = '/wallet-setup.html';
|
||||
return false;
|
||||
}
|
||||
secureLogger.info('✅ Security credentials verified', { component: 'Router' });
|
||||
@ -1228,7 +1220,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
|
||||
} catch (error) {
|
||||
secureLogger.error('❌ Security key management failed:', error, { component: 'Router' });
|
||||
secureLogger.info('🔐 Redirecting to security setup...', { component: 'Router' });
|
||||
window.location.href = '/src/pages/security-setup/security-setup.html';
|
||||
window.location.href = '/security-setup.html';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1236,6 +1228,7 @@ async function handleSecurityKeyManagement(): Promise<boolean> {
|
||||
/**
|
||||
* ÉTAPE 5: Handshake
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function performHandshake(_services: any): Promise<void> {
|
||||
secureLogger.info('🤝 Performing handshake...', { component: 'Router' });
|
||||
|
||||
@ -1252,6 +1245,7 @@ async function performHandshake(_services: any): Promise<void> {
|
||||
/**
|
||||
* ÉTAPE 6: Pairing
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function handlePairing(services: any): Promise<void> {
|
||||
secureLogger.info('🔗 Handling device pairing...', { component: 'Router' });
|
||||
|
||||
@ -1276,6 +1270,7 @@ async function handlePairing(services: any): Promise<void> {
|
||||
/**
|
||||
* ÉTAPE 7: Écoute des processus
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function startProcessListening(services: any): Promise<void> {
|
||||
secureLogger.info('👂 Starting process listening...', { component: 'Router' });
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-env browser, serviceworker */
|
||||
const addResourcesToCache = async resources => {
|
||||
const cache = await caches.open('v1');
|
||||
await cache.addAll(resources);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-env browser, serviceworker */
|
||||
const EMPTY32BYTES = String('').padStart(64, '0');
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
@ -109,10 +110,10 @@ const DATABASE_CONFIG = {
|
||||
async function openDatabase() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
|
||||
request.onerror = event => {
|
||||
request.onerror = () => {
|
||||
reject(request.error);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
request.onsuccess = () => {
|
||||
resolve(request.result);
|
||||
};
|
||||
request.onupgradeneeded = event => {
|
||||
@ -180,6 +181,7 @@ async function getProcesses(processIds) {
|
||||
return results.filter(result => result !== undefined);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function getAllDiffsNeedValidation() {
|
||||
const db = await openDatabase();
|
||||
|
||||
|
||||
@ -58,7 +58,7 @@ export class AsyncEncoderService {
|
||||
component: 'AsyncEncoderService',
|
||||
operation: 'initializeWorker'
|
||||
});
|
||||
} catch (error) {
|
||||
} catch {
|
||||
secureLogger.warn('Failed to initialize encoder worker, falling back to sync encoding', {
|
||||
component: 'AsyncEncoderService',
|
||||
operation: 'initializeWorker'
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
import { secureLogger } from '../secure-logger';
|
||||
import { SecurityMode } from '../security-mode.service';
|
||||
import { WebAuthnCredential, EncryptionResult } from './types';
|
||||
import { WebAuthnCredential } from './types';
|
||||
import { DATABASE_CONFIG } from '../database-config';
|
||||
|
||||
export class WebAuthnService {
|
||||
@ -74,7 +74,7 @@ export class WebAuthnService {
|
||||
}
|
||||
|
||||
// Vérifier si on est dans un contexte sécurisé (requis pour WebAuthn)
|
||||
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
||||
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -93,33 +93,33 @@ export class WebAuthnService {
|
||||
*/
|
||||
async detectProtonPass(): Promise<boolean> {
|
||||
try {
|
||||
console.log('🔍 Detecting Proton Pass availability...');
|
||||
secureLogger.debug('🔍 Detecting Proton Pass availability...', { component: 'WebAuthn' });
|
||||
|
||||
const available = await this.detectAvailableAuthenticators();
|
||||
if (!available) {
|
||||
console.log('❌ WebAuthn not available');
|
||||
secureLogger.debug('❌ WebAuthn not available', { component: 'WebAuthn' });
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('✅ WebAuthn is available, checking Proton Pass support...');
|
||||
secureLogger.debug('✅ WebAuthn is available, checking Proton Pass support...', { component: 'WebAuthn' });
|
||||
|
||||
// Vérifier la disponibilité sans faire d'appel réel à WebAuthn
|
||||
// Juste vérifier que les APIs sont disponibles
|
||||
if (!navigator.credentials?.create) {
|
||||
console.log('❌ WebAuthn credentials API not available');
|
||||
secureLogger.debug('❌ WebAuthn credentials API not available', { component: 'WebAuthn' });
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier si on est dans un contexte sécurisé (requis pour WebAuthn)
|
||||
if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
||||
console.log('❌ WebAuthn requires HTTPS or localhost');
|
||||
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
|
||||
secureLogger.debug('❌ WebAuthn requires HTTPS or localhost', { component: 'WebAuthn' });
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('✅ Proton Pass should be available (basic checks passed)');
|
||||
secureLogger.debug('✅ Proton Pass should be available (basic checks passed)', { component: 'WebAuthn' });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log('❌ Error detecting Proton Pass:', error);
|
||||
secureLogger.error('❌ Error detecting Proton Pass:', error as Error, { component: 'WebAuthn' });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,10 +4,11 @@
|
||||
* Ce fichier centralise toutes les définitions de la base de données pour éviter
|
||||
* les erreurs de version et d'incohérence entre les différents fichiers.
|
||||
*/
|
||||
import { secureLogger } from './secure-logger';
|
||||
|
||||
export const DATABASE_CONFIG = {
|
||||
name: '4nk',
|
||||
version: 4,
|
||||
version: 5,
|
||||
stores: {
|
||||
wallet: {
|
||||
name: 'wallet',
|
||||
@ -75,18 +76,18 @@ export async function openDatabase(): Promise<IDBDatabase> {
|
||||
const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version);
|
||||
|
||||
request.onerror = () => {
|
||||
console.error(`❌ Failed to open database ${DATABASE_CONFIG.name}:`, request.error);
|
||||
secureLogger.error(`Failed to open database ${DATABASE_CONFIG.name}`, request.error as Error, { component: 'DatabaseConfig' });
|
||||
reject(request.error);
|
||||
};
|
||||
|
||||
request.onsuccess = () => {
|
||||
console.log(`✅ Database ${DATABASE_CONFIG.name} opened successfully`);
|
||||
secureLogger.info(`Database ${DATABASE_CONFIG.name} opened successfully`, { component: 'DatabaseConfig' });
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result;
|
||||
console.log(`🔄 Database upgrade needed for ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}`);
|
||||
secureLogger.info(`Database upgrade needed for ${DATABASE_CONFIG.name} version ${DATABASE_CONFIG.version}`, { component: 'DatabaseConfig' });
|
||||
|
||||
// Créer les stores manquants
|
||||
Object.values(DATABASE_CONFIG.stores).forEach(storeConfig => {
|
||||
@ -106,9 +107,9 @@ export async function openDatabase(): Promise<IDBDatabase> {
|
||||
store.createIndex(indexConfig.name, indexConfig.keyPath, { unique: indexConfig.unique || false });
|
||||
});
|
||||
|
||||
console.log(`✅ Created store: ${storeConfig.name}`);
|
||||
secureLogger.info(`Created store: ${storeConfig.name}`, { component: 'DatabaseConfig' });
|
||||
} else {
|
||||
console.log(`✅ Store already exists: ${storeConfig.name}`);
|
||||
secureLogger.info(`Store already exists: ${storeConfig.name}`, { component: 'DatabaseConfig' });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import Services from './service';
|
||||
import { DATABASE_CONFIG } from './database-config';
|
||||
import { secureLogger } from './secure-logger';
|
||||
|
||||
export class Database {
|
||||
private static instance: Database;
|
||||
@ -94,7 +95,7 @@ export class Database {
|
||||
};
|
||||
|
||||
request.onerror = () => {
|
||||
console.error('Database error:', request.error);
|
||||
secureLogger.error('Database error:', request.error as Error, { component: 'Database' });
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
@ -117,7 +118,7 @@ export class Database {
|
||||
|
||||
public async registerServiceWorker(path: string) {
|
||||
if (!('serviceWorker' in navigator)) {return;} // Ensure service workers are supported
|
||||
console.log('registering worker at', path);
|
||||
secureLogger.debug('Registering worker at', { component: 'Database', path });
|
||||
|
||||
try {
|
||||
// Get existing service worker registrations
|
||||
@ -127,7 +128,7 @@ export class Database {
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, {
|
||||
type: 'module',
|
||||
});
|
||||
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
|
||||
secureLogger.info('Service Worker registered with scope:', { component: 'Database', scope: this.serviceWorkerRegistration.scope });
|
||||
|
||||
// Show spinner during service worker initialization
|
||||
this.showServiceWorkerSpinner('Initializing database service...');
|
||||
@ -135,16 +136,16 @@ export class Database {
|
||||
// One existing worker: update it (restart it) without unregistering.
|
||||
this.serviceWorkerRegistration = registrations[0];
|
||||
await this.serviceWorkerRegistration.update();
|
||||
console.log('Service Worker updated');
|
||||
secureLogger.info('Service Worker updated', { component: 'Database' });
|
||||
} else {
|
||||
// More than one existing worker: unregister them all and register a new one.
|
||||
console.log('Multiple Service Worker(s) detected. Unregistering all...');
|
||||
secureLogger.warn('Multiple Service Worker(s) detected. Unregistering all...', { component: 'Database' });
|
||||
await Promise.all(registrations.map(reg => reg.unregister()));
|
||||
console.log('All previous Service Workers unregistered.');
|
||||
secureLogger.info('All previous Service Workers unregistered', { component: 'Database' });
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, {
|
||||
type: 'module',
|
||||
});
|
||||
console.log('Service Worker registered with scope:', this.serviceWorkerRegistration.scope);
|
||||
secureLogger.info('Service Worker registered with scope:', { component: 'Database', scope: this.serviceWorkerRegistration.scope });
|
||||
|
||||
// Show spinner during service worker initialization
|
||||
this.showServiceWorkerSpinner('Initializing database service...');
|
||||
@ -156,9 +157,9 @@ export class Database {
|
||||
this.checkForUpdates(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error('Update timeout')), 5000)),
|
||||
]);
|
||||
console.log('✅ Service worker updates completed');
|
||||
secureLogger.info('✅ Service worker updates completed', { component: 'Database' });
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Service worker update failed or timed out:', error);
|
||||
secureLogger.warn('⚠️ Service worker update failed or timed out:', { component: 'Database', error: String(error) });
|
||||
// Continue anyway - don't block the initialization
|
||||
}
|
||||
|
||||
@ -169,11 +170,11 @@ export class Database {
|
||||
|
||||
// Hide spinner once service worker is ready
|
||||
this.hideServiceWorkerSpinner();
|
||||
console.log('✅ Service worker initialization completed');
|
||||
secureLogger.info('✅ Service worker initialization completed', { component: 'Database' });
|
||||
|
||||
// Set up a global message listener for responses from the service worker.
|
||||
navigator.serviceWorker.addEventListener('message', async event => {
|
||||
console.log('Received message from service worker:', event.data);
|
||||
secureLogger.debug('Received message from service worker:', { component: 'Database', data: event.data });
|
||||
await this.handleServiceWorkerMessage(event.data);
|
||||
});
|
||||
|
||||
@ -193,13 +194,13 @@ export class Database {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Service worker scan failed:', error);
|
||||
secureLogger.warn('Service worker scan failed:', { component: 'Database', error: String(error) });
|
||||
// Continue the interval even if one scan fails
|
||||
}
|
||||
}, 5000);
|
||||
}, 10000); // Wait 10 seconds before starting the interval
|
||||
} catch (error) {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
secureLogger.error('Service Worker registration failed:', error as Error, { component: 'Database' });
|
||||
this.hideServiceWorkerSpinner();
|
||||
throw error; // Re-throw to be handled by the caller
|
||||
}
|
||||
@ -211,7 +212,7 @@ export class Database {
|
||||
): Promise<ServiceWorker | null> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
// Use reject parameter
|
||||
console.log('Service worker activation promise created');
|
||||
secureLogger.debug('Service worker activation promise created', { component: 'Database' });
|
||||
if (registration.active) {
|
||||
resolve(registration.active);
|
||||
return;
|
||||
@ -220,7 +221,7 @@ export class Database {
|
||||
// Set a timeout to prevent infinite waiting
|
||||
const timeout = setTimeout(() => {
|
||||
navigator.serviceWorker.removeEventListener('controllerchange', listener);
|
||||
console.warn('Service worker activation timeout');
|
||||
secureLogger.warn('Service worker activation timeout', { component: 'Database' });
|
||||
resolve(null); // Return null instead of rejecting to allow continuation
|
||||
}, 15000); // 15 second timeout
|
||||
|
||||
@ -250,7 +251,7 @@ export class Database {
|
||||
this.serviceWorkerRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for service worker updates:', error);
|
||||
secureLogger.error('Error checking for service worker updates:', error as Error, { component: 'Database' });
|
||||
throw error; // Re-throw to be caught by the calling function
|
||||
}
|
||||
}
|
||||
@ -262,7 +263,7 @@ export class Database {
|
||||
await this.handleDownloadList(message.data);
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown message type received from service worker:', message);
|
||||
secureLogger.warn('Unknown message type received from service worker:', { component: 'Database', message });
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,7 +275,7 @@ export class Database {
|
||||
const diff = await service.getDiffByValue(hash);
|
||||
if (!diff) {
|
||||
// This should never happen
|
||||
console.warn(`Missing a diff for hash ${hash}`);
|
||||
secureLogger.warn(`Missing a diff for hash ${hash}`, { component: 'Database' });
|
||||
continue;
|
||||
}
|
||||
const processId = diff.process_id;
|
||||
@ -297,7 +298,7 @@ export class Database {
|
||||
);
|
||||
} else {
|
||||
// We first request the data from managers
|
||||
console.log('Request data from managers of the process');
|
||||
secureLogger.debug('Request data from managers of the process', { component: 'Database' });
|
||||
// get the diff from db
|
||||
if (!requestedStateId.includes(stateId)) {
|
||||
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||
@ -305,21 +306,20 @@ export class Database {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
secureLogger.error('Error downloading missing data:', e as Error, { component: 'Database' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleAddObjectResponse = async (event: MessageEvent) => {
|
||||
// Use event parameter
|
||||
console.log('Add object response:', event);
|
||||
secureLogger.debug('Add object response received', { component: 'Database' });
|
||||
const data = event.data;
|
||||
console.log('Received response from service worker (ADD_OBJECT):', data);
|
||||
secureLogger.debug('Received response from service worker (ADD_OBJECT):', { component: 'Database', data });
|
||||
const service = await Services.getInstance();
|
||||
if (data.type === 'NOTIFICATIONS') {
|
||||
service.setNotifications(data.data);
|
||||
} else if (data.type === 'TO_DOWNLOAD') {
|
||||
console.log(`Received missing data ${data}`);
|
||||
secureLogger.debug(`Received missing data`, { component: 'Database', data });
|
||||
// Download the missing data
|
||||
const requestedStateId: string[] = [];
|
||||
for (const hash of data.data) {
|
||||
@ -331,7 +331,7 @@ export class Database {
|
||||
await service.saveBlobToDb(hash, blob);
|
||||
} else {
|
||||
// We first request the data from managers
|
||||
console.log('Request data from managers of the process');
|
||||
secureLogger.debug('Request data from managers of the process', { component: 'Database' });
|
||||
// get the diff from db
|
||||
const diff = await service.getDiffByValue(hash);
|
||||
if (diff === null) {
|
||||
@ -346,16 +346,14 @@ export class Database {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
secureLogger.error('Error downloading missing data:', e as Error, { component: 'Database' });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleGetObjectResponse = (event: MessageEvent) => {
|
||||
console.log('Received response from service worker (GET_OBJECT):', event.data);
|
||||
// Use event parameter
|
||||
console.log('Get object response event:', event);
|
||||
secureLogger.debug('Received response from service worker (GET_OBJECT):', { component: 'Database', data: event.data });
|
||||
};
|
||||
|
||||
public addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||
@ -368,10 +366,10 @@ export class Database {
|
||||
await this.addObjectAttempt(payload);
|
||||
return; // Success, exit retry loop
|
||||
} catch (error) {
|
||||
console.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, error);
|
||||
secureLogger.warn(`Attempt ${attempt}/${maxRetries} failed for addObject:`, { component: 'Database', error: String(error) });
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
console.error('All retry attempts failed for addObject');
|
||||
secureLogger.error('All retry attempts failed for addObject', { component: 'Database' });
|
||||
throw new Error(`Failed to add object after ${maxRetries} attempts: ${error}`);
|
||||
}
|
||||
|
||||
@ -381,23 +379,23 @@ export class Database {
|
||||
}
|
||||
}
|
||||
|
||||
private addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
private async addObjectAttempt(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
||||
// Check if the service worker is active
|
||||
if (!this.serviceWorkerRegistration) {
|
||||
secureLogger.debug('Service worker registration not ready, waiting...', { component: 'Database' });
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
}
|
||||
|
||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||
this.serviceWorkerRegistration
|
||||
);
|
||||
|
||||
if (!activeWorker) {
|
||||
throw new Error('Service worker not available');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Check if the service worker is active
|
||||
if (!this.serviceWorkerRegistration) {
|
||||
console.log('Service worker registration not ready, waiting...');
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
}
|
||||
|
||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||
this.serviceWorkerRegistration
|
||||
);
|
||||
|
||||
if (!activeWorker) {
|
||||
throw new Error('Service worker not available');
|
||||
}
|
||||
|
||||
// Create a message channel for communication
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
@ -447,10 +445,10 @@ export class Database {
|
||||
await this.batchWritingAttempt(payload);
|
||||
return; // Success, exit retry loop
|
||||
} catch (error) {
|
||||
console.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, error);
|
||||
secureLogger.warn(`Attempt ${attempt}/${maxRetries} failed for batchWriting:`, { component: 'Database', error: String(error) });
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
console.error('All retry attempts failed for batchWriting');
|
||||
secureLogger.error('All retry attempts failed for batchWriting', { component: 'Database' });
|
||||
throw new Error(`Failed to batch write objects after ${maxRetries} attempts: ${error}`);
|
||||
}
|
||||
|
||||
@ -464,21 +462,28 @@ export class Database {
|
||||
storeName: string;
|
||||
objects: { key: any; object: any }[];
|
||||
}): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if (!this.serviceWorkerRegistration) {
|
||||
console.log('Service worker registration not ready, waiting...');
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
}
|
||||
return this.batchWritingInternal(payload);
|
||||
}
|
||||
|
||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||
this.serviceWorkerRegistration
|
||||
);
|
||||
private async batchWritingInternal(payload: {
|
||||
storeName: string;
|
||||
objects: { key: any; object: any }[];
|
||||
}): Promise<void> {
|
||||
try {
|
||||
if (!this.serviceWorkerRegistration) {
|
||||
secureLogger.debug('Service worker registration not ready, waiting...', { component: 'Database' });
|
||||
this.serviceWorkerRegistration = await navigator.serviceWorker.ready;
|
||||
}
|
||||
|
||||
if (!activeWorker) {
|
||||
throw new Error('Service worker not available');
|
||||
}
|
||||
const activeWorker = await this.waitForServiceWorkerActivation(
|
||||
this.serviceWorkerRegistration
|
||||
);
|
||||
|
||||
if (!activeWorker) {
|
||||
throw new Error('Service worker not available');
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
// Set timeout for the operation
|
||||
@ -503,14 +508,14 @@ export class Database {
|
||||
},
|
||||
[messageChannel.port2]
|
||||
);
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to send message to service worker: ${error}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to send message to service worker: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getObject(storeName: string, key: string): Promise<any | null> {
|
||||
console.log(`🔍 DEBUG: Database.getObject - storeName: ${storeName}, key: ${key}`);
|
||||
secureLogger.debug(`Database.getObject - storeName: ${storeName}, key: ${key}`, { component: 'Database' });
|
||||
|
||||
// Utiliser directement IndexedDB au lieu du service worker pour éviter les problèmes de synchronisation
|
||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
@ -519,22 +524,22 @@ export class Database {
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
console.log(`🔍 DEBUG: Database.getObject - db obtained directly, objectStoreNames:`, Array.from(db.objectStoreNames));
|
||||
secureLogger.debug(`Database.getObject - db obtained directly`, { component: 'Database', objectStoreNames: Array.from(db.objectStoreNames) });
|
||||
const tx = db.transaction(storeName, 'readonly');
|
||||
const store = tx.objectStore(storeName);
|
||||
console.log(`🔍 DEBUG: Database.getObject - store opened: ${store.name}`);
|
||||
secureLogger.debug(`Database.getObject - store opened: ${store.name}`, { component: 'Database' });
|
||||
const result = await new Promise((resolve, reject) => {
|
||||
const getRequest = store.get(key);
|
||||
getRequest.onsuccess = () => {
|
||||
console.log(`🔍 DEBUG: Database.getObject - getRequest success, result:`, getRequest.result);
|
||||
secureLogger.debug(`Database.getObject - getRequest success`, { component: 'Database', hasResult: !!getRequest.result });
|
||||
resolve(getRequest.result);
|
||||
};
|
||||
getRequest.onerror = () => {
|
||||
console.log(`🔍 DEBUG: Database.getObject - getRequest error:`, getRequest.error);
|
||||
secureLogger.error(`Database.getObject - getRequest error`, getRequest.error as Error, { component: 'Database' });
|
||||
reject(getRequest.error);
|
||||
};
|
||||
});
|
||||
console.log(`🔍 DEBUG: Database.getObject - final result:`, result);
|
||||
secureLogger.debug(`Database.getObject - final result`, { component: 'Database', hasResult: !!result });
|
||||
return result ?? null; // Convert undefined to null
|
||||
}
|
||||
|
||||
@ -564,7 +569,7 @@ export class Database {
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching data from IndexedDB:', error);
|
||||
secureLogger.error('Error fetching data from IndexedDB:', error as Error, { component: 'Database' });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
import { DATABASE_CONFIG } from './database-config';
|
||||
import { SecureCredentialsService } from './secure-credentials.service';
|
||||
import { EncryptionService } from './encryption.service';
|
||||
import { secureLogger } from './secure-logger';
|
||||
|
||||
// Type simplifié pour éviter les problèmes d'import du SDK
|
||||
export interface Device {
|
||||
@ -38,7 +39,7 @@ export class DeviceReaderService {
|
||||
* Version légère sans nécessiter WebAssembly
|
||||
*/
|
||||
async getDeviceFromDatabase(): Promise<Device | null> {
|
||||
console.log('🔍 DeviceReaderService: Reading device from database...');
|
||||
secureLogger.debug('🔍 DeviceReaderService: Reading device from database...', { component: 'DeviceReader' });
|
||||
|
||||
// Utiliser directement IndexedDB
|
||||
const db = await new Promise<IDBDatabase>((resolve, reject) => {
|
||||
@ -59,14 +60,14 @@ export class DeviceReaderService {
|
||||
});
|
||||
|
||||
if (!dbRes) {
|
||||
console.log('🔍 DeviceReaderService: No device found in database');
|
||||
secureLogger.debug('🔍 DeviceReaderService: No device found in database', { component: 'DeviceReader' });
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if data is encrypted (new format) or plain (old format)
|
||||
if (dbRes['encrypted_device']) {
|
||||
// New encrypted format - need to decrypt
|
||||
console.log('🔐 DeviceReaderService: Device found in encrypted format, decrypting...');
|
||||
secureLogger.debug('🔐 DeviceReaderService: Device found in encrypted format, decrypting...', { component: 'DeviceReader' });
|
||||
|
||||
// Get the PBKDF2 key based on security mode
|
||||
const secureCredentialsService = SecureCredentialsService.getInstance();
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
* EventBus - Système de communication découplé
|
||||
* Permet la communication entre services sans couplage direct
|
||||
*/
|
||||
import { secureLogger } from './secure-logger';
|
||||
export interface EventData {
|
||||
[key: string]: any;
|
||||
}
|
||||
@ -46,7 +47,7 @@ export class EventBus {
|
||||
|
||||
// Vérifier la limite d'écouteurs
|
||||
if (eventListeners.length >= this.maxListeners) {
|
||||
console.warn(`Maximum listeners (${this.maxListeners}) reached for event: ${event}`);
|
||||
secureLogger.warn(`Maximum listeners (${this.maxListeners}) reached for event: ${event}`, { component: 'EventBus' });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,7 +107,7 @@ export class EventBus {
|
||||
try {
|
||||
listener(data);
|
||||
} catch (error) {
|
||||
console.error(`Error in event listener for ${event}:`, error);
|
||||
secureLogger.error(`Error in event listener for ${event}:`, error as Error, { component: 'EventBus' });
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -126,7 +127,7 @@ export class EventBus {
|
||||
const result = listener(data);
|
||||
return Promise.resolve(result);
|
||||
} catch (error) {
|
||||
console.error(`Error in async event listener for ${event}:`, error);
|
||||
secureLogger.error(`Error in async event listener for ${event}:`, error as Error, { component: 'EventBus' });
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
discoverAndJoinPairingProcessWithWords,
|
||||
prepareAndSendPairingTx,
|
||||
} from '../utils/sp-address.utils';
|
||||
import { secureLogger } from './secure-logger';
|
||||
|
||||
export default class IframePairingService {
|
||||
private static instance: IframePairingService;
|
||||
@ -29,7 +30,7 @@ export default class IframePairingService {
|
||||
// Detect if we're in an iframe
|
||||
if (window.parent !== window) {
|
||||
this.parentWindow = window.parent;
|
||||
console.log('🔗 Iframe pairing service initialized');
|
||||
secureLogger.info('🔗 Iframe pairing service initialized', { component: 'IframePairing' });
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,14 +50,14 @@ export default class IframePairingService {
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error handling iframe pairing message:', error);
|
||||
secureLogger.error('Error handling iframe pairing message:', error as Error, { component: 'IframePairing' });
|
||||
this.sendMessage(MessageType.PAIRING_4WORDS_ERROR, { error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCreatePairing(_data: any) {
|
||||
try {
|
||||
console.log('🔐 Creating pairing process via iframe...');
|
||||
secureLogger.info('🔐 Creating pairing process via iframe...', { component: 'IframePairing' });
|
||||
this._isCreator = true;
|
||||
|
||||
// Update status
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
* MemoryManager - Gestion intelligente de la mémoire
|
||||
* Surveille et optimise l'utilisation mémoire de l'application
|
||||
*/
|
||||
import { secureLogger } from './secure-logger';
|
||||
export interface MemoryStats {
|
||||
usedJSHeapSize: number;
|
||||
totalJSHeapSize: number;
|
||||
@ -188,7 +189,7 @@ export class MemoryManager {
|
||||
* Effectue un nettoyage de mémoire
|
||||
*/
|
||||
private performMemoryCleanup(): void {
|
||||
console.log('🧹 Performing memory cleanup...');
|
||||
secureLogger.info('🧹 Performing memory cleanup...', { component: 'MemoryManager' });
|
||||
|
||||
// Nettoyer les caches expirés
|
||||
this.cleanupExpiredEntries();
|
||||
@ -277,7 +278,8 @@ export class MemoryManager {
|
||||
private logMemoryStats(): void {
|
||||
const stats = this.getMemoryStats();
|
||||
if (stats) {
|
||||
console.log('📊 Memory Stats:', {
|
||||
secureLogger.debug('📊 Memory Stats', {
|
||||
component: 'MemoryManager',
|
||||
used: `${Math.round(stats.usedJSHeapSize / 1024 / 1024)}MB`,
|
||||
total: `${Math.round(stats.totalJSHeapSize / 1024 / 1024)}MB`,
|
||||
limit: `${Math.round(stats.jsHeapSizeLimit / 1024 / 1024)}MB`,
|
||||
@ -298,8 +300,7 @@ export class MemoryManager {
|
||||
const caches: Record<string, { size: number; entries: any[] }> = {};
|
||||
|
||||
this.caches.forEach((cache, name) => {
|
||||
// Use cache variable
|
||||
console.log('Cache:', cache);
|
||||
secureLogger.debug(`Cache stats for ${name}`, { component: 'MemoryManager', cacheSize: cache.size });
|
||||
caches[name] = this.getCacheStats(name);
|
||||
});
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { addressToEmoji } from '../utils/sp-address.utils';
|
||||
import { RoleDefinition } from 'pkg/sdk_client';
|
||||
import { initValidationModal } from '../components/validation-modal/validation-modal';
|
||||
import { interpolate } from '../utils/html.utils';
|
||||
import { secureLogger } from './secure-logger';
|
||||
|
||||
interface ConfirmationModalOptions {
|
||||
title: string;
|
||||
@ -97,7 +98,7 @@ export default class ModalService {
|
||||
throw new Error('Must have exactly 1 member');
|
||||
}
|
||||
|
||||
console.log('MEMBERS:', members);
|
||||
secureLogger.debug('Modal members list', { component: 'ModalService', membersCount: members.length });
|
||||
// We take all the addresses except our own
|
||||
const service = await Services.getInstance();
|
||||
const localAddress = service.getDeviceAddress();
|
||||
@ -126,7 +127,7 @@ export default class ModalService {
|
||||
};
|
||||
}
|
||||
confirmLogin() {
|
||||
console.log('=============> Confirm Login');
|
||||
secureLogger.info('Confirm Login action triggered', { component: 'ModalService' });
|
||||
}
|
||||
async closeLoginModal() {
|
||||
if (this.modal) {this.modal.style.display = 'none';}
|
||||
|
||||
@ -27,8 +27,7 @@ export class PairingService {
|
||||
private processRepo: ProcessRepository,
|
||||
private sdkClient: any
|
||||
) {
|
||||
// Use parameters
|
||||
console.log('Pairing service constructor:', { deviceRepo: this.deviceRepo, processRepo: this.processRepo, sdkClient: this.sdkClient });
|
||||
secureLogger.debug('Pairing service constructor initialized', { component: 'PairingService' });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,8 +81,6 @@ export class PairingService {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
// Use errorMessage variable
|
||||
console.log('Pairing error:', errorMessage);
|
||||
|
||||
secureLogger.error('Failed to create pairing process', error as Error, {
|
||||
component: 'PairingService',
|
||||
@ -131,8 +128,6 @@ export class PairingService {
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
// Use errorMessage variable
|
||||
console.log('Pairing error:', errorMessage);
|
||||
|
||||
secureLogger.error('Failed to join pairing process', error as Error, {
|
||||
component: 'PairingService',
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
* PerformanceMonitor - Surveillance des performances
|
||||
* Mesure et optimise les performances de l'application
|
||||
*/
|
||||
import { secureLogger } from './secure-logger';
|
||||
export interface PerformanceMetric {
|
||||
name: string;
|
||||
value: number;
|
||||
@ -54,7 +55,7 @@ export class PerformanceMonitor {
|
||||
if (this.isMonitoring) {return;}
|
||||
|
||||
this.isMonitoring = true;
|
||||
console.log('📊 Performance monitoring started');
|
||||
secureLogger.info('📊 Performance monitoring started', { component: 'PerformanceMonitor' });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -64,15 +65,13 @@ export class PerformanceMonitor {
|
||||
this.isMonitoring = false;
|
||||
this.observers.forEach(observer => observer.disconnect());
|
||||
this.observers = [];
|
||||
console.log('📊 Performance monitoring stopped');
|
||||
secureLogger.info('📊 Performance monitoring stopped', { component: 'PerformanceMonitor' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre une métrique de performance
|
||||
*/
|
||||
recordMetric(name: string, value: number, unit: string = 'ms'): void {
|
||||
// Use unit parameter
|
||||
console.log('Performance metric unit:', unit);
|
||||
recordMetric(name: string, value: number, _unit: string = 'ms'): void {
|
||||
if (!this.isMonitoring) {return;}
|
||||
|
||||
if (!this.metrics.has(name)) {
|
||||
@ -151,8 +150,6 @@ export class PerformanceMonitor {
|
||||
const result: Record<string, PerformanceStats> = {};
|
||||
|
||||
this.metrics.forEach((values, name) => {
|
||||
// Use values parameter
|
||||
console.log('Performance metric values:', values);
|
||||
const stats = this.getMetricStats(name);
|
||||
if (stats) {
|
||||
result[name] = stats;
|
||||
@ -220,9 +217,9 @@ export class PerformanceMonitor {
|
||||
if (!threshold) {return;}
|
||||
|
||||
if (value > threshold.critical) {
|
||||
console.warn(`🚨 Critical performance threshold exceeded for ${name}: ${value}ms`);
|
||||
secureLogger.warn(`🚨 Critical performance threshold exceeded for ${name}: ${value}ms`, { component: 'PerformanceMonitor', metric: name, value });
|
||||
} else if (value > threshold.warning) {
|
||||
console.warn(`⚠️ Performance warning for ${name}: ${value}ms`);
|
||||
secureLogger.warn(`⚠️ Performance warning for ${name}: ${value}ms`, { component: 'PerformanceMonitor', metric: name, value });
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,7 +242,7 @@ export class PerformanceMonitor {
|
||||
measureObserver.observe({ entryTypes: ['measure'] });
|
||||
this.observers.push(measureObserver);
|
||||
} catch (error) {
|
||||
console.warn('Failed to observe performance measures:', error);
|
||||
secureLogger.warn('Failed to observe performance measures', { component: 'PerformanceMonitor', error: String(error) });
|
||||
}
|
||||
|
||||
// Observer la navigation
|
||||
@ -263,7 +260,7 @@ export class PerformanceMonitor {
|
||||
navigationObserver.observe({ entryTypes: ['navigation'] });
|
||||
this.observers.push(navigationObserver);
|
||||
} catch (error) {
|
||||
console.warn('Failed to observe navigation performance:', error);
|
||||
secureLogger.warn('Failed to observe navigation performance', { component: 'PerformanceMonitor', error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,7 +282,7 @@ export class PerformanceMonitor {
|
||||
performance.measure(name, startMark);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to measure ${name}:`, error);
|
||||
secureLogger.warn(`Failed to measure ${name}`, { component: 'PerformanceMonitor', metric: name, error: String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,8 +291,6 @@ export class PerformanceMonitor {
|
||||
*/
|
||||
cleanup(): void {
|
||||
const cutoff = Date.now() - (24 * 60 * 60 * 1000); // 24 heures
|
||||
// Use cutoff variable
|
||||
console.log('Performance cleanup cutoff:', cutoff);
|
||||
|
||||
this.metrics.forEach((values, name) => {
|
||||
// Garder seulement les 100 dernières valeurs
|
||||
|
||||
@ -57,21 +57,33 @@ export class SecureLogger {
|
||||
/**
|
||||
* Log un avertissement
|
||||
*/
|
||||
warn(message: string, context?: LogContext): void {
|
||||
this.log(LogLevel.WARN, message, context);
|
||||
warn(message: string, contextOrError?: LogContext | Error | unknown, context?: LogContext): void {
|
||||
// If we have 3 params, second is error, third is context
|
||||
if (context) {
|
||||
// Message, error, context case
|
||||
this.log(LogLevel.WARN, message, context);
|
||||
} else if (contextOrError && (contextOrError instanceof Error || typeof contextOrError !== 'object')) {
|
||||
// Message, error case - convert to context if needed
|
||||
this.log(LogLevel.WARN, message, undefined);
|
||||
} else {
|
||||
// Message, context case
|
||||
this.log(LogLevel.WARN, message, contextOrError as LogContext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log une erreur
|
||||
*/
|
||||
error(message: string, error?: Error, context?: LogContext): void {
|
||||
this.log(LogLevel.ERROR, message, context, error);
|
||||
error(message: string, error?: Error | unknown, context?: LogContext): void {
|
||||
// Convert unknown to Error if needed
|
||||
const errorObj = error instanceof Error ? error : (error ? new Error(String(error)) : undefined);
|
||||
this.log(LogLevel.ERROR, message, context, errorObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log sécurisé avec sanitisation
|
||||
*/
|
||||
private log(level: LogLevel, message: string, context?: LogContext, error?: Error): void {
|
||||
private log(level: LogLevel, message: string, context?: LogContext, error?: Error | undefined): void {
|
||||
const sanitizedContext = this.sanitizeContext(context);
|
||||
const sanitizedMessage = this.sanitizeMessage(message);
|
||||
|
||||
|
||||
@ -1303,7 +1303,7 @@ export default class Services {
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
secureLogger.debug('Error in operation', e as Error, { component: 'Service' });
|
||||
secureLogger.debug('Error in operation', { component: 'Service', error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -1704,6 +1704,10 @@ export default class Services {
|
||||
throw new Error(` Credential retrieval failed: ${credentialError}`);
|
||||
}
|
||||
|
||||
if (!credentials) {
|
||||
throw new Error('No credentials found');
|
||||
}
|
||||
|
||||
secureLogger.debug(' Credentials to inject:', { component: 'Service', data: {
|
||||
spendKey_length: credentials.spendKey?.length || 0,
|
||||
scanKey_length: credentials.scanKey?.length || 0,
|
||||
@ -1711,10 +1715,6 @@ export default class Services {
|
||||
scanKey_type: typeof credentials.scanKey
|
||||
} });
|
||||
|
||||
if (!credentials) {
|
||||
throw new Error('No credentials found');
|
||||
}
|
||||
|
||||
// Injecter les clés dans le device de la base de données avant restauration
|
||||
secureLogger.info('🔧 Injecting keys into device from database...', { component: 'Service' });
|
||||
try {
|
||||
|
||||
@ -62,7 +62,7 @@ export class LogAnalyzer {
|
||||
/**
|
||||
* Suggère un contexte basé sur le nom du fichier et le message
|
||||
*/
|
||||
private suggestContext(file: string, message: string): string {
|
||||
private suggestContext(file: string, _message: string): string {
|
||||
const fileName = file.split('/').pop()?.replace('.ts', '') || '';
|
||||
|
||||
// Patterns pour identifier le composant
|
||||
|
||||
@ -25,7 +25,7 @@ export class LogFixer {
|
||||
// Remplacer console.log par secureLogger.info
|
||||
fixedContent = fixedContent.replace(
|
||||
/console\.log\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
||||
(match, message) => {
|
||||
(_match, message) => {
|
||||
const level = this.determineLogLevel(message);
|
||||
const context = this.determineContext(filePath, message);
|
||||
return `secureLogger.${level}('${message}'${context})`;
|
||||
@ -35,7 +35,7 @@ export class LogFixer {
|
||||
// Remplacer console.warn par secureLogger.warn
|
||||
fixedContent = fixedContent.replace(
|
||||
/console\.warn\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
||||
(match, message) => {
|
||||
(_match, message) => {
|
||||
const context = this.determineContext(filePath, message);
|
||||
return `secureLogger.warn('${message}'${context})`;
|
||||
}
|
||||
@ -44,7 +44,7 @@ export class LogFixer {
|
||||
// Remplacer console.error par secureLogger.error
|
||||
fixedContent = fixedContent.replace(
|
||||
/console\.error\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
||||
(match, message) => {
|
||||
(_match, message) => {
|
||||
const context = this.determineContext(filePath, message);
|
||||
return `secureLogger.error('${message}'${context})`;
|
||||
}
|
||||
@ -53,7 +53,7 @@ export class LogFixer {
|
||||
// Remplacer console.info par secureLogger.info
|
||||
fixedContent = fixedContent.replace(
|
||||
/console\.info\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
||||
(match, message) => {
|
||||
(_match, message) => {
|
||||
const context = this.determineContext(filePath, message);
|
||||
return `secureLogger.info('${message}'${context})`;
|
||||
}
|
||||
@ -62,7 +62,7 @@ export class LogFixer {
|
||||
// Remplacer console.debug par secureLogger.debug
|
||||
fixedContent = fixedContent.replace(
|
||||
/console\.debug\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g,
|
||||
(match, message) => {
|
||||
(_match, message) => {
|
||||
const context = this.determineContext(filePath, message);
|
||||
return `secureLogger.debug('${message}'${context})`;
|
||||
}
|
||||
@ -95,7 +95,7 @@ export class LogFixer {
|
||||
/**
|
||||
* Détermine le contexte basé sur le fichier et le message
|
||||
*/
|
||||
private static determineContext(filePath: string, message: string): string {
|
||||
private static determineContext(filePath: string, _message: string): string {
|
||||
const fileName = filePath.split('/').pop()?.replace('.ts', '') || '';
|
||||
|
||||
let component = 'Application';
|
||||
|
||||
@ -2593,7 +2593,7 @@ async function onCreateButtonClick() {
|
||||
}
|
||||
|
||||
// New function for joiner to discover and join existing pairing process using 4 words
|
||||
export async function discoverAndJoinPairingProcessWithWords(words: string): Promise<void> {
|
||||
export async function discoverAndJoinPairingProcessWithWords(_words: string): Promise<void> {
|
||||
const service = await Services.getInstance();
|
||||
|
||||
try {
|
||||
@ -2647,13 +2647,13 @@ export async function discoverAndJoinPairingProcessWithWords(words: string): Pro
|
||||
|
||||
throw new Error(`❌ Failed to discover pairing process after ${maxRetries} attempts`);
|
||||
} catch (err) {
|
||||
secureLogger.error('❌ Joiner discovery failed:', (err as Error, { component: 'SPAddressUtils' }).message);
|
||||
secureLogger.error('❌ Joiner discovery failed:', err as Error, { component: 'SPAddressUtils' });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// New function for joiner to discover and join existing pairing process
|
||||
export async function discoverAndJoinPairingProcess(creatorAddress: string): Promise<void> {
|
||||
export async function discoverAndJoinPairingProcess(_creatorAddress: string): Promise<void> {
|
||||
const service = await Services.getInstance();
|
||||
|
||||
try {
|
||||
@ -2707,7 +2707,7 @@ export async function discoverAndJoinPairingProcess(creatorAddress: string): Pro
|
||||
|
||||
throw new Error(`❌ Failed to discover pairing process after ${maxRetries} attempts`);
|
||||
} catch (err) {
|
||||
secureLogger.error('❌ Joiner discovery failed:', (err as Error, { component: 'SPAddressUtils' }).message);
|
||||
secureLogger.error('❌ Joiner discovery failed:', err as Error, { component: 'SPAddressUtils' });
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { defineConfig } from 'vite';
|
||||
// import path from 'path';
|
||||
import path from 'path';
|
||||
// @ts-ignore - vite-plugin-wasm type definitions issue
|
||||
import wasm from 'vite-plugin-wasm';
|
||||
import topLevelAwait from 'vite-plugin-top-level-await';
|
||||
@ -19,6 +19,18 @@ export default defineConfig({
|
||||
target: 'esnext',
|
||||
minify: true, // Enable minification to reduce size
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: path.resolve(__dirname, 'index.html'),
|
||||
'wallet-setup': path.resolve(__dirname, 'src/pages/wallet-setup/wallet-setup.html'),
|
||||
'security-setup': path.resolve(__dirname, 'src/pages/security-setup/security-setup.html'),
|
||||
'birthday-setup': path.resolve(__dirname, 'src/pages/birthday-setup/birthday-setup.html'),
|
||||
'block-sync': path.resolve(__dirname, 'src/pages/block-sync/block-sync.html'),
|
||||
'pairing': path.resolve(__dirname, 'src/pages/pairing/pairing.html'),
|
||||
'home': path.resolve(__dirname, 'src/pages/home/home.html'),
|
||||
'iframe-home': path.resolve(__dirname, 'src/pages/home/iframe-home.html'),
|
||||
'account': path.resolve(__dirname, 'src/pages/account/account.html'),
|
||||
'iframe-pairing': path.resolve(__dirname, 'src/pages/iframe-pairing.html'),
|
||||
},
|
||||
output: {
|
||||
manualChunks: {
|
||||
// Split WebAssembly into separate chunk
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user