Compare commits

..

287 Commits

Author SHA1 Message Date
4NK CI Bot
d179aa704e chore: doc updates [skip ci] 2025-10-02 15:32:10 +00:00
4NK CI Bot
e72b3d56ab ci: Utiliser le tag docker ext dans la CI
Some checks failed
Docker Build and Push (ext) / build (push) Failing after 54s
2025-10-02 14:54:16 +00:00
4NK CI Bot
6733f79b83 ci: docker_tag=dev-test - Centralisation des fichiers CODE_OF_CONDUCT, CODEOWNERS, CONTRIBUTING, LICENSE
Some checks failed
Docker Build and Push (ext) / build (push) Failing after 51s
2025-10-01 21:05:51 +00:00
4NK CI Bot
2d5f731133 ci: docker_tag=dev-test - Mise à jour documentation et standardisation
Some checks failed
Docker Build and Push (ext) / build (push) Failing after 53s
2025-10-01 20:57:43 +00:00
4NK CI Bot
1a62a955f5 chore: sync from 4NK_env superproject 2025-10-01T16:23:14Z
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m23s
2025-10-01 16:23:14 +00:00
4NK CI Bot
8c11c59681 auto_clea
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m21s
Build and Push Docker image (ext) / docker (push) Successful in 1m25s
2025-09-25 18:33:28 +00:00
4NK CI Bot
b1be8e65ac auto_clea
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m15s
2025-09-25 15:24:56 +00:00
4NK CI Bot
09c2d1f969 auto_clea 2025-09-25 15:08:08 +00:00
4NK CI Bot
1b5f672f62 auto_clea 2025-09-25 15:03:38 +00:00
4NK CI Bot
390015e823 auto_clea
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m18s
2025-09-25 12:45:52 +00:00
4NK CI Bot
5a8c8e6d4f auto_clea
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m14s
2025-09-25 12:42:10 +00:00
4NK CI Bot
9b5542a3ba auto_clea
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m20s
2025-09-25 12:35:57 +00:00
4NK CI Bot
33853c7a6a ci: docker_tag=ext chore: sync ignores, logging and backups
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m16s
2025-09-25 08:58:18 +00:00
4NK CI Bot
310428dc42 chore(ihm): add favicon.ico for root\n\nci: docker_tag=ext
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m26s
Build and Push Docker image (ext) / docker (push) Successful in 1m23s
2025-09-23 18:00:37 +00:00
4NK CI Bot
e07aa3474e chore(ihm): add favicon for root app so /favicon.ico|.svg returns 200\n\nci: docker_tag=ext
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m18s
Build and Push Docker image (ext) / docker (push) Successful in 1m24s
2025-09-23 17:57:50 +00:00
4NK CI Bot
7681c9966b ci: docker_tag=ext sanitize tag extraction; add .gitea workflow; push image to ext
All checks were successful
Docker Build and Push (ext) / build (push) Successful in 1m18s
Build and Push Docker image (ext) / docker (push) Successful in 1m26s
2025-09-22 12:55:37 +00:00
4NK CI Bot
2d3ac5a884 ci: docker_tag=ext fix(ihm_client): build passes locally; adjust pkg imports, null-guards, types 2025-09-22 12:48:30 +00:00
4NK CI Bot
3f3fdc6b55 ci: docker_tag=ext\n\nTrigger CI build for Docker images
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 23s
2025-09-22 11:10:48 +00:00
4NK CI Bot
1bc594fa90 docs: add architecture, installation, deployment, functional, technical, flows, quality, security, TODO [skip ci] 2025-09-22 07:31:22 +00:00
4NK CI Bot
81e52c1da4 update submodule 2025-09-22 05:45:35 +00:00
4NK CI Bot
6a9bee3c99 ci: docker_tag=ext - Fix Dockerfile build error (remove file command)
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 1m22s
2025-09-21 21:46:58 +00:00
4NK CI Bot
67fc87314e ci: docker_tag=ext - Fix Vite allowedHosts configuration for external access
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 55s
2025-09-21 21:35:42 +00:00
4NK CI Bot
c1e3a5a588 ci: docker_tag=ext - Trigger CI build for Docker images
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 57s
2025-09-21 19:57:46 +00:00
4NK CI Bot
72173caeed ci: docker_tag=ext - Update for 4NK_env integration 2025-09-21 19:55:47 +00:00
4NK CI Bot
34d57c95f7 ci: docker_tag=ext - Migrate to Debian base with minimal packages
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 59s
2025-09-21 18:25:14 +00:00
4NK CI Bot
8c83757025 fix: Install missing packages in Dockerfile for Alpine Linux
- Add file command for WebAssembly verification
- Add curl, wget, jq, netcat-openbsd for debugging
- Fix CI build failure due to missing file command
2025-09-21 18:20:02 +00:00
4NK CI Bot
662b04bfdd ci: docker_tag=ext - Optimize Dockerfile to reduce image size (remove dev tools)
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 33s
2025-09-21 18:09:33 +00:00
4NK CI Bot
1542f0fe98 ci: docker_tag=ext - Fix CI workflow to verify WASM files instead of building them
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 2m11s
2025-09-21 17:13:07 +00:00
4NK CI Bot
9db61039c6 ci: docker_tag=ext
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 38s
2025-09-21 17:05:35 +00:00
4NK CI Bot
f7b30cb495 ci: docker_tag=ext - Fix Dockerfile to verify pkg files during build 2025-09-21 17:04:10 +00:00
4NK CI Bot
2379af9505 ci: docker_tag=ext 2025-09-21 16:57:26 +00:00
4NK CI Bot
18c61fd5e0 ci: docker_tag=ext - Fix import paths for sdk_client.js
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 39s
2025-09-21 16:51:39 +00:00
4NK CI Bot
b7df1bc58e ci: docker_tag=ext - Fix Alpine Dockerfile - remove cscope package 2025-09-21 15:36:11 +00:00
4NK CI Bot
4aa19b0b2a ci: docker_tag=ext - Fix WASM import paths in TypeScript files 2025-09-21 13:48:55 +00:00
4NK CI Bot
fd351374fa ci: docker_tag=ext - Fix Dockerfile issues with WASM package and Alpine compatibility 2025-09-21 13:46:47 +00:00
4NK CI Bot
8bba3e5437 ci: docker_tag=ext - Update Dockerfiles with comprehensive dependencies and tools 2025-09-21 13:45:52 +00:00
4NK CI Bot
e341c45639 ci: docker_tag=ext - Mise à jour Dockerfile avec outils requis et .cursorignore 2025-09-21 13:27:04 +00:00
4NK CI Bot
30a3960b2e ci: docker_tag=ext - Update ihm_client configuration and dependencies 2025-09-21 13:07:47 +00:00
4NK CI Bot
ae58d95e39 ci: docker_tag=ext - Fix WebAssembly import paths 2025-09-21 12:00:14 +00:00
4NK CI Bot
2bfee359e0 ci: docker_tag=ext - Ajouter build WebAssembly dans le workflow CI
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 38s
2025-09-21 09:43:09 +00:00
4NK CI Bot
4c3328b3db ci: docker_tag=ext - Modifier workflow pour déclenchement sur tag ext au lieu de branche dev4
Some checks are pending
Build and Push Docker image (ext) / docker (push) Waiting to run
2025-09-21 08:58:50 +00:00
4NK CI Bot
e41d17f7f4 ci: docker_tag=ext - Fix TypeScript imports and add curl to Dockerfile
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 55s
2025-09-21 08:21:32 +00:00
4NK CI Bot
09c8493aeb docs: Documentation WebSocket et architecture de l'iframe
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 54s
- Ajout de docs/WEBSOCKET_CONNECTION.md
- Architecture de l'iframe documentée
- Configuration WebSocket et gestion des messages
- Communication avec le parent documentée
- Problème persistant 502 Bad Gateway documenté
2025-09-20 22:15:07 +00:00
4NK CI Bot
cb6b4a02da Add WebSocket connection documentation
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
- Document VITE_BOOTSTRAPURL configuration
- Document RELAY_URLS configuration
- Document WebSocket headers requirements
- Document iframe connection process
2025-09-20 21:50:34 +00:00
4NK CI Bot
1756278094 fix: Correction de l'URL de l'API de transfert de fonds
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 49s
- Changement de /api/funds/transfer vers /api/v1/funds/transfer
- Correction de l'erreur 404 lors du transfert automatique de fonds
- Le frontend peut maintenant appeler la bonne API
2025-09-20 17:22:47 +00:00
4NK CI Bot
48ea3614b8 align docker images
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-20 17:13:35 +00:00
4NK CI Bot
2d0bbc425c feat: Détection automatique des fonds insuffisants dans createProcess
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
- Détection des erreurs 'Insufficient funds' et 'Missing sats'
- Transfert automatique de fonds via API
- Retry automatique après transfert
- Gestion des erreurs et fallback
2025-09-20 16:17:42 +00:00
4NK CI Bot
0a033dd636 docs: Documentation des corrections appliquées
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 1m0s
- Correction du problème de configuration WebSocket
- Configuration des URLs bootstrap et relais
- Installation des outils système
- État actuel et prochaines étapes
2025-09-20 14:23:12 +00:00
4NK CI Bot
c4d35331bd ci: docker_tag=ext - Configuration WebSocket corrigée: BOOTSTRAPURL vers dev3.4nkweb.com, RELAY_URLS avec relay local et bootstrap externe
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-20 13:42:14 +00:00
4NK CI Bot
85c4106ae6 ci: docker_tag=dev-test - Mise à jour documentation et services
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 49s
2025-09-20 08:14:09 +00:00
4NK CI Bot
0b57f6c79c fix: correction de l'URL WebSocket pour utiliser WSS (WebSocket Secure) au lieu de WS pour la compatibilité HTTPS
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-19 18:10:59 +00:00
4NK CI Bot
333101b3f4 fix: correction de l'URL WebSocket pour utiliser le proxy nginx /ws/ au lieu du port direct 8090
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 58s
2025-09-19 18:06:59 +00:00
4NK CI Bot
a302149ea8 fix: correction de la configuration WebSocket pour utiliser VITE_BOOTSTRAPURL au lieu de window.location.origin
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 51s
2025-09-19 17:55:39 +00:00
4NK CI Bot
537e627ebc ci: docker_tag=ext - Update for dev4 deployment
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 51s
2025-09-19 17:04:01 +00:00
4NK CI Bot
5e3f050132 ci: docker_tag=ext chore(ihm): rebuild ext
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-19 08:02:08 +00:00
4NK CI Bot
3e53ed70d1 ci: docker_tag=ext feat(ihm): callback bridge authorized-client.html
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 57s
2025-09-18 22:20:25 +00:00
4NK CI Bot
b9f435c2bf ci: docker_tag=ext
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-18 17:01:19 +00:00
4NK CI Bot
4f7d51e9b2 fix(ihm_client): génération et persistance locale d'une clé HMAC si VITE_JWT_SECRET_KEY absent
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-18 16:53:28 +00:00
4NK CI Bot
a6f54dc8f5 fix(ihm_client): favicon via chemin relatif /favicon.svg pour HTTPS
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 51s
2025-09-18 16:26:06 +00:00
4NK CI Bot
a40416a248 fix(ihm_client): double fallback IndexedDB si service worker.ready échoue
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 52s
2025-09-18 16:16:48 +00:00
4NK CI Bot
c21eb9170a fix(ihm_client): fallback IndexedDB direct si Service Worker indisponible (iframe)
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 53s
2025-09-18 16:14:25 +00:00
4NK CI Bot
d47db27583 fix(ihm_client): initialiser Database (IndexedDB) dans Services.init() et attendre readiness
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 51s
2025-09-18 16:08:58 +00:00
4NK CI Bot
eb6f6be67b fix(ihm_client): éviter fallback localhost; utiliser window.location.origin pour BASEURL/URLs par défaut
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-18 16:07:35 +00:00
4NK CI Bot
e9f6af41a1 fix(ihm_client): charger WASM ESM sans export default dans index.html
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 1m4s
2025-09-18 16:01:53 +00:00
4NK CI Bot
1c20450b05 fix(ihm_client): imports ESM explicites vers pkg/sdk_client.js et chemins relatifs
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-18 15:58:44 +00:00
4NK CI Bot
e76f2e1db9 ci: docker_tag=ext - utiliser pkg WASM fourni (ESM) et supprimer build wasm
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 50s
2025-09-18 15:54:56 +00:00
4NK CI Bot
0cf587fa6e ci: docker_tag=ext - wasm-bindgen --no-reference-types for web target
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m45s
2025-09-18 15:47:31 +00:00
4NK CI Bot
01b1b50d6b ci: docker_tag=ext - Fix WASM compilation for web target
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m44s
- Recompile sdk_client WASM with --target web for ES modules
- Update Dockerfile to build WASM in web target instead of bundler
- Modify index.html to initialize WASM before router
- Update service.ts and router.ts to use ES module imports
- Fix 'module is not defined' error in browser
2025-09-18 15:41:05 +00:00
4NK CI Bot
5da295be1a ci: docker_tag=ext chore: inclure binaire sdk_client (tgz) pour build ext
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 51s
2025-09-18 15:11:23 +00:00
4NK CI Bot
a4dd8c57f2 ci: docker_tag=ext | use prebuilt wasm pkg (no wasm build in CI)
All checks were successful
Build and Push Docker image (ext) / docker (push) Successful in 52s
2025-09-18 12:05:43 +00:00
4NK CI Bot
653b86fbc9 ci: docker_tag=ext | cargo update wasm-bindgen*(0.2.103) js-sys/web-sys (0.3.80) before build
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m41s
2025-09-18 11:32:46 +00:00
4NK CI Bot
70f3a30f64 ci: docker_tag=ext | revert reference-types; compile wasm without externref requirements
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m37s
2025-09-18 11:20:01 +00:00
4NK CI Bot
2dda47534b ci: docker_tag=ext | enable reference-types (RUSTFLAGS + wasm-bindgen --reference-types)
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m46s
2025-09-18 11:08:49 +00:00
4NK CI Bot
01ad5a3816 ci: docker_tag=ext | align wasm-bindgen-cli 0.2.103 with crate 0.2.103
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m44s
2025-09-18 11:01:04 +00:00
4NK CI Bot
5884e8d845 ci: docker_tag=ext | ensure wasm-bindgen in PATH + use wasm-bindgen bin
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m18s
2025-09-18 10:55:36 +00:00
4NK CI Bot
fb54522bce ci: docker_tag=ext | rust 1.82 + wasm-bindgen-cli 0.2.92 --locked (cargo+bindgen)
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 3m16s
2025-09-18 10:50:14 +00:00
4NK CI Bot
59ad93516c ci: docker_tag=ext | pin rust 1.73 + wasm-bindgen-cli 0.2.92 + cargo build wasm
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 58s
2025-09-18 10:47:19 +00:00
4NK CI Bot
6ef5186407 ci: docker_tag=ext | wasm-pack: use --reference-types for wasm-bindgen
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 1m50s
2025-09-18 10:42:14 +00:00
4NK CI Bot
c89d956821 ci: docker_tag=ext | wasm-build: force reference-types for wasm-bindgen
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 1m52s
2025-09-18 10:37:39 +00:00
4NK CI Bot
7a52bb2ee7 ci: docker_tag=ext | buildx ssh-agent pour clone sdk_client
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 1m57s
2025-09-18 10:33:31 +00:00
4NK CI Bot
903ad3849d ci: docker_tag=ext
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 43s
workflow: gitea ci ext, cleanup github workflow
2025-09-18 10:29:22 +00:00
4NK CI Bot
17e2cd282e ci: docker_tag=ext | Gitea Actions build/push image ext
Some checks failed
Build and Push Docker image (ext) / docker (push) Failing after 21s
2025-09-18 07:37:56 +00:00
4NK CI Bot
e6569cf908 docs(tests): analyse initiale ihm_client 2025-09-18 07:35:58 +00:00
Sosthene
0d473cf3d1 [bug] Update device blockheight right after initialization to prevent race condition
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m25s
2025-09-13 08:12:30 +02:00
Sosthene
457994c506 Updating testData in storage
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m15s
2025-09-13 07:34:43 +02:00
Sosthene
5fc485e233 Fix checkConnections, also check more aggressively 2025-09-13 07:34:43 +02:00
Sosthene
0d934e7b6e [bug] coerce potention undefined to null as return value of getObject() 2025-09-13 07:34:43 +02:00
Sadrinho27
02d28d46bb Add .env.exemple file
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m8s
2025-09-12 12:33:06 +02:00
Sadrinho27
723f4d5d85 Add .env to gitignore
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m11s
2025-09-12 12:30:27 +02:00
omaroughriss
6f9fa60e2f Use env variables to config urls
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m2s
2025-09-10 17:41:01 +02:00
omaroughriss
e729e32b35 Add handshakeMsg log
Some checks failed
Build and Push to Registry / build-and-push (push) Has been cancelled
2025-09-10 16:24:06 +02:00
omaroughriss
e4681f91e4 Merge branch 'dev' of https://git.4nkweb.com/4nk/ihm_client into dev
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m4s
2025-09-10 15:25:07 +02:00
omaroughriss
6363ec1189 Update relay handshake verification to use peer list or sp adresses 2025-09-10 15:25:05 +02:00
Sosthene
c8ac815e2b Refactor handleHandshakeMsg
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m11s
2025-09-08 22:00:36 +02:00
Sosthene
ef31cba983 Improve checkConnections to prevent no shared secrets 2025-09-08 22:00:36 +02:00
Omar Oughriss
47c7d31249 Rename workflow
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m18s
2025-09-08 16:53:44 +02:00
Omar Oughriss
ede8d95fd1 Add "dev" tagged image
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m17s
2025-09-08 16:44:33 +02:00
Omar Oughriss
0fc7b6e4c3 Update Dockerfile to use branche dev of sdk_client 2025-09-08 16:44:02 +02:00
Sosthene
3f64369852 [bug] Fix wrong api calls for storeData 2025-09-08 15:56:34 +02:00
Sosthene
e8c2d1a05a Get the storage urls from diffs 2025-09-08 15:56:14 +02:00
Sosthene
63ee4ce719 Merge branch 'bug_scan_blocks_update' into dev 2025-09-05 07:59:09 +02:00
Sosthene
e0e186f4f4 Refactor pairing logic
Account for the fact that we need the pairing id earlier now
2025-09-05 07:57:57 +02:00
Sosthene
bfca596e8b Adapt storage to new api 2025-09-05 07:57:50 +02:00
Sosthene
acb9739a80 [bug] Now save device after scan_blocks 2025-09-02 13:30:52 +02:00
Sosthene
c422881cd1 [bug] Failed to update tips 2025-09-02 13:30:52 +02:00
Sosthene
19da967605 Remove unnecessary async 2025-08-25 01:32:24 +02:00
Sosthene
d4223ce604 [bug] Correct args for parse_cipher 2025-08-25 01:31:35 +02:00
Sosthene
420979e63e [bug] Prevent duplicated addresses when sharing secrets 2025-08-25 01:30:58 +02:00
Sosthene
1c92a40984 [bug] remove test code 2025-08-23 16:10:11 +02:00
Sosthene
046eef18e6 Merge branch 'scan_blocks' into dev 2025-08-23 16:05:03 +02:00
Sosthene
2ba7be8dbb Call updateDeviceBlockHeight from router 2025-08-23 16:04:34 +02:00
Sosthene
77d9c1ad43 Add updateDeviceBlockHeight 2025-08-23 16:03:49 +02:00
Sosthene
3ce412d814 Keep currentBlockHeight in services 2025-08-23 16:02:31 +02:00
Sosthene
7100eda272 [bug] update parse_new_tx 2025-08-23 16:02:06 +02:00
Sosthene
1a3a2dbef1 Remove pdf code 2025-08-23 16:00:35 +02:00
Sosthene
76a1d38e09 Merge branch 'add_create_pairing' into dev 2025-08-23 15:59:17 +02:00
Sosthene
8a0a8e2df2 Add handleCreatePairing 2025-08-23 15:58:30 +02:00
Sosthene
48194dd2de Add CREATE_PAIRING MessageType 2025-08-23 15:55:39 +02:00
Sosthene
8e9d7f0c76 remove wasm processes cache 2025-08-23 15:54:22 +02:00
Sosthene
eda7102ded Merge branch 'cicd' into dev 2025-08-23 15:53:47 +02:00
ec99d101ab Merge pull request 'dev' (#8) from dev into cicd
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m8s
Reviewed-on: #8
2025-08-13 10:05:04 +00:00
Sosthene
0dd928d28b Merge branch 'bug_pairing_not_my_processes' into dev 2025-08-08 08:28:24 +02:00
Sosthene
5ba45a29be MyProcesses always include pairing 2025-08-08 08:27:46 +02:00
Sosthene
8541427b87 Merge branch 'fix_missing_human_readable' into dev 2025-08-08 08:25:57 +02:00
7b86318dec Fix error on pairing 2025-07-31 13:28:24 +02:00
omaroughriss
205796d22a Merge remote-tracking branch 'origin/dev' into cicd
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m7s
2025-07-23 13:40:06 +02:00
b072495cea revert 9a601056b70c856366fbb227e75f996c29683358
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m9s
revert Update cicd branche to dev
2025-07-23 11:26:52 +00:00
omaroughriss
9a601056b7 Update cicd branche to dev 2025-07-23 13:22:53 +02:00
Sosthene
d3e207c6da [bug] fix types mismatch with Device 2025-07-16 11:31:29 +02:00
Sosthene
cb5297e6fe Refactoring of handleUpdateProcess to try to commit again processes that have not been commited 2025-07-08 17:24:51 +02:00
Sosthene
f0151fa55e Don't try to batch write to db if objects is empty 2025-07-08 17:24:51 +02:00
Sosthene
5192745a48 Refactor handshake message handling using bach writing 2025-07-08 17:24:50 +02:00
Sosthene
a027004bd0 [bug] Set cache in restoreProcessesFromDb, not Backup 2025-07-08 17:24:14 +02:00
Sosthene
aae11200d4 Add batchSaveProcessesToDb() 2025-07-08 17:24:14 +02:00
Sosthene
dbb7f67154 Don't automatically connect to realys in init() 2025-07-08 17:24:14 +02:00
Sosthene
58fed7a53b use a processCache for optimization 2025-07-08 17:24:14 +02:00
Sosthene
19b2ab994e Batch writes processes at initialization 2025-07-08 17:24:14 +02:00
Sosthene
93d610e942 Add batchWriting() to database 2025-07-08 17:24:14 +02:00
Sosthene
1dad1d4e2b Add BATCH_WRITING to database.worker 2025-07-08 17:24:14 +02:00
Sosthene
5a98fac745 Update our pairing addresses on receiving updates 2025-07-08 17:22:22 +02:00
Sosthene
18d46531a0 Correctly handle states in pairDevice() 2025-07-08 17:22:22 +02:00
Sosthene
62ccfec315 Add GET_MEMBER_ADDRESSES and ADD_DEVICE messages 2025-07-07 15:26:40 +02:00
Sosthene
e9fc0b8454 Rm await on getDeviceAddress() 2025-07-07 15:24:55 +02:00
Sosthene
5119d04243 Don't retry commit message for Not enough members to validate errors 2025-07-07 15:24:15 +02:00
Sosthene
5a8c31df32 Add getUncommitedStates() 2025-07-07 15:23:47 +02:00
Sosthene
deebcefc3d Track states on pairing process 2025-07-07 15:23:05 +02:00
Sosthene
d9b8817ecc Create connections with devices in a pairing process 2025-07-07 15:22:23 +02:00
Sosthene
d8c2b22c3d Rm uneccessary async on decodeValue() 2025-07-07 15:21:25 +02:00
Sosthene
39f24114e1 Rm uneccessary async on getDeviceAddress() 2025-07-07 15:20:00 +02:00
Sosthene
189bd3d252 Don't throw error if unpaired while trying to get my processes 2025-07-04 12:26:11 +02:00
Sosthene
989263d44a handleValidateMerkleProof 2025-07-03 17:56:03 +02:00
Sosthene
7391a08a01 Add VALIDATE_MERKLE_PROOF MessageType 2025-07-03 17:54:36 +02:00
Sosthene
4e109e8fba Add validateMerkleProof 2025-07-03 17:54:07 +02:00
omaroughriss
13b605a850 Update port
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m5s
2025-07-03 11:38:40 +02:00
omaroughriss
0a860bd559 Add CICD
All checks were successful
Build and Push to Registry / build-and-push (push) Successful in 2m3s
2025-07-02 15:40:44 +02:00
omaroughriss
a8b0248b5f Minor updates 2025-07-02 15:39:51 +02:00
omaroughriss
0dc3c83c3c Add a start script 2025-07-02 15:39:32 +02:00
omaroughriss
1a87a4db14 Add nginx config 2025-07-02 15:37:34 +02:00
omaroughriss
67cd7a1662 Add Dockerfile 2025-07-02 15:36:42 +02:00
Sosthene
44f0d8c6c9 [bug] fix rolesContainsMember 2025-07-02 13:50:54 +02:00
Sosthene
10589b056f Solve potential race conditions in dumpStore() 2025-07-02 12:40:44 +02:00
Sosthene
926f41d270 Fix race condition on getMyProcesses 2025-07-02 12:40:44 +02:00
Sosthene
7c39795cef Add HASH_VALUE and GET_MERKLE_PROOF 2025-07-02 12:40:44 +02:00
Sosthene
207b308173 Add getMerkleProofForFile 2025-07-02 12:40:44 +02:00
Sosthene
337a6adc60 Add HASH_VALUE and GET_MERKLE_PROOF 2025-06-30 22:45:50 +02:00
Sosthene
d8422de94e Add getMerkleProofForFile 2025-06-30 22:45:25 +02:00
Sosthene
9edcc2e897 Add HASH and MERKLE MessageType 2025-06-30 19:49:41 +02:00
Sosthene
f5fae245e2 [bug] validateToken was bypassed 2025-06-30 19:49:06 +02:00
ed4fa732f7 Remove redundant log and dead code 2025-06-26 10:52:28 +02:00
ac11893e93 Add messageId to each message, refactor listeners 2025-06-25 16:59:20 +02:00
929e7ee36d [bug] fix updateProcess 2025-06-25 14:40:08 +02:00
c2a4b598a7 Validate token for getProcesses request 2025-06-25 14:39:42 +02:00
2bd2fdff98 Add mock VITE_JWT_SECRET_KEY 2025-06-25 14:39:12 +02:00
13731da7e1 Add getLastCommitedStateIndex 2025-06-25 14:38:52 +02:00
Sosthene
965f5da9a9 Add DECODE_PUBLIC_DATA MessageType 2025-06-24 18:22:10 +02:00
Sosthene
18ef18db71 Add DECODE_PUBLIC_DATA api 2025-06-24 15:52:23 +02:00
Sosthene
50a92995d7 Add decodeValue api 2025-06-23 19:45:21 +02:00
Sosthene
17bdcec317 [bug] Fix access verification in decryptAttribute 2025-06-23 19:45:08 +02:00
Sosthene
25caed410e [bug] checkConnections didn't get the member addresses 2025-06-23 19:44:36 +02:00
Sosthene
cf57681c31 Add handleUpdateProcess in router 2025-06-15 22:17:58 +02:00
Sosthene
91ba7205cc Fix return value of handleDecryptState 2025-06-15 22:17:22 +02:00
Sosthene
d31e18d4ae Use new utils splitPrivateData and isValid32ByteHex 2025-06-15 22:16:32 +02:00
Sosthene
6076c342f8 [bug] Do not request keys when we are not in the role in decryptAttribute 2025-06-15 22:12:22 +02:00
Sosthene
bb5d3ff16d [bug] Correctly encode data in updateProcess 2025-06-15 22:11:02 +02:00
Sosthene
a3fe29e4a0 [bug] Correctly iterate on members addresses in checkConnection 2025-06-15 22:10:25 +02:00
Sosthene
0d51f9d056 [bug] wrong variable name in handleDecryptState 2025-06-15 22:08:41 +02:00
Sosthene
c0d402b234 Add service.utils with splitPrivateData and isValid32ByteHex 2025-06-15 22:07:23 +02:00
Sosthene
dfae77de58 Remove dead code 2025-06-13 21:06:10 +02:00
Sosthene
e1494d5bf4 Reorganise MessageType and remove dead code from models 2025-06-13 21:04:15 +02:00
Sosthene
ed23adf8f1 Rename folderCreated to processData 2025-06-13 20:35:35 +02:00
Sosthene
2a7c0d6675 Add notify and validate api 2025-06-12 17:33:22 +02:00
Sosthene
25dba4e67b minor fixes 2025-06-12 17:26:51 +02:00
Sosthene
65d43686cb Add handleCreateProcess 2025-06-12 14:36:12 +02:00
Sosthene
18e82de549 Minor fixes and refactor in router 2025-06-12 14:36:12 +02:00
f4d8f8652f Profile and Folder event handler return object 2025-06-12 14:36:12 +02:00
39f2b086b5 Make createAndSend{Profile,Folder}Tx returns object 2025-06-12 14:36:12 +02:00
00bc3d8ad2 Add getMyProcesses api 2025-06-12 14:36:12 +02:00
b52ff937f0 Add GET_PAIRING_ID event listener 2025-06-11 15:40:15 +02:00
d6e06f3594 Generate the pdf beside the json file 2025-06-10 13:19:57 +02:00
05f13224fa Add generateProcessPdf to service 2025-06-10 13:18:57 +02:00
06295fe591 Add pdf-lib 2025-06-10 13:18:12 +02:00
72d43210de Add transaction check logic for document validation 2025-06-10 09:30:52 +02:00
73cee5d144 Center buttons and allow file picker 2025-06-09 10:23:49 +02:00
85fe8cc251 Refactor getDocumentValidation 2025-06-06 23:05:28 +02:00
ec9fe0f62c refactor decryptAttribute 2025-06-06 23:05:15 +02:00
b6a2a5fc3b Add getHashForFile 2025-06-06 23:04:57 +02:00
7417aec7e0 [bug] createProfileProcess 2025-06-06 23:04:17 +02:00
f42aca7eb9 createProcess split json and binary data and encode them separately 2025-06-06 23:03:47 +02:00
0f0b5d1af3 Download all encrypted data in separate files on process creation 2025-06-06 23:02:51 +02:00
84aa6298e3 [bug] rm useless variable 2025-06-05 17:23:04 +02:00
14b539595f [bug] wrong number of args to createPairingProcess 2025-06-05 15:42:42 +02:00
99400a71f7 Various bug fixes in Services 2025-06-05 15:42:12 +02:00
c5b58d999f Download the new process in json file 2025-06-05 15:39:33 +02:00
23a3b2a9e8 adding document when creating process takes a fileBlob type 2025-06-05 15:39:10 +02:00
6167d59501 document validation takes json certificates 2025-06-05 15:38:06 +02:00
b828e5197a Correct typing in showProcess 2025-06-05 15:36:51 +02:00
26ba3e6e93 [bug] Check for state_id when looking for a state 2025-06-04 15:38:50 +02:00
df726d929a Add fullscreen mode for modal - Fix css issue 2025-06-04 14:38:10 +02:00
0e44a01218 Add fullscreen mode for modal 2025-06-04 14:24:32 +02:00
8260c6c5da Add some decoding logic in service 2025-06-04 09:14:47 +02:00
8eb6f36b64 Refactor process creation for pairing/profile/folder use cases 2025-06-04 09:14:47 +02:00
e15da5c22a createProcess does encoding before creating a new process 2025-06-04 09:14:47 +02:00
a8b3631dc1 [bug] strict equality for address comparison 2025-06-04 09:14:47 +02:00
89e9b3e4e0 Add Process tab 2025-05-22 22:35:26 +02:00
c4db22f626 [bug] Actually pass the file object to wasm as bytes 2025-05-22 22:35:26 +02:00
accd427cab Add RoleDefintion type to role 2025-05-22 15:39:05 +02:00
381dcdf7a8 Update getProcesses iframe handler 2025-05-22 15:38:10 +02:00
0cbc07cf63 Update addFolder iframe handler 2025-05-22 14:37:43 +02:00
3c59105aa6 Add getProcesses iframe handler 2025-05-22 14:37:21 +02:00
325d2cbf13 Upodate processes creation and minor fixes 2025-05-22 14:36:19 +02:00
d4f1f36376 Add upload file button on create process screen 2025-05-22 11:20:22 +02:00
f6edadc535 Comment out fn (maybe use it in lecoffre stack) 2025-05-21 12:08:02 +02:00
0099a8c858 Minor changes 2025-05-21 12:00:10 +02:00
0e0c3946d2 Update tjwt logic to use refresh token 2025-05-21 11:58:16 +02:00
0a2a2674f8 Add jose jwt dependencies 2025-05-21 11:54:37 +02:00
9d461d63d7 Delete dead code 2025-05-21 11:53:06 +02:00
2f68c652dd Remove events listeners before adding, prevent duplication 2025-05-20 17:51:39 +02:00
147f4cfa7d register events listeners only if we're in an iframe 2025-05-20 17:51:00 +02:00
235aecd6a7 don't await service worker registering 2025-05-20 17:50:34 +02:00
e1f2483924 [bug] missing await on home 2025-05-20 17:50:10 +02:00
0c2df347ec Remove redundant event listeners adding in home 2025-05-20 17:49:53 +02:00
abfe581f29 Add document validation interface (mocked for now) 2025-05-20 17:49:14 +02:00
b66ee42ddd Add document validation logic 2025-05-20 17:48:50 +02:00
aecdcd93e1 Fix showQrCodeModal 2025-05-20 17:48:32 +02:00
c63e2a6fe9 Add missing async in addRowPairing 2025-05-20 17:47:16 +02:00
67963bfb02 Add process creation tab 2025-05-20 17:45:28 +02:00
4b12b560e1 Remove useless code 2025-05-20 17:44:22 +02:00
28c151254c [bug] missing await in getProcessId 2025-05-20 17:43:45 +02:00
5d0c617bbb Add createProcess() service method 2025-05-20 17:42:26 +02:00
ae88959496 Add process creation logic 2025-05-20 17:41:51 +02:00
e5a958b0b9 Add validation rule modal 2025-05-20 17:41:23 +02:00
6b77ec2972 [bug] Outdated api call in connectAddresses + don't connect with ourselves 2025-05-20 15:06:14 +02:00
a1ce472cad Update messages types 2025-05-06 16:52:48 +02:00
db48386f05 Minor updates 2025-05-06 16:51:21 +02:00
39b50d6789 Update handleRenewToken 2025-05-06 16:50:48 +02:00
86393e6cfa Handle token validation messages 2025-05-06 16:50:17 +02:00
bf06b6634a Update handleRequestLink 2025-05-06 16:47:56 +02:00
cfc9514656 Add refresh token 2025-05-06 16:44:40 +02:00
0f364c7c6e Update to use jose tokens library 2025-05-06 16:43:51 +02:00
ee7c79a7d5 Update css 2025-04-27 16:42:18 +02:00
37bdb3dad3 Add showConfirmationModal 2025-04-27 16:42:01 +02:00
ecba13594b Add tokenService 2025-04-27 16:39:55 +02:00
4c534973d2 [bug] fix rolesContainsMember 2025-04-27 16:39:18 +02:00
eca4d4de85 Replace handleSource by registeringAllListeners 2025-04-27 16:38:35 +02:00
824a0b88f6 handleSource link-service 2025-04-04 16:35:12 +02:00
e224921f86 handle partial_tx in handleApiReturn 2025-04-04 12:53:12 +02:00
cf18e46e17 getTokensFromFaucet 2025-04-04 12:52:32 +02:00
e6cf1c3658 Remove pairing from url params 2025-04-04 12:51:12 +02:00
b9851c587e Registering service worker must be called beside db initialisation 2025-03-31 15:52:34 +02:00
d601d94bf6 Export Database 2025-03-31 15:04:10 +02:00
d19ba72b4a build input is index.ts 2025-03-31 15:03:58 +02:00
2d0e15533a Build boilerplate 2025-03-28 12:53:24 +01:00
94ee8842e3 export Services from index.ts 2025-03-28 12:52:59 +01:00
7b7d13ce6c Various fix to build again the project 2025-03-28 12:52:47 +01:00
cc9396c4b8 Neutralize chat page 2025-03-28 12:52:00 +01:00
51c906866e Neutralize process page 2025-03-28 12:51:12 +01:00
3f42cb27a7 Slight refactoring and cleanup 2025-03-26 14:44:00 +01:00
2601418aaf pass membersList as argument when needed 2025-03-26 14:43:27 +01:00
455fe53fe2 getRoles and getPublicData both returns value in the first uncommited state if necessary 2025-03-26 14:41:35 +01:00
2f847514f0 Refactor getLastCommitedState 2025-03-26 14:41:00 +01:00
a0888f8c90 handleHandshakeMsg check we're part of a new state before adding it 2025-03-26 14:40:41 +01:00
f2e2aeaa9a One device pairing 2025-03-26 14:39:59 +01:00
2855365851 Don't call openPairingConfirmationModal from handleApiReturn 2025-03-26 13:42:31 +01:00
bb277706fd load myprocesses ok 2025-03-24 17:02:24 +01:00
05dddd9567 new member name service ok 2025-03-21 10:47:18 +01:00
d54ce71f02 delete device mocked ok 2025-03-19 16:22:44 +01:00
a42141246d load pairing device ok 2025-03-19 16:09:12 +01:00
124 changed files with 29454 additions and 5037 deletions

1
.ci-build Normal file
View File

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

2
.ci-trigger Normal file
View File

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

95
.cursorignore Normal file
View File

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

165
.cursorrules Normal file
View File

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

95
.dockerignore Normal file
View File

@ -0,0 +1,95 @@
# 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
View File

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

View File

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

@ -0,0 +1,69 @@
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,7 +1,95 @@
target/
pkg/
Cargo.lock
node_modules/
dist/
.vscode
public/ssl/
# 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/

17
CHANGELOG.md Normal file
View File

@ -0,0 +1,17 @@
# 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,13 +1,44 @@
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"]
# 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"]

103
README.md
View File

@ -4,49 +4,70 @@
## 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
## 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)

BIN
dist/assets/4nk_image.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
dist/assets/4nk_revoke.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

BIN
dist/assets/bgd.webp vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

BIN
dist/assets/camera.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

34
dist/assets/home.js vendored Executable file
View File

@ -0,0 +1,34 @@
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();
}
}

