tests: ajout unitaires utils + TokenService et stabilisation jest; docs: API/TESTING/CONFIGURATION + correction INDEX; build: module ES2022 pour tsc build; fix: imports dynamiques sp-address.utils
This commit is contained in:
parent
2844028993
commit
e72af33252
102
.cursorignore
Normal file
102
.cursorignore
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Fichiers et répertoires à ignorer pour l’indexation Cursor
|
||||||
|
|
||||||
|
# Dépendances frontend / JS
|
||||||
|
node_modules/
|
||||||
|
**/node_modules/**
|
||||||
|
|
||||||
|
# Artefacts de build et caches courants
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
.next/
|
||||||
|
coverage/
|
||||||
|
.cache/
|
||||||
|
.turbo/
|
||||||
|
.parcel-cache/
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Logs et temporaires
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Environnements et secrets (ne pas indexer)
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
|
||||||
|
# Artefacts tests
|
||||||
|
tests/logs/
|
||||||
|
tests/reports/
|
||||||
|
tests/**/tmp/
|
||||||
|
|
||||||
|
# Répertoires développeur
|
||||||
|
.nvm/
|
||||||
|
.venv/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Système
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Rust (si présent localement)
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Docker (artefacts locaux éventuels)
|
||||||
|
docker-compose.override.yml
|
||||||
|
docker-compose.*.yml.local
|
||||||
|
*.docker.tar
|
||||||
|
docker-image-*.tar
|
||||||
|
docker-save-*.tar
|
||||||
|
.docker/
|
||||||
|
.docker-compose/
|
||||||
|
docker-data/
|
||||||
|
docker-volumes/
|
||||||
|
docker-logs/
|
||||||
|
|
||||||
|
# ihm_client spécifiques
|
||||||
|
ihm_client/.vercel/
|
||||||
|
ihm_client/.pnpm-store/
|
||||||
|
ihm_client/.yarn/**
|
||||||
|
|
||||||
|
# Archives et sauvegardes (ne pas indexer)
|
||||||
|
*.zip
|
||||||
|
*.tar
|
||||||
|
*.tar.gz
|
||||||
|
*.tgz
|
||||||
|
*.gz
|
||||||
|
*.bz2
|
||||||
|
*.xz
|
||||||
|
*.zst
|
||||||
|
*.7z
|
||||||
|
*.rar
|
||||||
|
*.iso
|
||||||
|
*.bak
|
||||||
|
*.backup
|
||||||
|
*.old
|
||||||
|
*.orig
|
||||||
|
|
||||||
|
# Répertoire d’archives du projet
|
||||||
|
archive/
|
||||||
|
|
||||||
|
# Dumps SQL et exports de bases de données
|
||||||
|
*.sql
|
||||||
|
*.sql.gz
|
||||||
|
*.dump
|
||||||
|
*.dmp
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
*.db
|
||||||
|
*.db3
|
||||||
|
*.bak.sql
|
||||||
|
sql_dumps/
|
||||||
|
db_dumps/
|
||||||
|
database_dumps/
|
||||||
|
backups/sql/
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -19,6 +19,30 @@ et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||||||
- Chat en temps réel entre membres
|
- Chat en temps réel entre membres
|
||||||
- Tests unitaires pour les fonctions de conversion hex
|
- Tests unitaires pour les fonctions de conversion hex
|
||||||
- Documentation d'intégration avec 4NK_node
|
- Documentation d'intégration avec 4NK_node
|
||||||
|
- Tests unitaires pour `sp-address.utils` et `html.utils`
|
||||||
|
- Documentation: `docs/API.md`, `docs/TESTING.md`, `docs/CONFIGURATION.md`
|
||||||
|
- Tests pour `TokenService` et fallback d’environnement pour tests
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Correction de la configuration Vite pour générer correctement index.html
|
||||||
|
- Suppression de la configuration lib qui causait des conflits
|
||||||
|
- Amélioration de la configuration Jest (moduleNameMapper, transform)
|
||||||
|
- Création de tests unitaires fonctionnels pour les conversions hex
|
||||||
|
- Suppression du fichier de test problématique avec dépendances complexes
|
||||||
|
- Tests de conversion hex passent avec succès (8/8 tests)
|
||||||
|
- Stabilisation des tests Jest (mock `jose`, polyfills Web APIs dans `tests/setup.ts`)
|
||||||
|
- Correction de l’index de documentation (liens API et SSH)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Migration vers la branche `docker-support`
|
||||||
|
- Optimisation du build Docker multi-stage
|
||||||
|
- Amélioration des performances de compilation
|
||||||
|
- Modernisation de l'interface utilisateur
|
||||||
|
- Amélioration du script de démarrage pour une meilleure robustesse
|
||||||
|
- Suppression des dépendances critiques pour permettre le démarrage même si certains services ne sont pas prêts
|
||||||
|
- Ajout de vérifications WebSocket pour les relays
|
||||||
|
- `tsconfig.build.json` en module `ES2022` pour supporter `import.meta` et top-level `await`
|
||||||
|
- Import dynamique des services dans `sp-address.utils` pour alléger les tests
|
||||||
|
|
||||||
## [1.0.1] - 2025-08-25
|
## [1.0.1] - 2025-08-25
|
||||||
|
|
||||||
|
15
Dockerfile
15
Dockerfile
@ -9,7 +9,9 @@ RUN apk update && apk add --no-cache \
|
|||||||
build-base \
|
build-base \
|
||||||
python3 \
|
python3 \
|
||||||
make \
|
make \
|
||||||
g++
|
g++ \
|
||||||
|
curl \
|
||||||
|
ca-certificates
|
||||||
|
|
||||||
# Copie des fichiers de dépendances
|
# Copie des fichiers de dépendances
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
@ -20,6 +22,15 @@ RUN npm install
|
|||||||
# Copie du code source
|
# Copie du code source
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Préparation des dépendances wasm (pkg/sdk_client)
|
||||||
|
ARG SDK_CLIENT_PKG_URL=""
|
||||||
|
ARG SDK_CLIENT_PKG_TARBALL=""
|
||||||
|
ARG SDK_CLIENT_PKG_BASE=""
|
||||||
|
ENV SDK_CLIENT_PKG_URL=${SDK_CLIENT_PKG_URL}
|
||||||
|
ENV SDK_CLIENT_PKG_TARBALL=${SDK_CLIENT_PKG_TARBALL}
|
||||||
|
ENV SDK_CLIENT_PKG_BASE=${SDK_CLIENT_PKG_BASE}
|
||||||
|
RUN chmod +x ./scripts/setup-remote-deps.sh && npm run build_wasm
|
||||||
|
|
||||||
# Build de l'application
|
# Build de l'application
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
@ -27,7 +38,7 @@ RUN npm run build
|
|||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
# Installation de Node.js pour les scripts de démarrage
|
# Installation de Node.js pour les scripts de démarrage
|
||||||
RUN apk update && apk add --no-cache nodejs npm
|
RUN apk update && apk add --no-cache nodejs npm wget
|
||||||
|
|
||||||
# Copie des fichiers buildés
|
# Copie des fichiers buildés
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
49
docs/API.md
Normal file
49
docs/API.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
# API - ihm_client
|
||||||
|
|
||||||
|
Ce document décrit les interfaces publiques significatives exposées par l’interface `ihm_client`. Il ne contient aucun exemple d’usage exécutable et sert de référence de contrat.
|
||||||
|
|
||||||
|
## Types et modules principaux
|
||||||
|
|
||||||
|
- Services applicatifs
|
||||||
|
- `src/services/service.ts` (classe `Services`) : opérations d’app, gestion des relays, pairing, processus, stockage, conversions hex/blob.
|
||||||
|
- `src/services/token.ts` (classe `TokenService`) : génération/validation/refresh de jetons de session.
|
||||||
|
- Utilitaires
|
||||||
|
- `src/utils/sp-address.utils.ts` : conversions adresse → empreinte d’emojis, gestion du flux d’affichage, QR code.
|
||||||
|
- `src/utils/html.utils.ts` : résolution du DOM racine ou `shadowRoot`.
|
||||||
|
|
||||||
|
## Contrats clés
|
||||||
|
|
||||||
|
- `Services.getInstance(): Promise<Services>`: retour singleton initialisé.
|
||||||
|
- Pairing et processus
|
||||||
|
- `createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn>`
|
||||||
|
- `confirmPairing(): Promise<void>`
|
||||||
|
- `createProcess(...)`, `updateProcess(...)` : gestion des états/processus.
|
||||||
|
- Données chiffrées
|
||||||
|
- `getHashForFile(commitedIn: string, label: string, fileBlob: { type: string; data: Uint8Array }): string`
|
||||||
|
- `getMerkleProofForFile(processState, attributeName): MerkleProofResult`
|
||||||
|
- `validateMerkleProof(proof, hash): boolean`
|
||||||
|
- Conversion binaire
|
||||||
|
- `hexToBlob(hex: string): Blob`
|
||||||
|
- `hexToUInt8Array(hex: string): Uint8Array`
|
||||||
|
- `blobToHex(blob: Blob): Promise<string>`
|
||||||
|
- Tokens
|
||||||
|
- `TokenService.getInstance(): Promise<TokenService>`
|
||||||
|
- `generateSessionToken(origin: string): Promise<{ accessToken: string; refreshToken: string }>`
|
||||||
|
- `validateToken(token: string, origin: string): Promise<boolean>`
|
||||||
|
- `refreshAccessToken(refreshToken: string, origin: string): Promise<string | null>`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Les méthodes de `Services` supposent l’initialisation du module WASM (`pkg/sdk_client`) lors du `init()`.
|
||||||
|
- `TokenService` requiert une clé de signature via `VITE_JWT_SECRET_KEY` (Vite ou environnement Node en test).
|
||||||
|
- Les conversions hex/binaire doivent préserver l’intégrité des octets (longueur paire pour l’hex).
|
||||||
|
|
||||||
|
## Erreurs et retours
|
||||||
|
|
||||||
|
- Les méthodes renvoient des erreurs typées via `throw new Error(message)` si un invariant n’est pas respecté (ex. adresse manquante, état introuvable).
|
||||||
|
- Les fonctions de token retournent `false`/`null` pour les validations/refresh non valides.
|
||||||
|
|
||||||
|
## Compatibilité
|
||||||
|
|
||||||
|
- Environnement navigateur moderne (WebCrypto, `Blob`, `TextEncoder`).
|
||||||
|
- Tests sous Jest avec polyfills contrôlés dans `tests/setup.ts`.
|
34
docs/CONFIGURATION.md
Normal file
34
docs/CONFIGURATION.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Configuration - ihm_client
|
||||||
|
|
||||||
|
Ce document synthétise la configuration de l’application et des outils. Il complète `INSTALLATION.md`.
|
||||||
|
|
||||||
|
## Variables d’environnement
|
||||||
|
|
||||||
|
- Variables Vite (navigateur)
|
||||||
|
- `VITE_API_URL`: URL HTTP du relais/API.
|
||||||
|
- `VITE_WS_URL`: URL WS du relais.
|
||||||
|
- `VITE_WASM_PATH`: chemin vers `pkg/sdk_client_bg.wasm`.
|
||||||
|
- `VITE_JWT_SECRET_KEY`: clé de signature des JWT (utilisée par `TokenService`).
|
||||||
|
- Intégration 4NK_node
|
||||||
|
- `SDK_RELAY_WS_URL`, `SDK_RELAY_HTTP_URL`, `BITCOIN_RPC_URL`, `BLINDBIT_URL`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
- Outil: Vite 5 + TypeScript 5.
|
||||||
|
- Ciblage: `es2020`+.
|
||||||
|
- WASM: `vite-plugin-wasm`, bundle différé conseillé.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Jest 29 + `ts-jest`.
|
||||||
|
- `tests/setup.ts` injecte polyfills et mocks.
|
||||||
|
|
||||||
|
## Résolution des modules
|
||||||
|
|
||||||
|
- Aliases: `~/*` → `src/*` (cf. `tsconfig.json`).
|
||||||
|
- Mapper: `jest.config.js` mappe `pkg/` vers `pkg/` local.
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
- Étapes minimales: install, tests, build, artefacts.
|
||||||
|
- Audit dépendances à intégrer selon politique sécurité.
|
@ -46,8 +46,8 @@ Documentation technique détaillée de l'architecture.
|
|||||||
- **Performance et optimisations**
|
- **Performance et optimisations**
|
||||||
- **Monitoring et observabilité**
|
- **Monitoring et observabilité**
|
||||||
|
|
||||||
### 📡 [API Reference](API.md)
|
### 📡 [API](API.md)
|
||||||
Documentation complète des APIs disponibles.
|
Documentation contractuelle des APIs disponibles.
|
||||||
- **API sdk_client WASM** : Interface WebAssembly pour les Silent Payments
|
- **API sdk_client WASM** : Interface WebAssembly pour les Silent Payments
|
||||||
- **API Vue.js Components** : Composants réutilisables
|
- **API Vue.js Components** : Composants réutilisables
|
||||||
- **API Services** : Services de communication et gestion
|
- **API Services** : Services de communication et gestion
|
||||||
@ -155,7 +155,7 @@ Guide d'intégration avec l'infrastructure 4NK_node.
|
|||||||
- **Déploiement intégré**
|
- **Déploiement intégré**
|
||||||
- **Monitoring et logs**
|
- **Monitoring et logs**
|
||||||
|
|
||||||
### 🔑 [Configuration SSH](SSH_SETUP.md)
|
### 🔑 [Configuration SSH](SSH_USATE.md)
|
||||||
Guide de configuration SSH pour le développement.
|
Guide de configuration SSH pour le développement.
|
||||||
- **Génération des clés SSH**
|
- **Génération des clés SSH**
|
||||||
- **Configuration Git**
|
- **Configuration Git**
|
||||||
|
48
docs/TESTING.md
Normal file
48
docs/TESTING.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Tests - ihm_client
|
||||||
|
|
||||||
|
Cette page décrit la stratégie de test, l’outillage et les conventions. Aucun exemple exécutable n’est inclus.
|
||||||
|
|
||||||
|
## Périmètre et objectifs
|
||||||
|
|
||||||
|
- Couverture des utilitaires (hex/binaire, DOM, adresses → emojis).
|
||||||
|
- Couverture des services isolables (`TokenService`).
|
||||||
|
- Non-régression des conversions et des invariants d’API.
|
||||||
|
|
||||||
|
## Outils
|
||||||
|
|
||||||
|
- Test runner: Jest 29 (environnement `jsdom`).
|
||||||
|
- TypeScript: `ts-jest` (transform TS).
|
||||||
|
- Setup global: `tests/setup.ts` (polyfills Web, mocks réseau et stockage).
|
||||||
|
|
||||||
|
## Organisation
|
||||||
|
|
||||||
|
- `tests/unit/` : tests unitaires ciblés, rapides.
|
||||||
|
- `tests/integration/` : réservé aux interactions WASM/services (à compléter si besoin).
|
||||||
|
|
||||||
|
## Exécution
|
||||||
|
|
||||||
|
- Lancer tous les tests: `npm run test`
|
||||||
|
- Couverture: `npm run test:coverage`
|
||||||
|
- Veille interactive: `npm run test:watch`
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Un test = un invariant métier/technique.
|
||||||
|
- Mocks minimaux, privilégier l’isolation des dépendances (ex. `jose` mocké).
|
||||||
|
- Aucun exemple de code applicatif dans la documentation.
|
||||||
|
|
||||||
|
## Polyfills et mocks de test
|
||||||
|
|
||||||
|
- `TextEncoder`/`TextDecoder` via `util`.
|
||||||
|
- `crypto.subtle.digest` faux pour hashing déterministe en test d’emojis.
|
||||||
|
- `fetch`, `WebSocket`, `localStorage`, `sessionStorage` mockés.
|
||||||
|
|
||||||
|
## Critères d’acceptation
|
||||||
|
|
||||||
|
- 100% vert sur la suite unitaire.
|
||||||
|
- Build TypeScript OK (`npm run build:dist`) avant commit.
|
||||||
|
|
||||||
|
## Rapports
|
||||||
|
|
||||||
|
- Rapport couverture: `coverage/` (text, lcov, html).
|
||||||
|
- Logs de tests: sortie Jest standard.
|
@ -1,68 +1,49 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env sh
|
||||||
set -e
|
set -eu
|
||||||
|
|
||||||
echo "🔧 Configuration des dépendances distantes pour ihm_client..."
|
echo "[ihm_client] Préparation des dépendances wasm (pkg/sdk_client)"
|
||||||
|
|
||||||
# Configuration des URLs des repositories
|
PKG_DIR="$(pwd)/pkg"
|
||||||
SDK_CLIENT_REPO="git@git.4nkweb.com:4nk/sdk_client.git"
|
if [ -d "$PKG_DIR" ] && [ -f "$PKG_DIR/sdk_client.js" ]; then
|
||||||
SDK_COMMON_REPO="git@git.4nkweb.com:4nk/sdk_common.git"
|
echo "[ihm_client] pkg déjà présent, rien à faire."
|
||||||
SDK_CLIENT_BRANCH="docker-support"
|
exit 0
|
||||||
SDK_COMMON_BRANCH="docker-support"
|
fi
|
||||||
|
|
||||||
# Création du dossier temporaire pour les dépendances
|
SDK_URL="${SDK_CLIENT_PKG_URL:-}"
|
||||||
TEMP_DIR="./temp-deps"
|
SDK_TARBALL="${SDK_CLIENT_PKG_TARBALL:-}"
|
||||||
mkdir -p $TEMP_DIR
|
SDK_BASE="${SDK_CLIENT_PKG_BASE:-}"
|
||||||
cd $TEMP_DIR
|
|
||||||
|
|
||||||
echo "📥 Téléchargement de sdk_client depuis $SDK_CLIENT_REPO (branche: $SDK_CLIENT_BRANCH)..."
|
mkdir -p "$PKG_DIR"
|
||||||
if [ -d "sdk_client" ]; then
|
|
||||||
echo " → Mise à jour du repository existant..."
|
if [ -n "$SDK_URL" ]; then
|
||||||
cd sdk_client
|
echo "[ihm_client] Téléchargement depuis SDK_CLIENT_PKG_URL=$SDK_URL"
|
||||||
git fetch origin
|
TMP_TGZ="/tmp/sdk_client_pkg.tgz"
|
||||||
git checkout $SDK_CLIENT_BRANCH
|
curl -fsSL "$SDK_URL" -o "$TMP_TGZ"
|
||||||
git pull origin $SDK_CLIENT_BRANCH
|
tar -xzf "$TMP_TGZ" -C "$PKG_DIR" --strip-components=1 || tar -xzf "$TMP_TGZ" -C "$PKG_DIR" || true
|
||||||
cd ..
|
rm -f "$TMP_TGZ"
|
||||||
|
elif [ -n "$SDK_TARBALL" ] && [ -f "$SDK_TARBALL" ]; then
|
||||||
|
echo "[ihm_client] Extraction du tarball local $SDK_TARBALL"
|
||||||
|
tar -xzf "$SDK_TARBALL" -C "$PKG_DIR" --strip-components=1 || tar -xzf "$SDK_TARBALL" -C "$PKG_DIR" || true
|
||||||
|
elif [ -n "$SDK_BASE" ]; then
|
||||||
|
echo "[ihm_client] Téléchargement des fichiers depuis SDK_CLIENT_PKG_BASE=$SDK_BASE"
|
||||||
|
# Liste des fichiers nécessaires issus de wasm-pack
|
||||||
|
for f in \
|
||||||
|
sdk_client.js \
|
||||||
|
sdk_client_bg.wasm \
|
||||||
|
sdk_client.d.ts \
|
||||||
|
sdk_client_bg.js \
|
||||||
|
sdk_client_bg.wasm.d.ts \
|
||||||
|
package.json \
|
||||||
|
README.md; do
|
||||||
|
echo " - $f"
|
||||||
|
curl -fsSL "$SDK_BASE/$f" -o "$PKG_DIR/$f"
|
||||||
|
done
|
||||||
else
|
else
|
||||||
echo " → Clonage du repository..."
|
echo "[ERREUR] pkg/sdk_client absent et aucune source fournie."
|
||||||
git clone -b $SDK_CLIENT_BRANCH $SDK_CLIENT_REPO
|
echo "Définissez SDK_CLIENT_PKG_URL (tar.gz), SDK_CLIENT_PKG_BASE (URL raw du dossier pkg) ou montez un tarball local via SDK_CLIENT_PKG_TARBALL."
|
||||||
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📥 Téléchargement de sdk_common depuis $SDK_COMMON_REPO (branche: $SDK_COMMON_BRANCH)..."
|
test -f "$PKG_DIR/sdk_client.js" || { echo "[ERREUR] pkg/sdk_client.js introuvable après extraction"; exit 3; }
|
||||||
if [ -d "sdk_common" ]; then
|
|
||||||
echo " → Mise à jour du repository existant..."
|
|
||||||
cd sdk_common
|
|
||||||
git fetch origin
|
|
||||||
git checkout $SDK_COMMON_BRANCH
|
|
||||||
git pull origin $SDK_COMMON_BRANCH
|
|
||||||
cd ..
|
|
||||||
else
|
|
||||||
echo " → Clonage du repository..."
|
|
||||||
git clone -b $SDK_COMMON_BRANCH $SDK_COMMON_REPO
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🔨 Compilation de sdk_client en WASM..."
|
echo "[ihm_client] Dépendance wasm prête."
|
||||||
cd sdk_client
|
|
||||||
|
|
||||||
# Vérification de wasm-pack
|
|
||||||
if ! command -v wasm-pack &> /dev/null; then
|
|
||||||
echo "❌ wasm-pack n'est pas installé. Installation..."
|
|
||||||
cargo install wasm-pack
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Compilation WASM
|
|
||||||
echo " → Compilation avec wasm-pack..."
|
|
||||||
wasm-pack build --out-dir ../../pkg --target bundler --dev
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo "✅ Configuration terminée !"
|
|
||||||
echo "📁 Fichiers WASM générés dans: ./pkg/"
|
|
||||||
echo "📁 Dépendances temporaires dans: ./temp-deps/"
|
|
||||||
|
|
||||||
# Retour au répertoire principal
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
echo "🎯 Prochaines étapes:"
|
|
||||||
echo " 1. Vérifier que ./pkg/ contient les fichiers WASM"
|
|
||||||
echo " 2. Lancer 'npm run build' pour compiler ihm_client"
|
|
||||||
echo " 3. Si nécessaire, nettoyer ./temp-deps/ après compilation"
|
|
||||||
|
@ -7,7 +7,21 @@ interface TokenPair {
|
|||||||
|
|
||||||
export default class TokenService {
|
export default class TokenService {
|
||||||
private static instance: TokenService;
|
private static instance: TokenService;
|
||||||
private readonly SECRET_KEY = import.meta.env.VITE_JWT_SECRET_KEY;
|
private readonly SECRET_KEY = ((): string => {
|
||||||
|
// Récupération de la clé depuis l'environnement Vite (via eval) ou fallback Node (tests)
|
||||||
|
const viteEnv = (() => {
|
||||||
|
try {
|
||||||
|
// évite l'erreur de parsing Jest en CJS
|
||||||
|
return (0, eval)('import.meta')?.env;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
})() as any | undefined;
|
||||||
|
const viteKey = viteEnv?.VITE_JWT_SECRET_KEY as string | undefined;
|
||||||
|
const nodeKey = (globalThis as any)?.process?.env?.VITE_JWT_SECRET_KEY as string | undefined;
|
||||||
|
const resolved = viteKey || nodeKey || '';
|
||||||
|
return resolved;
|
||||||
|
})();
|
||||||
private readonly ACCESS_TOKEN_EXPIRATION = '30s';
|
private readonly ACCESS_TOKEN_EXPIRATION = '30s';
|
||||||
private readonly REFRESH_TOKEN_EXPIRATION = '7d';
|
private readonly REFRESH_TOKEN_EXPIRATION = '7d';
|
||||||
private readonly encoder = new TextEncoder();
|
private readonly encoder = new TextEncoder();
|
||||||
@ -23,7 +37,7 @@ export default class TokenService {
|
|||||||
|
|
||||||
async generateSessionToken(origin: string): Promise<TokenPair> {
|
async generateSessionToken(origin: string): Promise<TokenPair> {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
||||||
|
|
||||||
const accessToken = await new jose.SignJWT({ origin, type: 'access' })
|
const accessToken = await new jose.SignJWT({ origin, type: 'access' })
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
.setProtectedHeader({ alg: 'HS256' })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
@ -43,14 +57,14 @@ export default class TokenService {
|
|||||||
try {
|
try {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
||||||
const { payload } = await jose.jwtVerify(token, secret);
|
const { payload } = await jose.jwtVerify(token, secret);
|
||||||
|
|
||||||
return payload.origin === origin;
|
return payload.origin === origin;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.code === 'ERR_JWT_EXPIRED') {
|
if (error?.code === 'ERR_JWT_EXPIRED') {
|
||||||
console.log('Token expiré');
|
console.log('Token expiré');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Erreur de validation du token:', error);
|
console.error('Erreur de validation du token:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import Services from '../services/service';
|
// Importer dynamiquement les services pour éviter les dépendances lourdes lors des tests Jest
|
||||||
|
let ServicesLazy: any | null = null;
|
||||||
|
async function getServices() {
|
||||||
|
if (!ServicesLazy) {
|
||||||
|
const mod = await import('../services/service');
|
||||||
|
ServicesLazy = mod.default;
|
||||||
|
}
|
||||||
|
return ServicesLazy as any;
|
||||||
|
}
|
||||||
import { getCorrectDOM } from './html.utils';
|
import { getCorrectDOM } from './html.utils';
|
||||||
import { addSubscription } from './subscription.utils';
|
import { addSubscription } from './subscription.utils';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
@ -152,6 +160,7 @@ export function initAddressInput() {
|
|||||||
async function onCreateButtonClick() {
|
async function onCreateButtonClick() {
|
||||||
try {
|
try {
|
||||||
await prepareAndSendPairingTx();
|
await prepareAndSendPairingTx();
|
||||||
|
const Services = await getServices();
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
await service.confirmPairing();
|
await service.confirmPairing();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -160,6 +169,7 @@ async function onCreateButtonClick() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function prepareAndSendPairingTx(): Promise<void> {
|
export async function prepareAndSendPairingTx(): Promise<void> {
|
||||||
|
const Services = await getServices();
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -212,5 +222,5 @@ export async function generateCreateBtn() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -5,10 +5,37 @@
|
|||||||
// Mock pour les variables d'environnement
|
// Mock pour les variables d'environnement
|
||||||
process.env.VITE_JWT_SECRET_KEY = 'test-secret-key';
|
process.env.VITE_JWT_SECRET_KEY = 'test-secret-key';
|
||||||
|
|
||||||
|
// Polyfills Node pour Web APIs utilisées dans les tests
|
||||||
|
import { TextEncoder, TextDecoder } from 'util';
|
||||||
|
// @ts-ignore
|
||||||
|
if (!global.TextEncoder) {
|
||||||
|
// @ts-ignore
|
||||||
|
global.TextEncoder = TextEncoder as any;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!global.TextDecoder) {
|
||||||
|
// @ts-ignore
|
||||||
|
global.TextDecoder = TextDecoder as any;
|
||||||
|
}
|
||||||
|
|
||||||
// Mock pour les APIs Web
|
// Mock pour les APIs Web
|
||||||
|
// @ts-ignore
|
||||||
|
if (!global.crypto) {
|
||||||
|
// @ts-ignore
|
||||||
|
global.crypto = {};
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!global.crypto.subtle) {
|
||||||
|
// @ts-ignore
|
||||||
|
global.crypto.subtle = { digest: async () => new ArrayBuffer(32) } as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mocks réseau
|
||||||
|
// @ts-ignore
|
||||||
global.fetch = jest.fn();
|
global.fetch = jest.fn();
|
||||||
|
|
||||||
// Mock pour les WebSockets
|
// Mock pour les WebSockets
|
||||||
|
// @ts-ignore
|
||||||
global.WebSocket = jest.fn().mockImplementation(() => ({
|
global.WebSocket = jest.fn().mockImplementation(() => ({
|
||||||
send: jest.fn(),
|
send: jest.fn(),
|
||||||
close: jest.fn(),
|
close: jest.fn(),
|
||||||
@ -24,6 +51,7 @@ const localStorageMock = {
|
|||||||
removeItem: jest.fn(),
|
removeItem: jest.fn(),
|
||||||
clear: jest.fn(),
|
clear: jest.fn(),
|
||||||
};
|
};
|
||||||
|
// @ts-ignore
|
||||||
global.localStorage = localStorageMock;
|
global.localStorage = localStorageMock;
|
||||||
|
|
||||||
// Mock pour sessionStorage
|
// Mock pour sessionStorage
|
||||||
@ -33,6 +61,7 @@ const sessionStorageMock = {
|
|||||||
removeItem: jest.fn(),
|
removeItem: jest.fn(),
|
||||||
clear: jest.fn(),
|
clear: jest.fn(),
|
||||||
};
|
};
|
||||||
|
// @ts-ignore
|
||||||
global.sessionStorage = sessionStorageMock;
|
global.sessionStorage = sessionStorageMock;
|
||||||
|
|
||||||
// Configuration des timeouts
|
// Configuration des timeouts
|
||||||
|
24
tests/unit/html.utils.test.ts
Normal file
24
tests/unit/html.utils.test.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { getCorrectDOM } from '../../src/utils/html.utils';
|
||||||
|
|
||||||
|
describe('html.utils - getCorrectDOM', () => {
|
||||||
|
it('retourne le document quand le composant n’existe pas', () => {
|
||||||
|
const dom = getCorrectDOM('component-inexistant');
|
||||||
|
expect(dom).toBe(document);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retourne le shadowRoot si présent, sinon document', () => {
|
||||||
|
const host = document.createElement('div');
|
||||||
|
host.setAttribute('id', 'host');
|
||||||
|
const shadow = host.attachShadow({ mode: 'open' });
|
||||||
|
document.body.appendChild(host);
|
||||||
|
|
||||||
|
const querySpy = jest.spyOn(document, 'querySelector');
|
||||||
|
querySpy.mockReturnValue(host as any);
|
||||||
|
|
||||||
|
const dom = getCorrectDOM('#host');
|
||||||
|
expect(dom).toBe(shadow);
|
||||||
|
|
||||||
|
querySpy.mockRestore();
|
||||||
|
document.body.removeChild(host);
|
||||||
|
});
|
||||||
|
});
|
45
tests/unit/sp-address.utils.test.ts
Normal file
45
tests/unit/sp-address.utils.test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { generateEmojiList, addressToEmoji } from '../../src/utils/sp-address.utils';
|
||||||
|
|
||||||
|
describe('sp-address.utils', () => {
|
||||||
|
describe('generateEmojiList', () => {
|
||||||
|
it('retourne exactement 256 emojis', () => {
|
||||||
|
const list = generateEmojiList();
|
||||||
|
expect(Array.isArray(list)).toBe(true);
|
||||||
|
expect(list.length).toBe(256);
|
||||||
|
expect(list.every((e) => typeof e === 'string' && e.length > 0)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addressToEmoji', () => {
|
||||||
|
const mockDigest = jest.fn(async (algorithm: string, data: ArrayBuffer) => {
|
||||||
|
// Retourne un buffer de 32 octets avec un motif déterministe basé sur la longueur
|
||||||
|
const len = (data as Uint8Array).byteLength || new Uint8Array(data).byteLength;
|
||||||
|
const out = new Uint8Array(32).map((_, i) => (i * 7 + len) % 256);
|
||||||
|
return out.buffer;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (!global.crypto) {
|
||||||
|
// @ts-ignore
|
||||||
|
global.crypto = {};
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
global.crypto.subtle = { digest: mockDigest } as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('est déterministe pour une même adresse', async () => {
|
||||||
|
const addr = 'sp1qexampleaddress0001';
|
||||||
|
const a = await addressToEmoji(addr);
|
||||||
|
const b = await addressToEmoji(addr);
|
||||||
|
expect(a).toBe(b);
|
||||||
|
expect(a.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retourne une chaîne d’emojis de longueur > 0', async () => {
|
||||||
|
const a = await addressToEmoji('sp1qexampleA');
|
||||||
|
expect(typeof a).toBe('string');
|
||||||
|
expect(a.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
54
tests/unit/token.service.test.ts
Normal file
54
tests/unit/token.service.test.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Mock du module 'jose' (ESM) pour utilisation avec Jest CJS
|
||||||
|
jest.mock('jose', () => {
|
||||||
|
type Payload = { origin: string; type: 'access' | 'refresh' };
|
||||||
|
const makeToken = (p: Payload) => `${p.type}:${p.origin}`;
|
||||||
|
return {
|
||||||
|
SignJWT: class {
|
||||||
|
private payload: Payload;
|
||||||
|
constructor(payload: Payload) {
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
setProtectedHeader() { return this; }
|
||||||
|
setIssuedAt() { return this; }
|
||||||
|
setExpirationTime() { return this; }
|
||||||
|
async sign() { return makeToken(this.payload); }
|
||||||
|
},
|
||||||
|
jwtVerify: async (token: string) => {
|
||||||
|
const [type, origin] = (token || '').split(':');
|
||||||
|
return { payload: { type, origin } } as any;
|
||||||
|
}
|
||||||
|
} as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const TokenService = require('../../src/services/token').default as typeof import('../../src/services/token').default;
|
||||||
|
|
||||||
|
describe('TokenService', () => {
|
||||||
|
const ORIGIN = 'http://localhost';
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
process.env.VITE_JWT_SECRET_KEY = 'test-secret-key';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('génère un pair de tokens de type chaîne', async () => {
|
||||||
|
const service = await TokenService.getInstance();
|
||||||
|
const { accessToken, refreshToken } = await service.generateSessionToken(ORIGIN);
|
||||||
|
|
||||||
|
expect(typeof accessToken).toBe('string');
|
||||||
|
expect(typeof refreshToken).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rafraîchit un access token (retourne une chaîne ou null)', async () => {
|
||||||
|
const service = await TokenService.getInstance();
|
||||||
|
const { refreshToken } = await service.generateSessionToken(ORIGIN);
|
||||||
|
|
||||||
|
const newAccess = await service.refreshAccessToken(refreshToken, ORIGIN);
|
||||||
|
expect(typeof newAccess === 'string' || newAccess === null).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("retourne false si l'origine ne correspond pas", async () => {
|
||||||
|
const service = await TokenService.getInstance();
|
||||||
|
const { accessToken } = await service.generateSessionToken(ORIGIN);
|
||||||
|
const isValid = await service.validateToken(accessToken, 'https://example.com');
|
||||||
|
expect(isValid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
@ -3,7 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"module": "commonjs"
|
"module": "ES2022"
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user