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.

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.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-being/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