tracwell
v0.2.0
Published
Small, privacy-aware browser analytics SDK for Tracwell
Maintainers
Readme
SDK
Browser analytics SDK for page views, custom events, identity, batching, and bounded delivery retries.
Script installation
The production browser artifact is served from the existing Collector domain:
<script
defer
src="https://collect.tracwell.app/script.js"
data-project-key="tw_live_..."
data-collection-mode="private"
data-consent="granted"
data-respect-do-not-track="true"
></script>Loading the script captures the initial page view and exposes the same client
as window.tracwell for custom events:
window.tracwell?.track("signup_completed", { plan: "starter" });The script derives /v1/events from its own origin, so the same artifact works
against the loopback Collector during local development. The production bundle
reports SDK version 0.2.0, targets ES2022-capable browsers, and has a 4.7 KB
gzip budget enforced by
build:cdn.
Public API
Install the npm package:
pnpm add tracwellimport { createTracwell } from "tracwell";
const tracwell = createTracwell({
collectionMode: "private",
projectKey: "tw_live_...",
});
tracwell.track("signup_completed", { plan: "starter" });
tracwell.identify("customer_123");
const session = tracwell.getSession();createTracwell() starts collection immediately in the browser and captures
the initial page view by default. It must be called after the document is
available, not during server rendering.
Framework compatibility
The npm SDK is a framework-neutral ESM browser library. It has no React, Vue,
Svelte, Angular, router, or rendering-framework dependency. Importing it during
SSR is safe; call createTracwell() from that framework's browser mount or
hydration lifecycle after document is available.
Multi-page applications capture a page view each time the browser loads the
application. Single-page applications using the standard History API receive
automatic page views for pushState, replaceState, and back/forward
navigation. This covers the default path-based routers in modern web
frameworks. Hash fragments remain intentionally excluded from analytics URLs;
hash-only navigation does not create a page view.
Framework adapters should own only lifecycle integration and dependency injection. Event validation, identity, attribution, navigation observation, batching, retries, consent, and delivery remain in this core package.
Responsibilities
- Generate stable event and batch IDs before delivery.
- Capture initial page views, SPA navigation, referrer, UTM, and tagged-link context.
- Expose typed custom-event and identity APIs.
- Batch only against the limits exported by
@tracwell/contracts. - Retry bounded transient failures without changing event IDs.
- Respect configured consent and Do Not Track behavior before collection.
The SDK must remain browser-only, small, non-blocking, and unable to set trusted collector fields. It must not contain secrets or connect to ClickHouse.
Identity and attribution
Collection mode is explicit:
privateis the default. It never reads or writes cookies,localStorage, orsessionStorage;identify()reportsIDENTIFY_UNAVAILABLE. Anonymous and session values exist only in memory until the collector replaces them.productpersists first-party anonymous, session, and optional user identity inlocalStorage. Configure consent before collection where required.Private mode rejects an explicit
persistence: "localStorage"instead of silently weakening the project's privacy behavior.anonymous_idis first-party and persists per project unless persistence is disabled or identity is reset.session_idrotates after 30 minutes without a captured event.A different non-empty UTM campaign starts a new session immediately.
The first external referrer and UTM values are preserved for the session.
The SDK preserves the absolute referrer. Trusted ClickHouse row mapping uses registered-domain/public-suffix parsing so sibling subdomains are not reported as external acquisition.
identify()validates the supplied opaque user ID before associating it with future events.Identifying a different user on the same installation rotates both anonymous identity and session before recording the new identity. Call
reset()on logout so an anonymous post-logout visit also starts cleanly.getSession()returns the active Product-modeanonymousId,sessionId, and optionaluserIdso a server-created checkout can carry the same identifiers into an authenticated revenue event. It returnsundefinedin Private mode, without consent, when Do Not Track blocks collection, or after shutdown.Treat the returned IDs as attribution context, not authorization. Revenue
user_idmust come from the authenticated account or verified payment webhook rather than a client-supplied value.Hash-only URL changes do not create duplicate SPA page views.
URL fragments are excluded from captured URLs and
pathcontains only the pathname.
Product identity uses localStorage, never fingerprinting. If storage is
disabled or unavailable, the SDK explicitly reports STORAGE_UNAVAILABLE
through onError and uses in-memory identity for the page lifetime.
Delivery semantics
- Events receive lightweight client preflight validation before entering memory. The Collector remains the canonical Zod validation boundary for all untrusted browser input.
- Normal cross-origin delivery uses a safelisted
text/plainJSON body so it does not add a CORS preflight request; the collector still parses and validates the body as JSON. - Batches default to 25 events and are reduced when necessary to remain within the shared 48 KiB limit.
- Normal delivery reports
acceptedonly after a matching202receipt confirms the batch ID and event count. - Network errors,
408,429, and5xxresponses use bounded in-memory retry without changing event or batch IDs. - Other
4xxresponses reportDELIVERY_REJECTEDand are not retried. - Page-hide and hidden-document delivery prefers
sendBeacon; a successful browser handoff reportshanded_off, neveraccepted. - Exhausted delivery is surfaced through
onErrorwith the affected event IDs.
The default collector endpoint is
https://collect.tracwell.app/v1/events. Production overrides must use HTTPS;
loopback HTTP is allowed only for local development.
Privacy controls
consent: "required"prevents identity creation and collection untilsetConsent("granted").- Revoking consent stops listeners, removes queued events, and resets identity.
- Do Not Track is respected by default and can be disabled explicitly per customer configuration.
persistence: "none"keeps all identifiers in memory.- Private collection forces
persistence: "none"and blocksidentify().
Structure
src/
index.ts Stable public exports and browser-only factory
script.ts Lean self-starting CDN entry and `window.tracwell` exposure
script-config.ts Strict script-dataset parsing and normalized CDN defaults
client.ts Consent-aware API and lifecycle orchestration
config.ts Defaults and configuration validation
identity.ts Anonymous, session, and user identity state
attribution.ts First-touch-per-session UTM and referrer capture
events.ts Context creation and contract-validated events
transport.ts Size-aware batching, receipts, beacon, and retries
preflight.ts Small client-side event and receipt checks
runtime.ts Browser API adapter
types.ts Public configuration, client, delivery, and error types
client.test.ts Deterministic SDK behavior and failure tests
index.test.ts SSR import and browser-initialization boundary
scripts/
build-cdn.mjs Minified browser build and gzip-size enforcement
build-npm.mjs Self-contained ESM package build
smoke-npm.mjs Packed runtime and TypeScript consumer smoke test
static/
_headers Cross-origin browser asset headersKeep browser globals isolated in runtime.ts, keep index.ts as the stable
public API, and test behavior through an injected deterministic runtime.
Dependencies
@tracwell/contractsfor inferred types and shared limits- Browser platform APIs for navigation, lifecycle, and delivery
esbuildat build time for the minified browser artifact
The published npm artifact bundles its runtime contract constants and ships generated declarations, so consumers do not need the private contracts workspace. Compile-time compatibility checks keep the duplicated public property and issue types aligned with the canonical contracts package.
The npm package is distributed under the MIT License. Publishing the package does not make the private application repository public.
Verify
pnpm --filter {packages/sdk} typecheck
pnpm --filter {packages/sdk} test
pnpm --filter {packages/sdk} build:cdn
pnpm --filter {packages/sdk} check:package