Compare commits

..

No commits in common. "ext" and "create-account" have entirely different histories.

124 changed files with 5023 additions and 29440 deletions

View File

@ -1 +0,0 @@
# CI Build Trigger

View File

@ -1,2 +0,0 @@
# CI Trigger
# CI Trigger Sun Sep 21 19:57:46 UTC 2025

View File

@ -1,95 +0,0 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/

View File

@ -1,165 +0,0 @@
# Règles globales Cursor pour les projets
## Principes généraux
- Lire impérativement le fichier `.cursorrules` au démarrage de chaque session.
- Lire tous les fichiers du dossier `docs/`, le code et les paramètres avant de commencer.
- Poser des questions et proposer des améliorations si nécessaire.
- Ajouter les leçons apprises à ce fichier `.cursorrules`.
- Écrire des documents complets et exhaustifs.
- Respecter strictement les règles de lint du Markdown.
- Préférer toujours un shell **bash** à PowerShell.
- Fermer et relancer le terminal avant chaque utilisation.
- Si le terminal est interrompu, analyser la commande précédente (interruption probablement volontaire).
- Exécuter automatiquement les étapes de résolution de problème.
- Expliquer les commandes complexes avant de les lancer.
- Compiler régulièrement et corriger toutes les erreurs avant de passer à létape suivante.
- Tester, documenter, compiler, aligner tag git, changelog et version avant déploiement et push.
- Utiliser `docx2txt` pour lire les fichiers `.docx`.
- Ajouter automatiquement les dépendances et rechercher systématiquement les dernières versions.
- Faire des commandes simples et claires en plusieurs étapes.
- Vérifie toujours tes hypothèses avant de commencer.
- N'oublie jamais qu'après la correction d'un problème, il faut corriger toutes les erreurs qui peuvent en découler.
## Organisation des fichiers et répertoires
- Scripts regroupés dans `scripts/`
- Configurations regroupées dans `confs/`
- Journaux regroupés dans `logs/`
- Répertoires obligatoires :
- `docs/` : documentation de toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
- `tests/` : tests liés à toute fonctionnalité ajoutée, modifiée, supprimée ou découverte.
- Remplacer les résumés (`RESUME`) par des mises à jour dans `docs/`.
## Configuration critique des services
- Mempool du réseau signet :
`https://mempool2.4nkweb.com/fr/docs/api/rest`
## Développement et sécurité
- Ne jamais committer de clés privées ou secrets.
- Utiliser des variables denvironnement pour les données sensibles.
- Définir correctement les dépendances Docker avec healthchecks.
- Utiliser les URLs de service Docker Compose (`http://service_name:port`).
- Documenter toutes les modifications importantes dans `docs/`.
- Externaliser au maximum les variables denvironnement.
- Toujours utiliser une clé SSH pour cloner les dépôts.
- Monter en version les dépôts au début du travail.
- Pousser les tags docker `ext` via la CI sur `git.4nkweb.com`.
- Corriger systématiquement les problèmes, même mineurs, sans contournement.
## Scripts (règles critiques)
- Vérifier lexistence dun script dans `scripts/` avant toute action.
- Utiliser les scripts existants plutôt que des commandes directes.
- Ne jamais créer plusieurs versions ou noms de scripts.
- Améliorer lexistant au lieu de créer des variantes (`startup-v2.sh`, etc.).
## Images Docker (règles critiques)
- Ajouter systématiquement `apt update && apt upgrade` dans les Dockerfiles.
- Installer en arrière-plan dans les images Docker :
`curl, git, sed, awk, nc, wget, jq, telnet, tee, wscat, ping, npm (dernière version)`
- Appliquer à tous les Dockerfiles et `docker-compose.yml`.
- N'utilise pas les version test ou dev ou ext-dev mais toujours les version ext, relance leur compilation si nécessaire
## Fichiers de configuration (règles critiques)
- Vérifier lécriture effective après chaque modification.
- Fichiers concernés : `nginx.conf`, `bitcoin.conf`, `package.json`, `Cargo.toml`.
- Utiliser `cat`, `jq` ou vérificateurs de syntaxe.
## Connexion au réseau Bitcoin signet
Commande unique et obligatoire :
```bash
docker exec bitcoin-signet bitcoin-cli -signet -rpccookiefile=/home/bitcoin/.bitcoin/signet/.cookie getblockchaininfo
````
## Connexion au relay/faucet bootstrap
* Test via WSS : `wss://dev3.4nkweb.com/ws/`
* Envoi Faucet, réponse attendue avec `NewTx` (tx hex et tweak\_data).
## Debug
* Automatiser dans le code toute solution validée.
* Pérenniser les retours dexpérience dans code et paramètres.
* Compléter les tests pour éviter les régressions.
## Nginx
* Tous les fichiers dans `conf/ngnix` doivent être mappés avec ceux du serveur.
## Minage (règles critiques)
* Toujours valider les adresses utilisées (adresses TSP non reconnues).
* Utiliser uniquement des adresses Bitcoin valides (bech32m).
* Vérifier que le minage génère des blocs avec transactions, pas uniquement coinbase.
* Surveiller les logs du minage pour détecter les erreurs dadresse.
* Vérifier la propagation via le mempool externe.
## Mempool externe
* Utiliser `https://mempool2.4nkweb.com` pour vérifier les transactions.
* Vérifier la synchronisation entre réseau local et externe.
## Données et modèles
* Utiliser les fichiers CSV comme base des modèles de données.
* Être attentif aux en-têtes multi-lignes.
* Confirmer la structure comprise et demander définition de toutes les colonnes.
* Corriger automatiquement incohérences de type.
## Implémentation et architecture
* Code splitting avec `React.lazy` et `Suspense`.
* Centraliser létat avec Redux ou Context API.
* Créer une couche dabstraction pour les services de données.
* Aller systématiquement au bout dune implémentation.
## Préparation open source
Chaque projet doit être prêt pour un dépôt sur `git.4nkweb.com` :
* Inclure : `LICENSE` (MIT, Apache 2.0 ou GPL), `CONTRIBUTING.md`, `CHANGELOG.md`, `CODE_OF_CONDUCT.md`.
* Aligner documentation et tests avec `4NK_node`.
## Versioning et documentation
* Mettre à jour documentation et tests systématiquement.
* Gérer versioning avec changelog.
* Demander validation avant tag.
* Documenter les hypothèses testées dans un REX technique.
* Tester avant tout commit.
* Tester les buildsavant tout tag.
## Bonnes pratiques de confidentialité et sécurité
### Docker
- Ne jamais stocker de secrets (clés, tokens, mots de passe) dans les Dockerfiles ou docker-compose.yml.
- Utiliser des fichiers `.env` sécurisés (non commités avec copie en .env.example) pour toutes les variables sensibles.
- Ne pas exécuter de conteneurs avec lutilisateur root, privilégier un utilisateur dédié.
- Limiter les capacités des conteneurs (option `--cap-drop`) pour réduire la surface dattaque.
- Scanner régulièrement les images Docker avec un outil de sécurité (ex : Trivy, Clair).
- Mettre à jour en continu les images de base afin déliminer les vulnérabilités.
- Ne jamais exposer de ports inutiles.
- Restreindre les volumes montés au strict nécessaire.
- Utiliser des réseaux Docker internes pour la communication inter-containers.
- Vérifier et tenir à jour les .dockerignore.
### Git
- Ne jamais committer de secrets, clés ou identifiants (même temporairement).
- Configurer des hooks Git (pre-commit) pour détecter automatiquement les secrets et les failles.
- Vérifier lhistorique (`git log`, `git filter-repo`) pour sassurer quaucune information sensible na été poussée.
- Signer les commits avec GPG pour garantir lauthenticité.
- Utiliser systématiquement SSH pour les connexions à distance.
- Restreindre les accès aux dépôts (principes du moindre privilège).
- Documenter les changements sensibles dans `CHANGELOG.md`.
- Ne jamais pousser directement sur `main` ou `master`, toujours passer par des branches de feature ou PR.
- Vérifier et tenir à jour les .gitignore.
- Vérifier et tenir à jour les .gitkeep.
- Vérifier et tenir à jour les .gitattributes.
### Cursor
- Toujours ouvrir une session en commençant par relire le fichier `.cursorrules`.
- Vérifier que Cursor ne propose pas de commit contenant des secrets ou fichiers sensibles.
- Ne pas exécuter dans Cursor de commandes non comprises ou copiées sans vérification.
- Préférer lutilisation de scripts audités dans `scripts/` plutôt que des commandes directes dans Cursor.
- Fermer et relancer Cursor régulièrement pour éviter des contextes persistants non désirés.
- Ne jamais partager le contenu du terminal ou des fichiers sensibles via Cursor en dehors du périmètre du projet.
- Vérifier et tenir à jour les .cursorrules.
- Vérifier et tenir à jour les .cursorignore.

View File

@ -1,95 +0,0 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/

3
.env Normal file
View File

@ -0,0 +1,3 @@
# .env
VITE_API_URL=https://api.example.com
VITE_API_KEY=your_api_key

View File

@ -1,42 +0,0 @@
name: Docker Build and Push (ext)
on:
push:
branches:
- ext
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Derive DOCKER_TAG from commit message or fallback
id: tag
run: |
msg=$(git log -1 --pretty=%B | tr -d '\r')
tag=$(printf '%s' "$msg" | sed -n 's/.*docker_tag=\([A-Za-z0-9._-]\+\).*/\1/p' | head -n1)
if [ -z "$tag" ]; then tag=ext; fi
tag=$(printf '%s' "$tag" | tr -d '[:space:]')
echo "DOCKER_TAG=$tag" >> $GITHUB_ENV
echo "tag=$tag" >> $GITHUB_OUTPUT
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: git.4nkweb.com
username: ${{ secrets.USER }}
password: ${{ secrets.TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64
push: true
tags: |
git.4nkweb.com/4nk/ihm_client:${{ env.DOCKER_TAG }}

View File

@ -1,69 +0,0 @@
name: Build and Push Docker image (ext)
on:
push:
tags:
- ext
env:
REGISTRY: git.4nkweb.com
IMAGE_NAMESPACE: 4nk
IMAGE_NAME: ihm_client
DOCKER_TAG: ext
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.USER }}
password: ${{ secrets.TOKEN }}
- name: Setup SSH agent for git clone
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: |
${{ secrets.SSH_PRIVATE_KEY }}
- name: Verify WebAssembly files
run: |
set -euo pipefail
# Verify that the pre-built WASM files are present
echo "Verifying WebAssembly files..."
ls -la pkg/
file pkg/sdk_client_bg.wasm
echo "WebAssembly files verified successfully"
- name: Extract docker tag from commit message (optional)
id: tag
run: |
set -euo pipefail
MSG=$(git log -1 --pretty=%B | tr -d '\r')
if echo "$MSG" | grep -q "ci: docker_tag="; then
T=$(echo "$MSG" | sed -nE 's/.*ci: docker_tag=([^ ]+).*/\1/p' | tr -d '\n')
if [ -n "$T" ]; then
echo "tag=$T" >> $GITHUB_OUTPUT
fi
fi
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag || env.DOCKER_TAG }}
ssh: default

102
.gitignore vendored
View File

@ -1,95 +1,7 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/node_modules/
**/dist/
**/build/
**/target/
**/.next/
**/.turbo/
**/coverage/
**/.pytest_cache/
**/.cache/
**/.pnpm-store/
**/.venv/
**/vendor/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/backups/
**/*backups*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*
# Dossiers de logs communs
log/
logs/
**/log/
**/logs/
target/
pkg/
Cargo.lock
node_modules/
dist/
.vscode
public/ssl/

View File

@ -1,17 +0,0 @@
# Changelog
## [Unreleased]
### Documentation WebSocket et architecture
- **Documentation WebSocket** : Ajout de `docs/WEBSOCKET_CONNECTION.md` avec analyse complète de l'architecture
- **Architecture de l'iframe** : Documentation de la logique de fonctionnement (init, services, WebSocket)
- **Configuration WebSocket** : Documentation des variables d'environnement et connexions
- **Gestion des messages** : Documentation du handshake et mise à jour des adresses relay
- **Communication parent** : Documentation de l'écoute des messages et gestion des erreurs
- **Problème persistant** : 502 Bad Gateway - Nginx ne transmet pas les headers WebSocket
## [1.0.0]
### Version initiale
- Interface utilisateur pour LeCoffre
- Intégration WebSocket avec les relays
- Gestion des processus et authentification
- Communication avec le parent via postMessage

View File

@ -1,44 +1,13 @@
# syntax=docker/dockerfile:1.4
FROM debian:bookworm-slim
WORKDIR /app
# Installation des dépendances minimales nécessaires
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y --fix-missing \
ca-certificates curl jq git \
net-tools iputils-ping dnsutils \
netcat-openbsd telnet procps && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Installation de Node.js
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Création d'un utilisateur non-root
RUN useradd -m -u 1000 appuser && \
mkdir -p /app && chown -R appuser:appuser /app
# Copy project files
COPY . .
RUN chown -R appuser:appuser /app
# Ensure pkg directory exists and has correct permissions
RUN mkdir -p pkg && chmod -R 755 pkg
# Verify pkg files are present
RUN ls -la pkg/
# Copy the provided prebuilt WASM package (ESM)
# The directory pkg is provided in the build context
# and already contains sdk_client.js (ES module) and wasm
# so no compilation is required here.
# Installation des dépendances Node.js
USER appuser
RUN npm install
EXPOSE 3003
CMD ["npm", "start"]
FROM node:20
ENV TZ=Europe/Paris
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# use this user because he have uid et gid 1000 like theradia
USER node
WORKDIR /app
CMD ["npm", "start"]
# "--disable-host-check", "--host", "0.0.0.0", "--ssl", "--ssl-cert", "/ssl/certs/site.crt", "--ssl-key", "/ssl/private/site.dec.key"]

103
README.md
View File

@ -4,70 +4,49 @@
## HOW TO START
1 - clone sdk_common, commit name "doc pcd" from 28.10.2024
2 - clone sdk_client, commit name "Ignore messages" from 17.10.2024
3 - clone ihm_client_test3
4 - cargo build in sdk_common
5 - cargo run in sdk_client
6 - npm run build_wasm in ihm_client_test3
7 - npm run start in ihm_client_test3
1 - clone sdk_common, commit name "doc pcd" from 28.10.2024
2 - clone sdk_client, commit name "Ignore messages" from 17.10.2024
3 - clone ihm_client_test3
4 - cargo build in sdk_common
5 - cargo run in sdk_client
6 - npm run build_wasm in ihm_client_test3
7 - npm run start in ihm_client_test3
## USER STORIES
1 - I can login with my adress device
2 - I can login with QR code
3 - J'accède à la page Process après ma connexion
4 - Dans l'interface Process, je peux sélectionner un processus avec sa zone
5 - Je reçois des notifications dans la page Process
6 - Dans le menu, je peux importer mes données au format JSON
7 - Dans le menu, je peux accèder à la page Account
8 - Dans la page Account, je peux cliquer sur mon profil via la bulle de profil en haut à gauche et une popup de profil s'ouvre
9 - Dans la popup de profil, je peux voir mes informations personnelles et modifier certaines d'entre elles dont le nom, le prénom
10 - Dans la popup de profil, je peux changer ma photo de profil
11 - Dans la popup de profil, je peux fermer la popup en cliquant sur le bouton "X" en haut à droite de la popup
12 - Dans la popup de profil, je peux cliquer sur le bouton "Export User Data", ce qui me génère un fichier JSON
13 - Dans la popup de profil, je peux cliquer sur le bouton "Delete Account", ce qui me demande à valider mon choix
14 - Dans la popup de profil, je peux cliquer sur le bouton "Logout", ce qui me déconnecte
15 - Dans la popup de profil, je peux cliquer sur le bouton "Export Recovery", ce qui me demandera de confirmer mon choix ou d'annuler, si je confirme, je dois retenir et écrire les 4 mots de récupération, le bouton ne sera plus accessible après cela
16 - Dans l'onglet Pairing de la page Account, je peux ajouter un nouveau "device" en cliquant sur le bouton "Add Device"
17 - Dans l'onglet Pairing de la page Account, je peux supprimer un "device" en cliquant sur l'emoji de la poubelle à côté du device que je souhaite supprimer
18 - Dans l'onglet Pairing de la page Account, je peux cliquer sur le bouton "Scan QR Code" pour scanner le QR Code d'un nouveau device
19 - Dans l'onglet Pairing de la page Account, je peux renommer un "Device" en cliquant sur son nom et en modifiant le nom
20 - Dans l'onglet Wallet de la page Account, je peux ajouter un nouveau "wallet" en cliquant sur le bouton "Add a line"
1 - I can login with my adress device
2 - I can login with QR code
3 - J'accède à la page Process après ma connexion
4 - Dans l'interface Process, je peux sélectionner un processus avec sa zone
5 - Je reçois des notifications dans la page Process
6 - Dans le menu, je peux importer mes données au format JSON
7 - Dans le menu, je peux accèder à la page Account
8 - Dans la page Account, je peux cliquer sur mon profil via la bulle de profil en haut à gauche et une popup de profil s'ouvre
9 - Dans la popup de profil, je peux voir mes informations personnelles et modifier certaines d'entre elles dont le nom, le prénom
10 - Dans la popup de profil, je peux changer ma photo de profil
11 - Dans la popup de profil, je peux fermer la popup en cliquant sur le bouton "X" en haut à droite de la popup
12 - Dans la popup de profil, je peux cliquer sur le bouton "Export User Data", ce qui me génère un fichier JSON
13 - Dans la popup de profil, je peux cliquer sur le bouton "Delete Account", ce qui me demande à valider mon choix
14 - Dans la popup de profil, je peux cliquer sur le bouton "Logout", ce qui me déconnecte
15 - Dans la popup de profil, je peux cliquer sur le bouton "Export Recovery", ce qui me demandera de confirmer mon choix ou d'annuler, si je confirme, je dois retenir et écrire les 4 mots de récupération, le bouton ne sera plus accessible après cela
16 - Dans l'onglet Pairing de la page Account, je peux ajouter un nouveau "device" en cliquant sur le bouton "Add Device"
17 - Dans l'onglet Pairing de la page Account, je peux supprimer un "device" en cliquant sur l'emoji de la poubelle à côté du device que je souhaite supprimer
18 - Dans l'onglet Pairing de la page Account, je peux cliquer sur le bouton "Scan QR Code" pour scanner le QR Code d'un nouveau device
19 - Dans l'onglet Pairing de la page Account, je peux renommer un "Device" en cliquant sur son nom et en modifiant le nom
20 - Dans l'onglet Wallet de la page Account, je peux ajouter un nouveau "wallet" en cliquant sur le bouton "Add a line"
21 - Dans l'onglet Process de la page Account, je peux voir les Process disponibles et voir leur notifications en cliquant sur sur la sonnette à côté du processus
22 - Dans l'onglet Data de la page Account, je peux voir les données importées
23 - Je peux voir le contrat associé à une Data en cliquant sur le contrat dans la ligne de la Data
24 - Dans le menu je peux accèder à la page Chat
25 - Dans la page Chat, je peux voir les Processus
26 - Dans les Processus, je peux voir utilisateurs assignés à un rôle
27 - Dans les Processus, je peux envoyer des messages et des documents en cliquant sur le nom d'un utilisateur en en envoyant "send"
28 - Dans le menu je peux accèder à la page "Signatures"
29 - Je peux voir les documents à signer et vierge en cliquand sur l'emoji ⚙️ à côté du processus
30 - En cliquand sur l'onglet d'un processus, je peux voir les rôles assignés à un utilisateur en cliquant dessus
31 - En cliquand sur l'emoji 📁 à côté d'un rôle, je peux voir les documents associés à ce rôle
32 - Dans la vue des documents associés à un rôle, je peux créer un évènement de nouvelle signature pour tous les rôles associés à ce Processus, avec le bouton "New Request"
33 - En cliquant sur le bouton "New Request", une nouvelle fenêtre s'ouvre pour me permettre de rentrer la description, la visibilité, la date d'échéance, importer des documents, voir les signataires et "Request"
34 - Dans le menu, je peux me déconnecter avec le bouton "Disconnect"
22 - Dans l'onglet Data de la page Account, je peux voir les données importées
23 - Je peux voir le contrat associé à une Data en cliquant sur le contrat dans la ligne de la Data
24 - Dans le menu je peux accèder à la page Chat
25 - Dans la page Chat, je peux voir les Processus
26 - Dans les Processus, je peux voir utilisateurs assignés à un rôle
27 - Dans les Processus, je peux envoyer des messages et des documents en cliquant sur le nom d'un utilisateur en en envoyant "send"
28 - Dans le menu je peux accèder à la page "Signatures"
29 - Je peux voir les documents à signer et vierge en cliquand sur l'emoji ⚙️ à côté du processus
30 - En cliquand sur l'onglet d'un processus, je peux voir les rôles assignés à un utilisateur en cliquant dessus
31 - En cliquand sur l'emoji 📁 à côté d'un rôle, je peux voir les documents associés à ce rôle
32 - Dans la vue des documents associés à un rôle, je peux créer un évènement de nouvelle signature pour tous les rôles associés à ce Processus, avec le bouton "New Request"
33 - En cliquant sur le bouton "New Request", une nouvelle fenêtre s'ouvre pour me permettre de rentrer la description, la visibilité, la date d'échéance, importer des documents, voir les signataires et "Request"
34 - Dans le menu, je peux me déconnecter avec le bouton "Disconnect"
## TO DO
### Documentation centralisée
- Voir `/home/debian/4NK_env/docs/ihm_client/`
## Intégration dans lenvironnement 4NK
`ihm_client` sintègre dans larchitecture 4NK via une iframe et communique avec les services par variables denvironnement (préfixes `VITE_`/`NEXT_PUBLIC_`). Aucune URL de machine ne doit être codée en dur dans la documentation ou les exemples.
Références centrales:
- `docs/DEEP_ARCHITECTURE_ANALYSIS.md`
- `docs/TECHNICAL_REFERENCE.md`
- `docs/DEPLOYMENT_GUIDE.md`
## 📋 Fichiers centralisés
Les fichiers suivants sont centralisés dans le dépôt principal `4NK_env` :
- `CODE_OF_CONDUCT.md` - Code de conduite
- `CODEOWNERS` - Propriétaires du code
- `CONTRIBUTING.md` - Guide de contribution
- `LICENSE` - Licence du projet
Voir : [`4NK_env/CODE_OF_CONDUCT.md`](../../CODE_OF_CONDUCT.md), [`4NK_env/CODEOWNERS`](../../CODEOWNERS), [`4NK_env/CONTRIBUTING.md`](../../CONTRIBUTING.md), [`4NK_env/LICENSE`](../../LICENSE)
## TO DO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
dist/assets/bgd.webp vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 KiB

