Docs / Web
JavaScript
Browser and Node from one package, zero dependencies. Global handlers for whichever runtime you are in are installed for you.
What you need first
A project's DSN. Create a project, then copy it from that project's settings page: it carries the host and the project id, so it is the only thing this SDK needs to be pointed at.
https://<key>@cyclopes.localhost.co.zw/ingest/<project-id>
The key is write-only: it can send events and nothing else, so shipping it inside a mobile app is fine. Read it from your environment rather than committing it.
Install
The package is served by this install rather than by npm, so install it from the tarball this server publishes.
$ npm install https://cyclopes.localhost.co.zw/packages/cyclopes-sdk-0.4.0.tgz
Wire it up
import * as cyclopes from "@cyclopes/sdk";
cyclopes.init({
dsn: "https://<key>@cyclopes.localhost.co.zw/ingest/<project-id>",
environment: "production",
release: "webapp@2.0.0",
});
That is enough for errors. Uncaught errors and unhandled promise rejections are reported automatically: window.onerror and onunhandledrejection in the browser, uncaughtException and unhandledRejection in Node.
Errors
try {
risky();
} catch (err) {
cyclopes.captureException(err);
}
cyclopes.setTag("tier", "pro");
cyclopes.setUser({ id: "u_123" });
cyclopes.captureMessage("checkout completed", { level: "info" });
Events go out with fetch, using keepalive so they survive a page navigation, from a queue that drops silently on failure. A Cyclopes outage never breaks or blocks your application.
Logs and journeys
Logs and activity are buffered together and streamed in batches: 30 items, or five seconds after the first, whichever comes first, plus a best-effort flush on page hide and process exit.
cyclopes.logger.debug("cache miss", { key: "user:42" });
cyclopes.logger.info("checkout started");
cyclopes.logger.warn("retrying payment", { attempt: 2 }); // sent as "warning"
cyclopes.logger.error("payment failed", { gateway: "stripe" });
cyclopes.track("checkout_started", { cart_total: 42 });
cyclopes.screen("Cart", { items: 3 });
cyclopes.identify({ id: "u_123", email: "u@example.com" });
identify() also stores the user as the client's identity: later error events use it as event.user unless setUser() set one explicitly. Every batch and every error carries the same session id, so the server can line an error up with the journey that led to it.
screen() records where the user is, and every track() after it says so. For an arrival no navigation announces, a modal, a tab, a step in a wizard, say so yourself:
cyclopes.setCurrentScreen("PromoSheet");
cyclopes.flush() flushes pending events and the batch together. Call it before a short-lived Node process exits.
Sessions
A session is one continuous sitting. The SDK makes an id at init() and rotates it after 30 idle minutes (sessionTimeout: 1800; 0 disables). In the browser it lives in sessionStorage, so it survives navigation but dies with the tab: two tabs are two sessions, on purpose.
cyclopes.newSession(); // rotate explicitly, e.g. on logout
cyclopes.setSession("abc"); // pin a known id
On Node, scope one per request. Otherwise every user you serve shares one session id and their journeys merge together:
await cyclopes.withSession(req.id, async () => {
cyclopes.track("checkout_started", { cartTotal });
});
Nothing leaks across a withSession scope: one request's page is never stamped onto another request's actions.
Breadcrumbs and context
cyclopes.addBreadcrumb({ category: "navigation", message: "/checkout", level: "info" });
cyclopes.addBreadcrumb({ category: "http", message: "POST /api/pay → 500", level: "error" });
// browser (userAgent) and runtime (node/browser) contexts attach automatically
Every field, breadcrumbs and contexts included, passes through beforeSend(event) => event | null before it leaves the client, so you can redact or drop anything.
Options
| option | default | meaning |
|---|---|---|
| dsn | none | Required. Your project DSN. |
| environment | "" | e.g. production, staging. |
| release | "" | e.g. webapp@2.0.0. |
| beforeSend | null | (event) => event | null: scrub PII, or drop the event. |
| autoSessionHandlers | true | Install the global error handlers. |
| debug | false | Log transport problems to console.warn. |
Then
Naming is the part that is expensive to change once data exists under it, and journeys are what the names are for. Both are the same whatever you wrote the app in:
- Journeys, and the one rule about entry steps people get wrong by hand.
- Naming things, before you have a week of data under a name you regret.
- Coming from Sentry: keep the SDK, change the DSN.
Something here wrong or missing? cyclopes@localhost.co.zw.