Compare commits

..

No commits in common. "d78dc14a2b91ee3503269cbbeef0c2c54584fa32" and "8580070dc40dd8f990d3c39f424aa3553e7aec42" have entirely different histories.

3 changed files with 109 additions and 101 deletions

View File

@ -28,6 +28,7 @@
"vite-plugin-static-copy": "^1.0.6" "vite-plugin-static-copy": "^1.0.6"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.8",
"comlink": "^4.4.2", "comlink": "^4.4.2",
"jose": "^6.0.11", "jose": "^6.0.11",
"vite-plugin-wasm": "^3.3.0" "vite-plugin-wasm": "^3.3.0"

View File

@ -9,10 +9,6 @@ export class NetworkService {
// Cache local // Cache local
private localRelays: Record<string, string> = {}; private localRelays: Record<string, string> = {};
// Mécanisme d'attente (Events)
private relayReadyResolver: ((addr: string) => void) | null = null;
private relayReadyPromise: Promise<string> | null = null;
constructor(private bootstrapUrls: string[]) { constructor(private bootstrapUrls: string[]) {
this.workerInstance = new Worker( this.workerInstance = new Worker(
new URL("../../workers/network.worker.ts", import.meta.url), new URL("../../workers/network.worker.ts", import.meta.url),
@ -46,47 +42,36 @@ export class NetworkService {
await this.worker.sendMessage(flag as any, content); await this.worker.sendMessage(flag as any, content);
} }
// Cette méthode est appelée par le Worker (via Services.ts) ou par onStatusChange
public updateRelay(url: string, spAddress: string) { public updateRelay(url: string, spAddress: string) {
this.localRelays[url] = spAddress; this.localRelays[url] = spAddress;
// ✨ EVENT TRIGGER : Si quelqu'un attendait un relais, on le débloque !
if (spAddress && spAddress !== "" && this.relayReadyResolver) {
this.relayReadyResolver(spAddress);
this.relayReadyResolver = null;
this.relayReadyPromise = null;
}
} }
public getAllRelays() { public getAllRelays() {
return this.localRelays; return this.localRelays;
} }
// 🔥 CORRECTION ICI : Ajout d'une boucle d'attente (Polling)
public async getAvailableRelayAddress(): Promise<string> { public async getAvailableRelayAddress(): Promise<string> {
// 1. Vérification immédiate (Fast path) const maxRetries = 20; // 20 tentatives
const existing = Object.values(this.localRelays).find( const interval = 500; // toutes les 500ms = 10 secondes max
(addr) => addr && addr !== ""
);
if (existing) return existing;
// 2. Si pas encore là, on crée une "barrière" (Promise) for (let i = 0; i < maxRetries; i++) {
if (!this.relayReadyPromise) { // On demande au worker
console.log("[NetworkService] ⏳ Attente d'un événement Handshake..."); const addr = await this.worker.getAvailableRelay();
this.relayReadyPromise = new Promise<string>((resolve, reject) => {
this.relayReadyResolver = resolve;
// Timeout de sécurité (10s) pour ne pas bloquer indéfiniment if (addr && addr !== "") {
setTimeout(() => { return addr; // Trouvé !
if (this.relayReadyResolver) { }
reject(new Error("Timeout: Aucun relais reçu après 10s"));
this.relayReadyResolver = null; // Pas encore là ? On attend un peu...
this.relayReadyPromise = null; if (i === 0)
} console.log("[NetworkService] ⏳ Attente du Handshake relais...");
}, 10000); await new Promise((r) => setTimeout(r, interval));
});
} }
return this.relayReadyPromise; throw new Error(
"Timeout: Aucun relais disponible après 10s (Handshake non reçu ?)"
);
} }
// --- INTERNES --- // --- INTERNES ---
@ -102,8 +87,7 @@ export class NetworkService {
spAddress?: string spAddress?: string
) { ) {
if (status === "OPEN" && spAddress) { if (status === "OPEN" && spAddress) {
// Met à jour et déclenche potentiellement le resolve() this.localRelays[url] = spAddress;
this.updateRelay(url, spAddress);
} else if (status === "CLOSED") { } else if (status === "CLOSED") {
this.localRelays[url] = ""; this.localRelays[url] = "";
} }

View File

@ -1,112 +1,135 @@
export async function storeData( import axios, { AxiosResponse } from 'axios';
servers: string[],
key: string, export async function storeData(servers: string[], key: string, value: Blob, ttl: number | null): Promise<AxiosResponse | null> {
value: Blob,
ttl: number | null
): Promise<Response | null> {
for (const server of servers) { for (const server of servers) {
try { try {
// 1. Vérification d'existence (GET) // 1. On vérifie d'abord si la donnée existe en appelant le bon service
// On passe 'server' au lieu de 'url' pour que testData construise la bonne URL
const dataExists = await testData(server, key); const dataExists = await testData(server, key);
if (dataExists) { if (dataExists) {
console.log("Data already stored:", key); console.log('Data already stored:', key);
continue; continue;
} else { } else {
console.log( console.log('Data not stored for server, proceeding to POST:', key, server);
"Data not stored for server, proceeding to POST:",
key,
server
);
} }
// 2. Construction URL
// Construction de l'URL pour le POST (stockage)
// Cette partie était correcte
let url: string; let url: string;
if (server.startsWith("/")) { if (server.startsWith('/')) {
// Relative path
url = `${server}/store/${encodeURIComponent(key)}`; url = `${server}/store/${encodeURIComponent(key)}`;
if (ttl !== null) url += `?ttl=${ttl}`; if (ttl !== null) {
url += `?ttl=${ttl}`;
}
} else { } else {
// Absolute URL
const urlObj = new URL(`${server}/store/${encodeURIComponent(key)}`); const urlObj = new URL(`${server}/store/${encodeURIComponent(key)}`);
if (ttl !== null) urlObj.searchParams.append("ttl", ttl.toString()); if (ttl !== null) {
urlObj.searchParams.append('ttl', ttl.toString());
}
url = urlObj.toString(); url = urlObj.toString();
} }
// 3. Envoi (POST) avec Fetch // La ligne ci-dessous a été supprimée car le test est fait au-dessus
const response = await fetch(url, { // const testResponse = await testData(url, key); // <-- LIGNE BOGUÉE SUPPRIMÉE
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
},
body: value,
});
if (response.ok) { // Send the encrypted data as the raw request body.
// Status 200-299 const response = await axios.post(url, value, { // Note: c'est bien un POST sur 'url'
console.log("Data stored successfully:", key); headers: {
return response; 'Content-Type': 'application/octet-stream'
} else if (response.status === 409) { },
// Conflit (déjà existant), on retourne null comme avant });
return null; console.log('Data stored successfully:', key);
} else { if (response.status !== 200) {
console.error("Received response status", response.status); console.error('Received response status', response.status);
continue; continue;
} }
return response;
} catch (error) { } catch (error) {
console.error("Error storing data:", error); if (axios.isAxiosError(error) && error.response?.status === 409) {
return null; // 409 Conflict (Key already exists)
}
console.error('Error storing data:', error);
} }
} }
return null; return null;
} }
export async function retrieveData( // Fonction retrieveData (inchangée, elle était correcte)
servers: string[], export async function retrieveData(servers: string[], key: string): Promise<ArrayBuffer | null> {
key: string
): Promise<ArrayBuffer | null> {
for (const server of servers) { for (const server of servers) {
try { try {
const url = server.startsWith("/") const url = server.startsWith('/')
? `${server}/retrieve/${key}` ? `${server}/retrieve/${key}`
: new URL(`${server}/retrieve/${key}`).toString(); : new URL(`${server}/retrieve/${key}`).toString();
console.log("Retrieving data", key, " from:", url); console.log('Retrieving data', key,' from:', url);
const response = await fetch(url, { method: "GET" }); const response = await axios.get(url, {
responseType: 'arraybuffer'
});
if (response.ok) { if (response.status === 200) {
// Transformation en ArrayBuffer if (response.data instanceof ArrayBuffer) {
return await response.arrayBuffer(); return response.data;
} else {
if (response.status === 404) {
console.log(`Data not found on server ${server} for key ${key}`);
} else { } else {
console.error( console.error('Server returned non-ArrayBuffer data:', typeof response.data);
`Server ${server} returned status ${response.status}: ${response.statusText}` continue;
);
} }
} else {
console.error(`Server ${server} returned status ${response.status}`);
continue; continue;
} }
} catch (error) { } catch (error) {
console.error(`Unexpected error retrieving data from ${server}:`, error); if (axios.isAxiosError(error)) {
continue; if (error.response?.status === 404) {
// C'est normal si la donnée n'existe pas
console.log(`Data not found on server ${server} for key ${key}`);
continue;
} else if (error.response?.status) {
console.error(`Server ${server} error ${error.response.status}:`, error.response.statusText);
continue;
} else {
console.error(`Network error connecting to ${server}:`, error.message);
continue;
}
} else {
console.error(`Unexpected error retrieving data from ${server}:`, error);
continue;
}
} }
} }
return null; return null;
} }
interface TestResponse {
key: string;
value: boolean;
}
// Elle prend 'server' au lieu de 'url' et construit sa propre URL '/test/...'
export async function testData(server: string, key: string): Promise<boolean> { export async function testData(server: string, key: string): Promise<boolean> {
try { try {
const testUrl = server.startsWith("/") // Construit l'URL /test/...
const testUrl = server.startsWith('/')
? `${server}/test/${encodeURIComponent(key)}` ? `${server}/test/${encodeURIComponent(key)}`
: new URL(`${server}/test/${encodeURIComponent(key)}`).toString(); : new URL(`${server}/test/${encodeURIComponent(key)}`).toString();
// On utilise fetch ici aussi const response = await axios.get(testUrl); // Fait un GET sur /test/...
const response = await fetch(testUrl, { method: "GET" });
// 200 OK = existe // 200 OK = la donnée existe
return response.status === 200; return response.status === 200;
} catch (error) { } catch (error) {
// Erreur réseau if (axios.isAxiosError(error) && error.response?.status === 404) {
console.error("Error testing data:", error); // 404 Not Found = la donnée n'existe pas. C'est une réponse valide.
return false; return false;
}
// Toute autre erreur (serveur offline, 500, etc.)
console.error('Error testing data:', error);
return false; // On considère que le test a échoué
} }
} }