Getting started
Install
Section titled “Install”npm install crxtraceNo bundler? Grab the single-file IIFE build from
node_modules/crxtrace/dist/crxtrace.global.js and see
Without a bundler below.
Get a DSN
Section titled “Get a DSN”The dsn is the endpoint your events are sent to. Two ways to get one:
Use the hosted dashboard. Sign up at app.crxtrace.dev, create a project, and the setup page hands you a DSN that looks like:
https://app.crxtrace.dev/api/ingest/pk_live_...It’s safe to ship inside your extension bundle — it only allows writing events, never reading them.
Or run your own. The SDK works against any server that accepts the
envelope and answers 2xx. A complete receiver in ~150
lines of dependency-free Node ships in the repo, and
self-hosting covers storage, grouping and retention.
Either way the rest of this page is identical — nothing below depends on which you chose.
Manifest permissions
Section titled “Manifest permissions”{ "permissions": ["storage", "alarms"], "host_permissions": ["https://your-ingest-endpoint/*"]}| Permission | Why |
|---|---|
storage |
Required. It’s what makes the queue durable across worker death. Without it, CrxTrace is just a slower console.error. |
alarms |
Strongly recommended. A mostly-idle worker may never wake on its own; the alarm drains the queue on a schedule. |
host_permissions |
Your ingest origin, so the worker is allowed to fetch it. |
Initialise every surface
Section titled “Initialise every surface”Call init() once per surface — the service worker, each content script, and
any extension page you want covered. It’s the same call everywhere; the SDK
detects where it is running.
import * as CrxTrace from "crxtrace";
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });import * as CrxTrace from "crxtrace";
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });Only the service worker owns the durable queue and talks to the network. Content scripts and extension pages relay their events through the worker — they never send from a page they don’t control. That’s deliberate: a content script issuing cross-origin requests from someone else’s site is both a privacy problem and a CSP problem.
Name your async work
Section titled “Name your async work”This is the single highest-value thing you can do, and it takes one line.
const transcript = await CrxTrace.track("fetchTranscript", getTranscript(id));If Chrome terminates the worker while that promise is outstanding, the next
boot reports an sw_terminated event naming fetchTranscript. Without it you
get silence, because nothing threw — the worker simply stopped.
See Service worker deaths for what this catches and why nothing else catches it.
Report errors you handle
Section titled “Report errors you handle”Uncaught errors and unhandled rejections are captured automatically. For the ones you catch yourself:
try { await syncBookmarks();} catch (error) { CrxTrace.captureException(error, { tags: { feature: "sync" } });}Verify it works
Section titled “Verify it works”Set debug: true and the SDK narrates its decisions to the console:
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_...", debug: true,});Then throw something on purpose from your service worker console:
throw new Error("crxtrace smoke test");By default events are batched with a 1.5 second debounce, so give it a moment —
or call await CrxTrace.flush() to send immediately.
Without a bundler
Section titled “Without a bundler”The IIFE build exposes a CrxTrace global and works with importScripts():
importScripts("vendor/crxtrace.global.js");
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });<script src="vendor/crxtrace.global.js"></script><script> CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });</script>Copy the file out of node_modules/crxtrace/dist/crxtrace.global.js as part of
your build.
Try the demo extension
Section titled “Try the demo extension”The repository ships a real MV3 extension that triggers each failure mode on demand, plus a throwaway server that pretty-prints arriving events:
git clone https://github.com/sabbir-being/crxtrace.gitcd crxtracenpm installnpm run demo:sync # build the SDK into the demonpm run demo:ingest # start the receiverLoad examples/demo-extension unpacked at chrome://extensions and press the
buttons. The one worth waiting for is Start task, then let worker die —
close the popup, wait about 30 seconds, then wake the extension:
⚠ worker terminated after 31s while running: fetchTranscript- Service worker deaths — the failure mode this exists for
- Configuration — every option and its default
- Self-hosting — run your own backend