BIN
dist/assets/camera.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

34
dist/assets/home.js vendored
View File

@ -1,34 +0,0 @@
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.getElementById(tab.getAttribute('data-tab')).classList.add('active');
});
});
function toggleMenu() {
var menu = document.getElementById('menu');
if (menu.style.display === 'block') {
menu.style.display = 'none';
} else {
menu.style.display = 'block';
}
}
//// Modal
function openModal() {
document.getElementById('modal').style.display = 'flex';
}
function closeModal() {
document.getElementById('modal').style.display = 'none';
}
// Close modal when clicking outside of it
window.onclick = function(event) {
const modal = document.getElementById('modal');
if (event.target === modal) {
closeModal();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,111 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Redirection en cours…</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 2rem; color: #0a0a0a; }
.box { max-width: 720px; margin: 10vh auto; padding: 1.5rem; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
.muted { color: #6b7280; font-size: .95rem; }
.error { color: #b91c1c; }
.ok { color: #065f46; }
code { background: #f3f4f6; padding: .2rem .35rem; border-radius: 6px; }
a { color: #006BE0; text-decoration: none; }
</style>
</head>
<body>
<div class="box">
<h1>Connexion IdNot</h1>
<p class="muted" id="status">Traitement du code d'autorisation…</p>
<pre class="muted" id="details" style="white-space: pre-wrap"></pre>
</div>
<script>
(function () {
function getQueryParam(name) {
const params = new URLSearchParams(location.search);
return params.get(name);
}
function setCookie(name, value, days) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
// Domaine implicite: dev4.4nkweb.com (hébergement de cette page)
document.cookie = name + '=' + encodeURIComponent(value) + '; Path=/; Expires=' + expires + '; SameSite=None; Secure';
}
async function main() {
const code = getQueryParam('code');
const statusEl = document.getElementById('status');
const detailsEl = document.getElementById('details');
if (!code) {
statusEl.textContent = 'Aucun code reçu dans la redirection IdNot.';
statusEl.className = 'error';
detailsEl.textContent = 'Paramètre attendu: ?code=…\nRetour à l\'espace: https://dev4.4nkweb.com/lecoffre/';
return;
}
try {
const resp = await fetch('https://dev4.4nkweb.com/api/v1/idnot/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-ID': 'bridge_' + Math.random().toString(36).slice(2)
},
body: JSON.stringify({ code })
});
const text = await resp.text();
let data;
try { data = JSON.parse(text); } catch (_) { data = null; }
if (!resp.ok) {
statusEl.textContent = 'Connexion refusée (' + resp.status + ').';
statusEl.className = 'error';
detailsEl.textContent = (data && data.error && data.error.message) ? data.error.message : text;
// Redirige néanmoins vers le front avec état d\'erreur afin d\'afficher un message utilisateur.
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=' + encodeURIComponent(String(resp.status))); }, 600);
return;
}
// Attendu: { idNotUser, authToken }
if (!data || !data.authToken) {
statusEl.textContent = 'Réponse invalide du serveur.';
statusEl.className = 'error';
detailsEl.textContent = text;
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=invalid_response'); }, 800);
return;
}
// Stocker le jeton pour le domaine dev4 (utilisé par le front)
setCookie('leCoffreAccessToken', data.authToken, 1);
statusEl.textContent = 'Connexion réussie, redirection…';
statusEl.className = 'ok';
detailsEl.textContent = '';
// Redirection vers le front avec token en hash en sauvegarde
location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#token=' + encodeURIComponent(data.authToken));
} catch (e) {
statusEl.textContent = 'Erreur réseau lors de la connexion.';
statusEl.className = 'error';
detailsEl.textContent = String(e && e.message || e);
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=network'); }, 1000);
}
}
main();
})();
</script>
</body>
</html>

9139
dist/index.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

877
dist/style/4nk.css vendored
View File

@ -1,877 +0,0 @@
:root {
--primary-color
: #3A506B;
/* Bleu métallique */
--secondary-color
: #B0BEC5;
/* Gris acier */
--accent-color
: #D68C45;
/* Cuivre */
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
background-image: url(../assets/bgd.webp);
background-repeat:no-repeat;
background-size: cover;
background-blend-mode :soft-light;
height: 100vh;
}
.message {
margin: 30px 0;
font-size: 14px;
overflow-wrap: anywhere;
}
.message strong{
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
font-size: 20px;
}
/** Modal Css */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 3;
}
.modal-content {
width: 55%;
height: 30%;
background-color: white;
border-radius: 4px;
padding: 20px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.modal-title {
margin: 0;
padding-bottom: 8px;
width: 100%;
font-size: 0.9em;
border-bottom: 1px solid #ccc;
}
.confirmation-box {
/* margin-top: 20px; */
align-content: center;
width: 70%;
height: 20%;
/* padding: 20px; */
font-size: 1.5em;
color: #333333;
top: 5%;
position: relative;
}
/* Confirmation Modal Styles */
#confirmation-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.modal-confirmation {
text-align: left;
padding: 10px;
}
.modal-confirmation h3 {
margin-bottom: 15px;
color: var(--primary-color);
font-size: 1.1em;
}
.modal-confirmation p {
margin: 8px 0;
font-size: 0.9em;
line-height: 1.4;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.modal-footer button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 0.9em;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-secondary {
background: var(--secondary-color);
color: white;
}
/* Responsive adjustments */
@media only screen and (max-width: 600px) {
.modal-content {
width: 95%;
margin: 10px;
padding: 15px;
}
.modal-confirmation h3 {
font-size: 1em;
}
.modal-confirmation p {
font-size: 0.85em;
}
}
.nav-wrapper {
position: fixed;
z-index: 2;
background: radial-gradient(circle, white, var(--primary-color));
/* background-color: #CFD8DC; */
display: flex;
justify-content: flex-end;
align-items: center;
color: #37474F;
height: 9vh;
width: 100vw;
left: 0;
top: 0;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);
.nav-right-icons {
display: flex;
.notification-container {
position: relative;
display: inline-block;
}
.notification-bell, .burger-menu {
z-index: 3;
height: 20px;
width: 20px;
margin-right: 1rem;
}
.notification-badge {
position: absolute;
top: -.7rem;
left: -.8rem;
background-color: red;
color: white;
border-radius: 50%;
padding: 2.5px 6px;
font-size: 0.8em;
font-weight: bold;
}
}
.notification-board {
position: absolute;
width: 20rem;
min-height: 8rem;
background-color: white;
right: 0.5rem;
display: none;
border-radius: 4px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: none;
.notification-element {
padding: .8rem 0;
width: 100%;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
}
.notification-element:not(:last-child) {
border-bottom: 1px solid;
}
}
}
.brand-logo {
height: 100%;
width: 100vw;
align-content: center;
position: relative;
display: flex;
position: absolute;
align-items: center;
justify-content: center;
text-align: center;
font-size: 1.5em;
font-weight: bold;
}
.container {
text-align: center;
display: grid;
height: 100vh;
grid-template-columns: repeat(7, 1fr);
gap: 10px;
grid-auto-rows: 10vh 15vh 1fr;
}
.title-container {
grid-column: 2 / 7;
grid-row: 2;
}
.page-container {
grid-column: 2 / 7;
grid-row: 3 ;
justify-content: center;
display: flex;
padding: 1rem;
box-sizing: border-box;
max-height: 60vh;
}
h1 {
font-size: 2em;
margin: 20px 0;
}
@media only screen and (min-width: 600px) {
.tab-container {
display: none;
}
.page-container {
display: flex;
align-items: center;
}
.process-container {
grid-column: 3 / 6;
grid-row: 3 ;
.card {
min-width: 40vw;
}
}
.separator {
width: 2px;
background-color: #78909C;
height: 80%;
margin: 0 0.5em;
}
.tab-content {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
height: 80%;
}
}
@media only screen and (max-width: 600px) {
.process-container {
grid-column: 2 / 7;
grid-row: 3 ;
}
.container {
grid-auto-rows: 10vh 15vh 15vh 1fr;
}
.tab-container {
grid-column: 1 / 8;
grid-row: 3;
}
.page-container {
grid-column: 2 / 7;
grid-row: 4 ;
}
.separator {
display: none;
}
.tabs {
display: flex;
flex-grow: 1;
overflow: hidden;
z-index: 1;
border-bottom-style: solid;
border-bottom-width: 1px;
border-bottom-color: #E0E4D6;
}
.tab {
flex: 1;
text-align: center;
padding: 10px 0;
cursor: pointer;
font-size: 1em;
color: #6200ea;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
}
.tab.active {
border-bottom: 2px solid #6200ea;
font-weight: bold;
}
.card.tab-content {
display: none;
}
.tab-content.active {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 80%;
}
.modal-content {
width: 80%;
height: 20%;
}
}
.qr-code {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.emoji-display {
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
font-size: 20px;
}
#emoji-display-2{
margin-top: 30px;
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
font-size: 20px;
}
#okButton{
margin-bottom: 2em;
cursor: pointer;
background-color: #D0D0D7;
color: white;
border-style: none;
border-radius: 5px;
color: #000;
padding: 2px;
margin-top: 10px;
}
.pairing-request {
font-family: "Segoe UI Emoji", "Noto Color Emoji", "Apple Color Emoji", sans-serif;
font-size: 14px;
margin-top: 0px;
}
.sp-address-btn {
margin-bottom: 2em;
cursor: pointer;
background-color: #D0D0D7;
color: white;
border-style: none;
border-radius: 5px;
color: #000;
padding: 2px;
}
.camera-card {
display: flex;
justify-content: center;
align-items: center;
/* height: 200px; */
}
.btn {
display: inline-block;
padding: 10px 20px;
background-color: var(--primary-color);
color: white;
text-align: center;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
.btn:hover {
background-color: #3700b3;
}
.card {
min-width: 300px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
height: 60vh;
justify-content: flex-start;
padding: 1rem;
overflow-y: auto;
}
.card-content {
flex-grow: 1;
flex-direction: column;
display: flex;
justify-content: flex-start;
align-items: center;
text-align: left;
font-size: .8em;
position: relative;
left: 2vw;
width: 90%;
.process-title {
font-weight: bold;
padding: 1rem 0;
}
.process-element {
padding: .4rem 0;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
&.selected {
background-color: rgba(26, 28, 24, .08);
}
}
}
.card-description {
padding: 20px;
font-size: 1em;
color: #333;
width: 90%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 0px;
}
.card-action {
width: 100%;
}
.menu-content {
display: none;
position: absolute;
top: 3.4rem;
right: 1rem;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
}
.menu-content a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: #333;
border-bottom: 1px solid #e0e0e0;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
}
.menu-content a:last-child {
border-bottom: none;
}
.qr-code-scanner {
display: none;
}
/* QR READER */
#qr-reader div {
position: inherit;
}
#qr-reader div img{
top: 15px ;
right: 25px;
margin-top: 5px;
}
/* INPUT CSS **/
.input-container {
position: relative;
width: 100%;
background-color: #ECEFF1;
}
.input-field {
width: 36vw;
padding: 10px 0;
font-size: 1em;
border: none;
border-bottom: 1px solid #ccc;
outline: none;
background: transparent;
transition: border-color 0.3s;
}
.input-field:focus {
border-bottom: 2px solid #6200ea;
}
.input-label {
position: absolute;
margin-top: -0.5em;
top: 0;
left: 0;
padding: 10px 0;
font-size: 1em;
color: #999;
pointer-events: none;
transition: transform 0.3s, color 0.3s, font-size 0.3s;
}
.input-field:focus + .input-label,
.input-field:not(:placeholder-shown) + .input-label {
transform: translateY(-20px);
font-size: 0.8em;
color: #6200ea;
}
.input-underline {
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 2px;
background-color: #6200ea;
transition: width 0.3s, left 0.3s;
}
.input-field:focus ~ .input-underline {
width: 100%;
left: 0;
}
.dropdown-content {
position: absolute;
flex-direction: column;
top: 100%;
left: 0;
width: 100%;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ccc;
border-radius: 4px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: none;
z-index: 1;
}
.dropdown-content span {
padding: 10px;
cursor: pointer;
list-style: none;
}
.dropdown-content span:hover {
background-color: #f0f0f0;
}
/** AUTOCOMPLETE **/
select[data-multi-select-plugin] {
display: none !important;
}
.multi-select-component {
width: 36vw;
padding: 5px 0;
font-size: 1em;
border: none;
border-bottom: 1px solid #ccc;
outline: none;
background: transparent;
display: flex;
flex-direction: row;
height: auto;
width: 100%;
-o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
.autocomplete-list {
border-radius: 4px 0px 0px 4px;
}
.multi-select-component:focus-within {
box-shadow: inset 0px 0px 0px 2px #78ABFE;
}
.multi-select-component .btn-group {
display: none !important;
}
.multiselect-native-select .multiselect-container {
width: 100%;
}
.selected-processes {
background-color: white;
padding: 0.4em;
}
.selected-wrapper {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
display: inline-block;
border: 1px solid #d9d9d9;
background-color: #ededed;
white-space: nowrap;
margin: 1px 5px 5px 0;
height: 22px;
vertical-align: top;
cursor: default;
}
.selected-wrapper .selected-label {
max-width: 514px;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 4px;
vertical-align: top;
}
.selected-wrapper .selected-close {
display: inline-block;
text-decoration: none;
font-size: 14px;
line-height: 1.49em;
margin-left: 5px;
padding-bottom: 10px;
height: 100%;
vertical-align: top;
padding-right: 4px;
opacity: 0.2;
color: #000;
text-shadow: 0 1px 0 #fff;
font-weight: 700;
}
.search-container {
display: flex;
flex-direction: row;
}
.search-container .selected-input {
background: none;
border: 0;
height: 20px;
width: 60px;
padding: 0;
margin-bottom: 6px;
-webkit-box-shadow: none;
box-shadow: none;
}
.search-container .selected-input:focus {
outline: none;
}
.dropdown-icon.active {
transform: rotateX(180deg)
}
.search-container .dropdown-icon {
display: inline-block;
padding: 10px 5px;
position: absolute;
top: 5px;
right: 5px;
width: 10px;
height: 10px;
border: 0 !important;
/* needed */
-webkit-appearance: none;
-moz-appearance: none;
/* SVG background image */
background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2012%2012%22%3E%3Ctitle%3Edown-arrow%3C%2Ftitle%3E%3Cg%20fill%3D%22%23818181%22%3E%3Cpath%20d%3D%22M10.293%2C3.293%2C6%2C7.586%2C1.707%2C3.293A1%2C1%2C0%2C0%2C0%2C.293%2C4.707l5%2C5a1%2C1%2C0%2C0%2C0%2C1.414%2C0l5-5a1%2C1%2C0%2C1%2C0-1.414-1.414Z%22%20fill%3D%22%23818181%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3C%2Fsvg%3E");
background-position: center;
background-size: 10px;
background-repeat: no-repeat;
}
.search-container ul {
position: absolute;
list-style: none;
padding: 0;
z-index: 3;
margin-top: 29px;
width: 100%;
right: 0px;
background: #fff;
border: 1px solid #ccc;
border-top: none;
border-bottom: none;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}
.search-container ul :focus {
outline: none;
}
.search-container ul li {
display: block;
text-align: left;
padding: 8px 29px 2px 12px;
border-bottom: 1px solid #ccc;
font-size: 14px;
min-height: 31px;
}
.search-container ul li:first-child {
border-top: 1px solid #ccc;
border-radius: 4px 0px 0 0;
}
.search-container ul li:last-child {
border-radius: 4px 0px 0 0;
}
.search-container ul li:hover.not-cursor {
cursor: default;
}
.search-container ul li:hover {
color: #333;
background-color: #f0f0f0;
;
border-color: #adadad;
cursor: pointer;
}
/* Adding scrool to select options */
.autocomplete-list {
max-height: 130px;
overflow-y: auto;
}
/**************************************** Process page card ******************************************************/
.process-card {
min-width: 300px;
border: 1px solid #e0e0e0;
border-radius: 8px;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-height: 40vh;
max-height: 60vh;
justify-content: space-between;
padding: 1rem;
overflow-y: auto;
}
.process-card-content {
text-align: left;
font-size: .8em;
position: relative;
left: 2vw;
width: 90%;
.process-title {
font-weight: bold;
padding: 1rem 0;
}
.process-element {
padding: .4rem 0;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
&.selected {
background-color: rgba(26, 28, 24, .08);
}
}
.selected-process-zone {
background-color: rgba(26, 28, 24, .08);
}
}
.process-card-description {
padding: 20px;
font-size: 1em;
color: #333;
width: 90%;
}
.process-card-action {
width: 100%;
}

1507
dist/style/account.css vendored

File diff suppressed because it is too large Load Diff

597
dist/style/chat.css vendored
View File

@ -1,597 +0,0 @@
/* Styles de base */
:root {
--primary-color: #3A506B;
/* Bleu métallique */
--secondary-color: #B0BEC5;
/* Gris acier */
--accent-color: #D68C45;
/* Cuivre */
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* 4NK NAVBAR */
.brand-logo {
text-align: center;
font-size: 1.5em;
font-weight: bold;
}
.nav-wrapper {
position: fixed;
background: radial-gradient(circle, white, var(--primary-color));
display: flex;
justify-content: space-between;
align-items: center;
color: #37474F;
height: 9vh;
width: 100vw;
left: 0;
top: 0;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);
}
/* Icônes de la barre de navigation */
.nav-right-icons {
display: flex;
}
.notification-bell,
.burger-menu {
height: 20px;
width: 20px;
margin-right: 1rem;
cursor: pointer;
}
.notification-container {
position: relative;
/* Conserve la position pour le notification-board */
display: inline-flex;
align-items: center;
}
.notification-board {
position: absolute;
/* Position absolue pour le placer par rapport au container */
top: 40px;
right: 0;
background-color: white;
border: 1px solid #ccc;
padding: 10px;
width: 200px;
max-height: 300px;
overflow-y: auto;
/* Scroll si les notifications dépassent la taille */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
/* Définit la priorité d'affichage au-dessus des autres éléments */
display: none;
/* Par défaut, la notification est masquée */
}
.notification-item{
cursor: pointer;
}
.notification-badge {
position: absolute;
top: -18px;
right: 35px;
background-color: red;
color: white;
border-radius: 50%;
padding: 4px 8px;
font-size: 12px;
display: none;
/* S'affiche seulement lorsqu'il y a des notifications */
z-index: 10;
}
/* Par défaut, le menu est masqué */
#menu {
display: none;
/* Menu caché par défaut */
transition: display 0.3s ease-in-out;
}
.burger-menu {
cursor: pointer;
}
/* Icône burger */
#burger-icon {
cursor: pointer;
}
.menu-content {
display: none;
position: absolute;
top: 3.4rem;
right: 1rem;
background-color: white;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
}
.menu-content a {
display: block;
padding: 10px 20px;
text-decoration: none;
color: #333;
border-bottom: 1px solid #e0e0e0;
&:hover {
background-color: rgba(26, 28, 24, .08);
}
}
.menu-content a:last-child {
border-bottom: none;
}
/* Ajustement pour la barre de navigation fixe */
.container {
display: flex;
flex: 1;
height: 90vh;
margin-top: 9vh;
margin-left: -1%;
text-align: left;
width: 100vw;
}
/* Liste des groupes */
.group-list {
width: 25%;
background-color: #1f2c3d;
color: white;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
border-right: 2px solid #2c3e50;
flex-shrink: 0;
padding-right: 10px;
height: 91vh;
}
.group-list ul {
cursor: pointer;
list-style: none;
padding: 0;
padding-right: 10px;
margin-left: 20px;
}
.group-list li {
margin-bottom: 20px;
padding: 15px;
border-radius: 8px;
background-color: #273646;
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.3s;
}
.group-list li:hover {
background-color: #34495e;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.group-list .member-container {
position: relative;
}
.group-list .member-container button {
margin-left: 40px;
padding: 5px;
cursor: pointer;
background: var(--primary-color);
color: white;
border: 0px solid var(--primary-color);
border-radius: 50px;
position: absolute;
top: -25px;
right: -25px;
}
.group-list .member-container button:hover {
background: var(--accent-color)
}
/* Zone de chat */
.chat-area {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
background-color:#f1f1f1;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
margin: 1% 0% 0.5% 1%;
}
/* En-tête du chat */
.chat-header {
background-color: #34495e;
color: white;
padding: 15px;
font-size: 20px;
font-weight: bold;
border-radius: 10px 10px 0 0;
text-align: center;
}
/* Messages */
.messages {
flex: 1;
padding: 20px;
overflow-y: auto;
background-color: #f1f1f1;
border-top: 1px solid #ddd;
}
.message-container {
display: flex;
margin: 8px;
}
.message-container .message {
align-self: flex-start;
}
.message-container .message.user {
align-self: flex-end;
margin-left: auto;
color: white;
}
.message {
max-width: 70%;
padding: 10px;
border-radius: 12px;
background:var(--secondary-color);
margin: 2px 0;
}
/* Messages de l'utilisateur */
.message.user {
background: #2196f3;
color: white;
}
.message-time {
font-size: 0.7em;
opacity: 0.7;
margin-left: 0px;
margin-top: 5px;
}
/* Amélioration de l'esthétique des messages */
/* .message.user:before {
content: '';
position: absolute;
top: 10px;
right: -10px;
border: 10px solid transparent;
border-left-color: #3498db;
} */
/* Zone de saisie */
.input-area {
padding: 10px;
background-color: #bdc3c7;
display: flex;
align-items: center;
border-radius: 10px;
margin: 1%;
/* Alignement vertical */
}
.input-area input[type="text"] {
flex: 1;
/* Prend l'espace restant */
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
}
.input-area .attachment-icon {
margin: 0 10px;
cursor: pointer;
display: flex;
align-items: center;
}
.input-area button {
padding: 10px;
margin-left: 10px;
background-color: #2980b9;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.input-area button:hover {
background-color: #1f608d;
}
.tabs {
display: flex;
margin: 20px 0px;
gap: 10px;
}
.tabs button {
padding: 10px 20px;
cursor: pointer;
background: var(--primary-color);
color: white;
border: 0px solid var(--primary-color);
margin-right: 5px;
border-radius: 10px;
}
.tabs button:hover {
background: var(--secondary-color);
color: var(--primary-color);
}
/* Signature */
.signature-area {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
background-color:#f1f1f1;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
margin: 1% 0% 0.5% 1%;
transition: all 1s ease 0.1s;
visibility: visible;
}
.signature-area.hidden {
opacity: 0;
visibility: hidden;
display: none;
pointer-events: none;
}
.signature-header {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--primary-color);
color: white;
border-radius: 10px 10px 0 0;
padding-left: 4%;
}
.signature-content {
padding: 10px;
background-color: var(--secondary-color);
color: var(--primary-color);
height: 100%;
border-radius: 10px;
margin: 1%;
display: flex;
flex-direction: column;
align-items: center;
}
.signature-description {
height: 20%;
width: 100%;
margin: 0% 10% 0% 10%;
overflow: auto;
display: flex;
}
.signature-description li {
margin: 1% 0% 1% 0%;
list-style: none;
padding: 2%;
border-radius: 10px;
background-color: var(--primary-color);
color: var(--secondary-color);
width: 20%;
text-align: center;
cursor: pointer;
font-weight: bold;
margin-right: 2%;
overflow: auto;
}
.signature-description li .member-list {
margin-left: -30%;
}
.signature-description li .member-list li {
width: 100%;
}
.signature-description li .member-list li:hover {
background-color: var(--secondary-color);
color: var(--primary-color);
}
.signature-documents {
height: 80%;
width: 100%;
margin: 0% 10% 0% 10%;
overflow: auto;
display: flex;
}
.signature-documents-header {
display: flex;
width: 100%;
height: 15%;
align-items: center;
}
#request-document-button {
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 10px;
padding: 8px;
cursor: pointer;
margin-left: 5%;
font-weight: bold;
}
#request-document-button:hover {
background-color: var(--accent-color);
font-weight: bold;
}
#close-signature {
cursor: pointer;
align-items: center;
margin-left: auto;
margin-right: 2%;
border-radius: 50%;
background-color: var(--primary-color);
color: white;
border: none;
padding: -3%;
margin-top: -5%;
font-size: 1em;
font-weight: bold;
}
#close-signature:hover {
background-color: var(--secondary-color);
color: var(--primary-color);
}
/* REQUEST MODAL */
.request-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: var(--secondary-color);
padding: 20px;
border-radius: 8px;
position: relative;
min-width: 300px;
}
.close-modal {
position: absolute;
top: 10px;
right: 10px;
border: none;
background: none;
font-size: 1.5em;
cursor: pointer;
font-weight: bold;
}
.close-modal:hover {
color: var(--accent-color);
}
.modal-members {
display: flex;
justify-content: space-between;
}
.modal-members ul li{
list-style: none;
}
.file-upload-container {
margin: 10px 0;
}
.file-list {
margin-top: 10px;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px;
margin: 5px 0;
background: var(--background-color-secondary);
border-radius: 4px;
}
.remove-file {
background: none;
border: none;
color: var(--text-color);
cursor: pointer;
padding: 0 5px;
}
.remove-file:hover {
color: var(--error-color);
}
#message-input {
width: 100%;
height: 50px;
resize: none;
padding: 10px;
box-sizing: border-box;
overflow: auto;
max-width: 100%;
border-radius: 10px;
}
/* Responsive */
@media screen and (max-width: 768px) {
.group-list {
display: none;
/* Masquer la liste des groupes sur les petits écrans */
}
.chat-area {
margin: 0;
}
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: var(--primary-color);
border-radius: 5px;
}
::-webkit-scrollbar-thumb {
background: var(--secondary-color);
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-color);
}