BIN
dist/assets/qr_code.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

111
dist/authorized-client.html vendored Normal file
View File

@ -0,0 +1,111 @@
<!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 Normal file

File diff suppressed because it is too large Load Diff

1885
dist/sdk_client-6DvC0K4R.mjs vendored Normal file

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,877 @@
: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 Executable file

File diff suppressed because it is too large Load Diff

597
dist/style/chat.css vendored Executable file
View File

@ -0,0 +1,597 @@
/* 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 Executable file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,14 @@
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;

3
dist/types/index.d.ts vendored Normal file
View File

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

View File

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

@ -0,0 +1,10 @@
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 Normal file
View File

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

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

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

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

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

View File

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

View File

@ -0,0 +1,24 @@
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;
}

56
dist/types/models/process.model.d.ts vendored Normal file
View File

@ -0,0 +1,56 @@
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"
}

60
dist/types/models/signature.models.d.ts vendored Normal file
View File

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

@ -0,0 +1,12 @@
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 };

98
dist/types/pages/account/account.d.ts vendored Normal file
View File

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

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

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

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

4
dist/types/pages/account/process.d.ts vendored Normal file
View File

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

View File

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

4
dist/types/pages/home/home.d.ts vendored Normal file
View File

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

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

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

View File

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

@ -0,0 +1,71 @@
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 };

5
dist/types/router.d.ts vendored Normal file
View File

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

@ -0,0 +1,43 @@
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;

28
dist/types/services/modal.service.d.ts vendored Normal file
View File

@ -0,0 +1,28 @@
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 {};

171
dist/types/services/service.d.ts vendored Normal file
View File

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

@ -0,0 +1,4 @@
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>;

18
dist/types/services/token.d.ts vendored Normal file
View File

@ -0,0 +1,18 @@
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 {};

1
dist/types/utils/document.utils.d.ts vendored Normal file
View File

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

4
dist/types/utils/html.utils.d.ts vendored Normal file
View File

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

12
dist/types/utils/messageMock.d.ts vendored Normal file
View File

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

@ -0,0 +1,23 @@
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 {};

5
dist/types/utils/service.utils.d.ts vendored Normal file
View File

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

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

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

5
dist/types/websockets.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
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;

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -9,6 +9,7 @@
<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>
@ -17,9 +18,18 @@
</div>
<!-- <script type="module" src="/src/index.ts"></script> -->
<script type="module">
import { init } from '/src/router.ts';
// 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';
(async () => {
await init();
try {
await initRouter();
} catch (error) {
console.error('Failed to initialize application:', error);
}
})();
</script>
</body>

3833
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,12 @@
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev1/pkg ../sdk_client --target bundler --dev",
"build_wasm": "wasm-pack build --out-dir ../ihm_client/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}\""
"prettify": "prettier --config ./.prettierrc --write \"src/**/*{.ts,.html,.css,.js}\"",
"build:dist": "tsc -p tsconfig.build.json"
},
"keywords": [],
"author": "",
@ -30,11 +31,15 @@
},
"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 Normal file
View File

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

