@heycatch/sdk 0.6.0 → 0.7.0-dev.351

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Event properties a customer's app can send with `track`.
3
+ *
4
+ * Deliberately not `unknown`: these values are serialised onto the wire and
5
+ * rendered in a dashboard, so a nested object or a function would either be
6
+ * dropped upstream or arrive as `[object Object]`. Restricting the type puts
7
+ * that failure at the call site, in the customer's editor.
8
+ */
9
+ type HeyCatchProperties = Record<string, string | number | boolean | null>;
10
+ /**
11
+ * Person properties, with the canonical keys the dashboard reads.
12
+ *
13
+ * The named keys are the contract: `email` and `name` drive how a person is
14
+ * displayed, `plan` powers plan-level breakdowns, `signup_date` anchors
15
+ * account age. Use these exact names whenever the app has the value -
16
+ * customer A writing `plan` while customer B writes `tier` is what makes
17
+ * every cross-project feature impossible.
18
+ *
19
+ * Everything else is deliberately open: extra context about the user
20
+ * (payment state, feature usage, whatever is useful) passes through as-is,
21
+ * nested values included - an unlisted key must never be a type error at
22
+ * the customer's call site.
23
+ */
24
+ interface HeyCatchPersonProperties {
25
+ /** How the dashboard shows the person. A property - never the identity. */
26
+ email?: string;
27
+ /** Display name, alongside `email`. */
28
+ name?: string;
29
+ /** The app's own plan name (`free`, `pro`, …). Values are yours; the key is the contract. */
30
+ plan?: string;
31
+ /** ISO 8601. Belongs in set-once, so a later sign-in cannot move it. */
32
+ signup_date?: string;
33
+ /** Anything else useful about the user - passes through untouched. */
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * Person properties, in the two flavours every analytics backend distinguishes.
38
+ *
39
+ * - `set` overwrites on every send - use for values that change (`plan`,
40
+ * `last_seen_at`).
41
+ * - `setOnce` writes only if the person does not already have the key - use for
42
+ * values that describe the beginning and must not be overwritten by a later
43
+ * visit (`signup_date`, `initial_referrer`).
44
+ *
45
+ * Named `set` / `setOnce` rather than the transport's own `$set` / `$set_once`:
46
+ * the `$` names are an implementation detail of who we send to, and this is a
47
+ * published customer-facing API that must not change if that ever does.
48
+ */
49
+ interface PersonPropertyUpdate {
50
+ set?: HeyCatchPersonProperties;
51
+ setOnce?: HeyCatchPersonProperties;
52
+ }
53
+ /**
54
+ * Options for `trackEvent`, as bundler-resolved projects see them. This is
55
+ * the shape behind the `browser` and `default` type conditions - and
56
+ * `moduleResolution: "bundler"` (Next.js, Vite) type-checks a whole
57
+ * project, route handlers included, against it, which is why `userId` must
58
+ * exist here even though the browser ignores it: one tsconfig cannot hold
59
+ * two type surfaces per file. Pure-Node backends (`nodenext`) resolve the
60
+ * `node` condition instead and get {@link ServerTrackOptions}, where the
61
+ * requirement is enforced at compile time.
62
+ *
63
+ * Delivery timing is deliberately not an option: the browser batches (and
64
+ * the transport flushes the queue on page unload), the server sends
65
+ * immediately - per-environment behaviour is the SDK's call, not the
66
+ * caller's.
67
+ */
68
+ interface TrackOptions extends PersonPropertyUpdate {
69
+ /**
70
+ * Who the event belongs to. REQUIRED on the server - there is no ambient
71
+ * person in a webhook, and the server entry's own types (and its runtime
72
+ * guard) enforce it. Ignored in the browser, where the session already
73
+ * knows. Must be the same stable internal user id passed to
74
+ * `setIdentity`.
75
+ */
76
+ userId?: string;
77
+ /**
78
+ * Server only, and only meaningful when the event happens during the
79
+ * user's OWN request (an API route, a server action): pass the incoming
80
+ * request and the event joins their live browser session - the web SDK
81
+ * stamps a session header on same-origin requests automatically. A
82
+ * webhook has no request to pass. Ignored in the browser.
83
+ */
84
+ request?: TracingRequest;
85
+ }
86
+ /**
87
+ * The slice of an incoming request the server entry reads. Both shapes in
88
+ * the wild satisfy it: a Fetch `Request` (Next route handlers, Hono, Bun -
89
+ * `headers.get`) and a Node/Express `req` (`headers` as a plain
90
+ * lowercase-keyed object), so the same call works in either.
91
+ */
92
+ interface TracingRequest {
93
+ headers: {
94
+ get: (name: string) => string | null;
95
+ } | Record<string, string | string[] | undefined>;
96
+ }
97
+ /**
98
+ * Options for the server `trackEvent` - REQUIRED, with a REQUIRED
99
+ * `userId`: a server process has no ambient person, so every event must
100
+ * say who it belongs to. Must be the same stable internal id the app
101
+ * passes to `setIdentity` in the browser; that is what joins the two. The
102
+ * server entry exports this shape as its `TrackOptions`.
103
+ */
104
+ interface ServerTrackOptions extends TrackOptions {
105
+ userId: string;
106
+ }
107
+
108
+ /**
109
+ * Which HeyCatch backend this bundle was built against - `dev` or `prod`.
110
+ * Introspection for debugging ("which stage bundle is this page running?"),
111
+ * and stamped on every event as `heycatch_sdk_stage`. Describes OUR backend,
112
+ * not the customer's own app environment.
113
+ */
114
+ declare const STAGE: "dev" | "prod";
115
+ declare const SDK_VERSION: string;
116
+ /** Stamped when the installer could not determine a value. */
117
+ declare const UNKNOWN_INSTALL_VALUE: "unknown";
118
+ /**
119
+ * Frameworks the install guides can detect. **Written out as a union, not
120
+ * derived from the array below**, because
121
+ * `apps/landing-web/tests/agent-install-guides.spec.ts` matches this
122
+ * declaration as source text to assert the set agrees with the guide's
123
+ * detection table. Collapsing it to `(typeof …)[number]` leaves that guard
124
+ * matching nothing.
125
+ *
126
+ * For the same reason, do not restate the declaration's opening line anywhere
127
+ * above it - including in a comment. The guard takes the FIRST match in the
128
+ * file, so a prose copy of it wins and yields zero ids. (Written here after
129
+ * doing exactly that; the spec's vacuity check is what caught it.)
130
+ */
131
+ type KnownFramework = 'nextjs' | 'vite-react' | 'react' | 'vue' | 'svelte' | 'astro' | 'angular' | 'react-native' | 'web';
132
+ /** Coding agents that run the install. `other` is the catch-all. */
133
+ type KnownInstallAgent = 'lovable' | 'bolt' | 'v0' | 'replit' | 'cursor' | 'claude-code' | 'codex' | 'windsurf' | 'other';
134
+ /**
135
+ * The same two sets at RUNTIME, for code that has to ask "is this reported
136
+ * value one we recognise?" - a question a type union cannot answer, because
137
+ * `install.framework` is deliberately `KnownFramework | (string & {})` so an
138
+ * unlisted framework still installs cleanly.
139
+ *
140
+ * The consumer is the internal stats page, which flags an unrecognised or
141
+ * missing value rather than silently rendering it as though it were expected.
142
+ *
143
+ * Drift between each array and its union is a **compile error** (see the
144
+ * assertions below), so this is a second spelling, not a second source.
145
+ */
146
+ declare const KNOWN_FRAMEWORKS: readonly ["nextjs", "vite-react", "react", "vue", "svelte", "astro", "angular", "react-native", "web"];
147
+ declare const KNOWN_INSTALL_AGENTS: readonly ["lovable", "bolt", "v0", "replit", "cursor", "claude-code", "codex", "windsurf", "other"];
148
+ /**
149
+ * Who installed the SDK and into what. Stamped as super-properties on
150
+ * every event - the dashboard reads them off the event stream, so there
151
+ * is no separate install call to make or to fail.
152
+ *
153
+ * `(string & {})` keeps the known ids as editor completions while still
154
+ * accepting anything: a framework we haven't listed yet must not block
155
+ * an install.
156
+ */
157
+ interface HeyCatchInstall {
158
+ /** Framework id from the install guide, e.g. `nextjs`. */
159
+ framework?: KnownFramework | (string & {});
160
+ /** That framework's major version, e.g. `15`. */
161
+ frameworkVersion?: string;
162
+ /** The agent doing the install, e.g. `claude-code`. */
163
+ agent?: KnownInstallAgent | (string & {});
164
+ }
165
+ /** Configuration for {@link init}. */
166
+ interface HeyCatchConfig {
167
+ /** Your project's publishable key (`hck_pk_...`). */
168
+ projectKey: string;
169
+ /** Install metadata, stamped on every event. Omit any field you can't determine. */
170
+ install?: HeyCatchInstall;
171
+ /**
172
+ * Hostnames of the app's own backends on OTHER origins (e.g.
173
+ * `api.example.com`) whose requests should carry the session header for
174
+ * server-side `trackEvent({ request })` linking. Same-origin requests
175
+ * are covered automatically - omit this unless the API lives elsewhere.
176
+ */
177
+ tracingHosts?: string[];
178
+ }
179
+
180
+ export { type HeyCatchConfig as H, KNOWN_FRAMEWORKS as K, type PersonPropertyUpdate as P, SDK_VERSION as S, type TrackOptions as T, UNKNOWN_INSTALL_VALUE as U, type HeyCatchPersonProperties as a, type HeyCatchProperties as b, KNOWN_INSTALL_AGENTS as c, STAGE as d, type ServerTrackOptions as e };
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Event properties a customer's app can send with `track`.
3
+ *
4
+ * Deliberately not `unknown`: these values are serialised onto the wire and
5
+ * rendered in a dashboard, so a nested object or a function would either be
6
+ * dropped upstream or arrive as `[object Object]`. Restricting the type puts
7
+ * that failure at the call site, in the customer's editor.
8
+ */
9
+ type HeyCatchProperties = Record<string, string | number | boolean | null>;
10
+ /**
11
+ * Person properties, with the canonical keys the dashboard reads.
12
+ *
13
+ * The named keys are the contract: `email` and `name` drive how a person is
14
+ * displayed, `plan` powers plan-level breakdowns, `signup_date` anchors
15
+ * account age. Use these exact names whenever the app has the value -
16
+ * customer A writing `plan` while customer B writes `tier` is what makes
17
+ * every cross-project feature impossible.
18
+ *
19
+ * Everything else is deliberately open: extra context about the user
20
+ * (payment state, feature usage, whatever is useful) passes through as-is,
21
+ * nested values included - an unlisted key must never be a type error at
22
+ * the customer's call site.
23
+ */
24
+ interface HeyCatchPersonProperties {
25
+ /** How the dashboard shows the person. A property - never the identity. */
26
+ email?: string;
27
+ /** Display name, alongside `email`. */
28
+ name?: string;
29
+ /** The app's own plan name (`free`, `pro`, …). Values are yours; the key is the contract. */
30
+ plan?: string;
31
+ /** ISO 8601. Belongs in set-once, so a later sign-in cannot move it. */
32
+ signup_date?: string;
33
+ /** Anything else useful about the user - passes through untouched. */
34
+ [key: string]: unknown;
35
+ }
36
+ /**
37
+ * Person properties, in the two flavours every analytics backend distinguishes.
38
+ *
39
+ * - `set` overwrites on every send - use for values that change (`plan`,
40
+ * `last_seen_at`).
41
+ * - `setOnce` writes only if the person does not already have the key - use for
42
+ * values that describe the beginning and must not be overwritten by a later
43
+ * visit (`signup_date`, `initial_referrer`).
44
+ *
45
+ * Named `set` / `setOnce` rather than the transport's own `$set` / `$set_once`:
46
+ * the `$` names are an implementation detail of who we send to, and this is a
47
+ * published customer-facing API that must not change if that ever does.
48
+ */
49
+ interface PersonPropertyUpdate {
50
+ set?: HeyCatchPersonProperties;
51
+ setOnce?: HeyCatchPersonProperties;
52
+ }
53
+ /**
54
+ * Options for `trackEvent`, as bundler-resolved projects see them. This is
55
+ * the shape behind the `browser` and `default` type conditions - and
56
+ * `moduleResolution: "bundler"` (Next.js, Vite) type-checks a whole
57
+ * project, route handlers included, against it, which is why `userId` must
58
+ * exist here even though the browser ignores it: one tsconfig cannot hold
59
+ * two type surfaces per file. Pure-Node backends (`nodenext`) resolve the
60
+ * `node` condition instead and get {@link ServerTrackOptions}, where the
61
+ * requirement is enforced at compile time.
62
+ *
63
+ * Delivery timing is deliberately not an option: the browser batches (and
64
+ * the transport flushes the queue on page unload), the server sends
65
+ * immediately - per-environment behaviour is the SDK's call, not the
66
+ * caller's.
67
+ */
68
+ interface TrackOptions extends PersonPropertyUpdate {
69
+ /**
70
+ * Who the event belongs to. REQUIRED on the server - there is no ambient
71
+ * person in a webhook, and the server entry's own types (and its runtime
72
+ * guard) enforce it. Ignored in the browser, where the session already
73
+ * knows. Must be the same stable internal user id passed to
74
+ * `setIdentity`.
75
+ */
76
+ userId?: string;
77
+ /**
78
+ * Server only, and only meaningful when the event happens during the
79
+ * user's OWN request (an API route, a server action): pass the incoming
80
+ * request and the event joins their live browser session - the web SDK
81
+ * stamps a session header on same-origin requests automatically. A
82
+ * webhook has no request to pass. Ignored in the browser.
83
+ */
84
+ request?: TracingRequest;
85
+ }
86
+ /**
87
+ * The slice of an incoming request the server entry reads. Both shapes in
88
+ * the wild satisfy it: a Fetch `Request` (Next route handlers, Hono, Bun -
89
+ * `headers.get`) and a Node/Express `req` (`headers` as a plain
90
+ * lowercase-keyed object), so the same call works in either.
91
+ */
92
+ interface TracingRequest {
93
+ headers: {
94
+ get: (name: string) => string | null;
95
+ } | Record<string, string | string[] | undefined>;
96
+ }
97
+ /**
98
+ * Options for the server `trackEvent` - REQUIRED, with a REQUIRED
99
+ * `userId`: a server process has no ambient person, so every event must
100
+ * say who it belongs to. Must be the same stable internal id the app
101
+ * passes to `setIdentity` in the browser; that is what joins the two. The
102
+ * server entry exports this shape as its `TrackOptions`.
103
+ */
104
+ interface ServerTrackOptions extends TrackOptions {
105
+ userId: string;
106
+ }
107
+
108
+ /**
109
+ * Which HeyCatch backend this bundle was built against - `dev` or `prod`.
110
+ * Introspection for debugging ("which stage bundle is this page running?"),
111
+ * and stamped on every event as `heycatch_sdk_stage`. Describes OUR backend,
112
+ * not the customer's own app environment.
113
+ */
114
+ declare const STAGE: "dev" | "prod";
115
+ declare const SDK_VERSION: string;
116
+ /** Stamped when the installer could not determine a value. */
117
+ declare const UNKNOWN_INSTALL_VALUE: "unknown";
118
+ /**
119
+ * Frameworks the install guides can detect. **Written out as a union, not
120
+ * derived from the array below**, because
121
+ * `apps/landing-web/tests/agent-install-guides.spec.ts` matches this
122
+ * declaration as source text to assert the set agrees with the guide's
123
+ * detection table. Collapsing it to `(typeof …)[number]` leaves that guard
124
+ * matching nothing.
125
+ *
126
+ * For the same reason, do not restate the declaration's opening line anywhere
127
+ * above it - including in a comment. The guard takes the FIRST match in the
128
+ * file, so a prose copy of it wins and yields zero ids. (Written here after
129
+ * doing exactly that; the spec's vacuity check is what caught it.)
130
+ */
131
+ type KnownFramework = 'nextjs' | 'vite-react' | 'react' | 'vue' | 'svelte' | 'astro' | 'angular' | 'react-native' | 'web';
132
+ /** Coding agents that run the install. `other` is the catch-all. */
133
+ type KnownInstallAgent = 'lovable' | 'bolt' | 'v0' | 'replit' | 'cursor' | 'claude-code' | 'codex' | 'windsurf' | 'other';
134
+ /**
135
+ * The same two sets at RUNTIME, for code that has to ask "is this reported
136
+ * value one we recognise?" - a question a type union cannot answer, because
137
+ * `install.framework` is deliberately `KnownFramework | (string & {})` so an
138
+ * unlisted framework still installs cleanly.
139
+ *
140
+ * The consumer is the internal stats page, which flags an unrecognised or
141
+ * missing value rather than silently rendering it as though it were expected.
142
+ *
143
+ * Drift between each array and its union is a **compile error** (see the
144
+ * assertions below), so this is a second spelling, not a second source.
145
+ */
146
+ declare const KNOWN_FRAMEWORKS: readonly ["nextjs", "vite-react", "react", "vue", "svelte", "astro", "angular", "react-native", "web"];
147
+ declare const KNOWN_INSTALL_AGENTS: readonly ["lovable", "bolt", "v0", "replit", "cursor", "claude-code", "codex", "windsurf", "other"];
148
+ /**
149
+ * Who installed the SDK and into what. Stamped as super-properties on
150
+ * every event - the dashboard reads them off the event stream, so there
151
+ * is no separate install call to make or to fail.
152
+ *
153
+ * `(string & {})` keeps the known ids as editor completions while still
154
+ * accepting anything: a framework we haven't listed yet must not block
155
+ * an install.
156
+ */
157
+ interface HeyCatchInstall {
158
+ /** Framework id from the install guide, e.g. `nextjs`. */
159
+ framework?: KnownFramework | (string & {});
160
+ /** That framework's major version, e.g. `15`. */
161
+ frameworkVersion?: string;
162
+ /** The agent doing the install, e.g. `claude-code`. */
163
+ agent?: KnownInstallAgent | (string & {});
164
+ }
165
+ /** Configuration for {@link init}. */
166
+ interface HeyCatchConfig {
167
+ /** Your project's publishable key (`hck_pk_...`). */
168
+ projectKey: string;
169
+ /** Install metadata, stamped on every event. Omit any field you can't determine. */
170
+ install?: HeyCatchInstall;
171
+ /**
172
+ * Hostnames of the app's own backends on OTHER origins (e.g.
173
+ * `api.example.com`) whose requests should carry the session header for
174
+ * server-side `trackEvent({ request })` linking. Same-origin requests
175
+ * are covered automatically - omit this unless the API lives elsewhere.
176
+ */
177
+ tracingHosts?: string[];
178
+ }
179
+
180
+ export { type HeyCatchConfig as H, KNOWN_FRAMEWORKS as K, type PersonPropertyUpdate as P, SDK_VERSION as S, type TrackOptions as T, UNKNOWN_INSTALL_VALUE as U, type HeyCatchPersonProperties as a, type HeyCatchProperties as b, KNOWN_INSTALL_AGENTS as c, STAGE as d, type ServerTrackOptions as e };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heycatch/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0-dev.351",
4
4
  "description": "HeyCatch SDK",
5
5
  "keywords": [
6
6
  "analytics",
@@ -23,25 +23,65 @@
23
23
  "type": "module",
24
24
  "exports": {
25
25
  ".": {
26
- "types": "./dist/index.d.ts",
27
- "import": "./dist/index.js",
28
- "require": "./dist/index.cjs"
26
+ "react-native": {
27
+ "types": "./dist/react-native.d.ts",
28
+ "import": "./dist/react-native.js",
29
+ "require": "./dist/react-native.cjs"
30
+ },
31
+ "browser": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "require": "./dist/index.cjs"
35
+ },
36
+ "node": {
37
+ "types": "./dist/server.d.ts",
38
+ "import": "./dist/server.js",
39
+ "require": "./dist/server.cjs"
40
+ },
41
+ "default": {
42
+ "types": "./dist/index.d.ts",
43
+ "import": "./dist/index.js",
44
+ "require": "./dist/index.cjs"
45
+ }
46
+ },
47
+ "./server": {
48
+ "types": "./dist/server.d.ts",
49
+ "import": "./dist/server.js",
50
+ "require": "./dist/server.cjs"
51
+ },
52
+ "./react-native": {
53
+ "types": "./dist/react-native.d.ts",
54
+ "import": "./dist/react-native.js",
55
+ "require": "./dist/react-native.cjs"
29
56
  }
30
57
  },
31
58
  "main": "./dist/index.cjs",
32
59
  "module": "./dist/index.js",
33
60
  "types": "./dist/index.d.ts",
34
61
  "files": [
35
- "dist/index.js",
36
- "dist/index.cjs",
37
- "dist/index.d.ts",
38
- "dist/index.d.cts",
62
+ "dist/*.js",
63
+ "dist/*.cjs",
64
+ "dist/*.d.ts",
65
+ "dist/*.d.cts",
39
66
  "README.md",
40
- "AGENTS.md",
41
- "docs",
42
67
  "LICENSE",
43
68
  "THIRD-PARTY-LICENSES.md"
44
69
  ],
70
+ "dependencies": {
71
+ "posthog-react-native": "^4.61.1"
72
+ },
73
+ "peerDependencies": {
74
+ "expo-router": "*",
75
+ "react": "^18.2.0 || ^19.0.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "expo-router": {
79
+ "optional": true
80
+ },
81
+ "react": {
82
+ "optional": true
83
+ }
84
+ },
45
85
  "publishConfig": {
46
86
  "access": "public",
47
87
  "registry": "https://registry.npmjs.org/"
package/AGENTS.md DELETED
@@ -1,132 +0,0 @@
1
- # @heycatch/sdk - setup guide
2
-
3
- Offline copy, bundled with this release. **If you can fetch URLs, follow
4
- https://heycatch.ai/agents.md instead** - it routes to a detailed guide per
5
- framework and is updated between releases. Use this file only when you cannot
6
- fetch.
7
-
8
- You need the user's project key - a string starting with `hck_`, included in
9
- the prompt that pointed you here. If you don't have one, stop and tell the
10
- user: "Copy the install prompt from your project's Install page in the HeyCatch
11
- dashboard - it includes your project key."
12
-
13
- ## 1. Install the package
14
-
15
- Add the `@heycatch/sdk` npm package as a regular dependency, latest version,
16
- with whatever package manager the project uses. No shell available? Add it to
17
- `dependencies` in `package.json`; your platform installs it on rebuild. The
18
- package has no install scripts and no native code.
19
-
20
- ## 2. Call `init` once at the client entry point
21
-
22
- ```ts
23
- import { init } from '@heycatch/sdk';
24
-
25
- init({
26
- projectKey: 'hck_…', // the key from the prompt, inlined
27
- install: {
28
- framework: 'nextjs', // the id from the table below (a real value)
29
- frameworkVersion: '<major>', // the detected major, e.g. 16
30
- agent: '<your-id>', // ← YOUR id, not a copied example. See below.
31
- },
32
- });
33
- ```
34
-
35
- Where that call goes - read `package.json` and pick the first matching row.
36
- Check **both** `dependencies` and `devDependencies`: `vite`, `astro` and most
37
- build-time frameworks are dev-only.
38
-
39
- | Project has | framework id | `init` goes in |
40
- | ----------------------- | ------------ | ----------------------------------------------------------------------------------------------------------- |
41
- | `next` >= 15.3 | `nextjs` | `instrumentation-client.ts` at the project root |
42
- | `next` < 15.3, `app/` | `nextjs` | a `'use client'` component in the root layout |
43
- | `next` < 15.3, `pages/` | `nextjs` | top of `pages/_app.tsx`, module scope |
44
- | `vite` and `react` | `vite-react` | `src/main.tsx`, above the render call |
45
- | `react` without `vite` | `react` | `src/index.tsx` / `.jsx` / `.js`, whichever exists, above the render call |
46
- | `nuxt` | `vue` | `plugins/heycatch.client.ts` |
47
- | `vue` (plain Vite) | `vue` | `src/main.ts` |
48
- | `@sveltejs/kit` | `svelte` | `src/hooks.client.ts` |
49
- | `svelte` (plain Vite) | `svelte` | `src/main.ts` |
50
- | `astro` | `astro` | a `<script>` in the base layout |
51
- | `@angular/core` | `angular` | `src/main.ts` |
52
- | anything else | `web` | the client entry file that boots the app in the browser |
53
- | no `package.json` | `web` | `<script type="module">` in `<head>`, importing from `https://esm.sh/@heycatch/sdk@0.3.0` (pin the version) |
54
-
55
- Pick by what exists in the repo - if the entry file is named something else,
56
- use that. Pass that row's framework id and the framework's major version as
57
- `install.framework` / `install.frameworkVersion`. `install.agent` is who YOU
58
- are, lowercase: `lovable`, `bolt`, `v0`, `replit`, `cursor`, `claude-code`,
59
- `codex`, `windsurf`, or `other`. Omit any field you cannot determine.
60
-
61
- Rules for every framework:
62
-
63
- - Module scope. Not inside a component, `useEffect`, lifecycle hook, or
64
- handler. The one exception is the pre-15.3 App Router row above.
65
- - Static import - no dynamic `import()`, no lazy chunk. Parts of the SDK act
66
- the moment the bundle evaluates.
67
- - `init` is idempotent and SSR-safe - no guards needed.
68
- - Inline the key. It is a publishable key, safe in the client bundle by
69
- design - do not build env-var plumbing for it.
70
- - Do not pass `apiHost` - the default is correct.
71
- - Autocapture starts immediately - do NOT instrument individual events, and do
72
- not add route or history listeners.
73
- - Browser only. There is no server-side capture yet, so server routes,
74
- handlers, and jobs are not tracked - do not build a substitute. If the
75
- project is React Native, Flutter, another non-browser target, or a
76
- server-only service with no frontend, stop and tell the user HeyCatch
77
- doesn't support it yet.
78
-
79
- ## 3. If the app has authentication, wire identity
80
-
81
- ```ts
82
- import { identify, reset, setPersonProperties } from '@heycatch/sdk';
83
-
84
- // After sign-in. The ID is a stable internal user id - never an email or a
85
- // session token. Email and name go in the PROPERTIES, and sending them is
86
- // what makes the dashboard show "Olek Vons <olek@example.com>" instead of an
87
- // opaque id - always pass what the app has.
88
- identify(
89
- user.id,
90
- { email: user.email, name: user.name, plan: user.plan },
91
- // Third arg is set-ONCE: written only if absent, so later sign-ins cannot
92
- // overwrite it. Use for values that describe the beginning.
93
- { signup_date: user.createdAt },
94
- );
95
-
96
- // When a property changes OUTSIDE sign-in (plan upgrade, name edit):
97
- setPersonProperties({ plan: 'pro' });
98
-
99
- reset(); // on sign-out, so the next visitor starts anonymous
100
- ```
101
-
102
- Any auth provider works. All three are no-ops before `init` or on the server.
103
- Property values must be primitives (string / number / boolean / null). No auth
104
- in the app? Skip this step.
105
-
106
- ## 4. Set up short links
107
-
108
- Short paths of 1-2 lowercase letters/digits are reserved for channel
109
- attribution and need one host redirect rule - follow
110
- [docs/short-links.md](./docs/short-links.md) (ships inside this package). If
111
- your tools cannot read or apply it, skip and tell the user short links still
112
- need setup.
113
-
114
- ## 5. Verify
115
-
116
- Self-check - confirm every line before finishing:
117
-
118
- - [ ] `@heycatch/sdk` is in `dependencies` (not `devDependencies`)
119
- - [ ] `init` is called exactly once, statically imported, at the client entry
120
- - [ ] The project key is inlined and starts with `hck_`
121
- - [ ] `install` carries every field you could determine - the framework id, its
122
- major version, and your own agent id. Omitting one you genuinely cannot
123
- determine is fine; guessing is not
124
- - [ ] If the app has auth: `identify` on sign-in WITH the email/name the app
125
- has as properties, `reset` on sign-out
126
- - [ ] Short links set up (or the user was told they still need setup)
127
-
128
- Then open or preview the app once so the first event fires. The browser console
129
- logs `[HeyCatch] SDK v<version> initialized → <apiHost>` on success, or a
130
- `[HeyCatch] …` error describing what's wrong. If you cannot run the app, tell
131
- the user to open it once. Then tell the user: "HeyCatch is installed - your
132
- dashboard will show data once people use the app."
@@ -1,69 +0,0 @@
1
- # Short links
2
-
3
- HeyCatch attributes single-letter paths (`yoursite.com/x`) to marketing
4
- channels. The letter-to-channel mapping lives entirely on HeyCatch's side -
5
- your only job is routing:
6
-
7
- 1. **Single-letter paths `/a`-`/z` are reserved for HeyCatch.** The app must
8
- not have real routes there; rename any that exist (or exclude that letter
9
- below).
10
- 2. **Redirect every letter path to `/?hc=<letter>` (302).** The target must
11
- carry `?hc=<letter>` - a bare redirect to `/` loses the attribution. (The
12
- SDK strips the param from the URL bar after landing - nothing else to
13
- clean up.)
14
- 3. Set once - the rules never change when the user's channels change.
15
-
16
- ## Add the rule for the host
17
-
18
- **Next.js** (`next.config.*`; redirects run before routes - exclude any real
19
- single-letter page from the regex, e.g. `([a-df-z])`):
20
-
21
- ```js
22
- async redirects() {
23
- return [{ source: '/:l([a-z])', destination: '/?hc=:l', permanent: false }];
24
- }
25
- ```
26
-
27
- **Vercel (non-Next)** (`vercel.json`):
28
-
29
- ```json
30
- { "redirects": [{ "source": "/:l([a-z])", "destination": "/?hc=:l" }] }
31
- ```
32
-
33
- **Netlify** (`_redirects`; the placeholder matches any single segment, but
34
- non-forced rules only fire when no real page matches - real routes always
35
- win, and a dead non-letter path like `/abou` just lands home with an `hc`
36
- value HeyCatch ignores. Keep this line above any `/*` catch-all):
37
-
38
- ```text
39
- /:l /?hc=:l 302
40
- ```
41
-
42
- **Cloudflare Pages** (`_redirects` fires before assets - a placeholder would
43
- shadow real pages, so enumerate, skipping real routes):
44
-
45
- ```text
46
- /a /?hc=a 302
47
- /b /?hc=b 302
48
- # ...one line per letter a-z
49
- ```
50
-
51
- **GitHub Pages** (no redirect rules - `404.html` with an inline forward):
52
-
53
- ```html
54
- <script>
55
- var m = /^\/([a-z])\/?$/.exec(location.pathname);
56
- if (m) location.replace('/?hc=' + m[1]);
57
- </script>
58
- ```
59
-
60
- **Plain SPA** - no rule needed; the fallback below covers it.
61
-
62
- ## Fallback: the SDK forwards by itself
63
-
64
- If you cannot edit host config on this platform, skip the rule - the SDK
65
- auto-forwards single-letter paths to `/?hc=<letter>` the moment it loads.
66
- The one requirement: unknown paths must serve a page that loads the SDK
67
- (automatic for SPAs and Next.js; on a static host add a catch-all fallback,
68
- e.g. Netlify `/* /index.html 200`). Host rules are still preferred - they
69
- forward before any paint, so the visitor never glimpses a not-found page.