openvibers/openvibe.extensions AGENTS.md
AGENTS.md
| 1 | # AGENTS.md — OpenVibe.Extensions |
| 2 | |
| 3 | ## Project Overview |
| 4 | |
| 5 | OpenVibe.Extensions is the home of OpenVibe's first-party **client surfaces** (ADR-023): the OpenVibe Companion, a |
| 6 | Manifest V3 browser extension for Chromium and Firefox, plus the kiosk companion and the Raspberry Pi hardware |
| 7 | scripts. There is no server, port, domain or database in this repository; the extension talks to first-party |
| 8 | services from the person's browser. |
| 9 | |
| 10 | - **Package:** `openvibe-extensions` 0.9.2 (private) · **Runtime:** Node ≥22 for build/test · **License:** MIT |
| 11 | - **Read first:** [README.md](README.md) (authoritative on permissions and privacy), [store/privacy.md](store/privacy.md), |
| 12 | [store/listing.md](store/listing.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md). |
| 13 | |
| 14 | **Network context:** the Companion calls OpenVibe.Coupons (public API; `cpx_` install token), OpenVibe.Network (Dock |
| 15 | state, OAuth), OpenVibe.Chat (Dock Chats), OpenVibe.Codes (Dock agent view), OpenVibe.Mail (masks, with the person's |
| 16 | bearer), OpenVibe.Locker (encrypted logins) and OpenVibe.Live (the agent view's embed). Its hand-off buttons open |
| 17 | OpenVibe.Rent, OpenVibe.Watch, OpenVibe.Food and OpenVibe.Reviews in a new tab. It is a client: it owns no account, |
| 18 | coupon, mail, locker or Dock data. |
| 19 | |
| 20 | ## Commands |
| 21 | |
| 22 | ```bash |
| 23 | npm test # every test/*.test.js; no browser needed, local stubs |
| 24 | npm test -- manifest # only files whose name contains the word |
| 25 | npm test -- --strict # a skipped test fails the run |
| 26 | npm run pack # dist/openvibe-companion-<version>.zip (load unpacked; API https://openvibe.coupons) |
| 27 | npm run pack:stores # dist/…-chrome.zip and …-firefox.zip (store uploads, runs the audit first) |
| 28 | npm run pack:dev # a local API build (http://localhost:4850, dev only) |
| 29 | npm run lint:firefox # Mozilla web-ext lint, warnings as errors (CI runs it) |
| 30 | npm run icons # regenerate browser/icons/ |
| 31 | node scripts/check-panel.js # the side panel in headless Chrome (also test/panel.test.js) |
| 32 | node scripts/check-autoapply.js # autoapply on fixture checkouts in headless Chrome |
| 33 | node test/locker-browser.js # optional: Playwright/Chromium (set PLAYWRIGHT_MODULE), 12 screenshots, clipboard |
| 34 | ``` |
| 35 | |
| 36 | No dependencies for the extension or the test runner; tests/scripts use Node's standard library. There is no |
| 37 | build step for the shipped code: the zip is exactly `browser/`. |
| 38 | |
| 39 | ## Architecture at a Glance |
| 40 | |
| 41 | - **`browser/manifest.json`** — MV3 for Chromium (minimum 114) and Firefox (gecko id, strict 128); permissions |
| 42 | `activeTab`, `identity`, `scripting`, `storage`, `alarms`, `clipboardWrite`, and Chrome-only `offscreen`/`sidePanel`; |
| 43 | host permissions only for the named OpenVibe origins; the CSP (no remote script, no inline script, frames only Live). |
| 44 | - **`browser/background.js`** — the service worker; wires `lib/mask-service.js` and the Dock/agent/mail/locker modules |
| 45 | to `browser.*`. |
| 46 | - **`browser/panel.html` + `panel.js` + `panel.css`** — the OpenVibe Dock in the side panel (`shared/` is the released |
| 47 | OpenVibe.Shared Dock, bundled because the CSP allows no remote script); tabs Vibe/Agents/Chats/Activity; the Agents |
| 48 | tab reads the person's token from Codes (`GET /api/v1/me/agent-view`) and embeds Live. |
| 49 | - **`browser/popup.html` + `popup.js`** — DOM wiring only; `lib/controller.js` holds the coupon logic (tested in Link); |
| 50 | `lib/format.js` the wording; `lib/api.js` the only Coupons client. |
| 51 | - **`browser/options.html` + `options.js`** — connect/disconnect a `cpx_` token, grant the API host permission, the |
| 52 | pasted-token fallback and sign-out. |
| 53 | - **`browser/lib/`** — `config.js` (origins, scopes, keys), `host.js` (URL → hostname, everything else dropped), |
| 54 | `handoff.js` (cleaned Rent/Watch/Food/Reviews URLs), `autoapply.js` (injected coupon-box runner), |
| 55 | `rating.js`/`listing.js` (injected, read-only), `platform.js` (`chrome.*`/`browser.*` adapter), |
| 56 | `auth.js` (PKCE OAuth, refresh, sign-out), `network-api.js` (Dock state), `mail-api.js`/`mask*.js`, |
| 57 | `locker-*.js` (envelope, vault, fill, clipboard, passwords), `controller.js`, `format.js`. |
| 58 | - **`browser/shared/`** — the vendored Dock/SDK browser bundle (`dock*.js`, `ov-locker-core.js`, `kit.css`, glyphs, |
| 59 | brands). Treat it as a release: update from the pinned Shared/SDK version, do not hand-edit crypto. |
| 60 | - **`kiosk-companion/`** — the Firefox new-tab/history bridge for OpenVibe.Live's `/kiosk` (moved from Live). |
| 61 | - **`hardware/`** — the Raspberry Pi streamer/controller scripts (moved from Live; `streamer.py`, `controller.py`). |
| 62 | - **`scripts/`** — `audit.js` (the permission audit), `pack.js` (load/store zips, reproducible), `check-panel.js`, |
| 63 | `check-autoapply.js`, `lint-firefox.js`, `icons.js`; tests in [test/](test/). |
| 64 | - **`store/`** — `listing.md`, `privacy.md`, `screenshots.md`: the exact store fields/disclosures. The owner uploads; |
| 65 | never upload from CI. |
| 66 | |
| 67 | ## Permissions, Privacy and Credentials |
| 68 | |
| 69 | - **Only four buttons touch a page**, and only after a click (`activeTab`): "Try these codes" (`lib/autoapply.js`), |
| 70 | "Save its rating" (`lib/rating.js`), "Keep on my Rent board"/"Check the rent" (`lib/listing.js`). No content |
| 71 | scripts, no `externally_connectable`, no web-accessible resources, no `<all_urls>`, no `tabs`/`cookies`/`webRequest`. |
| 72 | - The Coupons part sends only the **hostname** (`lib/host.js` drops path/query/fragment/port/credentials), with |
| 73 | `credentials: 'omit'`, `referrerPolicy: 'no-referrer'`, `redirect: 'error'`. Never send page content or the full URL. |
| 74 | - A `cpx_` token is an install token with `coupons.lookup` and optionally `coupons.report`; it is not a Network |
| 75 | session. Refuse to store anything that is not `cpx_`. The Dock's OAuth is a public client (`companion`, PKCE S256, |
| 76 | no secret) with scopes `dock activity chat.read agent.view mail.mask.manage locker.login.manage run.fill.approve`; |
| 77 | the access token stays in `storage.session` (memory), the rotating refresh token in `storage.local` (this device, |
| 78 | never sync). Sign-out revokes at the Network. |
| 79 | - Locker: the popup unlocks with the passphrase/recovery kit (a passkey belongs to openvibe.locker, so it offers |
| 80 | **Open Locker**); keys live only in `storage.session`/background memory, never local/sync; lock now, account |
| 81 | change, browser restart and 15 idle minutes clear them. Logins match the exact origin (scheme/host/port); fill |
| 82 | requires a click, refuses ambiguous forms, re-checks the origin before writing, and never runs on page load. |
| 83 | Copy clears the clipboard 30 s after the latest copy (does not read it). |
| 84 | - Mail masks: only OpenVibe.Mail's `/api/v1/masks` with the person's bearer and the `mail.mask.manage` scope; a mask is |
| 85 | never offered on an `openvibe.*` site and none is created until the person presses the button; a site with a mask |
| 86 | reuses it, never a second one. |
| 87 | - CSP is a guarantee: `default-src 'none'`, `script-src 'self'`, styles only `'unsafe-inline'`, images `'self' blob:`, |
| 88 | frames only Live, object/base none. No `eval`, no remote code, no `innerHTML` in the extension's own code. |
| 89 | - Host permissions and `connect-src` must agree with `lib/config.js` for every origin; the audit fails if one widens. |
| 90 | |
| 91 | ## Key Pitfalls |
| 92 | |
| 93 | - **Never widen the manifest, host permissions, CSP or page access.** Any extra permission, origin, content script, |
| 94 | web-accessible resource, `externally_connectable`, optional permission, `unsafe-eval` or remote script is a privacy |
| 95 | regression and fails [test/manifest.test.js](test/manifest.test.js). Add a feature within the existing surface. |
| 96 | - **Never store a token in `storage.sync`.** Only the `cpx_` install token (local), the Dock access token (session) |
| 97 | and the rotating refresh token (local) exist; the old pasted `networkToken` is a temporary fallback only. |
| 98 | - **Hand-offs open a visible new tab** on an allow-listed `https://openvibe.<tld>` origin with a cleaned URL (no |
| 99 | credentials/fragment/tracking); nothing opens when the popup opens, and `tabs.open` refuses any other origin. |
| 100 | - **No page content is sent** except the one read the person asked for; `test/privacy.test.js` inspects every request |
| 101 | on checkout URLs full of personal data. |
| 102 | - **Vendored `shared/` is a release**, not source: keep the SDK/Shared version and the crypto framing |
| 103 | (`locker-payload.js`/`locker-passwords.js` from Locker `9c2744e`) compatible; run `npm test -- locker`, |
| 104 | `npm run pack:stores`, `npm run lint:firefox`. |
| 105 | - **Firefox MV3 background pages are non-persistent:** a clipboard alarm must back up an in-memory timer; Chrome uses |
| 106 | the documented offscreen clipboard document. Do not assume a long-lived page. |
| 107 | - **The Dock's agent view is behind two server-side gaps:** Codes' session check and Live's `frame-ancestors` do not |
| 108 | yet accept the extension/Dock credential, so the view is bundled and tested against a stub but hidden in |
| 109 | production until Codes or Network accepts that token. Do not fake it on. |
| 110 | - **Version:** bump `browser/manifest.json` for every store upload; stores refuse a version they have seen. |
| 111 | - **Never upload from CI.** `pack:stores` builds; the owner uploads with [store/listing.md](store/listing.md), |
| 112 | [store/privacy.md](store/privacy.md) and [store/screenshots.md](store/screenshots.md). |
| 113 | |
| 114 | ## Testing |
| 115 | |
| 116 | `npm test` runs each file in its own process with local stubs and fake browser APIs: the manifest/permission audit, |
| 117 | API client, privacy on checkout URLs, hand-offs, the popup controller, pack reproducibility, the Dock's network API, |
| 118 | auth (PKCE/refresh/sign-out), messages, mask rules/service/router, Locker payload/vault/passwords/fill, and the |
| 119 | headless-Chrome panel check (skips without Chrome). `scripts/check-autoapply.js` runs the injected coupon runner on |
| 120 | real checkout fixtures; `test/locker-browser.js` is the optional Playwright check (screenshots + clipboard). A skipped |
| 121 | test is not a pass; `--strict` makes one fail. Always `node --check` changed JS. |
| 122 | |
| 123 | ## Documentation |
| 124 | |
| 125 | - [README.md](README.md) — purpose, Companion features, what it can see/send/stores, the install token, Dock in the |
| 126 | side panel, layout, tests, packing, store release, dependencies, Locker passwords. |
| 127 | - [store/listing.md](store/listing.md), [store/privacy.md](store/privacy.md), [store/screenshots.md](store/screenshots.md). |
| 128 | - [kiosk-companion/README.md](kiosk-companion/README.md), [hardware/README.md](hardware/README.md). |
| 129 | - [CHANGELOG.md](CHANGELOG.md), [SECURITY.md](SECURITY.md). |
| 130 |