@refraction-ui/react 0.7.0 → 0.9.0
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/dist/applicationinsights-web-62HUCH3W.js +23262 -0
- package/dist/applicationinsights-web-62HUCH3W.js.map +1 -0
- package/dist/index.cjs +36187 -5602
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +887 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/analytics-sink-app-insights/index.d.cts +165 -0
- package/dist/internal/analytics-sink-app-insights/index.d.ts +165 -0
- package/dist/internal/analytics-sink-ga4/index.d.cts +219 -0
- package/dist/internal/analytics-sink-ga4/index.d.ts +219 -0
- package/dist/internal/analytics-sink-posthog/index.d.cts +155 -0
- package/dist/internal/analytics-sink-posthog/index.d.ts +155 -0
- package/dist/internal/analytics-sink-posthog/replay.d.cts +78 -0
- package/dist/internal/analytics-sink-posthog/replay.d.ts +78 -0
- package/dist/internal/react-analytics/index.d.cts +4 -0
- package/dist/internal/react-analytics/index.d.ts +4 -0
- package/dist/internal/react-logger/index.d.cts +6 -2
- package/dist/internal/react-logger/index.d.ts +6 -2
- package/dist/module-XHEQRMQB.js +5320 -0
- package/dist/module-XHEQRMQB.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @refraction-ui/analytics-sink-ga4 — option contracts.
|
|
5
|
+
*
|
|
6
|
+
* GA4 is "just a sink" in the epic's model (no privileged engine). This
|
|
7
|
+
* adapter implements the `AnalyticsSink` SPI from `@refraction-ui/analytics`
|
|
8
|
+
* in two interchangeable modes:
|
|
9
|
+
*
|
|
10
|
+
* - `client-sdk` — lazy-loads gtag.js in the browser (no hard vendor dep;
|
|
11
|
+
* the script is injected on first delivery only). Bridges Consent Mode.
|
|
12
|
+
* - `http` — GA4 Measurement Protocol (`/mp/collect`). No browser
|
|
13
|
+
* library is ever loaded — server-relay friendly.
|
|
14
|
+
*
|
|
15
|
+
* Default = `http` (protocol adapter, no vendor lib in the browser), matching
|
|
16
|
+
* the epic's "default = protocol adapters" stance.
|
|
17
|
+
*/
|
|
18
|
+
/** Minimal gtag.js function signature (we never import the real types). */
|
|
19
|
+
type GtagFn = (...args: unknown[]) => void;
|
|
20
|
+
/** GA4 Consent Mode signal values. */
|
|
21
|
+
type ConsentState = 'granted' | 'denied';
|
|
22
|
+
/**
|
|
23
|
+
* GA4 Consent Mode bridge — maps our consent categories to the gtag
|
|
24
|
+
* `consent` command. Only used in `client-sdk` mode.
|
|
25
|
+
*/
|
|
26
|
+
interface GA4ConsentBridge {
|
|
27
|
+
/**
|
|
28
|
+
* Default consent state pushed via `gtag('consent', 'default', …)` before
|
|
29
|
+
* the GA4 tag loads. Keys are GA4 consent types
|
|
30
|
+
* (`analytics_storage`, `ad_storage`, `ad_user_data`,
|
|
31
|
+
* `ad_personalization`, `functionality_storage`, …).
|
|
32
|
+
*/
|
|
33
|
+
default?: Record<string, ConsentState>;
|
|
34
|
+
/**
|
|
35
|
+
* Maps a refraction consent category → the GA4 consent types it controls.
|
|
36
|
+
* When the router reports the sink may deliver (category granted) the
|
|
37
|
+
* adapter pushes `gtag('consent', 'update', { <types>: 'granted' })`.
|
|
38
|
+
* Example: `{ analytics: ['analytics_storage'] }`.
|
|
39
|
+
*/
|
|
40
|
+
map?: Record<string, string[]>;
|
|
41
|
+
}
|
|
42
|
+
interface GA4CommonOptions {
|
|
43
|
+
/** GA4 Measurement ID, e.g. `G-XXXXXXXXXX`. */
|
|
44
|
+
measurementId: string;
|
|
45
|
+
/**
|
|
46
|
+
* Consent categories this sink requires. The router will not deliver until
|
|
47
|
+
* all are granted. Default: `['analytics']`.
|
|
48
|
+
*/
|
|
49
|
+
consentCategories?: string[];
|
|
50
|
+
/** Stable sink name. Default `'ga4'`. */
|
|
51
|
+
name?: string;
|
|
52
|
+
}
|
|
53
|
+
/** `client-sdk` mode — runs gtag.js in the browser. */
|
|
54
|
+
interface GA4ClientSdkOptions extends GA4CommonOptions {
|
|
55
|
+
mode: 'client-sdk';
|
|
56
|
+
/**
|
|
57
|
+
* Inject the GA4 Consent Mode bridge. The default state (if any) is pushed
|
|
58
|
+
* before the tag loads; category grants drive `consent: update`.
|
|
59
|
+
*/
|
|
60
|
+
consentMode?: GA4ConsentBridge;
|
|
61
|
+
/**
|
|
62
|
+
* Inject an existing gtag function (tests / apps that manage the tag
|
|
63
|
+
* themselves). When provided, the script loader is NOT used and **no**
|
|
64
|
+
* vendor script is injected.
|
|
65
|
+
*/
|
|
66
|
+
gtag?: GtagFn;
|
|
67
|
+
/**
|
|
68
|
+
* Inject the script loader (tests / SSR-safe apps). Receives the gtag.js
|
|
69
|
+
* src URL; must resolve once the script has executed. Defaults to a DOM
|
|
70
|
+
* `<script>` injector (browser only).
|
|
71
|
+
*/
|
|
72
|
+
scriptLoader?: (src: string) => Promise<void>;
|
|
73
|
+
/** Override the gtag.js base URL (testing). */
|
|
74
|
+
gtagSrcBase?: string;
|
|
75
|
+
}
|
|
76
|
+
/** `http` mode — GA4 Measurement Protocol. No browser library. */
|
|
77
|
+
interface GA4HttpOptions extends GA4CommonOptions {
|
|
78
|
+
mode?: 'http';
|
|
79
|
+
/** GA4 Measurement Protocol API secret (server-side credential). */
|
|
80
|
+
apiSecret: string;
|
|
81
|
+
/** Override the `/mp/collect` base URL (testing / EU endpoint). */
|
|
82
|
+
endpoint?: string;
|
|
83
|
+
/** Use the Measurement Protocol `/debug/mp/collect` validation endpoint. */
|
|
84
|
+
debug?: boolean;
|
|
85
|
+
/** Injected fetch (defaults to global fetch). Never loads a vendor lib. */
|
|
86
|
+
fetchImpl?: typeof fetch;
|
|
87
|
+
}
|
|
88
|
+
/** Discriminated union of the two modes. */
|
|
89
|
+
type GA4SinkOptions = GA4ClientSdkOptions | GA4HttpOptions;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `createGA4Sink` — the single entry point. Dispatches on `mode`:
|
|
93
|
+
* - `mode: 'client-sdk'` → lazy gtag.js adapter
|
|
94
|
+
* - `mode: 'http'` (default) → Measurement Protocol adapter, no vendor lib
|
|
95
|
+
*
|
|
96
|
+
* Default is `http` to honour the epic's "default = protocol adapters (no
|
|
97
|
+
* vendor libs in the browser)" stance. GA4 is just a sink — register it via
|
|
98
|
+
* `createAnalytics({ sinks: [createGA4Sink(...)] })` or `analytics.addSink`.
|
|
99
|
+
*
|
|
100
|
+
* Both factories are independent modules. The `http` factory has ZERO
|
|
101
|
+
* references to gtag.js / DOM-script code, and the `client-sdk` factory only
|
|
102
|
+
* touches the vendor script lazily (first delivery). Selecting one mode never
|
|
103
|
+
* executes the other's load path; consumers that only ever construct an
|
|
104
|
+
* `http` sink never run any vendor code.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
declare function createGA4Sink(options: GA4SinkOptions): AnalyticsSink;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* GA4 `http` adapter — Measurement Protocol (`/mp/collect`).
|
|
111
|
+
*
|
|
112
|
+
* Server-relay friendly: this path NEVER loads gtag.js or any browser
|
|
113
|
+
* library. It only POSTs JSON to the Measurement Protocol endpoint:
|
|
114
|
+
*
|
|
115
|
+
* POST {endpoint}/mp/collect?measurement_id={id}&api_secret={secret}
|
|
116
|
+
* Content-Type: application/json
|
|
117
|
+
* { client_id, user_id?, user_properties?, events: [{ name, params }] }
|
|
118
|
+
*
|
|
119
|
+
* GA4 Measurement Protocol constraints honoured here:
|
|
120
|
+
* - up to 25 events per request → batches are chunked.
|
|
121
|
+
* - `identify` envelopes carry no event; they still POST so user_id /
|
|
122
|
+
* user_properties propagate (events array may be empty for a user-props
|
|
123
|
+
* only ping, which GA4 accepts).
|
|
124
|
+
* - 2xx (incl. 204) = accepted. The MP endpoint always returns 2xx for
|
|
125
|
+
* well-formed requests; the `debug` endpoint returns validation messages.
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Create the GA4 Measurement-Protocol (`http`) sink.
|
|
130
|
+
*
|
|
131
|
+
* No vendor library is loaded on this path under any circumstance.
|
|
132
|
+
*/
|
|
133
|
+
declare function createGA4HttpSink(options: GA4HttpOptions): AnalyticsSink;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* GA4 `client-sdk` adapter — lazy gtag.js.
|
|
137
|
+
*
|
|
138
|
+
* The vendor library is NEVER a hard dependency and is NEVER bundled. On the
|
|
139
|
+
* first delivery the adapter:
|
|
140
|
+
* 1. installs the gtag stub + dataLayer,
|
|
141
|
+
* 2. pushes the Consent Mode default (if a bridge is configured),
|
|
142
|
+
* 3. lazily injects `https://www.googletagmanager.com/gtag/js?id=<id>`
|
|
143
|
+
* via a `<script>` tag (or an injected loader for tests/SSR),
|
|
144
|
+
* 4. runs `gtag('js', Date)` + `gtag('config', <id>, { send_page_view:false })`.
|
|
145
|
+
*
|
|
146
|
+
* Subsequent deliveries map each canonical envelope through the shared mapper
|
|
147
|
+
* and call `gtag('set', 'user_properties', …)` / `gtag('set', { user_id })` /
|
|
148
|
+
* `gtag('event', name, params)`.
|
|
149
|
+
*
|
|
150
|
+
* Consent Mode bridge: `init` pushes `consent: default`; `deliver` is only
|
|
151
|
+
* reached when the router's consent gate already allows this sink, at which
|
|
152
|
+
* point `consent: update` is pushed for the mapped GA4 consent types.
|
|
153
|
+
*/
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create the GA4 gtag.js (`client-sdk`) sink. The vendor script is dynamically
|
|
157
|
+
* loaded on first delivery only — there is no static import of any GA4/gtag
|
|
158
|
+
* library, so `http`-mode consumers never pull this code path.
|
|
159
|
+
*/
|
|
160
|
+
declare function createGA4ClientSdkSink(options: GA4ClientSdkOptions): AnalyticsSink;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Canonical envelope → GA4 mapping.
|
|
164
|
+
*
|
|
165
|
+
* One mapper, two consumers: the `client-sdk` adapter feeds the result into
|
|
166
|
+
* `gtag('event', name, params)` / `gtag('set', ...)`, and the `http` adapter
|
|
167
|
+
* serialises it into a Measurement Protocol `/mp/collect` payload. Keeping the
|
|
168
|
+
* mapping in one place guarantees the two modes stay behaviourally identical.
|
|
169
|
+
*
|
|
170
|
+
* Identity / param mapping (issue #216, epic #213):
|
|
171
|
+
* anonymousId → client_id (GA4 device/browser id)
|
|
172
|
+
* userId → user_id (GA4 User-ID, cross-device)
|
|
173
|
+
* properties → event params (track/page/screen/group payload)
|
|
174
|
+
* identify → user_properties (traits become GA4 user properties)
|
|
175
|
+
* sessionId → session_id param (so GA4 sessionisation can align)
|
|
176
|
+
*
|
|
177
|
+
* GA4 event-name normalisation: GA4 recommends snake_case event names and
|
|
178
|
+
* forbids spaces. `page` → `page_view`, `screen` → `screen_view` (GA4
|
|
179
|
+
* Enhanced-Measurement parity); everything else is lower_snake_cased.
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
/** A GA4 event ready for gtag.js or the Measurement Protocol. */
|
|
183
|
+
interface GA4Event {
|
|
184
|
+
/** GA4 event name (snake_case, no spaces). */
|
|
185
|
+
name: string;
|
|
186
|
+
/** Event params (GA4 caps these; we pass them through verbatim). */
|
|
187
|
+
params: Record<string, unknown>;
|
|
188
|
+
}
|
|
189
|
+
/** The fully-mapped result for a single canonical envelope. */
|
|
190
|
+
interface GA4Mapped {
|
|
191
|
+
/** GA4 client_id (from anonymousId). Always present. */
|
|
192
|
+
clientId: string;
|
|
193
|
+
/** GA4 user_id (from userId). Present only after identify. */
|
|
194
|
+
userId?: string;
|
|
195
|
+
/**
|
|
196
|
+
* GA4 user_properties (from identify/group traits). Each value is wrapped
|
|
197
|
+
* in `{ value }` as the Measurement Protocol requires; the gtag adapter
|
|
198
|
+
* unwraps when it calls `gtag('set', 'user_properties', ...)`.
|
|
199
|
+
*/
|
|
200
|
+
userProperties?: Record<string, {
|
|
201
|
+
value: unknown;
|
|
202
|
+
}>;
|
|
203
|
+
/**
|
|
204
|
+
* The GA4 event to send. `identify` calls carry no event (they only set
|
|
205
|
+
* user_id / user_properties), so this is optional.
|
|
206
|
+
*/
|
|
207
|
+
event?: GA4Event;
|
|
208
|
+
}
|
|
209
|
+
/** Lower_snake_case an arbitrary event/trait name for GA4. */
|
|
210
|
+
declare function toGa4Name(name: string): string;
|
|
211
|
+
/** Map a Segment call type + name to its GA4 event name. */
|
|
212
|
+
declare function ga4EventName(ev: AnalyticsEvent): string | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* Map one canonical envelope to its GA4 representation. Pure — no transport,
|
|
215
|
+
* no vendor lib; both the gtag and Measurement-Protocol adapters consume this.
|
|
216
|
+
*/
|
|
217
|
+
declare function mapEvent(ev: AnalyticsEvent): GA4Mapped;
|
|
218
|
+
|
|
219
|
+
export { type ConsentState, type GA4ClientSdkOptions, type GA4ConsentBridge, type GA4Event, type GA4HttpOptions, type GA4Mapped, type GA4SinkOptions, type GtagFn, createGA4ClientSdkSink, createGA4HttpSink, createGA4Sink, ga4EventName, mapEvent, toGa4Name };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.cjs';
|
|
2
|
+
|
|
3
|
+
interface PostHogHttpSinkOptions {
|
|
4
|
+
/** PostHog project API key (the public, write-only key). */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/**
|
|
7
|
+
* Ingestion host. Default `https://us.i.posthog.com`. Use
|
|
8
|
+
* `https://eu.i.posthog.com`, a self-hosted host, or — recommended for
|
|
9
|
+
* production — your own reverse-proxy/relay path.
|
|
10
|
+
*/
|
|
11
|
+
host?: string;
|
|
12
|
+
/** Sink name. Default `posthog`. */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
15
|
+
consentCategories?: string[];
|
|
16
|
+
/** Max retries for 429/5xx (exponential backoff). Default 3. */
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
/** Base backoff delay in ms. Default 500. */
|
|
19
|
+
backoffBaseMs?: number;
|
|
20
|
+
/** Injected fetch (defaults to global fetch). */
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
/** Injected sendBeacon (defaults to navigator.sendBeacon). */
|
|
23
|
+
beaconImpl?: (url: string, body: string) => boolean;
|
|
24
|
+
/** Soft per-batch byte cap. PostHog limit ≈ 20MB; default 1MB. */
|
|
25
|
+
maxBatchBytes?: number;
|
|
26
|
+
/** Soft per-event byte cap. Default 32KB. */
|
|
27
|
+
maxEventBytes?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create the PostHog `http`-mode sink (default). Pure protocol adapter —
|
|
31
|
+
* no `posthog-js`, safe in Node and the browser.
|
|
32
|
+
*/
|
|
33
|
+
declare function createPostHogHttpSink(options: PostHogHttpSinkOptions): AnalyticsSink;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* PostHog `client-sdk` sink — OPTIONAL, opt-in mode.
|
|
37
|
+
*
|
|
38
|
+
* For client-exclusive PostHog features (autocapture, feature flags,
|
|
39
|
+
* surveys, web experiments) that the protocol API alone cannot drive.
|
|
40
|
+
* `posthog-js` is loaded **lazily via dynamic `import()`** the first time the
|
|
41
|
+
* sink delivers, so a consumer who only uses the default `http` sink never
|
|
42
|
+
* pays the browser-library cost and `posthog-js` stays a fully optional peer.
|
|
43
|
+
*
|
|
44
|
+
* Session replay is deliberately NOT touched here — it lives in the separate
|
|
45
|
+
* `@refraction-ui/analytics-sink-posthog/replay` module so it is never on the
|
|
46
|
+
* event path and tree-shakes out unless explicitly enabled.
|
|
47
|
+
*/
|
|
48
|
+
/** Minimal structural type for the bits of `posthog-js` we use. */
|
|
49
|
+
interface PostHogJs {
|
|
50
|
+
init(apiKey: string, options: Record<string, unknown>): void;
|
|
51
|
+
capture(event: string, properties?: Record<string, unknown>): void;
|
|
52
|
+
identify(distinctId: string, set?: Record<string, unknown>): void;
|
|
53
|
+
alias(alias: string, original?: string): void;
|
|
54
|
+
group(groupType: string, groupKey: string, properties?: Record<string, unknown>): void;
|
|
55
|
+
reset(): void;
|
|
56
|
+
}
|
|
57
|
+
interface PostHogClientSdkSinkOptions {
|
|
58
|
+
/** PostHog project API key. */
|
|
59
|
+
apiKey: string;
|
|
60
|
+
/** PostHog API host. Default `https://us.i.posthog.com`. */
|
|
61
|
+
host?: string;
|
|
62
|
+
/** Sink name. Default `posthog`. */
|
|
63
|
+
name?: string;
|
|
64
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
65
|
+
consentCategories?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* Extra `posthog-js` init options. `autocapture`, `capture_pageview`, and
|
|
68
|
+
* `session_recording` default to OFF — the canonical router owns the event
|
|
69
|
+
* path; replay is opt-in via the separate module.
|
|
70
|
+
*/
|
|
71
|
+
posthogOptions?: Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Injected loader (testing / custom bundling). Defaults to a lazy
|
|
74
|
+
* `import('posthog-js')`.
|
|
75
|
+
*/
|
|
76
|
+
loadPostHog?: () => Promise<{
|
|
77
|
+
default: PostHogJs;
|
|
78
|
+
} | PostHogJs>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create the OPTIONAL PostHog `client-sdk`-mode sink. `posthog-js` is
|
|
82
|
+
* dynamically imported on first use, never at module load.
|
|
83
|
+
*/
|
|
84
|
+
declare function createPostHogClientSdkSink(options: PostHogClientSdkSinkOptions): AnalyticsSink;
|
|
85
|
+
|
|
86
|
+
/** PostHog fan-out mode. `http` is the default (no browser library). */
|
|
87
|
+
type PostHogSinkMode = 'http' | 'client-sdk';
|
|
88
|
+
type PostHogSinkOptions = ({
|
|
89
|
+
mode?: 'http';
|
|
90
|
+
} & PostHogHttpSinkOptions) | ({
|
|
91
|
+
mode: 'client-sdk';
|
|
92
|
+
} & PostHogClientSdkSinkOptions);
|
|
93
|
+
/**
|
|
94
|
+
* Create a PostHog `AnalyticsSink`.
|
|
95
|
+
*
|
|
96
|
+
* - `mode: 'http'` (DEFAULT) — pure protocol adapter against PostHog's
|
|
97
|
+
* `/capture` + `/batch` API. No `posthog-js`. Server-relay friendly.
|
|
98
|
+
* - `mode: 'client-sdk'` — OPTIONAL. Lazily dynamic-imports `posthog-js`
|
|
99
|
+
* for client-exclusive features. Only loaded if this mode is selected.
|
|
100
|
+
*
|
|
101
|
+
* Session replay is NOT configurable here — import the separate
|
|
102
|
+
* `@refraction-ui/analytics-sink-posthog/replay` module to opt in. It is
|
|
103
|
+
* never on the event path and tree-shakes away when unused.
|
|
104
|
+
*/
|
|
105
|
+
declare function createPostHogSink(options: PostHogSinkOptions): AnalyticsSink;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Canonical envelope → PostHog event mapping.
|
|
109
|
+
*
|
|
110
|
+
* This module is pure (no I/O, no transport) so the http sink and the
|
|
111
|
+
* client-sdk sink share exactly one mapping definition and it can be unit
|
|
112
|
+
* tested in isolation against a mock transport.
|
|
113
|
+
*
|
|
114
|
+
* PostHog identity model:
|
|
115
|
+
* - `distinct_id` is the single id PostHog buckets a person under.
|
|
116
|
+
* - Before `identify`, the anonymous visitor is keyed by `anonymousId`.
|
|
117
|
+
* - `identify` upgrades the person to the opaque app `userId`, sending
|
|
118
|
+
* `$set` traits and an `$anon_distinct_id` so PostHog stitches the
|
|
119
|
+
* pre-identify anonymous history onto the identified person.
|
|
120
|
+
* - `alias` emits PostHog's `$create_alias` linking `previousId` (alias)
|
|
121
|
+
* to the canonical `userId`/`anonymousId` (distinct_id).
|
|
122
|
+
* - `group` emits a `$groupidentify` event with `$group_set` traits.
|
|
123
|
+
*
|
|
124
|
+
* See https://posthog.com/docs/api/capture for the event shape.
|
|
125
|
+
*/
|
|
126
|
+
/** A single PostHog capture event (the `/capture` and `/batch` item shape). */
|
|
127
|
+
interface PostHogEvent {
|
|
128
|
+
/** PostHog event name (Segment names map to `$pageview`/`$screen`/etc.). */
|
|
129
|
+
event: string;
|
|
130
|
+
/** The person bucket id. */
|
|
131
|
+
distinct_id: string;
|
|
132
|
+
/** Event + person/group properties. */
|
|
133
|
+
properties: Record<string, unknown>;
|
|
134
|
+
/** ISO-8601 client timestamp (PostHog corrects for skew server-side). */
|
|
135
|
+
timestamp: string;
|
|
136
|
+
/** Idempotency key — PostHog dedupes on this. Mirrors `messageId`. */
|
|
137
|
+
uuid: string;
|
|
138
|
+
}
|
|
139
|
+
/** Resolve the PostHog `distinct_id` for an envelope. */
|
|
140
|
+
declare function distinctId(ev: AnalyticsEvent): string;
|
|
141
|
+
/**
|
|
142
|
+
* Map one canonical envelope to one PostHog event.
|
|
143
|
+
*
|
|
144
|
+
* `track` → the event name verbatim, `properties` passed through.
|
|
145
|
+
* `page` → `$pageview` (PostHog's built-in pageview).
|
|
146
|
+
* `screen` → `$screen` with `$screen_name`.
|
|
147
|
+
* `identify` → `$identify` with `$set` traits + `$anon_distinct_id` stitch.
|
|
148
|
+
* `group` → `$groupidentify` with `$group_set` traits.
|
|
149
|
+
* `alias` → `$create_alias` linking `previousId` → distinct id.
|
|
150
|
+
*/
|
|
151
|
+
declare function toPostHogEvent(ev: AnalyticsEvent): PostHogEvent;
|
|
152
|
+
/** Map a batch of canonical envelopes to PostHog events. */
|
|
153
|
+
declare function toPostHogBatch(batch: AnalyticsEvent[]): PostHogEvent[];
|
|
154
|
+
|
|
155
|
+
export { type PostHogClientSdkSinkOptions, type PostHogEvent, type PostHogHttpSinkOptions, type PostHogSinkMode, type PostHogSinkOptions, createPostHogClientSdkSink, createPostHogHttpSink, createPostHogSink, distinctId, toPostHogBatch, toPostHogEvent };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.js';
|
|
2
|
+
|
|
3
|
+
interface PostHogHttpSinkOptions {
|
|
4
|
+
/** PostHog project API key (the public, write-only key). */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/**
|
|
7
|
+
* Ingestion host. Default `https://us.i.posthog.com`. Use
|
|
8
|
+
* `https://eu.i.posthog.com`, a self-hosted host, or — recommended for
|
|
9
|
+
* production — your own reverse-proxy/relay path.
|
|
10
|
+
*/
|
|
11
|
+
host?: string;
|
|
12
|
+
/** Sink name. Default `posthog`. */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
15
|
+
consentCategories?: string[];
|
|
16
|
+
/** Max retries for 429/5xx (exponential backoff). Default 3. */
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
/** Base backoff delay in ms. Default 500. */
|
|
19
|
+
backoffBaseMs?: number;
|
|
20
|
+
/** Injected fetch (defaults to global fetch). */
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
/** Injected sendBeacon (defaults to navigator.sendBeacon). */
|
|
23
|
+
beaconImpl?: (url: string, body: string) => boolean;
|
|
24
|
+
/** Soft per-batch byte cap. PostHog limit ≈ 20MB; default 1MB. */
|
|
25
|
+
maxBatchBytes?: number;
|
|
26
|
+
/** Soft per-event byte cap. Default 32KB. */
|
|
27
|
+
maxEventBytes?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create the PostHog `http`-mode sink (default). Pure protocol adapter —
|
|
31
|
+
* no `posthog-js`, safe in Node and the browser.
|
|
32
|
+
*/
|
|
33
|
+
declare function createPostHogHttpSink(options: PostHogHttpSinkOptions): AnalyticsSink;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* PostHog `client-sdk` sink — OPTIONAL, opt-in mode.
|
|
37
|
+
*
|
|
38
|
+
* For client-exclusive PostHog features (autocapture, feature flags,
|
|
39
|
+
* surveys, web experiments) that the protocol API alone cannot drive.
|
|
40
|
+
* `posthog-js` is loaded **lazily via dynamic `import()`** the first time the
|
|
41
|
+
* sink delivers, so a consumer who only uses the default `http` sink never
|
|
42
|
+
* pays the browser-library cost and `posthog-js` stays a fully optional peer.
|
|
43
|
+
*
|
|
44
|
+
* Session replay is deliberately NOT touched here — it lives in the separate
|
|
45
|
+
* `@refraction-ui/analytics-sink-posthog/replay` module so it is never on the
|
|
46
|
+
* event path and tree-shakes out unless explicitly enabled.
|
|
47
|
+
*/
|
|
48
|
+
/** Minimal structural type for the bits of `posthog-js` we use. */
|
|
49
|
+
interface PostHogJs {
|
|
50
|
+
init(apiKey: string, options: Record<string, unknown>): void;
|
|
51
|
+
capture(event: string, properties?: Record<string, unknown>): void;
|
|
52
|
+
identify(distinctId: string, set?: Record<string, unknown>): void;
|
|
53
|
+
alias(alias: string, original?: string): void;
|
|
54
|
+
group(groupType: string, groupKey: string, properties?: Record<string, unknown>): void;
|
|
55
|
+
reset(): void;
|
|
56
|
+
}
|
|
57
|
+
interface PostHogClientSdkSinkOptions {
|
|
58
|
+
/** PostHog project API key. */
|
|
59
|
+
apiKey: string;
|
|
60
|
+
/** PostHog API host. Default `https://us.i.posthog.com`. */
|
|
61
|
+
host?: string;
|
|
62
|
+
/** Sink name. Default `posthog`. */
|
|
63
|
+
name?: string;
|
|
64
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
65
|
+
consentCategories?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* Extra `posthog-js` init options. `autocapture`, `capture_pageview`, and
|
|
68
|
+
* `session_recording` default to OFF — the canonical router owns the event
|
|
69
|
+
* path; replay is opt-in via the separate module.
|
|
70
|
+
*/
|
|
71
|
+
posthogOptions?: Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Injected loader (testing / custom bundling). Defaults to a lazy
|
|
74
|
+
* `import('posthog-js')`.
|
|
75
|
+
*/
|
|
76
|
+
loadPostHog?: () => Promise<{
|
|
77
|
+
default: PostHogJs;
|
|
78
|
+
} | PostHogJs>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create the OPTIONAL PostHog `client-sdk`-mode sink. `posthog-js` is
|
|
82
|
+
* dynamically imported on first use, never at module load.
|
|
83
|
+
*/
|
|
84
|
+
declare function createPostHogClientSdkSink(options: PostHogClientSdkSinkOptions): AnalyticsSink;
|
|
85
|
+
|
|
86
|
+
/** PostHog fan-out mode. `http` is the default (no browser library). */
|
|
87
|
+
type PostHogSinkMode = 'http' | 'client-sdk';
|
|
88
|
+
type PostHogSinkOptions = ({
|
|
89
|
+
mode?: 'http';
|
|
90
|
+
} & PostHogHttpSinkOptions) | ({
|
|
91
|
+
mode: 'client-sdk';
|
|
92
|
+
} & PostHogClientSdkSinkOptions);
|
|
93
|
+
/**
|
|
94
|
+
* Create a PostHog `AnalyticsSink`.
|
|
95
|
+
*
|
|
96
|
+
* - `mode: 'http'` (DEFAULT) — pure protocol adapter against PostHog's
|
|
97
|
+
* `/capture` + `/batch` API. No `posthog-js`. Server-relay friendly.
|
|
98
|
+
* - `mode: 'client-sdk'` — OPTIONAL. Lazily dynamic-imports `posthog-js`
|
|
99
|
+
* for client-exclusive features. Only loaded if this mode is selected.
|
|
100
|
+
*
|
|
101
|
+
* Session replay is NOT configurable here — import the separate
|
|
102
|
+
* `@refraction-ui/analytics-sink-posthog/replay` module to opt in. It is
|
|
103
|
+
* never on the event path and tree-shakes away when unused.
|
|
104
|
+
*/
|
|
105
|
+
declare function createPostHogSink(options: PostHogSinkOptions): AnalyticsSink;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Canonical envelope → PostHog event mapping.
|
|
109
|
+
*
|
|
110
|
+
* This module is pure (no I/O, no transport) so the http sink and the
|
|
111
|
+
* client-sdk sink share exactly one mapping definition and it can be unit
|
|
112
|
+
* tested in isolation against a mock transport.
|
|
113
|
+
*
|
|
114
|
+
* PostHog identity model:
|
|
115
|
+
* - `distinct_id` is the single id PostHog buckets a person under.
|
|
116
|
+
* - Before `identify`, the anonymous visitor is keyed by `anonymousId`.
|
|
117
|
+
* - `identify` upgrades the person to the opaque app `userId`, sending
|
|
118
|
+
* `$set` traits and an `$anon_distinct_id` so PostHog stitches the
|
|
119
|
+
* pre-identify anonymous history onto the identified person.
|
|
120
|
+
* - `alias` emits PostHog's `$create_alias` linking `previousId` (alias)
|
|
121
|
+
* to the canonical `userId`/`anonymousId` (distinct_id).
|
|
122
|
+
* - `group` emits a `$groupidentify` event with `$group_set` traits.
|
|
123
|
+
*
|
|
124
|
+
* See https://posthog.com/docs/api/capture for the event shape.
|
|
125
|
+
*/
|
|
126
|
+
/** A single PostHog capture event (the `/capture` and `/batch` item shape). */
|
|
127
|
+
interface PostHogEvent {
|
|
128
|
+
/** PostHog event name (Segment names map to `$pageview`/`$screen`/etc.). */
|
|
129
|
+
event: string;
|
|
130
|
+
/** The person bucket id. */
|
|
131
|
+
distinct_id: string;
|
|
132
|
+
/** Event + person/group properties. */
|
|
133
|
+
properties: Record<string, unknown>;
|
|
134
|
+
/** ISO-8601 client timestamp (PostHog corrects for skew server-side). */
|
|
135
|
+
timestamp: string;
|
|
136
|
+
/** Idempotency key — PostHog dedupes on this. Mirrors `messageId`. */
|
|
137
|
+
uuid: string;
|
|
138
|
+
}
|
|
139
|
+
/** Resolve the PostHog `distinct_id` for an envelope. */
|
|
140
|
+
declare function distinctId(ev: AnalyticsEvent): string;
|
|
141
|
+
/**
|
|
142
|
+
* Map one canonical envelope to one PostHog event.
|
|
143
|
+
*
|
|
144
|
+
* `track` → the event name verbatim, `properties` passed through.
|
|
145
|
+
* `page` → `$pageview` (PostHog's built-in pageview).
|
|
146
|
+
* `screen` → `$screen` with `$screen_name`.
|
|
147
|
+
* `identify` → `$identify` with `$set` traits + `$anon_distinct_id` stitch.
|
|
148
|
+
* `group` → `$groupidentify` with `$group_set` traits.
|
|
149
|
+
* `alias` → `$create_alias` linking `previousId` → distinct id.
|
|
150
|
+
*/
|
|
151
|
+
declare function toPostHogEvent(ev: AnalyticsEvent): PostHogEvent;
|
|
152
|
+
/** Map a batch of canonical envelopes to PostHog events. */
|
|
153
|
+
declare function toPostHogBatch(batch: AnalyticsEvent[]): PostHogEvent[];
|
|
154
|
+
|
|
155
|
+
export { type PostHogClientSdkSinkOptions, type PostHogEvent, type PostHogHttpSinkOptions, type PostHogSinkMode, type PostHogSinkOptions, createPostHogClientSdkSink, createPostHogHttpSink, createPostHogSink, distinctId, toPostHogBatch, toPostHogEvent };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @refraction-ui/analytics-sink-posthog/replay
|
|
3
|
+
*
|
|
4
|
+
* OPTIONAL, lazy session-replay (rrweb, via `posthog-js`).
|
|
5
|
+
*
|
|
6
|
+
* Hard guarantees this module exists to enforce:
|
|
7
|
+
*
|
|
8
|
+
* 1. **Off by default.** Nothing in the main `@refraction-ui/analytics-sink-posthog`
|
|
9
|
+
* entry imports this file, so it (and `posthog-js`, and rrweb) are fully
|
|
10
|
+
* tree-shaken out of any bundle that does not explicitly import it.
|
|
11
|
+
* 2. **Never on the event path.** Replay is not an `AnalyticsSink`. It does
|
|
12
|
+
* not see, transform, or block canonical envelopes. `track`/`identify`/…
|
|
13
|
+
* keep flowing through the sink even if replay never starts or fails.
|
|
14
|
+
* 3. **Privacy/consent gated.** `startSessionReplay` refuses to start unless
|
|
15
|
+
* a consent predicate returns true, and re-checks it; `stop()` tears the
|
|
16
|
+
* recorder down. Masking defaults are maximally private.
|
|
17
|
+
* 4. **Lazy.** `posthog-js` is `import()`-ed only when `startSessionReplay`
|
|
18
|
+
* is actually called.
|
|
19
|
+
*
|
|
20
|
+
* This is a thin controller around `posthog-js`'s built-in rrweb session
|
|
21
|
+
* recording — we do not bundle rrweb ourselves.
|
|
22
|
+
*/
|
|
23
|
+
/** Minimal structural view of the `posthog-js` replay surface. */
|
|
24
|
+
interface PostHogReplay {
|
|
25
|
+
init(apiKey: string, options: Record<string, unknown>): void;
|
|
26
|
+
startSessionRecording(): void;
|
|
27
|
+
stopSessionRecording(): void;
|
|
28
|
+
sessionRecordingStarted?(): boolean;
|
|
29
|
+
}
|
|
30
|
+
interface SessionReplayOptions {
|
|
31
|
+
/** PostHog project API key. */
|
|
32
|
+
apiKey: string;
|
|
33
|
+
/** PostHog API host. Default `https://us.i.posthog.com`. */
|
|
34
|
+
host?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Consent predicate. Replay starts ONLY when this returns `true`, and is
|
|
37
|
+
* re-checked by `enforceConsent()`. There is no default-allow: if omitted,
|
|
38
|
+
* replay is treated as NOT consented and will not start.
|
|
39
|
+
*/
|
|
40
|
+
hasConsent?: () => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* rrweb masking. Defaults are maximally private: all text + all inputs
|
|
43
|
+
* masked. Override deliberately and document the privacy review.
|
|
44
|
+
*/
|
|
45
|
+
maskAllText?: boolean;
|
|
46
|
+
maskAllInputs?: boolean;
|
|
47
|
+
/** Extra `posthog-js` session_recording options (advanced). */
|
|
48
|
+
recordingOptions?: Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Injected loader (testing / custom bundling). Defaults to a lazy
|
|
51
|
+
* `import('posthog-js')`.
|
|
52
|
+
*/
|
|
53
|
+
loadPostHog?: () => Promise<{
|
|
54
|
+
default: PostHogReplay;
|
|
55
|
+
} | PostHogReplay>;
|
|
56
|
+
}
|
|
57
|
+
/** Handle returned by {@link startSessionReplay}. */
|
|
58
|
+
interface SessionReplayHandle {
|
|
59
|
+
/** True if the rrweb recorder is currently running. */
|
|
60
|
+
readonly recording: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Re-evaluate the consent predicate. If consent was revoked, recording is
|
|
63
|
+
* stopped. Call this from your consent-change handler.
|
|
64
|
+
*/
|
|
65
|
+
enforceConsent(): void;
|
|
66
|
+
/** Stop recording and release the recorder. Idempotent. */
|
|
67
|
+
stop(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start PostHog/rrweb session replay. Resolves to a handle, or to a
|
|
71
|
+
* non-recording handle if consent is not granted (it never throws on the
|
|
72
|
+
* consent path — replay simply does not start).
|
|
73
|
+
*
|
|
74
|
+
* `posthog-js` is dynamically imported here and nowhere else.
|
|
75
|
+
*/
|
|
76
|
+
declare function startSessionReplay(options: SessionReplayOptions): Promise<SessionReplayHandle>;
|
|
77
|
+
|
|
78
|
+
export { type SessionReplayHandle, type SessionReplayOptions, startSessionReplay };
|