1664
dist/style/signature.css vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
export declare function unpair(): Promise<void>;
export declare function initHeader(): Promise<void>;

View File

@ -1 +0,0 @@
export declare function closeConfirmationModal(): Promise<void>;

View File

@ -1,10 +0,0 @@
export default class QrScannerComponent extends HTMLElement {
videoElement: any;
wrapper: any;
qrScanner: any;
constructor();
connectedCallback(): void;
initializeScanner(): Promise<void>;
onQrCodeScanned(result: any): Promise<void>;
disconnectedCallback(): void;
}

View File

@ -1 +0,0 @@
export declare function initValidationModal(processDiffs: any): Promise<void>;

View File

@ -1,14 +0,0 @@
export interface ValidationRule {
quorum: number;
fields: string[];
min_sig_member: number;
}
/**
* Loads and injects the modal HTML into the document if not already loaded.
*/
export declare function loadValidationRuleModal(templatePath?: string): Promise<void>;
/**
* Opens the modal and lets the user input a ValidationRule.
* Calls the callback with the constructed rule on submit.
*/
export declare function showValidationRuleModal(onSubmit: (rule: ValidationRule) => void): void;

View File

@ -1,3 +0,0 @@
export { default as Services } from './services/service';
export { default as Database } from './services/database.service';
export { MessageType } from './models/process.model';

View File

@ -1,24 +0,0 @@
import { DocumentSignature } from '~/models/signature.models';
export interface Group {
id: number;
name: string;
description: string;
roles: Array<{
name: string;
members: Array<{
id: string | number;
name: string;
}>;
documents?: Array<any>;
}>;
commonDocuments: Array<{
id: number;
name: string;
visibility: string;
description: string;
createdAt?: string | null;
deadline?: string | null;
signatures?: DocumentSignature[];
status?: string;
}>;
}

View File

@ -1,10 +0,0 @@
export interface Member {
id: string | number;
name: string;
email?: string;
avatar?: string;
processRoles?: Array<{
processId: number | string;
role: string;
}>;
}

13
dist/types/main.d.ts vendored
View File

@ -1,13 +0,0 @@
import { SignatureComponent } from './pages/signature/signature-component';
import { SignatureElement } from './pages/signature/signature';
import { AccountComponent } from './pages/account/account-component';
import { AccountElement } from './pages/account/account';
export { SignatureComponent, SignatureElement, AccountComponent, AccountElement };
declare global {
interface HTMLElementTagNameMap {
'signature-component': SignatureComponent;
'signature-element': SignatureElement;
'account-component': AccountComponent;
'account-element': AccountElement;
}
}

View File

@ -1,118 +0,0 @@
export declare const ALLOWED_ROLES: string[];
export declare const STORAGE_KEYS: {
pairing: string;
wallet: string;
process: string;
data: string;
};
export declare const defaultRows: {
column1: string;
column2: string;
column3: string;
}[];
export declare const mockNotifications: {
[key: string]: Notification[];
};
export declare const notificationMessages: string[];
export declare const mockDataRows: {
column1: string;
column2: string;
column3: string;
column4: string;
column5: string;
column6: string;
processName: string;
zone: string;
}[];
export declare const mockProcessRows: ({
process: string;
role: string;
notification: {
messages: {
id: number;
read: boolean;
date: string;
message: string;
}[];
unread?: undefined;
total?: undefined;
};
} | {
process: string;
role: string;
notification: {
unread: number;
total: number;
messages: {
id: number;
read: boolean;
date: string;
message: string;
}[];
};
})[];
export declare const mockContracts: {
'Contract #123': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #456': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #789': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #101': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #102': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #103': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #104': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #105': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
'Contract #106': {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
};
};

View File

@ -1,38 +0,0 @@
export interface Row {
column1: string;
column2: string;
column3: string;
}
export interface Contract {
title: string;
date: string;
parties: string[];
terms: string[];
content: string;
}
export interface WalletRow {
column1: string;
column2: string;
column3: string;
}
export interface DataRow {
column1: string;
column2: string;
column3: string;
column4: string;
column5: string;
column6: string;
processName: string;
zone: string;
}
export interface Notification {
message: string;
timestamp: string;
isRead: boolean;
}
export interface NotificationMessage {
id: number;
read: boolean;
date: string;
message: string;
}

View File

@ -1,119 +0,0 @@
export const groupsMock: {
id: number;
name: string;
description: string;
commonDocuments: {
id: number;
name: string;
description: string;
visibility: string;
status: string;
createdAt: null;
deadline: null;
signatures: never[];
}[];
roles: ({
name: string;
members: {
id: number;
name: string;
}[];
documents: ({
id: number;
name: string;
description: string;
visibility: string;
createdAt: string;
deadline: string;
signatures: ({
member: {
id: number;
name: string;
};
signed: boolean;
signedAt: string;
} | {
member: {
id: number;
name: string;
};
signed: boolean;
signedAt?: undefined;
})[];
status?: undefined;
} | {
id: number;
name: string;
description: string;
visibility: string;
createdAt: null;
deadline: null;
signatures: never[];
status?: undefined;
} | {
id: number;
name: string;
description: string;
visibility: string;
status: string;
createdAt: null;
deadline: null;
signatures: never[];
})[];
} | {
name: string;
members: {
id: number;
name: string;
}[];
documents: ({
id: number;
name: string;
description: string;
visibility: string;
createdAt: string;
deadline: string;
signatures: {
member: {
id: number;
name: string;
};
signed: boolean;
signedAt: string;
}[];
status?: undefined;
} | {
id: number;
name: string;
description: string;
visibility: string;
status: string;
createdAt: null;
deadline: null;
signatures: never[];
} | {
id: number;
name: string;
description: string;
visibility: string;
status: string;
createdAt: string;
deadline: string;
signatures: ({
member: {
id: number;
name: string;
};
signed: boolean;
signedAt: string;
} | {
member: {
id: number;
name: string;
};
signed: boolean;
signedAt?: undefined;
})[];
})[];
})[];
}[];

View File

@ -1,10 +0,0 @@
export const membersMock: {
id: number;
name: string;
avatar: string;
email: string;
processRoles: {
processId: number;
role: string;
}[];
}[];

View File

@ -1,9 +0,0 @@
export declare const messagesMock: {
memberId: number;
messages: {
id: number;
sender: string;
text: string;
time: string;
}[];
}[];

View File

@ -1,24 +0,0 @@
export interface INotification {
id: number;
title: string;
description: string;
sendToNotificationPage?: boolean;
path?: string;
}
export interface IUser {
id: string;
information?: any;
}
export interface IMessage {
id: string;
message: any;
}
export interface UserDiff {
new_state_merkle_root: string;
field: string;
previous_value: string;
new_value: string;
notify_user: boolean;
need_validation: boolean;
proof: any;
}

View File

@ -1,56 +0,0 @@
export interface IProcess {
id: number;
name: string;
description: string;
icon?: string;
zoneList: IZone[];
}
export interface IZone {
id: number;
name: string;
path: string;
icon?: string;
}
export interface INotification {
id: number;
title: string;
description: string;
sendToNotificationPage?: boolean;
path?: string;
}
export declare enum MessageType {
LISTENING = "LISTENING",
REQUEST_LINK = "REQUEST_LINK",
LINK_ACCEPTED = "LINK_ACCEPTED",
CREATE_PAIRING = "CREATE_PAIRING",
PAIRING_CREATED = "PAIRING_CREATED",
ERROR = "ERROR",
VALIDATE_TOKEN = "VALIDATE_TOKEN",
RENEW_TOKEN = "RENEW_TOKEN",
GET_PAIRING_ID = "GET_PAIRING_ID",
GET_PROCESSES = "GET_PROCESSES",
GET_MY_PROCESSES = "GET_MY_PROCESSES",
PROCESSES_RETRIEVED = "PROCESSES_RETRIEVED",
RETRIEVE_DATA = "RETRIEVE_DATA",
DATA_RETRIEVED = "DATA_RETRIEVED",
DECODE_PUBLIC_DATA = "DECODE_PUBLIC_DATA",
PUBLIC_DATA_DECODED = "PUBLIC_DATA_DECODED",
GET_MEMBER_ADDRESSES = "GET_MEMBER_ADDRESSES",
MEMBER_ADDRESSES_RETRIEVED = "MEMBER_ADDRESSES_RETRIEVED",
CREATE_PROCESS = "CREATE_PROCESS",
PROCESS_CREATED = "PROCESS_CREATED",
UPDATE_PROCESS = "UPDATE_PROCESS",
PROCESS_UPDATED = "PROCESS_UPDATED",
NOTIFY_UPDATE = "NOTIFY_UPDATE",
UPDATE_NOTIFIED = "UPDATE_NOTIFIED",
VALIDATE_STATE = "VALIDATE_STATE",
STATE_VALIDATED = "STATE_VALIDATED",
HASH_VALUE = "HASH_VALUE",
VALUE_HASHED = "VALUE_HASHED",
GET_MERKLE_PROOF = "GET_MERKLE_PROOF",
MERKLE_PROOF_RETRIEVED = "MERKLE_PROOF_RETRIEVED",
VALIDATE_MERKLE_PROOF = "VALIDATE_MERKLE_PROOF",
MERKLE_PROOF_VALIDATED = "MERKLE_PROOF_VALIDATED",
ADD_DEVICE = "ADD_DEVICE",
DEVICE_ADDED = "DEVICE_ADDED"
}

View File

@ -1,60 +0,0 @@
export interface Group {
id: number;
name: string;
description?: string;
roles: {
id?: number;
name: string;
members: {
id: string | number;
name: string;
}[];
documents?: {
id: number;
name: string;
description?: string;
visibility: string;
createdAt: string | null;
deadline: string | null;
signatures: DocumentSignature[];
status?: string;
files?: Array<{
name: string;
url: string;
}>;
}[];
}[];
}
export interface Message {
id: number;
sender: string;
text?: string;
time: string;
type: 'text' | 'file';
fileName?: string;
fileData?: string;
}
export interface MemberMessages {
memberId: string;
messages: Message[];
}
export interface DocumentSignature {
signed: boolean;
member: {
name: string;
};
signedAt?: string;
}
export interface RequestParams {
processId: number;
processName: string;
roleId: number;
roleName: string;
documentId: number;
documentName: string;
}
export interface Notification {
memberId: string;
text: string;
time: string;
}

View File

@ -1,12 +0,0 @@
import { AccountElement } from './account';
declare class AccountComponent extends HTMLElement {
_callback: any;
accountElement: AccountElement | null;
constructor();
connectedCallback(): void;
fetchData(): Promise<void>;
set callback(fn: any);
get callback(): any;
render(): void;
}
export { AccountComponent };

View File

@ -1,98 +0,0 @@
declare global {
interface Window {
initAccount: () => void;
showContractPopup: (contractId: string) => void;
showPairing: () => Promise<void>;
showWallet: () => void;
showData: () => void;
addWalletRow: () => void;
confirmWalletRow: () => void;
cancelWalletRow: () => void;
openAvatarPopup: () => void;
closeAvatarPopup: () => void;
editDeviceName: (cell: HTMLTableCellElement) => void;
showNotifications: (processName: string) => void;
closeNotificationPopup: (event: Event) => void;
markAsRead: (processName: string, messageId: number, element: HTMLElement) => void;
exportRecovery: () => void;
confirmDeleteAccount: () => void;
deleteAccount: () => void;
updateNavbarBanner: (bannerUrl: string) => void;
saveBannerToLocalStorage: (bannerUrl: string) => void;
loadSavedBanner: () => void;
cancelAddRowPairing: () => void;
saveName: (cell: HTMLElement, input: HTMLInputElement) => void;
showProcessNotifications: (processName: string) => void;
handleLogout: () => void;
initializeEventListeners: () => void;
showProcess: () => void;
showProcessCreation: () => void;
showDocumentValidation: () => void;
updateNavbarName: (name: string) => void;
updateNavbarLastName: (lastName: string) => void;
showAlert: (title: string, text?: string, icon?: string) => void;
addRowPairing: () => void;
confirmRowPairing: () => void;
cancelRowPairing: () => void;
deleteRowPairing: (button: HTMLButtonElement) => void;
generateRecoveryWords: () => string[];
exportUserData: () => void;
updateActionButtons: () => void;
showQRCodeModal: (pairingId: string) => void;
}
}
declare class AccountElement extends HTMLElement {
private dom;
constructor();
connectedCallback(): void;
private showAlert;
private confirmDeleteAccount;
private deleteAccount;
private updateNavbarBanner;
private saveBannerToLocalStorage;
private loadSavedBanner;
private closeNotificationPopup;
private markAsRead;
private calculateNotifications;
private exportRecovery;
private generateRecoveryWords;
private exportUserData;
private updateActionButtons;
private getConfirmFunction;
private getCancelFunction;
private addRowPairing;
private updateTableContent;
private confirmRowPairing;
private cancelRowPairing;
private resetButtonContainer;
private deleteRowPairing;
private editDeviceName;
private finishEditing;
private handleAvatarUpload;
private showProcessCreation;
private showDocumentValidation;
private showProcess;
private showProcessNotifications;
private handleLogout;
private showContractPopup;
private hideAllContent;
private showPairing;
private showWallet;
private updateWalletTableContent;
private showData;
private addWalletRow;
private confirmWalletRow;
private cancelWalletRow;
private updateDataTableContent;
private openAvatarPopup;
private setupEventListeners;
private closeAvatarPopup;
private loadAvatar;
private loadUserInfo;
private updateNavbarName;
private updateNavbarLastName;
private updateProfilePreview;
private initializeEventListeners;
private showQRCodeModal;
}
export { AccountElement };

