Skip to content

Getting started

Terminal window
npm install crxtrace

No bundler? Grab the single-file IIFE build from node_modules/crxtrace/dist/crxtrace.global.js and see Without a bundler below.

manifest.json
{
"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.

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.

background.js
import * as CrxTrace from "crxtrace";
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });
content.js
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.

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.

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" } });
}

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.

The IIFE build exposes a CrxTrace global and works with importScripts():

background.js
importScripts("vendor/crxtrace.global.js");
CrxTrace.init({ dsn: "https://your-ingest-endpoint/pk_live_..." });
popup.html
<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.

The repository ships a real MV3 extension that triggers each failure mode on demand, plus a throwaway server that pretty-prints arriving events:

Terminal window
git clone https://github.com/sabbir-offc/crxtrace.git
cd crxtrace
npm install
npm run demo:sync # build the SDK into the demo
npm run demo:ingest # start the receiver

Load 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