Compare commits
8 Commits
7ea4ef1920
...
bbb0c12506
| Author | SHA1 | Date | |
|---|---|---|---|
| bbb0c12506 | |||
| 614569f5aa | |||
| 465a4a3c18 | |||
| dddbe04a2d | |||
| f78ed88cb1 | |||
| 696fc5833c | |||
| 059f3e2e33 | |||
| 9d30e84bd2 |
385
WORKFLOW_PAIRING_ANALYSIS.md
Normal file
385
WORKFLOW_PAIRING_ANALYSIS.md
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
# Analyse du Workflow de Pairing - Processus de Couplage d'Appareils
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Ce document présente une analyse complète du workflow de pairing (processus de couplage) dans l'écosystème 4NK, qui permet à deux appareils de s'associer de manière sécurisée. Le processus implique à la fois le code TypeScript côté client (`ihm_client_dev2`) et les fonctionnalités WebAssembly compilées depuis Rust (`sdk_client`).
|
||||||
|
|
||||||
|
## Vue d'ensemble du Processus
|
||||||
|
|
||||||
|
Le workflow de pairing est déclenché par la fonction `onCreateButtonClick()` et suit une séquence d'actions coordonnées entre l'interface utilisateur, les services JavaScript et le module WebAssembly.
|
||||||
|
|
||||||
|
## Étapes Détaillées du Workflow
|
||||||
|
|
||||||
|
### 1. Déclenchement Initial
|
||||||
|
|
||||||
|
**Fonction :** `onCreateButtonClick()`
|
||||||
|
**Localisation :** `src/utils/sp-address.utils.ts:152-160`
|
||||||
|
|
||||||
|
```@/home/ank/dev/ihm_client_dev2/src/utils/sp-address.utils.ts#152:160
|
||||||
|
async function onCreateButtonClick() {
|
||||||
|
try {
|
||||||
|
await prepareAndSendPairingTx();
|
||||||
|
// Don't call confirmPairing immediately - it will be called when the pairing process is complete
|
||||||
|
console.log('Pairing process initiated. Waiting for completion...');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`onCreateButtonClick error: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Actions :**
|
||||||
|
- Appel de `prepareAndSendPairingTx()`
|
||||||
|
- Gestion d'erreur avec logging
|
||||||
|
- Attente de la complétion du processus
|
||||||
|
|
||||||
|
### 2. Préparation et Envoi de la Transaction de Pairing
|
||||||
|
|
||||||
|
**Fonction :** `prepareAndSendPairingTx()`
|
||||||
|
**Localisation :** `src/utils/sp-address.utils.ts:162-201`
|
||||||
|
|
||||||
|
**Actions principales :**
|
||||||
|
|
||||||
|
#### 2.1 Initialisation du Service
|
||||||
|
```typescript
|
||||||
|
const service = await Services.getInstance();
|
||||||
|
const relayAddress = service.getAllRelays();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Création du Processus de Pairing
|
||||||
|
```typescript
|
||||||
|
const createPairingProcessReturn = await service.createPairingProcess("", []);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.3 Vérification des Connexions
|
||||||
|
```typescript
|
||||||
|
await service.checkConnections(
|
||||||
|
createPairingProcessReturn.updated_process.current_process,
|
||||||
|
createPairingProcessReturn.updated_process.current_process.states[0].state_id
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.4 Configuration des Identifiants
|
||||||
|
```typescript
|
||||||
|
service.setProcessId(createPairingProcessReturn.updated_process.process_id);
|
||||||
|
service.setStateId(createPairingProcessReturn.updated_process.current_process.states[0].state_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.5 Mise à Jour du Device
|
||||||
|
```typescript
|
||||||
|
const currentDevice = await service.getDeviceFromDatabase();
|
||||||
|
if (currentDevice) {
|
||||||
|
currentDevice.pairing_process_commitment = createPairingProcessReturn.updated_process.process_id;
|
||||||
|
await service.saveDeviceInDatabase(currentDevice);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.6 Traitement du Retour API
|
||||||
|
```typescript
|
||||||
|
await service.handleApiReturn(createPairingProcessReturn);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Création du Processus de Pairing (Côté Service)
|
||||||
|
|
||||||
|
**Fonction :** `createPairingProcess()`
|
||||||
|
**Localisation :** `src/services/service.ts:334-371`
|
||||||
|
|
||||||
|
**Paramètres :**
|
||||||
|
- `userName`: Nom d'utilisateur (vide dans ce cas)
|
||||||
|
- `pairWith`: Liste des adresses à coupler (vide initialement)
|
||||||
|
|
||||||
|
**Actions :**
|
||||||
|
|
||||||
|
#### 3.1 Vérification de l'État de Pairing
|
||||||
|
```typescript
|
||||||
|
if (this.sdkClient.is_paired()) {
|
||||||
|
throw new Error('Device already paired');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 Préparation des Données
|
||||||
|
```typescript
|
||||||
|
const myAddress: string = this.sdkClient.get_address();
|
||||||
|
pairWith.push(myAddress);
|
||||||
|
|
||||||
|
const privateData = {
|
||||||
|
description: 'pairing',
|
||||||
|
counter: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const publicData = {
|
||||||
|
memberPublicName: userName,
|
||||||
|
pairedAddresses: pairWith,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.3 Définition des Rôles
|
||||||
|
```typescript
|
||||||
|
const roles: Record<string, RoleDefinition> = {
|
||||||
|
pairing: {
|
||||||
|
members: [],
|
||||||
|
validation_rules: [{
|
||||||
|
quorum: 1.0,
|
||||||
|
fields: validation_fields,
|
||||||
|
min_sig_member: 1.0,
|
||||||
|
}],
|
||||||
|
storages: [STORAGEURL]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.4 Appel de Création du Processus
|
||||||
|
```typescript
|
||||||
|
return this.createProcess(privateData, publicData, roles);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Création du Processus (Côté WebAssembly)
|
||||||
|
|
||||||
|
**Fonction :** `create_new_process()`
|
||||||
|
**Localisation :** `sdk_client/src/api.rs:1218-1264`
|
||||||
|
|
||||||
|
**Actions principales :**
|
||||||
|
|
||||||
|
#### 4.1 Validation des Rôles
|
||||||
|
```rust
|
||||||
|
if roles.is_empty() {
|
||||||
|
return Err(ApiError { message: "Roles can't be empty".to_owned() });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.2 Création de la Transaction
|
||||||
|
```rust
|
||||||
|
let relay_address: SilentPaymentAddress = relay_address.try_into()?;
|
||||||
|
let tx = create_transaction_for_addresses(&local_device, &freezed_utxos, &vec![relay_address], fee_rate_checked)?;
|
||||||
|
let unsigned_transaction = SpClient::finalize_transaction(tx)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.3 Gestion des Secrets Partagés
|
||||||
|
```rust
|
||||||
|
let new_secrets = get_shared_secrets_in_transaction(&unsigned_transaction, &vec![relay_address])?;
|
||||||
|
let mut shared_secrets = lock_shared_secrets()?;
|
||||||
|
for (address, secret) in new_secrets {
|
||||||
|
shared_secrets.confirm_secret_for_address(secret, address);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4.4 Création de l'État du Processus
|
||||||
|
```rust
|
||||||
|
let process_id = OutPoint::new(unsigned_transaction.unsigned_tx.as_ref().unwrap().txid(), 0);
|
||||||
|
let mut new_state = ProcessState::new(process_id, private_data.clone(), public_data.clone(), roles.clone())?;
|
||||||
|
let mut process = Process::new(process_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Vérification des Connexions
|
||||||
|
|
||||||
|
**Fonction :** `checkConnections()`
|
||||||
|
**Localisation :** `src/services/service.ts:232-289`
|
||||||
|
|
||||||
|
**Actions :**
|
||||||
|
|
||||||
|
#### 5.1 Validation des États
|
||||||
|
```typescript
|
||||||
|
if (process.states.length < 2) {
|
||||||
|
throw new Error('Process doesn\'t have any state yet');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 Extraction des Rôles et Membres
|
||||||
|
```typescript
|
||||||
|
let roles: Record<string, RoleDefinition> | null = null;
|
||||||
|
if (!stateId) {
|
||||||
|
roles = process.states[process.states.length - 2].roles;
|
||||||
|
} else {
|
||||||
|
roles = process.states.find(state => state.state_id === stateId)?.roles || null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.3 Gestion des Processus de Pairing
|
||||||
|
```typescript
|
||||||
|
if (members.size === 0) {
|
||||||
|
// This must be a pairing process
|
||||||
|
const publicData = process.states[0]?.public_data;
|
||||||
|
if (!publicData || !publicData['pairedAddresses']) {
|
||||||
|
throw new Error('Not a pairing process');
|
||||||
|
}
|
||||||
|
const decodedAddresses = this.decodeValue(publicData['pairedAddresses']);
|
||||||
|
members.add({ sp_addresses: decodedAddresses });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.4 Connexion aux Adresses Non Connectées
|
||||||
|
```typescript
|
||||||
|
if (unconnectedAddresses && unconnectedAddresses.size != 0) {
|
||||||
|
const apiResult = await this.connectAddresses(Array.from(unconnectedAddresses));
|
||||||
|
await this.handleApiReturn(apiResult);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Traitement du Retour API
|
||||||
|
|
||||||
|
**Fonction :** `handleApiReturn()`
|
||||||
|
**Localisation :** `src/services/service.ts:648-775`
|
||||||
|
|
||||||
|
**Actions principales :**
|
||||||
|
|
||||||
|
#### 6.1 Signature de Transaction
|
||||||
|
```typescript
|
||||||
|
if (apiReturn.partial_tx) {
|
||||||
|
const res = this.sdkClient.sign_transaction(apiReturn.partial_tx);
|
||||||
|
apiReturn.new_tx_to_send = res.new_tx_to_send;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.2 Envoi de Transaction
|
||||||
|
```typescript
|
||||||
|
if (apiReturn.new_tx_to_send && apiReturn.new_tx_to_send.transaction.length != 0) {
|
||||||
|
this.sendNewTxMessage(JSON.stringify(apiReturn.new_tx_to_send));
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.3 Gestion des Secrets
|
||||||
|
```typescript
|
||||||
|
if (apiReturn.secrets) {
|
||||||
|
const unconfirmedSecrets = apiReturn.secrets.unconfirmed_secrets;
|
||||||
|
const confirmedSecrets = apiReturn.secrets.shared_secrets;
|
||||||
|
// Sauvegarde en base de données
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.4 Mise à Jour du Processus
|
||||||
|
```typescript
|
||||||
|
if (apiReturn.updated_process) {
|
||||||
|
const updatedProcess = apiReturn.updated_process;
|
||||||
|
const processId: string = updatedProcess.process_id;
|
||||||
|
await this.saveProcessToDb(processId, updatedProcess.current_process);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6.5 Confirmation Automatique du Pairing
|
||||||
|
```typescript
|
||||||
|
// Check if this is a pairing process that's ready for confirmation
|
||||||
|
const existingDevice = await this.getDeviceFromDatabase();
|
||||||
|
if (existingDevice && existingDevice.pairing_process_commitment === processId) {
|
||||||
|
const lastState = updatedProcess.current_process.states[updatedProcess.current_process.states.length - 1];
|
||||||
|
if (lastState && lastState.public_data && lastState.public_data['pairedAddresses']) {
|
||||||
|
console.log('Pairing process updated with paired addresses, confirming pairing...');
|
||||||
|
await this.confirmPairing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Confirmation du Pairing
|
||||||
|
|
||||||
|
**Fonction :** `confirmPairing()`
|
||||||
|
**Localisation :** `src/services/service.ts:793-851`
|
||||||
|
|
||||||
|
**Actions :**
|
||||||
|
|
||||||
|
#### 7.1 Récupération du Processus de Pairing
|
||||||
|
```typescript
|
||||||
|
const existingDevice = await this.getDeviceFromDatabase();
|
||||||
|
const pairingProcessId = existingDevice.pairing_process_commitment;
|
||||||
|
const myPairingProcess = await this.getProcess(pairingProcessId);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.2 Extraction des Adresses Couplées
|
||||||
|
```typescript
|
||||||
|
let myPairingState = this.getLastCommitedState(myPairingProcess);
|
||||||
|
const encodedSpAddressList = myPairingState.public_data['pairedAddresses'];
|
||||||
|
const spAddressList = this.decodeValue(encodedSpAddressList);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.3 Pairing Effectif du Device
|
||||||
|
```typescript
|
||||||
|
this.sdkClient.unpair_device(); // Clear any existing pairing
|
||||||
|
this.sdkClient.pair_device(pairingProcessId, spAddressList);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.4 Sauvegarde du Device Mis à Jour
|
||||||
|
```typescript
|
||||||
|
const newDevice = this.dumpDeviceFromMemory();
|
||||||
|
newDevice.pairing_process_commitment = pairingProcessId;
|
||||||
|
await this.saveDeviceInDatabase(newDevice);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Technique
|
||||||
|
|
||||||
|
### Côté Client (TypeScript)
|
||||||
|
|
||||||
|
**Composants principaux :**
|
||||||
|
- **Interface Utilisateur :** Gestion des événements de clic et affichage des emojis
|
||||||
|
- **Services :** Orchestration du workflow et communication avec WebAssembly
|
||||||
|
- **Base de Données :** Stockage des devices, secrets et processus
|
||||||
|
- **WebSocket :** Communication avec les relais
|
||||||
|
|
||||||
|
### Côté WebAssembly (Rust)
|
||||||
|
|
||||||
|
**Fonctionnalités clés :**
|
||||||
|
- **Gestion des Wallets :** Création et manipulation des portefeuilles Silent Payment
|
||||||
|
- **Cryptographie :** Génération de secrets partagés et signatures
|
||||||
|
- **Transactions :** Création et finalisation des transactions Bitcoin
|
||||||
|
- **Processus :** Gestion des états et validation des changements
|
||||||
|
|
||||||
|
## Types de Données Importants
|
||||||
|
|
||||||
|
### Device
|
||||||
|
```typescript
|
||||||
|
interface Device {
|
||||||
|
sp_wallet: SpWallet;
|
||||||
|
pairing_process_commitment: OutPoint | null;
|
||||||
|
paired_member: Member;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Process
|
||||||
|
```typescript
|
||||||
|
interface Process {
|
||||||
|
states: ProcessState[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApiReturn
|
||||||
|
```typescript
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Flux de Communication
|
||||||
|
|
||||||
|
1. **UI → Service :** Déclenchement du processus via `onCreateButtonClick()`
|
||||||
|
2. **Service → WebAssembly :** Appel des fonctions WASM pour la création du processus
|
||||||
|
3. **WebAssembly → Service :** Retour des données via `ApiReturn`
|
||||||
|
4. **Service → WebSocket :** Envoi des messages aux relais
|
||||||
|
5. **WebSocket → Service :** Réception des réponses des relais
|
||||||
|
6. **Service → Database :** Sauvegarde des états et secrets
|
||||||
|
7. **Service → UI :** Mise à jour de l'interface utilisateur
|
||||||
|
|
||||||
|
## Points d'Attention
|
||||||
|
|
||||||
|
### Sécurité
|
||||||
|
- Les secrets partagés sont générés côté WebAssembly (Rust)
|
||||||
|
- Les transactions sont signées de manière sécurisée
|
||||||
|
- Les données sensibles sont chiffrées avant stockage
|
||||||
|
|
||||||
|
### Asynchronisme
|
||||||
|
- Le processus est entièrement asynchrone
|
||||||
|
- Utilisation de promesses et callbacks pour la coordination
|
||||||
|
- Gestion d'erreur à chaque étape critique
|
||||||
|
|
||||||
|
### État du Processus
|
||||||
|
- Suivi de l'état via `processId` et `stateId`
|
||||||
|
- Sauvegarde persistante en base de données
|
||||||
|
- Récupération possible en cas d'interruption
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Le workflow de pairing représente un processus complexe mais bien structuré qui coordonne l'interface utilisateur TypeScript avec les fonctionnalités cryptographiques Rust via WebAssembly. Cette architecture permet une sécurité maximale tout en maintenant une expérience utilisateur fluide. Le processus est conçu pour être résilient aux interruptions et permet une récupération d'état en cas de problème.
|
||||||
|
|
||||||
|
La séparation claire entre les responsabilités (UI, orchestration, cryptographie) facilite la maintenance et les évolutions futures du système de pairing.
|
||||||
@ -26,7 +26,7 @@ server {
|
|||||||
|
|
||||||
location /storage/ {
|
location /storage/ {
|
||||||
rewrite ^/storage(/.*)$ $1 break;
|
rewrite ^/storage(/.*)$ $1 break;
|
||||||
proxy_pass http://localhost:8080;
|
proxy_pass http://localhost:8081;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "Upgrade";
|
proxy_set_header Connection "Upgrade";
|
||||||
|
|||||||
79
nginx.prod.conf
Normal file
79
nginx.prod.conf
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# --- 1. REDIRECTION HTTP VERS HTTPS ---
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name dev2.4nkweb.com;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 2. CONFIGURATION HTTPS PRINCIPALE ---
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name dev2.4nkweb.com;
|
||||||
|
|
||||||
|
# Chemins des certificats SSL
|
||||||
|
ssl_certificate /etc/letsencrypt/live/dev2.4nkweb.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/dev2.4nkweb.com/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
|
||||||
|
# --- LOCATION POUR VITE (Front-end + HMR WebSocket) ---
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3003;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- LOCATION POUR L'AUTRE WEBSOCKET (port 8090) ---
|
||||||
|
location /ws/ {
|
||||||
|
proxy_pass http://localhost:8090;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
proxy_read_timeout 86400;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- LOCATION POUR SDK_STORAGE (port 8081) ---
|
||||||
|
location /storage/ {
|
||||||
|
# Gestion du préflight CORS
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
|
||||||
|
add_header 'Access-Control-Max-Age' 86400;
|
||||||
|
add_header 'Content-Length' 0;
|
||||||
|
add_header 'Content-Type' 'text/plain';
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
# Headers CORS pour les requêtes réelles
|
||||||
|
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||||
|
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||||
|
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, X-Requested-With' always;
|
||||||
|
rewrite ^/storage(/.*)$ $1 break;
|
||||||
|
proxy_pass http://localhost:8081;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- LOCATION POUR TON API (port 8091) ---
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://localhost:8091;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS headers
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
|
||||||
|
add_header Access-Control-Allow-Headers "Authorization,Content-Type,Accept,X-Requested-With" always;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sdk_client",
|
"name": "sdk_client",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"build_wasm": "wasm-pack build --out-dir ../ihm_client/pkg ../sdk_client --target bundler --dev",
|
"build_wasm": "wasm-pack build --out-dir ../ihm_client_dev2/pkg ../sdk_client --target bundler --dev",
|
||||||
"start": "vite --host 0.0.0.0",
|
"start": "vite --host 0.0.0.0",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"deploy": "sudo cp -r dist/* /var/www/html/",
|
"deploy": "sudo cp -r dist/* /var/www/html/",
|
||||||
|
|||||||
22
src/main.ts
22
src/main.ts
@ -1,20 +1,20 @@
|
|||||||
import { SignatureComponent } from './pages/signature/signature-component';
|
import { SignatureComponent } from './pages/signature/signature-component';
|
||||||
import { SignatureElement } from './pages/signature/signature';
|
import { SignatureElement } from './pages/signature/signature';
|
||||||
/*import { ChatComponent } from './pages/chat/chat-component';
|
// import { ChatComponent } from './pages/chat/chat-component';
|
||||||
import { ChatElement } from './pages/chat/chat';*/
|
// import { ChatElement } from './pages/chat/chat';
|
||||||
import { AccountComponent } from './pages/account/account-component';
|
// import { AccountComponent } from './pages/account/account-component';
|
||||||
import { AccountElement } from './pages/account/account';
|
// import { AccountElement } from './pages/account/account';
|
||||||
|
|
||||||
export { SignatureComponent, SignatureElement, AccountComponent, AccountElement };
|
export { SignatureComponent, SignatureElement };
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'signature-component': SignatureComponent;
|
'signature-component': SignatureComponent;
|
||||||
'signature-element': SignatureElement;
|
'signature-element': SignatureElement;
|
||||||
/*'chat-component': ChatComponent;
|
// 'chat-component': ChatComponent;
|
||||||
'chat-element': ChatElement;*/
|
// 'chat-element': ChatElement;
|
||||||
'account-component': AccountComponent;
|
// 'account-component': AccountComponent;
|
||||||
'account-element': AccountElement;
|
// 'account-element': AccountElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +25,6 @@ if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB) {
|
|||||||
customElements.define('signature-element', SignatureElement);
|
customElements.define('signature-element', SignatureElement);
|
||||||
/*customElements.define('chat-component', ChatComponent);
|
/*customElements.define('chat-component', ChatComponent);
|
||||||
customElements.define('chat-element', ChatElement);*/
|
customElements.define('chat-element', ChatElement);*/
|
||||||
customElements.define('account-component', AccountComponent);
|
// customElements.define('account-component', AccountComponent);
|
||||||
customElements.define('account-element', AccountElement);
|
// customElements.define('account-element', AccountElement);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,62 +1,62 @@
|
|||||||
import { AccountElement } from './account';
|
// import { AccountElement } from './account';
|
||||||
import accountCss from '../../../public/style/account.css?raw';
|
// import accountCss from '../../../style/account.css?raw';
|
||||||
import Services from '../../services/service.js';
|
// import Services from '../../services/service.js';
|
||||||
|
|
||||||
class AccountComponent extends HTMLElement {
|
// class AccountComponent extends HTMLElement {
|
||||||
_callback: any;
|
// _callback: any;
|
||||||
accountElement: AccountElement | null = null;
|
// accountElement: AccountElement | null = null;
|
||||||
|
|
||||||
constructor() {
|
// constructor() {
|
||||||
super();
|
// super();
|
||||||
console.log('INIT');
|
// console.log('INIT');
|
||||||
this.attachShadow({ mode: 'open' });
|
// this.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
this.accountElement = this.shadowRoot?.querySelector('account-element') || null;
|
// this.accountElement = this.shadowRoot?.querySelector('account-element') || null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
connectedCallback() {
|
// connectedCallback() {
|
||||||
console.log('CALLBACKs');
|
// console.log('CALLBACKs');
|
||||||
this.render();
|
// this.render();
|
||||||
this.fetchData();
|
// this.fetchData();
|
||||||
|
|
||||||
if (!customElements.get('account-element')) {
|
// if (!customElements.get('account-element')) {
|
||||||
customElements.define('account-element', AccountElement);
|
// customElements.define('account-element', AccountElement);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async fetchData() {
|
// async fetchData() {
|
||||||
if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB === false) {
|
// if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB === false) {
|
||||||
const data = await (window as any).myService?.getProcesses();
|
// const data = await (window as any).myService?.getProcesses();
|
||||||
} else {
|
// } else {
|
||||||
const service = await Services.getInstance();
|
// const service = await Services.getInstance();
|
||||||
const data = await service.getProcesses();
|
// const data = await service.getProcesses();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
set callback(fn) {
|
// set callback(fn) {
|
||||||
if (typeof fn === 'function') {
|
// if (typeof fn === 'function') {
|
||||||
this._callback = fn;
|
// this._callback = fn;
|
||||||
} else {
|
// } else {
|
||||||
console.error('Callback is not a function');
|
// console.error('Callback is not a function');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
get callback() {
|
// get callback() {
|
||||||
return this._callback;
|
// return this._callback;
|
||||||
}
|
// }
|
||||||
|
|
||||||
render() {
|
// render() {
|
||||||
if (this.shadowRoot && !this.shadowRoot.querySelector('account-element')) {
|
// if (this.shadowRoot && !this.shadowRoot.querySelector('account-element')) {
|
||||||
const style = document.createElement('style');
|
// const style = document.createElement('style');
|
||||||
style.textContent = accountCss;
|
// style.textContent = accountCss;
|
||||||
|
|
||||||
const accountElement = document.createElement('account-element');
|
// const accountElement = document.createElement('account-element');
|
||||||
|
|
||||||
this.shadowRoot.appendChild(style);
|
// this.shadowRoot.appendChild(style);
|
||||||
this.shadowRoot.appendChild(accountElement);
|
// this.shadowRoot.appendChild(accountElement);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
export { AccountComponent };
|
// export { AccountComponent };
|
||||||
customElements.define('account-component', AccountComponent);
|
// customElements.define('account-component', AccountComponent);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!-- <!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Account</title>
|
<title>Account</title>
|
||||||
@ -7,4 +7,4 @@
|
|||||||
<account-component></account-component>
|
<account-component></account-component>
|
||||||
<script type="module" src="./account.ts"></script>
|
<script type="module" src="./account.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html> -->
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,321 +1,321 @@
|
|||||||
import { ProcessState } from '../../../pkg/sdk_client';
|
// import { ProcessState } from '../../../pkg/sdk_client';
|
||||||
import Services from '../../services/service';
|
// import Services from '../../services/service';
|
||||||
|
|
||||||
interface State {
|
// interface State {
|
||||||
file: File | null;
|
// file: File | null;
|
||||||
fileHash: string | null;
|
// fileHash: string | null;
|
||||||
certificate: ProcessState | null;
|
// certificate: ProcessState | null;
|
||||||
commitmentHashes: string[];
|
// commitmentHashes: string[];
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface Vin {
|
// export interface Vin {
|
||||||
txid: string; // The txid of the previous transaction (being spent)
|
// txid: string; // The txid of the previous transaction (being spent)
|
||||||
vout: number; // The output index in the previous tx
|
// vout: number; // The output index in the previous tx
|
||||||
prevout: {
|
// prevout: {
|
||||||
scriptpubkey: string;
|
// scriptpubkey: string;
|
||||||
scriptpubkey_asm: string;
|
// scriptpubkey_asm: string;
|
||||||
scriptpubkey_type: string;
|
// scriptpubkey_type: string;
|
||||||
scriptpubkey_address: string;
|
// scriptpubkey_address: string;
|
||||||
value: number;
|
// value: number;
|
||||||
};
|
// };
|
||||||
scriptsig: string;
|
// scriptsig: string;
|
||||||
scriptsig_asm: string;
|
// scriptsig_asm: string;
|
||||||
witness: string[];
|
// witness: string[];
|
||||||
is_coinbase: boolean;
|
// is_coinbase: boolean;
|
||||||
sequence: number;
|
// sequence: number;
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface TransactionInfo {
|
// export interface TransactionInfo {
|
||||||
txid: string;
|
// txid: string;
|
||||||
version: number;
|
// version: number;
|
||||||
locktime: number;
|
// locktime: number;
|
||||||
vin: Vin[];
|
// vin: Vin[];
|
||||||
vout: any[];
|
// vout: any[];
|
||||||
size: number;
|
// size: number;
|
||||||
weight: number;
|
// weight: number;
|
||||||
fee: number;
|
// fee: number;
|
||||||
status: {
|
// status: {
|
||||||
confirmed: boolean;
|
// confirmed: boolean;
|
||||||
block_height: number;
|
// block_height: number;
|
||||||
block_hash: string;
|
// block_hash: string;
|
||||||
block_time: number;
|
// block_time: number;
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
export function getDocumentValidation(container: HTMLElement) {
|
// export function getDocumentValidation(container: HTMLElement) {
|
||||||
const state: State = {
|
// const state: State = {
|
||||||
file: null,
|
// file: null,
|
||||||
fileHash: null,
|
// fileHash: null,
|
||||||
certificate: null,
|
// certificate: null,
|
||||||
commitmentHashes: []
|
// commitmentHashes: []
|
||||||
}
|
// }
|
||||||
|
|
||||||
container.innerHTML = '';
|
// container.innerHTML = '';
|
||||||
container.style.cssText = `
|
// container.style.cssText = `
|
||||||
display: flex;
|
// display: flex;
|
||||||
flex-direction: column;
|
// flex-direction: column;
|
||||||
justify-content: center;
|
// justify-content: center;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
min-height: 100vh;
|
// min-height: 100vh;
|
||||||
gap: 2rem;
|
// gap: 2rem;
|
||||||
`;
|
// `;
|
||||||
|
|
||||||
function createDropButton(
|
// function createDropButton(
|
||||||
label: string,
|
// label: string,
|
||||||
onDrop: (file: File, updateVisuals: (file: File) => void) => void,
|
// onDrop: (file: File, updateVisuals: (file: File) => void) => void,
|
||||||
accept: string = '*/*'
|
// accept: string = '*/*'
|
||||||
): HTMLElement {
|
// ): HTMLElement {
|
||||||
const wrapper = document.createElement('div');
|
// const wrapper = document.createElement('div');
|
||||||
wrapper.style.cssText = `
|
// wrapper.style.cssText = `
|
||||||
width: 200px;
|
// width: 200px;
|
||||||
height: 100px;
|
// height: 100px;
|
||||||
border: 2px dashed #888;
|
// border: 2px dashed #888;
|
||||||
border-radius: 8px;
|
// border-radius: 8px;
|
||||||
display: flex;
|
// display: flex;
|
||||||
flex-direction: column;
|
// flex-direction: column;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
justify-content: center;
|
// justify-content: center;
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
font-weight: bold;
|
// font-weight: bold;
|
||||||
background: #f8f8f8;
|
// background: #f8f8f8;
|
||||||
text-align: center;
|
// text-align: center;
|
||||||
padding: 0.5rem;
|
// padding: 0.5rem;
|
||||||
box-sizing: border-box;
|
// box-sizing: border-box;
|
||||||
`;
|
// `;
|
||||||
|
|
||||||
const title = document.createElement('div');
|
// const title = document.createElement('div');
|
||||||
title.textContent = label;
|
// title.textContent = label;
|
||||||
|
|
||||||
const filename = document.createElement('div');
|
// const filename = document.createElement('div');
|
||||||
filename.style.cssText = `
|
// filename.style.cssText = `
|
||||||
font-size: 0.85rem;
|
// font-size: 0.85rem;
|
||||||
margin-top: 0.5rem;
|
// margin-top: 0.5rem;
|
||||||
color: #444;
|
// color: #444;
|
||||||
word-break: break-word;
|
// word-break: break-word;
|
||||||
text-align: center;
|
// text-align: center;
|
||||||
`;
|
// `;
|
||||||
|
|
||||||
wrapper.appendChild(title);
|
// wrapper.appendChild(title);
|
||||||
wrapper.appendChild(filename);
|
// wrapper.appendChild(filename);
|
||||||
|
|
||||||
const updateVisuals = (file: File) => {
|
// const updateVisuals = (file: File) => {
|
||||||
wrapper.style.borderColor = 'green';
|
// wrapper.style.borderColor = 'green';
|
||||||
wrapper.style.background = '#e6ffed';
|
// wrapper.style.background = '#e6ffed';
|
||||||
filename.textContent = file.name;
|
// filename.textContent = file.name;
|
||||||
};
|
// };
|
||||||
|
|
||||||
// === Hidden file input ===
|
// // === Hidden file input ===
|
||||||
const fileInput = document.createElement('input');
|
// const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
// fileInput.type = 'file';
|
||||||
fileInput.accept = accept;
|
// fileInput.accept = accept;
|
||||||
fileInput.style.display = 'none';
|
// fileInput.style.display = 'none';
|
||||||
document.body.appendChild(fileInput);
|
// document.body.appendChild(fileInput);
|
||||||
|
|
||||||
fileInput.onchange = () => {
|
// fileInput.onchange = () => {
|
||||||
const file = fileInput.files?.[0];
|
// const file = fileInput.files?.[0];
|
||||||
if (file) {
|
// if (file) {
|
||||||
onDrop(file, updateVisuals);
|
// onDrop(file, updateVisuals);
|
||||||
fileInput.value = ''; // reset so same file can be re-selected
|
// fileInput.value = ''; // reset so same file can be re-selected
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// === Handle drag-and-drop ===
|
// // === Handle drag-and-drop ===
|
||||||
wrapper.ondragover = e => {
|
// wrapper.ondragover = e => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
wrapper.style.background = '#e0e0e0';
|
// wrapper.style.background = '#e0e0e0';
|
||||||
};
|
// };
|
||||||
|
|
||||||
wrapper.ondragleave = () => {
|
// wrapper.ondragleave = () => {
|
||||||
wrapper.style.background = '#f8f8f8';
|
// wrapper.style.background = '#f8f8f8';
|
||||||
};
|
// };
|
||||||
|
|
||||||
wrapper.ondrop = e => {
|
// wrapper.ondrop = e => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
wrapper.style.background = '#f8f8f8';
|
// wrapper.style.background = '#f8f8f8';
|
||||||
|
|
||||||
const file = e.dataTransfer?.files?.[0];
|
// const file = e.dataTransfer?.files?.[0];
|
||||||
if (file) {
|
// if (file) {
|
||||||
onDrop(file, updateVisuals);
|
// onDrop(file, updateVisuals);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// === Handle click to open file manager ===
|
// // === Handle click to open file manager ===
|
||||||
wrapper.onclick = () => {
|
// wrapper.onclick = () => {
|
||||||
fileInput.click();
|
// fileInput.click();
|
||||||
};
|
// };
|
||||||
|
|
||||||
return wrapper;
|
// return wrapper;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const fileDropButton = createDropButton('Drop file', async (file, updateVisuals) => {
|
// const fileDropButton = createDropButton('Drop file', async (file, updateVisuals) => {
|
||||||
try {
|
// try {
|
||||||
state.file = file;
|
// state.file = file;
|
||||||
updateVisuals(file);
|
// updateVisuals(file);
|
||||||
console.log('Loaded file:', state.file);
|
// console.log('Loaded file:', state.file);
|
||||||
checkReady();
|
// checkReady();
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
alert('Failed to drop the file.');
|
// alert('Failed to drop the file.');
|
||||||
console.error(err);
|
// console.error(err);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
const certDropButton = createDropButton('Drop certificate', async (file, updateVisuals) => {
|
// const certDropButton = createDropButton('Drop certificate', async (file, updateVisuals) => {
|
||||||
try {
|
// try {
|
||||||
const text = await file.text();
|
// const text = await file.text();
|
||||||
const json = JSON.parse(text);
|
// const json = JSON.parse(text);
|
||||||
if (
|
// if (
|
||||||
typeof json === 'object' &&
|
// typeof json === 'object' &&
|
||||||
json !== null &&
|
// json !== null &&
|
||||||
typeof json.pcd_commitment === 'object' &&
|
// typeof json.pcd_commitment === 'object' &&
|
||||||
typeof json.state_id === 'string'
|
// typeof json.state_id === 'string'
|
||||||
) {
|
// ) {
|
||||||
state.certificate = json as ProcessState;
|
// state.certificate = json as ProcessState;
|
||||||
|
|
||||||
state.commitmentHashes = Object.values(json.pcd_commitment).map((h: string) =>
|
// state.commitmentHashes = Object.values(json.pcd_commitment).map((h: string) =>
|
||||||
h.toLowerCase()
|
// h.toLowerCase()
|
||||||
);
|
// );
|
||||||
|
|
||||||
updateVisuals(file);
|
// updateVisuals(file);
|
||||||
console.log('Loaded certificate, extracted hashes:', state.commitmentHashes);
|
// console.log('Loaded certificate, extracted hashes:', state.commitmentHashes);
|
||||||
checkReady();
|
// checkReady();
|
||||||
} else {
|
// } else {
|
||||||
alert('Invalid certificate structure.');
|
// alert('Invalid certificate structure.');
|
||||||
}
|
// }
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
alert('Failed to parse certificate JSON.');
|
// alert('Failed to parse certificate JSON.');
|
||||||
console.error(err);
|
// console.error(err);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
const buttonRow = document.createElement('div');
|
// const buttonRow = document.createElement('div');
|
||||||
buttonRow.style.display = 'flex';
|
// buttonRow.style.display = 'flex';
|
||||||
buttonRow.style.gap = '2rem';
|
// buttonRow.style.gap = '2rem';
|
||||||
buttonRow.appendChild(fileDropButton);
|
// buttonRow.appendChild(fileDropButton);
|
||||||
buttonRow.appendChild(certDropButton);
|
// buttonRow.appendChild(certDropButton);
|
||||||
|
|
||||||
container.appendChild(buttonRow);
|
// container.appendChild(buttonRow);
|
||||||
|
|
||||||
async function checkReady() {
|
// async function checkReady() {
|
||||||
if (state.file && state.certificate && state.commitmentHashes.length > 0) {
|
// 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
|
// // We take the commited_in and all pcd_commitment keys to reconstruct all the possible hash
|
||||||
const fileBlob = {
|
// const fileBlob = {
|
||||||
type: state.file.type,
|
// type: state.file.type,
|
||||||
data: new Uint8Array(await state.file.arrayBuffer())
|
// data: new Uint8Array(await state.file.arrayBuffer())
|
||||||
};
|
// };
|
||||||
const service = await Services.getInstance();
|
// const service = await Services.getInstance();
|
||||||
const commitedIn = state.certificate.commited_in;
|
// const commitedIn = state.certificate.commited_in;
|
||||||
if (!commitedIn) return;
|
// if (!commitedIn) return;
|
||||||
const [prevTxid, prevTxVout] = commitedIn.split(':');
|
// const [prevTxid, prevTxVout] = commitedIn.split(':');
|
||||||
const processId = state.certificate.process_id;
|
// const processId = state.certificate.process_id;
|
||||||
const stateId = state.certificate.state_id;
|
// const stateId = state.certificate.state_id;
|
||||||
const process = await service.getProcess(processId);
|
// const process = await service.getProcess(processId);
|
||||||
if (!process) return;
|
// if (!process) return;
|
||||||
|
|
||||||
// Get the transaction that comes right after the commited_in
|
// // Get the transaction that comes right after the commited_in
|
||||||
const nextState = service.getNextStateAfterId(process, stateId);
|
// const nextState = service.getNextStateAfterId(process, stateId);
|
||||||
|
|
||||||
if (!nextState) {
|
// if (!nextState) {
|
||||||
alert(`❌ Validation failed: No next state, is the state you're trying to validate commited?`);
|
// alert(`❌ Validation failed: No next state, is the state you're trying to validate commited?`);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const [outspentTxId, _] = nextState.commited_in.split(':');
|
// const [outspentTxId, _] = nextState.commited_in.split(':');
|
||||||
console.log(outspentTxId);
|
// console.log(outspentTxId);
|
||||||
|
|
||||||
// Check that the commitment transaction exists, and that it commits to the state id
|
// // Check that the commitment transaction exists, and that it commits to the state id
|
||||||
|
|
||||||
const txInfo = await fetchTransaction(outspentTxId);
|
// const txInfo = await fetchTransaction(outspentTxId);
|
||||||
if (!txInfo) {
|
// if (!txInfo) {
|
||||||
console.error(`Validation error: Can't fetch new state commitment transaction`);
|
// console.error(`Validation error: Can't fetch new state commitment transaction`);
|
||||||
alert(`❌ Validation failed: invalid or non existent commited_in for state ${stateId}.`);
|
// alert(`❌ Validation failed: invalid or non existent commited_in for state ${stateId}.`);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// We must check that this transaction indeed spend the commited_in we have in the certificate
|
// // We must check that this transaction indeed spend the commited_in we have in the certificate
|
||||||
let found = false;
|
// let found = false;
|
||||||
for (const vin of txInfo.vin) {
|
// for (const vin of txInfo.vin) {
|
||||||
if (vin.txid === prevTxid) {
|
// if (vin.txid === prevTxid) {
|
||||||
found = true;
|
// found = true;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!found) {
|
// if (!found) {
|
||||||
console.error(`Validation error: new state doesn't spend previous state commitment transaction`);
|
// console.error(`Validation error: new state doesn't spend previous state commitment transaction`);
|
||||||
alert('❌ Validation failed: Unconsistent commitment transactions history.');
|
// alert('❌ Validation failed: Unconsistent commitment transactions history.');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// set found back to false for next check
|
// // set found back to false for next check
|
||||||
found = false;
|
// found = false;
|
||||||
|
|
||||||
// is the state_id commited in the transaction?
|
// // is the state_id commited in the transaction?
|
||||||
for (const vout of txInfo.vout) {
|
// for (const vout of txInfo.vout) {
|
||||||
console.log(vout);
|
// console.log(vout);
|
||||||
if (vout.scriptpubkey_type && vout.scriptpubkey_type === 'op_return') {
|
// if (vout.scriptpubkey_type && vout.scriptpubkey_type === 'op_return') {
|
||||||
found = true;
|
// found = true;
|
||||||
} else {
|
// } else {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (vout.scriptpubkey_asm) {
|
// if (vout.scriptpubkey_asm) {
|
||||||
const hash = extractHexFromScriptAsm(vout.scriptpubkey_asm);
|
// const hash = extractHexFromScriptAsm(vout.scriptpubkey_asm);
|
||||||
if (hash) {
|
// if (hash) {
|
||||||
if (hash !== stateId) {
|
// if (hash !== stateId) {
|
||||||
console.error(`Validation error: expected stateId ${stateId}, got ${hash}`);
|
// console.error(`Validation error: expected stateId ${stateId}, got ${hash}`);
|
||||||
alert('❌ Validation failed: Transaction does not commit to that state.');
|
// alert('❌ Validation failed: Transaction does not commit to that state.');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!found) {
|
// if (!found) {
|
||||||
alert('❌ Validation failed: Transaction does not contain data.');
|
// alert('❌ Validation failed: Transaction does not contain data.');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// set found back to false for next check
|
// // set found back to false for next check
|
||||||
found = false;
|
// found = false;
|
||||||
|
|
||||||
for (const label of Object.keys(state.certificate.pcd_commitment)) {
|
// for (const label of Object.keys(state.certificate.pcd_commitment)) {
|
||||||
// Compute the hash for this label
|
// // Compute the hash for this label
|
||||||
console.log(`Computing hash with label ${label}`)
|
// console.log(`Computing hash with label ${label}`)
|
||||||
const fileHex = service.getHashForFile(commitedIn, label, fileBlob);
|
// const fileHex = service.getHashForFile(commitedIn, label, fileBlob);
|
||||||
console.log(`Found hash ${fileHex}`);
|
// console.log(`Found hash ${fileHex}`);
|
||||||
found = state.commitmentHashes.includes(fileHex);
|
// found = state.commitmentHashes.includes(fileHex);
|
||||||
if (found) break;
|
// if (found) break;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (found) {
|
// if (found) {
|
||||||
alert('✅ Validation successful: file hash found in pcd_commitment.');
|
// alert('✅ Validation successful: file hash found in pcd_commitment.');
|
||||||
} else {
|
// } else {
|
||||||
alert('❌ Validation failed: file hash NOT found in pcd_commitment.');
|
// alert('❌ Validation failed: file hash NOT found in pcd_commitment.');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
async function fetchTransaction(txid: string): Promise<TransactionInfo> {
|
// async function fetchTransaction(txid: string): Promise<TransactionInfo> {
|
||||||
const url = `https://mempool.4nkweb.com/api/tx/${txid}`;
|
// const url = `https://mempool.4nkweb.com/api/tx/${txid}`;
|
||||||
|
|
||||||
const response = await fetch(url);
|
// const response = await fetch(url);
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch outspend status: ${response.statusText}`);
|
// throw new Error(`Failed to fetch outspend status: ${response.statusText}`);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const outspend: TransactionInfo = await response.json();
|
// const outspend: TransactionInfo = await response.json();
|
||||||
return outspend;
|
// return outspend;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function extractHexFromScriptAsm(scriptAsm: string): string | null {
|
// function extractHexFromScriptAsm(scriptAsm: string): string | null {
|
||||||
const parts = scriptAsm.trim().split(/\s+/);
|
// const parts = scriptAsm.trim().split(/\s+/);
|
||||||
const last = parts[parts.length - 1];
|
// const last = parts[parts.length - 1];
|
||||||
|
|
||||||
// Basic validation: must be 64-char hex (32 bytes)
|
// // Basic validation: must be 64-char hex (32 bytes)
|
||||||
if (/^[0-9a-fA-F]{64}$/.test(last)) {
|
// if (/^[0-9a-fA-F]{64}$/.test(last)) {
|
||||||
return last.toLowerCase();
|
// return last.toLowerCase();
|
||||||
}
|
// }
|
||||||
|
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,196 +1,196 @@
|
|||||||
import { ValidationRule, RoleDefinition } from '../../../pkg/sdk_client';
|
// import { ValidationRule, RoleDefinition } from '../../../pkg/sdk_client';
|
||||||
import { showValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
|
// import { showValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
|
||||||
|
|
||||||
export function createKeyValueSection(title: string, id: string, isRoleSection = false) {
|
// export function createKeyValueSection(title: string, id: string, isRoleSection = false) {
|
||||||
const section = document.createElement('div');
|
// const section = document.createElement('div');
|
||||||
section.id = id;
|
// 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);';
|
// 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');
|
// const titleEl = document.createElement('h2');
|
||||||
titleEl.textContent = title;
|
// titleEl.textContent = title;
|
||||||
titleEl.style.cssText = 'font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;';
|
// titleEl.style.cssText = 'font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;';
|
||||||
section.appendChild(titleEl);
|
// section.appendChild(titleEl);
|
||||||
|
|
||||||
const rowContainer = document.createElement('div');
|
// const rowContainer = document.createElement('div');
|
||||||
section.appendChild(rowContainer);
|
// section.appendChild(rowContainer);
|
||||||
|
|
||||||
const addBtn = document.createElement('button');
|
// const addBtn = document.createElement('button');
|
||||||
addBtn.textContent = '+ Add Row';
|
// addBtn.textContent = '+ Add Row';
|
||||||
addBtn.style.cssText = `
|
// addBtn.style.cssText = `
|
||||||
margin-top: 1rem;
|
// margin-top: 1rem;
|
||||||
padding: 0.5rem 1rem;
|
// padding: 0.5rem 1rem;
|
||||||
border: 1px solid #888;
|
// border: 1px solid #888;
|
||||||
border-radius: 0.375rem;
|
// border-radius: 0.375rem;
|
||||||
background-color: #f9f9f9;
|
// background-color: #f9f9f9;
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
`;
|
// `;
|
||||||
section.appendChild(addBtn);
|
// section.appendChild(addBtn);
|
||||||
|
|
||||||
const roleRowStates: {
|
// const roleRowStates: {
|
||||||
roleNameInput: HTMLInputElement;
|
// roleNameInput: HTMLInputElement;
|
||||||
membersInput: HTMLInputElement;
|
// membersInput: HTMLInputElement;
|
||||||
storagesInput: HTMLInputElement;
|
// storagesInput: HTMLInputElement;
|
||||||
validationRules: ValidationRule[];
|
// validationRules: ValidationRule[];
|
||||||
}[] = [];
|
// }[] = [];
|
||||||
type fileBlob = {
|
// type fileBlob = {
|
||||||
type: string,
|
// type: string,
|
||||||
data: Uint8Array
|
// data: Uint8Array
|
||||||
};
|
// };
|
||||||
const nonRoleRowStates: {
|
// const nonRoleRowStates: {
|
||||||
keyInput: HTMLInputElement,
|
// keyInput: HTMLInputElement,
|
||||||
valueInput: HTMLInputElement,
|
// valueInput: HTMLInputElement,
|
||||||
fileInput: HTMLInputElement,
|
// fileInput: HTMLInputElement,
|
||||||
fileBlob: fileBlob | null
|
// fileBlob: fileBlob | null
|
||||||
}[] = [];
|
// }[] = [];
|
||||||
|
|
||||||
const inputStyle = 'flex: 1; height: 2.5rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem;';
|
// const inputStyle = 'flex: 1; height: 2.5rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem;';
|
||||||
|
|
||||||
const createRow = () => {
|
// const createRow = () => {
|
||||||
const row = document.createElement('div');
|
// const row = document.createElement('div');
|
||||||
row.style.cssText = 'display: flex; gap: 1rem; margin-bottom: 0.5rem; align-items: center;';
|
// row.style.cssText = 'display: flex; gap: 1rem; margin-bottom: 0.5rem; align-items: center;';
|
||||||
|
|
||||||
const deleteBtn = document.createElement('button');
|
// const deleteBtn = document.createElement('button');
|
||||||
deleteBtn.textContent = '🗑️';
|
// deleteBtn.textContent = '🗑️';
|
||||||
deleteBtn.style.cssText = 'background: none; border: none; font-size: 1.2rem; cursor: pointer;';
|
// deleteBtn.style.cssText = 'background: none; border: none; font-size: 1.2rem; cursor: pointer;';
|
||||||
deleteBtn.onclick = () => {
|
// deleteBtn.onclick = () => {
|
||||||
row.remove();
|
// row.remove();
|
||||||
updateDeleteButtons();
|
// updateDeleteButtons();
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (isRoleSection) {
|
// if (isRoleSection) {
|
||||||
const roleName = document.createElement('input');
|
// const roleName = document.createElement('input');
|
||||||
const members = document.createElement('input');
|
// const members = document.createElement('input');
|
||||||
const storages = document.createElement('input');
|
// const storages = document.createElement('input');
|
||||||
|
|
||||||
roleName.placeholder = 'Role name';
|
// roleName.placeholder = 'Role name';
|
||||||
members.placeholder = 'members';
|
// members.placeholder = 'members';
|
||||||
storages.placeholder = 'storages';
|
// storages.placeholder = 'storages';
|
||||||
[roleName, members, storages].forEach(input => {
|
// [roleName, members, storages].forEach(input => {
|
||||||
input.type = 'text';
|
// input.type = 'text';
|
||||||
input.style.cssText = inputStyle;
|
// input.style.cssText = inputStyle;
|
||||||
});
|
// });
|
||||||
|
|
||||||
const ruleButton = document.createElement('button');
|
// const ruleButton = document.createElement('button');
|
||||||
ruleButton.textContent = 'Add Validation Rule';
|
// 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;';
|
// ruleButton.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
||||||
|
|
||||||
const rules: ValidationRule[] = [];
|
// const rules: ValidationRule[] = [];
|
||||||
ruleButton.onclick = () => {
|
// ruleButton.onclick = () => {
|
||||||
showValidationRuleModal(rule => {
|
// showValidationRuleModal(rule => {
|
||||||
rules.push(rule);
|
// rules.push(rule);
|
||||||
ruleButton.textContent = `Rules (${rules.length})`;
|
// ruleButton.textContent = `Rules (${rules.length})`;
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
|
|
||||||
row.appendChild(roleName);
|
// row.appendChild(roleName);
|
||||||
row.appendChild(members);
|
// row.appendChild(members);
|
||||||
row.appendChild(storages);
|
// row.appendChild(storages);
|
||||||
row.appendChild(ruleButton);
|
// row.appendChild(ruleButton);
|
||||||
row.appendChild(deleteBtn);
|
// row.appendChild(deleteBtn);
|
||||||
|
|
||||||
roleRowStates.push({ roleNameInput: roleName, membersInput: members, storagesInput: storages, validationRules: rules });
|
// roleRowStates.push({ roleNameInput: roleName, membersInput: members, storagesInput: storages, validationRules: rules });
|
||||||
} else {
|
// } else {
|
||||||
const fileInput = document.createElement('input');
|
// const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
// fileInput.type = 'file';
|
||||||
fileInput.style.display = 'none';
|
// fileInput.style.display = 'none';
|
||||||
fileInput.onchange = async () => {
|
// fileInput.onchange = async () => {
|
||||||
const file = fileInput.files?.[0];
|
// const file = fileInput.files?.[0];
|
||||||
if (!file) return;
|
// if (!file) return;
|
||||||
|
|
||||||
const buffer = await file.arrayBuffer();
|
// const buffer = await file.arrayBuffer();
|
||||||
const uint8 = new Uint8Array(buffer);
|
// const uint8 = new Uint8Array(buffer);
|
||||||
|
|
||||||
rowState.fileBlob = {
|
// rowState.fileBlob = {
|
||||||
type: file.type,
|
// type: file.type,
|
||||||
data: uint8,
|
// data: uint8,
|
||||||
};
|
// };
|
||||||
|
|
||||||
valueInput.value = `📄 ${file.name}`;
|
// valueInput.value = `📄 ${file.name}`;
|
||||||
valueInput.disabled = true;
|
// valueInput.disabled = true;
|
||||||
attachBtn.textContent = `📎 ${file.name}`;
|
// attachBtn.textContent = `📎 ${file.name}`;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const attachBtn = document.createElement('button');
|
// const attachBtn = document.createElement('button');
|
||||||
attachBtn.textContent = '📎 Attach';
|
// attachBtn.textContent = '📎 Attach';
|
||||||
attachBtn.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
// attachBtn.style.cssText = 'padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;';
|
||||||
attachBtn.onclick = () => fileInput.click();
|
// attachBtn.onclick = () => fileInput.click();
|
||||||
|
|
||||||
const keyInput = document.createElement('input');
|
// const keyInput = document.createElement('input');
|
||||||
const valueInput = document.createElement('input');
|
// const valueInput = document.createElement('input');
|
||||||
|
|
||||||
const rowState = {
|
// const rowState = {
|
||||||
keyInput,
|
// keyInput,
|
||||||
valueInput,
|
// valueInput,
|
||||||
fileInput,
|
// fileInput,
|
||||||
fileBlob: null as fileBlob | null
|
// fileBlob: null as fileBlob | null
|
||||||
};
|
// };
|
||||||
nonRoleRowStates.push(rowState);
|
// nonRoleRowStates.push(rowState);
|
||||||
|
|
||||||
keyInput.placeholder = 'Key';
|
// keyInput.placeholder = 'Key';
|
||||||
valueInput.placeholder = 'Value';
|
// valueInput.placeholder = 'Value';
|
||||||
[keyInput, valueInput].forEach(input => {
|
// [keyInput, valueInput].forEach(input => {
|
||||||
input.type = 'text';
|
// input.type = 'text';
|
||||||
input.style.cssText = inputStyle;
|
// input.style.cssText = inputStyle;
|
||||||
});
|
// });
|
||||||
|
|
||||||
row.appendChild(keyInput);
|
// row.appendChild(keyInput);
|
||||||
row.appendChild(valueInput);
|
// row.appendChild(valueInput);
|
||||||
|
|
||||||
row.appendChild(attachBtn);
|
// row.appendChild(attachBtn);
|
||||||
row.appendChild(fileInput);
|
// row.appendChild(fileInput);
|
||||||
|
|
||||||
row.appendChild(deleteBtn);
|
// row.appendChild(deleteBtn);
|
||||||
}
|
// }
|
||||||
|
|
||||||
rowContainer.appendChild(row);
|
// rowContainer.appendChild(row);
|
||||||
updateDeleteButtons();
|
// updateDeleteButtons();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const updateDeleteButtons = () => {
|
// const updateDeleteButtons = () => {
|
||||||
const rows = Array.from(rowContainer.children);
|
// const rows = Array.from(rowContainer.children);
|
||||||
rows.forEach(row => {
|
// rows.forEach(row => {
|
||||||
const btn = row.querySelector('button:last-child') as HTMLButtonElement;
|
// const btn = row.querySelector('button:last-child') as HTMLButtonElement;
|
||||||
if (rows.length === 1) {
|
// if (rows.length === 1) {
|
||||||
btn.disabled = true;
|
// btn.disabled = true;
|
||||||
btn.style.visibility = 'hidden';
|
// btn.style.visibility = 'hidden';
|
||||||
} else {
|
// } else {
|
||||||
btn.disabled = false;
|
// btn.disabled = false;
|
||||||
btn.style.visibility = 'visible';
|
// btn.style.visibility = 'visible';
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
};
|
// };
|
||||||
|
|
||||||
createRow();
|
// createRow();
|
||||||
addBtn.addEventListener('click', createRow);
|
// addBtn.addEventListener('click', createRow);
|
||||||
|
|
||||||
return {
|
// return {
|
||||||
element: section,
|
// element: section,
|
||||||
getData: () => {
|
// getData: () => {
|
||||||
if (isRoleSection) {
|
// if (isRoleSection) {
|
||||||
const data: Record<string, RoleDefinition> = {};
|
// const data: Record<string, RoleDefinition> = {};
|
||||||
for (const row of roleRowStates) {
|
// for (const row of roleRowStates) {
|
||||||
const key = row.roleNameInput.value.trim();
|
// const key = row.roleNameInput.value.trim();
|
||||||
if (!key) continue;
|
// if (!key) continue;
|
||||||
data[key] = {
|
// data[key] = {
|
||||||
members: row.membersInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
// members: row.membersInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
||||||
storages: row.storagesInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
// storages: row.storagesInput.value.split(',').map(x => x.trim()).filter(Boolean),
|
||||||
validation_rules: row.validationRules
|
// validation_rules: row.validationRules
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
return data;
|
// return data;
|
||||||
} else {
|
// } else {
|
||||||
const data: Record<string, string | fileBlob> = {};
|
// const data: Record<string, string | fileBlob> = {};
|
||||||
for (const row of nonRoleRowStates) {
|
// for (const row of nonRoleRowStates) {
|
||||||
const key = row.keyInput.value.trim();
|
// const key = row.keyInput.value.trim();
|
||||||
if (!key) continue;
|
// if (!key) continue;
|
||||||
if (row.fileBlob) {
|
// if (row.fileBlob) {
|
||||||
data[key] = row.fileBlob;
|
// data[key] = row.fileBlob;
|
||||||
} else {
|
// } else {
|
||||||
data[key] = row.valueInput.value.trim();
|
// data[key] = row.valueInput.value.trim();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return data;
|
// return data;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,91 +1,91 @@
|
|||||||
import { createKeyValueSection } from './key-value-section';
|
// import { createKeyValueSection } from './key-value-section';
|
||||||
import { loadValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
|
// import { loadValidationRuleModal } from '../../components/validation-rule-modal/validation-rule-modal';
|
||||||
import Services from '../../services/service';
|
// import Services from '../../services/service';
|
||||||
import { RoleDefinition } from '../../../pkg/sdk_client';
|
// import { RoleDefinition } from '../../../pkg/sdk_client';
|
||||||
|
|
||||||
export async function getProcessCreation(container: HTMLElement) {
|
// export async function getProcessCreation(container: HTMLElement) {
|
||||||
await loadValidationRuleModal();
|
// await loadValidationRuleModal();
|
||||||
|
|
||||||
container.style.display = 'block';
|
// container.style.display = 'block';
|
||||||
container.innerHTML = `<div class="parameter-header">Process Creation</div>`;
|
// container.innerHTML = `<div class="parameter-header">Process Creation</div>`;
|
||||||
const privateSec = createKeyValueSection('Private Data', 'private-section');
|
// const privateSec = createKeyValueSection('Private Data', 'private-section');
|
||||||
const publicSec = createKeyValueSection('Public Data', 'public-section');
|
// const publicSec = createKeyValueSection('Public Data', 'public-section');
|
||||||
const rolesSec = createKeyValueSection('Roles', 'roles-section', true);
|
// const rolesSec = createKeyValueSection('Roles', 'roles-section', true);
|
||||||
|
|
||||||
container.appendChild(privateSec.element);
|
// container.appendChild(privateSec.element);
|
||||||
container.appendChild(publicSec.element);
|
// container.appendChild(publicSec.element);
|
||||||
container.appendChild(rolesSec.element);
|
// container.appendChild(rolesSec.element);
|
||||||
|
|
||||||
const btn = document.createElement('button');
|
// const btn = document.createElement('button');
|
||||||
btn.textContent = 'Create Process';
|
// btn.textContent = 'Create Process';
|
||||||
btn.style.cssText = `
|
// btn.style.cssText = `
|
||||||
display: block;
|
// display: block;
|
||||||
margin: 2rem auto 0;
|
// margin: 2rem auto 0;
|
||||||
padding: 0.75rem 2rem;
|
// padding: 0.75rem 2rem;
|
||||||
font-size: 1rem;
|
// font-size: 1rem;
|
||||||
font-weight: bold;
|
// font-weight: bold;
|
||||||
background-color: #4f46e5;
|
// background-color: #4f46e5;
|
||||||
color: white;
|
// color: white;
|
||||||
border: none;
|
// border: none;
|
||||||
border-radius: 0.5rem;
|
// border-radius: 0.5rem;
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
`;
|
// `;
|
||||||
|
|
||||||
btn.onclick = async () => {
|
// btn.onclick = async () => {
|
||||||
const privateData = privateSec.getData();
|
// const privateData = privateSec.getData();
|
||||||
const publicData = publicSec.getData();
|
// const publicData = publicSec.getData();
|
||||||
const roles = rolesSec.getData() as Record<string, RoleDefinition>;
|
// const roles = rolesSec.getData() as Record<string, RoleDefinition>;
|
||||||
|
|
||||||
console.log('Private:', privateData);
|
// console.log('Private:', privateData);
|
||||||
console.log('Public:', publicData);
|
// console.log('Public:', publicData);
|
||||||
console.log('Roles:', roles);
|
// console.log('Roles:', roles);
|
||||||
|
|
||||||
const service = await Services.getInstance();
|
// const service = await Services.getInstance();
|
||||||
|
|
||||||
const createProcessResult = await service.createProcess(privateData, publicData, roles);
|
// const createProcessResult = await service.createProcess(privateData, publicData, roles);
|
||||||
const processId = createProcessResult.updated_process!.process_id;
|
// const processId = createProcessResult.updated_process!.process_id;
|
||||||
const stateId = createProcessResult.updated_process!.current_process.states[0].state_id;
|
// const stateId = createProcessResult.updated_process!.current_process.states[0].state_id;
|
||||||
await service.handleApiReturn(createProcessResult);
|
// await service.handleApiReturn(createProcessResult);
|
||||||
|
|
||||||
// Now we want to validate the update and register the first state of our new process
|
// // Now we want to validate the update and register the first state of our new process
|
||||||
const updateProcessResult = await service.createPrdUpdate(processId, stateId);
|
// const updateProcessResult = await service.createPrdUpdate(processId, stateId);
|
||||||
await service.handleApiReturn(createProcessResult);
|
// await service.handleApiReturn(createProcessResult);
|
||||||
|
|
||||||
const approveChangeResult = await service.approveChange(processId, stateId);
|
// const approveChangeResult = await service.approveChange(processId, stateId);
|
||||||
await service.handleApiReturn(approveChangeResult);
|
// await service.handleApiReturn(approveChangeResult);
|
||||||
if (approveChangeResult) {
|
// if (approveChangeResult) {
|
||||||
const process = await service.getProcess(processId);
|
// const process = await service.getProcess(processId);
|
||||||
let newState = service.getStateFromId(process, stateId);
|
// let newState = service.getStateFromId(process, stateId);
|
||||||
if (!newState) return;
|
// if (!newState) return;
|
||||||
for (const label of Object.keys(newState.keys)) {
|
// for (const label of Object.keys(newState.keys)) {
|
||||||
const hash = newState.pcd_commitment[label];
|
// const hash = newState.pcd_commitment[label];
|
||||||
const encryptedData = await service.getBlobFromDb(hash);
|
// const encryptedData = await service.getBlobFromDb(hash);
|
||||||
const filename = `${label}-${hash.slice(0,8)}.bin`;
|
// const filename = `${label}-${hash.slice(0,8)}.bin`;
|
||||||
|
|
||||||
const blob = new Blob([encryptedData], { type: "application/octet-stream" });
|
// const blob = new Blob([encryptedData], { type: "application/octet-stream" });
|
||||||
const link = document.createElement("a");
|
// const link = document.createElement("a");
|
||||||
link.href = URL.createObjectURL(blob);
|
// link.href = URL.createObjectURL(blob);
|
||||||
link.download = filename;
|
// link.download = filename;
|
||||||
link.click();
|
// link.click();
|
||||||
|
|
||||||
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
// setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
||||||
}
|
// }
|
||||||
|
|
||||||
await service.generateProcessPdf(processId, newState);
|
// await service.generateProcessPdf(processId, newState);
|
||||||
|
|
||||||
// Add processId to the state we export
|
// // Add processId to the state we export
|
||||||
newState['process_id'] = processId;
|
// newState['process_id'] = processId;
|
||||||
const blob = new Blob([JSON.stringify(newState, null, 2)], { type: 'application/json' });
|
// const blob = new Blob([JSON.stringify(newState, null, 2)], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
// const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
const a = document.createElement('a');
|
// const a = document.createElement('a');
|
||||||
a.href = url;
|
// a.href = url;
|
||||||
a.download = `process_${processId}_${stateId}.json`;
|
// a.download = `process_${processId}_${stateId}.json`;
|
||||||
a.click();
|
// a.click();
|
||||||
|
|
||||||
URL.revokeObjectURL(url); // Clean up
|
// URL.revokeObjectURL(url); // Clean up
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
container.appendChild(btn);
|
// container.appendChild(btn);
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,66 +1,66 @@
|
|||||||
export function createProcessTab(container: HTMLElement, processes: { name: string, publicData: Record<string, any> }[]): HTMLElement {
|
// export function createProcessTab(container: HTMLElement, processes: { name: string, publicData: Record<string, any> }[]): HTMLElement {
|
||||||
container.id = 'process-tab';
|
// container.id = 'process-tab';
|
||||||
container.style.display = 'block';
|
// container.style.display = 'block';
|
||||||
container.style.cssText = 'padding: 1.5rem;';
|
// container.style.cssText = 'padding: 1.5rem;';
|
||||||
|
|
||||||
const title = document.createElement('h2');
|
// const title = document.createElement('h2');
|
||||||
title.textContent = 'Processes';
|
// title.textContent = 'Processes';
|
||||||
title.style.cssText = 'font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;';
|
// title.style.cssText = 'font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;';
|
||||||
container.appendChild(title);
|
// container.appendChild(title);
|
||||||
|
|
||||||
processes.forEach(proc => {
|
// processes.forEach(proc => {
|
||||||
const card = document.createElement('div');
|
// const card = document.createElement('div');
|
||||||
card.style.cssText = 'margin-bottom: 1rem; padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; background: #fff;';
|
// card.style.cssText = 'margin-bottom: 1rem; padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; background: #fff;';
|
||||||
|
|
||||||
const nameEl = document.createElement('h3');
|
// const nameEl = document.createElement('h3');
|
||||||
nameEl.textContent = proc.name;
|
// nameEl.textContent = proc.name;
|
||||||
nameEl.style.cssText = 'font-size: 1.2rem; font-weight: bold; margin-bottom: 0.5rem;';
|
// nameEl.style.cssText = 'font-size: 1.2rem; font-weight: bold; margin-bottom: 0.5rem;';
|
||||||
card.appendChild(nameEl);
|
// card.appendChild(nameEl);
|
||||||
|
|
||||||
const dataList = document.createElement('div');
|
// const dataList = document.createElement('div');
|
||||||
for (const [key, value] of Object.entries(proc.publicData)) {
|
// for (const [key, value] of Object.entries(proc.publicData)) {
|
||||||
const item = document.createElement('div');
|
// const item = document.createElement('div');
|
||||||
item.style.cssText = 'margin-bottom: 0.5rem;';
|
// item.style.cssText = 'margin-bottom: 0.5rem;';
|
||||||
|
|
||||||
const label = document.createElement('strong');
|
// const label = document.createElement('strong');
|
||||||
label.textContent = key + ': ';
|
// label.textContent = key + ': ';
|
||||||
item.appendChild(label);
|
// item.appendChild(label);
|
||||||
|
|
||||||
// Let's trim the quotes
|
// // Let's trim the quotes
|
||||||
const trimmed = value.replace(/^'|'$/g, '');
|
// const trimmed = value.replace(/^'|'$/g, '');
|
||||||
let parsed;
|
// let parsed;
|
||||||
try {
|
// try {
|
||||||
parsed = JSON.parse(trimmed);
|
// parsed = JSON.parse(trimmed);
|
||||||
} catch (_) {
|
// } catch (_) {
|
||||||
parsed = trimmed;
|
// parsed = trimmed;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (parsed && typeof parsed === 'object') {
|
// if (parsed && typeof parsed === 'object') {
|
||||||
const saveBtn = document.createElement('button');
|
// const saveBtn = document.createElement('button');
|
||||||
saveBtn.textContent = '💾 Save as JSON';
|
// 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.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 = () => {
|
// saveBtn.onclick = () => {
|
||||||
const blob = new Blob([JSON.stringify(parsed, null, 2)], { type: 'application/json' });
|
// const blob = new Blob([JSON.stringify(parsed, null, 2)], { type: 'application/json' });
|
||||||
const url = URL.createObjectURL(blob);
|
// const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
// const a = document.createElement('a');
|
||||||
a.href = url;
|
// a.href = url;
|
||||||
a.download = `${proc.name}_${key}.json`;
|
// a.download = `${proc.name}_${key}.json`;
|
||||||
a.click();
|
// a.click();
|
||||||
URL.revokeObjectURL(url);
|
// URL.revokeObjectURL(url);
|
||||||
};
|
// };
|
||||||
item.appendChild(saveBtn);
|
// item.appendChild(saveBtn);
|
||||||
} else {
|
// } else {
|
||||||
const span = document.createElement('span');
|
// const span = document.createElement('span');
|
||||||
span.textContent = String(parsed);
|
// span.textContent = String(parsed);
|
||||||
item.appendChild(span);
|
// item.appendChild(span);
|
||||||
}
|
// }
|
||||||
|
|
||||||
dataList.appendChild(item);
|
// dataList.appendChild(item);
|
||||||
}
|
// }
|
||||||
|
|
||||||
card.appendChild(dataList);
|
// card.appendChild(dataList);
|
||||||
container.appendChild(card);
|
// container.appendChild(card);
|
||||||
});
|
// });
|
||||||
|
|
||||||
return container;
|
// return container;
|
||||||
}
|
// }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
|
// src/pages/home/home-component.ts
|
||||||
|
|
||||||
import loginHtml from './home.html?raw';
|
import loginHtml from './home.html?raw';
|
||||||
import loginScript from './home.ts?raw';
|
|
||||||
import loginCss from '../../4nk.css?raw';
|
import loginCss from '../../4nk.css?raw';
|
||||||
import { initHomePage } from './home';
|
import { initHomePage } from './home';
|
||||||
|
|
||||||
@ -11,11 +12,26 @@ export class LoginComponent extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
console.log('CALLBACK LOGIN PAGE');
|
|
||||||
this.render();
|
this.render();
|
||||||
setTimeout(() => {
|
|
||||||
initHomePage();
|
try {
|
||||||
}, 500);
|
if (this.shadowRoot) {
|
||||||
|
initHomePage(this.shadowRoot);
|
||||||
|
} else {
|
||||||
|
console.error("[LoginComponent] 💥 ShadowRoot est nul. Impossible d'initialiser.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[LoginComponent] 💥 Échec de l'initHomePage:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>${loginCss}</style>
|
||||||
|
${loginHtml}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set callback(fn) {
|
set callback(fn) {
|
||||||
@ -25,23 +41,9 @@ export class LoginComponent extends HTMLElement {
|
|||||||
console.error('Callback is not a function');
|
console.error('Callback is not a function');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get callback() {
|
get callback() {
|
||||||
return this._callback;
|
return this._callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.shadowRoot)
|
|
||||||
this.shadowRoot.innerHTML = `
|
|
||||||
<style>
|
|
||||||
${loginCss}
|
|
||||||
</style>${loginHtml}
|
|
||||||
<script type="module">
|
|
||||||
${loginScript}
|
|
||||||
</scipt>
|
|
||||||
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!customElements.get('login-4nk-component')) {
|
if (!customElements.get('login-4nk-component')) {
|
||||||
|
|||||||
@ -13,9 +13,6 @@
|
|||||||
<div id="tab1" class="card tab-content active">
|
<div id="tab1" class="card tab-content active">
|
||||||
<div class="card-description">Create an account :</div>
|
<div class="card-description">Create an account :</div>
|
||||||
<div class="pairing-request"></div>
|
<div class="pairing-request"></div>
|
||||||
<!-- <div class="card-image qr-code">
|
|
||||||
<img src="assets/qr_code.png" alt="QR Code" width="150" height="150" />
|
|
||||||
</div> -->
|
|
||||||
<button id="createButton" class="create-btn"></button>
|
<button id="createButton" class="create-btn"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
@ -23,20 +20,16 @@
|
|||||||
<div class="card-description">Add a device for an existing member :</div>
|
<div class="card-description">Add a device for an existing member :</div>
|
||||||
<div class="card-image camera-card">
|
<div class="card-image camera-card">
|
||||||
<img id="scanner" src="assets/camera.jpg" alt="QR Code" width="150" height="150" />
|
<img id="scanner" src="assets/camera.jpg" alt="QR Code" width="150" height="150" />
|
||||||
<button id="scan-btn" onclick="scanDevice()">Scan</button>
|
<button id="scan-btn" onclick="scanDevice()">Scan</button> <div class="qr-code-scanner">
|
||||||
<div class="qr-code-scanner">
|
|
||||||
<div id="qr-reader" style="width: 200px; display: contents"></div>
|
<div id="qr-reader" style="width: 200px; display: contents"></div>
|
||||||
<div id="qr-reader-results"></div>
|
<div id="qr-reader-results"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Or</p>
|
<p>Or</p>
|
||||||
<!-- <input type="text" id="addressInput" placeholder="Paste address" />
|
|
||||||
<div id="emoji-display-2"></div> -->
|
|
||||||
<div class="card-description">Chose a member :</div>
|
<div class="card-description">Chose a member :</div>
|
||||||
<select name="memberSelect" id="memberSelect" size="5" class="custom-select">
|
<select name="memberSelect" id="memberSelect" size="5" class="custom-select">
|
||||||
<!-- Options -->
|
</select>
|
||||||
</select>
|
|
||||||
|
|
||||||
<button id="okButton" style="display: none">OK</button>
|
<button id="okButton" style="display: none">OK</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,20 +1,30 @@
|
|||||||
|
// src/pages/home/home.ts
|
||||||
|
|
||||||
import Routing from '../../services/modal.service';
|
import Routing from '../../services/modal.service';
|
||||||
import Services from '../../services/service';
|
import Services from '../../services/service';
|
||||||
import { addSubscription } from '../../utils/subscription.utils';
|
import { addSubscription } from '../../utils/subscription.utils';
|
||||||
import { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji} from '../../utils/sp-address.utils';
|
import { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji } from '../../utils/sp-address.utils';
|
||||||
import { getCorrectDOM } from '../../utils/html.utils';
|
|
||||||
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
|
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
|
||||||
import { navigate, registerAllListeners } from '../../router';
|
|
||||||
|
|
||||||
export { QrScannerComponent };
|
export { QrScannerComponent };
|
||||||
export async function initHomePage(): Promise<void> {
|
|
||||||
console.log('INIT-HOME');
|
/**
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
* Fonction d'initialisation principale.
|
||||||
container.querySelectorAll('.tab').forEach((tab) => {
|
* Elle est appelée par home-component.ts et reçoit le ShadowRoot.
|
||||||
|
*/
|
||||||
|
export async function initHomePage(container: ShadowRoot): Promise<void> {
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
console.error('[home.ts] 💥 ERREUR: Le shadowRoot est nul !');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabs = container.querySelectorAll('.tab');
|
||||||
|
|
||||||
|
tabs.forEach((tab) => {
|
||||||
addSubscription(tab, 'click', () => {
|
addSubscription(tab, 'click', () => {
|
||||||
container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
|
container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
|
||||||
tab.classList.add('active');
|
tab.classList.add('active');
|
||||||
|
|
||||||
container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));
|
container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));
|
||||||
container.querySelector(`#${tab.getAttribute('data-tab') as string}`)?.classList.add('active');
|
container.querySelector(`#${tab.getAttribute('data-tab') as string}`)?.classList.add('active');
|
||||||
});
|
});
|
||||||
@ -22,41 +32,21 @@ export async function initHomePage(): Promise<void> {
|
|||||||
|
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
const spAddress = await service.getDeviceAddress();
|
const spAddress = await service.getDeviceAddress();
|
||||||
// generateQRCode(spAddress);
|
|
||||||
generateCreateBtn();
|
generateCreateBtn();
|
||||||
displayEmojis(spAddress);
|
displayEmojis(spAddress);
|
||||||
|
|
||||||
// Add this line to populate the select when the page loads
|
await populateMemberSelect(container);
|
||||||
await populateMemberSelect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Modal
|
/**
|
||||||
export async function openModal(myAddress: string, receiverAddress: string) {
|
* Remplit le <select> des membres.
|
||||||
const router = await Routing.getInstance();
|
* Doit utiliser le 'container' (ShadowRoot) pour trouver l'élément.
|
||||||
router.openLoginModal(myAddress, receiverAddress);
|
*/
|
||||||
}
|
async function populateMemberSelect(container: ShadowRoot) {
|
||||||
|
|
||||||
// const service = await Services.getInstance()
|
|
||||||
// service.setNotification()
|
|
||||||
|
|
||||||
function scanDevice() {
|
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
|
||||||
const scannerImg = container.querySelector('#scanner') as HTMLElement;
|
|
||||||
if (scannerImg) scannerImg.style.display = 'none';
|
|
||||||
const scannerQrCode = container.querySelector('.qr-code-scanner') as HTMLElement;
|
|
||||||
if (scannerQrCode) scannerQrCode.style.display = 'block';
|
|
||||||
const scanButton = container?.querySelector('#scan-btn') as HTMLElement;
|
|
||||||
if (scanButton) scanButton.style.display = 'none';
|
|
||||||
const reader = container?.querySelector('#qr-reader');
|
|
||||||
if (reader) reader.innerHTML = '<qr-scanner></qr-scanner>';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function populateMemberSelect() {
|
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
|
||||||
const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement;
|
const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement;
|
||||||
|
|
||||||
if (!memberSelect) {
|
if (!memberSelect) {
|
||||||
console.error('Could not find memberSelect element');
|
console.error('[home.ts] Impossible de trouver #memberSelect dans le shadowRoot.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,8 +55,7 @@ async function populateMemberSelect() {
|
|||||||
|
|
||||||
for (const [processId, member] of Object.entries(members)) {
|
for (const [processId, member] of Object.entries(members)) {
|
||||||
const process = await service.getProcess(processId);
|
const process = await service.getProcess(processId);
|
||||||
let memberPublicName;
|
let memberPublicName = 'Unnamed Member';
|
||||||
|
|
||||||
if (process) {
|
if (process) {
|
||||||
const publicMemberData = service.getPublicData(process);
|
const publicMemberData = service.getPublicData(process);
|
||||||
if (publicMemberData) {
|
if (publicMemberData) {
|
||||||
@ -76,14 +65,9 @@ async function populateMemberSelect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!memberPublicName) {
|
|
||||||
memberPublicName = 'Unnamed Member';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupérer les emojis pour ce processId
|
|
||||||
const emojis = await addressToEmoji(processId);
|
const emojis = await addressToEmoji(processId);
|
||||||
|
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = processId;
|
option.value = processId;
|
||||||
option.textContent = `${memberPublicName} (${emojis})`;
|
option.textContent = `${memberPublicName} (${emojis})`;
|
||||||
@ -91,6 +75,32 @@ async function populateMemberSelect() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(window as any).populateMemberSelect = populateMemberSelect;
|
/**
|
||||||
|
* Fonction appelée par le 'onclick="scanDevice()"' dans le HTML.
|
||||||
|
* Doit être attachée à 'window' pour être globale.
|
||||||
|
*/
|
||||||
|
function scanDevice() {
|
||||||
|
const hostElement = document.querySelector('login-4nk-component');
|
||||||
|
const container = hostElement?.shadowRoot;
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
console.error('[home.ts] scanDevice: Impossible de trouver le shadowRoot !');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scannerImg = container.querySelector('#scanner') as HTMLElement;
|
||||||
|
if (scannerImg) scannerImg.style.display = 'none';
|
||||||
|
const scannerQrCode = container.querySelector('.qr-code-scanner') as HTMLElement;
|
||||||
|
if (scannerQrCode) scannerQrCode.style.display = 'block';
|
||||||
|
const scanButton = container.querySelector('#scan-btn') as HTMLElement;
|
||||||
|
if (scanButton) scanButton.style.display = 'none';
|
||||||
|
const reader = container.querySelector('#qr-reader');
|
||||||
|
if (reader) reader.innerHTML = '<qr-scanner></qr-scanner>';
|
||||||
|
}
|
||||||
|
|
||||||
(window as any).scanDevice = scanDevice;
|
(window as any).scanDevice = scanDevice;
|
||||||
|
|
||||||
|
export async function openModal(myAddress: string, receiverAddress: string) {
|
||||||
|
const router = await Routing.getInstance();
|
||||||
|
router.openLoginModal(myAddress, receiverAddress);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import processHtml from './process-element.html?raw';
|
// src/pages/process-element/process-component.ts
|
||||||
import processScript from './process-element.ts?raw';
|
|
||||||
import processCss from '../../4nk.css?raw';
|
|
||||||
import { initProcessElement } from './process-element';
|
|
||||||
|
|
||||||
export class ProcessListComponent extends HTMLElement {
|
import processHtml from './process-element.html?raw';
|
||||||
|
import processCss from '../../4nk.css?raw';
|
||||||
|
import { initProcessElement } from './process-element'; // On importe la vraie fonction
|
||||||
|
|
||||||
|
// 1. Nom de classe corrigé (plus logique)
|
||||||
|
export class ProcessElementComponent extends HTMLElement {
|
||||||
_callback: any;
|
_callback: any;
|
||||||
id: string = '';
|
|
||||||
zone: string = '';
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -14,38 +14,61 @@ export class ProcessListComponent extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
console.log('CALLBACK PROCESS LIST PAGE');
|
console.log('[ProcessElementComponent] 1. Composant connecté.');
|
||||||
this.render();
|
|
||||||
setTimeout(() => {
|
// 2. Lire les attributs passés par le routeur (router.ts)
|
||||||
initProcessElement(this.id, this.zone);
|
const processId = this.getAttribute('process-id');
|
||||||
}, 500);
|
const stateId = this.getAttribute('state-id');
|
||||||
}
|
|
||||||
|
|
||||||
set callback(fn) {
|
if (!processId || !stateId) {
|
||||||
if (typeof fn === 'function') {
|
console.error("💥 ProcessElementComponent a été créé sans 'process-id' ou 'state-id'.");
|
||||||
this._callback = fn;
|
this.renderError("Erreur: ID de processus ou d'état manquant.");
|
||||||
} else {
|
return;
|
||||||
console.error('Callback is not a function');
|
}
|
||||||
|
|
||||||
|
// 3. Afficher le HTML/CSS du squelette
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
// 4. Appeler la logique (init) en lui passant le shadowRoot et les IDs
|
||||||
|
try {
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
console.log(`[ProcessElementComponent] 2. Appel de initProcessElement pour ${processId}_${stateId}`);
|
||||||
|
initProcessElement(this.shadowRoot, processId, stateId);
|
||||||
|
} else {
|
||||||
|
console.error("[ProcessElementComponent] 💥 ShadowRoot est nul.");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ProcessElementComponent] 💥 Échec de l'initProcessElement():", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get callback() {
|
|
||||||
return this._callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.shadowRoot)
|
if (this.shadowRoot) {
|
||||||
this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>${processCss}</style>
|
||||||
${processCss}
|
${processHtml}
|
||||||
</style>${processHtml}
|
|
||||||
<script type="module">
|
|
||||||
${processScript}
|
|
||||||
</scipt>
|
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderError(message: string) {
|
||||||
|
if (this.shadowRoot) {
|
||||||
|
this.shadowRoot.innerHTML = `<style>${processCss}</style>
|
||||||
|
<div class="title-container"><h1>Erreur</h1></div>
|
||||||
|
<div class="process-container"><p>${message}</p></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... (Tes callbacks) ...
|
||||||
|
set callback(fn) {
|
||||||
|
if (typeof fn === 'function') { this._callback = fn; }
|
||||||
|
else { console.error('Callback is not a function'); }
|
||||||
|
}
|
||||||
|
get callback() { return this._callback; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 6. Utilisation du bon nom de classe
|
||||||
if (!customElements.get('process-4nk-component')) {
|
if (!customElements.get('process-4nk-component')) {
|
||||||
customElements.define('process-4nk-component', ProcessListComponent);
|
console.log('[ProcessElementComponent] Définition de <process-4nk-component>.');
|
||||||
}
|
customElements.define('process-4nk-component', ProcessElementComponent);
|
||||||
|
}
|
||||||
@ -1,50 +1,111 @@
|
|||||||
|
// src/pages/process-element/process-element.ts
|
||||||
|
|
||||||
import { interpolate } from '../../utils/html.utils';
|
import { interpolate } from '../../utils/html.utils';
|
||||||
import Services from '../../services/service';
|
import Services from '../../services/service';
|
||||||
import { Process } from 'pkg/sdk_client';
|
import { Process, ProcessState } from 'pkg/sdk_client';
|
||||||
import { getCorrectDOM } from '~/utils/document.utils';
|
// 1. Plus besoin de 'getCorrectDOM'
|
||||||
|
|
||||||
let currentPageStyle: HTMLStyleElement | null = null;
|
/**
|
||||||
|
* Fonction d'initialisation, appelée par process-component.ts
|
||||||
|
* Reçoit le shadowRoot et les IDs.
|
||||||
|
*/
|
||||||
|
export async function initProcessElement(container: ShadowRoot, processId: string, stateId: string) {
|
||||||
|
console.log(`[process-element.ts] 3. init() appelé pour ${processId}_${stateId}`);
|
||||||
|
|
||||||
|
const services = await Services.getInstance();
|
||||||
|
|
||||||
export async function initProcessElement(id: string, zone: string) {
|
// 2. Récupérer les éléments du DOM *dans* le shadowRoot (container)
|
||||||
const processes = await getProcesses();
|
const titleH1 = container.querySelector('h1');
|
||||||
const container = getCorrectDOM('process-4nk-component');
|
const processContainer = container.querySelector('.process-container');
|
||||||
// const currentProcess = processes.find((process) => process[0] === id)[1];
|
|
||||||
// const currentProcess = {title: 'Hello', html: '', css: ''};
|
if (!titleH1 || !processContainer) {
|
||||||
// await loadPage({ processTitle: currentProcess.title, inputValue: 'Hello World !' });
|
console.error("[process-element.ts] 💥 Le HTML de base (h1 ou .process-container) est introuvable !");
|
||||||
// const wrapper = document.querySelector('.process-container');
|
return;
|
||||||
// if (wrapper) {
|
}
|
||||||
// wrapper.innerHTML = interpolate(currentProcess.html, { processTitle: currentProcess.title, inputValue: 'Hello World !' });
|
|
||||||
// injectCss(currentProcess.css);
|
// 3. Récupérer les données
|
||||||
// }
|
const process = await services.getProcess(processId);
|
||||||
|
if (!process) {
|
||||||
|
console.error(`[process-element.ts] 💥 Processus ${processId} non trouvé !`);
|
||||||
|
titleH1.innerText = "Erreur";
|
||||||
|
processContainer.innerHTML = `<p>Le processus ${processId} n'a pas pu être chargé.</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = services.getStateFromId(process, stateId);
|
||||||
|
if (!state) {
|
||||||
|
console.error(`[process-element.ts] 💥 État ${stateId} non trouvé dans le processus ${processId} !`);
|
||||||
|
titleH1.innerText = "Erreur";
|
||||||
|
processContainer.innerHTML = `<p>L'état ${stateId} n'a pas pu être chargé.</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("[process-element.ts] ✅ Processus et État chargés.");
|
||||||
|
|
||||||
|
// 4. Mettre à jour le titre
|
||||||
|
const processName = services.getProcessName(process) || "Processus";
|
||||||
|
titleH1.innerHTML = interpolate(titleH1.innerHTML, { processTitle: processName });
|
||||||
|
|
||||||
|
// 5. Logique de rendu de l'élément (À COMPLÉTER PAR TES SOINS)
|
||||||
|
// C'est là que tu dois construire le HTML pour cet état spécifique
|
||||||
|
// Par exemple, déchiffrer les attributs et les afficher.
|
||||||
|
processContainer.innerHTML = `
|
||||||
|
<div class="card" style="margin: 1rem; padding: 1rem;">
|
||||||
|
<h3>État: ${stateId.substring(0, 10)}...</h3>
|
||||||
|
<p>Commit: ${state.commited_in}</p>
|
||||||
|
<div id="attributes-list">
|
||||||
|
<p><em>Chargement des attributs...</em></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 6. Tenter de déchiffrer les données de cet état
|
||||||
|
await decryptAndDisplayAttributes(services, container, processId, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPage(data?: any) {
|
/**
|
||||||
const content = document.getElementById('containerId');
|
* Helper (exemple) pour déchiffrer et afficher les données dans le conteneur
|
||||||
if (content && data) {
|
*/
|
||||||
if (data) {
|
async function decryptAndDisplayAttributes(services: Services, container: ShadowRoot, processId: string, state: ProcessState) {
|
||||||
content.innerHTML = interpolate(content.innerHTML, data);
|
const attributesList = container.querySelector('#attributes-list');
|
||||||
|
if (!attributesList) return;
|
||||||
|
|
||||||
|
console.log(`[process-element.ts] 🔐 Déchiffrement des attributs pour l'état ${state.state_id}...`);
|
||||||
|
attributesList.innerHTML = ''; // Vide le message "Chargement..."
|
||||||
|
let hasPrivateData = false;
|
||||||
|
|
||||||
|
// Affiche les données publiques
|
||||||
|
if (state.public_data) {
|
||||||
|
for (const [key, value] of Object.entries(state.public_data)) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'attribute-pair public';
|
||||||
|
el.innerHTML = `<strong>${key} (public):</strong> ${JSON.stringify(services.decodeValue(value))}`;
|
||||||
|
attributesList.appendChild(el);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectCss(cssContent: string) {
|
// Affiche les données privées
|
||||||
removeCss(); // Ensure that the previous CSS is removed
|
for (const attribute of Object.keys(state.pcd_commitment)) {
|
||||||
|
if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) {
|
||||||
currentPageStyle = document.createElement('style');
|
continue; // Skip les données publiques (déjà fait) et les rôles
|
||||||
currentPageStyle.type = 'text/css';
|
}
|
||||||
currentPageStyle.appendChild(document.createTextNode(cssContent));
|
|
||||||
document.head.appendChild(currentPageStyle);
|
const decryptedValue = await services.decryptAttribute(processId, state, attribute);
|
||||||
}
|
|
||||||
|
const el = document.createElement('div');
|
||||||
function removeCss() {
|
el.className = 'attribute-pair private';
|
||||||
if (currentPageStyle) {
|
|
||||||
document.head.removeChild(currentPageStyle);
|
if (decryptedValue) {
|
||||||
currentPageStyle = null;
|
hasPrivateData = true;
|
||||||
}
|
el.innerHTML = `<strong>${attribute} (privé):</strong> ${JSON.stringify(decryptedValue)}`;
|
||||||
}
|
} else {
|
||||||
|
el.innerHTML = `<strong>${attribute} (privé):</strong> <span style="color: red;">[Déchiffrement impossible / Clé manquante]</span>`;
|
||||||
async function getProcesses(): Promise<Record<string, Process>> {
|
}
|
||||||
const service = await Services.getInstance();
|
attributesList.appendChild(el);
|
||||||
const processes = await service.getProcesses();
|
}
|
||||||
return processes;
|
|
||||||
}
|
if (!hasPrivateData && !(state.public_data && Object.keys(state.public_data).length > 0)) {
|
||||||
|
console.log("[process-element.ts] ℹ️ Aucun attribut (public ou privé) trouvé pour cet état.");
|
||||||
|
attributesList.innerHTML = '<p>Cet état ne contient aucun attribut visible.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,49 +1,40 @@
|
|||||||
// import processHtml from './process.html?raw';
|
// src/pages/process/process-list-component.ts
|
||||||
// import processScript from './process.ts?raw';
|
|
||||||
// import processCss from '../../4nk.css?raw';
|
|
||||||
// import { init } from './process';
|
|
||||||
|
|
||||||
// export class ProcessListComponent extends HTMLElement {
|
import processHtml from './process.html?raw';
|
||||||
// _callback: any;
|
import processCss from '../../4nk.css?raw';
|
||||||
// constructor() {
|
import { init } from './process';
|
||||||
// super();
|
|
||||||
// this.attachShadow({ mode: 'open' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// connectedCallback() {
|
export class ProcessListComponent extends HTMLElement {
|
||||||
// console.log('CALLBACK PROCESS LIST PAGE');
|
constructor() {
|
||||||
// this.render();
|
super();
|
||||||
// setTimeout(() => {
|
this.attachShadow({ mode: 'open' });
|
||||||
// init();
|
}
|
||||||
// }, 500);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// set callback(fn) {
|
connectedCallback() {
|
||||||
// if (typeof fn === 'function') {
|
this.render();
|
||||||
// this._callback = fn;
|
|
||||||
// } else {
|
|
||||||
// console.error('Callback is not a function');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// get callback() {
|
try {
|
||||||
// return this._callback;
|
if (this.shadowRoot) {
|
||||||
// }
|
init(this.shadowRoot);
|
||||||
|
} else {
|
||||||
|
console.error('[ProcessListComponent] 💥 ShadowRoot est nul.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ProcessListComponent] 💥 Échec de l'init():", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// render() {
|
render() {
|
||||||
// if (this.shadowRoot)
|
if (this.shadowRoot) {
|
||||||
// this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
// <style>
|
<style>${processCss}</style>
|
||||||
// ${processCss}
|
${processHtml}
|
||||||
// </style>${processHtml}
|
`;
|
||||||
// <script type="module">
|
}
|
||||||
// ${processScript}
|
}
|
||||||
// </scipt>
|
|
||||||
|
|
||||||
// `;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!customElements.get('process-list-4nk-component')) {
|
}
|
||||||
// customElements.define('process-list-4nk-component', ProcessListComponent);
|
|
||||||
// }
|
if (!customElements.get('process-list-4nk-component')) {
|
||||||
|
customElements.define('process-list-4nk-component', ProcessListComponent);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<!-- <div class="title-container">
|
<div class="title-container">
|
||||||
<h1>Process Selection</h1>
|
<h1>Process Selection</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -7,13 +7,17 @@
|
|||||||
<div class="process-card-description">
|
<div class="process-card-description">
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<select multiple data-multi-select-plugin id="autocomplete" placeholder="Filter processes..." class="select-field"></select>
|
<select multiple data-multi-select-plugin id="autocomplete" placeholder="Filter processes..." class="select-field"></select>
|
||||||
|
|
||||||
<label for="autocomplete" class="input-label">Filter processes :</label>
|
<label for="autocomplete" class="input-label">Filter processes :</label>
|
||||||
|
|
||||||
<div class="selected-processes"></div>
|
<div class="selected-processes"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="process-card-content"></div>
|
<div class="process-card-content"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="process-card-action">
|
<div class="process-card-action">
|
||||||
<a class="btn" onclick="goToProcessPage()">OK</a>
|
<a class="btn" id="go-to-process-btn">OK</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
@ -1,520 +1,486 @@
|
|||||||
// import { addSubscription } from '../../utils/subscription.utils';
|
// src/pages/process/process.ts
|
||||||
// import Services from '../../services/service';
|
|
||||||
// import { getCorrectDOM } from '~/utils/html.utils';
|
|
||||||
// import { Process } from 'pkg/sdk_client';
|
|
||||||
// import chatStyle from '../../../public/style/chat.css?inline';
|
|
||||||
// import { Database } from '../../services/database.service';
|
|
||||||
|
|
||||||
// // Initialize function, create initial tokens with itens that are already selected by the user
|
import { addSubscription } from '../../utils/subscription.utils';
|
||||||
// export async function init() {
|
import Services from '../../services/service';
|
||||||
|
import { Process, ProcessState, UserDiff } from 'pkg/sdk_client';
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
|
||||||
// const element = container.querySelector('select') as HTMLSelectElement;
|
|
||||||
// // Create div that wroaps all the elements inside (select, elements selected, search div) to put select inside
|
|
||||||
// const wrapper = document.createElement('div');
|
|
||||||
// if (wrapper) addSubscription(wrapper, 'click', clickOnWrapper);
|
|
||||||
// wrapper.classList.add('multi-select-component');
|
|
||||||
// wrapper.classList.add('input-field');
|
|
||||||
|
|
||||||
// // Create elements of search
|
// On garde une référence aux éléments du DOM pour ne pas les chercher partout
|
||||||
// const search_div = document.createElement('div');
|
let shadowContainer: ShadowRoot;
|
||||||
// search_div.classList.add('search-container');
|
let services: Services;
|
||||||
// const input = document.createElement('input');
|
let wrapper: HTMLElement;
|
||||||
// input.classList.add('selected-input');
|
let select: HTMLSelectElement;
|
||||||
// input.setAttribute('autocomplete', 'off');
|
let inputSearch: HTMLInputElement;
|
||||||
// input.setAttribute('tabindex', '0');
|
let autocompleteList: HTMLUListElement;
|
||||||
// if (input) {
|
let selectedProcessesContainer: HTMLElement;
|
||||||
// addSubscription(input, 'keyup', inputChange);
|
let processCardContent: HTMLElement;
|
||||||
// addSubscription(input, 'keydown', deletePressed);
|
let okButton: HTMLElement;
|
||||||
// addSubscription(input, 'click', openOptions);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const dropdown_icon = document.createElement('a');
|
/**
|
||||||
// dropdown_icon.classList.add('dropdown-icon');
|
* Fonction d'initialisation principale, appelée par le composant.
|
||||||
|
*/
|
||||||
|
export async function init(container: ShadowRoot) {
|
||||||
|
console.log('[process.ts] 1. init() appelé.');
|
||||||
|
|
||||||
// if (dropdown_icon) addSubscription(dropdown_icon, 'click', clickDropdown);
|
// Stocke les références globales
|
||||||
// const autocomplete_list = document.createElement('ul');
|
shadowContainer = container;
|
||||||
// autocomplete_list.classList.add('autocomplete-list');
|
services = await Services.getInstance();
|
||||||
// search_div.appendChild(input);
|
|
||||||
// search_div.appendChild(autocomplete_list);
|
|
||||||
// search_div.appendChild(dropdown_icon);
|
|
||||||
|
|
||||||
// // set the wrapper as child (instead of the element)
|
const element = container.querySelector('select') as HTMLSelectElement;
|
||||||
// element.parentNode?.replaceChild(wrapper, element);
|
if (!element) {
|
||||||
// // set element as child of wrapper
|
console.error("[process.ts] 💥 Échec de l'init: <select> introuvable dans process.html !");
|
||||||
// wrapper.appendChild(element);
|
return;
|
||||||
// wrapper.appendChild(search_div);
|
}
|
||||||
|
|
||||||
// addPlaceholder(wrapper);
|
console.log('[process.ts] 2. <select> trouvé. Construction du DOM du multi-select...');
|
||||||
// }
|
|
||||||
|
|
||||||
// function removePlaceholder(wrapper: HTMLElement) {
|
// --- 1. Logique de Création du DOM (Ton code original, mais au bon endroit) ---
|
||||||
// const input_search = wrapper.querySelector('.selected-input');
|
const newWrapper = document.createElement('div');
|
||||||
// input_search?.removeAttribute('placeholder');
|
if (newWrapper) addSubscription(newWrapper, 'click', clickOnWrapper);
|
||||||
// }
|
newWrapper.classList.add('multi-select-component');
|
||||||
|
newWrapper.classList.add('input-field');
|
||||||
|
|
||||||
// function addPlaceholder(wrapper: HTMLElement) {
|
const search_div = document.createElement('div');
|
||||||
// const input_search = wrapper.querySelector('.selected-input');
|
search_div.classList.add('search-container');
|
||||||
// const tokens = wrapper.querySelectorAll('.selected-wrapper');
|
|
||||||
// if (!tokens.length && !(document.activeElement === input_search)) input_search?.setAttribute('placeholder', '---------');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Listener of user search
|
const newInput = document.createElement('input');
|
||||||
// function inputChange(e: Event) {
|
newInput.classList.add('selected-input');
|
||||||
// const target = e.target as HTMLInputElement;
|
newInput.setAttribute('autocomplete', 'off');
|
||||||
// const wrapper = target?.parentNode?.parentNode;
|
newInput.setAttribute('tabindex', '0');
|
||||||
// const select = wrapper?.querySelector('select') as HTMLSelectElement;
|
|
||||||
// const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
||||||
|
|
||||||
// const input_val = target?.value;
|
const dropdown_icon = document.createElement('a');
|
||||||
|
dropdown_icon.classList.add('dropdown-icon');
|
||||||
|
|
||||||
// if (input_val) {
|
const newAutocompleteList = document.createElement('ul');
|
||||||
// dropdown?.classList.add('active');
|
newAutocompleteList.classList.add('autocomplete-list');
|
||||||
// populateAutocompleteList(select, input_val.trim());
|
|
||||||
// } else {
|
|
||||||
// dropdown?.classList.remove('active');
|
|
||||||
// const event = new Event('click');
|
|
||||||
// dropdown?.dispatchEvent(event);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Listen for clicks on the wrapper, if click happens focus on the input
|
search_div.appendChild(newInput);
|
||||||
// function clickOnWrapper(e: Event) {
|
search_div.appendChild(newAutocompleteList);
|
||||||
// const wrapper = e.target as HTMLElement;
|
search_div.appendChild(dropdown_icon);
|
||||||
// if (wrapper.tagName == 'DIV') {
|
|
||||||
// const input_search = wrapper.querySelector('.selected-input');
|
|
||||||
// const dropdown = wrapper.querySelector('.dropdown-icon');
|
|
||||||
// if (!dropdown?.classList.contains('active')) {
|
|
||||||
// const event = new Event('click');
|
|
||||||
// dropdown?.dispatchEvent(event);
|
|
||||||
// }
|
|
||||||
// (input_search as HTMLInputElement)?.focus();
|
|
||||||
// removePlaceholder(wrapper);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function openOptions(e: Event) {
|
// Remplace le <select> original par le 'wrapper'
|
||||||
// const input_search = e.target as HTMLElement;
|
element.parentNode?.replaceChild(newWrapper, element);
|
||||||
// const wrapper = input_search?.parentElement?.parentElement;
|
// Ajoute le <select> (caché) et la 'search_div' (visible) dans le wrapper
|
||||||
// const dropdown = wrapper?.querySelector('.dropdown-icon');
|
newWrapper.appendChild(element);
|
||||||
// if (!dropdown?.classList.contains('active')) {
|
newWrapper.appendChild(search_div);
|
||||||
// const event = new Event('click');
|
console.log('[process.ts] 3. DOM du multi-select construit.');
|
||||||
// dropdown?.dispatchEvent(event);
|
|
||||||
// }
|
|
||||||
// e.stopPropagation();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Function that create a token inside of a wrapper with the given value
|
// --- 2. Sélection des éléments (Maintenant qu'ils existent) ---
|
||||||
// function createToken(wrapper: HTMLElement, value: any) {
|
// Note: On utilise 'newWrapper' comme base pour être plus rapide
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
wrapper = newWrapper;
|
||||||
// const search = wrapper.querySelector('.search-container');
|
select = wrapper.querySelector('select') as HTMLSelectElement;
|
||||||
// const inputInderline = container.querySelector('.selected-processes');
|
inputSearch = wrapper.querySelector('.selected-input') as HTMLInputElement;
|
||||||
// // Create token wrapper
|
autocompleteList = wrapper.querySelector('.autocomplete-list') as HTMLUListElement;
|
||||||
// const token = document.createElement('div');
|
|
||||||
// token.classList.add('selected-wrapper');
|
|
||||||
// const token_span = document.createElement('span');
|
|
||||||
// token_span.classList.add('selected-label');
|
|
||||||
// token_span.innerText = value;
|
|
||||||
// const close = document.createElement('a');
|
|
||||||
// close.classList.add('selected-close');
|
|
||||||
// close.setAttribute('tabindex', '-1');
|
|
||||||
// close.setAttribute('data-option', value);
|
|
||||||
// close.setAttribute('data-hits', '0');
|
|
||||||
// close.innerText = 'x';
|
|
||||||
// if (close) addSubscription(close, 'click', removeToken);
|
|
||||||
// token.appendChild(token_span);
|
|
||||||
// token.appendChild(close);
|
|
||||||
// inputInderline?.appendChild(token);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Listen for clicks in the dropdown option
|
// Ces éléments sont en dehors du wrapper, on utilise le 'shadowContainer'
|
||||||
// function clickDropdown(e: Event) {
|
selectedProcessesContainer = shadowContainer.querySelector('.selected-processes') as HTMLElement;
|
||||||
// const dropdown = e.target as HTMLElement;
|
processCardContent = shadowContainer.querySelector('.process-card-content') as HTMLElement;
|
||||||
// const wrapper = dropdown?.parentNode?.parentNode;
|
okButton = shadowContainer.querySelector('#go-to-process-btn') as HTMLElement;
|
||||||
// const input_search = wrapper?.querySelector('.selected-input') as HTMLInputElement;
|
|
||||||
// const select = wrapper?.querySelector('select') as HTMLSelectElement;
|
|
||||||
// dropdown.classList.toggle('active');
|
|
||||||
|
|
||||||
// if (dropdown.classList.contains('active')) {
|
if (!wrapper || !select || !inputSearch || !autocompleteList || !okButton || !selectedProcessesContainer || !processCardContent) {
|
||||||
// removePlaceholder(wrapper as HTMLElement);
|
console.error("[process.ts] 💥 Échec de l'init: Un ou plusieurs éléments DOM sont introuvables après la construction.", {
|
||||||
// input_search?.focus();
|
wrapper,
|
||||||
|
select,
|
||||||
|
inputSearch,
|
||||||
|
autocompleteList,
|
||||||
|
okButton,
|
||||||
|
selectedProcessesContainer,
|
||||||
|
processCardContent,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// if (!input_search?.value) {
|
// --- 3. Attachement des Écouteurs ---
|
||||||
// populateAutocompleteList(select, '', true);
|
addSubscription(inputSearch, 'keyup', inputChange);
|
||||||
// } else {
|
addSubscription(inputSearch, 'keydown', deletePressed);
|
||||||
// populateAutocompleteList(select, input_search.value);
|
addSubscription(inputSearch, 'click', openOptions);
|
||||||
// }
|
addSubscription(dropdown_icon, 'click', clickDropdown);
|
||||||
// } else {
|
addSubscription(okButton, 'click', goToProcessPage);
|
||||||
// clearAutocompleteList(select);
|
|
||||||
// addPlaceholder(wrapper as HTMLElement);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Clears the results of the autocomplete list
|
// Gère le clic en dehors du composant
|
||||||
// function clearAutocompleteList(select: HTMLSelectElement) {
|
addSubscription(document, 'click', (event: Event) => {
|
||||||
// const wrapper = select.parentNode;
|
const isClickInside = wrapper.contains(event.target as Node);
|
||||||
|
if (!isClickInside) {
|
||||||
|
closeAutocomplete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
// --- 4. Initialisation de l'état ---
|
||||||
// if (autocomplete_list) autocomplete_list.innerHTML = '';
|
addPlaceholder();
|
||||||
// }
|
|
||||||
|
|
||||||
// async function populateAutocompleteList(select: HTMLSelectElement, query: string, dropdown = false) {
|
// Peuple la liste une première fois (elle sera vide, c'est OK)
|
||||||
// const { autocomplete_options } = getOptions(select);
|
console.log('[process.ts] 4. Peuplement initial (probablement vide)...');
|
||||||
|
await populateAutocompleteList('');
|
||||||
|
|
||||||
// let options_to_show = [];
|
// S'abonne à l'événement de mise à jour du service
|
||||||
|
console.log('[process.ts] 5. 🎧 Abonnement à l\'événement "processes-updated"');
|
||||||
|
addSubscription(document, 'processes-updated', async () => {
|
||||||
|
console.log('[process.ts] 🔔 Événement "processes-updated" reçu ! Re-population de la liste...');
|
||||||
|
await populateAutocompleteList(inputSearch.value);
|
||||||
|
});
|
||||||
|
|
||||||
// const service = await Services.getInstance();
|
console.log('[process.ts] 6. init() terminée. Le composant est prêt.');
|
||||||
// const mineArray: string[] = await service.getMyProcesses();
|
}
|
||||||
// const allProcesses = await service.getProcesses();
|
|
||||||
// const allArray: string[] = Object.keys(allProcesses).filter(x => !mineArray.includes(x));
|
|
||||||
|
|
||||||
// const wrapper = select.parentNode;
|
// ==========================================
|
||||||
// const input_search = wrapper?.querySelector('.search-container');
|
// Fonctions de l'Autocomplete Multi-Select
|
||||||
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
// (Toutes les fonctions ci-dessous sont des "helpers" pour init)
|
||||||
// if (autocomplete_list) autocomplete_list.innerHTML = '';
|
// ==========================================
|
||||||
|
|
||||||
// const addProcessToList = (processId:string, isMine: boolean) => {
|
function removePlaceholder() {
|
||||||
// const li = document.createElement('li');
|
inputSearch?.removeAttribute('placeholder');
|
||||||
// li.innerText = processId;
|
}
|
||||||
// li.setAttribute("data-value", processId);
|
|
||||||
|
|
||||||
// if (isMine) {
|
function addPlaceholder() {
|
||||||
// li.classList.add("my-process");
|
if (!selectedProcessesContainer) return; // Sécurité
|
||||||
// li.style.cssText = `color: var(--accent-color)`;
|
const tokens = selectedProcessesContainer.querySelectorAll('.selected-wrapper');
|
||||||
// }
|
|
||||||
|
|
||||||
// if (li) addSubscription(li, 'click', selectOption);
|
if (!tokens?.length && document.activeElement !== inputSearch) {
|
||||||
// autocomplete_list?.appendChild(li);
|
inputSearch?.setAttribute('placeholder', 'Filtrer les processus...');
|
||||||
// };
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mineArray.forEach(processId => addProcessToList(processId, true));
|
function inputChange(e: Event) {
|
||||||
// allArray.forEach(processId => addProcessToList(processId, false));
|
const input_val = (e.target as HTMLInputElement).value;
|
||||||
|
const dropdown = wrapper.querySelector('.dropdown-icon');
|
||||||
// if (mineArray.length === 0 && allArray.length === 0) {
|
|
||||||
// const li = document.createElement('li');
|
|
||||||
// li.classList.add('not-cursor');
|
|
||||||
// li.innerText = 'No options found';
|
|
||||||
// autocomplete_list?.appendChild(li);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Listener to autocomplete results when clicked set the selected property in the select option
|
if (input_val) {
|
||||||
// function selectOption(e: any) {
|
dropdown?.classList.add('active');
|
||||||
// console.log('🎯 Click event:', e);
|
populateAutocompleteList(input_val.trim());
|
||||||
// console.log('🎯 Target value:', e.target.dataset.value);
|
} else {
|
||||||
|
dropdown?.classList.remove('active');
|
||||||
// const wrapper = e.target.parentNode.parentNode.parentNode;
|
dropdown?.dispatchEvent(new Event('click'));
|
||||||
// const select = wrapper.querySelector('select');
|
}
|
||||||
// const input_search = wrapper.querySelector('.selected-input');
|
}
|
||||||
// const option = wrapper.querySelector(`select option[value="${e.target.dataset.value}"]`);
|
|
||||||
|
|
||||||
// console.log('🎯 Selected option:', option);
|
|
||||||
// console.log('🎯 Process ID:', option?.getAttribute('data-process-id'));
|
|
||||||
|
|
||||||
// if (e.target.dataset.value.includes('messaging')) {
|
function clickOnWrapper(e: Event) {
|
||||||
// const messagingNumber = parseInt(e.target.dataset.value.split(' ')[1]);
|
// Ouvre la liste si on clique dans la zone vide du wrapper
|
||||||
// const processId = select.getAttribute(`data-messaging-id-${messagingNumber}`);
|
if (e.target === wrapper || e.target === selectedProcessesContainer) {
|
||||||
|
openAutocomplete();
|
||||||
// console.log('🚀 Dispatching newMessagingProcess event:', {
|
}
|
||||||
// processId,
|
}
|
||||||
// processName: `Messaging Process ${processId}`
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Dispatch l'événement avant la navigation
|
function openOptions(e: Event) {
|
||||||
// document.dispatchEvent(new CustomEvent('newMessagingProcess', {
|
const dropdown = wrapper.querySelector('.dropdown-icon');
|
||||||
// detail: {
|
if (!dropdown?.classList.contains('active')) {
|
||||||
// processId: processId,
|
dropdown?.dispatchEvent(new Event('click'));
|
||||||
// processName: `Messaging Process ${processId}`
|
}
|
||||||
// }
|
e.stopPropagation();
|
||||||
// }));
|
}
|
||||||
|
|
||||||
// // Navigation vers le chat
|
|
||||||
// const navigateEvent = new CustomEvent('navigate', {
|
|
||||||
// detail: {
|
|
||||||
// page: 'chat',
|
|
||||||
// processId: processId || ''
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// document.dispatchEvent(navigateEvent);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// option.setAttribute('selected', '');
|
|
||||||
// createToken(wrapper, e.target.dataset.value);
|
|
||||||
// if (input_search.value) {
|
|
||||||
// input_search.value = '';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// showSelectedProcess(e.target.dataset.value);
|
function createToken(processId: string, name: string) {
|
||||||
|
const token = document.createElement('div');
|
||||||
|
token.classList.add('selected-wrapper');
|
||||||
|
token.setAttribute('data-process-id', processId); // Stocke l'ID
|
||||||
|
|
||||||
// input_search.focus();
|
const tokenSpan = document.createElement('span');
|
||||||
|
tokenSpan.classList.add('selected-label');
|
||||||
|
tokenSpan.innerText = name; // Affiche le nom
|
||||||
|
|
||||||
// e.target.remove();
|
const close = document.createElement('a');
|
||||||
// const autocomplete_list = wrapper.querySelector('.autocomplete-list');
|
close.classList.add('selected-close');
|
||||||
|
close.innerText = 'x';
|
||||||
|
close.setAttribute('data-process-id', processId); // Utilise l'ID pour la suppression
|
||||||
|
addSubscription(close, 'click', removeToken);
|
||||||
|
|
||||||
// if (!autocomplete_list.children.length) {
|
token.appendChild(tokenSpan);
|
||||||
// const li = document.createElement('li');
|
token.appendChild(close);
|
||||||
// li.classList.add('not-cursor');
|
selectedProcessesContainer.appendChild(token);
|
||||||
// li.innerText = 'No options found';
|
removePlaceholder();
|
||||||
// autocomplete_list.appendChild(li);
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// const event = new Event('keyup');
|
function clickDropdown(e: Event) {
|
||||||
// input_search.dispatchEvent(event);
|
const dropdown = e.currentTarget as HTMLElement;
|
||||||
// e.stopPropagation();
|
dropdown.classList.toggle('active');
|
||||||
// }
|
|
||||||
|
|
||||||
// // function that returns a list with the autcomplete list of matches
|
if (dropdown.classList.contains('active')) {
|
||||||
// function autocomplete(query: string, options: any) {
|
openAutocomplete();
|
||||||
// // No query passed, just return entire list
|
} else {
|
||||||
// if (!query) {
|
closeAutocomplete();
|
||||||
// return options;
|
}
|
||||||
// }
|
}
|
||||||
// let options_return = [];
|
|
||||||
|
|
||||||
// for (let i = 0; i < options.length; i++) {
|
function openAutocomplete() {
|
||||||
// if (query.toLowerCase() === options[i].slice(0, query.length).toLowerCase()) {
|
if (!wrapper || !inputSearch) return;
|
||||||
// options_return.push(options[i]);
|
const dropdown = wrapper.querySelector('.dropdown-icon');
|
||||||
// }
|
dropdown?.classList.add('active');
|
||||||
// }
|
removePlaceholder();
|
||||||
// return options_return;
|
inputSearch.focus();
|
||||||
// }
|
populateAutocompleteList(inputSearch.value);
|
||||||
|
}
|
||||||
|
|
||||||
// // Returns the options that are selected by the user and the ones that are not
|
function closeAutocomplete() {
|
||||||
// function getOptions(select: HTMLSelectElement) {
|
if (!wrapper || !autocompleteList) return;
|
||||||
// // Select all the options available
|
const dropdown = wrapper.querySelector('.dropdown-icon');
|
||||||
// const all_options = Array.from(select.querySelectorAll('option')).map((el) => el.value);
|
dropdown?.classList.remove('active');
|
||||||
|
autocompleteList.innerHTML = '';
|
||||||
|
addPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
// // Get the options that are selected from the user
|
function clearAutocompleteList() {
|
||||||
// const options_selected = Array.from(select.querySelectorAll('option:checked')).map((el: any) => el.value);
|
if (autocompleteList) autocompleteList.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
// // Create an autocomplete options array with the options that are not selected by the user
|
async function populateAutocompleteList(query: string) {
|
||||||
// const autocomplete_options: any[] = [];
|
if (!autocompleteList || !select) return; // Sécurité
|
||||||
// all_options.forEach((option) => {
|
|
||||||
// if (!options_selected.includes(option)) {
|
|
||||||
// autocomplete_options.push(option);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// autocomplete_options.sort();
|
autocompleteList.innerHTML = ''; // Vide la liste visible
|
||||||
|
select.innerHTML = ''; // Vide le select caché
|
||||||
|
|
||||||
// return {
|
const mineArray: string[] = (await services.getMyProcesses()) ?? [];
|
||||||
// options_selected,
|
const allProcesses = await services.getProcesses();
|
||||||
// autocomplete_options,
|
const allArray: string[] = Object.keys(allProcesses).filter((x) => !mineArray.includes(x));
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Listener for when the user wants to remove a given token.
|
const processIdsToShow = [...mineArray, ...allArray];
|
||||||
// function removeToken(e: Event) {
|
let itemsFound = 0;
|
||||||
// // Get the value to remove
|
|
||||||
// const target = e.target as HTMLSelectElement;
|
|
||||||
// const value_to_remove = target.dataset.option;
|
|
||||||
// const wrapper = target.parentNode?.parentNode?.parentNode;
|
|
||||||
// const input_search = wrapper?.querySelector('.selected-input');
|
|
||||||
// const dropdown = wrapper?.querySelector('.dropdown-icon');
|
|
||||||
// // Get the options in the select to be unselected
|
|
||||||
// const option_to_unselect = wrapper?.querySelector(`select option[value="${value_to_remove}"]`);
|
|
||||||
// option_to_unselect?.removeAttribute('selected');
|
|
||||||
// // Remove token attribute
|
|
||||||
// (target.parentNode as any)?.remove();
|
|
||||||
// dropdown?.classList.remove('active');
|
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
|
||||||
|
|
||||||
// const process = container.querySelector('#' + target.dataset.option);
|
for (const processId of processIdsToShow) {
|
||||||
// process?.remove();
|
const process = allProcesses[processId];
|
||||||
// }
|
if (!process) continue;
|
||||||
|
|
||||||
// // Listen for 2 sequence of hits on the delete key, if this happens delete the last token if exist
|
const name = services.getProcessName(process) || processId;
|
||||||
// function deletePressed(e: Event) {
|
|
||||||
// const input_search = e.target as HTMLInputElement;
|
|
||||||
// const wrapper = input_search?.parentNode?.parentNode;
|
|
||||||
// const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode;
|
|
||||||
// const tokens = wrapper?.querySelectorAll('.selected-wrapper');
|
|
||||||
|
|
||||||
// if (tokens?.length) {
|
// Filtre
|
||||||
// const last_token_x = tokens[tokens.length - 1].querySelector('a');
|
if (query && !name.toLowerCase().includes(query.toLowerCase()) && !processId.includes(query)) {
|
||||||
// let hits = +(last_token_x?.dataset?.hits || 0);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// if (key == 8 || key == 46) {
|
itemsFound++;
|
||||||
// if (!input_search.value) {
|
|
||||||
// if (hits > 1) {
|
|
||||||
// // Trigger delete event
|
|
||||||
// const event = new Event('click');
|
|
||||||
// last_token_x?.dispatchEvent(event);
|
|
||||||
// } else {
|
|
||||||
// if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Dismiss on outside click
|
// 1. Crée l'élément VISIBLE (<li>)
|
||||||
// addSubscription(document, 'click', () => {
|
const li = document.createElement('li');
|
||||||
// // get select that has the options available
|
li.innerText = name;
|
||||||
// const select = document.querySelectorAll('[data-multi-select-plugin]');
|
li.setAttribute('data-value', processId); // L'ID est le 'data-value'
|
||||||
// for (let i = 0; i < select.length; i++) {
|
if (mineArray.includes(processId)) {
|
||||||
// if (event) {
|
li.classList.add('my-process');
|
||||||
// var isClickInside = select[i].parentElement?.parentElement?.contains(event.target as Node);
|
li.style.cssText = `color: var(--accent-color)`;
|
||||||
|
}
|
||||||
|
addSubscription(li, 'click', selectOption);
|
||||||
|
autocompleteList.appendChild(li);
|
||||||
|
|
||||||
// if (!isClickInside) {
|
// 2. Crée l'élément CACHÉ (<option>)
|
||||||
// const wrapper = select[i].parentElement?.parentElement;
|
const option = document.createElement('option');
|
||||||
// const dropdown = wrapper?.querySelector('.dropdown-icon');
|
option.value = processId; // 'value' correspond au 'data-value'
|
||||||
// const autocomplete_list = wrapper?.querySelector('.autocomplete-list');
|
option.text = name;
|
||||||
// //the click was outside the specifiedElement, do something
|
option.setAttribute('data-process-id', processId);
|
||||||
// dropdown?.classList.remove('active');
|
select.appendChild(option);
|
||||||
// if (autocomplete_list) autocomplete_list.innerHTML = '';
|
}
|
||||||
// addPlaceholder(wrapper as HTMLElement);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// async function showSelectedProcess(elem: MouseEvent) {
|
if (itemsFound === 0) {
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
const li = document.createElement('li');
|
||||||
|
li.classList.add('not-cursor');
|
||||||
|
li.innerText = 'No options found';
|
||||||
|
autocompleteList.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (elem) {
|
function selectOption(e: any) {
|
||||||
// const cardContent = container.querySelector('.process-card-content');
|
console.log('🎯 Click event:', e);
|
||||||
|
const selectedLi = e.currentTarget as HTMLLIElement; // Utilise currentTarget
|
||||||
|
const clickedValue = selectedLi.dataset.value;
|
||||||
|
console.log('🎯 Clicked value (depuis le <li>):', clickedValue);
|
||||||
|
|
||||||
// const processes = await getProcesses();
|
if (!clickedValue) {
|
||||||
// const process = processes.find((process: any) => process[1].title === elem);
|
console.error("💥 Clic sur un élément sans 'data-value'.");
|
||||||
// if (process) {
|
return;
|
||||||
// const processDiv = document.createElement('div');
|
}
|
||||||
// processDiv.className = 'process';
|
|
||||||
// processDiv.id = process[0];
|
|
||||||
// const titleDiv = document.createElement('div');
|
|
||||||
// titleDiv.className = 'process-title';
|
|
||||||
// titleDiv.innerHTML = `${process[1].title} : ${process[1].description}`;
|
|
||||||
// processDiv.appendChild(titleDiv);
|
|
||||||
// for (const zone of process.zones) {
|
|
||||||
// const zoneElement = document.createElement('div');
|
|
||||||
// zoneElement.className = 'process-element';
|
|
||||||
// const zoneId = process[1].title + '-' + zone.id;
|
|
||||||
// zoneElement.setAttribute('zone-id', zoneId);
|
|
||||||
// zoneElement.setAttribute('process-title', process[1].title);
|
|
||||||
// zoneElement.setAttribute('process-id', `${process[0]}_${zone.id}`);
|
|
||||||
// zoneElement.innerHTML = `${zone.title}: ${zone.description}`;
|
|
||||||
// addSubscription(zoneElement, 'click', select);
|
|
||||||
// processDiv.appendChild(zoneElement);
|
|
||||||
// }
|
|
||||||
// if (cardContent) cardContent.appendChild(processDiv);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function select(event: Event) {
|
const processIdValue = clickedValue; // C'est déjà le bon ID
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
|
||||||
// const target = event.target as HTMLElement;
|
|
||||||
// const oldSelectedProcess = container.querySelector('.selected-process-zone');
|
|
||||||
// oldSelectedProcess?.classList.remove('selected-process-zone');
|
|
||||||
// if (target) {
|
|
||||||
// target.classList.add('selected-process-zone');
|
|
||||||
// }
|
|
||||||
// const name = target.getAttribute('zone-id');
|
|
||||||
// console.log('🚀 ~ select ~ name:', name);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// function goToProcessPage() {
|
// --- Gestion 'messaging' ---
|
||||||
// const container = getCorrectDOM('process-list-4nk-component') as HTMLElement;
|
if (clickedValue.includes('messaging')) {
|
||||||
|
// ... (ta logique 'messaging' reste ici) ...
|
||||||
|
// Note: cette logique est probablement cassée si 'messaging' n'est pas un processId
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// const target = container.querySelector('.selected-process-zone');
|
// --- 🚨 CORRECTION DU CRASH ---
|
||||||
// console.log('🚀 ~ goToProcessPage ~ event:', target);
|
const option = select?.querySelector(`option[value="${processIdValue}"]`) as HTMLOptionElement;
|
||||||
// if (target) {
|
|
||||||
// const process = target?.getAttribute('process-id');
|
|
||||||
|
|
||||||
// console.log('=======================> going to process page', process);
|
if (!option) {
|
||||||
// // navigate('process-element/' + process);
|
console.error(`💥 BUG: Impossible de trouver l'option avec la valeur "${processIdValue}"`);
|
||||||
// document.querySelector('process-list-4nk-component')?.dispatchEvent(
|
return;
|
||||||
// new CustomEvent('processSelected', {
|
}
|
||||||
// detail: {
|
|
||||||
// process: process,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// (window as any).goToProcessPage = goToProcessPage;
|
option.setAttribute('selected', 'true');
|
||||||
|
option.selected = true;
|
||||||
|
|
||||||
// async function createMessagingProcess(): Promise<void> {
|
createToken(processIdValue, option.text); // Passe l'ID et le nom
|
||||||
// console.log('Creating messaging process');
|
if (inputSearch.value) {
|
||||||
// const service = await Services.getInstance();
|
inputSearch.value = '';
|
||||||
// const otherMembers = [
|
}
|
||||||
// {
|
|
||||||
// sp_addresses: [
|
|
||||||
// "tsp1qqd7snxfh44am8f7a3x36znkh4v0dcagcgakfux488ghsg0tny7degq4gd9q4n4us0cyp82643f2p4jgcmtwknadqwl3waf9zrynl6n7lug5tg73a",
|
|
||||||
// "tsp1qqvd8pak9fyz55rxqj90wxazqzwupf2egderc96cn84h3l84z8an9vql85scudrmwvsnltfuy9ungg7pxnhys2ft5wnf2gyr3n4ukvezygswesjuc"
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// sp_addresses: [
|
|
||||||
// "tsp1qqgl5vawdey6wnnn2sfydcejsr06uzwsjlfa6p6yr8u4mkqwezsnvyqlazuqmxhxd8crk5eq3wfvdwv4k3tn68mkj2nj72jj39d2ngauu4unfx0q7",
|
|
||||||
// "tsp1qqthmj56gj8vvkjzwhcmswftlrf6ye7ukpks2wra92jkehqzrvx7m2q570q5vv6zj6dnxvussx2h8arvrcfwz9sp5hpdzrfugmmzz90pmnganxk28"
|
|
||||||
// ]
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// sp_addresses: [
|
|
||||||
// "tsp1qqwjtxr9jye7d40qxrsmd6h02egdwel6mfnujxzskgxvxphfya4e6qqjq4tsdmfdmtnmccz08ut24q8y58qqh4lwl3w8pvh86shlmavrt0u3smhv2",
|
|
||||||
// "tsp1qqwn7tf8q2jhmfh8757xze53vg2zc6x5u6f26h3wyty9mklvcy0wnvqhhr4zppm5uyyte4y86kljvh8r0tsmkmszqqwa3ecf2lxcs7q07d56p8sz5"
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// ];
|
|
||||||
// await service.checkConnections(otherMembers);
|
|
||||||
// const relayAddress = service.getAllRelays().pop();
|
|
||||||
// if (!relayAddress) {
|
|
||||||
// throw new Error('Empty relay address list');
|
|
||||||
// }
|
|
||||||
// const feeRate = 1;
|
|
||||||
// setTimeout(async () => {
|
|
||||||
// const createProcessReturn = await service.createMessagingProcess(otherMembers, relayAddress.spAddress, feeRate);
|
|
||||||
// const updatedProcess = createProcessReturn.updated_process.current_process;
|
|
||||||
// if (!updatedProcess) {
|
|
||||||
// console.error('Failed to retrieved new messaging process');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const processId = updatedProcess.states[0].commited_in;
|
|
||||||
// const stateId = updatedProcess.states[0].state_id;
|
|
||||||
// await service.handleApiReturn(createProcessReturn);
|
|
||||||
// const createPrdReturn = await service.createPrdUpdate(processId, stateId);
|
|
||||||
// await service.handleApiReturn(createPrdReturn);
|
|
||||||
// const approveChangeReturn = await service.approveChange(processId, stateId);
|
|
||||||
// await service.handleApiReturn(approveChangeReturn);
|
|
||||||
// }, 500)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async function getDescription(processId: string, process: Process): Promise<string | null> {
|
showSelectedProcess(processIdValue);
|
||||||
// const service = await Services.getInstance();
|
|
||||||
// // Get the `commited_in` value of the last state and remove it from the array
|
|
||||||
// const currentCommitedIn = process.states.pop()?.commited_in;
|
|
||||||
|
|
||||||
// if (currentCommitedIn === undefined) {
|
inputSearch.focus();
|
||||||
// return null; // No states available
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Find the last state where `commited_in` is different
|
selectedLi.remove(); // Supprime le <li> de la liste des suggestions
|
||||||
// let lastDifferentState = process.states.findLast(
|
|
||||||
// state => state.commited_in !== currentCommitedIn
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if (!lastDifferentState) {
|
if (!autocompleteList?.children.length) {
|
||||||
// // It means that we only have one state that is not commited yet, that can happen with process we just created
|
const li = document.createElement('li');
|
||||||
// // let's assume that the right description is in the last concurrent state and not handle the (arguably rare) case where we have multiple concurrent states on a creation
|
li.classList.add('not-cursor');
|
||||||
// lastDifferentState = process.states.pop();
|
li.innerText = 'No options found';
|
||||||
// }
|
autocompleteList.appendChild(li);
|
||||||
|
}
|
||||||
|
|
||||||
// // Take the description out of the state, if any
|
inputSearch.dispatchEvent(new Event('keyup'));
|
||||||
// const description = lastDifferentState!.pcd_commitment['description'];
|
e.stopPropagation();
|
||||||
// if (description) {
|
}
|
||||||
// const userDiff = await service.getDiffByValue(description);
|
|
||||||
// if (userDiff) {
|
|
||||||
// console.log("Successfully retrieved userDiff:", userDiff);
|
|
||||||
// return userDiff.new_value;
|
|
||||||
// } else {
|
|
||||||
// console.log("Failed to retrieve a non-null userDiff.");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return null;
|
function removeToken(e: Event) {
|
||||||
// }
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const closeButton = e.currentTarget as HTMLElement;
|
||||||
|
const processId = closeButton.dataset.processId;
|
||||||
|
|
||||||
|
if (!processId) return;
|
||||||
|
|
||||||
|
// 1. Supprime le "token" visuel
|
||||||
|
const token = shadowContainer.querySelector(`.selected-wrapper[data-process-id="${processId}"]`);
|
||||||
|
token?.remove();
|
||||||
|
|
||||||
|
// 2. Désélectionne l'option dans le <select> caché
|
||||||
|
const option = select?.querySelector(`option[value="${processId}"]`) as HTMLOptionElement;
|
||||||
|
if (option) {
|
||||||
|
option.removeAttribute('selected');
|
||||||
|
option.selected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vide le panneau de détails
|
||||||
|
if (processCardContent) processCardContent.innerHTML = '';
|
||||||
|
|
||||||
|
// 4. Recharge la liste d'autocomplétion (pour y remettre l'option)
|
||||||
|
populateAutocompleteList(inputSearch.value);
|
||||||
|
addPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
function deletePressed(e: Event) {
|
||||||
|
const key = (e as KeyboardEvent).keyCode || (e as KeyboardEvent).charCode;
|
||||||
|
|
||||||
|
if (!selectedProcessesContainer) return;
|
||||||
|
const tokens = selectedProcessesContainer.querySelectorAll('.selected-wrapper');
|
||||||
|
|
||||||
|
if (tokens?.length) {
|
||||||
|
const last_token_x = tokens[tokens.length - 1].querySelector('a');
|
||||||
|
let hits = +(last_token_x?.dataset?.hits || 0);
|
||||||
|
|
||||||
|
if (key == 8 || key == 46) {
|
||||||
|
// Backspace ou Suppr
|
||||||
|
if (!inputSearch.value) {
|
||||||
|
if (hits > 1) {
|
||||||
|
last_token_x?.dispatchEvent(new Event('click'));
|
||||||
|
} else {
|
||||||
|
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '2';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (last_token_x?.dataset.hits) last_token_x.dataset.hits = '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Fonctions de Logique (Affichage)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
async function showSelectedProcess(processIdValue: string) {
|
||||||
|
const processId = processIdValue;
|
||||||
|
// const processId = processIdValue.split(':')[0];
|
||||||
|
|
||||||
|
if (!processId || !processCardContent) return;
|
||||||
|
|
||||||
|
console.log(`[process.ts] 🔍 Affichage des détails pour: ${processId}`);
|
||||||
|
processCardContent.innerHTML = ''; // Nettoie
|
||||||
|
|
||||||
|
const process = await services.getProcess(processId);
|
||||||
|
if (process) {
|
||||||
|
const processDiv = document.createElement('div');
|
||||||
|
processDiv.className = 'process';
|
||||||
|
processDiv.id = processId;
|
||||||
|
|
||||||
|
const titleDiv = document.createElement('div');
|
||||||
|
titleDiv.className = 'process-title';
|
||||||
|
const processName = services.getProcessName(process) || 'Processus sans nom';
|
||||||
|
const description = await getDescription(process);
|
||||||
|
titleDiv.innerHTML = `${processName} : ${description || 'Pas de description'}`;
|
||||||
|
processDiv.appendChild(titleDiv);
|
||||||
|
|
||||||
|
const uncommittedStates = services.getUncommitedStates(process);
|
||||||
|
if (uncommittedStates.length > 0) {
|
||||||
|
for (const state of uncommittedStates) {
|
||||||
|
const zoneElement = document.createElement('div');
|
||||||
|
zoneElement.className = 'process-element';
|
||||||
|
const zoneId = `${processId}_${state.state_id}`;
|
||||||
|
zoneElement.setAttribute('zone-id', zoneId);
|
||||||
|
zoneElement.setAttribute('process-id', zoneId);
|
||||||
|
zoneElement.innerHTML = `État (non "commité"): ${state.state_id.substring(0, 10)}...`;
|
||||||
|
addSubscription(zoneElement, 'click', selectState);
|
||||||
|
processDiv.appendChild(zoneElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const noStateSpan = document.createElement('span');
|
||||||
|
noStateSpan.innerText = "Ce processus n'a pas d'état en attente.";
|
||||||
|
processDiv.appendChild(noStateSpan);
|
||||||
|
}
|
||||||
|
processCardContent.appendChild(processDiv);
|
||||||
|
} else {
|
||||||
|
console.error(`[process.ts] 💥 Processus ${processId} non trouvé.`);
|
||||||
|
processCardContent.innerHTML = '<span>Erreur: Processus non trouvé.</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProcesses(): Promise<[string, Process][]> {
|
||||||
|
const processes = await services.getProcesses();
|
||||||
|
const res = Object.entries(processes);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectState(event: Event) {
|
||||||
|
const target = event.currentTarget as HTMLElement;
|
||||||
|
const oldSelectedProcess = shadowContainer.querySelector('.selected-process-zone');
|
||||||
|
oldSelectedProcess?.classList.remove('selected-process-zone');
|
||||||
|
if (target) {
|
||||||
|
target.classList.add('selected-process-zone');
|
||||||
|
}
|
||||||
|
const name = target.getAttribute('zone-id');
|
||||||
|
console.log('[process.ts] 🎯 État sélectionné:', name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToProcessPage() {
|
||||||
|
const target = shadowContainer.querySelector('.selected-process-zone');
|
||||||
|
console.log('[process.ts] 🚀 Clic sur "OK". État sélectionné:', target);
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
const process = target?.getAttribute('process-id');
|
||||||
|
console.log('=======================> Navigation vers process-element', process);
|
||||||
|
|
||||||
|
// Dispatch l'événement 'navigate' que 'router.ts' écoute
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent('navigate', {
|
||||||
|
detail: {
|
||||||
|
page: `process-element/${process}`,
|
||||||
|
},
|
||||||
|
bubbles: true,
|
||||||
|
composed: true, // Permet à l'événement de sortir du Shadow DOM
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.warn('[process.ts] ⚠️ Clic sur "OK" mais aucun état n\'est sélectionné.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDescription(process: Process): Promise<string | null> {
|
||||||
|
const lastDifferentState = services.getLastCommitedState(process);
|
||||||
|
if (!lastDifferentState) return null;
|
||||||
|
|
||||||
|
const descriptionHash = lastDifferentState!.pcd_commitment['description'];
|
||||||
|
if (descriptionHash) {
|
||||||
|
const userDiff = await services.getDiffByValue(descriptionHash);
|
||||||
|
if (userDiff) {
|
||||||
|
return userDiff.value_commitment; // Utilise le bon champ
|
||||||
|
} else {
|
||||||
|
console.warn(`[process.ts] Impossible de trouver le 'diff' pour la description (hash: ${descriptionHash})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { SignatureElement } from './signature';
|
import { SignatureElement } from './signature';
|
||||||
import signatureCss from '../../../public/style/signature.css?raw'
|
import signatureCss from '../../../style/signature.css?raw'
|
||||||
import Services from '../../services/service.js'
|
import Services from '../../services/service.js'
|
||||||
|
|
||||||
class SignatureComponent extends HTMLElement {
|
class SignatureComponent extends HTMLElement {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import signatureStyle from '../../../public/style/signature.css?inline';
|
import signatureStyle from '../../../style/signature.css?inline';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
1270
src/router.ts
1270
src/router.ts
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,16 +3,31 @@ import axios, { AxiosResponse } from 'axios';
|
|||||||
export async function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null> {
|
export async function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null> {
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
try {
|
try {
|
||||||
// Handle relative paths (for development proxy) vs absolute URLs (for production)
|
// --- DÉBUT DE LA CORRECTION ---
|
||||||
|
// 1. On vérifie d'abord si la donnée existe en appelant le bon service
|
||||||
|
// On passe 'server' au lieu de 'url' pour que testData construise la bonne URL
|
||||||
|
const dataExists = await testData(server, key);
|
||||||
|
|
||||||
|
if (dataExists) {
|
||||||
|
console.log('Data already stored:', key);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
console.log('Data not stored for server, proceeding to POST:', key, server);
|
||||||
|
}
|
||||||
|
// --- FIN DE LA CORRECTION ---
|
||||||
|
|
||||||
|
|
||||||
|
// Construction de l'URL pour le POST (stockage)
|
||||||
|
// Cette partie était correcte
|
||||||
let url: string;
|
let url: string;
|
||||||
if (server.startsWith('/')) {
|
if (server.startsWith('/')) {
|
||||||
// Relative path - construct manually for proxy
|
// Relative path
|
||||||
url = `${server}/store/${encodeURIComponent(key)}`;
|
url = `${server}/store/${encodeURIComponent(key)}`;
|
||||||
if (ttl !== null) {
|
if (ttl !== null) {
|
||||||
url += `?ttl=${ttl}`;
|
url += `?ttl=${ttl}`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Absolute URL - use URL constructor
|
// Absolute URL
|
||||||
const urlObj = new URL(`${server}/store/${encodeURIComponent(key)}`);
|
const urlObj = new URL(`${server}/store/${encodeURIComponent(key)}`);
|
||||||
if (ttl !== null) {
|
if (ttl !== null) {
|
||||||
urlObj.searchParams.append('ttl', ttl.toString());
|
urlObj.searchParams.append('ttl', ttl.toString());
|
||||||
@ -20,17 +35,11 @@ export async function storeData(servers: string[], key: string, value: Blob, ttl
|
|||||||
url = urlObj.toString();
|
url = urlObj.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test first that data is not already stored
|
// La ligne ci-dessous a été supprimée car le test est fait au-dessus
|
||||||
const testResponse = await testData(url, key);
|
// const testResponse = await testData(url, key); // <-- LIGNE BOGUÉE SUPPRIMÉE
|
||||||
if (testResponse) {
|
|
||||||
console.log('Data already stored:', key);
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
console.log('Data not stored for server:', key, server);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the encrypted ArrayBuffer as the raw request body.
|
// Send the encrypted data as the raw request body.
|
||||||
const response = await axios.post(url, value, {
|
const response = await axios.post(url, value, { // Note: c'est bien un POST sur 'url'
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/octet-stream'
|
'Content-Type': 'application/octet-stream'
|
||||||
},
|
},
|
||||||
@ -43,7 +52,7 @@ export async function storeData(servers: string[], key: string, value: Blob, ttl
|
|||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error) && error.response?.status === 409) {
|
if (axios.isAxiosError(error) && error.response?.status === 409) {
|
||||||
return null;
|
return null; // 409 Conflict (Key already exists)
|
||||||
}
|
}
|
||||||
console.error('Error storing data:', error);
|
console.error('Error storing data:', error);
|
||||||
}
|
}
|
||||||
@ -51,22 +60,21 @@ export async function storeData(servers: string[], key: string, value: Blob, ttl
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonction retrieveData (inchangée, elle était correcte)
|
||||||
export async function retrieveData(servers: string[], key: string): Promise<ArrayBuffer | null> {
|
export async function retrieveData(servers: string[], key: string): Promise<ArrayBuffer | null> {
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
try {
|
try {
|
||||||
// Handle relative paths (for development proxy) vs absolute URLs (for production)
|
|
||||||
const url = server.startsWith('/')
|
const url = server.startsWith('/')
|
||||||
? `${server}/retrieve/${key}` // Relative path - use as-is for proxy
|
? `${server}/retrieve/${key}`
|
||||||
: new URL(`${server}/retrieve/${key}`).toString(); // Absolute URL - construct properly
|
: new URL(`${server}/retrieve/${key}`).toString();
|
||||||
|
|
||||||
console.log('Retrieving data', key,' from:', url);
|
console.log('Retrieving data', key,' from:', url);
|
||||||
// When fetching the data from the server:
|
|
||||||
const response = await axios.get(url, {
|
const response = await axios.get(url, {
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
// Validate that we received an ArrayBuffer
|
|
||||||
if (response.data instanceof ArrayBuffer) {
|
if (response.data instanceof ArrayBuffer) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
@ -80,8 +88,9 @@ export async function retrieveData(servers: string[], key: string): Promise<Arra
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (axios.isAxiosError(error)) {
|
if (axios.isAxiosError(error)) {
|
||||||
if (error.response?.status === 404) {
|
if (error.response?.status === 404) {
|
||||||
|
// C'est normal si la donnée n'existe pas
|
||||||
console.log(`Data not found on server ${server} for key ${key}`);
|
console.log(`Data not found on server ${server} for key ${key}`);
|
||||||
continue; // Try next server
|
continue;
|
||||||
} else if (error.response?.status) {
|
} else if (error.response?.status) {
|
||||||
console.error(`Server ${server} error ${error.response.status}:`, error.response.statusText);
|
console.error(`Server ${server} error ${error.response.status}:`, error.response.statusText);
|
||||||
continue;
|
continue;
|
||||||
@ -103,17 +112,27 @@ interface TestResponse {
|
|||||||
value: boolean;
|
value: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function testData(url: string, key: string): Promise<boolean | null> {
|
// --- FONCTION testData CORRIGÉE ---
|
||||||
|
// Elle prend 'server' au lieu de 'url' et construit sa propre URL '/test/...'
|
||||||
|
export async function testData(server: string, key: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(url);
|
// Construit l'URL /test/...
|
||||||
if (response.status !== 200) {
|
const testUrl = server.startsWith('/')
|
||||||
console.error(`Test response status: ${response.status}`);
|
? `${server}/test/${encodeURIComponent(key)}`
|
||||||
return false;
|
: new URL(`${server}/test/${encodeURIComponent(key)}`).toString();
|
||||||
}
|
|
||||||
|
const response = await axios.get(testUrl); // Fait un GET sur /test/...
|
||||||
|
|
||||||
|
// 200 OK = la donnée existe
|
||||||
|
return response.status === 200;
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error testing data:', error);
|
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||||
return null;
|
// 404 Not Found = la donnée n'existe pas. C'est une réponse valide.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Toute autre erreur (serveur offline, 500, etc.)
|
||||||
|
console.error('Error testing data:', error);
|
||||||
|
return false; // On considère que le test a échoué
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ export async function displayEmojis(text: string) {
|
|||||||
|
|
||||||
// Verify Other address
|
// Verify Other address
|
||||||
export function initAddressInput() {
|
export function initAddressInput() {
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
const addressInput = container.querySelector('#addressInput') as HTMLInputElement;
|
const addressInput = container.querySelector('#addressInput') as HTMLInputElement;
|
||||||
const emojiDisplay = container.querySelector('#emoji-display-2');
|
const emojiDisplay = container.querySelector('#emoji-display-2');
|
||||||
const okButton = container.querySelector('#okButton') as HTMLButtonElement;
|
const okButton = container.querySelector('#okButton') as HTMLButtonElement;
|
||||||
@ -155,7 +155,7 @@ async function onCreateButtonClick() {
|
|||||||
// Don't call confirmPairing immediately - it will be called when the pairing process is complete
|
// Don't call confirmPairing immediately - it will be called when the pairing process is complete
|
||||||
console.log('Pairing process initiated. Waiting for completion...');
|
console.log('Pairing process initiated. Waiting for completion...');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`onCreateButtonClick error: ${e}`);
|
console.error(`onCreateButtonClick error: ${e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,17 +164,14 @@ export async function prepareAndSendPairingTx(): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const relayAddress = service.getAllRelays();
|
const relayAddress = service.getAllRelays();
|
||||||
const createPairingProcessReturn = await service.createPairingProcess(
|
const createPairingProcessReturn = await service.createPairingProcess('', []);
|
||||||
"",
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!createPairingProcessReturn.updated_process) {
|
if (!createPairingProcessReturn.updated_process) {
|
||||||
throw new Error('createPairingProcess returned an empty new process');
|
throw new Error('createPairingProcess returned an empty new process');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await service.checkConnections(createPairingProcessReturn.updated_process.current_process, createPairingProcessReturn.updated_process.current_process.states[0].state_id);
|
await service.ensureConnections(createPairingProcessReturn.updated_process.current_process, createPairingProcessReturn.updated_process.current_process.states[0].state_id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -194,7 +191,6 @@ export async function prepareAndSendPairingTx(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await service.handleApiReturn(createPairingProcessReturn);
|
await service.handleApiReturn(createPairingProcessReturn);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@ -202,7 +198,7 @@ export async function prepareAndSendPairingTx(): Promise<void> {
|
|||||||
|
|
||||||
export async function generateQRCode(spAddress: string) {
|
export async function generateQRCode(spAddress: string) {
|
||||||
try {
|
try {
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
const currentUrl = 'https://' + window.location.host;
|
const currentUrl = 'https://' + window.location.host;
|
||||||
const url = await QRCode.toDataURL(currentUrl + '?sp_address=' + spAddress);
|
const url = await QRCode.toDataURL(currentUrl + '?sp_address=' + spAddress);
|
||||||
const qrCode = container?.querySelector('.qr-code img');
|
const qrCode = container?.querySelector('.qr-code img');
|
||||||
@ -213,9 +209,9 @@ export async function generateQRCode(spAddress: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function generateCreateBtn() {
|
export async function generateCreateBtn() {
|
||||||
try{
|
try {
|
||||||
//Generate CreateBtn
|
// Generate CreateBtn
|
||||||
const container = getCorrectDOM('login-4nk-component') as HTMLElement
|
const container = getCorrectDOM('login-4nk-component') as HTMLElement;
|
||||||
const createBtn = container?.querySelector('.create-btn');
|
const createBtn = container?.querySelector('.create-btn');
|
||||||
if (createBtn) {
|
if (createBtn) {
|
||||||
createBtn.textContent = 'CREATE';
|
createBtn.textContent = 'CREATE';
|
||||||
@ -223,5 +219,4 @@ export async function generateCreateBtn() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -5,6 +5,10 @@ import {createHtmlPlugin} from 'vite-plugin-html';
|
|||||||
import typescript from "@rollup/plugin-typescript";
|
import typescript from "@rollup/plugin-typescript";
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
// import pluginTerminal from 'vite-plugin-terminal';
|
// import pluginTerminal from 'vite-plugin-terminal';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user