View File

@ -1,33 +0,0 @@
export interface Vin {
txid: string;
vout: number;
prevout: {
scriptpubkey: string;
scriptpubkey_asm: string;
scriptpubkey_type: string;
scriptpubkey_address: string;
value: number;
};
scriptsig: string;
scriptsig_asm: string;
witness: string[];
is_coinbase: boolean;
sequence: number;
}
export interface TransactionInfo {
txid: string;
version: number;
locktime: number;
vin: Vin[];
vout: any[];
size: number;
weight: number;
fee: number;
status: {
confirmed: boolean;
block_height: number;
block_hash: string;
block_time: number;
};
}
export declare function getDocumentValidation(container: HTMLElement): void;

View File

@ -1,8 +0,0 @@
import { RoleDefinition } from '../../../pkg/sdk_client.js';
export declare function createKeyValueSection(title: string, id: string, isRoleSection?: boolean): {
element: HTMLDivElement;
getData: () => Record<string, RoleDefinition> | Record<string, string | {
type: string;
data: Uint8Array;
}>;
};

View File

@ -1 +0,0 @@
export declare function getProcessCreation(container: HTMLElement): Promise<void>;

View File

@ -1,4 +0,0 @@
export declare function createProcessTab(container: HTMLElement, processes: {
name: string;
publicData: Record<string, any>;
}[]): HTMLElement;

View File

@ -1,8 +0,0 @@
export declare class LoginComponent extends HTMLElement {
_callback: any;
constructor();
connectedCallback(): void;
set callback(fn: any);
get callback(): any;
render(): void;
}

View File

@ -1,4 +0,0 @@
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
export { QrScannerComponent };
export declare function initHomePage(): Promise<void>;
export declare function openModal(myAddress: string, receiverAddress: string): Promise<void>;

View File

@ -1,10 +0,0 @@
export declare class ProcessListComponent extends HTMLElement {
_callback: any;
id: string;
zone: string;
constructor();
connectedCallback(): void;
set callback(fn: any);
get callback(): any;
render(): void;
}

View File

@ -1 +0,0 @@
export declare function initProcessElement(id: string, zone: string): Promise<void>;

View File

@ -1,12 +0,0 @@
import { SignatureElement } from './signature';
declare class SignatureComponent extends HTMLElement {
_callback: any;
signatureElement: SignatureElement | null;
constructor();
connectedCallback(): void;
fetchData(): Promise<void>;
set callback(fn: any);
get callback(): any;
render(): void;
}
export { SignatureComponent };

View File

@ -1,71 +0,0 @@
declare global {
interface Window {
toggleUserList: () => void;
switchUser: (userId: string | number) => void;
closeProcessDetails: (groupId: number) => void;
loadMemberChat: (memberId: string | number) => void;
closeRoleDocuments: (roleName: string) => void;
newRequest: (params: RequestParams) => void;
submitRequest: () => void;
closeNewRequest: () => void;
closeModal: (button: HTMLElement) => void;
submitDocumentRequest: (documentId: number) => void;
submitNewDocument: (event: Event) => void;
submitCommonDocument: (event: Event) => void;
signDocument: (documentId: number, processId: number, isCommonDocument: boolean) => void;
confirmSignature: (documentId: number, processId: number, isCommonDocument: boolean) => void;
}
}
import { RequestParams } from '../../models/signature.models';
declare class SignatureElement extends HTMLElement {
private selectedMemberId;
private messagesMock;
private dom;
private notifications;
private notificationBadge;
private notificationBoard;
private notificationBell;
private selectedSignatories;
private allMembers;
private showAlert;
private signDocument;
constructor();
private initMessageEvents;
private initFileUpload;
private calculateDuration;
private canUserAccessDocument;
private canUserSignDocument;
private closeProcessDetails;
private removeNotification;
private renderNotifications;
private updateNotificationBadge;
private addNotification;
private sendMessage;
private showProcessDetails;
private scrollToBottom;
private loadMemberChat;
private toggleMembers;
private toggleRoles;
private loadGroupList;
private toggleUserList;
private switchUser;
private updateCurrentUserDisplay;
private generateAutoReply;
private sendFile;
private fileList;
private getFileList;
private showRoleDocuments;
private closeRoleDocuments;
private handleFiles;
private newRequest;
private closeModal;
private submitNewDocument;
private submitCommonDocument;
private submitRequest;
private closeNewRequest;
private submitDocumentRequest;
private confirmSignature;
private initializeEventListeners;
connectedCallback(): void;
}
export { SignatureElement };

View File

@ -1,5 +0,0 @@
import '../public/style/4nk.css';
export declare let currentRoute: string;
export declare function navigate(path: string): Promise<void>;
export declare function init(): Promise<void>;
export declare function registerAllListeners(): Promise<void>;

View File

@ -1,43 +0,0 @@
export declare class Database {
private static instance;
private db;
private dbName;
private dbVersion;
private serviceWorkerRegistration;
private messageChannel;
private messageChannelForGet;
private serviceWorkerCheckIntervalId;
private storeDefinitions;
private constructor();
static getInstance(): Promise<Database>;
private init;
getDb(): Promise<IDBDatabase>;
getStoreList(): {
[key: string]: string;
};
registerServiceWorker(path: string): Promise<void>;
private waitForServiceWorkerActivation;
private checkForUpdates;
private handleServiceWorkerMessage;
private handleDownloadList;
private handleAddObjectResponse;
private handleGetObjectResponse;
addObject(payload: {
storeName: string;
object: any;
key: any;
}): Promise<void>;
batchWriting(payload: {
storeName: string;
objects: {
key: any;
object: any;
}[];
}): Promise<void>;
getObject(storeName: string, key: string): Promise<any | null>;
dumpStore(storeName: string): Promise<Record<string, any>>;
deleteObject(storeName: string, key: string): Promise<void>;
clearStore(storeName: string): Promise<void>;
requestStoreByIndex(storeName: string, indexName: string, request: string): Promise<any[]>;
}
export default Database;

View File

@ -1,28 +0,0 @@
import { RoleDefinition } from '../../pkg/sdk_client.js';
interface ConfirmationModalOptions {
title: string;
content: string;
confirmText?: string;
cancelText?: string;
}
export default class ModalService {
private static instance;
private stateId;
private processId;
private constructor();
private paired_addresses;
private modal;
static getInstance(): Promise<ModalService>;
openLoginModal(myAddress: string, receiverAddress: string): void;
injectModal(members: any[]): Promise<void>;
injectCreationModal(members: any[]): Promise<void>;
injectWaitingModal(): Promise<void>;
injectValidationModal(processDiff: any): Promise<void>;
closeValidationModal(): Promise<void>;
openPairingConfirmationModal(roleDefinition: Record<string, RoleDefinition>, processId: string, stateId: string): Promise<void>;
confirmLogin(): void;
closeLoginModal(): Promise<void>;
showConfirmationModal(options: ConfirmationModalOptions, fullscreen?: boolean): Promise<boolean>;
closeConfirmationModal(): Promise<void>;
}
export {};

View File

@ -1,171 +0,0 @@
import { ApiReturn, Device, Member, MerkleProofResult, Process, ProcessState, RoleDefinition, SecretsStore, UserDiff } from '../../pkg/sdk_client.js';
import { BackUp } from '~/models/backup.model';
export declare const U32_MAX = 4294967295;
export default class Services {
private static initializing;
private static instance;
private processId;
private stateId;
private sdkClient;
private processesCache;
private myProcesses;
private notifications;
private subscriptions;
private database;
private routingInstance;
private relayAddresses;
private membersList;
private currentBlockHeight;
private constructor();
static getInstance(): Promise<Services>;
init(): Promise<void>;
setProcessId(processId: string | null): void;
setStateId(stateId: string | null): void;
getProcessId(): string | null;
getStateId(): string | null;
/**
* Calls `this.addWebsocketConnection` for each `wsurl` in relayAddresses.
* Waits for at least one handshake message before returning.
*/
connectAllRelays(): Promise<void>;
addWebsocketConnection(url: string): Promise<void>;
/**
* Add or update a key/value pair in relayAddresses.
* @param wsurl - The WebSocket URL (key).
* @param spAddress - The SP Address (value).
*/
updateRelay(wsurl: string, spAddress: string): void;
/**
* Retrieve the spAddress for a given wsurl.
* @param wsurl - The WebSocket URL to look up.
* @returns The SP Address if found, or undefined if not.
*/
getSpAddress(wsurl: string): string | undefined;
/**
* Get all key/value pairs from relayAddresses.
* @returns An array of objects containing wsurl and spAddress.
*/
getAllRelays(): {
wsurl: string;
spAddress: string;
}[];
/**
* Print all key/value pairs for debugging.
*/
printAllRelays(): void;
isPaired(): boolean;
unpairDevice(): Promise<void>;
getSecretForAddress(address: string): Promise<string | null>;
getAllSecrets(): Promise<SecretsStore>;
getAllDiffs(): Promise<Record<string, UserDiff>>;
getDiffByValue(value: string): Promise<UserDiff | null>;
private getTokensFromFaucet;
checkConnections(process: Process, stateId?: string | null): Promise<void>;
connectAddresses(addresses: string[]): Promise<ApiReturn>;
private ensureSufficientAmount;
private waitForAmount;
createPairingProcess(userName: string, pairWith: string[]): Promise<ApiReturn>;
private isFileBlob;
private splitData;
createProcess(privateData: Record<string, any>, publicData: Record<string, any>, roles: Record<string, RoleDefinition>): Promise<ApiReturn>;
/**
* Déclenche un transfert automatique de fonds du wallet mining vers le relay
*/
private triggerAutomaticFundsTransfer;
updateProcess(process: Process, privateData: Record<string, any>, publicData: Record<string, any>, roles: Record<string, RoleDefinition> | null): Promise<ApiReturn>;
createPrdUpdate(processId: string, stateId: string): Promise<ApiReturn>;
createPrdResponse(processId: string, stateId: string): Promise<ApiReturn>;
approveChange(processId: string, stateId: string): Promise<ApiReturn>;
rejectChange(processId: string, stateId: string): Promise<ApiReturn>;
resetDevice(): Promise<void>;
sendNewTxMessage(message: string): void;
sendCommitMessage(message: string): void;
sendCipherMessages(ciphers: string[]): void;
sendFaucetMessage(message: string): void;
parseCipher(message: string): Promise<void>;
parseNewTx(newTxMsg: string): Promise<void>;
handleApiReturn(apiReturn: ApiReturn): Promise<void>;
openPairingConfirmationModal(processId: string): Promise<void>;
confirmPairing(): Promise<void>;
updateDevice(): Promise<void>;
pairDevice(processId: string, spAddressList: string[]): void;
getAmount(): BigInt;
getDeviceAddress(): string;
dumpDeviceFromMemory(): Device;
dumpNeuteredDevice(): Device | null;
getPairingProcessId(): string;
saveDeviceInDatabase(device: Device): Promise<void>;
getDeviceFromDatabase(): Promise<Device | null>;
getMemberFromDevice(): Promise<string[] | null>;
isChildRole(parent: any, child: any): boolean;
rolesContainsUs(roles: Record<string, RoleDefinition>): boolean;
rolesContainsMember(roles: Record<string, RoleDefinition>, pairingProcessId: string): boolean;
dumpWallet(): Promise<any>;
createFaucetMessage(): any;
createNewDevice(): Promise<string>;
restoreDevice(device: Device): void;
updateDeviceBlockHeight(): Promise<void>;
private removeProcess;
batchSaveProcessesToDb(processes: Record<string, Process>): Promise<void>;
saveProcessToDb(processId: string, process: Process): Promise<void>;
saveBlobToDb(hash: string, data: Blob): Promise<void>;
getBlobFromDb(hash: string): Promise<Blob | null>;
saveDataToStorage(hash: string, storages: string[], data: Blob, ttl: number | null): Promise<void>;
fetchValueFromStorage(hash: string): Promise<ArrayBuffer | null>;
getDiffByValueFromDb(hash: string): Promise<UserDiff | null>;
saveDiffsToDb(diffs: UserDiff[]): Promise<void>;
getProcess(processId: string): Promise<Process | null>;
getProcesses(): Promise<Record<string, Process>>;
restoreProcessesFromBackUp(processes: Record<string, Process>): Promise<void>;
restoreProcessesFromDB(): Promise<void>;
clearSecretsFromDB(): Promise<void>;
restoreSecretsFromBackUp(secretsStore: SecretsStore): Promise<void>;
restoreSecretsFromDB(): Promise<void>;
decodeValue(value: number[]): any | null;
decryptAttribute(processId: string, state: ProcessState, attribute: string): Promise<any | null>;
getNotifications(): any[] | null;
setNotifications(notifications: any[]): void;
importJSON(backup: BackUp): Promise<void>;
createBackUp(): Promise<BackUp | null>;
device1: boolean;
device2Ready: boolean;
resetState(): void;
handleHandshakeMsg(url: string, parsedMsg: any): Promise<void>;
private lookForStateId;
/**
* Waits for at least one handshake message to be received from any connected relay.
* This ensures that the relay addresses are fully populated and the member list is updated.
* @returns A promise that resolves when at least one handshake message is received.
*/
private waitForHandshakeMessage;
/**
* Retourne la liste de tous les membres ordonnés par leur process id
* @returns Un tableau contenant tous les membres
*/
getAllMembersSorted(): Record<string, Member>;
getAllMembers(): Record<string, Member>;
getAddressesForMemberId(memberId: string): string[] | null;
compareMembers(memberA: string[], memberB: string[]): boolean;
handleCommitError(response: string): Promise<void>;
getRoles(process: Process): Record<string, RoleDefinition> | null;
getPublicData(process: Process): Record<string, any> | null;
getProcessName(process: Process): string | null;
getMyProcesses(): Promise<string[] | null>;
requestDataFromPeers(processId: string, stateIds: string[], roles: Record<string, RoleDefinition>[]): Promise<void>;
hexToBlob(hexString: string): Blob;
hexToUInt8Array(hexString: string): Uint8Array;
blobToHex(blob: Blob): Promise<string>;
getHashForFile(commitedIn: string, label: string, fileBlob: {
type: string;
data: Uint8Array;
}): string;
getMerkleProofForFile(processState: ProcessState, attributeName: string): MerkleProofResult;
validateMerkleProof(proof: MerkleProofResult, hash: string): boolean;
getLastCommitedState(process: Process): ProcessState | null;
getLastCommitedStateIndex(process: Process): number | null;
getUncommitedStates(process: Process): ProcessState[];
getStateFromId(process: Process, stateId: string): ProcessState | null;
getNextStateAfterId(process: Process, stateId: string): ProcessState | null;
isPairingProcess(roles: Record<string, RoleDefinition>): boolean;
updateMemberPublicName(process: Process, newName: string): Promise<ApiReturn>;
}

View File

@ -1,4 +0,0 @@
import { AxiosResponse } from 'axios';
export declare function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null>;
export declare function retrieveData(servers: string[], key: string): Promise<ArrayBuffer | null>;
export declare function testData(url: string, key: string): Promise<boolean | null>;

View File

@ -1,18 +0,0 @@
interface TokenPair {
accessToken: string;
refreshToken: string;
}
export default class TokenService {
private static instance;
private readonly SECRET_KEY;
private readonly ACCESS_TOKEN_EXPIRATION;
private readonly REFRESH_TOKEN_EXPIRATION;
private readonly encoder;
private constructor();
static getInstance(): Promise<TokenService>;
private getOrCreateSecret;
generateSessionToken(origin: string): Promise<TokenPair>;
validateToken(token: string, origin: string): Promise<boolean>;
refreshAccessToken(refreshToken: string, origin: string): Promise<string | null>;
}
export {};

View File

@ -1 +0,0 @@
export declare function getCorrectDOM(componentTag: string): Node;

View File

@ -1,4 +0,0 @@
export declare function interpolate(template: string, data: {
[key: string]: string;
}): string;
export declare function getCorrectDOM(componentTag: string): Node;

View File

@ -1,12 +0,0 @@
declare class MessageStore {
private readonly STORAGE_KEY;
private messages;
constructor();
private loadFromLocalStorage;
getMessages(): any[];
setMessages(messages: any[]): void;
private saveToLocalStorage;
addMessage(memberId: string | number, message: any): void;
}
export declare const messageStore: MessageStore;
export {};

View File

@ -1,23 +0,0 @@
interface INotification {
id: number;
title: string;
description: string;
time?: string;
memberId?: string;
}
declare class NotificationStore {
private static instance;
private notifications;
private constructor();
static getInstance(): NotificationStore;
addNotification(notification: INotification): void;
removeNotification(index: number): void;
getNotifications(): INotification[];
private saveToLocalStorage;
private loadFromLocalStorage;
private updateUI;
private renderNotificationBoard;
refreshNotifications(): void;
}
export declare const notificationStore: NotificationStore;
export {};

View File

@ -1,5 +0,0 @@
export declare function splitPrivateData(data: Record<string, any>, privateFields: string[]): {
privateData: Record<string, any>;
publicData: Record<string, any>;
};
export declare function isValid32ByteHex(value: string): boolean;

View File

@ -1,8 +0,0 @@
export declare function copyToClipboard(fullAddress: string): Promise<void>;
export declare function generateEmojiList(): string[];
export declare function addressToEmoji(text: string): Promise<string>;
export declare function displayEmojis(text: string): Promise<void>;
export declare function initAddressInput(): void;
export declare function prepareAndSendPairingTx(): Promise<void>;
export declare function generateQRCode(spAddress: string): Promise<void>;
export declare function generateCreateBtn(): Promise<void>;

View File

@ -1,2 +0,0 @@
export declare function cleanSubscriptions(): void;
export declare function addSubscription(element: Element | Document, event: any, eventHandler: EventListenerOrEventListenerObject): void;

View File

@ -1,5 +0,0 @@
import { AnkFlag } from '.././pkg/sdk_client.js';
export declare function initWebsocket(url: string): Promise<void>;
export declare function sendMessage(flag: AnkFlag, message: string): void;
export declare function getUrl(): string;
export declare function close(): void;

4
doc/BDD_ihm.drawio.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -9,7 +9,6 @@
<link rel="stylesheet" href="./style/4nk.css">
<script src="https://unpkg.com/html5-qrcode"></script>
<title>4NK Application</title>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
</head>
<body>
<div id="header-container"></div>
@ -18,18 +17,9 @@
</div>
<!-- <script type="module" src="/src/index.ts"></script> -->
<script type="module">
// Charge le module WASM (ESM auto-start, pas d'export default)
import './pkg/sdk_client.js';
// Initialise l'application
import { init as initRouter } from '/src/router.ts';
import { init } from '/src/router.ts';
(async () => {
try {
await initRouter();
} catch (error) {
console.error('Failed to initialize application:', error);
}
await init();
})();
</script>
</body>

