/** * Event properties a customer's app can send with `track`. * * Deliberately not `unknown`: these values are serialised onto the wire and * rendered in a dashboard, so a nested object or a function would either be * dropped upstream or arrive as `[object Object]`. Restricting the type puts * that failure at the call site, in the customer's editor. */ type HeyCatchProperties = Record; /** * Person properties, with the canonical keys the dashboard reads. * * The named keys are the contract: `email` and `name` drive how a person is * displayed, `plan` powers plan-level breakdowns, `signup_date` anchors * account age. Use these exact names whenever the app has the value - * customer A writing `plan` while customer B writes `tier` is what makes * every cross-project feature impossible. * * Everything else is deliberately open: extra context about the user * (payment state, feature usage, whatever is useful) passes through as-is, * nested values included - an unlisted key must never be a type error at * the customer's call site. */ interface HeyCatchPersonProperties { /** How the dashboard shows the person. A property - never the identity. */ email?: string; /** Display name, alongside `email`. */ name?: string; /** The app's own plan name (`free`, `pro`, …). Values are yours; the key is the contract. */ plan?: string; /** ISO 8601. Belongs in set-once, so a later sign-in cannot move it. */ signup_date?: string; /** Anything else useful about the user - passes through untouched. */ [key: string]: unknown; } /** * Person properties, in the two flavours every analytics backend distinguishes. * * - `set` overwrites on every send - use for values that change (`plan`, * `last_seen_at`). * - `setOnce` writes only if the person does not already have the key - use for * values that describe the beginning and must not be overwritten by a later * visit (`signup_date`, `initial_referrer`). * * Named `set` / `setOnce` rather than the transport's own `$set` / `$set_once`: * the `$` names are an implementation detail of who we send to, and this is a * published customer-facing API that must not change if that ever does. */ interface PersonPropertyUpdate { set?: HeyCatchPersonProperties; setOnce?: HeyCatchPersonProperties; } /** * Options for `trackEvent`, as bundler-resolved projects see them. This is * the shape behind the `browser` and `default` type conditions - and * `moduleResolution: "bundler"` (Next.js, Vite) type-checks a whole * project, route handlers included, against it, which is why `userId` must * exist here even though the browser ignores it: one tsconfig cannot hold * two type surfaces per file. Pure-Node backends (`nodenext`) resolve the * `node` condition instead and get {@link ServerTrackOptions}, where the * requirement is enforced at compile time. * * Delivery timing is deliberately not an option: the browser batches (and * the transport flushes the queue on page unload), the server sends * immediately - per-environment behaviour is the SDK's call, not the * caller's. */ interface TrackOptions extends PersonPropertyUpdate { /** * Who the event belongs to. REQUIRED on the server - there is no ambient * person in a webhook, and the server entry's own types (and its runtime * guard) enforce it. Ignored in the browser, where the session already * knows. Must be the same stable internal user id passed to * `setIdentity`. */ userId?: string; /** * Server only, and only meaningful when the event happens during the * user's OWN request (an API route, a server action): pass the incoming * request and the event joins their live browser session - the web SDK * stamps a session header on same-origin requests automatically. A * webhook has no request to pass. Ignored in the browser. */ request?: TracingRequest; } /** * The slice of an incoming request the server entry reads. Both shapes in * the wild satisfy it: a Fetch `Request` (Next route handlers, Hono, Bun - * `headers.get`) and a Node/Express `req` (`headers` as a plain * lowercase-keyed object), so the same call works in either. */ interface TracingRequest { headers: { get: (name: string) => string | null; } | Record; } /** * Options for the server `trackEvent` - REQUIRED, with a REQUIRED * `userId`: a server process has no ambient person, so every event must * say who it belongs to. Must be the same stable internal id the app * passes to `setIdentity` in the browser; that is what joins the two. The * server entry exports this shape as its `TrackOptions`. */ interface ServerTrackOptions extends TrackOptions { userId: string; } /** * Which HeyCatch backend this bundle was built against - `dev` or `prod`. * Introspection for debugging ("which stage bundle is this page running?"), * and stamped on every event as `heycatch_sdk_stage`. Describes OUR backend, * not the customer's own app environment. */ declare const STAGE: "dev" | "prod"; declare const SDK_VERSION: string; /** * Frameworks the install guides can detect. Kept as a type alias, not a * runtime array: `apps/landing-web/tests/agent-install-guides.spec.ts` * reads this source text and asserts the set matches the guides' tables, * so docs, types, and reported values cannot drift. */ type KnownFramework = 'nextjs' | 'vite-react' | 'react' | 'vue' | 'svelte' | 'astro' | 'angular' | 'react-native' | 'web'; /** Coding agents that run the install. `other` is the catch-all. */ type KnownInstallAgent = 'lovable' | 'bolt' | 'v0' | 'replit' | 'cursor' | 'claude-code' | 'codex' | 'windsurf' | 'other'; /** * Who installed the SDK and into what. Stamped as super-properties on * every event - the dashboard reads them off the event stream, so there * is no separate install call to make or to fail. * * `(string & {})` keeps the known ids as editor completions while still * accepting anything: a framework we haven't listed yet must not block * an install. */ interface HeyCatchInstall { /** Framework id from the install guide, e.g. `nextjs`. */ framework?: KnownFramework | (string & {}); /** That framework's major version, e.g. `15`. */ frameworkVersion?: string; /** The agent doing the install, e.g. `claude-code`. */ agent?: KnownInstallAgent | (string & {}); } /** Configuration for {@link init}. */ interface HeyCatchConfig { /** Your project's publishable key (`hck_pk_...`). */ projectKey: string; /** Install metadata, stamped on every event. Omit any field you can't determine. */ install?: HeyCatchInstall; /** * Hostnames of the app's own backends on OTHER origins (e.g. * `api.example.com`) whose requests should carry the session header for * server-side `trackEvent({ request })` linking. Same-origin requests * are covered automatically - omit this unless the API lives elsewhere. */ tracingHosts?: string[]; } export { type HeyCatchConfig as H, type PersonPropertyUpdate as P, SDK_VERSION as S, type TrackOptions as T, type HeyCatchPersonProperties as a, type HeyCatchProperties as b, STAGE as c, type ServerTrackOptions as d };