17
pkg/package.json Normal file
View File

@ -0,0 +1,17 @@
{
"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 Normal file
View File

@ -0,0 +1,249 @@
/* 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[];
}

5
pkg/sdk_client.js Normal file
View File

@ -0,0 +1,5 @@
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();

1691
pkg/sdk_client_bg.js Normal file

File diff suppressed because it is too large Load Diff

BIN
pkg/sdk_client_bg.wasm Normal file

Binary file not shown.

73
pkg/sdk_client_bg.wasm.d.ts vendored Normal file
View File

@ -0,0 +1,73 @@
/* 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

@ -0,0 +1,111 @@
<!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>

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

5
public/favicon.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -77,6 +77,99 @@ 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,24 +427,43 @@ body {
/* Style pour la modal de confirmation */
.confirm-delete-modal {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
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;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1100;
max-width: 400px;
width: 90%;
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 {
@ -452,25 +471,27 @@ body {
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.confirm-btn {
.confirm-delete-buttons .confirm-btn {
background-color: #dc3545;
color: white;
}
.cancel-btn {
.confirm-delete-buttons .confirm-btn:hover {
background-color: #c82333;
}
.confirm-delete-buttons .cancel-btn {
background-color: #6c757d;
color: white;
}
.delete-account-section {
border-top: 1px solid #eee;
padding-top: 15px;
margin-top: 15px;
.confirm-delete-buttons .cancel-btn:hover {
background-color: #5a6268;
}
/*-------------------------------------- Export--------------------------------------*/
.export-section {
margin: 20px 0;
@ -1423,3 +1444,64 @@ 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;
}

BIN
sdk_client-0.1.3.tgz Normal file

Binary file not shown.

View File

@ -9,7 +9,7 @@ let notifications = [];
export async function unpair() {
const service = await Services.getInstance();
await service.unpairDevice();
navigate('home');
await 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();
setNotification(data);
const data = service.getNotifications() || [];
await 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');
navigate('home');
await 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(spAddress);
await prepareAndSendPairingTx();
} catch (e) {
console.error('Failed to pair:', e);
}

View File

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

@ -0,0 +1,61 @@
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,39 +1,3 @@
// 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);
// }
// });
export { default as Services } from './services/service';
export { default as Database } from './services/database.service';
export { MessageType } from './models/process.model';

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, ChatComponent, ChatElement, AccountComponent, AccountElement };
export { SignatureComponent, SignatureElement, 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";
import { Device, Process, SecretsStore } from "../../pkg/sdk_client.js";
export interface BackUp {
device: Device,

View File

@ -21,3 +21,45 @@ 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,23 +20,25 @@ declare global {
updateNavbarBanner: (bannerUrl: string) => void;
saveBannerToLocalStorage: (bannerUrl: string) => void;
loadSavedBanner: () => void;
cancelAddRow: () => 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;
addRow: () => void;
confirmRow: () => void;
cancelRow: () => void;
deleteRow: (button: HTMLButtonElement) => void;
addRowPairing: () => void;
confirmRowPairing: () => void;
cancelRowPairing: () => void;
deleteRowPairing: (button: HTMLButtonElement) => void;
generateRecoveryWords: () => string[];
exportUserData: () => void;
updateActionButtons: () => void;
showQRCodeModal: (address: string) => void;
showQRCodeModal: (pairingId: string) => void;
}
}
@ -47,11 +49,31 @@ 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;
@ -141,7 +163,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>
@ -154,27 +176,33 @@ 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.showData()">Data 💾</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> -->
</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="data-content"></div>
<div id="process-creation-content"></div>
<div id="document-validation-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();
@ -184,10 +212,10 @@ class AccountElement extends HTMLElement {
window.handleLogout = () => this.handleLogout();
window.confirmDeleteAccount = () => this.confirmDeleteAccount();
window.showContractPopup = (contractId: string) => this.showContractPopup(contractId);
window.addRow = () => this.addRow();
window.deleteRow = (button: HTMLButtonElement) => this.deleteRow(button);
window.confirmRow = () => this.confirmRow();
window.cancelRow = () => this.cancelRow();
window.addRowPairing = () => this.addRowPairing();
window.deleteRowPairing = (button: HTMLButtonElement) => this.deleteRowPairing(button);
window.confirmRowPairing = () => this.confirmRowPairing();
window.cancelRowPairing = () => this.cancelRowPairing();
window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl);
window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl);
window.loadSavedBanner = () => this.loadSavedBanner();
@ -199,7 +227,7 @@ class AccountElement extends HTMLElement {
window.updateActionButtons = () => this.updateActionButtons();
window.openAvatarPopup = () => this.openAvatarPopup();
window.closeAvatarPopup = () => this.closeAvatarPopup();
window.showQRCodeModal = (address: string) => this.showQRCodeModal(address);
window.showQRCodeModal = (pairingId: string) => this.showQRCodeModal(pairingId);
if (!localStorage.getItem('rows')) {
localStorage.setItem('rows', JSON.stringify(defaultRows));
@ -210,22 +238,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);
}
@ -242,17 +270,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 {
@ -293,13 +321,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;
}
@ -340,7 +368,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 = `
@ -353,7 +381,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) {
@ -390,7 +418,7 @@ private exportRecovery(): void {
if (result.isConfirmed) {
const recoveryWords = this.generateRecoveryWords();
localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords));
Swal.fire({
title: 'Your Recovery Words',
html: `
@ -419,7 +447,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;
@ -488,7 +516,7 @@ private getConfirmFunction(): string {
case 'process':
return 'window.confirmProcessRow()';
default:
return 'window.confirmRow()';
return 'window.confirmRowPairing()';
}
}
@ -499,50 +527,94 @@ private getCancelFunction(): string {
case 'process':
return 'window.cancelProcessRow()';
default:
return 'window.cancelRow()';
return 'window.cancelRowPairing()';
}
}
// Fonctions de gestion des tableaux
private addRow(): void {
private async addRowPairing(): Promise<void> {
if (isAddingRow) return;
isAddingRow = true;
const table = this.shadowRoot?.querySelector<HTMLTableElement>('#pairing-table tbody');
if (!table) 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 (index === 2) {
input.readOnly = true;
}
cell.appendChild(input);
// 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;
});
const deleteCell = currentRow.insertCell();
deleteCell.style.width = '40px';
// Gérer la confirmation
confirmButton?.addEventListener('click', () => {
const spAddress = spAddressInput?.value.trim();
const deviceName = deviceNameInput?.value.trim();
const spEmojis = spEmojisInput?.value.trim();
this.updateActionButtons();
if (!spAddress || !deviceName) {
this.showAlert('Please fill in all required fields');
return;
}
//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;
});
// Gérer l'annulation
cancelButton?.addEventListener('click', () => {
modal.remove();
isAddingRow = false;
});
}
// Fonctions de mise à jour de l'interface
@ -552,18 +624,17 @@ 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.deleteRow(this)">
<button class="delete-button" onclick="window.deleteRowPairing(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>
@ -575,7 +646,7 @@ private updateTableContent(rows: Row[]): void {
private confirmRow(): void {
private confirmRowPairing(): void {
if (!currentRow) return;
const inputs = currentRow.getElementsByTagName('input');
@ -606,18 +677,18 @@ private confirmRow(): void {
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
this.updateTableContent(rows);
}
private cancelRow(): void {
private cancelRowPairing(): void {
if (!currentRow) return;
currentRow.remove();
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
}
@ -626,11 +697,11 @@ private resetButtonContainer(): void {
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a line</button>
`;
}
private deleteRow(button: HTMLButtonElement): void {
private deleteRowPairing(button: HTMLButtonElement): void {
const row = button.closest('tr');
if (!row) return;
@ -643,21 +714,53 @@ private deleteRow(button: HTMLButtonElement): void {
return;
}
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%)';
// 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>
`;
setTimeout(() => {
row.remove();
this.shadowRoot?.appendChild(modal);
// 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));
}
}, 300);
// 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();
});
}
private editDeviceName(cell: HTMLTableCellElement): void {
@ -668,7 +771,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') {
@ -682,7 +785,7 @@ private editDeviceName(cell: HTMLTableCellElement): void {
input.focus();
}
private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void {
private async finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): Promise<void> {
const newValue = input.value.trim();
if (newValue === '') {
cell.textContent = cell.getAttribute('data-original-value') || '';
@ -690,102 +793,103 @@ private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void
return;
}
const row = cell.closest('tr');
if (!row) 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 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));
// 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');
}
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 showProcess(): void {
//console.log("showProcess called");
currentMode = 'process';
private async showProcessCreation(): Promise<void> {
this.hideAllContent();
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);
const container = this.shadowRoot?.getElementById('process-creation-content');
if (container) {
getProcessCreation(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 showDocumentValidation(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('document-validation-content');
if (container) {
getDocumentValidation(container);
}
}
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);
@ -793,15 +897,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>`
@ -852,9 +956,11 @@ private handleLogout(): void {
// Fonctions de gestion des contrats
private showContractPopup(contractId: string) {
// Empêcher la navigation par défaut
event?.preventDefault();
private showContractPopup(contractId: string, event?: Event) {
if (event) {
event.preventDefault();
}
// Check if the contract exists in mockContracts
const contract = mockContracts[contractId as keyof typeof mockContracts];
if (!contract) {
@ -862,7 +968,6 @@ private showContractPopup(contractId: string) {
return;
}
// Créer la popup
const popup = document.createElement('div');
popup.className = 'contract-popup-overlay';
popup.innerHTML = `
@ -881,13 +986,11 @@ private showContractPopup(contractId: string) {
</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();
@ -896,7 +999,7 @@ private showContractPopup(contractId: string) {
// Fonction utilitaire pour cacher tous les contenus
private hideAllContent(): void {
const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content'];
const contents = ['pairing-content', 'wallet-content', 'process-content', 'process-creation-content', 'data-content', 'document-validation-content'];
contents.forEach(id => {
const element = this.shadowRoot?.getElementById(id);
if (element) {
@ -909,13 +1012,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';
@ -930,7 +1033,6 @@ 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>
@ -940,30 +1042,29 @@ private async showPairing(): Promise<void> {
<tbody></tbody>
</table>
<div class="button-container">
<button class="add-row-button button-style" onclick="window.addRow()">Add a line</button>
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a device</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]?.metadata?.userName
|| pairingProcess?.states?.[0]?.metadata?.name
const userName = pairingProcess?.states?.[0]?.public_data?.memberPublicName
|| localStorage.getItem('userName')
console.log('Username found:', userName);
const newRow = {
@ -984,7 +1085,7 @@ private async showPairing(): Promise<void> {
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
}
}
this.updateTableContent(rows);
}
}
@ -995,11 +1096,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';
@ -1021,7 +1122,7 @@ private showWallet(): void {
</div>
</div>
`;
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]');
this.updateWalletTableContent(rows);
}
@ -1044,10 +1145,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';
@ -1069,7 +1170,7 @@ private showData(): void {
</table>
</div>
`;
const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]');
this.updateDataTableContent(rows);
}
@ -1098,7 +1199,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>
@ -1136,20 +1237,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 {
@ -1209,16 +1310,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>
@ -1236,7 +1337,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();
@ -1275,10 +1376,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);
@ -1298,10 +1399,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);
@ -1354,7 +1455,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');
@ -1370,7 +1471,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;
@ -1378,7 +1479,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;
@ -1402,11 +1503,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;
@ -1444,7 +1545,7 @@ private initializeEventListeners() {
input.type = 'text';
input.value = currentValue;
input.className = 'edit-input';
field.textContent = '';
field.appendChild(input);
field.classList.add('editing');
@ -1460,16 +1561,16 @@ private initializeEventListeners() {
}
}
private showQRCodeModal(address: string): void {
private showQRCodeModal(pairingId: 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=${address}"
alt="QR Code Large"
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${pairingId}"
alt="QR Code Large"
class="qr-code-large">
<div class="qr-address">${decodeURIComponent(address)}</div>
<div class="qr-address">${decodeURIComponent(pairingId)}</div>
</div>
`;

View File

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

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

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

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