3805
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,11 @@
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build_wasm": "wasm-pack build --out-dir ../ihm_client/pkg ../sdk_client --target bundler --dev",
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev1/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/",
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\"",
"build:dist": "tsc -p tsconfig.build.json"
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\""
},
"keywords": [],
"author": "",
@ -31,15 +30,11 @@
},
"dependencies": {
"@angular/elements": "^19.0.1",
"@types/jsonwebtoken": "^9.0.9",
"@types/qrcode": "^1.5.5",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-vue": "^5.0.5",
"axios": "^1.7.8",
"html5-qrcode": "^2.3.8",
"jose": "^6.0.11",
"jsonwebtoken": "^9.0.2",
"pdf-lib": "^1.17.1",
"qr-scanner": "^1.4.2",
"qrcode": "^1.5.3",
"sweetalert2": "^11.14.5",

79
pkg/.gitignore vendored
View File

@ -1,79 +0,0 @@
# 4NK Environment - Git Ignore
# ============================
confs/
# Dossiers de sauvegarde des scripts
**/backup/
**/*backup*
**/.cargo/
# Fichiers temporaires
**/*.tmp*
**/*.temp*
**/*.log*
**/*.pid*
# Fichiers de configuration locale
**/*.env*
**/*.conf*
**/*.yaml*
**/*.yml*
**/*.ini*
**/*.json*
**/*.toml*
**/*.lock*
# Données et logs
**/*.logs*
**/*.data
*.db
*.sqlite
# Certificats et clés
**/*.key
**/*.pem
**/*.crt
**/*.p12
**/*.pfx
ssl/
certs/
# Docker
**/*.docker*
# Cache et build
**/*.node_modules/
**/*.dist/
**/*build/
**/*target/
**/*.*.o
**/*.so
**/*.dylib
# IDE et éditeurs
**/*.vscode/
**/*.idea/
**/*.swp
**/*.swo
**/*~
# OS
**/*.DS_Store
**/*Thumbs.db
**/*tmp*
# Git
**/*.git/
**/*.orig*
# Backup des projets existants
**/*backup*
**/*wallet*
**/*keys*
**/*node_modules*
**/*cursor*
**/*pid*
**/*next*

View File

@ -1,17 +0,0 @@
{
"name": "sdk_client",
"type": "module",
"version": "0.1.3",
"files": [
"sdk_client_bg.wasm",
"sdk_client.js",
"sdk_client_bg.js",
"sdk_client.d.ts"
],
"main": "sdk_client.js",
"types": "sdk_client.d.ts",
"sideEffects": [
"./sdk_client.js",
"./snippets/*"
]
}

249
pkg/sdk_client.d.ts vendored
View File

@ -1,249 +0,0 @@
/* tslint:disable */
/* eslint-disable */
export function setup(): void;
export function get_address(): string;
export function get_member(): Member;
export function restore_device(device: any): void;
export function create_device_from_sp_wallet(sp_wallet: string): string;
export function create_new_device(birthday: number, network_str: string): string;
export function scan_blocks(tip_height: number, blindbit_url: string): Promise<void>;
export function is_paired(): boolean;
export function pair_device(process_id: string, sp_addresses: string[]): void;
export function unpair_device(): void;
export function dump_wallet(): string;
export function reset_shared_secrets(): void;
export function set_shared_secrets(secrets: string): void;
export function get_pairing_process_id(): string;
export function dump_device(): Device;
export function dump_neutered_device(): Device;
export function reset_device(): void;
export function get_txid(transaction: string): string;
export function get_prevouts(transaction: string): string[];
export function get_opreturn(transaction: string): string;
export function process_commit_new_state(process: Process, state_id: string, new_tip: string): Process;
export function parse_new_tx(new_tx_msg: string, block_height: number, members_list: OutPointMemberMap): ApiReturn;
export function parse_cipher(cipher_msg: string, members_list: OutPointMemberMap, processes: OutPointProcessMap): ApiReturn;
export function get_outputs(): any;
export function get_available_amount(): bigint;
/**
* We send a transaction that pays at least one output to each address
* The goal can be to establish a shared_secret to be used as an encryption key for further communication
* or if the recipient is a relay it can be the init transaction for a new process
*/
export function create_transaction(addresses: string[], fee_rate: number): ApiReturn;
export function sign_transaction(partial_tx: TsUnsignedTransaction): ApiReturn;
export function create_new_process(private_data: Pcd, roles: Roles, public_data: Pcd, relay_address: string, fee_rate: number, members_list: OutPointMemberMap): ApiReturn;
export function update_process(process: Process, new_attributes: Pcd, roles: Roles, new_public_data: Pcd, members_list: OutPointMemberMap): ApiReturn;
export function request_data(process_id: string, state_ids_str: string[], roles: any, members_list: OutPointMemberMap): ApiReturn;
export function create_update_message(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
export function validate_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
export function refuse_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
export function evaluate_state(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
export function create_response_prd(process: Process, state_id: string, members_list: OutPointMemberMap): ApiReturn;
export function create_faucet_msg(): string;
export function get_storages(process_outpoint: string): string[];
export function is_child_role(parent_roles: string, child_roles: string): void;
export function decrypt_data(key: Uint8Array, data: Uint8Array): Uint8Array;
export function encode_binary(data: any): Pcd;
export function encode_json(json_data: any): Pcd;
export function decode_value(value: Uint8Array): any;
export function hash_value(value: any, commited_in: string, label: string): string;
/**
* Generate a merkle proof for a specific attribute in a process state.
*
* This function creates a merkle proof that proves the existence of a specific attribute
* in a given state of a process. The proof can be used to verify that the attribute
* was indeed part of the state without revealing the entire state.
*
* # Arguments
* * `process_state` - The process state object as a JavaScript value
* * `attribute_name` - The name of the attribute to generate a proof for
*
* # Returns
* A MerkleProofResult object containing:
* * `proof` - The merkle proof as a hex string
* * `root` - The merkle root (state_id) as a hex string
* * `attribute` - The attribute name that was proven
* * `attribute_index` - The index of the attribute in the merkle tree
* * `total_leaves_count` - The total number of leaves in the merkle tree
*
* # Errors
* * "Failed to deserialize process state" - If the process state cannot be deserialized from JsValue
* * "Attribute not found in state" - If the attribute doesn't exist in the state
*/
export function get_merkle_proof(process_state: any, attribute_name: string): MerkleProofResult;
/**
* Validate a merkle proof for a specific attribute.
*
* This function verifies that a merkle proof is valid and proves the existence
* of a specific attribute in a given state. It checks that the proof correctly
* leads to the claimed root when combined with the attribute hash.
*
* # Arguments
* * `proof_result` - a JsValue expected to contain a MerkleProofResult with the proof and metadata
* * `hash` - The hash of the attribute data as a hex string (the leaf value)
*
* # Returns
* A boolean indicating whether the proof is valid
*
* # Errors
* * "serde_wasm_bindgen deserialization error" - If the proof is not a valid MerkleProofResult
* * "Invalid proof format" - If the proof cannot be parsed
* * "Invalid hash format" - If the hash is not a valid 32-byte hex string
* * "Invalid root format" - If the root is not a valid 32-byte hex string
*/
export function validate_merkle_proof(proof_result: any, hash: string): boolean;
export type DiffStatus = "None" | "Rejected" | "Validated";
export interface UserDiff {
process_id: string;
state_id: string;
value_commitment: string;
field: string;
roles: Roles;
description: string | null;
notify_user: boolean;
need_validation: boolean;
validation_status: DiffStatus;
storages: string[];
}
export interface UpdatedProcess {
process_id: OutPoint;
current_process: Process;
diffs: UserDiff[];
encrypted_data: Record<string, string>;
validated_state: number[] | null;
}
export interface ApiReturn {
secrets: SecretsStore | null;
updated_process: UpdatedProcess | null;
new_tx_to_send: NewTxMessage | null;
ciphers_to_send: string[];
commit_to_send: CommitMessage | null;
push_to_storage: string[];
partial_tx: TsUnsignedTransaction | null;
}
export interface encryptWithNewKeyResult {
cipher: string;
key: string;
}
export interface MerkleProofResult {
proof: string;
root: string;
attribute: string;
attribute_index: number;
total_leaves_count: number;
}
export interface Prd {
prd_type: PrdType;
process_id: OutPoint;
sender: OutPoint | null;
keys: Record<string, number[]>;
pcd_commitments: PcdCommitments;
validation_tokens: Proof[];
roles: Roles;
public_data: Pcd;
payload: string;
proof: Proof | null;
}
export type PrdType = "None" | "Connect" | "Message" | "Update" | "List" | "Response" | "Confirm" | "TxProposal" | "Request";
export interface Device {
sp_wallet: SpWallet;
pairing_process_commitment: OutPoint | null;
paired_member: Member;
}
export type OutPointProcessMap = Record<OutPoint, Process>;
export type OutPointMemberMap = Record<OutPoint, Member>;
export interface HandshakeMessage {
sp_address: string;
peers_list: OutPointMemberMap;
processes_list: OutPointProcessMap;
chain_tip: number;
}
export interface NewTxMessage {
transaction: string;
tweak_data: string | null;
error: AnkError | null;
}
export interface FaucetMessage {
sp_address: string;
commitment: string;
error: AnkError | null;
}
/**
* Message sent to the server to commit some state in a transaction
* Client must first send a commit message with empty validation_tokens
* Relay will ignore a commit message for an update he\'s not aware of that also bears validation_tokens
*/
export interface CommitMessage {
process_id: OutPoint;
pcd_commitment: PcdCommitments;
roles: Roles;
public_data: Pcd;
validation_tokens: Proof[];
error: AnkError | null;
}
export type AnkFlag = "NewTx" | "Faucet" | "Cipher" | "Commit" | "Handshake" | "Sync" | "Unknown";
export type TsUnsignedTransaction = SilentPaymentUnsignedTransaction;
export interface SecretsStore {
shared_secrets: Record<SilentPaymentAddress, AnkSharedSecretHash>;
unconfirmed_secrets: AnkSharedSecretHash[];
}
/**
* A process is basically a succession of states
* The latest state MUST be an empty state with only the commited_in field set at the last unspent outpoint
* Commiting this last empty state in a transaction is called obliterating a process, basically terminating it
*/
export interface Process {
states: ProcessState[];
}
export interface ProcessState {
commited_in: OutPoint;
pcd_commitment: Record<string, string>;
state_id: string;
keys: Record<string, string>;
validation_tokens: Proof[];
public_data: Pcd;
roles: Record<string, RoleDefinition>;
}
export type Roles = Record<string, RoleDefinition>;
export interface RoleDefinition {
members: OutPoint[];
validation_rules: ValidationRule[];
storages: string[];
}
export interface ValidationRule {
quorum: number;
fields: string[];
min_sig_member: number;
}
export type PcdCommitments = Record<string, string>;
export type Pcd = Record<string, number[]>;
export interface Member {
sp_addresses: string[];
}

View File

@ -1,5 +0,0 @@
import * as wasm from "./sdk_client_bg.wasm";
export * from "./sdk_client_bg.js";
import { __wbg_set_wasm } from "./sdk_client_bg.js";
__wbg_set_wasm(wasm);
wasm.__wbindgen_start();

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,73 +0,0 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const setup: () => void;
export const get_address: () => [number, number, number, number];
export const get_member: () => [number, number, number];
export const restore_device: (a: any) => [number, number];
export const create_device_from_sp_wallet: (a: number, b: number) => [number, number, number, number];
export const create_new_device: (a: number, b: number, c: number) => [number, number, number, number];
export const scan_blocks: (a: number, b: number, c: number) => any;
export const is_paired: () => [number, number, number];
export const pair_device: (a: number, b: number, c: number, d: number) => [number, number];
export const unpair_device: () => [number, number];
export const dump_wallet: () => [number, number, number, number];
export const reset_shared_secrets: () => [number, number];
export const set_shared_secrets: (a: number, b: number) => [number, number];
export const get_pairing_process_id: () => [number, number, number, number];
export const dump_device: () => [number, number, number];
export const dump_neutered_device: () => [number, number, number];
export const reset_device: () => [number, number];
export const get_txid: (a: number, b: number) => [number, number, number, number];
export const get_prevouts: (a: number, b: number) => [number, number, number, number];
export const get_opreturn: (a: number, b: number) => [number, number, number, number];
export const process_commit_new_state: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
export const parse_new_tx: (a: number, b: number, c: number, d: any) => [number, number, number];
export const parse_cipher: (a: number, b: number, c: any, d: any) => [number, number, number];
export const get_outputs: () => [number, number, number];
export const get_available_amount: () => [bigint, number, number];
export const create_transaction: (a: number, b: number, c: number) => [number, number, number];
export const sign_transaction: (a: any) => [number, number, number];
export const create_new_process: (a: any, b: any, c: any, d: number, e: number, f: number, g: any) => [number, number, number];
export const update_process: (a: any, b: any, c: any, d: any, e: any) => [number, number, number];
export const request_data: (a: number, b: number, c: number, d: number, e: any, f: any) => [number, number, number];
export const create_update_message: (a: any, b: number, c: number, d: any) => [number, number, number];
export const validate_state: (a: any, b: number, c: number, d: any) => [number, number, number];
export const refuse_state: (a: any, b: number, c: number, d: any) => [number, number, number];
export const evaluate_state: (a: any, b: number, c: number, d: any) => [number, number, number];
export const create_response_prd: (a: any, b: number, c: number, d: any) => [number, number, number];
export const create_faucet_msg: () => [number, number, number, number];
export const get_storages: (a: number, b: number) => [number, number, number, number];
export const is_child_role: (a: number, b: number, c: number, d: number) => [number, number];
export const decrypt_data: (a: number, b: number, c: number, d: number) => [number, number, number, number];
export const encode_binary: (a: any) => [number, number, number];
export const encode_json: (a: any) => [number, number, number];
export const decode_value: (a: number, b: number) => [number, number, number];
export const hash_value: (a: any, b: number, c: number, d: number, e: number) => [number, number, number, number];
export const get_merkle_proof: (a: any, b: number, c: number) => [number, number, number];
export const validate_merkle_proof: (a: any, b: number, c: number) => [number, number, number];
export const rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void;
export const rust_zstd_wasm_shim_malloc: (a: number) => number;
export const rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number;
export const rust_zstd_wasm_shim_calloc: (a: number, b: number) => number;
export const rust_zstd_wasm_shim_free: (a: number) => void;
export const rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number;
export const rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number;
export const rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number;
export const rustsecp256k1_v0_9_2_context_create: (a: number) => number;
export const rustsecp256k1_v0_9_2_context_destroy: (a: number) => void;
export const rustsecp256k1_v0_9_2_default_illegal_callback_fn: (a: number, b: number) => void;
export const rustsecp256k1_v0_9_2_default_error_callback_fn: (a: number, b: number) => void;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_export_4: WebAssembly.Table;
export const __wbindgen_export_5: WebAssembly.Table;
export const __externref_table_dealloc: (a: number) => void;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __externref_drop_slice: (a: number, b: number) => void;
export const wasm_bindgen__convert__closures_____invoke__hc142e2252e76ee8b: (a: number, b: number) => void;
export const closure681_externref_shim: (a: number, b: number, c: any) => void;
export const closure1281_externref_shim: (a: number, b: number, c: any, d: any) => void;
export const __wbindgen_start: () => void;

View File

@ -1,111 +0,0 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Redirection en cours…</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 2rem; color: #0a0a0a; }
.box { max-width: 720px; margin: 10vh auto; padding: 1.5rem; border: 1px solid #e5e7eb; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }
.muted { color: #6b7280; font-size: .95rem; }
.error { color: #b91c1c; }
.ok { color: #065f46; }
code { background: #f3f4f6; padding: .2rem .35rem; border-radius: 6px; }
a { color: #006BE0; text-decoration: none; }
</style>
</head>
<body>
<div class="box">
<h1>Connexion IdNot</h1>
<p class="muted" id="status">Traitement du code d'autorisation…</p>
<pre class="muted" id="details" style="white-space: pre-wrap"></pre>
</div>
<script>
(function () {
function getQueryParam(name) {
const params = new URLSearchParams(location.search);
return params.get(name);
}
function setCookie(name, value, days) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
// Domaine implicite: dev4.4nkweb.com (hébergement de cette page)
document.cookie = name + '=' + encodeURIComponent(value) + '; Path=/; Expires=' + expires + '; SameSite=None; Secure';
}
async function main() {
const code = getQueryParam('code');
const statusEl = document.getElementById('status');
const detailsEl = document.getElementById('details');
if (!code) {
statusEl.textContent = 'Aucun code reçu dans la redirection IdNot.';
statusEl.className = 'error';
detailsEl.textContent = 'Paramètre attendu: ?code=…\nRetour à l\'espace: https://dev4.4nkweb.com/lecoffre/';
return;
}
try {
const resp = await fetch('https://dev4.4nkweb.com/api/v1/idnot/auth', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Request-ID': 'bridge_' + Math.random().toString(36).slice(2)
},
body: JSON.stringify({ code })
});
const text = await resp.text();
let data;
try { data = JSON.parse(text); } catch (_) { data = null; }
if (!resp.ok) {
statusEl.textContent = 'Connexion refusée (' + resp.status + ').';
statusEl.className = 'error';
detailsEl.textContent = (data && data.error && data.error.message) ? data.error.message : text;
// Redirige néanmoins vers le front avec état d\'erreur afin d\'afficher un message utilisateur.
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=' + encodeURIComponent(String(resp.status))); }, 600);
return;
}
// Attendu: { idNotUser, authToken }
if (!data || !data.authToken) {
statusEl.textContent = 'Réponse invalide du serveur.';
statusEl.className = 'error';
detailsEl.textContent = text;
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=invalid_response'); }, 800);
return;
}
// Stocker le jeton pour le domaine dev4 (utilisé par le front)
setCookie('leCoffreAccessToken', data.authToken, 1);
statusEl.textContent = 'Connexion réussie, redirection…';
statusEl.className = 'ok';
detailsEl.textContent = '';
// Redirection vers le front avec token en hash en sauvegarde
location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#token=' + encodeURIComponent(data.authToken));
} catch (e) {
statusEl.textContent = 'Erreur réseau lors de la connexion.';
statusEl.className = 'error';
detailsEl.textContent = String(e && e.message || e);
setTimeout(function(){ location.replace('https://dev4.4nkweb.com/lecoffre/authorized-bridge#error=network'); }, 1000);
}
}
main();
})();
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 B

View File

@ -1,5 +0,0 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" rx="128" fill="white"/>
<path d="M185.067 93.7936C185.647 94.7771 186.831 95.2561 187.94 94.9282L206.362 89.4058C207.345 89.1284 208 88.2206 208 87.2372V52.4634C208 49.9922 206.009 48 203.539 48H51.7297C49.4869 48 48.126 49.5131 48.126 51.7069L48 131.341V203.537C48 206.008 49.9909 208 52.4606 208H204.27C206.513 208 207.874 206.487 207.874 204.268L208 124.659V124.407C208 121.456 207.572 119.414 203.565 120.548L196.332 122.692C195.173 123.045 194.417 124.129 194.467 125.314L194.593 127.987C194.593 159.685 178.087 182.43 148.955 191.357C101.3 205.957 57.8283 168.536 61.6841 122.213C64.3554 90.4145 89.909 64.5925 121.662 61.6674C148.526 59.1962 172.442 72.6872 185.042 93.7431L185.067 93.7936Z" fill="#005BCB"/>
<path d="M153.011 159.962C159.739 155.322 164.931 148.615 167.677 140.772C168.408 138.629 168.988 136.385 169.341 134.09C169.567 132.703 168.257 131.543 166.921 131.972L154.548 135.855C154.019 136.032 153.59 136.435 153.389 136.965C149.482 146.724 139.402 153.305 128.011 151.918C116.898 150.556 108.002 141.327 107.019 130.181C106.691 126.449 107.221 122.869 108.405 119.616C111.807 110.437 120.653 103.88 131.01 103.88C136.857 103.88 142.225 105.973 146.408 109.453C146.887 109.857 147.542 110.008 148.172 109.806L159.916 106.125C160.571 105.923 160.999 105.419 161.176 104.864C161.327 104.284 161.251 103.628 160.823 103.124C153.237 94.0206 141.544 88.4478 128.566 89.2295C128.415 89.2547 128.263 89.2799 128.112 89.2799L124.105 76.5202C123.601 74.8559 121.837 73.9228 120.174 74.4272L111.051 77.2767C109.388 77.8062 108.455 79.5714 108.985 81.2357L111.958 90.7425C112.488 92.4068 111.832 94.2224 110.371 95.1554C103.138 99.7448 97.4679 106.654 94.5194 114.824C93.3098 118.103 92.5538 121.582 92.3017 125.188C92.2261 126.525 92.2009 127.836 92.2513 129.122C92.3269 130.862 91.1929 132.426 89.5297 132.955L79.4997 136.183C77.8365 136.713 76.904 138.478 77.4081 140.142L80.2558 149.27C80.785 150.935 82.5491 151.842 84.2123 151.338L94.4186 148.035C96.0315 147.53 97.8207 148.11 98.7532 149.523C105.709 159.937 117.553 166.821 131.01 166.821C131.59 166.821 132.144 166.821 132.699 166.821C134.463 166.72 136.05 167.855 136.58 169.519L139.679 179.505C140.208 181.17 141.973 182.103 143.636 181.573L152.758 178.724C154.422 178.219 155.329 176.429 154.825 174.765L151.549 164.325C151.045 162.711 151.624 160.946 153.011 159.988V159.962Z" fill="#005BCB"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -77,99 +77,6 @@ body {
position: relative;
}
/* Confirmation Modal Styles */
#confirmation-modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1000;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.modal-confirmation {
text-align: left;
padding: 10px;
}
.modal-confirmation h3 {
margin-bottom: 15px;
color: var(--primary-color);
font-size: 1.1em;
}
.modal-confirmation p {
margin: 8px 0;
font-size: 0.9em;
line-height: 1.4;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.modal-footer button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 0.9em;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-secondary {
background: var(--secondary-color);
color: white;
}
/* Responsive adjustments */
@media only screen and (max-width: 600px) {
.modal-content {
width: 95%;
margin: 10px;
padding: 15px;
}
.modal-confirmation h3 {
font-size: 1em;
}
.modal-confirmation p {
font-size: 0.85em;
}
}
.nav-wrapper {
position: fixed;

View File

@ -427,43 +427,24 @@ body {
/* Style pour la modal de confirmation */
.confirm-delete-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.confirm-delete-content {
background-color: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
max-width: 400px;
width: 90%;
z-index: 1100;
text-align: center;
}
.confirm-delete-content h3 {
margin-top: 0;
color: #333;
}
.confirm-delete-content p {
margin: 15px 0;
color: #666;
}
.confirm-delete-buttons {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.confirm-delete-buttons button {
@ -471,27 +452,25 @@ body {
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.confirm-delete-buttons .confirm-btn {
.confirm-btn {
background-color: #dc3545;
color: white;
}
.confirm-delete-buttons .confirm-btn:hover {
background-color: #c82333;
}
.confirm-delete-buttons .cancel-btn {
.cancel-btn {
background-color: #6c757d;
color: white;
}
.confirm-delete-buttons .cancel-btn:hover {
background-color: #5a6268;
.delete-account-section {
border-top: 1px solid #eee;
padding-top: 15px;
margin-top: 15px;
}
/*-------------------------------------- Export--------------------------------------*/
.export-section {
margin: 20px 0;
@ -1444,64 +1423,3 @@ body {
font-size: 12px;
color: #666;
}
.pairing-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.pairing-modal-content {
background-color: white;
padding: 2rem;
border-radius: 8px;
width: 90%;
max-width: 500px;
}
.pairing-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: bold;
}
.button-group {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1rem;
}
.button-group button {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}
.confirm-button {
background-color: var(--accent-color);
color: white;
border: none;
}
.cancel-button {
background-color: #ccc;
border: none;
}

View File

@ -234,17 +234,17 @@ body {
overflow-wrap: break-word;
word-wrap: break-word;
background-color: #f1f1f1;
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
}
.message-container .message {
align-self: flex-start;
align-self: flex-start;
}
.message-container .message.user {
align-self: flex-end;
margin-left: auto;
align-self: flex-end;
margin-left: auto;
color: white;
}
@ -703,23 +703,23 @@ body {
}
.new-request-btn {
background-color: #4caf50;
color: white;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
margin-left: 10px;
}
.new-request-btn:hover {
background-color: #45a049;
background-color: #45a049;
}
.header-buttons {
display: flex;
align-items: center;
gap: 10px;
align-items: center;
gap: 10px;
}
.close-btn {
@ -762,7 +762,7 @@ body {
width: 100%;
height: 100%;
background-color: white;
z-index: 1000;
z-index: 1000;
padding: 20px;
box-sizing: border-box;
overflow-y: auto;
@ -816,7 +816,7 @@ body {
display: flex;
align-items: center;
gap: 10px;
flex-direction: row;
flex-direction: row;
}
.close-btn {
@ -912,12 +912,12 @@ body {
}
.submit-btn {
background-color: #4caf50;
color: white;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
margin-left: 47%;
margin-top: 20px;
}
@ -1046,8 +1046,8 @@ body {
/* Style pour les labels */
.form-group label {
font-weight: bold;
margin-bottom: 5px;
font-weight: bold;
margin-bottom: 5px;
}
/* Style pour les champs de saisie */
@ -1061,17 +1061,17 @@ body {
}
.add-members-btn {
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
padding: 10px 15px;
cursor: pointer;
margin-top: 10px; /* Espacement au-dessus du bouton */
}
.add-members-btn:hover {
background-color: #45a049;
background-color: #45a049;
}
/* Styles pour l'overlay et la modale */
@ -1137,11 +1137,11 @@ body {
}
.cancel-btn {
background-color: #df2020;
color: white;
background-color: #df2020;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}

Binary file not shown.

View File

@ -9,7 +9,7 @@ let notifications = [];
export async function unpair() {
const service = await Services.getInstance();
await service.unpairDevice();
await navigate('home');
navigate('home');
}
(window as any).unpair = unpair;
@ -28,7 +28,7 @@ function toggleMenu() {
async function getNotifications() {
const service = await Services.getInstance();
notifications = service.getNotifications() || [];
notifications = service.getNotifications();
return notifications;
}
function openCloseNotifications() {
@ -102,8 +102,8 @@ async function setNotification(notifications: any[]): Promise<void> {
async function fetchNotifications() {
const service = await Services.getInstance();
const data = service.getNotifications() || [];
await setNotification(data);
const data = service.getNotifications();
setNotification(data);
}
async function loadUserProfile() {
@ -204,7 +204,7 @@ async function disconnect() {
await Promise.all(registrations.map(registration => registration.unregister()));
console.log('Service worker unregistered');
await navigate('home');
navigate('home');
setTimeout(() => {
window.location.href = window.location.origin;

View File

@ -1,9 +1,9 @@
import ModalService from '../../services/modal.service';
const modalService = await ModalService.getInstance();
// export async function confirm() {
// modalService.confirmPairing();
// }
export async function confirm() {
modalService.confirmPairing();
}
export async function closeConfirmationModal() {
modalService.closeConfirmationModal();

View File

@ -55,7 +55,7 @@ export default class QrScannerComponent extends HTMLElement {
if (spAddress) {
// Call the sendPairingTx function with the extracted sp_address
try {
await prepareAndSendPairingTx();
await prepareAndSendPairingTx(spAddress);
} catch (e) {
console.error('Failed to pair:', e);
}

View File

@ -1,42 +0,0 @@
<div id="validation-rule-modal" style="
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 9999;
">
<div style="
background: white;
padding: 2rem;
border-radius: 0.5rem;
width: 400px;
max-width: 90%;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
">
<h2 style="font-size: 1.2rem; font-weight: bold; margin-bottom: 1rem;">
Add Validation Rule
</h2>
<label style="display: block; margin-bottom: 0.5rem;">
Quorum:
<input id="vr-quorum" type="number" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
</label>
<label style="display: block; margin-bottom: 0.5rem;">
Min Sig Member:
<input id="vr-minsig" type="number" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
</label>
<label style="display: block; margin-bottom: 1rem;">
Fields (comma-separated):
<input id="vr-fields" type="text" placeholder="e.g. field1, field2" style="width: 100%; padding: 0.5rem; margin-top: 0.25rem;" />
</label>
<div style="display: flex; justify-content: flex-end; gap: 1rem;">
<button id="vr-cancel" style="padding: 0.5rem 1rem;">Cancel</button>
<button id="vr-submit" style="padding: 0.5rem 1rem; background-color: #4f46e5; color: white; border: none; border-radius: 0.375rem;">Add</button>
</div>
</div>
</div>

View File

@ -1,61 +0,0 @@
export interface ValidationRule {
quorum: number;
fields: string[];
min_sig_member: number;
}
/**
* Loads and injects the modal HTML into the document if not already loaded.
*/
export async function loadValidationRuleModal(templatePath: string = '/src/components/validation-rule-modal/validation-rule-modal.html') {
if (document.getElementById('validation-rule-modal')) return;
const res = await fetch(templatePath);
const html = await res.text();
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const modal = tempDiv.querySelector('#validation-rule-modal');
if (!modal) {
throw new Error('Modal HTML missing #validation-rule-modal');
}
document.body.appendChild(modal);
}
/**
* Opens the modal and lets the user input a ValidationRule.
* Calls the callback with the constructed rule on submit.
*/
export function showValidationRuleModal(onSubmit: (rule: ValidationRule) => void) {
const modal = document.getElementById('validation-rule-modal')!;
const quorumInput = document.getElementById('vr-quorum') as HTMLInputElement;
const minsigInput = document.getElementById('vr-minsig') as HTMLInputElement;
const fieldsInput = document.getElementById('vr-fields') as HTMLInputElement;
const cancelBtn = document.getElementById('vr-cancel')!;
const submitBtn = document.getElementById('vr-submit')!;
// Reset values
quorumInput.value = '';
minsigInput.value = '';
fieldsInput.value = '';
modal.style.display = 'flex';
cancelBtn.onclick = () => {
modal.style.display = 'none';
};
submitBtn.onclick = () => {
const rule: ValidationRule = {
quorum: parseInt(quorumInput.value),
min_sig_member: parseInt(minsigInput.value),
fields: fieldsInput.value.split(',').map(f => f.trim()).filter(Boolean),
};
modal.style.display = 'none';
onSubmit(rule);
};
}

View File

@ -1,3 +1,39 @@
export { default as Services } from './services/service';
export { default as Database } from './services/database.service';
export { MessageType } from './models/process.model';
// import Services from './services/service';
// document.addEventListener('DOMContentLoaded', async () => {
// try {
// const services = await Services.getInstance();
// setTimeout( async () => {
// let device = await services.getDevice()
// console.log("🚀 ~ setTimeout ~ device:", device)
// if(!device) {
// device = await services.createNewDevice();
// } else {
// await services.restoreDevice(device)
// }
// await services.restoreProcesses();
// await services.restoreMessages();
// const amount = await services.getAmount();
// if (amount === 0n) {
// const faucetMsg = await services.createFaucetMessage();
// await services.sendFaucetMessage(faucetMsg);
// }
// if (services.isPaired()) { await services.injectProcessListPage() }
// else {
// const queryString = window.location.search;
// const urlParams = new URLSearchParams(queryString)
// const pairingAddress = urlParams.get('sp_address')
// if(pairingAddress) {
// setTimeout(async () => await services.sendPairingTx(pairingAddress), 2000)
// }
// }
// }, 500);
// } catch (error) {
// console.error(error);
// }
// });

View File

@ -1,18 +1,18 @@
import { SignatureComponent } from './pages/signature/signature-component';
import { SignatureElement } from './pages/signature/signature';
/*import { ChatComponent } from './pages/chat/chat-component';
import { ChatElement } from './pages/chat/chat';*/
import { ChatComponent } from './pages/chat/chat-component';
import { ChatElement } from './pages/chat/chat';
import { AccountComponent } from './pages/account/account-component';
import { AccountElement } from './pages/account/account';
export { SignatureComponent, SignatureElement, AccountComponent, AccountElement };
export { SignatureComponent, SignatureElement, ChatComponent, ChatElement, AccountComponent, AccountElement };
declare global {
interface HTMLElementTagNameMap {
'signature-component': SignatureComponent;
'signature-element': SignatureElement;
/*'chat-component': ChatComponent;
'chat-element': ChatElement;*/
'chat-component': ChatComponent;
'chat-element': ChatElement;
'account-component': AccountComponent;
'account-element': AccountElement;
}
@ -23,8 +23,8 @@ if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB) {
// Initialiser les composants si nécessaire
customElements.define('signature-component', SignatureComponent);
customElements.define('signature-element', SignatureElement);
/*customElements.define('chat-component', ChatComponent);
customElements.define('chat-element', ChatElement);*/
customElements.define('chat-component', ChatComponent);
customElements.define('chat-element', ChatElement);
customElements.define('account-component', AccountComponent);
customElements.define('account-element', AccountElement);
}

View File

@ -1,4 +1,4 @@
import { Device, Process, SecretsStore } from "../../pkg/sdk_client.js";
import { Device, Process, SecretsStore } from "pkg/sdk_client";
export interface BackUp {
device: Device,

View File

@ -21,45 +21,3 @@ export interface INotification {
sendToNotificationPage?: boolean;
path?: string;
}
export enum MessageType {
// Establish connection and keep alive
LISTENING = 'LISTENING',
REQUEST_LINK = 'REQUEST_LINK',
LINK_ACCEPTED = 'LINK_ACCEPTED',
CREATE_PAIRING = 'CREATE_PAIRING',
PAIRING_CREATED = 'PAIRING_CREATED',
ERROR = 'ERROR',
VALIDATE_TOKEN = 'VALIDATE_TOKEN',
RENEW_TOKEN = 'RENEW_TOKEN',
// Get various information
GET_PAIRING_ID = 'GET_PAIRING_ID',
GET_PROCESSES = 'GET_PROCESSES',
GET_MY_PROCESSES = 'GET_MY_PROCESSES',
PROCESSES_RETRIEVED = 'PROCESSES_RETRIEVED',
RETRIEVE_DATA = 'RETRIEVE_DATA',
DATA_RETRIEVED = 'DATA_RETRIEVED',
DECODE_PUBLIC_DATA = 'DECODE_PUBLIC_DATA',
PUBLIC_DATA_DECODED = 'PUBLIC_DATA_DECODED',
GET_MEMBER_ADDRESSES = 'GET_MEMBER_ADDRESSES',
MEMBER_ADDRESSES_RETRIEVED = 'MEMBER_ADDRESSES_RETRIEVED',
// Processes
CREATE_PROCESS = 'CREATE_PROCESS',
PROCESS_CREATED = 'PROCESS_CREATED',
UPDATE_PROCESS = 'UPDATE_PROCESS',
PROCESS_UPDATED = 'PROCESS_UPDATED',
NOTIFY_UPDATE = 'NOTIFY_UPDATE',
UPDATE_NOTIFIED = 'UPDATE_NOTIFIED',
VALIDATE_STATE = 'VALIDATE_STATE',
STATE_VALIDATED = 'STATE_VALIDATED',
// Hash and merkle proof
HASH_VALUE = 'HASH_VALUE',
VALUE_HASHED = 'VALUE_HASHED',
GET_MERKLE_PROOF = 'GET_MERKLE_PROOF',
MERKLE_PROOF_RETRIEVED = 'MERKLE_PROOF_RETRIEVED',
VALIDATE_MERKLE_PROOF = 'VALIDATE_MERKLE_PROOF',
MERKLE_PROOF_VALIDATED = 'MERKLE_PROOF_VALIDATED',
// Account management
ADD_DEVICE = 'ADD_DEVICE',
DEVICE_ADDED = 'DEVICE_ADDED',
}

View File

@ -20,25 +20,23 @@ declare global {
updateNavbarBanner: (bannerUrl: string) => void;
saveBannerToLocalStorage: (bannerUrl: string) => void;
loadSavedBanner: () => void;
cancelAddRowPairing: () => void;
cancelAddRow: () => void;
saveName: (cell: HTMLElement, input: HTMLInputElement) => void;
showProcessNotifications: (processName: string) => void;
handleLogout: () => void;
initializeEventListeners: () => void;
showProcess: () => void;
showProcessCreation: () => void;
showDocumentValidation: () => void;
updateNavbarName: (name: string) => void;
updateNavbarLastName: (lastName: string) => void;
showAlert: (title: string, text?: string, icon?: string) => void;
addRowPairing: () => void;
confirmRowPairing: () => void;
cancelRowPairing: () => void;
deleteRowPairing: (button: HTMLButtonElement) => void;
addRow: () => void;
confirmRow: () => void;
cancelRow: () => void;
deleteRow: (button: HTMLButtonElement) => void;
generateRecoveryWords: () => string[];
exportUserData: () => void;
updateActionButtons: () => void;
showQRCodeModal: (pairingId: string) => void;
showQRCodeModal: (address: string) => void;
}
}
@ -49,31 +47,11 @@ import { addressToEmoji } from '../../utils/sp-address.utils';
import { getCorrectDOM } from '../../utils/document.utils';
import accountStyle from '../../../public/style/account.css?inline';
import Services from '../../services/service';
import { getProcessCreation } from './process-creation';
import { getDocumentValidation } from './document-validation';
import { createProcessTab } from './process';
let isAddingRow = false;
let currentRow: HTMLTableRowElement | null = null;
let currentMode: keyof typeof STORAGE_KEYS = 'pairing';
interface Process {
states: Array<{
committed_in: string;
keys: {};
pcd_commitment: {
counter: string;
};
public_data: {
memberPublicName?: string;
};
roles: {
pairing?: {};
};
state_id: string;
validation_tokens: Array<any>;
}>;
}
class AccountElement extends HTMLElement {
private dom: Node;
@ -163,7 +141,7 @@ class AccountElement extends HTMLElement {
<!-- User Info Section -->
<div class="popup-info">
<p><strong>Name:</strong> <span class="editable" id="popup-name"></span></p>
<!--<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>-->
<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>
<p><strong>Address:</strong> 🏠 🌍 🗽🎊😩-🎊😑🎄😩</p>
</div>
@ -176,33 +154,27 @@ class AccountElement extends HTMLElement {
</ul>
<ul class="parameter-list-ul" onclick="window.showPairing()">Pairing 🔗</ul>
<!-- <ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul> -->
<ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul>
<ul class="parameter-list-ul" onclick="window.showProcess()">Process </ul>
<ul class="parameter-list-ul" onclick="window.showProcessCreation()">Process Creation</ul>
<ul class="parameter-list-ul" onclick="window.showDocumentValidation()">Document Validation</ul>
<!-- <ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul> -->
<ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul>
</div>
<!-- Parameter Area -->
<div class="parameter-area">
<div class="content-container">
<div id="pairing-content"></div>
<!-- <div id="wallet-content"></div> -->
<div id="wallet-content"></div>
<div id="process-content"></div>
<div id="process-creation-content"></div>
<div id="document-validation-content"></div>
<!-- <div id="data-content"></div> -->
<div id="data-content"></div>
</div>
</div>
</div>
`;
window.showPairing = () => this.showPairing();
window.showWallet = () => this.showWallet();
window.showProcess = () => this.showProcess();
window.showProcessCreation = () => this.showProcessCreation();
window.showDocumentValidation = () => this.showDocumentValidation();
window.showData = () => this.showData();
window.addWalletRow = () => this.addWalletRow();
window.confirmWalletRow = () => this.confirmWalletRow();
@ -212,10 +184,10 @@ class AccountElement extends HTMLElement {
window.handleLogout = () => this.handleLogout();
window.confirmDeleteAccount = () => this.confirmDeleteAccount();
window.showContractPopup = (contractId: string) => this.showContractPopup(contractId);
window.addRowPairing = () => this.addRowPairing();
window.deleteRowPairing = (button: HTMLButtonElement) => this.deleteRowPairing(button);
window.confirmRowPairing = () => this.confirmRowPairing();
window.cancelRowPairing = () => this.cancelRowPairing();
window.addRow = () => this.addRow();
window.deleteRow = (button: HTMLButtonElement) => this.deleteRow(button);
window.confirmRow = () => this.confirmRow();
window.cancelRow = () => this.cancelRow();
window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl);
window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl);
window.loadSavedBanner = () => this.loadSavedBanner();
@ -227,7 +199,7 @@ class AccountElement extends HTMLElement {
window.updateActionButtons = () => this.updateActionButtons();
window.openAvatarPopup = () => this.openAvatarPopup();
window.closeAvatarPopup = () => this.closeAvatarPopup();
window.showQRCodeModal = (pairingId: string) => this.showQRCodeModal(pairingId);
window.showQRCodeModal = (address: string) => this.showQRCodeModal(address);
if (!localStorage.getItem('rows')) {
localStorage.setItem('rows', JSON.stringify(defaultRows));
@ -238,22 +210,22 @@ class AccountElement extends HTMLElement {
this.initializeEventListeners();
this.loadSavedBanner();
this.loadUserInfo();
const savedAvatar = localStorage.getItem('userAvatar');
const savedBanner = localStorage.getItem('userBanner');
const savedName = localStorage.getItem('userName');
const savedLastName = localStorage.getItem('userLastName');
if (savedAvatar) {
const navAvatar = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement;
if (navAvatar) navAvatar.src = savedAvatar;
}
if (savedBanner) {
const navBanner = this.shadowRoot?.querySelector('.banner-image') as HTMLImageElement;
if (navBanner) navBanner.src = savedBanner;
}
if (savedName) {
this.updateNavbarName(savedName);
}
@ -270,17 +242,17 @@ class AccountElement extends HTMLElement {
alertPopup.className = 'alert-popup';
this.shadowRoot?.appendChild(alertPopup);
}
// Définir le message et afficher la popup
alertPopup.textContent = message;
(alertPopup as HTMLElement).style.display = 'block';
// Cacher la popup après 3 secondes
setTimeout(() => {
(alertPopup as HTMLElement).style.display = 'none';
}, 3000);
}
// Fonctions de gestion des comptes et de l'interface utilisateur
private confirmDeleteAccount(): void {
@ -321,13 +293,13 @@ private updateNavbarBanner(imageUrl: string): void {
if (!navbarSection) return;
let bannerImg = navbarSection.querySelector<HTMLImageElement>('.banner-image');
if (!bannerImg) {
bannerImg = document.createElement('img');
bannerImg.className = 'banner-image';
navbarSection.insertBefore(bannerImg, navbarSection.firstChild);
}
bannerImg.src = imageUrl;
}
@ -368,7 +340,7 @@ private markAsRead(processName: string, messageId: number, element: HTMLElement)
element.classList.remove('unread');
element.classList.add('read');
const statusIcon = element.querySelector('.notification-status');
if (statusIcon) {
statusIcon.innerHTML = `
@ -381,7 +353,7 @@ private markAsRead(processName: string, messageId: number, element: HTMLElement)
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
if (countElement) {
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
const bellContainer = countElement.closest('.notification-container');
const bell = bellContainer?.querySelector('svg'); // Changé de .fa-bell à svg
if (bell && bellContainer && notifCount.unread === 0) {
@ -418,7 +390,7 @@ private exportRecovery(): void {
if (result.isConfirmed) {
const recoveryWords = this.generateRecoveryWords();
localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords));
Swal.fire({
title: 'Your Recovery Words',
html: `
@ -447,7 +419,7 @@ private exportRecovery(): void {
if (result.isConfirmed) {
// Stocker l'état du bouton dans le localStorage
localStorage.setItem('recoveryExported', 'true');
const exportRecoveryBtn = this.shadowRoot?.querySelector('.recovery-btn') as HTMLButtonElement;
if (exportRecoveryBtn) {
exportRecoveryBtn.disabled = true;
@ -516,7 +488,7 @@ private getConfirmFunction(): string {
case 'process':
return 'window.confirmProcessRow()';
default:
return 'window.confirmRowPairing()';
return 'window.confirmRow()';
}
}
@ -527,94 +499,50 @@ private getCancelFunction(): string {
case 'process':
return 'window.cancelProcessRow()';
default:
return 'window.cancelRowPairing()';
return 'window.cancelRow()';
}
}
// Fonctions de gestion des tableaux
private async addRowPairing(): Promise<void> {
private addRow(): void {
if (isAddingRow) return;
isAddingRow = true;
const table = this.shadowRoot?.querySelector<HTMLTableElement>('#pairing-table tbody');
if (!table) return;
// Créer la popup
const modal = document.createElement('div');
modal.className = 'pairing-modal';
modal.innerHTML = `
<div class="pairing-modal-content">
<h3>Add New Device</h3>
<div class="pairing-form">
<div class="form-group">
<label for="sp-address">SP Address</label>
<input type="text" id="sp-address" class="edit-input" placeholder="Enter SP Address">
</div>
<div class="form-group">
<label for="device-name">Device Name</label>
<input type="text" id="device-name" class="edit-input" placeholder="Enter Device Name">
</div>
<div class="form-group">
<label for="sp-emojis">SP Emojis</label>
<input type="text" id="sp-emojis" class="edit-input" readonly>
</div>
<div class="button-group">
<button class="confirm-button">Confirm</button>
<button class="cancel-button">Cancel</button>
</div>
</div>
</div>
`;
this.shadowRoot?.appendChild(modal);
// Ajouter les event listeners
const spAddressInput = modal.querySelector('#sp-address') as HTMLInputElement;
const spEmojisInput = modal.querySelector('#sp-emojis') as HTMLInputElement;
const deviceNameInput = modal.querySelector('#device-name') as HTMLInputElement;
const confirmButton = modal.querySelector('.confirm-button');
const cancelButton = modal.querySelector('.cancel-button');
// Mettre à jour les emojis automatiquement
spAddressInput?.addEventListener('input', async () => {
const emojis = await addressToEmoji(spAddressInput.value);
if (spEmojisInput) spEmojisInput.value = emojis;
});
// Gérer la confirmation
confirmButton?.addEventListener('click', () => {
const spAddress = spAddressInput?.value.trim();
const deviceName = deviceNameInput?.value.trim();
const spEmojis = spEmojisInput?.value.trim();
if (!spAddress || !deviceName) {
this.showAlert('Please fill in all required fields');
return;
currentRow = table.insertRow();
const cells = ['SP Address', 'Device Name', 'SP Emojis'];
cells.forEach((_, index) => {
const cell = currentRow!.insertCell();
const input = document.createElement('input');
input.type = 'text';
input.className = 'edit-input';
// Ajouter un événement pour mettre à jour automatiquement les emojis
if (index === 0) {
input.addEventListener('input', async (e) => {
const addressInput = e.target as HTMLInputElement;
const emojiCell = currentRow!.cells[2];
const emojis = await addressToEmoji(addressInput.value);
if (emojiCell.querySelector('input')) {
(emojiCell.querySelector('input') as HTMLInputElement).value = emojis;
}
});
}
//if (spAddress.length !== 118) {
// this.showAlert('SP Address must be exactly 118 characters long');
// return;
//}
const newRow: Row = {
column1: spAddress,
column2: deviceName,
column3: spEmojis || ''
};
const storageKey = STORAGE_KEYS[currentMode];
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
rows.push(newRow);
localStorage.setItem(storageKey, JSON.stringify(rows));
this.updateTableContent(rows);
modal.remove();
isAddingRow = false;
if (index === 2) {
input.readOnly = true;
}
cell.appendChild(input);
});
// Gérer l'annulation
cancelButton?.addEventListener('click', () => {
modal.remove();
isAddingRow = false;
});
const deleteCell = currentRow.insertCell();
deleteCell.style.width = '40px';
this.updateActionButtons();
}
// Fonctions de mise à jour de l'interface
@ -624,17 +552,18 @@ private updateTableContent(rows: Row[]): void {
tbody.innerHTML = rows.map(row => `
<tr>
<td>${row.column1}</td>
<td class="device-name" onclick="window.editDeviceName(this)">${row.column2}</td>
<td>${row.column3}</td>
<td>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
alt="QR Code"
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
alt="QR Code"
title="${row.column1}"
class="qr-code"
onclick="window.showQRCodeModal('${encodeURIComponent(row.column1)}')">
</td>
<td>
<button class="delete-button" onclick="window.deleteRowPairing(this)">
<button class="delete-button" onclick="window.deleteRow(this)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="red">
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
</svg>
@ -646,7 +575,7 @@ private updateTableContent(rows: Row[]): void {
private confirmRowPairing(): void {
private confirmRow(): void {
if (!currentRow) return;
const inputs = currentRow.getElementsByTagName('input');
@ -677,18 +606,18 @@ private confirmRowPairing(): void {
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
this.updateTableContent(rows);
}
private cancelRowPairing(): void {
private cancelRow(): void {
if (!currentRow) return;
currentRow.remove();
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
}
@ -697,11 +626,11 @@ private resetButtonContainer(): void {
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a line</button>
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
`;
}
private deleteRowPairing(button: HTMLButtonElement): void {
private deleteRow(button: HTMLButtonElement): void {
const row = button.closest('tr');
if (!row) return;
@ -714,53 +643,21 @@ private deleteRowPairing(button: HTMLButtonElement): void {
return;
}
// Créer la modal de confirmation
const modal = document.createElement('div');
modal.className = 'confirm-delete-modal';
modal.innerHTML = `
<div class="confirm-delete-content">
<h3>Confirm Deletion</h3>
<p>Are you sure you want to delete this device?</p>
<div class="confirm-delete-buttons">
<button class="cancel-btn">Cancel</button>
<button class="confirm-btn">Delete</button>
</div>
</div>
`;
const index = Array.from(table.children).indexOf(row);
row.style.transition = 'opacity 0.3s, transform 0.3s';
row.style.opacity = '0';
row.style.transform = 'translateX(-100%)';
this.shadowRoot?.appendChild(modal);
setTimeout(() => {
row.remove();
// Gérer les boutons de la modal
const confirmBtn = modal.querySelector('.confirm-btn');
const cancelBtn = modal.querySelector('.cancel-btn');
confirmBtn?.addEventListener('click', () => {
// Calculer l'index AVANT de supprimer la ligne du DOM
const index = Array.from(table.children).indexOf(row);
const storageKey = STORAGE_KEYS[currentMode];
const rows = JSON.parse(localStorage.getItem(storageKey) || '[]');
// Supprimer du localStorage
if (index > -1) {
rows.splice(index, 1);
localStorage.setItem(storageKey, JSON.stringify(rows));
}
// Animation et suppression du DOM
row.style.transition = 'opacity 0.3s, transform 0.3s';
row.style.opacity = '0';
row.style.transform = 'translateX(-100%)';
setTimeout(() => {
row.remove();
}, 300);
modal.remove();
});
cancelBtn?.addEventListener('click', () => {
modal.remove();
});
}, 300);
}
private editDeviceName(cell: HTMLTableCellElement): void {
@ -771,7 +668,7 @@ private editDeviceName(cell: HTMLTableCellElement): void {
input.type = 'text';
input.value = currentValue;
input.className = 'edit-input';
input.addEventListener('blur', () => this.finishEditing(cell, input));
input.addEventListener('keypress', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
@ -785,7 +682,7 @@ private editDeviceName(cell: HTMLTableCellElement): void {
input.focus();
}
private async finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): Promise<void> {
private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void {
const newValue = input.value.trim();
if (newValue === '') {
cell.textContent = cell.getAttribute('data-original-value') || '';
@ -793,103 +690,102 @@ private async finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement)
return;
}
try {
const service = await Services.getInstance();
const pairingProcessId = service.getPairingProcessId();
const process = await service.getProcess(pairingProcessId);
if (!process) throw new Error('Pairing process not found');
// Mettre à jour le nom via le service
await service.updateMemberPublicName(process, newValue);
const row = cell.closest('tr');
if (!row) return;
// Mettre à jour l'interface
cell.textContent = newValue;
cell.classList.remove('editing');
} catch (error) {
console.error('Failed to update name:', error);
// Restaurer l'ancienne valeur en cas d'erreur
cell.textContent = cell.getAttribute('data-original-value') || '';
cell.classList.remove('editing');
const table = row.closest('tbody');
if (!table) return;
const index = Array.from(table.children).indexOf(row);
const storageKey = STORAGE_KEYS[currentMode];
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
if (rows[index]) {
rows[index].column2 = newValue;
localStorage.setItem(storageKey, JSON.stringify(rows));
}
}
cell.textContent = newValue;
cell.classList.remove('editing');
}
// Fonction pour gérer le téléchargement de l'avatar
private handleAvatarUpload(event: Event): void {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const result = e.target?.result as string;
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
const navAvatar = this.shadowRoot?.querySelector('.nav-wrapper .avatar') as HTMLImageElement;
if (popupAvatar) popupAvatar.src = result;
if (navAvatar) navAvatar.src = result;
localStorage.setItem('userAvatar', result);
};
reader.readAsDataURL(file);
}
}
private async showProcessCreation(): Promise<void> {
private showProcess(): void {
//console.log("showProcess called");
currentMode = 'process';
this.hideAllContent();
const container = this.shadowRoot?.getElementById('process-creation-content');
if (container) {
getProcessCreation(container);
const headerTitle = this.shadowRoot?.getElementById('header-title');
if (headerTitle) headerTitle.textContent = 'Process';
const processContent = this.shadowRoot?.getElementById('process-content');
if (processContent) {
processContent.style.display = 'block';
processContent.innerHTML = `
<div class="parameter-header" id="parameter-header">Process</div>
<div class="table-container">
<table class="parameter-table" id="process-table">
<thead>
<tr>
<th>Process Name</th>
<th>Role</th>
<th>Notifications</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
`;
this.updateProcessTableContent(mockProcessRows);
}
}
private async showDocumentValidation(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('document-validation-content');
if (container) {
getDocumentValidation(container);
}
// Fonction utilitaire pour mettre à jour le contenu du tableau Process
private updateProcessTableContent(rows: any[]): void {
const tbody = this.shadowRoot?.querySelector('#process-table tbody');
if (!tbody) return;
tbody.innerHTML = rows.map(row => `
<tr>
<td>${row.process}</td>
<td>${row.role}</td>
<td>
<div class="notification-container" onclick="window.showProcessNotifications('${row.process}')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16">
<path d="M224 0c-17.7 0-32 14.3-32 32V51.2C119 66 64 130.6 64 208v18.8c0 47-17.3 92.4-48.5 127.6l-7.4 8.3c-8.4 9.4-10.4 22.9-5.3 34.4S19.4 416 32 416H416c12.6 0 24-7.4 29.2-18.9s3.1-25-5.3-34.4l-7.4-8.3C401.3 319.2 384 273.9 384 226.8V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32zm45.3 493.3c12-12 18.7-28.3 18.7-45.3H160c0 17 6.7 33.3 18.7 45.3s28.3 18.7 45.3 18.7s33.3-6.7 45.3-18.7z"/>
</svg>
<span class="notification-count" data-process="${row.process}">
${row.notification?.messages?.filter((m: any) => !m.read).length || 0}/${row.notification?.messages?.length || 0}
</span>
</div>
</td>
</tr>
`).join('');
}
private async showProcess(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('process-content');
if (container) {
const service = await Services.getInstance();
const myProcesses = await service.getMyProcesses();
if (myProcesses && myProcesses.length != 0) {
const myProcessesDataUnfiltered = await Promise.all(myProcesses.map(async processId => {
const process = await service.getProcess(processId);
if (!process) return undefined;
const lastState = service.getLastCommitedState(process);
if (!lastState) {
return {
name: '',
publicData: {}
};
}
const description = await service.decryptAttribute(processId, lastState, 'description');
const name = description ? description : 'N/A';
const publicData = await service.getPublicData(process);
if (!publicData) {
return {
name: '',
publicData: {}
};
}
return {
name: name,
publicData: publicData
};
}));
const myProcessesData = (myProcessesDataUnfiltered.filter(
(p) => p && p.name !== '' && Object.keys(p.publicData).length != 0
)) as { name: string, publicData: Record<string, any> }[];
createProcessTab(container, myProcessesData);
} else {
createProcessTab(container, []);
}
}
}
private showProcessNotifications(processName: string): void {
const process = mockProcessRows.find(p => p.process === processName);
@ -897,15 +793,15 @@ private showProcessNotifications(processName: string): void {
const modal = document.createElement('div');
modal.className = 'notifications-modal';
let notificationsList = process.notification.messages.map(msg => `
<div class="notification-item ${msg.read ? 'read' : 'unread'}"
<div class="notification-item ${msg.read ? 'read' : 'unread'}"
onclick="window.markAsRead('${processName}', ${msg.id}, this)">
<div class="notification-status">
${msg.read ?
${msg.read ?
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
</svg>` :
</svg>` :
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16" fill="black">
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"/>
</svg>`
@ -956,11 +852,9 @@ private handleLogout(): void {
// Fonctions de gestion des contrats
private showContractPopup(contractId: string, event?: Event) {
if (event) {
event.preventDefault();
}
private showContractPopup(contractId: string) {
// Empêcher la navigation par défaut
event?.preventDefault();
// Check if the contract exists in mockContracts
const contract = mockContracts[contractId as keyof typeof mockContracts];
if (!contract) {
@ -968,6 +862,7 @@ private showContractPopup(contractId: string, event?: Event) {
return;
}
// Créer la popup
const popup = document.createElement('div');
popup.className = 'contract-popup-overlay';
popup.innerHTML = `
@ -986,11 +881,13 @@ private showContractPopup(contractId: string, event?: Event) {
</div>
`;
// Ajouter la popup au body
this.shadowRoot?.appendChild(popup);
// Gérer la fermeture
const closeBtn = popup.querySelector('.close-contract-popup');
const closePopup = () => popup.remove();
closeBtn?.addEventListener('click', closePopup);
popup.addEventListener('click', (e) => {
if (e.target === popup) closePopup();
@ -999,7 +896,7 @@ private showContractPopup(contractId: string, event?: Event) {
// Fonction utilitaire pour cacher tous les contenus
private hideAllContent(): void {
const contents = ['pairing-content', 'wallet-content', 'process-content', 'process-creation-content', 'data-content', 'document-validation-content'];
const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content'];
contents.forEach(id => {
const element = this.shadowRoot?.getElementById(id);
if (element) {
@ -1012,13 +909,13 @@ private hideAllContent(): void {
private async showPairing(): Promise<void> {
const service = await Services.getInstance();
const spAddress = await service.getDeviceAddress();
isAddingRow = false;
currentRow = null;
currentMode = 'pairing';
this.hideAllContent();
const headerElement = this.shadowRoot?.getElementById('parameter-header');
if (headerElement) {
headerElement.textContent = 'Pairing';
@ -1033,6 +930,7 @@ private async showPairing(): Promise<void> {
<table class="parameter-table" id="pairing-table">
<thead>
<tr>
<th>SP Address</th>
<th>Device Name</th>
<th>SP Emojis</th>
<th>QR Code</th>
@ -1042,29 +940,30 @@ private async showPairing(): Promise<void> {
<tbody></tbody>
</table>
<div class="button-container">
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a device</button>
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
</div>
</div>
`;
let rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]');
const deviceExists = rows.some((row: Row) => row.column1 === spAddress);
if (!deviceExists && spAddress) {
const emojis = await addressToEmoji(spAddress);
try {
// Déboguer le processus de pairing
const pairingProcessId = await service.getPairingProcessId();
console.log('Pairing Process ID:', pairingProcessId);
const pairingProcess = await service.getProcess(pairingProcessId);
console.log('Pairing Process:', pairingProcess);
const userName = pairingProcess?.states?.[0]?.public_data?.memberPublicName
const userName = pairingProcess?.states?.[0]?.metadata?.userName
|| pairingProcess?.states?.[0]?.metadata?.name
|| localStorage.getItem('userName')
console.log('Username found:', userName);
const newRow = {
@ -1085,7 +984,7 @@ private async showPairing(): Promise<void> {
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
}
}
this.updateTableContent(rows);
}
}
@ -1096,11 +995,11 @@ private showWallet(): void {
currentMode = 'wallet';
this.hideAllContent();
// Mettre à jour le titre
const headerTitle = this.shadowRoot?.getElementById('header-title');
if (headerTitle) headerTitle.textContent = 'Wallet';
const walletContent = this.shadowRoot?.getElementById('wallet-content');
if (!walletContent) return;
walletContent.style.display = 'block';
@ -1122,7 +1021,7 @@ private showWallet(): void {
</div>
</div>
`;
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]');
this.updateWalletTableContent(rows);
}
@ -1145,10 +1044,10 @@ private showData(): void {
//console.log("showData called");
currentMode = 'data';
this.hideAllContent();
const headerTitle = this.shadowRoot?.getElementById('header-title');
if (headerTitle) headerTitle.textContent = 'Data';
const dataContent = this.shadowRoot?.getElementById('data-content');
if (dataContent) {
dataContent.style.display = 'block';
@ -1170,7 +1069,7 @@ private showData(): void {
</table>
</div>
`;
const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]');
this.updateDataTableContent(rows);
}
@ -1199,7 +1098,7 @@ private addWalletRow(): void {
// Remplacer le bouton "Add a line" par les boutons de confirmation/annulation
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<div class="action-buttons-wrapper">
<button onclick="confirmWalletRow()" class="action-button confirm-button"></button>
@ -1237,20 +1136,20 @@ private confirmWalletRow(): void {
private cancelWalletRow(): void {
if (!currentRow) return;
currentRow.remove();
isAddingRow = false;
currentRow = null;
// Réinitialiser le conteneur de boutons avec le bouton "Add a line"
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<button class="add-row-button button-style" onclick="window.addWalletRow()">Add a line</button>
`;
}
private updateDataTableContent(rows: DataRow[]): void {
@ -1310,16 +1209,16 @@ private openAvatarPopup(): void {
<strong>Name:</strong>
<input type="text" id="userName" value="${savedName}" class="editable">
</div>
<!--<div class="info-row">
<div class="info-row">
<strong>Last Name:</strong>
<input type="text" id="userLastName" value="${savedLastName}" class="editable">
</div>-->
</div>
<div class="info-row">
<strong>Address:</strong>
<span>${savedAddress}</span>
</div>
</div>
<div class="popup-button-container">
<div class="action-buttons-row">
<button class="export-btn" onclick="window.exportUserData()">Export User Data</button>
@ -1337,7 +1236,7 @@ private openAvatarPopup(): void {
// Ajouter le gestionnaire d'événements pour la bannière
const bannerImg = popup.querySelector('#popup-banner-img');
const bannerInput = popup.querySelector('#banner-upload') as HTMLInputElement;
if (bannerImg && bannerInput) {
bannerImg.addEventListener('click', () => {
bannerInput.click();
@ -1376,10 +1275,10 @@ private setupEventListeners(popup: HTMLElement): void {
// Mise à jour de l'avatar dans la preview et le popup
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
if (popupAvatar) popupAvatar.src = result;
if (previewAvatar) previewAvatar.src = result;
localStorage.setItem('userAvatar', result);
};
reader.readAsDataURL(file);
@ -1399,10 +1298,10 @@ private setupEventListeners(popup: HTMLElement): void {
// Mise à jour de la bannière dans la preview et le popup
const popupBanner = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement;
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
if (popupBanner) popupBanner.src = result;
if (previewBanner) previewBanner.src = result;
localStorage.setItem('userBanner', result);
};
reader.readAsDataURL(file);
@ -1455,7 +1354,7 @@ private loadUserInfo(): void {
const savedLastName = localStorage.getItem('userLastName');
const savedAvatar = localStorage.getItem('userAvatar');
const savedBanner = localStorage.getItem('userBanner');
// Mise à jour du nom dans la preview
if (savedName) {
const previewName = this.shadowRoot?.querySelector('.preview-name');
@ -1471,7 +1370,7 @@ private loadUserInfo(): void {
previewLastName.textContent = savedLastName;
}
}
// Mise à jour de l'avatar dans la preview
if (savedAvatar) {
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
@ -1479,7 +1378,7 @@ private loadUserInfo(): void {
previewAvatar.src = savedAvatar;
}
}
// Mise à jour de la bannière dans la preview
if (savedBanner) {
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
@ -1503,11 +1402,11 @@ private updateNavbarLastName(lastName: string): void {
}
}
private updateProfilePreview(data: {
avatar?: string,
banner?: string,
name?: string,
lastName?: string
private updateProfilePreview(data: {
avatar?: string,
banner?: string,
name?: string,
lastName?: string
}): void {
if (data.avatar) {
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
@ -1545,7 +1444,7 @@ private initializeEventListeners() {
input.type = 'text';
input.value = currentValue;
input.className = 'edit-input';
field.textContent = '';
field.appendChild(input);
field.classList.add('editing');
@ -1561,16 +1460,16 @@ private initializeEventListeners() {
}
}
private showQRCodeModal(pairingId: string): void {
private showQRCodeModal(address: string): void {
const modal = document.createElement('div');
modal.className = 'qr-modal';
modal.innerHTML = `
<div class="qr-modal-content">
<span class="close-qr-modal">&times;</span>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${pairingId}"
alt="QR Code Large"
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${address}"
alt="QR Code Large"
class="qr-code-large">
<div class="qr-address">${decodeURIComponent(pairingId)}</div>
<div class="qr-address">${decodeURIComponent(address)}</div>
</div>
`;

View File

@ -1,321 +0,0 @@
import { ProcessState } from '../../../pkg/sdk_client.js';
import Services from '../../services/service.js';
interface State {
file: File | null;
fileHash: string | null;
certificate: ProcessState | null;
commitmentHashes: string[];
}
export interface Vin {
txid: string; // The txid of the previous transaction (being spent)
vout: number; // The output index in the previous tx
prevout: {
scriptpubkey: string;
scriptpubkey_asm: string;
scriptpubkey_type: string;
scriptpubkey_address: string;
value: number;
};
scriptsig: string;
scriptsig_asm: string;
witness: string[];
is_coinbase: boolean;
sequence: number;
}
export interface TransactionInfo {
txid: string;
version: number;
locktime: number;
vin: Vin[];
vout: any[];
size: number;
weight: number;
fee: number;
status: {
confirmed: boolean;
block_height: number;
block_hash: string;
block_time: number;
};
}
export function getDocumentValidation(container: HTMLElement) {
const state: State = {
file: null,
fileHash: null,
certificate: null,
commitmentHashes: []
}
container.innerHTML = '';
container.style.cssText = `
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
gap: 2rem;
`;
function createDropButton(
label: string,
onDrop: (file: File, updateVisuals: (file: File) => void) => void,
accept: string = '*/*'
): HTMLElement {
const wrapper = document.createElement('div');
wrapper.style.cssText = `
width: 200px;
height: 100px;
border: 2px dashed #888;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
font-weight: bold;
background: #f8f8f8;
text-align: center;
padding: 0.5rem;
box-sizing: border-box;
`;
const title = document.createElement('div');
title.textContent = label;
const filename = document.createElement('div');
filename.style.cssText = `
font-size: 0.85rem;
margin-top: 0.5rem;
color: #444;
word-break: break-word;
text-align: center;
`;
wrapper.appendChild(title);
wrapper.appendChild(filename);
const updateVisuals = (file: File) => {
wrapper.style.borderColor = 'green';
wrapper.style.background = '#e6ffed';
filename.textContent = file.name;
};
// === Hidden file input ===
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = accept;
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
fileInput.onchange = () => {
const file = fileInput.files?.[0];
if (file) {
onDrop(file, updateVisuals);
fileInput.value = ''; // reset so same file can be re-selected
}
};
// === Handle drag-and-drop ===
wrapper.ondragover = e => {
e.preventDefault();
wrapper.style.background = '#e0e0e0';
};
wrapper.ondragleave = () => {
wrapper.style.background = '#f8f8f8';
};
wrapper.ondrop = e => {
e.preventDefault();
wrapper.style.background = '#f8f8f8';
const file = e.dataTransfer?.files?.[0];
if (file) {
onDrop(file, updateVisuals);
}
};
// === Handle click to open file manager ===
wrapper.onclick = () => {
fileInput.click();
};
return wrapper;
}
const fileDropButton = createDropButton('Drop file', async (file, updateVisuals) => {
try {
state.file = file;
updateVisuals(file);
console.log('Loaded file:', state.file);
checkReady();
} catch (err) {
alert('Failed to drop the file.');
console.error(err);
}
});
const certDropButton = createDropButton('Drop certificate', async (file, updateVisuals) => {
try {
const text = await file.text();
const json = JSON.parse(text);
if (
typeof json === 'object' &&
json !== null &&
typeof json.pcd_commitment === 'object' &&
typeof json.state_id === 'string'
) {
state.certificate = json as ProcessState;
state.commitmentHashes = Object.values(json.pcd_commitment).map((value: unknown, _idx: number, _arr: unknown[]) =>
String(value).toLowerCase()
);
updateVisuals(file);
console.log('Loaded certificate, extracted hashes:', state.commitmentHashes);
checkReady();
} else {
alert('Invalid certificate structure.');
}
} catch (err) {
alert('Failed to parse certificate JSON.');
console.error(err);
}
});
const buttonRow = document.createElement('div');
buttonRow.style.display = 'flex';
buttonRow.style.gap = '2rem';
buttonRow.appendChild(fileDropButton);
buttonRow.appendChild(certDropButton);
container.appendChild(buttonRow);
async function checkReady() {
if (state.file && state.certificate && state.commitmentHashes.length > 0) {
// We take the commited_in and all pcd_commitment keys to reconstruct all the possible hash
const fileBlob = {
type: state.file.type,
data: new Uint8Array(await state.file.arrayBuffer())
};
const service = await Services.getInstance();
const commitedIn = state.certificate.commited_in;
if (!commitedIn) return;
const [prevTxid, prevTxVout] = commitedIn.split(':');
const processId = (state.certificate as any).process_id as string;
const stateId = state.certificate.state_id;
const process = await service.getProcess(processId);
if (!process) return;
// Get the transaction that comes right after the commited_in
const nextState = service.getNextStateAfterId(process, stateId);
if (!nextState) {
alert(`❌ Validation failed: No next state, is the state you're trying to validate commited?`);
return;
}
const [outspentTxId, _] = nextState.commited_in.split(':');
console.log(outspentTxId);
// Check that the commitment transaction exists, and that it commits to the state id
const txInfo = await fetchTransaction(outspentTxId);
if (!txInfo) {
console.error(`Validation error: Can't fetch new state commitment transaction`);
alert(`❌ Validation failed: invalid or non existent commited_in for state ${stateId}.`);
return;
}
// We must check that this transaction indeed spend the commited_in we have in the certificate
let found = false;
for (const vin of txInfo.vin) {
if (vin.txid === prevTxid) {
found = true;
break;
}
}
if (!found) {
console.error(`Validation error: new state doesn't spend previous state commitment transaction`);
alert('❌ Validation failed: Unconsistent commitment transactions history.');
return;
}
// set found back to false for next check
found = false;
// is the state_id commited in the transaction?
for (const vout of txInfo.vout) {
console.log(vout);
if (vout.scriptpubkey_type && vout.scriptpubkey_type === 'op_return') {
found = true;
} else {
continue;
}
if (vout.scriptpubkey_asm) {
const hash = extractHexFromScriptAsm(vout.scriptpubkey_asm);
if (hash) {
if (hash !== stateId) {
console.error(`Validation error: expected stateId ${stateId}, got ${hash}`);
alert('❌ Validation failed: Transaction does not commit to that state.');
return;
}
}
}
}
if (!found) {
alert('❌ Validation failed: Transaction does not contain data.');
return;
}
// set found back to false for next check
found = false;
for (const label of Object.keys(state.certificate.pcd_commitment)) {
// Compute the hash for this label
console.log(`Computing hash with label ${label}`)
const fileHex = service.getHashForFile(commitedIn, label, fileBlob);
console.log(`Found hash ${fileHex}`);
found = state.commitmentHashes.includes(fileHex);
if (found) break;
}
if (found) {
alert('✅ Validation successful: file hash found in pcd_commitment.');
} else {
alert('❌ Validation failed: file hash NOT found in pcd_commitment.');
}
}
}
async function fetchTransaction(txid: string): Promise<TransactionInfo> {
const url = `https://mempool.4nkweb.com/api/tx/${txid}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch outspend status: ${response.statusText}`);
}
const outspend: TransactionInfo = await response.json();
return outspend;
}
function extractHexFromScriptAsm(scriptAsm: string): string | null {
const parts = scriptAsm.trim().split(/\s+/);
const last = parts[parts.length - 1];
// Basic validation: must be 64-char hex (32 bytes)
if (/^[0-9a-fA-F]{64}$/.test(last)) {
return last.toLowerCase();
}
return null;
}
}

View File

@ -1,196 +0,0 @@
import { ValidationRule, RoleDefinition } from '../../../pkg/sdk_client.js';
import { showValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal.js';
export function createKeyValueSection(title: string, id: string, isRoleSection = false) {
const section = document.createElement('div');
section.id = id;
section.style.cssText = 'margin-bottom: 2rem; background: #fff; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1);';
const titleEl = document.createElement('h2');
titleEl.textContent = title;
titleEl.style.cssText = 'font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;';
section.appendChild(titleEl);
const rowContainer = document.createElement('div');
section.appendChild(rowContainer);
const addBtn = document.createElement('button');
addBtn.textContent = '+ Add Row';
addBtn.style.cssText = `
margin-top: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #888;
border-radius: 0.375rem;
background-color: #f9f9f9;
cursor: pointer;
`;
section.appendChild(addBtn);
const roleRowStates: {
roleNameInput: HTMLInputElement;
membersInput: HTMLInputElement;
storagesInput: HTMLInputElement;
validationRules: ValidationRule[];
}[] = [];
type fileBlob = {
type: string,
data: Uint8Array
};
const nonRoleRowStates: {
keyInput: HTMLInputElement,
valueInput: HTMLInputElement,
fileInput: HTMLInputElement,
fileBlob: fileBlob | null
}[] = [];
const inputStyle = 'flex: 1; height: 2.5rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem;';
const createRow = () => {
const row = document.createElement('div');
row.style.cssText = 'display: flex; gap: 1rem; margin-bottom: 0.5rem; align-items: center;';
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '🗑️';
deleteBtn.style.cssText = 'background: none; border: none; font-size: 1.2rem; cursor: pointer;';
deleteBtn.onclick = () => {
row.remove();
updateDeleteButtons();
};
if (isRoleSection) {
const roleName = document.createElement('input');
const members = document.createElement('input');
const storages = document.createElement('input');
roleName.placeholder = 'Role name';
members.placeholder = 'members';
storages.placeholder = 'storages';
[roleName, members, storages].forEach(input => {
input.type = 'text';
input.style.cssText = inputStyle;
});
const ruleButton = document.createElement('button');
ruleButton.textContent = 'Add Validation Rule';
ruleButton.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
const rules: ValidationRule[] = [];
ruleButton.onclick = () => {
showValidationRuleModal(rule => {
rules.push(rule);
ruleButton.textContent = `Rules (${rules.length})`;
});
};
row.appendChild(roleName);
row.appendChild(members);
row.appendChild(storages);
row.appendChild(ruleButton);
row.appendChild(deleteBtn);
roleRowStates.push({ roleNameInput: roleName, membersInput: members, storagesInput: storages, validationRules: rules });
} else {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.onchange = async () => {
const file = fileInput.files?.[0];
if (!file) return;
const buffer = await file.arrayBuffer();
const uint8 = new Uint8Array(buffer);
rowState.fileBlob = {
type: file.type,
data: uint8,
};
valueInput.value = `📄 ${file.name}`;
valueInput.disabled = true;
attachBtn.textContent = `📎 ${file.name}`;
};
const attachBtn = document.createElement('button');
attachBtn.textContent = '📎 Attach';
attachBtn.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
attachBtn.onclick = () => fileInput.click();
const keyInput = document.createElement('input');
const valueInput = document.createElement('input');
const rowState = {
keyInput,
valueInput,
fileInput,
fileBlob: null as fileBlob | null
};
nonRoleRowStates.push(rowState);
keyInput.placeholder = 'Key';
valueInput.placeholder = 'Value';
[keyInput, valueInput].forEach(input => {
input.type = 'text';
input.style.cssText = inputStyle;
});
row.appendChild(keyInput);
row.appendChild(valueInput);
row.appendChild(attachBtn);
row.appendChild(fileInput);
row.appendChild(deleteBtn);
}
rowContainer.appendChild(row);
updateDeleteButtons();
};
const updateDeleteButtons = () => {
const rows = Array.from(rowContainer.children);
rows.forEach(row => {
const btn = row.querySelector('button:last-child') as HTMLButtonElement;
if (rows.length === 1) {
btn.disabled = true;
btn.style.visibility = 'hidden';
} else {
btn.disabled = false;
btn.style.visibility = 'visible';
}
});
};
createRow();
addBtn.addEventListener('click', createRow);
return {
element: section,
getData: () => {
if (isRoleSection) {
const data: Record<string, RoleDefinition> = {};
for (const row of roleRowStates) {
const key = row.roleNameInput.value.trim();
if (!key) continue;
data[key] = {
members: row.membersInput.value.split(',').map(x => x.trim()).filter(Boolean),
storages: row.storagesInput.value.split(',').map(x => x.trim()).filter(Boolean),
validation_rules: row.validationRules
};
}
return data;
} else {
const data: Record<string, string | fileBlob> = {};
for (const row of nonRoleRowStates) {
const key = row.keyInput.value.trim();
if (!key) continue;
if (row.fileBlob) {
data[key] = row.fileBlob;
} else {
data[key] = row.valueInput.value.trim();
}
}
return data;
}
}
};
}

View File

@ -1,95 +0,0 @@
import { createKeyValueSection } from './key-value-section';
import { loadValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
import Services from '../../services/service';
import { RoleDefinition } from '../../../pkg/sdk_client.js';
export async function getProcessCreation(container: HTMLElement) {
await loadValidationRuleModal();
container.style.display = 'block';
container.innerHTML = `<div class="parameter-header">Process Creation</div>`;
const privateSec = createKeyValueSection('Private Data', 'private-section');
const publicSec = createKeyValueSection('Public Data', 'public-section');
const rolesSec = createKeyValueSection('Roles', 'roles-section', true);
container.appendChild(privateSec.element);
container.appendChild(publicSec.element);
container.appendChild(rolesSec.element);
const btn = document.createElement('button');
btn.textContent = 'Create Process';
btn.style.cssText = `
display: block;
margin: 2rem auto 0;
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: bold;
background-color: #4f46e5;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
`;
btn.onclick = async () => {
const privateData = privateSec.getData();
const publicData = publicSec.getData();
const roles = rolesSec.getData() as Record<string, RoleDefinition>;
console.log('Private:', privateData);
console.log('Public:', publicData);
console.log('Roles:', roles);
const service = await Services.getInstance();
const createProcessResult = await service.createProcess(privateData, publicData, roles);
const processId = createProcessResult.updated_process!.process_id;
const stateId = createProcessResult.updated_process!.current_process.states[0].state_id;
await service.handleApiReturn(createProcessResult);
// Now we want to validate the update and register the first state of our new process
const updateProcessResult = await service.createPrdUpdate(processId, stateId);
await service.handleApiReturn(createProcessResult);
const approveChangeResult = await service.approveChange(processId, stateId);
await service.handleApiReturn(approveChangeResult);
if (approveChangeResult) {
const process = await service.getProcess(processId);
if (!process) return;
const newState = service.getStateFromId(process, stateId);
if (!newState) return;
for (const label of Object.keys(newState.keys)) {
const hash = newState.pcd_commitment[label];
const encryptedData = await service.getBlobFromDb(hash);
if (!encryptedData) continue;
const filename = `${label}-${hash.slice(0,8)}.bin`;
const blob = new Blob([encryptedData], { type: "application/octet-stream" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
}
if (typeof (service as any).generateProcessPdf === 'function') {
await (service as any).generateProcessPdf(processId, newState);
}
// Add processId to the state we export
(newState as any)['process_id'] = processId;
const blob = new Blob([JSON.stringify(newState as unknown as object, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `process_${processId}_${stateId}.json`;
a.click();
URL.revokeObjectURL(url); // Clean up
}
};
container.appendChild(btn);
}

View File

@ -1,66 +0,0 @@
export function createProcessTab(container: HTMLElement, processes: { name: string, publicData: Record<string, any> }[]): HTMLElement {
container.id = 'process-tab';
container.style.display = 'block';
container.style.cssText = 'padding: 1.5rem;';
const title = document.createElement('h2');
title.textContent = 'Processes';
title.style.cssText = 'font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;';
container.appendChild(title);
processes.forEach(proc => {
const card = document.createElement('div');
card.style.cssText = 'margin-bottom: 1rem; padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; background: #fff;';
const nameEl = document.createElement('h3');
nameEl.textContent = proc.name;
nameEl.style.cssText = 'font-size: 1.2rem; font-weight: bold; margin-bottom: 0.5rem;';
card.appendChild(nameEl);
const dataList = document.createElement('div');
for (const [key, value] of Object.entries(proc.publicData)) {
const item = document.createElement('div');
item.style.cssText = 'margin-bottom: 0.5rem;';
const label = document.createElement('strong');
label.textContent = key + ': ';
item.appendChild(label);
// Let's trim the quotes
const trimmed = value.replace(/^'|'$/g, '');
let parsed;
try {
parsed = JSON.parse(trimmed);
} catch (_) {
parsed = trimmed;
}
if (parsed && typeof parsed === 'object') {
const saveBtn = document.createElement('button');
saveBtn.textContent = '💾 Save as JSON';
saveBtn.style.cssText = 'margin-left: 0.5rem; padding: 0.25rem 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
saveBtn.onclick = () => {
const blob = new Blob([JSON.stringify(parsed, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${proc.name}_${key}.json`;
a.click();
URL.revokeObjectURL(url);
};
item.appendChild(saveBtn);
} else {
const span = document.createElement('span');
span.textContent = String(parsed);
item.appendChild(span);
}
dataList.appendChild(item);
}
card.appendChild(dataList);
container.appendChild(card);
});
return container;
}

Some files were not shown because too many files have changed in this diff Show More