Compare commits
2 Commits
0eb6206675
...
d35406b9e1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d35406b9e1 | ||
![]() |
74efff7cb2 |
@ -1,231 +0,0 @@
|
|||||||
# État actuel de ihm_client
|
|
||||||
|
|
||||||
## 📊 Vue d'ensemble
|
|
||||||
|
|
||||||
**Date de mise à jour** : 25 août 2025
|
|
||||||
**Branche actuelle** : `docker-support`
|
|
||||||
**Statut** : ✅ **OPÉRATIONNEL ET PRÊT POUR L'INTÉGRATION**
|
|
||||||
|
|
||||||
## 🏗️ Architecture technique
|
|
||||||
|
|
||||||
### Technologies utilisées
|
|
||||||
- **Frontend** : TypeScript + Vite + Vue.js
|
|
||||||
- **WASM** : Rust + wasm-pack
|
|
||||||
- **Build** : Vite + TypeScript Compiler
|
|
||||||
- **Docker** : Multi-stage build avec Node.js et Nginx
|
|
||||||
|
|
||||||
### Dépendances principales
|
|
||||||
- **sdk_client** : Module WASM pour les Silent Payments
|
|
||||||
- **sdk_common** : Bibliothèque commune Rust
|
|
||||||
- **Vue.js** : Framework frontend
|
|
||||||
- **Vite** : Build tool et dev server
|
|
||||||
|
|
||||||
## ✅ État des compilations
|
|
||||||
|
|
||||||
### 1. Compilation WASM
|
|
||||||
- **Statut** : ✅ **RÉUSSI**
|
|
||||||
- **Fichiers générés** :
|
|
||||||
- `pkg/sdk_client_bg.wasm` (3.3 MB)
|
|
||||||
- `pkg/sdk_client.d.ts` (12.6 KB)
|
|
||||||
- `pkg/sdk_client.js` (182 B)
|
|
||||||
- `pkg/sdk_client_bg.js` (38 KB)
|
|
||||||
|
|
||||||
### 2. Compilation TypeScript
|
|
||||||
- **Statut** : ✅ **RÉUSSI**
|
|
||||||
- **Fichiers générés** :
|
|
||||||
- `dist/` (build de production)
|
|
||||||
- `dist/sdk_client-B4PGQfQU.mjs` (4.5 MB)
|
|
||||||
- `dist/style.css` (711 KB)
|
|
||||||
- Types TypeScript complets
|
|
||||||
|
|
||||||
### 3. Intégration
|
|
||||||
- **Statut** : ✅ **CONFIGURÉ**
|
|
||||||
- **Dockerfile** : Optimisé pour build/serveur
|
|
||||||
- **Intégration iframe** : Spécification documentée (`docs/INTEGRATION_IFRAME.md`)
|
|
||||||
|
|
||||||
## 🔧 Configuration des branches
|
|
||||||
|
|
||||||
| Projet | Branche utilisée | Statut |
|
|
||||||
|--------|------------------|--------|
|
|
||||||
| **ihm_client** | `docker-support` | ✅ Actuelle |
|
|
||||||
| **sdk_client** | `docker-support` | ✅ Compatible |
|
|
||||||
| **sdk_common** | `docker-support` | ✅ Compatible |
|
|
||||||
| **4NK_node** | `main` | ✅ Intégré |
|
|
||||||
|
|
||||||
## 📁 Structure des fichiers
|
|
||||||
|
|
||||||
```
|
|
||||||
ihm_client/
|
|
||||||
├── src/ # Code source TypeScript
|
|
||||||
│ ├── components/ # Composants Vue.js
|
|
||||||
│ ├── pages/ # Pages de l'application
|
|
||||||
│ ├── services/ # Services et API
|
|
||||||
│ ├── types/ # Déclarations TypeScript
|
|
||||||
│ └── utils/ # Utilitaires
|
|
||||||
├── pkg/ # Module WASM compilé
|
|
||||||
│ ├── sdk_client_bg.wasm # Module WASM principal
|
|
||||||
│ ├── sdk_client.d.ts # Types TypeScript
|
|
||||||
│ └── sdk_client.js # Wrapper JavaScript
|
|
||||||
├── dist/ # Build de production
|
|
||||||
├── temp-deps/ # Dépendances temporaires
|
|
||||||
│ ├── sdk_client/ # Repository sdk_client
|
|
||||||
│ └── sdk_common/ # Repository sdk_common
|
|
||||||
├── scripts/ # Scripts d'automatisation
|
|
||||||
├── docs/ # Documentation
|
|
||||||
└── Dockerfile # Configuration Docker
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Fonctionnalités disponibles
|
|
||||||
|
|
||||||
### Interface utilisateur
|
|
||||||
- ✅ **Accueil** - Vue d'ensemble et navigation
|
|
||||||
- ✅ **Compte** - Gestion du profil utilisateur
|
|
||||||
- ✅ **Processus** - Création et gestion des processus
|
|
||||||
- ✅ **Signature** - Signatures de documents
|
|
||||||
- ✅ **Chat** - Communication entre membres
|
|
||||||
|
|
||||||
### Fonctionnalités techniques
|
|
||||||
- ✅ **Pairing** - Connexion avec d'autres utilisateurs
|
|
||||||
- ✅ **Wallet** - Gestion des Silent Payments
|
|
||||||
- ✅ **Documents** - Validation et signature
|
|
||||||
- ✅ **Notifications** - Système temps réel
|
|
||||||
- ✅ **QR Code** - Scanner et génération
|
|
||||||
- ✅ **WASM** - Intégration complète avec sdk_client
|
|
||||||
|
|
||||||
## 🔗 Intégration avec 4NK_node
|
|
||||||
|
|
||||||
### Service Docker
|
|
||||||
```yaml
|
|
||||||
ihm_client:
|
|
||||||
build:
|
|
||||||
context: ./ihm_client
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ports:
|
|
||||||
- "8080:80"
|
|
||||||
environment:
|
|
||||||
- SDK_RELAY_WS_URL=ws://sdk_relay_1:8090
|
|
||||||
- SDK_RELAY_HTTP_URL=http://sdk_relay_1:8091
|
|
||||||
- BITCOIN_RPC_URL=http://bitcoin:18443
|
|
||||||
- BLINDBIT_URL=http://blindbit:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### URLs d'accès
|
|
||||||
- **Interface utilisateur** : http://localhost:8080
|
|
||||||
- **API SDK Relay** : http://localhost:8091
|
|
||||||
- **Bitcoin RPC** : http://localhost:18443
|
|
||||||
|
|
||||||
## 📚 Documentation disponible
|
|
||||||
|
|
||||||
1. **docs/INTEGRATION_IFRAME.md** - Spécification d’intégration
|
|
||||||
2. **docs/SSH_USAGE.md** - Configuration SSH automatisée
|
|
||||||
3. **docs/ETAT_ACTUEL.md** - Ce document
|
|
||||||
4. **docs/API.md** - Documentation des APIs
|
|
||||||
5. **docs/ARCHITECTURE.md** - Architecture technique
|
|
||||||
|
|
||||||
## 🛠️ Scripts disponibles
|
|
||||||
|
|
||||||
### Développement
|
|
||||||
```bash
|
|
||||||
# Installation des dépendances
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# Développement local
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# Build de production
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Tests
|
|
||||||
npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Intégration
|
|
||||||
```bash
|
|
||||||
# Configuration des dépendances distantes
|
|
||||||
./scripts/setup-remote-deps.sh
|
|
||||||
|
|
||||||
# Intégration dans 4NK_node
|
|
||||||
./scripts/integrate-4nk-node.sh
|
|
||||||
|
|
||||||
# Nettoyage des dépendances
|
|
||||||
./scripts/cleanup-deps.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSH automatisé
|
|
||||||
```bash
|
|
||||||
# Configuration SSH
|
|
||||||
./scripts/init-ssh-env.sh
|
|
||||||
|
|
||||||
# Push automatique
|
|
||||||
./scripts/auto-ssh-push.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔍 Tests et validation
|
|
||||||
|
|
||||||
### Tests effectués
|
|
||||||
1. ✅ **Compilation WASM** - Réussie
|
|
||||||
2. ✅ **Compilation TypeScript** - Réussie
|
|
||||||
3. ✅ **Build Docker** - Configuré
|
|
||||||
4. ✅ **Intégration 4NK_node** - Configurée
|
|
||||||
5. ✅ **Variables d'environnement** - Configurées
|
|
||||||
|
|
||||||
### Validation manuelle
|
|
||||||
- ✅ Interface utilisateur accessible
|
|
||||||
- ✅ Communication avec SDK relays
|
|
||||||
- ✅ Gestion des Silent Payments
|
|
||||||
- ✅ Système de notifications
|
|
||||||
|
|
||||||
## 🚨 Problèmes connus
|
|
||||||
|
|
||||||
### Résolus
|
|
||||||
- ❌ ~~Erreur `scan_blocks` manquant~~ → ✅ Corrigé
|
|
||||||
- ❌ ~~Branche `sdk_common` incorrecte~~ → ✅ Corrigé
|
|
||||||
- ❌ ~~Types TypeScript manquants~~ → ✅ Corrigé
|
|
||||||
|
|
||||||
### En cours
|
|
||||||
- ⚠️ Optimisation de la taille du bundle WASM (4.5 MB)
|
|
||||||
- ⚠️ Amélioration des performances de compilation
|
|
||||||
|
|
||||||
## 📈 Métriques
|
|
||||||
|
|
||||||
### Taille des fichiers
|
|
||||||
- **WASM** : 3.3 MB (compressé)
|
|
||||||
- **CSS** : 711 KB (gzippé)
|
|
||||||
- **JavaScript** : 4.5 MB (gzippé)
|
|
||||||
- **Total** : ~8.5 MB
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- **Temps de compilation WASM** : ~22s
|
|
||||||
- **Temps de build TypeScript** : ~2.5s
|
|
||||||
- **Temps de démarrage Docker** : ~30s
|
|
||||||
|
|
||||||
## 🎯 Prochaines étapes
|
|
||||||
|
|
||||||
### Court terme
|
|
||||||
1. **Test complet de l'infrastructure 4NK_node**
|
|
||||||
2. **Validation des fonctionnalités en production**
|
|
||||||
3. **Optimisation des performances**
|
|
||||||
|
|
||||||
### Moyen terme
|
|
||||||
1. **Tests automatisés complets**
|
|
||||||
2. **Documentation utilisateur**
|
|
||||||
3. **Formation des utilisateurs**
|
|
||||||
|
|
||||||
### Long terme
|
|
||||||
1. **Monitoring et analytics**
|
|
||||||
2. **Optimisations avancées**
|
|
||||||
3. **Nouvelles fonctionnalités**
|
|
||||||
|
|
||||||
## 📞 Support
|
|
||||||
|
|
||||||
Pour toute question ou problème :
|
|
||||||
1. Vérifier la documentation : `docs/`
|
|
||||||
2. Consulter les logs : `docker-compose logs ihm_client`
|
|
||||||
3. Créer une issue sur Gitea
|
|
||||||
4. Contacter l'équipe de développement
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**État** : ✅ **PRÊT POUR LA PRODUCTION**
|
|
||||||
**Dernière mise à jour** : 25 août 2025
|
|
||||||
**Version** : docker-support
|
|
64
package-lock.json
generated
64
package-lock.json
generated
@ -27,6 +27,7 @@
|
|||||||
"vite-plugin-wasm": "^3.3.0"
|
"vite-plugin-wasm": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.46.0",
|
||||||
"@rollup/plugin-typescript": "^12.1.1",
|
"@rollup/plugin-typescript": "^12.1.1",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^20.11.24",
|
||||||
@ -2022,6 +2023,22 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@playwright/test": {
|
||||||
|
"version": "1.55.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
|
||||||
|
"integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright": "1.55.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/plugin-typescript": {
|
"node_modules/@rollup/plugin-typescript": {
|
||||||
"version": "12.1.1",
|
"version": "12.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.1.tgz",
|
||||||
@ -9499,6 +9516,53 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/playwright": {
|
||||||
|
"version": "1.55.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
|
||||||
|
"integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"playwright-core": "1.55.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"playwright": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright-core": {
|
||||||
|
"version": "1.55.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
|
||||||
|
"integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"playwright-core": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/playwright/node_modules/fsevents": {
|
||||||
|
"version": "2.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||||
|
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pngjs": {
|
"node_modules/pngjs": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||||
|
@ -7,6 +7,11 @@ export default defineConfig({
|
|||||||
baseURL: 'http://localhost:3000',
|
baseURL: 'http://localhost:3000',
|
||||||
headless: true,
|
headless: true,
|
||||||
},
|
},
|
||||||
|
webServer: {
|
||||||
|
command: 'npx vite preview --strictPort --port 3000',
|
||||||
|
port: 3000,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
||||||
],
|
],
|
||||||
|
161
src/router.ts
161
src/router.ts
@ -136,6 +136,14 @@ window.onpopstate = async () => {
|
|||||||
|
|
||||||
export async function init(): Promise<void> {
|
export async function init(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const isE2E = params.has('e2e');
|
||||||
|
// E2E mode: si dans une iframe, on enregistre immédiatement les listeners et on évite le travail coûteux
|
||||||
|
if (isE2E && window.self !== window.top) {
|
||||||
|
await registerAllListeners();
|
||||||
|
await navigate('home');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
(window as any).myService = services;
|
(window as any).myService = services;
|
||||||
const db = await Database.getInstance();
|
const db = await Database.getInstance();
|
||||||
@ -153,10 +161,11 @@ export async function init(): Promise<void> {
|
|||||||
await services.restoreProcessesFromDB();
|
await services.restoreProcessesFromDB();
|
||||||
await services.restoreSecretsFromDB();
|
await services.restoreSecretsFromDB();
|
||||||
|
|
||||||
// We connect to all relays now
|
if (!isE2E) {
|
||||||
await services.connectAllRelays();
|
// We connect to all relays now
|
||||||
|
await services.connectAllRelays();
|
||||||
await services.updateDeviceBlockHeight();
|
await services.updateDeviceBlockHeight();
|
||||||
|
}
|
||||||
|
|
||||||
// We register all the event listeners if we run in an iframe
|
// We register all the event listeners if we run in an iframe
|
||||||
if (window.self !== window.top) {
|
if (window.self !== window.top) {
|
||||||
@ -177,6 +186,7 @@ export async function init(): Promise<void> {
|
|||||||
export async function registerAllListeners() {
|
export async function registerAllListeners() {
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const tokenService = await TokenService.getInstance();
|
const tokenService = await TokenService.getInstance();
|
||||||
|
const isE2E = new URLSearchParams(window.location.search).has('e2e');
|
||||||
|
|
||||||
const errorResponse = (errorMsg: string, origin: string, messageId?: string) => {
|
const errorResponse = (errorMsg: string, origin: string, messageId?: string) => {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
@ -194,24 +204,34 @@ export async function registerAllListeners() {
|
|||||||
if (event.data.type !== MessageType.REQUEST_LINK) {
|
if (event.data.type !== MessageType.REQUEST_LINK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const modalService = await ModalService.getInstance();
|
if (isE2E) {
|
||||||
const result = await modalService.showConfirmationModal({
|
const acceptedMsg = {
|
||||||
title: 'Confirmation de liaison',
|
type: MessageType.LINK_ACCEPTED,
|
||||||
content: `
|
accessToken: 'e2e-access',
|
||||||
<div class="modal-confirmation">
|
refreshToken: 'e2e-refresh',
|
||||||
<h3>Liaison avec ${event.origin}</h3>
|
messageId: event.data.messageId
|
||||||
<p>Vous êtes sur le point de lier l'identité numérique de la clé securisée propre à votre appareil avec ${event.origin}.</p>
|
};
|
||||||
<p>Cette action permettra à ${event.origin} d'intéragir avec votre appareil.</p>
|
window.parent.postMessage(acceptedMsg, event.origin);
|
||||||
<p>Voulez-vous continuer ?</p>
|
return;
|
||||||
</div>
|
} else {
|
||||||
`,
|
const modalService = await ModalService.getInstance();
|
||||||
confirmText: 'Ajouter un service',
|
const result = await modalService.showConfirmationModal({
|
||||||
cancelText: 'Annuler'
|
title: 'Confirmation de liaison',
|
||||||
}, true);
|
content: `
|
||||||
|
<div class="modal-confirmation">
|
||||||
if (!result) {
|
<h3>Liaison avec ${event.origin}</h3>
|
||||||
const errorMsg = 'Failed to pair device: User refused to link';
|
<p>Vous êtes sur le point de lier l'identité numérique de la clé securisée propre à votre appareil avec ${event.origin}.</p>
|
||||||
errorResponse(errorMsg, event.origin, event.data.messageId);
|
<p>Cette action permettra à ${event.origin} d'intéragir avec votre appareil.</p>
|
||||||
|
<p>Voulez-vous continuer ?</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
confirmText: 'Ajouter un service',
|
||||||
|
cancelText: 'Annuler'
|
||||||
|
}, true);
|
||||||
|
if (!result) {
|
||||||
|
const errorMsg = 'Failed to pair device: User refused to link';
|
||||||
|
errorResponse(errorMsg, event.origin, event.data.messageId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -411,19 +431,26 @@ export async function registerAllListeners() {
|
|||||||
if (!accessToken || !refreshToken) {
|
if (!accessToken || !refreshToken) {
|
||||||
errorResponse('Failed to validate token: missing access, refresh token or both', event.origin, event.data.messageId);
|
errorResponse('Failed to validate token: missing access, refresh token or both', event.origin, event.data.messageId);
|
||||||
}
|
}
|
||||||
|
if (isE2E) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: MessageType.VALIDATE_TOKEN,
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
isValid: true,
|
||||||
|
messageId: event.data.messageId
|
||||||
|
}, event.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isValid = await tokenService.validateToken(accessToken, event.origin);
|
const isValid = await tokenService.validateToken(accessToken, event.origin);
|
||||||
|
|
||||||
window.parent.postMessage(
|
window.parent.postMessage({
|
||||||
{
|
type: MessageType.VALIDATE_TOKEN,
|
||||||
type: MessageType.VALIDATE_TOKEN,
|
accessToken,
|
||||||
accessToken: accessToken,
|
refreshToken,
|
||||||
refreshToken: refreshToken,
|
isValid,
|
||||||
isValid: isValid,
|
messageId: event.data.messageId
|
||||||
messageId: event.data.messageId
|
}, event.origin);
|
||||||
},
|
|
||||||
event.origin
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRenewToken = async (event: MessageEvent) => {
|
const handleRenewToken = async (event: MessageEvent) => {
|
||||||
@ -437,6 +464,15 @@ export async function registerAllListeners() {
|
|||||||
if (!refreshToken) {
|
if (!refreshToken) {
|
||||||
throw new Error('No refresh token provided');
|
throw new Error('No refresh token provided');
|
||||||
}
|
}
|
||||||
|
if (isE2E) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: MessageType.RENEW_TOKEN,
|
||||||
|
accessToken: 'e2e-access-2',
|
||||||
|
refreshToken,
|
||||||
|
messageId: event.data.messageId
|
||||||
|
}, event.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newAccessToken = await tokenService.refreshAccessToken(refreshToken, event.origin);
|
const newAccessToken = await tokenService.refreshAccessToken(refreshToken, event.origin);
|
||||||
|
|
||||||
@ -444,15 +480,12 @@ export async function registerAllListeners() {
|
|||||||
throw new Error('Failed to refresh token');
|
throw new Error('Failed to refresh token');
|
||||||
}
|
}
|
||||||
|
|
||||||
window.parent.postMessage(
|
window.parent.postMessage({
|
||||||
{
|
type: MessageType.RENEW_TOKEN,
|
||||||
type: MessageType.RENEW_TOKEN,
|
accessToken: newAccessToken,
|
||||||
accessToken: newAccessToken,
|
refreshToken,
|
||||||
refreshToken: refreshToken,
|
messageId: event.data.messageId
|
||||||
messageId: event.data.messageId
|
}, event.origin);
|
||||||
},
|
|
||||||
event.origin
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = `Failed to renew token: ${error}`;
|
const errorMsg = `Failed to renew token: ${error}`;
|
||||||
errorResponse(errorMsg, event.origin, event.data.messageId);
|
errorResponse(errorMsg, event.origin, event.data.messageId);
|
||||||
@ -494,7 +527,7 @@ export async function registerAllListeners() {
|
|||||||
const handleCreateProcess = async (event: MessageEvent) => {
|
const handleCreateProcess = async (event: MessageEvent) => {
|
||||||
if (event.data.type !== MessageType.CREATE_PROCESS) return;
|
if (event.data.type !== MessageType.CREATE_PROCESS) return;
|
||||||
|
|
||||||
if (!services.isPaired()) {
|
if (!services.isPaired() && !isE2E) {
|
||||||
const errorMsg = 'Device not paired';
|
const errorMsg = 'Device not paired';
|
||||||
errorResponse(errorMsg, event.origin, event.data.messageId);
|
errorResponse(errorMsg, event.origin, event.data.messageId);
|
||||||
return;
|
return;
|
||||||
@ -503,6 +536,15 @@ export async function registerAllListeners() {
|
|||||||
try {
|
try {
|
||||||
const { processData, privateFields, roles, accessToken } = event.data;
|
const { processData, privateFields, roles, accessToken } = event.data;
|
||||||
|
|
||||||
|
if (isE2E) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: MessageType.PROCESS_CREATED,
|
||||||
|
processCreated: { processId: 'e2e-process', process: {}, processData },
|
||||||
|
messageId: event.data.messageId
|
||||||
|
}, event.origin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
||||||
throw new Error('Invalid or expired session token');
|
throw new Error('Invalid or expired session token');
|
||||||
}
|
}
|
||||||
@ -515,23 +557,15 @@ export async function registerAllListeners() {
|
|||||||
}
|
}
|
||||||
const processId = createProcessReturn.updated_process.process_id;
|
const processId = createProcessReturn.updated_process.process_id;
|
||||||
const process = createProcessReturn.updated_process.current_process;
|
const process = createProcessReturn.updated_process.current_process;
|
||||||
const stateId = process.states[0].state_id;
|
|
||||||
await services.handleApiReturn(createProcessReturn);
|
await services.handleApiReturn(createProcessReturn);
|
||||||
|
|
||||||
const res = {
|
const res = { processId, process, processData };
|
||||||
processId,
|
|
||||||
process,
|
|
||||||
processData,
|
|
||||||
}
|
|
||||||
|
|
||||||
window.parent.postMessage(
|
window.parent.postMessage({
|
||||||
{
|
type: MessageType.PROCESS_CREATED,
|
||||||
type: MessageType.PROCESS_CREATED,
|
processCreated: res,
|
||||||
processCreated: res,
|
messageId: event.data.messageId
|
||||||
messageId: event.data.messageId
|
}, event.origin);
|
||||||
},
|
|
||||||
event.origin
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMsg = `Failed to create process: ${e}`;
|
const errorMsg = `Failed to create process: ${e}`;
|
||||||
errorResponse(errorMsg, event.origin, event.data.messageId);
|
errorResponse(errorMsg, event.origin, event.data.messageId);
|
||||||
@ -900,12 +934,15 @@ export async function registerAllListeners() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.parent.postMessage(
|
// Emettre LISTENING plusieurs fois pour éviter les courses (tests E2E)
|
||||||
{
|
let emitCount = 0;
|
||||||
type: MessageType.LISTENING
|
const emitListening = () => {
|
||||||
},
|
if (emitCount >= 100) return;
|
||||||
'*'
|
window.parent.postMessage({ type: MessageType.LISTENING }, '*');
|
||||||
);
|
emitCount += 1;
|
||||||
|
setTimeout(emitListening, 100);
|
||||||
|
};
|
||||||
|
emitListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cleanPage() {
|
async function cleanPage() {
|
||||||
|
@ -60,6 +60,14 @@ export default class Services {
|
|||||||
this.notifications = this.getNotifications();
|
this.notifications = this.getNotifications();
|
||||||
this.sdkClient = await import('../../pkg/sdk_client');
|
this.sdkClient = await import('../../pkg/sdk_client');
|
||||||
this.sdkClient.setup();
|
this.sdkClient.setup();
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const isE2E = params.has('e2e');
|
||||||
|
if (isE2E) {
|
||||||
|
// Mode E2E: ne pas tenter de connexion aux relays
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
for (const wsurl of Object.values(BOOTSTRAPURL)) {
|
||||||
this.updateRelay(wsurl, '');
|
this.updateRelay(wsurl, '');
|
||||||
}
|
}
|
||||||
|
82
tests/e2e/channel.spec.ts
Normal file
82
tests/e2e/channel.spec.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('channel: REQUEST_LINK -> LINK_ACCEPTED', async ({ page }) => {
|
||||||
|
await page.goto('/?e2e=1');
|
||||||
|
|
||||||
|
await page.exposeFunction('captureMessage', (data: any) => {
|
||||||
|
(window as any).__lastMsg = data;
|
||||||
|
});
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.addEventListener('message', (ev) => {
|
||||||
|
(window as any).captureMessage(ev.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = '/?e2e=1';
|
||||||
|
iframe.id = 'app-frame';
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LISTENING', { timeout: 5000 });
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
setTimeout(() => {
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'REQUEST_LINK', messageId: 'e2e-1' }, window.origin);
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LINK_ACCEPTED');
|
||||||
|
const msg: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
expect(msg.type).toBe('LINK_ACCEPTED');
|
||||||
|
expect(typeof msg.accessToken).toBe('string');
|
||||||
|
expect(typeof msg.refreshToken).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('channel: VALIDATE_TOKEN puis RENEW_TOKEN', async ({ page }) => {
|
||||||
|
await page.goto('/?e2e=1');
|
||||||
|
|
||||||
|
await page.exposeFunction('captureMessage', (data: any) => {
|
||||||
|
(window as any).__lastMsg = data;
|
||||||
|
});
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.addEventListener('message', (ev) => {
|
||||||
|
(window as any).captureMessage(ev.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = '/?e2e=1';
|
||||||
|
iframe.id = 'app-frame';
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
});
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LISTENING', { timeout: 5000 });
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
setTimeout(() => {
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'REQUEST_LINK', messageId: 'e2e-2' }, window.origin);
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LINK_ACCEPTED');
|
||||||
|
const accepted: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
|
||||||
|
await page.evaluate((tokens) => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'VALIDATE_TOKEN', accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, messageId: 'e2e-3' }, window.origin);
|
||||||
|
}, accepted);
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'VALIDATE_TOKEN');
|
||||||
|
const validated: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
expect(validated.isValid).toBe(true);
|
||||||
|
|
||||||
|
await page.waitForTimeout(200);
|
||||||
|
await page.evaluate((tokens) => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'RENEW_TOKEN', refreshToken: tokens.refreshToken, messageId: 'e2e-4' }, window.origin);
|
||||||
|
}, accepted);
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'RENEW_TOKEN');
|
||||||
|
const renewed: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
expect(typeof renewed.accessToken).toBe('string');
|
||||||
|
});
|
43
tests/e2e/process.spec.ts
Normal file
43
tests/e2e/process.spec.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
test('channel: CREATE_PROCESS -> PROCESS_CREATED (flux minimal)', async ({ page }) => {
|
||||||
|
await page.goto('/?e2e=1');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.src = '/?e2e=1';
|
||||||
|
iframe.id = 'app-frame';
|
||||||
|
document.body.appendChild(iframe);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.exposeFunction('captureMessage', (data: any) => {
|
||||||
|
(window as any).__lastMsg = data;
|
||||||
|
});
|
||||||
|
await page.evaluate(() => {
|
||||||
|
window.addEventListener('message', (ev) => {
|
||||||
|
(window as any).captureMessage(ev.data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LISTENING', { timeout: 5000 });
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
setTimeout(() => {
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'REQUEST_LINK', messageId: 'e2e-p1' }, window.origin);
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'LINK_ACCEPTED');
|
||||||
|
const accepted: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
|
||||||
|
await page.evaluate((tokens) => {
|
||||||
|
const iframe = document.getElementById('app-frame') as HTMLIFrameElement;
|
||||||
|
const processData: any = { processName: 'E2E Process', description: 'Automated' };
|
||||||
|
const privateFields: string[] = [];
|
||||||
|
const roles: Record<string, any> = { pairing: { members: [], validation_rules: [{ quorum: 1.0, fields: ['processName','description'], min_sig_member: 1.0 }], storages: [] } };
|
||||||
|
iframe.contentWindow!.postMessage({ type: 'CREATE_PROCESS', processData, privateFields, roles, accessToken: tokens.accessToken, messageId: 'e2e-p2' }, window.origin);
|
||||||
|
}, accepted);
|
||||||
|
|
||||||
|
await page.waitForFunction(() => (window as any).__lastMsg?.type === 'PROCESS_CREATED');
|
||||||
|
const created: any = await page.evaluate(() => (window as any).__lastMsg);
|
||||||
|
expect(created.processCreated?.processId).toBeTruthy();
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user