diff --git a/src/background/background.js b/src/background/background.js index d9a7d650c7f9a32519ffca04feddc0ff01b67fd5..fffbc9d381eea465fe1d3604562760eba27834cc 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -12,14 +12,14 @@ const AUTH_BALEX_URL = "https://prisms.lezinter.net/fr/headquarters/balex"; // ───────────────────────────────────────────────────────────────────────────── // Logs de démarrage // ───────────────────────────────────────────────────────────────────────────── -console.log("🚀 ff2BaLex (background) chargé."); +log("🚀 ff2BaLex (background) chargé."); browser.runtime.onInstalled.addListener((details) => { - console.log("🔔 Extension installée ou mise à jour. Raison :", details.reason); + log("🔔 Extension installée ou mise à jour. Raison :", details.reason); }); browser.runtime.onStartup.addListener(() => { - console.log("🔄 Extension démarrée (onStartup)."); + log("🔄 Extension démarrée (onStartup)."); }); // ───────────────────────────────────────────────────────────────────────────── @@ -28,15 +28,36 @@ browser.runtime.onStartup.addListener(() => { browser.storage.onChanged.addListener((changes) => { if (changes.extensionActive) { isExtensionActive = changes.extensionActive.newValue; - console.log("✅ Extension activée :", isExtensionActive); + log("✅ Extension activée :", isExtensionActive); } if (changes.statsActive) { areStatsActive = changes.statsActive.newValue; - console.log("📊 Statistiques activées :", areStatsActive); + log("📊 Statistiques activées :", areStatsActive); } refreshAllUI(); }); +browser.storage.onChanged.addListener((changes, area) => { + if (area === "local" && changes.accessToken) { + const newToken = changes.accessToken.newValue; + if (newToken) { + browser.storage.local.get("extensionActive").then(({ extensionActive }) => { + if (!extensionActive) { + log("Token ajouté, activation automatique de l'extension."); + browser.storage.local.set({ extensionActive: true }); + enableExtensionFeatures(); + browser.runtime.sendMessage({ + action: "updateUI", + extensionActive: true, + isTrackingActive: true, + autoAdd: true + }); + } + }); + } + } +}); + // ───────────────────────────────────────────────────────────────────────────── // Fonctions utilitaires // ───────────────────────────────────────────────────────────────────────────── @@ -46,7 +67,7 @@ async function isUserConnected() { } async function refreshAllUI() { - console.log("🔄 Rafraîchissement global de l'UI..."); + log("🔄 Rafraîchissement global de l'UI..."); browser.runtime.sendMessage({ action: "refreshUI" }); } @@ -57,12 +78,11 @@ browser.runtime.onConnect.addListener((port) => { if (port.name === "auth") { port.onMessage.addListener(async (message) => { if (message.action === "toggleAuth") { - console.log("🔄 toggleAuth reçu via port dans le background."); + log("🔄 toggleAuth reçu via port dans le background."); const isConnected = await isUserConnected(); if (isConnected) { await disconnectFromLexicalDB(); } else { - // Ouvre directement la page de connexion actuallyOpenLoginPage(); } } @@ -70,50 +90,52 @@ browser.runtime.onConnect.addListener((port) => { } }); -// Ouvre directement la page de connexion async function actuallyOpenLoginPage() { - console.log("🔗 Ouverture de la page de connexion."); + log("🔗 Ouverture de la page de connexion."); // Mémoriser l'onglet actif const [currentTab] = await browser.tabs.query({ active: true, currentWindow: true }); if (currentTab) { originalTabId = currentTab.id; - console.log("✅ Onglet courant mémorisé, ID =", originalTabId); + log("✅ Onglet courant mémorisé, ID =", originalTabId); } // Ouvre un nouvel onglet pour la page de connexion et l'active const loginTab = await browser.tabs.create({ url: AUTH_LOGIN_URL, active: true }); loginTabId = loginTab.id; - console.log("✅ Onglet de login créé, ID =", loginTabId); + log("✅ Onglet de login créé, ID =", loginTabId); // Notifie que l'authentification est en cours browser.runtime.sendMessage({ action: "authStatusChanged", isLoggedIn: false }); } -// Déconnecte l'utilisateur async function disconnectFromLexicalDB() { - console.log("🔓 Déconnexion en cours..."); await browser.storage.local.remove("accessToken"); - console.log("🔓 Token supprimé avec succès."); - // Réinitialiser les couleurs des lexiques dans le local storage - try { - await browser.storage.local.remove("lexiconColors"); - console.log("Les couleurs des lexiques ont été réinitialisées dans le local storage."); - } catch (error) { - console.error("Erreur lors de la réinitialisation des couleurs :", error); - } + log("🔓 Token supprimé avec succès."); + + await browser.storage.local.remove("lexiconColors"); + + // Désactivation automatique de l'extension + await browser.storage.local.set({ extensionActive: false }); + disableExtensionFeatures(); + browser.runtime.sendMessage({ + action: "updateUI", + extensionActive: false, + isTrackingActive: false, + autoAdd: false + }); + setTimeout(async () => { await refreshAllUI(); }, 500); } -// Sauvegarde le token et ferme l'onglet de login si nécessaire async function saveToken(token) { - console.log("✅ Sauvegarde du token :", token); + log("✅ Sauvegarde du token :", token); await browser.storage.local.set({ accessToken: token }); if (loginTabId) { try { await browser.tabs.remove(loginTabId); - console.log("🗙 Onglet de login fermé après connexion réussie."); + log("🗙 Onglet de login fermé après connexion réussie."); } catch (err) { console.warn("Impossible de fermer l'onglet de login :", err); } @@ -122,20 +144,33 @@ async function saveToken(token) { if (originalTabId) { try { await browser.tabs.update(originalTabId, { active: true }); - console.log("🔙 Retour sur l'onglet initial :", originalTabId); + log("🔙 Retour sur l'onglet initial :", originalTabId); } catch (err) { console.warn("Impossible de basculer sur l'onglet initial :", err); } originalTabId = null; } + + // Activer automatiquement l'extension + const { extensionActive } = await browser.storage.local.get("extensionActive"); + if (!extensionActive) { + await browser.storage.local.set({ extensionActive: true }); + enableExtensionFeatures(); + browser.runtime.sendMessage({ + action: "updateUI", + extensionActive: true, + isTrackingActive: true, + autoAdd: true + }); + } await refreshAllUI(); } // ───────────────────────────────────────────────────────────────────────────── -// Gestion des messages reçus +// Gestion des messages reçus (depuis popup, etc.) // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { - //console.log("📩 Message reçu dans background.js :", message); + log("📩 Message reçu dans background.js :", message); switch (message.action) { case "toggleAuth": { @@ -150,7 +185,7 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { } case "getDefinitionWiki": { if (message.selectedText && message.selectedText.trim() !== "") { - console.log("🌠Requête Wiktionnaire pour :", message.selectedText); + log("🌠Requête Wiktionnaire pour :", message.selectedText); const definition = await window.fetchWiktionaryDefinition(message.selectedText.trim()); browser.runtime.sendMessage({ action: "fetchWiktionaryDefinitionResponse", @@ -171,7 +206,7 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { break; } case "authStatusChanged": { - console.log("🔄 Mise à jour de l'état d'authentification :", message.isLoggedIn); + log("🔄 Mise à jour de l'état d'authentification :", message.isLoggedIn); break; } case "saveToken": { @@ -192,30 +227,26 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { // Web Navigation : Injection de scripts et récupération du token // ───────────────────────────────────────────────────────────────────────────── browser.webNavigation.onCompleted.addListener(async (details) => { - if (!isExtensionActive) { - console.log("🚫 Extension désactivée, aucune injection script."); - return; - } const url = new URL(details.url); // Injection d'un popup d'instruction sur la page de login if (url.hostname === "prisms.lezinter.net" && url.pathname === "/fr/login") { - console.log("📘 Injection du popup d'instruction sur la page de login Prisms."); + log("📘 Injection du popup d'instruction sur la page de login Prisms."); showInstructionPopup(details); } // Récupération du token sur la page /balex if (url.hostname === "prisms.lezinter.net" && url.pathname === "/fr/headquarters/balex") { - console.log("🟢 Page /balex détectée. Tentative de récupération du token."); + log("🟢 Page /balex détectée. Tentative de récupération du token."); try { await new Promise(resolve => setTimeout(resolve, 3000)); await browser.tabs.executeScript(details.tabId, { code: ` (function() { - console.log("🔠Recherche du token..."); + log("🔠Recherche du token..."); const tokenElement = document.getElementById("accessToken") || document.getElementById("accesToken"); if (tokenElement) { const token = tokenElement.innerText.trim(); - console.log("🔠Token détecté :", token); + log("🔠Token détecté :", token); browser.runtime.sendMessage({ action: "saveToken", token }); } else { console.error("⌠Token introuvable."); @@ -236,7 +267,7 @@ browser.webNavigation.onCompleted.addListener(async (details) => { browser.webRequest.onBeforeRequest.addListener( function(details) { if (details.url === "https://prisms.lezinter.net/fr/headquarters/") { - console.log("🚀 Redirection automatique vers /balex."); + log("🚀 Redirection automatique vers /balex."); return { redirectUrl: AUTH_BALEX_URL }; } }, @@ -263,7 +294,7 @@ function showInstructionPopup(details) { popup.style.borderRadius = "10px"; popup.style.boxShadow = "0 2px 10px rgba(0, 0, 0, 0.3)"; popup.style.zIndex = "10000"; - popup.style.fontFamily = "Helvetica, sans-serif"; + popup.style.fontFamily = "Luciole"; popup.style.fontSize = "14px"; popup.style.width = "300px"; popup.style.textAlign = "center"; @@ -299,30 +330,61 @@ function showInstructionPopup(details) { // ───────────────────────────────────────────────────────────────────────────── // Initialisation du WebWorker // ───────────────────────────────────────────────────────────────────────────── - let worker = null; -// Fonction pour initialiser le WebWorker -// LOGS function initWorker() { if (!worker) { console.log("[Background] Initialisation du WebWorker..."); try { - worker = new Worker("src/workers/pyodide_worker.js"); - worker.onmessage = (event) => { - console.log("[Background] Message reçu du WebWorker :", event.data); - }; - worker.onerror = (error) => { - console.error("[Background] Erreur dans le WebWorker :", error); - }; - console.log("[Background] WebWorker initialisé avec succès."); + worker = new Worker(browser.runtime.getURL("src/workers/pyodide_worker.js")); + // Centralisation de l'écoute des messages et erreurs du Worker + worker.addEventListener("message", handleWorkerMessage); + worker.addEventListener("error", handleWorkerError); + console.log("[Background] WebWorker initialisé avec succès."); } catch (error) { - console.error("[Background] Échec de l'initialisation du WebWorker :", error); - }} + console.error("[Background] Échec de l'initialisation du WebWorker :", error); + } + } +} + +function handleWorkerMessage(event) { + const data = event.data; + console.log("[Background] Message du WebWorker :", data); + + switch (data.type) { + case "pyodide-simplemma": + if (data.status === "success") { + console.log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); + browser.storage.local.set({ pyodideSimplemmaReady: true }); + checkAndUpdateTracking(); + } + break; + case "update-frequencies": + console.log("[Background] Mise à jour des fréquences :", data.frequencies); + notifyAllTabs({ command: "update-frequencies", frequencies: data.frequencies }); + // Si un port stats est connecté, vous pouvez lui transmettre également : + // port.postMessage({ command: "update-frequencies", frequencies: data.frequencies }); + browser.storage.local.set({ lemmaFrequencies: data.frequencies }); + break; + case "threshold-exceeded": + console.log("[Background] Mots dépassant le seuil :", data.wordsAboveThreshold); + notifyAllTabs({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); + break; + case "word-added": + console.log(`[Background] Mot ajouté : '${data.word}' dans '${data.language}' (Lexiques: ${data.lexicons})`); + break; + default: + console.warn("[Background] Message non traité du Worker :", data); + break; } +} -//Initialisation du worker -initWorker() +function handleWorkerError(error) { + console.error("[Background] Erreur dans le WebWorker :", error); +} + +// Initialisation du worker dès le démarrage +initWorker(); // ───────────────────────────────────────────────────────────────────────────── // Écoute des messages de la popup et transmission au WebWorker @@ -335,152 +397,162 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { } if (message.command === "toggle-stats") { - console.log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); - //Valeur du storage : actif - const { isActive } = message; - await browser.storage.local.set({ isTrackingActive: isActive }); - //Si on désactive les stats, on met aussi la valeur autoAdd false - if (!isActive) { - await browser.storage.local.set({ autoAdd: false }); - //console.log("[Background] autoAdd désactivé car les statistiques ont été désactivées."); - //Vérifier et activer le tracking des stats - checkAndUpdateTracking(); - } + console.log(`[Background] Statistiques ${message.isActive ? "activées" : "désactivées"}`); + const { isActive } = message; + await browser.storage.local.set({ isTrackingActive: isActive }); + if (!isActive) { + await browser.storage.local.set({ autoAdd: false }); + } + checkAndUpdateTracking(); } - + if (message.command === "pyodide-simplemma") { console.log("[Background] Demande d'initialisation de Pyodide et Simplemma..."); worker.postMessage({ command: "pyodide-simplemma" }); } + return true; +}); + + - // if (message.command === "process-text") { - // console.log("[Background] Envoi du texte au Worker pour traitement :", message.text); - // worker.postMessage({ command: "process-text", text: message.text }); - - // // // Récupérer les préférences utilisateur depuis le stockage local - // // const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ - // // "accessToken", "trackedLanguages", "threshold", "autoAdd" - // // ]) || { trackedLanguages: [], threshold: 10, autoAdd: false }; - - // // // Envoyer les données au Worker - // // worker.postMessage({ - // // type: "process-text", - // // text: message.text, - // // accessToken, - // // trackedLanguages, - // // threshold, - // // autoAdd - // // }); - // } - return true; +// ───────────────────────────────────────────────────────────────────────────── +// Envoi des données au WebWorker : lexiques personnels +// ───────────────────────────────────────────────────────────────────────────── + +// Charger et envoyer les lexiques au worker à la connexion +browser.storage.onChanged.addListener(async (changes, area) => { + if (area === "local" && changes.accessToken) { + console.log("Token mis à jour, récupération des lexiques..."); + sendLexiconsToWorker(); + sendAuthTokenToWorker(); + } }); -// Charger les fréquences stockées au démarrage +async function sendLexiconsToWorker() { + const { accessToken } = await browser.storage.local.get("accessToken"); + if (!accessToken) { + console.warn("Aucun token disponible, impossible de récupérer les lexiques."); + return; + } + + console.log("Récupération des lexiques de l'utilisateur..."); + const lexicons = await getLexicons(accessToken); + + // Filtrage : Ne garder que les lexiques de catégorie "User" + const userLexicons = lexicons.filter(lexicon => lexicon.category === "User"); + + if (Array.isArray(userLexicons) && userLexicons.length > 0) { + await browser.storage.local.set({ lexicons: userLexicons }); + console.log("Lexiques utilisateur stockés :", userLexicons); + + console.log("[Background] Envoi des lexiques utilisateur au Worker..."); + if (worker) { + worker.postMessage({ command: "update-lexicons", lexicons: userLexicons }); + console.log("[Background] Envoi des lexiques utilisateur réussi !"); + } + } else { + console.warn("Aucun lexique utilisateur trouvé."); + } +} + +// Charger et envoyer le token au worker à la connexion +async function sendAuthTokenToWorker() { + if (!worker) { + console.warn("âš ï¸ Worker non initialisé. Impossible d'envoyer le token."); + return; + } + + const { accessToken } = await browser.storage.local.get("accessToken"); + if (!accessToken) { + console.warn("🚨 Aucun token disponible. Le worker ne pourra pas interagir avec l’API."); + return; + } + + console.log("🔑 Envoi du token au Worker..."); + worker.postMessage({ command: "update-auth-token", accessToken }); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Stoplists : Chargement et envoi au Worker +// ───────────────────────────────────────────────────────────────────────────── +let stoplistFr = []; + +function loadStoplist() { + fetch(browser.runtime.getURL("stoplist_fr.txt")) + .then(response => response.text()) + .then(text => { + stoplistFr = text.split("\n").map(word => word.trim()); + console.log("[Background] Stoplist chargée :", stoplistFr); + sendStoplistToWorker(); + }) + .catch(error => console.error("[Background] Erreur lors du chargement de la stoplist :", error)); +} + +function sendStoplistToWorker() { + console.log("[Background] Envoi de la stoplist au Worker..."); + worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); +} + +browser.runtime.onStartup.addListener(loadStoplist); +browser.runtime.onInstalled.addListener(loadStoplist); + + +// ───────────────────────────────────────────────────────────────────────────── +// Chargement et sauvegarde des fréquences stockées +// ───────────────────────────────────────────────────────────────────────────── async function loadStoredFrequencies() { const { storedFrequencies } = await browser.storage.local.get("storedFrequencies"); return storedFrequencies || {}; } -// Initialiser le stockage au démarrage let storedFrequencies = {}; loadStoredFrequencies().then(frequencies => { - storedFrequencies = frequencies; - console.log("[Background] Fréquences initialisées :", storedFrequencies); + storedFrequencies = frequencies; + console.log("[Background] Fréquences initialisées :", storedFrequencies); }); -// ───────────────────────────────────────────────────────────────────────────── -// Écouter les réponses du WebWorker -// ───────────────────────────────────────────────────────────────────────────── -worker.onmessage = async (event) => { - console.log("[Background] Message du WebWorker :", event.data); - - //Si Pyodide et Simplemma sont prêts : AJOUTER UNE INFO VISUELLE - if (event.data.type === "pyodide-simplemma" && event.data.status === "success") { - console.log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); - // Stocker l'information dans le localStorage - await browser.storage.local.set({ pyodideSimplemmaReady: true }); - //Activer le tracking des stats - checkAndUpdateTracking(); - } - - - // if (event.data.type === "update-frequencies") { - // console.log("test") - // const { lang, frequencies } = data; - // console.log(`[Background] TEST LANGUE DETECTEE : ${lang}`); - // } - - //Stockage des fréquences - // if (event.data.type === "save-frequencies") { - // cconsole.log("[Background] Sauvegarde des fréquences :", event.data.frequencies); - // storedFrequencies = event.data.frequencies; - // try { - // await browser.storage.local.set({ storedFrequencies }); - // console.log("[Background] Fréquences sauvegardées avec succès."); - // } catch (error) { - // console.error("[Background] Erreur lors de la sauvegarde des fréquences :", error); - // } - // } -}; - // ───────────────────────────────────────────────────────────────────────────── // Statistiques : Vérification et activation/désactivation du tracking // ───────────────────────────────────────────────────────────────────────────── async function checkAndUpdateTracking() { const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); - if (isTrackingActive && pyodideSimplemmaReady) { - console.log("[Background] Activation du tracking."); - notifyAllTabs({ command: "activate-stats" }); + console.log("[Background] Activation du tracking."); + notifyAllTabs({ command: "activate-stats" }); } else { - console.log("[Background] Désactivation du tracking."); - notifyAllTabs({ command: "deactivate-stats" }); + console.log("[Background] Désactivation du tracking."); + notifyAllTabs({ command: "deactivate-stats" }); } } // ───────────────────────────────────────────────────────────────────────────── // Statistiques : Gestion des onglets // ───────────────────────────────────────────────────────────────────────────── -// Fonction pour envoyer des messages à TOUS les onglets async function notifyAllTabs(message) { browser.tabs.query({}).then((tabs) => { - tabs.forEach((tab) => { - //console.log(`[Background] Tentative d'envoi à l'onglet ${tab.id}`); - browser.tabs.sendMessage(tab.id, message) - .catch((error) => console.warn(`[Background] Impossible d'envoyer un message à l'onglet ${tab.id} : ${error}`)); - }); + tabs.forEach((tab) => { + browser.tabs.sendMessage(tab.id, message) + .catch((error) => console.warn(`[Background] Impossible d'envoyer un message à l'onglet ${tab.id} : ${error}`)); + }); }); } // ───────────────────────────────────────────────────────────────────────────── -// Statistiques : Écoute des modifications du stockage et mise à jour du tracking +// Écoute du changement des préférences et envoi des valeurs au Worker // ───────────────────────────────────────────────────────────────────────────── browser.storage.onChanged.addListener(async (changes, area) => { if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { checkAndUpdateTracking(); } - if (area === "local" && (changes.accessToken || changes.threshold || changes.trackedLanguages || changes.autoAdd)) { console.log("[Background] Mise à jour des préférences détectée."); - - // Récupérer les nouvelles valeurs depuis le local storage - const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ + const { accessToken, trackedLanguages, threshold, autoAdd} = await browser.storage.local.get([ "accessToken", "trackedLanguages", "threshold", "autoAdd" ]); - - // Vérifier si on est bien connecté const isAuthenticated = !!accessToken; - // console.log("[Background] Envoi des préférences au Worker :", { - // isAuthenticated, - // trackedLanguages, - // threshold, - // autoAdd - // }); - - // Envoyer les nouvelles préférences au Worker worker.postMessage({ command: "update-preferences", isAuthenticated, @@ -491,99 +563,49 @@ browser.storage.onChanged.addListener(async (changes, area) => { } }); - // ───────────────────────────────────────────────────────────────────────────── -// Écoute l'inscription de stats.js +// Écoute de l'inscription de stats.js // ───────────────────────────────────────────────────────────────────────────── browser.runtime.onMessage.addListener(async (message, sender) => { if (message.command === "register-stats-script") { - console.log("[Background] stats.js s'est enregistré."); - const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); - if (isTrackingActive && pyodideSimplemmaReady) { - browser.tabs.sendMessage(sender.tab.id, { command: "activate-stats" }) - .catch((error) => console.warn(`[Background] Impossible d'envoyer activate-stats à ${sender.tab.id} : ${error}`)); - } + console.log("[Background] stats.js s'est enregistré."); + const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); + if (isTrackingActive && pyodideSimplemmaReady) { + browser.tabs.sendMessage(sender.tab.id, { command: "activate-stats" }) + .catch((error) => console.warn(`[Background] Impossible d'envoyer activate-stats à ${sender.tab.id} : ${error}`)); + } } }); // ───────────────────────────────────────────────────────────────────────────── -// Statistiques : Créer une liaison entre stats.js et le Worker +// Connexion entre stats.js et le Worker via un port dédié // ───────────────────────────────────────────────────────────────────────────── -// Gestion des connexions des scripts (ex: stats.js) browser.runtime.onConnect.addListener((port) => { console.log("[Background] Connexion établie :", port.name); - if (port.name === "stats-worker-port") { - // Redirige les messages de stats.js vers pyodide-worker.js - port.onMessage.addListener((message) => { - console.log("[Background] Message reçu de stats.js :", message); - worker.postMessage(message); - - }); + // Redirige les messages de stats.js vers le Worker + port.onMessage.addListener((message) => { + console.log("[Background] Message reçu de stats.js :", message); + worker.postMessage(message); + }); - // Gérer les messages en retour du Worker vers stats.js - worker.onmessage = async (event) => { - console.log("[Background] Message du Worker :", event.data); - - //Mà j des fréquences - if (event.data.type === "update-frequencies") { - // const { lang, frequencies } = event.data; - const frequencies = event.data.frequencies; - //console.log("[Background] Mise à jour des fréquences :", frequencies); - //console.log(`[Background] Mise à jour des fréquences pour la langue : ${lang}`); - notifyAllTabs({ command: "update-frequencies", frequencies: event.data.frequencies }); - port.postMessage({ command: "update-frequencies", frequencies: event.data.frequencies }); - - // Enregistrer les fréquences dans le storage - browser.storage.local.set({ lemmaFrequencies: event.data.frequencies }) - } - - if (event.data.type === "threshold-exceeded") { - const wordsAboveThreshold = event.data.wordsAboveThreshold; - if (wordsAboveThreshold && Object.keys(wordsAboveThreshold).length > 0) { - console.log("[Background] Mots dépassant le seuil :", wordsAboveThreshold); - - // Envoyer ces mots à stats.js pour affichage ou autre action - notifyAllTabs({ - command: "threshold-exceeded", - wordsAboveThreshold: wordsAboveThreshold - }); - //Envoi à stats.js via le port - port.postMessage({ command: "threshold-exceeded", wordsAboveThreshold: wordsAboveThreshold }); - - } + // Transmettre les réponses du Worker à stats.js via le port + worker.addEventListener("message", (event) => { + const data = event.data; + if (data.type === "update-frequencies") { + port.postMessage({ command: "update-frequencies", frequencies: data.frequencies }); } + if (data.type === "threshold-exceeded") { + port.postMessage({ command: "threshold-exceeded", wordsAboveThreshold: data.wordsAboveThreshold }); + } + }); } -} }); + // ───────────────────────────────────────────────────────────────────────────── -// Stoplists (à modifier) +// (Code commenté concernant l'activation/désactivation de l'analyse) // ───────────────────────────────────────────────────────────────────────────── -// Charger la stoplist au démarrage -let stoplistFr = []; - -function loadStoplist() { - fetch(browser.runtime.getURL("stoplist_fr.txt")) - .then(response => response.text()) - .then(text => { - stoplistFr = text.split("\n").map(word => word.trim()); // Supprimer les espaces et \r éventuels - console.log("[Background] Stoplist chargée :", stoplistFr); - - // Envoyer la stoplist au worker dès son chargement - sendStoplistToWorker(); - }) - .catch(error => console.error("[Background] Erreur lors du chargement de la stoplist :", error)); -} - -// Envoyer la stoplist au worker -function sendStoplistToWorker() { - console.log("[Background] Envoi de la stoplist au Worker..."); - worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); - -} - -// Charger la stoplist au démarrage de l'extension -browser.runtime.onStartup.addListener(loadStoplist); -browser.runtime.onInstalled.addListener(loadStoplist); +// async function initializeExtensionState() { ... } +// initializeExtensionState(); diff --git a/src/utils/api.js b/src/utils/api.js index 154070497d58410ef996ea2e4739cd8143b11c62..952d5a46a66a2f41493e4e4283a7a257c1d0b425 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,5 +1,6 @@ -console.log("✅ api.js chargé correctement"); +log("✅ api.js chargé correctement"); window.authToken = null; + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Sélection de texte sur la page // ───────────────────────────────────────────────────────────────────────────── @@ -30,9 +31,9 @@ async function callApi(url, authToken = null, method = 'GET', data = null) { if (authToken) headers.Authorization = `Bearer ${authToken}`; const fetchOptions = { method, headers }; - console.log("Envoi de la requête vers :", url); + log("Envoi de la requête vers :", url); if (data) { - console.log("Body JSON :", JSON.stringify(data, null, 2)); + log("Body JSON :", JSON.stringify(data, null, 2)); fetchOptions.body = JSON.stringify(data); } @@ -48,39 +49,52 @@ async function callApi(url, authToken = null, method = 'GET', data = null) { } } - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Récupération des lexiques de l'utilisateur // ───────────────────────────────────────────────────────────────────────────── /** - * Récupère les lexiques pour l’utilisateur 4, + * Récupère les lexiques de l’utilisateur, * en langue par défaut "fr". * * @param {string} authToken - Le token d'authentification. - * @param {string} [language='fr'] - La langue (optionnel). * @returns {Promise<any[]>} - Liste des lexiques trouvés. */ -async function getLexicons(authToken, language = 'fr') { - const userId = 4; - const baseUrl = "https://babalex.lezinter.net/api/lexicon/search"; - const url = `${baseUrl}?user_id=${userId}&language=${encodeURIComponent(language)}`; +async function getLexicons(authToken) { + // Vérifier si les lexiques sont déjà en cache + const { lexicons } = await browser.storage.local.get("lexicons"); + if (Array.isArray(lexicons) && lexicons.length > 0) { + console.log("📌 Lexiques récupérés depuis le cache local :", lexicons); + return lexicons; // Retourne les lexiques en cache pour éviter une requête API inutile + } - return callApi(url, authToken); + const url = "https://babalex.lezinter.net/api/lexicon/search"; + + try { + const response = await callApi(url, authToken); + console.log("✅ Réponse de getLexicons :", response); + + if (Array.isArray(response) && response.length > 0) { + await browser.storage.local.set({ lexicons: response }); // Stocker en cache + } + + return response; + } catch (error) { + return []; + } } + + /** - * Récupère tous les lexiques pour l’utilisateur 4 + * Récupère tous les lexiques (catégories) de l’utilisateur */ async function getAllCategoriesLexicons(authToken) { const categories = ["User", "Group", "Zero", "New words"]; - const userId = 4; - const groupId = 1; - const promises = categories.map(async (category) => { const baseUrl = "https://babalex.lezinter.net/api/lexicon/search"; - const url = `${baseUrl}?user_id=${userId}&group_id=${groupId}&category=${encodeURIComponent(category)}&language=fr`; + const url = `${baseUrl}?&category=${encodeURIComponent(category)}&language=fr`; try { return await callApi(url, authToken); @@ -94,7 +108,7 @@ async function getAllCategoriesLexicons(authToken) { const resultsByCategory = await Promise.all(promises); const combined = resultsByCategory.flat(); - console.log("✅ Lexiques récupérés (toutes catégories confondues) :", combined); + log("✅ Lexiques récupérés (toutes catégories confondues) :", combined); return combined; } catch (error) { console.error("⌠Erreur lors de la récupération multi-catégories :", error); @@ -102,31 +116,6 @@ async function getAllCategoriesLexicons(authToken) { } } -/** - * Récupère des lexiques personnels "user" de l'utilisateur - */ -async function getUserLexicons(authToken) { - try { - if (!authToken) { - console.warn("âš ï¸ Aucun token disponible, récupération des lexiques annulée."); - return []; - } - - const lexicons = await callApi("https://babalex.lezinter.net/api/user/lexicons", authToken); - - if (!Array.isArray(lexicons) || lexicons.length === 0) { - console.warn("âš ï¸ Aucun lexique personnel trouvé."); - return []; - } - - console.log("📚 Lexiques personnels récupérés :", lexicons); - return lexicons; - } catch (error) { - console.error("⌠Erreur lors de la récupération des lexiques personnels :", error); - return []; - } -} - // ───────────────────────────────────────────────────────────────────────────── // â–Œ Récupération des entrées d'un lexique @@ -144,13 +133,9 @@ async function getLexiconEntries(authToken, lexiconId) { * Récupère toutes les graphies présentes dans tous les lexiques de l'utilisateur. */ async function getAllLexiconWords(authToken) { - const searchUrl = "https://babalex.lezinter.net/api/lexicon/search" - + "?user_id=4" - + "&language=fr"; - try { // 1) Récupération de la liste des lexiques - const lexicons = await callApi(searchUrl, authToken); + const lexicons = await getLexicons(authToken); if (!Array.isArray(lexicons) || lexicons.length === 0) { console.warn("âš ï¸ Aucun lexique retourné par l’API pour ces paramètres."); @@ -173,7 +158,7 @@ async function getAllLexiconWords(authToken) { allGraphiesByLexicon[lexiconName] = allGraphies; } - console.log("✅ Toutes les graphies récupérées :", allGraphiesByLexicon); + log("✅ Toutes les graphies récupérées :", allGraphiesByLexicon); return allGraphiesByLexicon; } catch (error) { console.error("⌠Erreur lors de la récupération des graphies des lexiques :", error); @@ -201,7 +186,7 @@ async function getWiktionaryDefinition(word) { const page = pages ? Object.values(pages)[0] : null; const definition = page?.extract?.trim() || "Aucune définition trouvée."; - console.log(`📖 Définition trouvée pour '${word}':`, definition); + log(`📖 Définition trouvée pour '${word}':`, definition); return [definition]; } catch (error) { console.error("Erreur lors de la récupération du Wiktionnaire :", error); @@ -237,7 +222,7 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { force, target_lex: lexiconIds }; - + log("Body envoyé à AddWord :", body); return callApi(url, authToken, "POST", body); } @@ -249,10 +234,10 @@ async function AddWord(authToken, selectedWord, lexiconIds, force = false) { window.callApi = callApi; window.getLexicons = getLexicons; -console.log("getLexicons exposée, type:", typeof window.getLexicons); window.getAllCategoriesLexicons = getAllCategoriesLexicons; window.getLexiconEntries = getLexiconEntries; window.getAllLexiconWords = getAllLexiconWords; window.getWiktionaryDefinition = getWiktionaryDefinition; window.AddWord = AddWord; + diff --git a/src/utils/stats.js b/src/utils/stats.js index 788f32cfd16adb801db9243cd66417e68adba474..1f7b7994c8041f861f20d9a8ae915a7cde535f74 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -20,9 +20,17 @@ if (message.command === "threshold-exceeded") { console.log("[Stats] Mots dépassant le seuil :", message.wordsAboveThreshold); let alertMessage = "Nouveaux mots dépassant le seuil :\n"; - for (const [lang, words] of Object.entries(message.wordsAboveThreshold)) { + if (typeof message.wordsAboveThreshold !== "object" || message.wordsAboveThreshold === null) { + return; + } + for (const [lang, words] of Object.entries(message.wordsAboveThreshold)) { + if (!Array.isArray(words)) { + continue; + } + alertMessage += `\n🔹 ${lang.toUpperCase()} : ${words.join(", ")}`; } + alert(alertMessage); } }); @@ -248,29 +256,86 @@ } // Indice visuel des statistiques actives + function injectBorder() { + const css = ` + #border-svg { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + pointer-events: none; + z-index: 999999; + } + @keyframes dashAnimation { + from { + stroke-dashoffset: 400; + } + to { + stroke-dashoffset: 0; + } + } + `; + const style = document.createElement("style"); + style.textContent = css; + document.head.appendChild(style); + + const svgNS = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(svgNS, "svg"); + svg.setAttribute("id", "border-svg"); + svg.setAttribute("viewBox", "0 0 100 100"); + svg.setAttribute("preserveAspectRatio", "none"); + + const defs = document.createElementNS(svgNS, "defs"); + const linearGradient = document.createElementNS(svgNS, "linearGradient"); + linearGradient.setAttribute("id", "border-gradient"); + linearGradient.setAttribute("x1", "0%"); + linearGradient.setAttribute("y1", "0%"); + linearGradient.setAttribute("x2", "100%"); + linearGradient.setAttribute("y2", "0%"); + const stop1 = document.createElementNS(svgNS, "stop"); + stop1.setAttribute("offset", "0%"); + stop1.setAttribute("stop-color", "#DDD6F3"); + const stop2 = document.createElementNS(svgNS, "stop"); + stop2.setAttribute("offset", "50%"); + stop2.setAttribute("stop-color", "#784BA0"); + const stop3 = document.createElementNS(svgNS, "stop"); + stop3.setAttribute("offset", "100%"); + stop3.setAttribute("stop-color", "#2B86C5"); + + linearGradient.appendChild(stop1); + linearGradient.appendChild(stop2); + linearGradient.appendChild(stop3); + defs.appendChild(linearGradient); + svg.appendChild(defs); + + const rect = document.createElementNS(svgNS, "rect"); + rect.setAttribute("x", "0.5"); + rect.setAttribute("y", "0.5"); + rect.setAttribute("width", "98"); + rect.setAttribute("height", "98"); + rect.setAttribute("fill", "none"); + rect.setAttribute("stroke", "url(#border-gradient)"); + rect.setAttribute("stroke-width", "0.5"); + rect.setAttribute("stroke-dasharray", "200 200"); + rect.setAttribute("stroke-dashoffset", "400"); + rect.style.animation = "dashAnimation 8s ease-in-out infinite"; + + svg.appendChild(rect); + document.body.appendChild(svg); + } + + // Fonction pour ajouter la bordure function addViewportBorder() { - const existingBorder = document.getElementById("viewport-border"); - if (existingBorder) { - existingBorder.remove(); + if (!document.getElementById("border-svg")) { + injectBorder(); } - const border = document.createElement("div"); - border.id = "viewport-border"; - border.style.position = "fixed"; - border.style.top = "0"; - border.style.left = "0"; - border.style.width = "100vw"; - border.style.height = "100vh"; - border.style.boxSizing = "border-box"; - border.style.border = "8px solid red"; - border.style.pointerEvents = "none"; - border.style.zIndex = "999999"; - document.body.appendChild(border); } - + // Fonction pour retirer la bordure function removeViewportBorder() { - const border = document.getElementById("viewport-border"); - if (border) { - border.remove(); + const svg = document.getElementById("border-svg"); + if (svg) { + svg.remove(); } } })(); diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index a85f4088bb3a0ef4b40aac32b37b1d7490ce778d..0d09462a9e4b42a1df433ec8a3e6cebd14ddb9b1 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -1,239 +1,308 @@ +console.log("✅ pyodide_worker.js chargé avec succès !"); + // URL de la version Pyodide la plus récente const LATEST_BASE_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full/"; let pyodide = null; -let pyodideLoaded = false; // Variable indiquant si Pyodide est en mémoire -let simplemmaLoaded = false; // Variable indiquant si simplemma est déjà installé -let storedFrequencies = {}; // Stockage des fréquences accumulées - +let pyodideLoaded = false; // Indique si Pyodide est en mémoire +let simplemmaLoaded = false; // Indique si simplemma est déjà installé +let storedFrequencies = {}; // Stockage des fréquences accumulées -let autoAddEnabled = false; // Ajout automatique désactivé par défaut -let isAuthenticated = false; // Non connecté par défaut -let userThreshold = 10; // Seuil par défaut -let trackedLanguages = []; // Aucune langue suivie par défaut -// Stockage des mots déjà signalés (évite d'afficher plusieurs fois la même alerte) -let notifiedWords = {}; // -let stoplistFr = new Set(); // Stockage optimisé de la stoplist +let autoAddEnabled = false; // Ajout automatique désactivé par défaut +let isAuthenticated = false; // Non connecté par défaut +let userThreshold = 10; // Seuil par défaut +let trackedLanguages = []; // Aucune langue suivie par défaut +let notifiedWords = {}; // Stockage des mots déjà signalés (pour éviter les doublons) +let stoplistFr = new Set(); // Stockage optimisé de la stoplist -// Écouteur des messages reçus du background script -self.onmessage = async (event) => { - const data = event.data; +let userLexicons = [] //Contient les lexiques et leurs ID (lexiques personnels) +let authToken = null; // Stockage local du token - console.log("[WebWorker] Message reçu du Background:", data); - // Initialisation unique de Pyodide puis de Simplemma - if (data.command === "pyodide-simplemma") { - if (pyodideLoaded && simplemmaLoaded) { - console.log("[Worker] Pyodide et Simplemma déjà chargés."); - self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); - return; - } - try { - if (!pyodideLoaded) { - console.log("[Worker] Chargement de Pyodide..."); - importScripts(`${LATEST_BASE_URL}pyodide.js`); - pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); - await pyodide.loadPackage("lzma"); - await pyodide.loadPackage("micropip"); - pyodideLoaded = true; - console.log("[Worker] Pyodide chargé avec succès !"); - } - - if (!simplemmaLoaded) { - console.log("[Worker] Installation de simplemma..."); - await pyodide.runPythonAsync(` - import micropip - import re - - try: - print("Installation de simplemma...") - await micropip.install("simplemma") - print("Installation réussie.") - - import simplemma - print("simplemma importé avec succès.") - - def tokenize(text): - return re.findall(r"\\b\\w+\\b", text.lower()) - - #Phrase de test pour vérifier l'installation de simplemma - print("Phrase de test:") - phrase = "Le chat mange la pomme" - - tokens = tokenize(phrase) - print("Tokens extraits :", tokens) - - lemmatized_tokens = [simplemma.lemmatize(token, lang="fr") for token in tokens] - print("Tokens lemmatisés :", lemmatized_tokens) - - lemmatized_tokens - - except Exception as e: - print("Erreur lors de l'installation ou de l'importation :", e) - e - - `); - simplemmaLoaded = true; - console.log("[Worker] Simplemma installé avec succès !"); - } - // Envoyer confirmation au background script - self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); - - } catch (error) { - console.error("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); - self.postMessage({ type: "pyodide-simplemma", status: "error", message: error.toString() }); - } +// Écouteur des messages reçus du background script +self.onmessage = async (event) => { + const data = event.data; + console.log("[WebWorker] Message reçu du Background:", data); + + if (data.command === "pyodide-simplemma") { + if (pyodideLoaded && simplemmaLoaded) { + console.log("[Worker] Pyodide et Simplemma déjà chargés."); + self.postMessage({ type: "pyodide-simplemma", status: "already_loaded", message: "Pyodide et Simplemma déjà en mémoire" }); + return; } - - // Traitement du texte (envoyé par stats.js) - if (data.command === "process-text") { - console.log("[Worker] Texte reçu pour analyse :", data.text); - + try { + if (!pyodideLoaded) { + console.log("[Worker] Chargement de Pyodide..."); try { - const stoplistArray = Array.from(stoplistFr); - console.log(stoplistArray) - const result = await pyodide.runPythonAsync(` - import json - import re - import simplemma - from simplemma import langdetect - - def detect_language(text): - lang_scores = simplemma.langdetect(text, lang=("fr", "en", "es", "de", "it", "pt")) - return lang_scores[0][0] if lang_scores else "unk" - - def preprocess(text): - contractions = ["l'", "d'", "j'", "c'", "s'", "t'", "qu'", "m'", "n'"] - for contraction in contractions: - text = re.sub(fr"{contraction}", f"{contraction} ", text) - return text - - def tokenize(text): - return re.findall(r"\\b[a-zA-ZÀ-ÿ'-]+\\b", text.lower()) - - # Chargement de la stoplist pour le français - stoplist = set(json.loads('${JSON.stringify(stoplistArray)}')) - #print("Stoplist utilisée pour le filtrage :", stoplist) - - - text = """${data.text.replace(/\"/g, '\\"')}""" - detected_lang = detect_language(text) - - if detected_lang == "unk": - detected_lang = "other" - - tokens = tokenize(text) - - if detected_lang == "fr": - filtered_tokens = [token.lower().strip() for token in tokens if token.lower().strip() not in stoplist] - #print(f"Tokens avant filtrage ({len(tokens)}) :", tokens) # Debug - #print(f"Tokens après filtrage ({len(filtered_tokens)}) :", filtered_tokens) # Debug - tokens = filtered_tokens # Appliquer le filtrage stoplist - - - lemmatized_tokens = [simplemma.lemmatize(token, lang=detected_lang) for token in tokens] - - freq = {} - for token in lemmatized_tokens: - if token not in stoplist: # Exclure les mots de la stoplist avant comptage - freq[token] = freq.get(token, 0) + 1 - - - json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) - `); - - const parsedResult = JSON.parse(result); - const detectedLang = parsedResult.lang; - //console.log("[Worker] Résultat du traitement :", parsedResult); - - // Vérifier si la langue existe dans storedFrequencies, sinon l'initialiser - if (!storedFrequencies[detectedLang]) { - storedFrequencies[detectedLang] = {}; - } - - // Mise à jour des fréquences stockées - for (const [word, count] of Object.entries(parsedResult.frequencies)) { - storedFrequencies[detectedLang][word] = (storedFrequencies[detectedLang][word] || 0) + count; - } - - // Envoyer le message update-frequencies => mà j des fréquences et sauvegarde dans le local storage - self.postMessage({ - type: "update-frequencies", - frequencies: storedFrequencies - }); - - //Vérifier le seuil uniquement si l'utilisateur a coché l'option - if (autoAddEnabled) { - checkThreshold(detectedLang) - } - - } catch (error) { - console.error("[Worker] Erreur dans l'analyse du texte :", error); + importScripts(`${LATEST_BASE_URL}pyodide.js`); + } catch (err) { + console.error("[Worker] Erreur lors de l'import de pyodide.js :", err); + self.postMessage({ type: "pyodide-simplemma", status: "error", message: err.toString() }); + return; } + pyodide = await loadPyodide({ indexURL: LATEST_BASE_URL }); + await pyodide.loadPackage("lzma"); + await pyodide.loadPackage("micropip"); + pyodideLoaded = true; + console.log("[Worker] Pyodide chargé avec succès !"); + } + + if (!simplemmaLoaded) { + console.log("[Worker] Installation de simplemma..."); + + // On encapsule la logique dans une fonction asynchrone pour faciliter l'usage d'await + await pyodide.runPythonAsync(` +import micropip +import asyncio + +async def main(): + print("Installation de simplemma...") + await micropip.install("simplemma") + print("Installation réussie.") + import simplemma + print("simplemma importé avec succès.") + # Test simple : extraction de tokens et lemmatisation + import re + def tokenize(text): + return re.findall(r"\\b\\w+\\b", text.lower()) + phrase = "Le chat mange la pomme" + tokens = tokenize(phrase) + print("Tokens extraits :", tokens) + lemmatized_tokens = [simplemma.lemmatize(token, lang="fr") for token in tokens] + print("Tokens lemmatisés :", lemmatized_tokens) + return lemmatized_tokens + +await main() + `); + simplemmaLoaded = true; + console.log("[Worker] Simplemma installé avec succès !"); + } + // Envoyer confirmation au background script + self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); + } catch (error) { + console.error("[Worker] Erreur lors du chargement de Pyodide ou Simplemma :", error); + self.postMessage({ type: "pyodide-simplemma", status: "error", message: error.toString() }); } - - if (data.command === "update-preferences") { - userThreshold = data.threshold; - trackedLanguages = data.trackedLanguages; - autoAddEnabled = data.autoAdd; - isAuthenticated = data.isAuthenticated; - - console.log("[Worker] Mise à jour des préférences :", { - userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated - }); + } + + // --- Traitement du texte envoyé par stats.js --- + if (data.command === "process-text") { + if (!pyodideLoaded) { + console.log("[Worker] Pyodide non chargé."); + self.postMessage({ type: "process-text", status: "error", message: "Pyodide pas encore chargé" }); + return; } - - if (data.command === "update-stoplist") { - stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); - console.log("[Worker] Stoplist FR mise à jour :", stoplistFr); + console.log("[Worker] Texte reçu pour analyse :", data.text); + try { + const stoplistArray = Array.from(stoplistFr); + console.log("Stoplist utilisée :", stoplistArray); + const result = await pyodide.runPythonAsync(` +import json +import re +import simplemma +from simplemma import langdetect + +def detect_language(text): + lang_scores = simplemma.langdetect(text, lang=("fr", "en", "es", "de", "it", "pt")) + return lang_scores[0][0] if lang_scores else "unk" + +def tokenize(text): + return re.findall(r"\\b[a-zA-ZÀ-ÿ'-]+\\b", text.lower()) + +# Chargement de la stoplist +stoplist = set(json.loads('${JSON.stringify(stoplistArray)}')) + +text = """${data.text.replace(/\"/g, '\\"')}""" +detected_lang = detect_language(text) +if detected_lang == "unk": + detected_lang = "other" + +tokens = tokenize(text) +if detected_lang == "fr": + tokens = [token.lower().strip() for token in tokens if token.lower().strip() not in stoplist] + +lemmatized_tokens = [simplemma.lemmatize(token, lang=detected_lang) for token in tokens] + +freq = {} +for token in lemmatized_tokens: + if token not in stoplist: + freq[token] = freq.get(token, 0) + 1 + +json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) + `); + const parsedResult = JSON.parse(result); + const detectedLang = parsedResult.lang; + if (!storedFrequencies[detectedLang]) { + storedFrequencies[detectedLang] = {}; + } + for (const [word, count] of Object.entries(parsedResult.frequencies)) { + storedFrequencies[detectedLang][word] = (storedFrequencies[detectedLang][word] || 0) + count; + } + self.postMessage({ type: "update-frequencies", frequencies: storedFrequencies }); + if (autoAddEnabled) { + checkThreshold(detectedLang); + } + } catch (error) { + console.error("[Worker] Erreur dans l'analyse du texte :", error); + } + } + + if (data.command === "update-preferences") { + userThreshold = data.threshold; + trackedLanguages = data.trackedLanguages; + autoAddEnabled = data.autoAdd; + isAuthenticated = data.isAuthenticated; + console.log("[Worker] Mise à jour des préférences :", { userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated }); + } + + if (data.command === "update-lexicons") { + userLexicons = data.lexicons; + console.log("[Worker] Lexiques mis à jour :", userLexicons); } -}; -// ───────────────────────────────────────────────────────────────────────────── -// Vérification du seuil et notification (sans duplication) -// ───────────────────────────────────────────────────────────────────────────── -function checkThreshold(lang) { - if (!autoAddEnabled || !isAuthenticated) { - console.log("[Worker] Auto-Add désactivé ou utilisateur non connecté. Aucune vérification de seuil."); - return; + if (data.command === "update-auth-token") { + authToken = data.accessToken; + console.log("[Worker] 🔑 Token mis à jour :", authToken ? "Disponible" : "Aucun token reçu"); } - if (!trackedLanguages.includes(lang)) { - console.log(`[Worker] La langue ${lang} n'est pas suivie.`); - return; - } + if (data.command === "update-stoplist") { + stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); + console.log("[Worker] Stoplist FR mise à jour :", stoplistFr); + } +}; - console.log(`[Worker] Vérification des fréquences pour la langue ${lang}...`); +// --- Vérification du seuil et notification --- +let pendingWords = {}; // Stocker temporairement les mots en attente d'ajout +let addWordTimeout = null; // Timer pour regrouper les ajouts - let wordsAboveThreshold = {}; +async function checkThreshold(lang) { + if (!autoAddEnabled || !isAuthenticated) { + console.log("[Worker] Auto-Add désactivé ou utilisateur non connecté."); + return; + } + if (!trackedLanguages.includes(lang)) { + console.log(`[Worker] La langue ${lang} n'est pas suivie.`); + return; + } - // Vérifier chaque mot de la langue sélectionnée - if (storedFrequencies[lang]) { - const exceededWords = Object.entries(storedFrequencies[lang]) - .filter(([word, count]) => count >= userThreshold && !(notifiedWords[lang] && notifiedWords[lang].includes(word))) - .map(([word]) => word); + console.log(`[Worker] Vérification des fréquences pour la langue ${lang}...`); - if (exceededWords.length > 0) { - // Ajouter ces mots aux notifications pour éviter les doublons - if (!notifiedWords[lang]) { - notifiedWords[lang] = []; - } - notifiedWords[lang].push(...exceededWords); + if (!storedFrequencies[lang]) return; - wordsAboveThreshold[lang] = exceededWords; - } - } + const exceededWords = Object.entries(storedFrequencies[lang]) + .filter(([word, count]) => count >= userThreshold && !(notifiedWords[lang] && notifiedWords[lang].includes(word))) + .map(([word]) => word); - // Si des mots dépassent le seuil, notifier stats.js - if (Object.keys(wordsAboveThreshold).length > 0) { - console.log("[Worker] Nouveaux mots dépassant le seuil :", wordsAboveThreshold); + if (exceededWords.length === 0) return; - self.postMessage({ - type: "threshold-exceeded", - wordsAboveThreshold: wordsAboveThreshold - }); - } else { - console.log("[Worker] Aucun nouveau mot n'a dépassé le seuil."); + if (!notifiedWords[lang]) notifiedWords[lang] = []; + notifiedWords[lang].push(...exceededWords); + + console.log("Mots dépassant le seuil :", exceededWords); + self.postMessage({ type: "threshold-exceeded", wordsAboveThreshold: exceededWords }); + + // Stocker les mots détectés pour un ajout groupé + if (!pendingWords[lang]) pendingWords[lang] = []; + pendingWords[lang].push(...exceededWords); + + // Déclencher un envoi groupé après un délai (3 secondes) + if (!addWordTimeout) { + addWordTimeout = setTimeout(async () => { + await processPendingWords(); + }, 3000); + } +} + +//Traiter les ajouts groupés +async function processPendingWords() { + console.log("Traitement des mots à ajouter en lot..."); + + for (const lang in pendingWords) { + const words = pendingWords[lang]; + for (const word of words) { + await addWordToLexicon(lang, word); } -}; + } + + // Réinitialiser la file d'attente et le timeout + pendingWords = {}; + addWordTimeout = null; +} + + +async function addWordToLexicon(lang, word) { + if (!authToken) { + console.warn("Impossible d'ajouter le mot : Aucun token d’authentification."); + return; + } + + console.log(`Tentative d'ajout du mot '${word}' pour la langue '${lang}'`); + + // Trouver les lexiques correspondant à la langue détectée + const targetLexicons = userLexicons + .filter(lexicon => lexicon.language === lang && lexicon.category === "User") + .map(lexicon => lexicon.id); + + if (targetLexicons.length === 0) { + console.warn(`Aucun lexique trouvé pour la langue '${lang}'. Impossible d'ajouter '${word}'.`); + return; + } + + try { + console.log(`Envoi de '${word}' aux lexiques ${targetLexicons}...`); + await AddWord(authToken, word, targetLexicons); + + console.log(`Mot '${word}' ajouté avec succès !`); + + // Notifier le background de l'ajout réussi + self.postMessage({ type: "word-added", word, language: lang, lexicons: targetLexicons }); + } catch (error) { + console.error(`Erreur lors de l'ajout du mot '${word}':`, error); + } +} + +//TODO : trouver un moyen d'accéder à cette fonction depuis api.js +async function AddWord(authToken, selectedWord, lexiconIds, force = false) { + if (!authToken) { + console.error("Aucun token d’authentification fourni."); + return; + } + if (!selectedWord) { + console.error("Aucun mot spécifié pour l’ajout."); + return; + } + if (!Array.isArray(lexiconIds) || lexiconIds.length === 0) { + console.error("Aucun lexique sélectionné pour l’ajout."); + return; + } + + const url = "https://babalex.lezinter.net/api/entry/create"; + const body = { + graphy: selectedWord, + force, + target_lex: lexiconIds + }; + + console.log("Envoi de la requête API AddWord :", body); + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${authToken}` + }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + throw new Error(`Erreur API (${response.status}): ${response.statusText}`); + } + + console.log(`Mot '${selectedWord}' ajouté avec succès aux lexiques ${lexiconIds}`); + return await response.json(); + } catch (error) { + console.error(`Erreur lors de l'ajout du mot '${selectedWord}':`, error); + } +}