@heycatch/sdk 0.6.0 → 0.7.0-dev.1029
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.
- package/README.md +59 -1
- package/THIRD-PARTY-LICENSES.md +360 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +43 -76
- package/dist/index.d.ts +43 -76
- package/dist/index.js +2 -2
- package/dist/react-native.cjs +2 -0
- package/dist/react-native.d.cts +110 -0
- package/dist/react-native.d.ts +110 -0
- package/dist/react-native.js +2 -0
- package/dist/server.cjs +3 -0
- package/dist/server.d.cts +105 -0
- package/dist/server.d.ts +105 -0
- package/dist/server.js +3 -0
- package/dist/shared-CaIZ-Tgs.d.cts +251 -0
- package/dist/shared-CaIZ-Tgs.d.ts +251 -0
- package/package.json +53 -10
- package/AGENTS.md +0 -132
- package/docs/short-links.md +0 -69
|
@@ -0,0 +1,251 @@
|
|
|
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
|
+
* One captured event, as {@link HeyCatchBeforeSend} sees it just before it
|
|
120
|
+
* is queued for sending.
|
|
121
|
+
*
|
|
122
|
+
* Declared structurally, listing only the fields the SDK can promise. The
|
|
123
|
+
* index signature carries the underlying transport's own extra keys through
|
|
124
|
+
* untouched, so nothing is lost and nothing here has to move when they
|
|
125
|
+
* change.
|
|
126
|
+
*/
|
|
127
|
+
interface HeyCatchCapturedEvent {
|
|
128
|
+
/** Event name - an autocapture name like `$autocapture`, or your own. */
|
|
129
|
+
event: string;
|
|
130
|
+
/** The event's property bag, as it will be sent. */
|
|
131
|
+
properties: Record<string, unknown>;
|
|
132
|
+
[key: string]: unknown;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Runs on every event right before it is queued. Return the event to keep
|
|
136
|
+
* it (mutated or not), or `null` to drop it.
|
|
137
|
+
*
|
|
138
|
+
* The SDK never calls this itself and never reads its return value - it
|
|
139
|
+
* forwards the function to the transport, whose own code calls it.
|
|
140
|
+
*/
|
|
141
|
+
type HeyCatchBeforeSend = (event: HeyCatchCapturedEvent | null) => HeyCatchCapturedEvent | null;
|
|
142
|
+
/** The transport's older, deprecated hook. Prefer {@link HeyCatchBeforeSend}. */
|
|
143
|
+
type HeyCatchSanitizeProperties = (properties: Record<string, unknown>, eventName: string) => Record<string, unknown>;
|
|
144
|
+
/** Where the SDK may persist the distinct id and super properties. */
|
|
145
|
+
type HeyCatchPersistence = 'localStorage' | 'cookie' | 'memory' | 'localStorage+cookie' | 'sessionStorage';
|
|
146
|
+
/**
|
|
147
|
+
* Frameworks the install guides can detect. **Written out as a union, not
|
|
148
|
+
* derived from the array below**, because
|
|
149
|
+
* `apps/landing-web/tests/agent-install-guides.spec.ts` matches this
|
|
150
|
+
* declaration as source text to assert the set agrees with the guide's
|
|
151
|
+
* detection table. Collapsing it to `(typeof …)[number]` leaves that guard
|
|
152
|
+
* matching nothing.
|
|
153
|
+
*
|
|
154
|
+
* For the same reason, do not restate the declaration's opening line anywhere
|
|
155
|
+
* above it - including in a comment. The guard takes the FIRST match in the
|
|
156
|
+
* file, so a prose copy of it wins and yields zero ids. (Written here after
|
|
157
|
+
* doing exactly that; the spec's vacuity check is what caught it.)
|
|
158
|
+
*/
|
|
159
|
+
type KnownFramework = 'nextjs' | 'vite-react' | 'react' | 'vue' | 'svelte' | 'astro' | 'angular' | 'react-native' | 'web';
|
|
160
|
+
/** Coding agents that run the install. `other` is the catch-all. */
|
|
161
|
+
type KnownInstallAgent = 'lovable' | 'bolt' | 'v0' | 'replit' | 'cursor' | 'claude-code' | 'codex' | 'windsurf' | 'other';
|
|
162
|
+
/**
|
|
163
|
+
* The same two sets at RUNTIME, for code that has to ask "is this reported
|
|
164
|
+
* value one we recognise?" - a question a type union cannot answer, because
|
|
165
|
+
* `install.framework` is deliberately `KnownFramework | (string & {})` so an
|
|
166
|
+
* unlisted framework still installs cleanly.
|
|
167
|
+
*
|
|
168
|
+
* The consumer is the internal stats page, which flags an unrecognised or
|
|
169
|
+
* missing value rather than silently rendering it as though it were expected.
|
|
170
|
+
*
|
|
171
|
+
* Drift between each array and its union is a **compile error** (see the
|
|
172
|
+
* assertions below), so this is a second spelling, not a second source.
|
|
173
|
+
*/
|
|
174
|
+
declare const KNOWN_FRAMEWORKS: readonly ["nextjs", "vite-react", "react", "vue", "svelte", "astro", "angular", "react-native", "web"];
|
|
175
|
+
declare const KNOWN_INSTALL_AGENTS: readonly ["lovable", "bolt", "v0", "replit", "cursor", "claude-code", "codex", "windsurf", "other"];
|
|
176
|
+
/**
|
|
177
|
+
* Who installed the SDK and into what. Stamped as super-properties on
|
|
178
|
+
* every event - the dashboard reads them off the event stream, so there
|
|
179
|
+
* is no separate install call to make or to fail.
|
|
180
|
+
*
|
|
181
|
+
* `(string & {})` keeps the known ids as editor completions while still
|
|
182
|
+
* accepting anything: a framework we haven't listed yet must not block
|
|
183
|
+
* an install.
|
|
184
|
+
*/
|
|
185
|
+
interface HeyCatchInstall {
|
|
186
|
+
/** Framework id from the install guide, e.g. `nextjs`. */
|
|
187
|
+
framework?: KnownFramework | (string & {});
|
|
188
|
+
/** That framework's major version, e.g. `15`. */
|
|
189
|
+
frameworkVersion?: string;
|
|
190
|
+
/** The agent doing the install, e.g. `claude-code`. */
|
|
191
|
+
agent?: KnownInstallAgent | (string & {});
|
|
192
|
+
}
|
|
193
|
+
/** Configuration for {@link init}. */
|
|
194
|
+
interface HeyCatchConfig {
|
|
195
|
+
/** Your project's publishable key (`hck_pk_...`). */
|
|
196
|
+
projectKey: string;
|
|
197
|
+
/** Install metadata, stamped on every event. Omit any field you can't determine. */
|
|
198
|
+
install?: HeyCatchInstall;
|
|
199
|
+
/**
|
|
200
|
+
* Hostnames of the app's own backends on OTHER origins (e.g.
|
|
201
|
+
* `api.example.com`) whose requests should carry the session header for
|
|
202
|
+
* server-side `trackEvent({ request })` linking. Same-origin requests
|
|
203
|
+
* are covered automatically - omit this unless the API lives elsewhere.
|
|
204
|
+
*/
|
|
205
|
+
tracingHosts?: string[];
|
|
206
|
+
/**
|
|
207
|
+
* Turn off autocapture (clicks and form submissions). Pageviews,
|
|
208
|
+
* page-leaves, and rage-clicks are captured separately and are NOT
|
|
209
|
+
* affected by this one. Costs the recap's friction signals - the web
|
|
210
|
+
* entry logs this once at `init()` so the tradeoff isn't silent.
|
|
211
|
+
*/
|
|
212
|
+
autocapture?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Strip autocaptured elements' text content. Costs the recap's friction
|
|
215
|
+
* labels - logged once at `init()`.
|
|
216
|
+
*/
|
|
217
|
+
maskAllText?: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* Strip autocaptured elements' attributes. Costs debugging context on
|
|
220
|
+
* autocaptured events - logged once at `init()`.
|
|
221
|
+
*/
|
|
222
|
+
maskAllElementAttributes?: boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Property keys to drop from every captured event. HeyCatch's own
|
|
225
|
+
* `heycatch_*` attribution keys cannot be denylisted - they are what
|
|
226
|
+
* attributes an event to your project, so they are filtered back out
|
|
227
|
+
* (and named in the console) rather than silently honoured.
|
|
228
|
+
*/
|
|
229
|
+
propertyDenylist?: string[];
|
|
230
|
+
/** @deprecated Superseded by {@link HeyCatchConfig.beforeSend}. */
|
|
231
|
+
sanitizeProperties?: HeyCatchSanitizeProperties;
|
|
232
|
+
/** Runs on every event right before it is queued; return `null` to drop it. */
|
|
233
|
+
beforeSend?: HeyCatchBeforeSend | HeyCatchBeforeSend[];
|
|
234
|
+
/** Where the SDK persists the distinct id and super properties. */
|
|
235
|
+
persistence?: HeyCatchPersistence;
|
|
236
|
+
/** Disable persistence entirely - every pageload starts a new anonymous person. */
|
|
237
|
+
disablePersistence?: boolean;
|
|
238
|
+
/** Days before the persistence cookie expires. Defaults to 365. */
|
|
239
|
+
cookieExpirationDays?: number;
|
|
240
|
+
/**
|
|
241
|
+
* Start with capturing off until the app calls
|
|
242
|
+
* `analytics.optInCapturing()` - a proper consent gate instead of the
|
|
243
|
+
* "don't call init" workaround. Browser storage is held off too, not
|
|
244
|
+
* just sending, so no distinct id is written before consent.
|
|
245
|
+
*/
|
|
246
|
+
optOutCapturingByDefault?: boolean;
|
|
247
|
+
/** Honor the browser's Do Not Track signal. */
|
|
248
|
+
respectDnt?: boolean;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export { type HeyCatchBeforeSend as H, KNOWN_FRAMEWORKS as K, type PersonPropertyUpdate as P, SDK_VERSION as S, type TrackOptions as T, UNKNOWN_INSTALL_VALUE as U, type HeyCatchCapturedEvent as a, type HeyCatchConfig as b, type HeyCatchPersistence as c, type HeyCatchPersonProperties as d, type HeyCatchProperties as e, type HeyCatchSanitizeProperties as f, KNOWN_INSTALL_AGENTS as g, STAGE as h, type ServerTrackOptions as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heycatch/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-dev.1029",
|
|
4
4
|
"description": "HeyCatch SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"analytics",
|
|
@@ -23,25 +23,68 @@
|
|
|
23
23
|
"type": "module",
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
36
|
-
"dist
|
|
37
|
-
"dist
|
|
38
|
-
"dist
|
|
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
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=22"
|
|
87
|
+
},
|
|
45
88
|
"publishConfig": {
|
|
46
89
|
"access": "public",
|
|
47
90
|
"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."
|
package/docs/short-links.md
DELETED
|
@@ -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.
|