Extension
Chrome Manifest V3 extension. Version 0.7.3.
Entry Points
| File | Role |
|---|---|
content.ts |
Injected into every page, captures DOM events |
background.ts |
Service worker, manages sessions and WebSocket |
popup.ts |
Extension popup with tabs (Status, Trails, About) |
sidepanel.ts |
Side panel with trail replay engine |
trail-db.ts |
IndexedDB wrapper for local trail persistence |
config.ts |
Constants (host URL, throttle values) |
types.ts |
Shared TypeScript interfaces |
blincr-picker.ts |
"Blincr this element" picker + create form |
capture-policy.ts |
Capture boundary: deny/nav-only classification, URL auth-param stripping, sensitive-field rules (pure, unit-tested) |
Content Script
Initializes once per page (guards with __blincr_injected flag). Registers:
- Click capture — Records element tag, text, href, CSS selector path
- Scroll capture — Throttled by
SCROLL_THROTTLE_MS, records scroll position - Selection capture — Fires on
selectionchange, records selected text - Clipboard capture — Tracks copy events with copied text content
- SPA navigation — Listens to
popstateandHistoryAPI monkey-patches - Overlay shell — Injects the overlay manager container into the page
Events are sent to background via chrome.runtime.sendMessage as BROWSE_EVENT.
Background Service Worker
Manages session lifecycle:
START_SESSION— Creates a new DO session, connects WebSocketJOIN_SESSION— Joins existing session by IDSTOP_SESSION— Disconnects WebSocket, saves trail locallyGET_STATUS— Returns current session stateBROWSE_EVENT— Forwards event to WebSocket (if live) and stores locallyGET_TRAIL— Returns current trail from memoryREPLAY_COMMAND— Forwards replay controls to side panelADD_WAYPOINT— Inserts a named waypoint event into the trailGET_FINGERPRINT— Returns persistent install ID fromchrome.storage.localGET_OVERLAYS/ACTIVATE_COMMENT_MODE— Overlay plugin coordinationCREATE_BLINCR— AuthenticatedPOST /api/user/blincrs(see Blincrs below)
Capture boundary (E12.2)
The cheapest data to protect is data never captured. Every page gets one of
three capture modes, decided in capture-policy.ts (pure module, tested in
tests/capture-policy.test.ts) and enforced at two layers — the content
script attaches no capture listeners at all on protected pages (pause is
physical), and background.ts's sendBrowseEvent re-checks the policy so no
relay path can bypass it:
deny— built-in categories (banking & payments, health portals, government & identity, adult) plus any site the user marked "Never capture here" in the popup. Nothing is captured; during an active session a dismissible "capture paused" badge shows the boundary working.nav_only— personal-workspace domains (webmail, chat: Gmail, Outlook, WhatsApp Web, Slack, Teams, …) where page content is largely other people's data. Only URL + title navigation events are captured — no fingerprint, clicks, scrolls, selections, or clipboard.full— normal capture, hardened: auth-shaped query/fragment params (token,access_token,sig,api_key,code, …) and URL userinfo are stripped from every captured URL; clicks on password / hidden /cc-*/ one-time-code fields record only{x, y, redacted: true}; and INPUT/TEXTAREA/contenteditable text never enters click payloads.
Settings live in chrome.storage.local (blincr_capture_policy): user
never-capture sites, extra workspace domains, disabled categories. The popup
shows the current tab's classification and a one-click "Never capture on
storage.onChanged, the content script on next injection.
Blincrs ("blincr this element")
A blincr is a standing page watcher. Signed-in users get cloud blincrs
(account-owned, run 24/7 by the watch-worker cron); signed-out users get
local blincrs (E14.1 — see below), which run entirely in the browser.
Entry point: popup button "👁 Blincr this
element" → chrome.tabs.sendMessage(tabId, { kind: "ACTIVATE_BLINCR_MODE" })
→ content.ts → blincr-picker.ts.
- Picker (
activateBlincrPicker()): hover-highlights the element under the cursor; a click builds a CSS selector (reusingcontent.ts'sbuildSelector), guesses a recipe (price-looking text →ltcomparator with a threshold at 90% of the parsed value; otherwisechanged), and opens a small form (label, comparator, threshold, check frequency). - Create: the form sends
{ kind: "CREATE_BLINCR", input }tobackground.ts, which callscreateBlincrRemote().
Auth bridge (why cookies permission is needed)
The extension has no session of its own — it piggybacks the user's existing
blincr.com login. The dashboard's session cookie is HttpOnly +
SameSite=Lax, which the browser never attaches to a chrome-extension://
origin's fetch (that origin counts as cross-site). So background.ts reads
the cookie value directly via chrome.cookies.get({ url: API_BASE, name: "blincr_session" }) — the cookies permission plus the existing
<all_urls> host permission make this a same-product read, not a
third-party leak — and forwards it as an X-Blincr-Ext-Session header.
getSessionToken() in src/ar.ts accepts either the cookie or this header.
If no cookie is found (user not logged in on blincr.com), the watcher is
saved locally instead (see below) — the toast explains the trade-off.
Local blincrs (E14.1 — autonomy ladder L0)
See docs/autonomy-ladder.md for the full doctrine. In the picker, every
watcher gets an explicit "Run where?" choice — "In this browser
(private, only while open)" (default) or "On blincr.com (24/7, needs
sign-in)" — with the trade-off shown live underneath. background.ts
(handleCreateBlincr) honors that choice: local always saves in-browser;
cloud requires sign-in and returns a named error
(blincr.watch.not_authenticated) rather than silently downgrading to
local. Older callers that send no choice keep the original
best-effort-cloud-else-local fallback.
- Engine: the shared platform-neutral core
(
extension/src/blincr-engine-core.ts— compare, intervals, regex/JSON-LD extraction; the workers re-export it viasrc/blincr-engine.ts). Local state transitions (extension/src/blincr-local.ts, pure, unit-tested) mirror the cron runner: baseline-not-change, 6h alert cooldown, sight-lost after 5 consecutive failures. - Scheduler: a persistent
chrome.alarmsalarm (blincr-local-checks, every 5 min) wakes the service worker, which fetches due pages itself (<all_urls>host permission) and honors each watcher's interval bucket. - CSS extraction: MV3 service workers have no DOM, so an offscreen
document (
offscreen/offscreen.html+dist/offscreen.js,offscreenpermission, reasonDOM_PARSER) answersOFFSCREEN_CSS_EXTRACTrequests withDOMParser+querySelector. Regex and JSON-LD recipes run inline. - Alerts:
chrome.notifications(notificationspermission) plus a per-watcher history (last 20) shown in the popup's Trails tab, where local watchers can be paused, resumed, deleted, or upgraded (☁ button →LOCAL_BLINCR_UPGRADEre-creates the watcher on blincr.com and deletes the local copy only on success — a failed upgrade leaves it untouched). - Limits, by design: checks run only while the browser is open; no webhooks or agent handoff (those are the cloud tier). The choice UI says exactly that — informed choice, no silent upsell.
Popup
Three-tab interface:
- Status — Session controls (start/stop/join), connection indicator, participant list
- Trails — Local trail library from IndexedDB with search, folder management
- About — Platform info, version badge (
__VERSION__,__BUILD_DATE__)
Record agent session (E13.4): a checkbox next to Start explicitly labels
the session as agent browsing. The toolbar badge turns purple ("AI"), the
owner participant is saved with role: "agent", and the finished trail gets
isAgent: true and is auto-filed into the "Agent sessions" folder — it never
appears in the popup's recent-trails list. Labeling is explicit only; no
cadence heuristics decide for the user.
Side Panel
Opens via chrome.sidePanel.open(). Contains the trail replay engine with
play/pause, step forward/back, speed control, skip-to-next-page, and seek.
The library's trail detail view offers Replay, Export (raw .blincr file),
For AI (agent-ready blincr-trail-bundle/1 export — JSON with full events
plus a Markdown digest, built entirely client-side so it works for every
visibility tier; see docs/trail-bundle.md), Upload, and Delete.
Trail Database (IndexedDB)
Database name: blincr-trails, version 1.
Object stores:
trails(keyPath:trailId) — Indexes:createdAt,folderId,title,syncStatusfolders(keyPath:folderId) — Indexes:parentId,name
Operations: CRUD for trails and folders, search by title/domain/tag, split, merge, export (single or full backup), import.
Overlay Plugin System
Extensible annotation layer managed by overlay-manager.ts.
- Plugins register via
registerPlugin()and providerender,update,remove, andcreateInputUImethods. - Overlays are positioned by viewport coordinates or CSS selector + offset.
- Persisted as
overlayevents in the trail.
Built-in plugin: comment — Persistent page annotations with five styles
(default, highlight, question, warning, success).
Build
cd extension && node build.js
Uses esbuild. Injects __VERSION__ and __BUILD_DATE__ via define. Output
goes to extension/dist/. Manifest version must be bumped on every source change.