Compare commits
4 Commits
d45cf7c530
...
96ee5b03e6
| Author | SHA1 | Date | |
|---|---|---|---|
| 96ee5b03e6 | |||
| 8a87fe38c5 | |||
| bbbca27009 | |||
| e1d220596e |
@ -1,6 +1,3 @@
|
|||||||
VITE_API_URL=https://api.example.com
|
|
||||||
VITE_API_KEY=your_api_key
|
|
||||||
VITE_JWT_SECRET_KEY=your_secret_key
|
|
||||||
VITE_BASEURL="your_base_url"
|
VITE_BASEURL="your_base_url"
|
||||||
VITE_BOOTSTRAPURL="your_bootstrap_url"
|
VITE_BOOTSTRAPURL="your_bootstrap_url"
|
||||||
VITE_STORAGEURL="your_storage_url"
|
VITE_STORAGEURL="your_storage_url"
|
||||||
|
|||||||
@ -1,82 +1,75 @@
|
|||||||
const EMPTY32BYTES = String('').padStart(64, '0');
|
// public/data.worker.js
|
||||||
|
|
||||||
|
const DB_NAME = "4nk";
|
||||||
|
const DB_VERSION = 1;
|
||||||
|
const EMPTY32BYTES = String("").padStart(64, "0");
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// SERVICE WORKER LIFECYCLE
|
// SERVICE WORKER LIFECYCLE
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(self.skipWaiting());
|
event.waitUntil(self.skipWaiting());
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
self.addEventListener("activate", (event) => {
|
||||||
event.waitUntil(self.clients.claim());
|
event.waitUntil(self.clients.claim());
|
||||||
});
|
});
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// MESSAGE HANDLER
|
// INDEXEDDB DIRECT ACCESS (READ-ONLY)
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
self.addEventListener('message', async (event) => {
|
/**
|
||||||
const data = event.data;
|
* Ouvre une connexion à la BDD directement depuis le Service Worker
|
||||||
console.log('[Service Worker] Message received:', data.type);
|
*/
|
||||||
|
function openDB() {
|
||||||
if (data.type === 'SCAN') {
|
|
||||||
try {
|
|
||||||
const myProcessesId = data.payload;
|
|
||||||
if (myProcessesId && myProcessesId.length != 0) {
|
|
||||||
const scanResult = await scanMissingData(myProcessesId, event.source);
|
|
||||||
|
|
||||||
if (scanResult.toDownload.length != 0) {
|
|
||||||
console.log('[Service Worker] Sending TO_DOWNLOAD message');
|
|
||||||
event.source.postMessage({ type: 'TO_DOWNLOAD', data: scanResult.toDownload });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanResult.diffsToCreate.length > 0) {
|
|
||||||
console.log('[Service Worker] Sending DIFFS_TO_CREATE message');
|
|
||||||
event.source.postMessage({ type: 'DIFFS_TO_CREATE', data: scanResult.diffsToCreate });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event.source.postMessage({ status: 'error', message: 'Empty lists' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[Service Worker] Scan error:', error);
|
|
||||||
event.source.postMessage({ status: 'error', message: error.message });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ============================================
|
|
||||||
// DATABASE COMMUNICATION
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
async function requestFromMainThread(client, action, payload) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const messageId = `sw_${Date.now()}_${Math.random()}`;
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
const messageHandler = (event) => {
|
request.onsuccess = () => resolve(request.result);
|
||||||
if (event.data.id === messageId) {
|
});
|
||||||
self.removeEventListener('message', messageHandler);
|
}
|
||||||
if (event.data.type === 'DB_RESPONSE') {
|
|
||||||
resolve(event.data.result);
|
/**
|
||||||
} else if (event.data.type === 'DB_ERROR') {
|
* Récupère un objet spécifique (équivalent à GET_OBJECT)
|
||||||
reject(new Error(event.data.error));
|
*/
|
||||||
}
|
function getObject(db, storeName, key) {
|
||||||
}
|
return new Promise((resolve, reject) => {
|
||||||
};
|
const transaction = db.transaction(storeName, "readonly");
|
||||||
|
const store = transaction.objectStore(storeName);
|
||||||
self.addEventListener('message', messageHandler);
|
const request = store.get(key);
|
||||||
|
request.onerror = () => reject(request.error);
|
||||||
client.postMessage({
|
request.onsuccess = () => resolve(request.result);
|
||||||
type: 'DB_REQUEST',
|
});
|
||||||
id: messageId,
|
}
|
||||||
action,
|
|
||||||
payload
|
/**
|
||||||
|
* Récupère plusieurs objets d'un coup (équivalent à GET_MULTIPLE_OBJECTS)
|
||||||
|
* Optimisé pour utiliser une seule transaction.
|
||||||
|
*/
|
||||||
|
function getMultipleObjects(db, storeName, keys) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const transaction = db.transaction(storeName, "readonly");
|
||||||
|
const store = transaction.objectStore(storeName);
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
let completed = 0;
|
||||||
|
if (keys.length === 0) resolve([]);
|
||||||
|
|
||||||
|
keys.forEach((key) => {
|
||||||
|
const request = store.get(key);
|
||||||
|
request.onsuccess = () => {
|
||||||
|
if (request.result) results.push(request.result);
|
||||||
|
completed++;
|
||||||
|
if (completed === keys.length) resolve(results);
|
||||||
|
};
|
||||||
|
request.onerror = () => {
|
||||||
|
console.warn(`[SW] Erreur lecture clé ${key}`);
|
||||||
|
completed++;
|
||||||
|
if (completed === keys.length) resolve(results);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
self.removeEventListener('message', messageHandler);
|
|
||||||
reject(new Error('Database request timeout'));
|
|
||||||
}, 10000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,40 +77,57 @@ async function requestFromMainThread(client, action, payload) {
|
|||||||
// SCAN LOGIC
|
// SCAN LOGIC
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
async function scanMissingData(processesToScan, client) {
|
async function scanMissingData(processesToScan) {
|
||||||
console.log('[Service Worker] Scanning for missing data...');
|
console.log("[Service Worker] 🚀 Scanning with DIRECT DB ACCESS...");
|
||||||
|
|
||||||
const myProcesses = await requestFromMainThread(client, 'GET_MULTIPLE_OBJECTS', {
|
let db;
|
||||||
storeName: 'processes',
|
try {
|
||||||
keys: processesToScan
|
db = await openDB();
|
||||||
});
|
} catch (e) {
|
||||||
|
console.error("[SW] Impossible d'ouvrir la BDD:", e);
|
||||||
|
return { toDownload: [], diffsToCreate: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Récupération directe des processus
|
||||||
|
const myProcesses = await getMultipleObjects(
|
||||||
|
db,
|
||||||
|
"processes",
|
||||||
|
processesToScan
|
||||||
|
);
|
||||||
|
|
||||||
let toDownload = new Set();
|
let toDownload = new Set();
|
||||||
let diffsToCreate = [];
|
let diffsToCreate = [];
|
||||||
|
|
||||||
if (myProcesses && myProcesses.length != 0) {
|
if (myProcesses && myProcesses.length !== 0) {
|
||||||
for (const process of myProcesses) {
|
for (const process of myProcesses) {
|
||||||
|
if (!process || !process.states) continue;
|
||||||
|
|
||||||
const firstState = process.states[0];
|
const firstState = process.states[0];
|
||||||
|
// Sécurisation : on vérifie que firstState existe
|
||||||
|
if (!firstState) continue;
|
||||||
|
|
||||||
const processId = firstState.commited_in;
|
const processId = firstState.commited_in;
|
||||||
|
|
||||||
for (const state of process.states) {
|
for (const state of process.states) {
|
||||||
if (state.state_id === EMPTY32BYTES) continue;
|
if (state.state_id === EMPTY32BYTES) continue;
|
||||||
|
|
||||||
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
for (const [field, hash] of Object.entries(state.pcd_commitment)) {
|
||||||
if (state.public_data[field] !== undefined || field === 'roles') continue;
|
// On ignore les données publiques ou les rôles
|
||||||
|
if (
|
||||||
const existingData = await requestFromMainThread(client, 'GET_OBJECT', {
|
(state.public_data && state.public_data[field] !== undefined) ||
|
||||||
storeName: 'data',
|
field === "roles"
|
||||||
key: hash
|
)
|
||||||
});
|
continue;
|
||||||
|
|
||||||
|
// 2. Vérification directe dans 'data'
|
||||||
|
const existingData = await getObject(db, "data", hash);
|
||||||
|
|
||||||
if (!existingData) {
|
if (!existingData) {
|
||||||
toDownload.add(hash);
|
toDownload.add(hash);
|
||||||
|
|
||||||
const existingDiff = await requestFromMainThread(client, 'GET_OBJECT', {
|
// 3. Vérification directe dans 'diffs'
|
||||||
storeName: 'diffs',
|
const existingDiff = await getObject(db, "diffs", hash);
|
||||||
key: hash
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!existingDiff) {
|
if (!existingDiff) {
|
||||||
diffsToCreate.push({
|
diffsToCreate.push({
|
||||||
process_id: processId,
|
process_id: processId,
|
||||||
@ -130,23 +140,64 @@ async function scanMissingData(processesToScan, client) {
|
|||||||
new_value: null,
|
new_value: null,
|
||||||
notify_user: false,
|
notify_user: false,
|
||||||
need_validation: false,
|
need_validation: false,
|
||||||
validation_status: 'None'
|
validation_status: "None",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (toDownload.delete(hash)) {
|
// Si on a trouvé la donnée, on est sûr de ne pas avoir besoin de la télécharger
|
||||||
console.log(`[Service Worker] Removing ${hash} from the set`);
|
if (toDownload.has(hash)) {
|
||||||
|
toDownload.delete(hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[Service Worker] Scan complete:', { toDownload: toDownload.size, diffsToCreate: diffsToCreate.length });
|
// On ferme la connexion BDD pour libérer les ressources
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
console.log("[Service Worker] Scan complete:", {
|
||||||
|
toDownload: toDownload.size,
|
||||||
|
diffsToCreate: diffsToCreate.length,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
toDownload: Array.from(toDownload),
|
toDownload: Array.from(toDownload),
|
||||||
diffsToCreate: diffsToCreate
|
diffsToCreate: diffsToCreate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// MESSAGE HANDLER
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
self.addEventListener("message", async (event) => {
|
||||||
|
const data = event.data;
|
||||||
|
|
||||||
|
if (data.type === "SCAN") {
|
||||||
|
try {
|
||||||
|
const myProcessesId = data.payload;
|
||||||
|
if (myProcessesId && myProcessesId.length !== 0) {
|
||||||
|
// Appel direct de la nouvelle fonction optimisée
|
||||||
|
const scanResult = await scanMissingData(myProcessesId);
|
||||||
|
|
||||||
|
if (scanResult.toDownload.length !== 0) {
|
||||||
|
event.source.postMessage({
|
||||||
|
type: "TO_DOWNLOAD",
|
||||||
|
data: scanResult.toDownload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanResult.diffsToCreate.length > 0) {
|
||||||
|
event.source.postMessage({
|
||||||
|
type: "DIFFS_TO_CREATE",
|
||||||
|
data: scanResult.diffsToCreate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[Service Worker] Scan error:", error);
|
||||||
|
// On évite de spammer l'UI avec des erreurs internes du worker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
// src/pages/process/Home.ts
|
// src/pages/process/Home.ts
|
||||||
import Services from '../../services/service';
|
import Services from "../../services/service";
|
||||||
import globalCss from '../../assets/styles/style.css?inline';
|
import globalCss from "../../assets/styles/style.css?inline";
|
||||||
import homeHtml from './home.html?raw';
|
import homeHtml from "./home.html?raw";
|
||||||
import { displayEmojis, generateCreateBtn, prepareAndSendPairingTx, addressToEmoji } from '../../utils/sp-address.utils';
|
import {
|
||||||
import { Router } from '../../router/index';
|
displayEmojis,
|
||||||
|
generateCreateBtn,
|
||||||
|
prepareAndSendPairingTx,
|
||||||
|
addressToEmoji,
|
||||||
|
} from "../../utils/sp-address.utils";
|
||||||
|
import { Router } from "../../router/index";
|
||||||
|
|
||||||
export class HomePage extends HTMLElement {
|
export class HomePage extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: "open" });
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@ -120,26 +125,36 @@ export class HomePage extends HTMLElement {
|
|||||||
const container = this.shadowRoot;
|
const container = this.shadowRoot;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
(window as any).__PAIRING_READY = false;
|
const loaderDiv = container.querySelector(
|
||||||
const loaderDiv = container.querySelector('#iframe-loader') as HTMLDivElement;
|
"#iframe-loader"
|
||||||
const mainContentDiv = container.querySelector('#main-content') as HTMLDivElement;
|
) as HTMLDivElement;
|
||||||
const tabs = container.querySelectorAll('.tab');
|
const mainContentDiv = container.querySelector(
|
||||||
|
"#main-content"
|
||||||
|
) as HTMLDivElement;
|
||||||
|
const tabs = container.querySelectorAll(".tab");
|
||||||
|
|
||||||
tabs.forEach((tab) => {
|
tabs.forEach((tab) => {
|
||||||
tab.addEventListener('click', () => {
|
tab.addEventListener("click", () => {
|
||||||
// Remplacement de addSubscription pour simplifier ici
|
// Remplacement de addSubscription pour simplifier ici
|
||||||
container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
|
container
|
||||||
tab.classList.add('active');
|
.querySelectorAll(".tab")
|
||||||
container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));
|
.forEach((t) => t.classList.remove("active"));
|
||||||
container.querySelector(`#${tab.getAttribute('data-tab') as string}`)?.classList.add('active');
|
tab.classList.add("active");
|
||||||
|
container
|
||||||
|
.querySelectorAll(".tab-content")
|
||||||
|
.forEach((content) => content.classList.remove("active"));
|
||||||
|
container
|
||||||
|
.querySelector(`#${tab.getAttribute("data-tab") as string}`)
|
||||||
|
?.classList.add("active");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
const delay = (ms: number) =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await delay(500);
|
await delay(500);
|
||||||
this.addLoaderStep('Initialisation des services...');
|
this.addLoaderStep("Initialisation des services...");
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
|
|
||||||
await delay(700);
|
await delay(700);
|
||||||
@ -149,42 +164,51 @@ export class HomePage extends HTMLElement {
|
|||||||
|
|
||||||
if (pairingId) {
|
if (pairingId) {
|
||||||
await delay(300);
|
await delay(300);
|
||||||
this.addLoaderStep('Appairage existant trouvé.');
|
this.addLoaderStep("Appairage existant trouvé.");
|
||||||
service.setProcessId(pairingId);
|
service.setProcessId(pairingId);
|
||||||
} else {
|
} else {
|
||||||
await delay(300);
|
await delay(300);
|
||||||
this.addLoaderStep("Création d'un appairage sécurisé...");
|
this.addLoaderStep("Création d'un appairage sécurisé...");
|
||||||
await prepareAndSendPairingTx();
|
await prepareAndSendPairingTx();
|
||||||
this.addLoaderStep('Appairage créé avec succès.');
|
this.addLoaderStep("Appairage créé avec succès.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- SUCCÈS ---
|
// --- SUCCÈS ---
|
||||||
(window as any).__PAIRING_READY = true;
|
console.log("[Home] Auto-pairing terminé avec succès.");
|
||||||
console.log('[Home] Auto-pairing terminé avec succès.');
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("app:pairing-ready", {
|
||||||
|
detail: { success: true },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (window.self !== window.top) {
|
if (window.self !== window.top) {
|
||||||
// CAS IFRAME : On ne bouge pas !
|
// CAS IFRAME : On ne bouge pas !
|
||||||
// On affiche juste un état "Prêt" dans le loader pour le debug visuel
|
// On affiche juste un état "Prêt" dans le loader pour le debug visuel
|
||||||
this.addLoaderStep("Prêt. En attente de l'application parente...");
|
this.addLoaderStep("Prêt. En attente de l'application parente...");
|
||||||
console.log('[Home] 📡 Mode Iframe : Pas de redirection. Attente des messages API.');
|
console.log(
|
||||||
|
"[Home] 📡 Mode Iframe : Pas de redirection. Attente des messages API."
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// CAS STANDALONE : On redirige
|
// CAS STANDALONE : On redirige
|
||||||
console.log('[Home] 🚀 Mode Standalone : Redirection vers /process...');
|
console.log("[Home] 🚀 Mode Standalone : Redirection vers /process...");
|
||||||
await delay(500);
|
await delay(500);
|
||||||
|
|
||||||
// On nettoie l'UI avant de partir
|
// On nettoie l'UI avant de partir
|
||||||
if (loaderDiv) loaderDiv.style.display = 'none';
|
if (loaderDiv) loaderDiv.style.display = "none";
|
||||||
if (mainContentDiv) mainContentDiv.style.display = 'block';
|
if (mainContentDiv) mainContentDiv.style.display = "block";
|
||||||
|
|
||||||
// Hop, on navigue
|
// Hop, on navigue
|
||||||
Router.navigate('process');
|
Router.navigate("process");
|
||||||
}
|
}
|
||||||
|
|
||||||
container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
|
container
|
||||||
container.querySelector('[data-tab="tab2"]')?.classList.add('active');
|
.querySelectorAll(".tab")
|
||||||
container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));
|
.forEach((t) => t.classList.remove("active"));
|
||||||
container.querySelector('#tab2')?.classList.add('active');
|
container.querySelector('[data-tab="tab2"]')?.classList.add("active");
|
||||||
|
container
|
||||||
|
.querySelectorAll(".tab-content")
|
||||||
|
.forEach((content) => content.classList.remove("active"));
|
||||||
|
container.querySelector("#tab2")?.classList.add("active");
|
||||||
|
|
||||||
const spAddress = await service.getDeviceAddress();
|
const spAddress = await service.getDeviceAddress();
|
||||||
generateCreateBtn();
|
generateCreateBtn();
|
||||||
@ -193,28 +217,35 @@ export class HomePage extends HTMLElement {
|
|||||||
|
|
||||||
await delay(1000);
|
await delay(1000);
|
||||||
|
|
||||||
if (loaderDiv) loaderDiv.style.display = 'none';
|
if (loaderDiv) loaderDiv.style.display = "none";
|
||||||
if (mainContentDiv) mainContentDiv.style.display = 'block';
|
if (mainContentDiv) mainContentDiv.style.display = "block";
|
||||||
|
|
||||||
console.log('[Home] Init terminée.');
|
console.log("[Home] Init terminée.");
|
||||||
(window as any).__PAIRING_READY = true;
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('[Home] Erreur:', e);
|
console.error("[Home] Erreur:", e);
|
||||||
this.addLoaderStep(`Erreur: ${e.message}`);
|
this.addLoaderStep(`Erreur: ${e.message}`);
|
||||||
(window as any).__PAIRING_READY = 'error';
|
document.dispatchEvent(
|
||||||
|
new CustomEvent("app:pairing-ready", {
|
||||||
|
detail: { success: false, error: e.message },
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addLoaderStep(text: string) {
|
addLoaderStep(text: string) {
|
||||||
const container = this.shadowRoot;
|
const container = this.shadowRoot;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const currentStep = container.querySelector('.loader-step.active') as HTMLParagraphElement;
|
const currentStep = container.querySelector(
|
||||||
if (currentStep) currentStep.classList.remove('active');
|
".loader-step.active"
|
||||||
|
) as HTMLParagraphElement;
|
||||||
|
if (currentStep) currentStep.classList.remove("active");
|
||||||
|
|
||||||
const stepsContainer = container.querySelector('#loader-steps-container') as HTMLDivElement;
|
const stepsContainer = container.querySelector(
|
||||||
|
"#loader-steps-container"
|
||||||
|
) as HTMLDivElement;
|
||||||
if (stepsContainer) {
|
if (stepsContainer) {
|
||||||
const newStep = document.createElement('p');
|
const newStep = document.createElement("p");
|
||||||
newStep.className = 'loader-step active';
|
newStep.className = "loader-step active";
|
||||||
newStep.textContent = text;
|
newStep.textContent = text;
|
||||||
stepsContainer.appendChild(newStep);
|
stepsContainer.appendChild(newStep);
|
||||||
}
|
}
|
||||||
@ -223,7 +254,9 @@ export class HomePage extends HTMLElement {
|
|||||||
async populateMemberSelect() {
|
async populateMemberSelect() {
|
||||||
const container = this.shadowRoot;
|
const container = this.shadowRoot;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement;
|
const memberSelect = container.querySelector(
|
||||||
|
"#memberSelect"
|
||||||
|
) as HTMLSelectElement;
|
||||||
if (!memberSelect) return;
|
if (!memberSelect) return;
|
||||||
|
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
@ -231,7 +264,7 @@ export class HomePage extends HTMLElement {
|
|||||||
|
|
||||||
for (const [processId, member] of Object.entries(members)) {
|
for (const [processId, member] of Object.entries(members)) {
|
||||||
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 = `Member (${emojis})`;
|
option.textContent = `Member (${emojis})`;
|
||||||
memberSelect.appendChild(option);
|
memberSelect.appendChild(option);
|
||||||
@ -239,5 +272,4 @@ export class HomePage extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('home-page', HomePage);
|
customElements.define("home-page", HomePage);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import processHtml from './process.html?raw';
|
// src/pages/process/ProcessList.ts
|
||||||
import globalCss from '../../assets/styles/style.css?inline';
|
|
||||||
import Services from '../../services/service';
|
import processHtml from "./process.html?raw";
|
||||||
import { Router } from '../../router';
|
import globalCss from "../../assets/styles/style.css?inline";
|
||||||
|
import Services from "../../services/service";
|
||||||
|
|
||||||
export class ProcessListPage extends HTMLElement {
|
export class ProcessListPage extends HTMLElement {
|
||||||
private services!: Services;
|
private services!: Services;
|
||||||
@ -16,187 +17,54 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: "open" });
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectedCallback() {
|
async connectedCallback() {
|
||||||
this.services = await Services.getInstance();
|
this.services = await Services.getInstance();
|
||||||
this.render();
|
this.render();
|
||||||
// Petit délai pour s'assurer que le DOM est prêt
|
|
||||||
setTimeout(() => this.initLogic(), 0);
|
setTimeout(() => this.initLogic(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.shadowRoot) {
|
if (this.shadowRoot) {
|
||||||
|
// Le CSS et le HTML de base sont statiques, donc innerHTML est OK ici.
|
||||||
this.shadowRoot.innerHTML = `
|
this.shadowRoot.innerHTML = `
|
||||||
<style>
|
<style>
|
||||||
${globalCss}
|
${globalCss}
|
||||||
|
/* ... (styles identiques à ton fichier d'origine, je ne les répète pas pour abréger) ... */
|
||||||
:host {
|
:host { display: block; width: 100%; }
|
||||||
display: block;
|
.process-layout { padding: 2rem; display: flex; justify-content: center; }
|
||||||
width: 100%;
|
.dashboard-container { width: 100%; max-width: 800px; display: flex; flex-direction: column; gap: 1.5rem; max-height: 85vh; overflow-y: auto; }
|
||||||
}
|
|
||||||
|
|
||||||
.process-layout {
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
max-height: 85vh; /* Pour scroller dedans si besoin */
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-header { text-align: center; }
|
.dashboard-header { text-align: center; }
|
||||||
.subtitle { color: var(--text-muted); margin-top: -0.5rem; }
|
.subtitle { color: var(--text-muted); margin-top: -0.5rem; }
|
||||||
|
.search-input-container { position: relative; display: flex; align-items: center; }
|
||||||
/* Search Styles */
|
.search-input-container input { padding-right: 40px; background: rgba(255,255,255,0.05); border: 1px solid var(--glass-border); transition: all 0.3s; }
|
||||||
.search-input-container {
|
.search-input-container input:focus { background: rgba(255,255,255,0.1); border-color: var(--primary); }
|
||||||
position: relative;
|
.search-icon { position: absolute; right: 12px; opacity: 0.5; }
|
||||||
display: flex;
|
.autocomplete-dropdown { list-style: none; margin-top: 5px; padding: 0; background: #1e293b; border: 1px solid var(--glass-border); border-radius: var(--radius-sm); max-height: 200px; overflow-y: auto; display: none; position: absolute; width: 100%; z-index: 10; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-input-container input {
|
|
||||||
padding-right: 40px; /* Place pour l'icone */
|
|
||||||
background: rgba(255,255,255,0.05);
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
.search-input-container input:focus {
|
|
||||||
background: rgba(255,255,255,0.1);
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-icon {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Autocomplete List */
|
|
||||||
.autocomplete-dropdown {
|
|
||||||
list-style: none;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding: 0;
|
|
||||||
background: #1e293b; /* Fond opaque pour lisibilité */
|
|
||||||
border: 1px solid var(--glass-border);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: none; /* Caché par défaut */
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Position relative pour que le dropdown s'aligne */
|
|
||||||
.custom-select-wrapper { position: relative; }
|
.custom-select-wrapper { position: relative; }
|
||||||
|
.autocomplete-dropdown li { padding: 10px 15px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.05); transition: background 0.2s; color: var(--text-main); }
|
||||||
.autocomplete-dropdown li {
|
.autocomplete-dropdown li:hover { background: var(--primary); color: white; }
|
||||||
padding: 10px 15px;
|
.autocomplete-dropdown li.my-process { border-left: 3px solid var(--accent); }
|
||||||
cursor: pointer;
|
.tags-container { display: flex; flex-wrap: wrap; gap: 8px; min-height: 30px; }
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
.tag { background: rgba(var(--primary-hue), 50, 50, 0.3); border: 1px solid var(--primary); color: white; padding: 4px 10px; border-radius: 20px; font-size: 0.85rem; display: flex; align-items: center; gap: 8px; animation: popIn 0.2s ease-out; }
|
||||||
transition: background 0.2s;
|
.tag-close { cursor: pointer; opacity: 0.7; font-weight: bold; }
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
.autocomplete-dropdown li:hover {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.autocomplete-dropdown li.my-process {
|
|
||||||
border-left: 3px solid var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tags */
|
|
||||||
.tags-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
background: rgba(var(--primary-hue), 50, 50, 0.3);
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
color: white;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
animation: popIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
.tag-close {
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.7;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.tag-close:hover { opacity: 1; }
|
.tag-close:hover { opacity: 1; }
|
||||||
|
|
||||||
@keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
@keyframes popIn { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
||||||
|
|
||||||
.divider { height: 1px; background: var(--glass-border); margin: 0.5rem 0; }
|
.divider { height: 1px; background: var(--glass-border); margin: 0.5rem 0; }
|
||||||
|
.details-content { background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); padding: 1rem; min-height: 100px; }
|
||||||
/* Process Details */
|
|
||||||
.details-content {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 1rem;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state { color: var(--text-muted); font-style: italic; text-align: center; padding: 2rem; }
|
.empty-state { color: var(--text-muted); font-style: italic; text-align: center; padding: 2rem; }
|
||||||
|
.process-item { margin-bottom: 1rem; border-bottom: 1px solid var(--glass-border); padding-bottom: 1rem; }
|
||||||
.process-item {
|
.process-title-display { font-size: 1.1rem; font-weight: bold; color: var(--accent); margin-bottom: 0.5rem; }
|
||||||
margin-bottom: 1rem;
|
.state-element { background: rgba(255,255,255,0.05); padding: 8px 12px; margin-top: 5px; border-radius: 4px; cursor: pointer; transition: background 0.2s; border: 1px solid transparent; font-family: monospace; font-size: 0.9rem; }
|
||||||
border-bottom: 1px solid var(--glass-border);
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.process-title-display {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--accent);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.state-element {
|
|
||||||
background: rgba(255,255,255,0.05);
|
|
||||||
padding: 8px 12px;
|
|
||||||
margin-top: 5px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.state-element:hover { background: rgba(255,255,255,0.1); }
|
.state-element:hover { background: rgba(255,255,255,0.1); }
|
||||||
|
.state-element.selected { background: rgba(var(--success), 0.2); border-color: var(--success); }
|
||||||
.state-element.selected {
|
.dashboard-footer { display: flex; justify-content: flex-end; }
|
||||||
background: rgba(var(--success), 0.2);
|
|
||||||
border-color: var(--success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbar custom */
|
|
||||||
::-webkit-scrollbar { width: 6px; }
|
::-webkit-scrollbar { width: 6px; }
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
::-webkit-scrollbar-track { background: transparent; }
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 3px; }
|
||||||
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); }
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); }
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
${processHtml}
|
${processHtml}
|
||||||
`;
|
`;
|
||||||
@ -207,47 +75,50 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
const root = this.shadowRoot;
|
const root = this.shadowRoot;
|
||||||
if (!root) return;
|
if (!root) return;
|
||||||
|
|
||||||
// Récupération des éléments
|
this.wrapper = root.querySelector("#autocomplete-wrapper") as HTMLElement;
|
||||||
this.wrapper = root.querySelector('#autocomplete-wrapper') as HTMLElement;
|
this.inputInput = root.querySelector("#process-input") as HTMLInputElement;
|
||||||
this.inputInput = root.querySelector('#process-input') as HTMLInputElement;
|
this.autocompleteList = root.querySelector(
|
||||||
this.autocompleteList = root.querySelector('#autocomplete-list') as HTMLUListElement;
|
"#autocomplete-list"
|
||||||
this.tagsContainer = root.querySelector('#selected-tags-container') as HTMLElement;
|
) as HTMLUListElement;
|
||||||
this.detailsContainer = root.querySelector('#process-details') as HTMLElement;
|
this.tagsContainer = root.querySelector(
|
||||||
this.okButton = root.querySelector('#go-to-process-btn') as HTMLButtonElement;
|
"#selected-tags-container"
|
||||||
|
) as HTMLElement;
|
||||||
|
this.detailsContainer = root.querySelector(
|
||||||
|
"#process-details"
|
||||||
|
) as HTMLElement;
|
||||||
|
this.okButton = root.querySelector(
|
||||||
|
"#go-to-process-btn"
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
// Listeners
|
this.inputInput.addEventListener("keyup", () => this.handleInput());
|
||||||
this.inputInput.addEventListener('keyup', () => this.handleInput());
|
this.inputInput.addEventListener("click", () => this.openDropdown());
|
||||||
this.inputInput.addEventListener('click', () => this.openDropdown());
|
|
||||||
|
|
||||||
// Fermeture du dropdown au clic extérieur
|
document.addEventListener("click", (e) => {
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
const path = e.composedPath();
|
const path = e.composedPath();
|
||||||
if (!path.includes(this.wrapper)) {
|
if (!path.includes(this.wrapper)) {
|
||||||
this.closeDropdown();
|
this.closeDropdown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.okButton.addEventListener('click', () => this.goToProcess());
|
this.okButton.addEventListener("click", () => this.goToProcess());
|
||||||
|
|
||||||
// Écoute des mises à jour du service
|
document.addEventListener("processes-updated", async () => {
|
||||||
document.addEventListener('processes-updated', async () => {
|
|
||||||
await this.populateList(this.inputInput.value);
|
await this.populateList(this.inputInput.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Chargement initial
|
await this.populateList("");
|
||||||
await this.populateList('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Logique Autocomplete ---
|
// --- Logique Autocomplete Sécurisée ---
|
||||||
|
|
||||||
async populateList(query: string) {
|
async populateList(query: string) {
|
||||||
this.autocompleteList.innerHTML = '';
|
this.autocompleteList.innerHTML = ""; // Vide la liste proprement
|
||||||
|
|
||||||
const mineArray = (await this.services.getMyProcesses()) ?? [];
|
const mineArray = (await this.services.getMyProcesses()) ?? [];
|
||||||
const allProcesses = await this.services.getProcesses();
|
const allProcesses = await this.services.getProcesses();
|
||||||
|
const otherProcesses = Object.keys(allProcesses).filter(
|
||||||
// On combine tout, en mettant les miens d'abord
|
(id) => !mineArray.includes(id)
|
||||||
const otherProcesses = Object.keys(allProcesses).filter((id) => !mineArray.includes(id));
|
);
|
||||||
const listToShow = [...mineArray, ...otherProcesses];
|
const listToShow = [...mineArray, ...otherProcesses];
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@ -258,22 +129,33 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
|
|
||||||
const name = this.services.getProcessName(process) || pid;
|
const name = this.services.getProcessName(process) || pid;
|
||||||
|
|
||||||
// Filtre
|
if (
|
||||||
if (query && !name.toLowerCase().includes(query.toLowerCase()) && !pid.includes(query)) {
|
query &&
|
||||||
|
!name.toLowerCase().includes(query.toLowerCase()) &&
|
||||||
|
!pid.includes(query)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
const li = document.createElement('li');
|
const li = document.createElement("li");
|
||||||
li.textContent = name;
|
|
||||||
|
const nameSpan = document.createElement("span");
|
||||||
|
nameSpan.textContent = name;
|
||||||
|
li.appendChild(nameSpan);
|
||||||
|
|
||||||
if (mineArray.includes(pid)) {
|
if (mineArray.includes(pid)) {
|
||||||
li.classList.add('my-process');
|
li.classList.add("my-process");
|
||||||
li.innerHTML += ' <small style="opacity:0.6">(Mien)</small>';
|
const small = document.createElement("small");
|
||||||
|
small.style.opacity = "0.6";
|
||||||
|
small.style.marginLeft = "8px";
|
||||||
|
small.textContent = "(Mien)"; // Texte statique sûr
|
||||||
|
li.appendChild(small);
|
||||||
}
|
}
|
||||||
|
|
||||||
li.addEventListener('click', () => {
|
li.addEventListener("click", () => {
|
||||||
this.addTag(pid, name);
|
this.addTag(pid, name);
|
||||||
this.inputInput.value = '';
|
this.inputInput.value = "";
|
||||||
this.showProcessDetails(pid);
|
this.showProcessDetails(pid);
|
||||||
this.closeDropdown();
|
this.closeDropdown();
|
||||||
});
|
});
|
||||||
@ -282,10 +164,10 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count === 0) {
|
if (count === 0) {
|
||||||
const empty = document.createElement('li');
|
const empty = document.createElement("li");
|
||||||
empty.textContent = 'Aucun résultat';
|
empty.textContent = "Aucun résultat";
|
||||||
empty.style.cursor = 'default';
|
empty.style.cursor = "default";
|
||||||
empty.style.opacity = '0.5';
|
empty.style.opacity = "0.5";
|
||||||
this.autocompleteList.appendChild(empty);
|
this.autocompleteList.appendChild(empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,84 +178,121 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openDropdown() {
|
openDropdown() {
|
||||||
this.autocompleteList.style.display = 'block';
|
this.autocompleteList.style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
closeDropdown() {
|
closeDropdown() {
|
||||||
this.autocompleteList.style.display = 'none';
|
this.autocompleteList.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Gestion des Tags ---
|
// --- Gestion des Tags Sécurisée ---
|
||||||
|
|
||||||
addTag(pid: string, name: string) {
|
addTag(pid: string, name: string) {
|
||||||
// On nettoie les anciens tags (mode single select pour l'instant, ou multi si tu veux)
|
this.tagsContainer.innerHTML = "";
|
||||||
this.tagsContainer.innerHTML = '';
|
|
||||||
|
|
||||||
const tag = document.createElement('div');
|
const tag = document.createElement("div");
|
||||||
tag.className = 'tag';
|
tag.className = "tag";
|
||||||
tag.innerHTML = `
|
|
||||||
<span>${name}</span>
|
|
||||||
<span class="tag-close">×</span>
|
|
||||||
`;
|
|
||||||
|
|
||||||
tag.querySelector('.tag-close')?.addEventListener('click', (e) => {
|
const spanName = document.createElement("span");
|
||||||
|
spanName.textContent = name;
|
||||||
|
tag.appendChild(spanName);
|
||||||
|
|
||||||
|
const closeBtn = document.createElement("span");
|
||||||
|
closeBtn.className = "tag-close";
|
||||||
|
closeBtn.innerHTML = "×";
|
||||||
|
|
||||||
|
closeBtn.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.removeTag();
|
this.removeTag();
|
||||||
});
|
});
|
||||||
|
tag.appendChild(closeBtn);
|
||||||
|
|
||||||
this.tagsContainer.appendChild(tag);
|
this.tagsContainer.appendChild(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTag() {
|
removeTag() {
|
||||||
this.tagsContainer.innerHTML = '';
|
this.tagsContainer.innerHTML = "";
|
||||||
this.detailsContainer.innerHTML = '<div class="empty-state"><p>Aucun processus sélectionné.</p></div>';
|
|
||||||
|
this.detailsContainer.innerHTML = "";
|
||||||
|
const emptyState = document.createElement("div");
|
||||||
|
emptyState.className = "empty-state";
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.textContent = "Aucun processus sélectionné.";
|
||||||
|
emptyState.appendChild(p);
|
||||||
|
this.detailsContainer.appendChild(emptyState);
|
||||||
|
|
||||||
this.okButton.disabled = true;
|
this.okButton.disabled = true;
|
||||||
this.okButton.classList.add('disabled');
|
this.okButton.classList.add("disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Détails du processus ---
|
// --- Détails du processus Sécurisés ---
|
||||||
|
|
||||||
async showProcessDetails(pid: string) {
|
async showProcessDetails(pid: string) {
|
||||||
this.detailsContainer.innerHTML = '<p style="text-align:center">Chargement...</p>';
|
this.detailsContainer.textContent = "Chargement..."; // Safe loader
|
||||||
|
|
||||||
const process = await this.services.getProcess(pid);
|
const process = await this.services.getProcess(pid);
|
||||||
if (!process) return;
|
if (!process) return;
|
||||||
|
|
||||||
this.detailsContainer.innerHTML = ''; // Clear
|
this.detailsContainer.innerHTML = "";
|
||||||
|
|
||||||
const name = this.services.getProcessName(process) || 'Sans nom';
|
const name = this.services.getProcessName(process) || "Sans nom";
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
let description = 'Pas de description';
|
let description = "Pas de description";
|
||||||
const lastState = this.services.getLastCommitedState(process);
|
const lastState = this.services.getLastCommitedState(process);
|
||||||
if (lastState?.pcd_commitment['description']) {
|
if (lastState?.pcd_commitment["description"]) {
|
||||||
const diff = await this.services.getDiffByValue(lastState.pcd_commitment['description']);
|
const diff = await this.services.getDiffByValue(
|
||||||
|
lastState.pcd_commitment["description"]
|
||||||
|
);
|
||||||
if (diff) description = diff.value_commitment;
|
if (diff) description = diff.value_commitment;
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerDiv = document.createElement('div');
|
const containerDiv = document.createElement("div");
|
||||||
containerDiv.className = 'process-item';
|
containerDiv.className = "process-item";
|
||||||
containerDiv.innerHTML = `
|
|
||||||
<div class="process-title-display">${name}</div>
|
// Titre
|
||||||
<div style="font-size:0.9rem; margin-bottom:10px">${description}</div>
|
const titleDiv = document.createElement("div");
|
||||||
<div style="font-size:0.8rem; opacity:0.7; margin-bottom:10px">ID: ${pid}</div>
|
titleDiv.className = "process-title-display";
|
||||||
<div style="font-weight:bold; margin-top:15px">États en attente :</div>
|
titleDiv.textContent = name; // Safe
|
||||||
`;
|
containerDiv.appendChild(titleDiv);
|
||||||
|
|
||||||
|
// Description
|
||||||
|
const descDiv = document.createElement("div");
|
||||||
|
descDiv.style.fontSize = "0.9rem";
|
||||||
|
descDiv.style.marginBottom = "10px";
|
||||||
|
descDiv.textContent = description; // Safe
|
||||||
|
containerDiv.appendChild(descDiv);
|
||||||
|
|
||||||
|
// ID
|
||||||
|
const idDiv = document.createElement("div");
|
||||||
|
idDiv.style.fontSize = "0.8rem";
|
||||||
|
idDiv.style.opacity = "0.7";
|
||||||
|
idDiv.style.marginBottom = "10px";
|
||||||
|
idDiv.textContent = `ID: ${pid}`; // Safe
|
||||||
|
containerDiv.appendChild(idDiv);
|
||||||
|
|
||||||
|
// Label "États en attente"
|
||||||
|
const labelDiv = document.createElement("div");
|
||||||
|
labelDiv.style.fontWeight = "bold";
|
||||||
|
labelDiv.style.marginTop = "15px";
|
||||||
|
labelDiv.textContent = "États en attente :";
|
||||||
|
containerDiv.appendChild(labelDiv);
|
||||||
|
|
||||||
const uncommitted = this.services.getUncommitedStates(process);
|
const uncommitted = this.services.getUncommitedStates(process);
|
||||||
|
|
||||||
if (uncommitted.length > 0) {
|
if (uncommitted.length > 0) {
|
||||||
uncommitted.forEach((state) => {
|
uncommitted.forEach((state) => {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement("div");
|
||||||
el.className = 'state-element';
|
el.className = "state-element";
|
||||||
|
// textContent ici aussi, même si state_id est technique
|
||||||
el.textContent = `État: ${state.state_id.substring(0, 16)}...`;
|
el.textContent = `État: ${state.state_id.substring(0, 16)}...`;
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener("click", () => {
|
||||||
// Gestion de la sélection
|
this.shadowRoot
|
||||||
this.shadowRoot?.querySelectorAll('.state-element').forEach((x) => x.classList.remove('selected'));
|
?.querySelectorAll(".state-element")
|
||||||
el.classList.add('selected');
|
.forEach((x) => x.classList.remove("selected"));
|
||||||
|
el.classList.add("selected");
|
||||||
|
|
||||||
// Activation du bouton
|
|
||||||
this.okButton.disabled = false;
|
this.okButton.disabled = false;
|
||||||
this.okButton.dataset.target = `${pid}/${state.state_id}`;
|
this.okButton.dataset.target = `${pid}/${state.state_id}`;
|
||||||
});
|
});
|
||||||
@ -381,10 +300,10 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
containerDiv.appendChild(el);
|
containerDiv.appendChild(el);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const empty = document.createElement('div');
|
const empty = document.createElement("div");
|
||||||
empty.style.padding = '10px';
|
empty.style.padding = "10px";
|
||||||
empty.style.opacity = '0.6';
|
empty.style.opacity = "0.6";
|
||||||
empty.textContent = 'Aucun état en attente de validation.';
|
empty.textContent = "Aucun état en attente de validation.";
|
||||||
containerDiv.appendChild(empty);
|
containerDiv.appendChild(empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,11 +313,10 @@ export class ProcessListPage extends HTMLElement {
|
|||||||
goToProcess() {
|
goToProcess() {
|
||||||
const target = this.okButton.dataset.target;
|
const target = this.okButton.dataset.target;
|
||||||
if (target) {
|
if (target) {
|
||||||
console.log('Navigation vers', target);
|
console.log("Navigation vers", target);
|
||||||
alert('Navigation vers la page de détail du processus (à implémenter): ' + target);
|
alert("Navigation vers : " + target);
|
||||||
// Router.navigate(`process-detail/${target}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('process-list-page', ProcessListPage);
|
customElements.define("process-list-page", ProcessListPage);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import Services from './service';
|
import Services from "./service";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database service managing IndexedDB operations via Web Worker and Service Worker
|
* Database service managing IndexedDB operations via Web Worker and Service Worker
|
||||||
@ -7,13 +7,16 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
// PRIVATE PROPERTIES
|
// PRIVATE PROPERTIES
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
private static instance: Database;
|
private static instance: Database;
|
||||||
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
|
private serviceWorkerRegistration: ServiceWorkerRegistration | null = null;
|
||||||
private serviceWorkerCheckIntervalId: number | null = null;
|
private serviceWorkerCheckIntervalId: number | null = null;
|
||||||
private indexedDBWorker: Worker | null = null;
|
private indexedDBWorker: Worker | null = null;
|
||||||
private messageIdCounter: number = 0;
|
private messageIdCounter: number = 0;
|
||||||
private pendingMessages: Map<number, { resolve: (value: any) => void; reject: (error: any) => void }> = new Map();
|
private pendingMessages: Map<
|
||||||
|
number,
|
||||||
|
{ resolve: (value: any) => void; reject: (error: any) => void }
|
||||||
|
> = new Map();
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// INITIALIZATION & SINGLETON
|
// INITIALIZATION & SINGLETON
|
||||||
@ -37,36 +40,39 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
private initIndexedDBWorker(): void {
|
private initIndexedDBWorker(): void {
|
||||||
this.indexedDBWorker = new Worker(new URL('../workers/database.worker.ts', import.meta.url), { type: 'module' });
|
this.indexedDBWorker = new Worker(
|
||||||
|
new URL("../workers/database.worker.ts", import.meta.url),
|
||||||
|
{ type: "module" }
|
||||||
|
);
|
||||||
|
|
||||||
this.indexedDBWorker.onmessage = (event) => {
|
this.indexedDBWorker.onmessage = (event) => {
|
||||||
const { id, type, result, error } = event.data;
|
const { id, type, result, error } = event.data;
|
||||||
const pending = this.pendingMessages.get(id);
|
const pending = this.pendingMessages.get(id);
|
||||||
|
|
||||||
if (pending) {
|
if (pending) {
|
||||||
this.pendingMessages.delete(id);
|
this.pendingMessages.delete(id);
|
||||||
|
|
||||||
if (type === 'SUCCESS') {
|
if (type === "SUCCESS") {
|
||||||
pending.resolve(result);
|
pending.resolve(result);
|
||||||
} else if (type === 'ERROR') {
|
} else if (type === "ERROR") {
|
||||||
pending.reject(new Error(error));
|
pending.reject(new Error(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.indexedDBWorker.onerror = (error) => {
|
this.indexedDBWorker.onerror = (error) => {
|
||||||
console.error('[Database] IndexedDB Worker error:', error);
|
console.error("[Database] IndexedDB Worker error:", error);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForWorkerReady(): Promise<void> {
|
private async waitForWorkerReady(): Promise<void> {
|
||||||
return this.sendMessageToWorker('INIT', {});
|
return this.sendMessageToWorker("INIT", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendMessageToWorker<T = any>(type: string, payload: any): Promise<T> {
|
private sendMessageToWorker<T = any>(type: string, payload: any): Promise<T> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.indexedDBWorker) {
|
if (!this.indexedDBWorker) {
|
||||||
reject(new Error('IndexedDB Worker not initialized'));
|
reject(new Error("IndexedDB Worker not initialized"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,74 +96,93 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
private initServiceWorker(): void {
|
private initServiceWorker(): void {
|
||||||
this.registerServiceWorker('/data.worker.js');
|
this.registerServiceWorker("/data.worker.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async registerServiceWorker(path: string): Promise<void> {
|
private async registerServiceWorker(path: string): Promise<void> {
|
||||||
if (!('serviceWorker' in navigator)) return;
|
if (!("serviceWorker" in navigator)) return;
|
||||||
console.log('[Database] Initializing Service Worker:', path);
|
console.log("[Database] Initializing Service Worker:", path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||||
|
|
||||||
for (const registration of registrations) {
|
for (const registration of registrations) {
|
||||||
const scriptURL = registration.active?.scriptURL || registration.installing?.scriptURL || registration.waiting?.scriptURL;
|
const scriptURL =
|
||||||
|
registration.active?.scriptURL ||
|
||||||
|
registration.installing?.scriptURL ||
|
||||||
|
registration.waiting?.scriptURL;
|
||||||
const scope = registration.scope;
|
const scope = registration.scope;
|
||||||
|
|
||||||
if (scope.includes('/src/service-workers/') || (scriptURL && scriptURL.includes('/src/service-workers/'))) {
|
if (
|
||||||
|
scope.includes("/src/service-workers/") ||
|
||||||
|
(scriptURL && scriptURL.includes("/src/service-workers/"))
|
||||||
|
) {
|
||||||
console.warn(`[Database] Removing old Service Worker (${scope})`);
|
console.warn(`[Database] Removing old Service Worker (${scope})`);
|
||||||
await registration.unregister();
|
await registration.unregister();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingValidWorker = registrations.find((r) => {
|
const existingValidWorker = registrations.find((r) => {
|
||||||
const url = r.active?.scriptURL || r.installing?.scriptURL || r.waiting?.scriptURL;
|
const url =
|
||||||
return url && url.endsWith(path.replace(/^\//,''));
|
r.active?.scriptURL ||
|
||||||
|
r.installing?.scriptURL ||
|
||||||
|
r.waiting?.scriptURL;
|
||||||
|
return url && url.endsWith(path.replace(/^\//, ""));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingValidWorker) {
|
if (!existingValidWorker) {
|
||||||
console.log('[Database] Registering new Service Worker');
|
console.log("[Database] Registering new Service Worker");
|
||||||
this.serviceWorkerRegistration = await navigator.serviceWorker.register(path, { type: 'module', scope: '/' });
|
this.serviceWorkerRegistration = await navigator.serviceWorker.register(
|
||||||
|
path,
|
||||||
|
{ type: "module", scope: "/" }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('[Database] Service Worker already active');
|
console.log("[Database] Service Worker already active");
|
||||||
this.serviceWorkerRegistration = existingValidWorker;
|
this.serviceWorkerRegistration = existingValidWorker;
|
||||||
await this.serviceWorkerRegistration.update();
|
await this.serviceWorkerRegistration.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.serviceWorker.addEventListener('message', async (event) => {
|
navigator.serviceWorker.addEventListener("message", async (event) => {
|
||||||
if (event.data.type === 'DB_REQUEST') {
|
// ✅ SIMPLIFICATION : Plus besoin de gérer les "DB_REQUEST"
|
||||||
await this.handleDatabaseRequest(event.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.handleServiceWorkerMessage(event.data);
|
await this.handleServiceWorkerMessage(event.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.serviceWorkerCheckIntervalId) clearInterval(this.serviceWorkerCheckIntervalId);
|
if (this.serviceWorkerCheckIntervalId)
|
||||||
|
clearInterval(this.serviceWorkerCheckIntervalId);
|
||||||
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
|
this.serviceWorkerCheckIntervalId = window.setInterval(async () => {
|
||||||
const activeWorker = this.serviceWorkerRegistration?.active || (await this.waitForServiceWorkerActivation(this.serviceWorkerRegistration!));
|
const activeWorker =
|
||||||
|
this.serviceWorkerRegistration?.active ||
|
||||||
|
(await this.waitForServiceWorkerActivation(
|
||||||
|
this.serviceWorkerRegistration!
|
||||||
|
));
|
||||||
const service = await Services.getInstance();
|
const service = await Services.getInstance();
|
||||||
const payload = await service.getMyProcesses();
|
const payload = await service.getMyProcesses();
|
||||||
if (payload && payload.length != 0) {
|
if (payload && payload.length != 0) {
|
||||||
activeWorker?.postMessage({ type: 'SCAN', payload });
|
activeWorker?.postMessage({ type: "SCAN", payload });
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Database] Service Worker error:', error);
|
console.error("[Database] Service Worker error:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForServiceWorkerActivation(registration: ServiceWorkerRegistration): Promise<ServiceWorker | null> {
|
private async waitForServiceWorkerActivation(
|
||||||
|
registration: ServiceWorkerRegistration
|
||||||
|
): Promise<ServiceWorker | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
if (registration.active) {
|
if (registration.active) {
|
||||||
resolve(registration.active);
|
resolve(registration.active);
|
||||||
} else {
|
} else {
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
if (registration.active) {
|
if (registration.active) {
|
||||||
navigator.serviceWorker.removeEventListener('controllerchange', listener);
|
navigator.serviceWorker.removeEventListener(
|
||||||
|
"controllerchange",
|
||||||
|
listener
|
||||||
|
);
|
||||||
resolve(registration.active);
|
resolve(registration.active);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
navigator.serviceWorker.addEventListener('controllerchange', listener);
|
navigator.serviceWorker.addEventListener("controllerchange", listener);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -168,10 +193,12 @@ export class Database {
|
|||||||
await this.serviceWorkerRegistration.update();
|
await this.serviceWorkerRegistration.update();
|
||||||
|
|
||||||
if (this.serviceWorkerRegistration.waiting) {
|
if (this.serviceWorkerRegistration.waiting) {
|
||||||
this.serviceWorkerRegistration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
this.serviceWorkerRegistration.waiting.postMessage({
|
||||||
|
type: "SKIP_WAITING",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking for service worker updates:', error);
|
console.error("Error checking for service worker updates:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,73 +206,34 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
// SERVICE WORKER MESSAGE HANDLERS
|
// SERVICE WORKER MESSAGE HANDLERS
|
||||||
// ============================================
|
// ============================================
|
||||||
private async handleDatabaseRequest(request: any): Promise<void> {
|
|
||||||
const { id, action, payload } = request;
|
// ✅ NETTOYAGE : handleDatabaseRequest() a été supprimé
|
||||||
|
|
||||||
try {
|
|
||||||
let result;
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'GET_OBJECT':
|
|
||||||
result = await this.getObject(payload.storeName, payload.key);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_MULTIPLE_OBJECTS':
|
|
||||||
result = await this.sendMessageToWorker('GET_MULTIPLE_OBJECTS', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS':
|
|
||||||
result = await this.sendMessageToWorker('GET_ALL_OBJECTS', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'GET_ALL_OBJECTS_WITH_FILTER':
|
|
||||||
result = await this.sendMessageToWorker('GET_ALL_OBJECTS_WITH_FILTER', payload);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown database action: ${action}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.serviceWorkerRegistration?.active) {
|
|
||||||
this.serviceWorkerRegistration.active.postMessage({
|
|
||||||
type: 'DB_RESPONSE',
|
|
||||||
id,
|
|
||||||
result
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('[Database] Error handling database request:', error);
|
|
||||||
|
|
||||||
if (this.serviceWorkerRegistration?.active) {
|
|
||||||
this.serviceWorkerRegistration.active.postMessage({
|
|
||||||
type: 'DB_ERROR',
|
|
||||||
id,
|
|
||||||
error: error.message || String(error)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async handleServiceWorkerMessage(message: any) {
|
private async handleServiceWorkerMessage(message: any) {
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
case 'TO_DOWNLOAD':
|
case "TO_DOWNLOAD":
|
||||||
await this.handleDownloadList(message.data);
|
await this.handleDownloadList(message.data);
|
||||||
break;
|
break;
|
||||||
case 'DIFFS_TO_CREATE':
|
case "DIFFS_TO_CREATE":
|
||||||
await this.handleDiffsToCreate(message.data);
|
await this.handleDiffsToCreate(message.data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn('Unknown message type received from service worker:', message);
|
console.warn(
|
||||||
|
"Unknown message type received from service worker:",
|
||||||
|
message
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleDiffsToCreate(diffs: any[]): Promise<void> {
|
private async handleDiffsToCreate(diffs: any[]): Promise<void> {
|
||||||
console.log(`[Database] Creating ${diffs.length} diffs from Service Worker scan`);
|
console.log(
|
||||||
|
`[Database] Creating ${diffs.length} diffs from Service Worker scan`
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await this.saveDiffs(diffs);
|
await this.saveDiffs(diffs);
|
||||||
console.log('[Database] Diffs created successfully');
|
console.log("[Database] Diffs created successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Database] Error creating diffs:', error);
|
console.error("[Database] Error creating diffs:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,15 +252,17 @@ export class Database {
|
|||||||
try {
|
try {
|
||||||
const valueBytes = await service.fetchValueFromStorage(hash);
|
const valueBytes = await service.fetchValueFromStorage(hash);
|
||||||
if (valueBytes) {
|
if (valueBytes) {
|
||||||
const blob = new Blob([valueBytes], { type: 'application/octet-stream' });
|
const blob = new Blob([valueBytes], {
|
||||||
|
type: "application/octet-stream",
|
||||||
|
});
|
||||||
await service.saveBlobToDb(hash, blob);
|
await service.saveBlobToDb(hash, blob);
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new CustomEvent('newDataReceived', {
|
new CustomEvent("newDataReceived", {
|
||||||
detail: { processId, stateId, hash },
|
detail: { processId, stateId, hash },
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.log('Request data from managers of the process');
|
console.log("Request data from managers of the process");
|
||||||
if (!requestedStateId.includes(stateId)) {
|
if (!requestedStateId.includes(stateId)) {
|
||||||
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
await service.requestDataFromPeers(processId, [stateId], [roles]);
|
||||||
requestedStateId.push(stateId);
|
requestedStateId.push(stateId);
|
||||||
@ -289,35 +279,50 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
public async getStoreList(): Promise<{ [key: string]: string }> {
|
public async getStoreList(): Promise<{ [key: string]: string }> {
|
||||||
return this.sendMessageToWorker('GET_STORE_LIST', {});
|
return this.sendMessageToWorker("GET_STORE_LIST", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addObject(payload: { storeName: string; object: any; key: any }): Promise<void> {
|
public async addObject(payload: {
|
||||||
await this.sendMessageToWorker('ADD_OBJECT', payload);
|
storeName: string;
|
||||||
|
object: any;
|
||||||
|
key: any;
|
||||||
|
}): Promise<void> {
|
||||||
|
await this.sendMessageToWorker("ADD_OBJECT", payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async batchWriting(payload: { storeName: string; objects: { key: any; object: any }[] }): Promise<void> {
|
public async batchWriting(payload: {
|
||||||
await this.sendMessageToWorker('BATCH_WRITING', payload);
|
storeName: string;
|
||||||
|
objects: { key: any; object: any }[];
|
||||||
|
}): Promise<void> {
|
||||||
|
await this.sendMessageToWorker("BATCH_WRITING", payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getObject(storeName: string, key: string): Promise<any | null> {
|
public async getObject(storeName: string, key: string): Promise<any | null> {
|
||||||
return this.sendMessageToWorker('GET_OBJECT', { storeName, key });
|
return this.sendMessageToWorker("GET_OBJECT", { storeName, key });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async dumpStore(storeName: string): Promise<Record<string, any>> {
|
public async dumpStore(storeName: string): Promise<Record<string, any>> {
|
||||||
return this.sendMessageToWorker('DUMP_STORE', { storeName });
|
return this.sendMessageToWorker("DUMP_STORE", { storeName });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteObject(storeName: string, key: string): Promise<void> {
|
public async deleteObject(storeName: string, key: string): Promise<void> {
|
||||||
await this.sendMessageToWorker('DELETE_OBJECT', { storeName, key });
|
await this.sendMessageToWorker("DELETE_OBJECT", { storeName, key });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async clearStore(storeName: string): Promise<void> {
|
public async clearStore(storeName: string): Promise<void> {
|
||||||
await this.sendMessageToWorker('CLEAR_STORE', { storeName });
|
await this.sendMessageToWorker("CLEAR_STORE", { storeName });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async requestStoreByIndex(storeName: string, indexName: string, request: string): Promise<any[]> {
|
public async requestStoreByIndex(
|
||||||
return this.sendMessageToWorker('REQUEST_STORE_BY_INDEX', { storeName, indexName, request });
|
storeName: string,
|
||||||
|
indexName: string,
|
||||||
|
request: string
|
||||||
|
): Promise<any[]> {
|
||||||
|
return this.sendMessageToWorker("REQUEST_STORE_BY_INDEX", {
|
||||||
|
storeName,
|
||||||
|
indexName,
|
||||||
|
request,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async clearMultipleStores(storeNames: string[]): Promise<void> {
|
public async clearMultipleStores(storeNames: string[]): Promise<void> {
|
||||||
@ -332,24 +337,22 @@ export class Database {
|
|||||||
|
|
||||||
public async saveDevice(device: any): Promise<void> {
|
public async saveDevice(device: any): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const existing = await this.getObject('wallet', '1');
|
const existing = await this.getObject("wallet", "1");
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await this.deleteObject('wallet', '1');
|
await this.deleteObject("wallet", "1");
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'wallet',
|
storeName: "wallet",
|
||||||
object: { pre_id: '1', device },
|
object: { pre_id: "1", device },
|
||||||
key: null,
|
key: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getDevice(): Promise<any | null> {
|
public async getDevice(): Promise<any | null> {
|
||||||
const result = await this.getObject('wallet', '1');
|
const result = await this.getObject("wallet", "1");
|
||||||
console.log(result);
|
return result ? result["device"] : null;
|
||||||
|
|
||||||
return result ? result['device'] : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -358,27 +361,32 @@ export class Database {
|
|||||||
|
|
||||||
public async saveProcess(processId: string, process: any): Promise<void> {
|
public async saveProcess(processId: string, process: any): Promise<void> {
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'processes',
|
storeName: "processes",
|
||||||
object: process,
|
object: process,
|
||||||
key: processId,
|
key: processId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveProcessesBatch(processes: Record<string, any>): Promise<void> {
|
public async saveProcessesBatch(
|
||||||
|
processes: Record<string, any>
|
||||||
|
): Promise<void> {
|
||||||
if (Object.keys(processes).length === 0) return;
|
if (Object.keys(processes).length === 0) return;
|
||||||
|
|
||||||
await this.batchWriting({
|
await this.batchWriting({
|
||||||
storeName: 'processes',
|
storeName: "processes",
|
||||||
objects: Object.entries(processes).map(([key, value]) => ({ key, object: value })),
|
objects: Object.entries(processes).map(([key, value]) => ({
|
||||||
|
key,
|
||||||
|
object: value,
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getProcess(processId: string): Promise<any | null> {
|
public async getProcess(processId: string): Promise<any | null> {
|
||||||
return this.getObject('processes', processId);
|
return this.getObject("processes", processId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllProcesses(): Promise<Record<string, any>> {
|
public async getAllProcesses(): Promise<Record<string, any>> {
|
||||||
return this.dumpStore('processes');
|
return this.dumpStore("processes");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -387,14 +395,14 @@ export class Database {
|
|||||||
|
|
||||||
public async saveBlob(hash: string, data: Blob): Promise<void> {
|
public async saveBlob(hash: string, data: Blob): Promise<void> {
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'data',
|
storeName: "data",
|
||||||
object: data,
|
object: data,
|
||||||
key: hash,
|
key: hash,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getBlob(hash: string): Promise<Blob | null> {
|
public async getBlob(hash: string): Promise<Blob | null> {
|
||||||
return this.getObject('data', hash);
|
return this.getObject("data", hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -406,7 +414,7 @@ export class Database {
|
|||||||
|
|
||||||
for (const diff of diffs) {
|
for (const diff of diffs) {
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'diffs',
|
storeName: "diffs",
|
||||||
object: diff,
|
object: diff,
|
||||||
key: null,
|
key: null,
|
||||||
});
|
});
|
||||||
@ -414,11 +422,11 @@ export class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getDiff(hash: string): Promise<any | null> {
|
public async getDiff(hash: string): Promise<any | null> {
|
||||||
return this.getObject('diffs', hash);
|
return this.getObject("diffs", hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllDiffs(): Promise<Record<string, any>> {
|
public async getAllDiffs(): Promise<Record<string, any>> {
|
||||||
return this.dumpStore('diffs');
|
return this.dumpStore("diffs");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@ -426,14 +434,17 @@ export class Database {
|
|||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
public async getSharedSecret(address: string): Promise<string | null> {
|
public async getSharedSecret(address: string): Promise<string | null> {
|
||||||
return this.getObject('shared_secrets', address);
|
return this.getObject("shared_secrets", address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveSecretsBatch(unconfirmedSecrets: any[], sharedSecrets: { key: string; value: any }[]): Promise<void> {
|
public async saveSecretsBatch(
|
||||||
|
unconfirmedSecrets: any[],
|
||||||
|
sharedSecrets: { key: string; value: any }[]
|
||||||
|
): Promise<void> {
|
||||||
if (unconfirmedSecrets && unconfirmedSecrets.length > 0) {
|
if (unconfirmedSecrets && unconfirmedSecrets.length > 0) {
|
||||||
for (const secret of unconfirmedSecrets) {
|
for (const secret of unconfirmedSecrets) {
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'unconfirmed_secrets',
|
storeName: "unconfirmed_secrets",
|
||||||
object: secret,
|
object: secret,
|
||||||
key: null,
|
key: null,
|
||||||
});
|
});
|
||||||
@ -443,7 +454,7 @@ export class Database {
|
|||||||
if (sharedSecrets && sharedSecrets.length > 0) {
|
if (sharedSecrets && sharedSecrets.length > 0) {
|
||||||
for (const { key, value } of sharedSecrets) {
|
for (const { key, value } of sharedSecrets) {
|
||||||
await this.addObject({
|
await this.addObject({
|
||||||
storeName: 'shared_secrets',
|
storeName: "shared_secrets",
|
||||||
object: value,
|
object: value,
|
||||||
key: key,
|
key: key,
|
||||||
});
|
});
|
||||||
@ -451,10 +462,13 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllSecrets(): Promise<{ shared_secrets: Record<string, any>; unconfirmed_secrets: any[] }> {
|
public async getAllSecrets(): Promise<{
|
||||||
const sharedSecrets = await this.dumpStore('shared_secrets');
|
shared_secrets: Record<string, any>;
|
||||||
const unconfirmedSecrets = await this.dumpStore('unconfirmed_secrets');
|
unconfirmed_secrets: any[];
|
||||||
|
}> {
|
||||||
|
const sharedSecrets = await this.dumpStore("shared_secrets");
|
||||||
|
const unconfirmedSecrets = await this.dumpStore("unconfirmed_secrets");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shared_secrets: sharedSecrets,
|
shared_secrets: sharedSecrets,
|
||||||
unconfirmed_secrets: Object.values(unconfirmedSecrets),
|
unconfirmed_secrets: Object.values(unconfirmedSecrets),
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MessageType } from '../types/index';
|
import { MessageType } from "../types/index";
|
||||||
import Services from './service';
|
import Services from "./service";
|
||||||
import TokenService from './token.service';
|
import TokenService from "./token.service";
|
||||||
import { cleanSubscriptions } from '../utils/subscription.utils';
|
import { cleanSubscriptions } from "../utils/subscription.utils";
|
||||||
import { splitPrivateData, isValid32ByteHex } from '../utils/service.utils';
|
import { splitPrivateData, isValid32ByteHex } from "../utils/service.utils";
|
||||||
import { MerkleProofResult } from '../../pkg/sdk_client';
|
import { MerkleProofResult } from "../../pkg/sdk_client";
|
||||||
|
|
||||||
export class IframeController {
|
export class IframeController {
|
||||||
private static isInitialized = false; // <--- VERROU
|
private static isInitialized = false; // <--- VERROU
|
||||||
@ -13,39 +13,57 @@ export class IframeController {
|
|||||||
|
|
||||||
// On ne lance l'écoute que si on est dans une iframe
|
// On ne lance l'écoute que si on est dans une iframe
|
||||||
if (window.self !== window.top) {
|
if (window.self !== window.top) {
|
||||||
console.log('[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API...');
|
console.log(
|
||||||
|
"[IframeController] 📡 Mode Iframe détecté. Démarrage des listeners API..."
|
||||||
|
);
|
||||||
await IframeController.registerAllListeners();
|
await IframeController.registerAllListeners();
|
||||||
} else {
|
} else {
|
||||||
console.log("[IframeController] ℹ️ Mode Standalone (pas d'iframe). Listeners API inactifs.");
|
console.log(
|
||||||
|
"[IframeController] ℹ️ Mode Standalone (pas d'iframe). Listeners API inactifs."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async registerAllListeners() {
|
private static async registerAllListeners() {
|
||||||
console.log('[Router:API] 🎧 Enregistrement des gestionnaires de messages (postMessage)...');
|
console.log(
|
||||||
|
"[Router:API] 🎧 Enregistrement des gestionnaires de messages (postMessage)..."
|
||||||
|
);
|
||||||
const services = await Services.getInstance();
|
const services = await Services.getInstance();
|
||||||
const tokenService = await TokenService.getInstance();
|
const tokenService = await TokenService.getInstance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction centralisée pour envoyer des réponses d'erreur à la fenêtre parente (l'application A).
|
* Fonction centralisée pour envoyer des réponses d'erreur à la fenêtre parente (l'application A).
|
||||||
*/
|
*/
|
||||||
const errorResponse = (errorMsg: string, origin: string, messageId?: string) => {
|
const errorResponse = (
|
||||||
console.error(`[Router:API] 📤 Envoi Erreur: ${errorMsg} (Origine: ${origin}, MsgID: ${messageId})`);
|
errorMsg: string,
|
||||||
|
origin: string,
|
||||||
|
messageId?: string
|
||||||
|
) => {
|
||||||
|
console.error(
|
||||||
|
`[Router:API] 📤 Envoi Erreur: ${errorMsg} (Origine: ${origin}, MsgID: ${messageId})`
|
||||||
|
);
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
type: MessageType.ERROR,
|
type: MessageType.ERROR,
|
||||||
error: errorMsg,
|
error: errorMsg,
|
||||||
messageId,
|
messageId,
|
||||||
},
|
},
|
||||||
origin,
|
origin
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper pour vérifier le token avant chaque action sensible
|
// Helper pour vérifier le token avant chaque action sensible
|
||||||
const withToken = async (event: MessageEvent, action: () => Promise<void>) => {
|
const withToken = async (
|
||||||
|
event: MessageEvent,
|
||||||
|
action: () => Promise<void>
|
||||||
|
) => {
|
||||||
const { accessToken } = event.data;
|
const { accessToken } = event.data;
|
||||||
// On vérifie si le token est présent ET valide pour l'origine de l'iframe
|
// On vérifie si le token est présent ET valide pour l'origine de l'iframe
|
||||||
if (!accessToken || !(await tokenService.validateToken(accessToken, event.origin))) {
|
if (
|
||||||
throw new Error('Invalid or expired session token');
|
!accessToken ||
|
||||||
|
!(await tokenService.validateToken(accessToken, event.origin))
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid or expired session token");
|
||||||
}
|
}
|
||||||
// Si tout est bon, on exécute l'action
|
// Si tout est bon, on exécute l'action
|
||||||
await action();
|
await action();
|
||||||
@ -54,35 +72,53 @@ export class IframeController {
|
|||||||
// --- Définitions des gestionnaires (Handlers) ---
|
// --- Définitions des gestionnaires (Handlers) ---
|
||||||
|
|
||||||
const handleRequestLink = async (event: MessageEvent) => {
|
const handleRequestLink = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}`);
|
console.log(
|
||||||
|
`[Router:API] 📨 Message ${MessageType.REQUEST_LINK} reçu de ${event.origin}`
|
||||||
|
);
|
||||||
|
|
||||||
// 1. Vérifier si l'appareil est DÉJÀ appairé (cas de la 2ème connexion)
|
// 1. Vérifier si l'appareil est DÉJÀ appairé (cas de la 2ème connexion)
|
||||||
const device = await services.getDeviceFromDatabase();
|
const device = await services.getDeviceFromDatabase();
|
||||||
|
|
||||||
if (device && device.pairing_process_commitment) {
|
if (device && device.pairing_process_commitment) {
|
||||||
console.log("[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts.");
|
console.log(
|
||||||
|
"[Router:API] Appareil déjà appairé. Pas besoin d'attendre home.ts."
|
||||||
|
);
|
||||||
// On saute l'attente et on passe directement à la suite.
|
// On saute l'attente et on passe directement à la suite.
|
||||||
} else {
|
} else {
|
||||||
// 2. Cas de la 1ère connexion (appareil non appairé)
|
// 2. Cas de la 1ère connexion (appareil non appairé)
|
||||||
// On doit attendre que home.ts (auto-pairing) ait fini son travail.
|
// On doit attendre que home.ts (auto-pairing) ait fini son travail.
|
||||||
console.log('[Router:API] Appareil non appairé. En attente du feu vert de home.ts...');
|
await new Promise<void>((resolve, reject) => {
|
||||||
const maxWait = 5000; // 5 sec
|
// Fonction de nettoyage pour éviter les fuites de mémoire
|
||||||
let waited = 0;
|
const cleanup = () => {
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
document.removeEventListener(
|
||||||
|
"app:pairing-ready",
|
||||||
|
handler as EventListener
|
||||||
|
);
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
|
||||||
// On attend le drapeau global
|
// Le gestionnaire de l'événement
|
||||||
while (!(window as any).__PAIRING_READY && waited < maxWait) {
|
const handler = (e: CustomEvent) => {
|
||||||
await delay(100);
|
cleanup();
|
||||||
waited += 100;
|
if (e.detail && e.detail.success) {
|
||||||
}
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error(e.detail?.error || "Auto-pairing failed"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 3. Vérifier le résultat de l'attente
|
// Timeout de sécurité (5 secondes)
|
||||||
if ((window as any).__PAIRING_READY === 'error') {
|
const timeoutId = setTimeout(() => {
|
||||||
throw new Error('Auto-pairing failed');
|
cleanup();
|
||||||
}
|
reject(new Error("Auto-pairing timed out (Event not received)"));
|
||||||
if (!(window as any).__PAIRING_READY) {
|
}, 5000);
|
||||||
throw new Error('Auto-pairing timed out');
|
|
||||||
}
|
// On écoute l'événement qu'on a créé dans Home.ts
|
||||||
|
document.addEventListener(
|
||||||
|
"app:pairing-ready",
|
||||||
|
handler as EventListener
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`[Router:API] Feu vert de home.ts reçu !`);
|
console.log(`[Router:API] Feu vert de home.ts reçu !`);
|
||||||
}
|
}
|
||||||
@ -98,50 +134,69 @@ export class IframeController {
|
|||||||
refreshToken: tokens.refreshToken,
|
refreshToken: tokens.refreshToken,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`[Router:API] ✅ ${MessageType.REQUEST_LINK} accepté et jetons envoyés.`
|
||||||
);
|
);
|
||||||
console.log(`[Router:API] ✅ ${MessageType.REQUEST_LINK} accepté et jetons envoyés.`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreatePairing = async (event: MessageEvent) => {
|
const handleCreatePairing = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PAIRING} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PAIRING} reçu`);
|
||||||
|
|
||||||
if (services.isPaired()) {
|
if (services.isPaired()) {
|
||||||
throw new Error('Device already paired — ignoring CREATE_PAIRING request');
|
throw new Error(
|
||||||
|
"Device already paired — ignoring CREATE_PAIRING request"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
console.log("[Router:API] 🚀 Démarrage du processus d'appairage...");
|
console.log("[Router:API] 🚀 Démarrage du processus d'appairage...");
|
||||||
|
|
||||||
const myAddress = services.getDeviceAddress();
|
const myAddress = services.getDeviceAddress();
|
||||||
console.log('[Router:API] 1/7: Création du processus de pairing...');
|
console.log("[Router:API] 1/7: Création du processus de pairing...");
|
||||||
const createPairingProcessReturn = await services.createPairingProcess('', [myAddress]);
|
const createPairingProcessReturn = await services.createPairingProcess(
|
||||||
|
"",
|
||||||
|
[myAddress]
|
||||||
|
);
|
||||||
|
|
||||||
const pairingId = createPairingProcessReturn.updated_process?.process_id;
|
const pairingId =
|
||||||
const stateId = createPairingProcessReturn.updated_process?.current_process?.states[0]?.state_id as string;
|
createPairingProcessReturn.updated_process?.process_id;
|
||||||
|
const stateId = createPairingProcessReturn.updated_process
|
||||||
|
?.current_process?.states[0]?.state_id as string;
|
||||||
if (!pairingId || !stateId) {
|
if (!pairingId || !stateId) {
|
||||||
throw new Error('Pairing process creation failed to return valid IDs');
|
throw new Error(
|
||||||
|
"Pairing process creation failed to return valid IDs"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`);
|
console.log(`[Router:API] 2/7: Processus ${pairingId} créé.`);
|
||||||
|
|
||||||
console.log("[Router:API] 3/7: Enregistrement local de l'appareil...");
|
console.log("[Router:API] 3/7: Enregistrement local de l'appareil...");
|
||||||
services.pairDevice(pairingId, [myAddress]);
|
services.pairDevice(pairingId, [myAddress]);
|
||||||
|
|
||||||
console.log('[Router:API] 4/7: Traitement du retour (handleApiReturn)...');
|
console.log(
|
||||||
|
"[Router:API] 4/7: Traitement du retour (handleApiReturn)..."
|
||||||
|
);
|
||||||
await services.handleApiReturn(createPairingProcessReturn);
|
await services.handleApiReturn(createPairingProcessReturn);
|
||||||
|
|
||||||
console.log('[Router:API] 5/7: Création de la mise à jour PRD...');
|
console.log("[Router:API] 5/7: Création de la mise à jour PRD...");
|
||||||
const createPrdUpdateReturn = await services.createPrdUpdate(pairingId, stateId);
|
const createPrdUpdateReturn = await services.createPrdUpdate(
|
||||||
|
pairingId,
|
||||||
|
stateId
|
||||||
|
);
|
||||||
await services.handleApiReturn(createPrdUpdateReturn);
|
await services.handleApiReturn(createPrdUpdateReturn);
|
||||||
|
|
||||||
console.log('[Router:API] 6/7: Approbation du changement...');
|
console.log("[Router:API] 6/7: Approbation du changement...");
|
||||||
const approveChangeReturn = await services.approveChange(pairingId, stateId);
|
const approveChangeReturn = await services.approveChange(
|
||||||
|
pairingId,
|
||||||
|
stateId
|
||||||
|
);
|
||||||
await services.handleApiReturn(approveChangeReturn);
|
await services.handleApiReturn(approveChangeReturn);
|
||||||
|
|
||||||
console.log('[Router:API] 7/7: Confirmation finale du pairing...');
|
console.log("[Router:API] 7/7: Confirmation finale du pairing...");
|
||||||
// await services.confirmPairing();
|
// await services.confirmPairing();
|
||||||
|
|
||||||
console.log('[Router:API] 🎉 Appairage terminé avec succès !');
|
console.log("[Router:API] 🎉 Appairage terminé avec succès !");
|
||||||
|
|
||||||
const successMsg = {
|
const successMsg = {
|
||||||
type: MessageType.PAIRING_CREATED,
|
type: MessageType.PAIRING_CREATED,
|
||||||
@ -153,8 +208,10 @@ export class IframeController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleGetMyProcesses = async (event: MessageEvent) => {
|
const handleGetMyProcesses = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`);
|
console.log(
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
`[Router:API] 📨 Message ${MessageType.GET_MY_PROCESSES} reçu`
|
||||||
|
);
|
||||||
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
const myProcesses = await services.getMyProcesses();
|
const myProcesses = await services.getMyProcesses();
|
||||||
@ -165,14 +222,14 @@ export class IframeController {
|
|||||||
myProcesses,
|
myProcesses,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGetProcesses = async (event: MessageEvent) => {
|
const handleGetProcesses = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.GET_PROCESSES} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
const processes = await services.getProcesses();
|
const processes = await services.getProcesses();
|
||||||
@ -183,14 +240,14 @@ export class IframeController {
|
|||||||
processes,
|
processes,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecryptState = async (event: MessageEvent) => {
|
const handleDecryptState = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.RETRIEVE_DATA} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { processId, stateId } = event.data;
|
const { processId, stateId } = event.data;
|
||||||
|
|
||||||
@ -199,22 +256,36 @@ export class IframeController {
|
|||||||
if (!process) throw new Error("Can't find process");
|
if (!process) throw new Error("Can't find process");
|
||||||
|
|
||||||
const state = services.getStateFromId(process, stateId);
|
const state = services.getStateFromId(process, stateId);
|
||||||
if (!state) throw new Error(`Unknown state ${stateId} for process ${processId}`);
|
if (!state)
|
||||||
|
throw new Error(`Unknown state ${stateId} for process ${processId}`);
|
||||||
|
|
||||||
console.log(`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`);
|
console.log(
|
||||||
|
`[Router:API] 🔐 Démarrage du déchiffrement pour ${processId}`
|
||||||
|
);
|
||||||
await services.ensureConnections(process, stateId);
|
await services.ensureConnections(process, stateId);
|
||||||
|
|
||||||
const res: Record<string, any> = {};
|
const res: Record<string, any> = {};
|
||||||
for (const attribute of Object.keys(state.pcd_commitment)) {
|
for (const attribute of Object.keys(state.pcd_commitment)) {
|
||||||
if (attribute === 'roles' || (state.public_data && state.public_data[attribute])) {
|
if (
|
||||||
|
attribute === "roles" ||
|
||||||
|
(state.public_data && state.public_data[attribute])
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const decryptedAttribute = await services.decryptAttribute(processId, state, attribute);
|
const decryptedAttribute = await services.decryptAttribute(
|
||||||
|
processId,
|
||||||
|
state,
|
||||||
|
attribute
|
||||||
|
);
|
||||||
if (decryptedAttribute) {
|
if (decryptedAttribute) {
|
||||||
res[attribute] = decryptedAttribute;
|
res[attribute] = decryptedAttribute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${Object.keys(res).length} attribut(s) déchiffré(s).`);
|
console.log(
|
||||||
|
`[Router:API] ✅ Déchiffrement terminé pour ${processId}. ${
|
||||||
|
Object.keys(res).length
|
||||||
|
} attribut(s) déchiffré(s).`
|
||||||
|
);
|
||||||
|
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
@ -222,7 +293,7 @@ export class IframeController {
|
|||||||
data: res,
|
data: res,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -232,10 +303,13 @@ export class IframeController {
|
|||||||
const accessToken = event.data.accessToken;
|
const accessToken = event.data.accessToken;
|
||||||
const refreshToken = event.data.refreshToken;
|
const refreshToken = event.data.refreshToken;
|
||||||
if (!accessToken || !refreshToken) {
|
if (!accessToken || !refreshToken) {
|
||||||
throw new Error('Missing access, refresh token or both');
|
throw new Error("Missing access, refresh token or both");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isValid = await tokenService.validateToken(accessToken, event.origin);
|
const isValid = await tokenService.validateToken(
|
||||||
|
accessToken,
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
console.log(`[Router:API] 🔑 Validation Jeton: ${isValid}`);
|
console.log(`[Router:API] 🔑 Validation Jeton: ${isValid}`);
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
@ -245,17 +319,21 @@ export class IframeController {
|
|||||||
isValid: isValid,
|
isValid: isValid,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRenewToken = async (event: MessageEvent) => {
|
const handleRenewToken = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.RENEW_TOKEN} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.RENEW_TOKEN} reçu`);
|
||||||
const refreshToken = event.data.refreshToken;
|
const refreshToken = event.data.refreshToken;
|
||||||
if (!refreshToken) throw new Error('No refresh token provided');
|
if (!refreshToken) throw new Error("No refresh token provided");
|
||||||
|
|
||||||
const newAccessToken = await tokenService.refreshAccessToken(refreshToken, event.origin);
|
const newAccessToken = await tokenService.refreshAccessToken(
|
||||||
if (!newAccessToken) throw new Error('Failed to refresh token (invalid refresh token)');
|
refreshToken,
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
if (!newAccessToken)
|
||||||
|
throw new Error("Failed to refresh token (invalid refresh token)");
|
||||||
|
|
||||||
console.log(`[Router:API] 🔑 Jeton d'accès renouvelé.`);
|
console.log(`[Router:API] 🔑 Jeton d'accès renouvelé.`);
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
@ -265,7 +343,7 @@ export class IframeController {
|
|||||||
refreshToken: refreshToken,
|
refreshToken: refreshToken,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -285,19 +363,29 @@ export class IframeController {
|
|||||||
if (device && device.pairing_process_commitment) {
|
if (device && device.pairing_process_commitment) {
|
||||||
// SUCCÈS ! L'ID est dans la BDD
|
// SUCCÈS ! L'ID est dans la BDD
|
||||||
pairingId = device.pairing_process_commitment;
|
pairingId = device.pairing_process_commitment;
|
||||||
console.log(`[Router:API] GET_PAIRING_ID: ID trouvé en BDD (tentative ${i + 1}/${maxRetries})`);
|
console.log(
|
||||||
|
`[Router:API] GET_PAIRING_ID: ID trouvé en BDD (tentative ${
|
||||||
|
i + 1
|
||||||
|
}/${maxRetries})`
|
||||||
|
);
|
||||||
break; // On sort de la boucle
|
break; // On sort de la boucle
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si non trouvé, on patiente
|
// Si non trouvé, on patiente
|
||||||
console.warn(`[Router:API] GET_PAIRING_ID: Non trouvé en BDD, nouvelle tentative... (${i + 1}/${maxRetries})`);
|
console.warn(
|
||||||
|
`[Router:API] GET_PAIRING_ID: Non trouvé en BDD, nouvelle tentative... (${
|
||||||
|
i + 1
|
||||||
|
}/${maxRetries})`
|
||||||
|
);
|
||||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si la boucle se termine sans succès
|
// Si la boucle se termine sans succès
|
||||||
if (!pairingId) {
|
if (!pairingId) {
|
||||||
console.error(`[Router:API] GET_PAIRING_ID: Échec final, non trouvé en BDD après ${maxRetries} tentatives.`);
|
console.error(
|
||||||
throw new Error('Device not paired');
|
`[Router:API] GET_PAIRING_ID: Échec final, non trouvé en BDD après ${maxRetries} tentatives.`
|
||||||
|
);
|
||||||
|
throw new Error("Device not paired");
|
||||||
}
|
}
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
@ -307,31 +395,42 @@ export class IframeController {
|
|||||||
userPairingId: pairingId,
|
userPairingId: pairingId,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateProcess = async (event: MessageEvent) => {
|
const handleCreateProcess = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.CREATE_PROCESS} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { processData, privateFields, roles } = event.data;
|
const { processData, privateFields, roles } = event.data;
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
console.log('[Router:API] 🚀 Démarrage de la création de processus standard...');
|
console.log(
|
||||||
const { privateData, publicData } = splitPrivateData(processData, privateFields);
|
"[Router:API] 🚀 Démarrage de la création de processus standard..."
|
||||||
|
);
|
||||||
|
const { privateData, publicData } = splitPrivateData(
|
||||||
|
processData,
|
||||||
|
privateFields
|
||||||
|
);
|
||||||
|
|
||||||
console.log('[Router:API] 1/2: Création du processus...');
|
console.log("[Router:API] 1/2: Création du processus...");
|
||||||
const createProcessReturn = await services.createProcess(privateData, publicData, roles);
|
const createProcessReturn = await services.createProcess(
|
||||||
|
privateData,
|
||||||
|
publicData,
|
||||||
|
roles
|
||||||
|
);
|
||||||
if (!createProcessReturn.updated_process) {
|
if (!createProcessReturn.updated_process) {
|
||||||
throw new Error('Empty updated_process in createProcessReturn');
|
throw new Error("Empty updated_process in createProcessReturn");
|
||||||
}
|
}
|
||||||
|
|
||||||
const processId = createProcessReturn.updated_process.process_id;
|
const processId = createProcessReturn.updated_process.process_id;
|
||||||
const process = createProcessReturn.updated_process.current_process;
|
const process = createProcessReturn.updated_process.current_process;
|
||||||
const stateId = process.states[0].state_id;
|
const stateId = process.states[0].state_id;
|
||||||
console.log(`[Router:API] 2/2: Processus ${processId} créé. Traitement...`);
|
console.log(
|
||||||
|
`[Router:API] 2/2: Processus ${processId} créé. Traitement...`
|
||||||
|
);
|
||||||
await services.handleApiReturn(createProcessReturn);
|
await services.handleApiReturn(createProcessReturn);
|
||||||
|
|
||||||
console.log(`[Router:API] 🎉 Processus ${processId} créé.`);
|
console.log(`[Router:API] 🎉 Processus ${processId} créé.`);
|
||||||
@ -348,19 +447,19 @@ export class IframeController {
|
|||||||
processCreated: res,
|
processCreated: res,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNotifyUpdate = async (event: MessageEvent) => {
|
const handleNotifyUpdate = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.NOTIFY_UPDATE} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { processId, stateId } = event.data;
|
const { processId, stateId } = event.data;
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
if (!isValid32ByteHex(stateId)) throw new Error('Invalid state id');
|
if (!isValid32ByteHex(stateId)) throw new Error("Invalid state id");
|
||||||
|
|
||||||
const res = await services.createPrdUpdate(processId, stateId);
|
const res = await services.createPrdUpdate(processId, stateId);
|
||||||
await services.handleApiReturn(res);
|
await services.handleApiReturn(res);
|
||||||
@ -370,14 +469,14 @@ export class IframeController {
|
|||||||
type: MessageType.UPDATE_NOTIFIED,
|
type: MessageType.UPDATE_NOTIFIED,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValidateState = async (event: MessageEvent) => {
|
const handleValidateState = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_STATE} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { processId, stateId } = event.data;
|
const { processId, stateId } = event.data;
|
||||||
|
|
||||||
@ -391,22 +490,29 @@ export class IframeController {
|
|||||||
validatedProcess: res.updated_process,
|
validatedProcess: res.updated_process,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateProcess = async (event: MessageEvent) => {
|
const handleUpdateProcess = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`);
|
console.log(`[Router:API] 📨 Message ${MessageType.UPDATE_PROCESS} reçu`);
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { processId, newData, privateFields, roles } = event.data;
|
const { processId, newData, privateFields, roles } = event.data;
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
console.log(`[Router:API] 🔄 Transfert de la mise à jour de ${processId} au service...`);
|
console.log(
|
||||||
|
`[Router:API] 🔄 Transfert de la mise à jour de ${processId} au service...`
|
||||||
|
);
|
||||||
|
|
||||||
// Le service gère maintenant tout : récupération, réparation d'état, et mise à jour.
|
// Le service gère maintenant tout : récupération, réparation d'état, et mise à jour.
|
||||||
const res = await services.updateProcess(processId, newData, privateFields, roles);
|
const res = await services.updateProcess(
|
||||||
|
processId,
|
||||||
|
newData,
|
||||||
|
privateFields,
|
||||||
|
roles
|
||||||
|
);
|
||||||
|
|
||||||
// Nous appelons handleApiReturn ici, comme avant.
|
// Nous appelons handleApiReturn ici, comme avant.
|
||||||
await services.handleApiReturn(res);
|
await services.handleApiReturn(res);
|
||||||
@ -418,14 +524,16 @@ export class IframeController {
|
|||||||
updatedProcess: res.updated_process, // res vient directement de l'appel service
|
updatedProcess: res.updated_process, // res vient directement de l'appel service
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDecodePublicData = async (event: MessageEvent) => {
|
const handleDecodePublicData = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`);
|
console.log(
|
||||||
if (!services.isPaired()) throw new Error('Device not paired');
|
`[Router:API] 📨 Message ${MessageType.DECODE_PUBLIC_DATA} reçu`
|
||||||
|
);
|
||||||
|
if (!services.isPaired()) throw new Error("Device not paired");
|
||||||
|
|
||||||
const { encodedData } = event.data;
|
const { encodedData } = event.data;
|
||||||
|
|
||||||
@ -437,7 +545,7 @@ export class IframeController {
|
|||||||
decodedData,
|
decodedData,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -454,30 +562,37 @@ export class IframeController {
|
|||||||
hash,
|
hash,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGetMerkleProof = async (event: MessageEvent) => {
|
const handleGetMerkleProof = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu`);
|
console.log(
|
||||||
|
`[Router:API] 📨 Message ${MessageType.GET_MERKLE_PROOF} reçu`
|
||||||
|
);
|
||||||
const { processState, attributeName } = event.data;
|
const { processState, attributeName } = event.data;
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
const proof = services.getMerkleProofForFile(processState, attributeName);
|
const proof = services.getMerkleProofForFile(
|
||||||
|
processState,
|
||||||
|
attributeName
|
||||||
|
);
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
type: MessageType.MERKLE_PROOF_RETRIEVED,
|
type: MessageType.MERKLE_PROOF_RETRIEVED,
|
||||||
proof,
|
proof,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValidateMerkleProof = async (event: MessageEvent) => {
|
const handleValidateMerkleProof = async (event: MessageEvent) => {
|
||||||
console.log(`[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu`);
|
console.log(
|
||||||
|
`[Router:API] 📨 Message ${MessageType.VALIDATE_MERKLE_PROOF} reçu`
|
||||||
|
);
|
||||||
const { merkleProof, documentHash } = event.data;
|
const { merkleProof, documentHash } = event.data;
|
||||||
|
|
||||||
await withToken(event, async () => {
|
await withToken(event, async () => {
|
||||||
@ -485,25 +600,28 @@ export class IframeController {
|
|||||||
try {
|
try {
|
||||||
parsedMerkleProof = JSON.parse(merkleProof);
|
parsedMerkleProof = JSON.parse(merkleProof);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Provided merkleProof is not a valid json object');
|
throw new Error("Provided merkleProof is not a valid json object");
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = services.validateMerkleProof(parsedMerkleProof, documentHash);
|
const res = services.validateMerkleProof(
|
||||||
|
parsedMerkleProof,
|
||||||
|
documentHash
|
||||||
|
);
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
{
|
{
|
||||||
type: MessageType.MERKLE_PROOF_VALIDATED,
|
type: MessageType.MERKLE_PROOF_VALIDATED,
|
||||||
isValid: res,
|
isValid: res,
|
||||||
messageId: event.data.messageId,
|
messageId: event.data.messageId,
|
||||||
},
|
},
|
||||||
event.origin,
|
event.origin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Le "Switchyard" : il reçoit tous les messages et les dispatche ---
|
// --- Le "Switchyard" : il reçoit tous les messages et les dispatche ---
|
||||||
|
|
||||||
window.removeEventListener('message', handleMessage);
|
window.removeEventListener("message", handleMessage);
|
||||||
window.addEventListener('message', handleMessage);
|
window.addEventListener("message", handleMessage);
|
||||||
|
|
||||||
async function handleMessage(event: MessageEvent) {
|
async function handleMessage(event: MessageEvent) {
|
||||||
try {
|
try {
|
||||||
@ -557,7 +675,7 @@ export class IframeController {
|
|||||||
await handleValidateMerkleProof(event);
|
await handleValidateMerkleProof(event);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn('[Router:API] ⚠️ Message non géré reçu:', event.data);
|
console.warn("[Router:API] ⚠️ Message non géré reçu:", event.data);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`;
|
const errorMsg = `[Router:API] 💥 Erreur de haut niveau: ${error}`;
|
||||||
@ -569,9 +687,10 @@ export class IframeController {
|
|||||||
{
|
{
|
||||||
type: MessageType.LISTENING,
|
type: MessageType.LISTENING,
|
||||||
},
|
},
|
||||||
'*',
|
"*"
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"[Router:API] ✅ Tous les listeners sont actifs. Envoi du message LISTENING au parent."
|
||||||
);
|
);
|
||||||
console.log('[Router:API] ✅ Tous les listeners sont actifs. Envoi du message LISTENING au parent.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import * as jose from 'jose';
|
import * as jose from "jose";
|
||||||
|
|
||||||
interface TokenPair {
|
interface TokenPair {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
@ -7,10 +7,14 @@ interface TokenPair {
|
|||||||
|
|
||||||
export default class TokenService {
|
export default class TokenService {
|
||||||
private static instance: TokenService;
|
private static instance: TokenService;
|
||||||
private readonly SECRET_KEY = import.meta.env.VITE_JWT_SECRET_KEY;
|
|
||||||
private readonly ACCESS_TOKEN_EXPIRATION = '30s';
|
// Constantes
|
||||||
private readonly REFRESH_TOKEN_EXPIRATION = '7d';
|
private readonly STORAGE_KEY = "4NK_SECURE_SESSION_KEY";
|
||||||
private readonly encoder = new TextEncoder();
|
private readonly ACCESS_TOKEN_EXPIRATION = "30s";
|
||||||
|
private readonly REFRESH_TOKEN_EXPIRATION = "7d";
|
||||||
|
|
||||||
|
// Cache mémoire pour éviter de lire le localStorage à chaque appel
|
||||||
|
private secretKeyCache: Uint8Array | null = null;
|
||||||
|
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
@ -21,17 +25,47 @@ export default class TokenService {
|
|||||||
return TokenService.instance;
|
return TokenService.instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère la clé secrète existante ou en génère une nouvelle
|
||||||
|
* et la sauvegarde dans le localStorage pour survivre aux refresh.
|
||||||
|
*/
|
||||||
|
private getSecretKey(): Uint8Array {
|
||||||
|
if (this.secretKeyCache) return this.secretKeyCache;
|
||||||
|
|
||||||
|
const storedKey = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
|
||||||
|
if (storedKey) {
|
||||||
|
// Restauration de la clé existante (Hex -> Uint8Array)
|
||||||
|
this.secretKeyCache = this.hexToBuffer(storedKey);
|
||||||
|
} else {
|
||||||
|
// Génération d'une nouvelle clé aléatoire de 32 octets (256 bits)
|
||||||
|
const newKey = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(newKey);
|
||||||
|
|
||||||
|
// Sauvegarde (Uint8Array -> Hex)
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, this.bufferToHex(newKey));
|
||||||
|
this.secretKeyCache = newKey;
|
||||||
|
console.log(
|
||||||
|
"[TokenService] 🔐 Nouvelle clé de session générée et stockée."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.secretKeyCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Méthodes Publiques ---
|
||||||
|
|
||||||
async generateSessionToken(origin: string): Promise<TokenPair> {
|
async generateSessionToken(origin: string): Promise<TokenPair> {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = this.getSecretKey();
|
||||||
|
|
||||||
const accessToken = await new jose.SignJWT({ origin, type: 'access' })
|
const accessToken = await new jose.SignJWT({ origin, type: "access" })
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
.setExpirationTime(this.ACCESS_TOKEN_EXPIRATION)
|
.setExpirationTime(this.ACCESS_TOKEN_EXPIRATION)
|
||||||
.sign(secret);
|
.sign(secret);
|
||||||
|
|
||||||
const refreshToken = await new jose.SignJWT({ origin, type: 'refresh' })
|
const refreshToken = await new jose.SignJWT({ origin, type: "refresh" })
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
.setExpirationTime(this.REFRESH_TOKEN_EXPIRATION)
|
.setExpirationTime(this.REFRESH_TOKEN_EXPIRATION)
|
||||||
.sign(secret);
|
.sign(secret);
|
||||||
@ -41,47 +75,64 @@ export default class TokenService {
|
|||||||
|
|
||||||
async validateToken(token: string, origin: string): Promise<boolean> {
|
async validateToken(token: string, origin: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
const secret = this.getSecretKey();
|
||||||
const { payload } = await jose.jwtVerify(token, secret);
|
const { payload } = await jose.jwtVerify(token, secret);
|
||||||
|
|
||||||
return payload.origin === origin;
|
return payload.origin === origin;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.code === 'ERR_JWT_EXPIRED') {
|
// On ignore les erreurs d'expiration classiques pour ne pas polluer la console
|
||||||
console.log('Token expiré');
|
if (error?.code === "ERR_JWT_EXPIRED") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error('Erreur de validation du token:', error);
|
console.warn(
|
||||||
|
"[TokenService] Validation échouée:",
|
||||||
|
error.code || error.message
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshAccessToken(refreshToken: string, origin: string): Promise<string | null> {
|
async refreshAccessToken(
|
||||||
|
refreshToken: string,
|
||||||
|
origin: string
|
||||||
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
// Vérifier si le refresh token est valide
|
// Validation du token (vérifie signature + expiration)
|
||||||
const isValid = await this.validateToken(refreshToken, origin);
|
const isValid = await this.validateToken(refreshToken, origin);
|
||||||
if (!isValid) {
|
if (!isValid) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier le type du token
|
const secret = this.getSecretKey();
|
||||||
const secret = new Uint8Array(this.encoder.encode(this.SECRET_KEY));
|
|
||||||
const { payload } = await jose.jwtVerify(refreshToken, secret);
|
const { payload } = await jose.jwtVerify(refreshToken, secret);
|
||||||
if (payload.type !== 'refresh') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Générer un nouveau access token
|
if (payload.type !== "refresh") return null;
|
||||||
const newAccessToken = await new jose.SignJWT({ origin, type: 'access' })
|
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
// Génération du nouveau token
|
||||||
|
return await new jose.SignJWT({ origin, type: "access" })
|
||||||
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
.setExpirationTime(this.ACCESS_TOKEN_EXPIRATION)
|
.setExpirationTime(this.ACCESS_TOKEN_EXPIRATION)
|
||||||
.sign(secret);
|
.sign(secret);
|
||||||
|
|
||||||
return newAccessToken;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors du refresh du token:', error);
|
console.error("[TokenService] Erreur refresh:", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// --- Utilitaires de conversion ---
|
||||||
|
|
||||||
|
private bufferToHex(buffer: Uint8Array): string {
|
||||||
|
return Array.from(buffer)
|
||||||
|
.map((b) => b.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private hexToBuffer(hex: string): Uint8Array {
|
||||||
|
if (hex.length % 2 !== 0) throw new Error("Invalid hex string");
|
||||||
|
const bytes = new Uint8Array(hex.length / 2);
|
||||||
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
|
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -116,14 +116,14 @@ export function initAddressInput() {
|
|||||||
if (address) {
|
if (address) {
|
||||||
const emojis = await addressToEmoji(address);
|
const emojis = await addressToEmoji(address);
|
||||||
if (emojiDisplay) {
|
if (emojiDisplay) {
|
||||||
emojiDisplay.innerHTML = emojis;
|
emojiDisplay.textContent = emojis;
|
||||||
}
|
}
|
||||||
if (okButton) {
|
if (okButton) {
|
||||||
okButton.style.display = 'inline-block';
|
okButton.style.display = 'inline-block';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (emojiDisplay) {
|
if (emojiDisplay) {
|
||||||
emojiDisplay.innerHTML = '';
|
emojiDisplay.textContent = '';
|
||||||
}
|
}
|
||||||
if (okButton) {
|
if (okButton) {
|
||||||
okButton.style.display = 'none';
|
okButton.style.display = 'none';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user