diff --git a/bin/build-extension.mjs b/bin/build-extension.mjs index 9e3c830..934b4b4 100644 --- a/bin/build-extension.mjs +++ b/bin/build-extension.mjs @@ -7,48 +7,79 @@ const args = process.argv.slice(2); const f = fileURLToPath(import.meta.url); const dirname = path.dirname(f); -args.forEach((browser) => { - if (["firefox", "chrome"].includes(browser)) { - fs.rmSync(`${dirname}/../dist/${browser}`, { - recursive: true, - force: true, - }); - fs.mkdirSync(`${dirname}/../dist/${browser}`, { recursive: true }); - fs.copySync( - `${dirname}/../src/manifest-${browser}.json`, - `${dirname}/../dist/${browser}/manifest.json`, - ); - fs.copySync( - `${dirname}/../src/popup`, - `${dirname}/../dist/${browser}/popup`, - ); - fs.copySync( - `${dirname}/../src/images`, - `${dirname}/../dist/${browser}/images`, +/** + * @param {"firefox" | "chrome"} browser + */ +async function buildForBrowser(browser) { + fs.rmSync(`${dirname}/../dist/${browser}`, { + recursive: true, + force: true, + }); + fs.mkdirSync(`${dirname}/../dist/${browser}`, { recursive: true }); + fs.copySync( + `${dirname}/../src/manifest-${browser}.json`, + `${dirname}/../dist/${browser}/manifest.json`, + ); + fs.copySync(`${dirname}/../src/popup`, `${dirname}/../dist/${browser}/popup`); + fs.copySync( + `${dirname}/../src/options`, + `${dirname}/../dist/${browser}/options`, + ); + fs.copySync( + `${dirname}/../src/images`, + `${dirname}/../dist/${browser}/images`, + ); + fs.copySync( + `${dirname}/../src/background`, + `${dirname}/../dist/${browser}/background`, + ); + fs.copySync( + `${dirname}/../src/common.js`, + `${dirname}/../dist/${browser}/common.js`, + ); + + const distPath = path.resolve(`${dirname}/../dist/${browser}`); + + await webExt.cmd.build( + { + sourceDir: distPath, + artifactsDir: `${dirname}/../web-ext-artifacts`, + overwriteDest: true, + filename: `ecoindex.fr-${browser}.zip`, + }, + { + shouldExitProgram: false, + }, + ); + + console.log("Build complete"); + + if (browser === "chrome") { + console.log( + "\nChrome (test local) : chrome://extensions → Mode développeur → « Charger l’extension non empaquetée ».", ); - fs.copySync( - `${dirname}/../src/background`, - `${dirname}/../dist/${browser}/background`, + console.log( + `Sélectionnez ce dossier (pas le .zip) :\n ${distPath}\n`, ); - fs.copySync( - `${dirname}/../src/common.js`, - `${dirname}/../dist/${browser}/common.js`, + console.log( + "Le fichier .zip sert au Chrome Web Store ou à une archive ; il ne convient pas au chargement non empaqueté.\n", ); + } +} - webExt.cmd - .build( - { - sourceDir: `${dirname}/../dist/${browser}`, - artifactsDir: `${dirname}/../web-ext-artifacts`, - overwriteDest: true, - filename: `ecoindex.fr-${browser}.zip`, - }, - { - shouldExitProgram: false, - }, - ) - .then(() => { - console.log("Build complete"); - }); +async function main() { + const browsers = args.filter((b) => ["firefox", "chrome"].includes(b)); + if (browsers.length === 0) { + console.error("Usage: node build-extension.mjs "); + process.exitCode = 1; + return; } + for (const browser of browsers) { + await buildForBrowser(/** @type {"chrome" | "firefox"} */ (browser)); + } +} + +main().catch((err) => { + console.error(err); + process.exitCode = 1; }); diff --git a/doc/PRIVACY-POLICY-FR.md b/doc/PRIVACY-POLICY-FR.md new file mode 100644 index 0000000..d2ca78e --- /dev/null +++ b/doc/PRIVACY-POLICY-FR.md @@ -0,0 +1,109 @@ +# POLITIQUE DE CONFIDENTIALITÉ DES UTILISATEURS + +## 1. Objet + +Les présentes Conditions Générales d’Utilisation (CGU) régissent l’utilisation de l’extension navigateur **Ecoindex Browser** (ci-après « l’Extension »). + +L’Extension permet d’afficher le score Ecoindex d’une page web en interrogeant un service backend dédié ([Ecoindex BFF](https://github.com/cnumr/EcoIndex_BFF) which is a Ecoindex official API middleware). + +--- + +## 2. Fonctionnement du service + +Lorsque l’Extension est activée sur une page web : + +- L’URL de la page courante est transmise au service backend Ecoindex BFF. +- Le service vérifie si un score Ecoindex existe déjà : + - en base de données, ou + - dans un cache existant. + +- Si un score existe, il est retourné immédiatement. +- Si aucun score n’est disponible : + - un calcul peut être effectué, + - le résultat est ensuite stocké de manière sécurisée. + +### Stockage des résultats + +Les résultats sont stockés sous forme chiffrée selon le principe suivant : + +- Clé : `ecoindex_sha1(url)` +- Valeur : score Ecoindex associé + +Aucune URL en clair n’est stockée dans ce mécanisme de cache. + +### 2.1 Permissions du navigateur (transparence) + +Le fichier manifeste de l’Extension peut contenir les déclarations suivantes, utilisées uniquement dans les conditions décrites ici : + +```json +{ + "permissions": ["activeTab", "storage"], + "host_permissions": [""] +} +``` + +- **`activeTab`** — Accorde un accès **temporaire** à l’onglet actif lorsque vous utilisez l’Extension (par exemple à l’ouverture de la popup). Elle sert à lire **l’URL de la page affichée** afin d’interroger le backend Ecoindex pour le résultat correspondant. Elle **ne** permet pas de lire en continu tous les onglets en arrière-plan. + +- **`storage`** — Sert à conserver **localement sur votre appareil** des données d’Extension : préférences et état d’interface de courte durée (par exemple le badge de la barre d’outils lié à l’onglet courant). Ce stockage n’est pas utilisé pour constituer un historique de navigation exploité par des tiers. + +- **`host_permissions` : ``** — Indique que l’Extension peut **s’exécuter dans le contexte des pages web sur les sites que vous visitez**. Ce schéma est en particulier nécessaire lorsque l’Extension injecte des **scripts de contenu** sur toutes les pages (par exemple pour partager du code avec la popup) ou lorsque le navigateur associe ce type d’injection à des autorisations d’hôtes larges. **Il ne s’agit pas d’un dispositif de suivi comportemental** : l’objectif reste d’obtenir l’URL de la page pour la requête Ecoindex décrite ci-dessus. Lorsque les politiques des magasins d’extensions le permettent, l’**accès réseau** au backend est limité à l’API officielle Ecoindex (`https://bff.ecoindex.fr/`) plutôt qu’à des sites arbitraires. + +Certaines versions peuvent aussi demander la permission **`tabs`** pour que la partie « arrière-plan » réagisse aux changements d’onglet ou de chargement de page, toujours dans le même objectif (par exemple mettre à jour le badge pour l’onglet actif). + +--- + +## 3. Données collectées + +### 3.1 Adresse IP + +- L’adresse IP de l’utilisateur peut être collectée **uniquement dans les logs techniques** du service backend. +- Ces logs sont utilisés exclusivement à des fins de sécurité, de maintenance et de diagnostic. + +### 3.2 URL des pages visitées + +- L’URL de la page visitée est transmise au service afin de récupérer ou calculer le score Ecoindex. +- Cette URL peut être utilisée pour générer une clé de cache (hash SHA1). + +--- + +## 4. Confidentialité et protection des données + +L’Extension respecte les principes suivants : + +- **Aucun croisement des données** : + - Les adresses IP et les URL ne sont pas corrélées ni stockées conjointement. + +- **Absence de tracking** : + - Les données ne sont pas utilisées à des fins d’analyse comportementale. + - Aucun mécanisme de suivi utilisateur n’est implémenté. + +- **Aucune exploitation commerciale** : + - Les données ne sont pas utilisées à des fins marketing ou publicitaires. + +- **Aucune transmission à des tiers** : + - Les données collectées ne sont en aucun cas transmises à des services tiers. + +--- + +## 5. Sécurité + +- Les données de cache sont stockées de manière chiffrée. +- Le système repose sur des mécanismes de hachage irréversible pour limiter l’exposition des URL. + +--- + +## 6. Acceptation des conditions + +L’installation et l’activation de l’Extension impliquent l’acceptation pleine et entière des présentes CGU, notamment en matière de traitement des données et de confidentialité. + +--- + +## 7. Évolution des CGU + +Ces conditions peuvent être mises à jour à tout moment. Il appartient à l’utilisateur de les consulter régulièrement. + +--- + +## 8. Contact + +Pour toute question relative à ces conditions ou au traitement des données, vous pouvez contacter les mainteneurs du projet. diff --git a/doc/PRIVACY-POLICY.md b/doc/PRIVACY-POLICY.md index 77641a1..2723880 100644 --- a/doc/PRIVACY-POLICY.md +++ b/doc/PRIVACY-POLICY.md @@ -1,8 +1,109 @@ # USER PRIVACY POLICY -This extension collects the following information: +## 1. Purpose -- Your current IP address is stored in the API logs. -- Your user agent is stored in the API logs. +These Terms of Use govern the use of the **Ecoindex Browser Extension** (hereinafter “the Extension”). -No other information is collected and no personal information is stored. +The Extension provides the Ecoindex score of a web page by querying a dedicated backend service ([Ecoindex BFF](https://github.com/cnumr/EcoIndex_BFF) which is a Ecoindex official API middleware). + +--- + +## 2. Service Operation + +When the Extension is active on a web page: + +- The current page URL is sent to the Ecoindex BFF backend service. +- The service checks whether an Ecoindex score already exists: + - in the database, or + - in cache. + +- If a score exists, it is returned immediately. +- If no score is available: + - it may be computed, + - and then securely stored. + +### Result Storage + +Results are stored in an encrypted manner using the following scheme: + +- Key: `ecoindex_sha1(url)` +- Value: associated Ecoindex data result + +No plain-text URL is stored in this caching mechanism. + +### 2.1 Browser permissions (transparency) + +The extension manifest may include the following declarations and use them only as described here: + +```json +{ + "permissions": ["activeTab", "storage"], + "host_permissions": [""] +} +``` + +- **`activeTab`** — Grants **temporary** access to the active tab when you interact with the extension (for example when you open the toolbar popup). The extension uses this to read the **URL of the page you are viewing** so it can request the corresponding Ecoindex result from the backend. It does **not** give ongoing read access to all tabs in the background. + +- **`storage`** — Used to keep **local data on your device**: extension preferences and short-lived UI state (for example toolbar badge information tied to the current tab). This storage is not used to build a browsing history for third parties. + +- **`host_permissions`: ``** — Declares that the extension may **run in the context of web pages on any site** you visit. This pattern is required when the extension uses **content scripts** injected on all pages (for example to share helpers with the popup) or when the browser ties injection to broad host permissions. **This is not used for behavioural tracking**: the goal remains to obtain the current page URL for the Ecoindex query described above. Where store policies allow, **network** access to the backend is kept to the official Ecoindex API host (`https://bff.ecoindex.fr/`) rather than arbitrary sites. + +Some builds may also request **`tabs`** so the background logic can react when you switch tabs or load a page, still in line with the same purpose (updating the badge for the active tab). + +--- + +## 3. Data Collection + +### 3.1 IP Address + +- The user’s IP address may be collected **only within backend technical logs**. +- These logs are strictly used for security, maintenance, and diagnostic purposes. + +### 3.2 Visited URLs + +- The visited page URL is transmitted to retrieve or compute the Ecoindex score. +- This URL may be used to generate a cache key (SHA1 hash). + +--- + +## 4. Privacy and Data Protection + +The Extension follows strict privacy principles: + +- **No data correlation**: + - IP addresses and visited URLs are not linked or stored together. + +- **No tracking**: + - No behavioral analysis is performed. + - No user tracking mechanisms are implemented. + +- **No commercial use**: + - Data is not used for marketing or advertising purposes. + +- **No third-party sharing**: + - Collected data is never shared with third-party services. + +--- + +## 5. Security + +- Cached data is stored in an encrypted format. +- Irreversible hashing mechanisms are used to minimize URL exposure. + +--- + +## 6. Acceptance of Terms + +By installing and activating the Extension, users fully accept these Terms of Use, including the privacy policy described herein. + +--- + +## 7. Changes to Terms + +These Terms may be updated at any time. Users are encouraged to review them regularly. + +--- + +## 8. Contact + +For any questions regarding these Terms or data handling, please contact the project maintainers. diff --git a/src/manifest-chrome.json b/src/manifest-chrome.json index 1c2b339..3427c93 100644 --- a/src/manifest-chrome.json +++ b/src/manifest-chrome.json @@ -23,5 +23,9 @@ ], "host_permissions": [ "https://bff.ecoindex.fr/*" - ] + ], + "options_ui": { + "page": "options/options.html", + "open_in_tab": true + } } \ No newline at end of file diff --git a/src/manifest-firefox.json b/src/manifest-firefox.json index a7a4d5a..e012158 100644 --- a/src/manifest-firefox.json +++ b/src/manifest-firefox.json @@ -20,5 +20,9 @@ "https://bff.ecoindex.fr/*", "storage", "tabs" - ] + ], + "options_ui": { + "page": "options/options.html", + "open_in_tab": true + } } \ No newline at end of file diff --git a/src/options/options.css b/src/options/options.css new file mode 100644 index 0000000..a70e2bd --- /dev/null +++ b/src/options/options.css @@ -0,0 +1,67 @@ +@import url("../popup/popup.css"); + +body.options-page.container { + width: min(100vw - 2.5rem, 40rem); + max-width: 40rem; + margin-left: auto; + margin-right: auto; +} + +.options-field { + display: flex; + align-items: flex-start; + gap: var(--s1); + margin: var(--s2) 0; +} + +.options-field input[type="checkbox"] { + margin-top: 0.35rem; + flex-shrink: 0; +} + +.options-field label { + cursor: pointer; + font-weight: var(--font-weight-normal); +} + +.options-meta { + margin-top: var(--s3); + font-size: var(--s1); + opacity: 0.9; +} + +.options-links a { + color: var(--color-lightest); +} + +#options-status { + min-height: 1.5em; + margin-top: var(--s1); + font-size: var(--s-1); +} + +.options-code { + font-family: var(--font-mono); + font-size: 0.85rem; + padding: var(--s1); + background: var(--color-dark); + color: var(--color-light); + border-radius: var(--border-radius-small); + overflow-x: auto; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.45; +} + +.options-perms-list { + padding-left: 1.25rem; + margin: var(--s1) 0; +} + +.options-perms-list li { + margin-bottom: var(--s1); +} + +.options-perms-list li:last-child { + margin-bottom: 0; +} diff --git a/src/options/options.html b/src/options/options.html new file mode 100644 index 0000000..4f641bf --- /dev/null +++ b/src/options/options.html @@ -0,0 +1,54 @@ + + + + + + + Paramètres — EcoIndex.fr + + + + +

Paramètres

+

Personnalisez le comportement de l’extension EcoIndex.fr.

+ +
+

Préférences

+
+
+ + +
+
+

+
+ +
+

Données et permissions

+

L’Extension peut déclarer dans son manifeste :

+
{
+  "permissions": ["activeTab", "storage"],
+  "host_permissions": ["<all_urls>"]
+}
+
    +
  • activeTab — Accès temporaire à l’onglet actif quand vous ouvrez la popup (ou utilisez l’Extension), pour lire l’URL de la page et interroger le service Ecoindex. Pas de lecture continue de tous les onglets.
  • +
  • storage — Stockage local des préférences et d’un état d’affichage de courte durée (par ex. badge), sur votre appareil.
  • +
  • host_permissions <all_urls> — Autorise l’Extension à s’exécuter sur les pages que vous visitez (notamment si des scripts de contenu sont injectés partout). Ce n’est pas utilisé pour du pistage publicitaire ; l’URL sert à demander le score Ecoindex. L’accès réseau vers le backend peut être limité à https://bff.ecoindex.fr/ selon la version du manifeste.
  • +
+

Détail : voir la politique de confidentialité du projet (doc/PRIVACY-POLICY-FR.md ou doc/PRIVACY-POLICY.md).

+
+ +
+

À propos

+

Version

+ +
+ + + + + diff --git a/src/options/options.js b/src/options/options.js new file mode 100644 index 0000000..a1babc9 --- /dev/null +++ b/src/options/options.js @@ -0,0 +1,43 @@ +import { getBrowserPolyfill } from "../common.js"; + +const STORAGE_KEY = "ecoindex_options"; +const DEFAULT_OPTIONS = { showScreenshot: true }; + +const browserApi = getBrowserPolyfill(); +const statusEl = document.getElementById("options-status"); +const checkbox = document.getElementById("show-screenshot"); +const versionEl = document.getElementById("extension-version"); + +function showSaved() { + statusEl.textContent = "Enregistré."; + setTimeout(() => { + statusEl.textContent = ""; + }, 2000); +} + +async function load() { + const manifest = browserApi.runtime.getManifest(); + versionEl.textContent = manifest.version ?? "—"; + + const stored = await browserApi.storage.local.get({ + [STORAGE_KEY]: DEFAULT_OPTIONS, + }); + const opts = { ...DEFAULT_OPTIONS, ...stored[STORAGE_KEY] }; + checkbox.checked = opts.showScreenshot !== false; +} + +async function save() { + await browserApi.storage.local.set({ + [STORAGE_KEY]: { + ...DEFAULT_OPTIONS, + showScreenshot: checkbox.checked, + }, + }); + showSaved(); +} + +checkbox.addEventListener("change", () => { + save().catch(console.error); +}); + +load().catch(console.error); diff --git a/src/popup/popup.js b/src/popup/popup.js index d50ca45..a4e429a 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -9,9 +9,18 @@ import { } from "../common.js"; let tabUrl; +/** @type {{ showScreenshot: boolean }} */ +let options = { showScreenshot: true }; const domTitle = document.getElementById("title"); const currentBrowser = getBrowserPolyfill(); +async function loadOptions() { + const stored = await currentBrowser.storage.local.get({ + ecoindex_options: { showScreenshot: true }, + }); + options = { ...stored.ecoindex_options }; +} + /** * display error message * @param string title @@ -188,7 +197,9 @@ function displayResult(ecoindexData) { resultLink.setAttribute("href", FETCH_RESULT_ECOINDEX_URL(latestResult.id)); document.getElementById("result").style.display = "block"; - displayImage(latestResult.id); + if (options.showScreenshot) { + displayImage(latestResult.id); + } updateLocalStorage(latestResult); } @@ -324,19 +335,23 @@ async function runAnalysis() { resetDisplay(); -document - .querySelector("#no-analysis button") - .addEventListener("click", runAnalysis); -document.getElementById("retest").addEventListener("click", runAnalysis); - -currentBrowser.tabs.query( - { - active: true, - lastFocusedWindow: true, - }, - (tabs) => { - tabUrl = tabs[0].url; - - getAndUpdateEcoindexData(tabUrl); - }, -); +loadOptions() + .then(() => { + document + .querySelector("#no-analysis button") + .addEventListener("click", runAnalysis); + document.getElementById("retest").addEventListener("click", runAnalysis); + + currentBrowser.tabs.query( + { + active: true, + lastFocusedWindow: true, + }, + (tabs) => { + tabUrl = tabs[0].url; + + getAndUpdateEcoindexData(tabUrl); + }, + ); + }) + .catch(console.error);