Fix AMO submission requirements: data collection permissions, innerHTML lint, source README

- Declare data_collection_permissions in browser_specific_settings.gecko
  (Firefox 140+/AMO validation requirement) to reflect wallet address,
  auth signature, and trading-identity data sent to the backend.
- Replace innerHTML assignments in blur-disable.ts and toast.ts with
  DOM construction (createElementNS/createElement) to clear the AMO
  linter's unsafe-innerHTML warning on content.js.
- Add build instructions to README.md for AMO's source code submission
  requirement, triggered by WXT/Vite's bundling and minification.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HoMzqFw3d93hG6c9xB4thU
This commit is contained in:
2026-09-09 12:20:31 +02:00
co-authored by claude
parent cd56cf60cd
commit 891d561aaa
4 changed files with 103 additions and 14 deletions
+42 -2
View File
@@ -1,2 +1,42 @@
# copilot # Osias — copilot
Your blockchain powered Agent to help with your trading emotions
Your blockchain-powered trading companion. This is the open-source browser extension
frontend (Apache License 2.0). See [`CLAUDE.md`](CLAUDE.md) for full architecture and
protocol documentation.
## Build instructions
Built with [WXT](https://wxt.dev) (Vite-based). To reproduce the exact `.output/firefox-mv3`
bundle submitted to addons.mozilla.org from source:
**Environment**
- Node.js 22.x (built and tested on 22.11.0)
- npm 11.x (built and tested on 11.14.1)
**Steps**
```sh
npm install # also runs `wxt prepare` via postinstall
npm run build:firefox
```
Output is written to `.output/firefox-mv3/`. To produce the uploaded `.zip` directly:
```sh
npm run zip:firefox
```
Output zip is written to `.output/`.
No build secrets, environment variables, or private dependencies are required — everything
needed to build is in this repository and on the public npm registry, pinned via
`package-lock.json`.
## Development
- `npm run dev:firefox` — dev build + watch targeting Firefox/Zen, auto-opens a temporary
profile with the extension loaded.
- `npm run compile` — `tsc --noEmit` type-check only.
## License
Apache License 2.0 — see [`LICENSE`](LICENSE).
@@ -17,8 +17,33 @@ const locked = new WeakMap<HTMLElement, LockedRecord>();
// reason toast is surfaced on `click` once the gesture completes. // reason toast is surfaced on `click` once the gesture completes.
const INTERCEPTED_EVENT_TYPES = ['pointerdown', 'mousedown', 'mouseup', 'click'] as const; const INTERCEPTED_EVENT_TYPES = ['pointerdown', 'mousedown', 'mouseup', 'click'] as const;
const LOCK_ICON_SVG = const SVG_NS = 'http://www.w3.org/2000/svg';
'<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="10" width="16" height="10" rx="2"></rect><path d="M8 10V7a4 4 0 0 1 8 0v3"></path></svg>';
function createLockIcon(): SVGSVGElement {
const svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('width', '16');
svg.setAttribute('height', '16');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
svg.setAttribute('stroke-linecap', 'round');
svg.setAttribute('stroke-linejoin', 'round');
const rect = document.createElementNS(SVG_NS, 'rect');
rect.setAttribute('x', '4');
rect.setAttribute('y', '10');
rect.setAttribute('width', '16');
rect.setAttribute('height', '10');
rect.setAttribute('rx', '2');
svg.appendChild(rect);
const path = document.createElementNS(SVG_NS, 'path');
path.setAttribute('d', 'M8 10V7a4 4 0 0 1 8 0v3');
svg.appendChild(path);
return svg;
}
// `backdrop-filter` (not `filter`) is deliberate: `filter` on the container // `backdrop-filter` (not `filter`) is deliberate: `filter` on the container
// would recomposite the container's entire subtree — including the lock // would recomposite the container's entire subtree — including the lock
@@ -80,7 +105,7 @@ export const blurDisable: Intervention = {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.dataset.osiasOverlay = 'true'; overlay.dataset.osiasOverlay = 'true';
overlay.style.cssText = OVERLAY_STYLE; overlay.style.cssText = OVERLAY_STYLE;
overlay.innerHTML = LOCK_ICON_SVG; overlay.appendChild(createLockIcon());
const onOverlayEvent = (event: Event) => { const onOverlayEvent = (event: Event) => {
event.preventDefault(); event.preventDefault();
+22 -9
View File
@@ -54,22 +54,35 @@ function ensureToast(): HTMLElement {
el = document.createElement('div'); el = document.createElement('div');
el.id = TOAST_ID; el.id = TOAST_ID;
el.innerHTML = `
<div class="osias-toast-icon">🔒</div> const icon = document.createElement('div');
<div class="osias-toast-body"> icon.className = 'osias-toast-icon';
<div class="osias-toast-title">Osias: buying locked</div> icon.textContent = '🔒';
<div class="osias-toast-reason"></div>
<div class="osias-toast-action">View details</div> const body = document.createElement('div');
</div> body.className = 'osias-toast-body';
`;
const title = document.createElement('div');
title.className = 'osias-toast-title';
title.textContent = 'Osias: buying locked';
const reasonEl = document.createElement('div');
reasonEl.className = 'osias-toast-reason';
const action = document.createElement('div');
action.className = 'osias-toast-action';
action.textContent = 'View details';
// No manual close button — the toast only goes away on its own (auto-hide // No manual close button — the toast only goes away on its own (auto-hide
// timer below) or when the lock state actually changes; clicking it never // timer below) or when the lock state actually changes; clicking it never
// unlocks anything. // unlocks anything.
el.querySelector('.osias-toast-action')?.addEventListener('click', () => { action.addEventListener('click', () => {
const message: OsiasMessage = { type: 'osias:open-popup' }; const message: OsiasMessage = { type: 'osias:open-popup' };
browser.runtime.sendMessage(message).catch(() => undefined); browser.runtime.sendMessage(message).catch(() => undefined);
}); });
body.append(title, reasonEl, action);
el.append(icon, body);
document.body.appendChild(el); document.body.appendChild(el);
return el; return el;
} }
+11
View File
@@ -17,6 +17,17 @@ export default defineConfig({
gecko: { gecko: {
// Placeholder id for local/dev builds; replace before publishing to AMO. // Placeholder id for local/dev builds; replace before publishing to AMO.
id: '[email protected]', id: '[email protected]',
// Firefox-required data collection disclosure (Firefox 140+ / AMO
// validation). Reflects what the extension actually transmits to the
// backend: wallet address + signature during Phantom sign-in
// (authenticationInfo, personallyIdentifyingInfo), and the wallet
// identity that ties the account to on-chain trading activity
// (financialAndPaymentInfo). See copilot/CLAUDE.md's "Backend
// connection" and "Wallet auth" sections.
data_collection_permissions: {
required: ['authenticationInfo', 'personallyIdentifyingInfo', 'financialAndPaymentInfo'],
optional: [],
},
}, },
}, },
}, },