@refraction-ui/react 0.6.0 → 0.8.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 +43862 -13281
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +881 -4
- 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 +1 -1
- package/dist/internal/react-logger/index.d.ts +1 -1
- package/dist/module-XHEQRMQB.js +5320 -0
- package/dist/module-XHEQRMQB.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { AnalyticsEvent, AnalyticsSink } from '../analytics/index.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural shape of the bits of `@microsoft/applicationinsights-web`
|
|
5
|
+
* we use. Declared locally so the vendor lib stays an OPTIONAL peer with NO
|
|
6
|
+
* type-level hard dependency — it is only ever reached via dynamic import.
|
|
7
|
+
*/
|
|
8
|
+
interface AppInsightsLike {
|
|
9
|
+
loadAppInsights?: () => unknown;
|
|
10
|
+
trackEvent: (event: {
|
|
11
|
+
name: string;
|
|
12
|
+
}, customProperties?: Record<string, unknown>) => void;
|
|
13
|
+
trackPageView: (pageView?: {
|
|
14
|
+
name?: string;
|
|
15
|
+
uri?: string;
|
|
16
|
+
properties?: Record<string, unknown>;
|
|
17
|
+
measurements?: Record<string, number>;
|
|
18
|
+
}) => void;
|
|
19
|
+
setAuthenticatedUserContext?: (authenticatedUserId: string, accountId?: string, storeInCookie?: boolean) => void;
|
|
20
|
+
clearAuthenticatedUserContext?: () => void;
|
|
21
|
+
flush?: (async?: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
/** Fan-out mode for the App Insights sink. */
|
|
24
|
+
type AppInsightsSinkMode = 'client-sdk' | 'http';
|
|
25
|
+
interface BaseOptions {
|
|
26
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
27
|
+
consentCategories?: string[];
|
|
28
|
+
/** Stable sink name. Default `'app-insights'`. */
|
|
29
|
+
name?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `client-sdk` mode — runs in the browser via `@microsoft/applicationinsights-web`.
|
|
33
|
+
*
|
|
34
|
+
* The vendor SDK is loaded **lazily via dynamic import** the first time a
|
|
35
|
+
* batch is delivered, so it never enters the bundle unless this sink is used.
|
|
36
|
+
* Callers may instead inject a ready instance (`appInsights`) or a custom
|
|
37
|
+
* `loadSdk` factory (used by tests with a mock transport — no network).
|
|
38
|
+
*/
|
|
39
|
+
interface ClientSdkOptions extends BaseOptions {
|
|
40
|
+
mode: 'client-sdk';
|
|
41
|
+
/** App Insights connection string (preferred) or instrumentation key. */
|
|
42
|
+
connectionString?: string;
|
|
43
|
+
/** Legacy instrumentation key (used when `connectionString` is absent). */
|
|
44
|
+
instrumentationKey?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Pre-constructed App Insights instance. When provided, no dynamic import
|
|
47
|
+
* or `loadAppInsights()` happens — used for DI/testing.
|
|
48
|
+
*/
|
|
49
|
+
appInsights?: AppInsightsLike;
|
|
50
|
+
/**
|
|
51
|
+
* Custom async SDK factory. Overrides the built-in dynamic import of
|
|
52
|
+
* `@microsoft/applicationinsights-web`. Receives the resolved config.
|
|
53
|
+
*/
|
|
54
|
+
loadSdk?: (config: {
|
|
55
|
+
connectionString?: string;
|
|
56
|
+
instrumentationKey?: string;
|
|
57
|
+
}) => Promise<AppInsightsLike>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `http` mode — POSTs to the App Insights `/v2/track` ingestion endpoint.
|
|
61
|
+
* No browser library is loaded; server-relay friendly.
|
|
62
|
+
*/
|
|
63
|
+
interface HttpOptions extends BaseOptions {
|
|
64
|
+
mode: 'http';
|
|
65
|
+
/** Instrumentation key (iKey) for the ingestion envelope. Required. */
|
|
66
|
+
instrumentationKey: string;
|
|
67
|
+
/**
|
|
68
|
+
* Ingestion endpoint base. Default the public global endpoint
|
|
69
|
+
* `https://dc.services.visualstudio.com`. Region endpoints are accepted.
|
|
70
|
+
* Events POST to `{endpoint}/v2/track`.
|
|
71
|
+
*/
|
|
72
|
+
endpoint?: string;
|
|
73
|
+
/** Injected fetch (defaults to global fetch). */
|
|
74
|
+
fetchImpl?: typeof fetch;
|
|
75
|
+
/** Max retries for 429/5xx (exponential backoff). Default 3. */
|
|
76
|
+
maxRetries?: number;
|
|
77
|
+
/** Base backoff delay in ms. Default 500. */
|
|
78
|
+
backoffBaseMs?: number;
|
|
79
|
+
}
|
|
80
|
+
type AppInsightsSinkOptions = ClientSdkOptions | HttpOptions;
|
|
81
|
+
interface IngestEnvelope {
|
|
82
|
+
name: string;
|
|
83
|
+
time: string;
|
|
84
|
+
iKey: string;
|
|
85
|
+
tags: Record<string, string>;
|
|
86
|
+
data: {
|
|
87
|
+
baseType: 'EventData' | 'PageViewData';
|
|
88
|
+
baseData: {
|
|
89
|
+
ver: 2;
|
|
90
|
+
name: string;
|
|
91
|
+
properties: Record<string, string>;
|
|
92
|
+
measurements: Record<string, number>;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build an App Insights ingestion envelope (Breeze schema) for one canonical
|
|
98
|
+
* event. `tags` carry session/user/operation correlation; `baseData` carries
|
|
99
|
+
* the custom event with the properties/measurements split.
|
|
100
|
+
*/
|
|
101
|
+
declare function buildIngestEnvelope(ev: AnalyticsEvent, iKey: string): IngestEnvelope;
|
|
102
|
+
/**
|
|
103
|
+
* Create an Azure Application Insights {@link AnalyticsSink}.
|
|
104
|
+
*
|
|
105
|
+
* Dual-mode:
|
|
106
|
+
* - `client-sdk` — browser, lazy `@microsoft/applicationinsights-web`
|
|
107
|
+
* (`trackEvent` / `trackPageView`); the vendor lib is an OPTIONAL peer
|
|
108
|
+
* loaded only via dynamic import, never a hard/static dependency.
|
|
109
|
+
* - `http` — App Insights `/v2/track` ingestion endpoint; no browser lib,
|
|
110
|
+
* server-relay friendly.
|
|
111
|
+
*
|
|
112
|
+
* Both modes map the canonical envelope to App Insights custom events with a
|
|
113
|
+
* properties/measurements split and surface identity as
|
|
114
|
+
* `authenticatedUserId` / `anonymous`.
|
|
115
|
+
*/
|
|
116
|
+
declare function createAppInsightsSink(options: AppInsightsSinkOptions): AnalyticsSink;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* App Insights custom-event payload after mapping the canonical envelope.
|
|
120
|
+
*
|
|
121
|
+
* App Insights splits a custom event's bag into two dictionaries:
|
|
122
|
+
* - `properties` — string-valued dimensions (everything non-numeric)
|
|
123
|
+
* - `measurements`— numeric metrics (queryable/aggregatable in KQL)
|
|
124
|
+
*
|
|
125
|
+
* We additionally surface identity:
|
|
126
|
+
* - `authenticatedUserId` — present iff the envelope has `userId`
|
|
127
|
+
* - `anonymous` — `'true'` when no `userId`, else `'false'` (string for the
|
|
128
|
+
* properties bag; App Insights properties are always strings)
|
|
129
|
+
*/
|
|
130
|
+
interface AppInsightsEvent {
|
|
131
|
+
/** App Insights custom-event name. */
|
|
132
|
+
name: string;
|
|
133
|
+
/** String-valued custom dimensions. */
|
|
134
|
+
properties: Record<string, string>;
|
|
135
|
+
/** Numeric custom metrics. */
|
|
136
|
+
measurements: Record<string, number>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Derive the App Insights custom-event name for a canonical event.
|
|
140
|
+
*
|
|
141
|
+
* - `page` → `Page View: <name|path>` (also routed to `trackPageView` in the
|
|
142
|
+
* client-sdk mode; the name here is the http-mode/event fallback)
|
|
143
|
+
* - `screen`→ `Screen: <name>`
|
|
144
|
+
* - `identify` → `Identify`
|
|
145
|
+
* - `group` → `Group`
|
|
146
|
+
* - `alias` → `Alias`
|
|
147
|
+
* - `track` → the event name (or `track` when absent)
|
|
148
|
+
*/
|
|
149
|
+
declare function eventName(ev: AnalyticsEvent): string;
|
|
150
|
+
/**
|
|
151
|
+
* Map a canonical {@link AnalyticsEvent} to an {@link AppInsightsEvent}.
|
|
152
|
+
*
|
|
153
|
+
* Properties bag composition:
|
|
154
|
+
* - canonical context: `app`, `env`, library name/version, page fields
|
|
155
|
+
* - envelope identity/routing: `messageId`, `anonymousId`, `sessionId`,
|
|
156
|
+
* `type`, `groupId?`, `previousId?`, `timestamp`, `schemaVersion`
|
|
157
|
+
* - identity surface: `authenticatedUserId` (iff `userId`), `anonymous`
|
|
158
|
+
* - the event's `properties` and `traits`, numbers → `measurements`
|
|
159
|
+
*
|
|
160
|
+
* The split is stable and lossless: every scalar lands in exactly one of the
|
|
161
|
+
* two dictionaries; nested objects are dot-flattened.
|
|
162
|
+
*/
|
|
163
|
+
declare function mapEvent(ev: AnalyticsEvent): AppInsightsEvent;
|
|
164
|
+
|
|
165
|
+
export { type AppInsightsEvent, type AppInsightsLike, type AppInsightsSinkMode, type AppInsightsSinkOptions, type ClientSdkOptions, type HttpOptions, buildIngestEnvelope, createAppInsightsSink, eventName, mapEvent };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { AnalyticsEvent, AnalyticsSink } from '../analytics/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal structural shape of the bits of `@microsoft/applicationinsights-web`
|
|
5
|
+
* we use. Declared locally so the vendor lib stays an OPTIONAL peer with NO
|
|
6
|
+
* type-level hard dependency — it is only ever reached via dynamic import.
|
|
7
|
+
*/
|
|
8
|
+
interface AppInsightsLike {
|
|
9
|
+
loadAppInsights?: () => unknown;
|
|
10
|
+
trackEvent: (event: {
|
|
11
|
+
name: string;
|
|
12
|
+
}, customProperties?: Record<string, unknown>) => void;
|
|
13
|
+
trackPageView: (pageView?: {
|
|
14
|
+
name?: string;
|
|
15
|
+
uri?: string;
|
|
16
|
+
properties?: Record<string, unknown>;
|
|
17
|
+
measurements?: Record<string, number>;
|
|
18
|
+
}) => void;
|
|
19
|
+
setAuthenticatedUserContext?: (authenticatedUserId: string, accountId?: string, storeInCookie?: boolean) => void;
|
|
20
|
+
clearAuthenticatedUserContext?: () => void;
|
|
21
|
+
flush?: (async?: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
/** Fan-out mode for the App Insights sink. */
|
|
24
|
+
type AppInsightsSinkMode = 'client-sdk' | 'http';
|
|
25
|
+
interface BaseOptions {
|
|
26
|
+
/** Consent categories this sink requires. Default `['analytics']`. */
|
|
27
|
+
consentCategories?: string[];
|
|
28
|
+
/** Stable sink name. Default `'app-insights'`. */
|
|
29
|
+
name?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* `client-sdk` mode — runs in the browser via `@microsoft/applicationinsights-web`.
|
|
33
|
+
*
|
|
34
|
+
* The vendor SDK is loaded **lazily via dynamic import** the first time a
|
|
35
|
+
* batch is delivered, so it never enters the bundle unless this sink is used.
|
|
36
|
+
* Callers may instead inject a ready instance (`appInsights`) or a custom
|
|
37
|
+
* `loadSdk` factory (used by tests with a mock transport — no network).
|
|
38
|
+
*/
|
|
39
|
+
interface ClientSdkOptions extends BaseOptions {
|
|
40
|
+
mode: 'client-sdk';
|
|
41
|
+
/** App Insights connection string (preferred) or instrumentation key. */
|
|
42
|
+
connectionString?: string;
|
|
43
|
+
/** Legacy instrumentation key (used when `connectionString` is absent). */
|
|
44
|
+
instrumentationKey?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Pre-constructed App Insights instance. When provided, no dynamic import
|
|
47
|
+
* or `loadAppInsights()` happens — used for DI/testing.
|
|
48
|
+
*/
|
|
49
|
+
appInsights?: AppInsightsLike;
|
|
50
|
+
/**
|
|
51
|
+
* Custom async SDK factory. Overrides the built-in dynamic import of
|
|
52
|
+
* `@microsoft/applicationinsights-web`. Receives the resolved config.
|
|
53
|
+
*/
|
|
54
|
+
loadSdk?: (config: {
|
|
55
|
+
connectionString?: string;
|
|
56
|
+
instrumentationKey?: string;
|
|
57
|
+
}) => Promise<AppInsightsLike>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `http` mode — POSTs to the App Insights `/v2/track` ingestion endpoint.
|
|
61
|
+
* No browser library is loaded; server-relay friendly.
|
|
62
|
+
*/
|
|
63
|
+
interface HttpOptions extends BaseOptions {
|
|
64
|
+
mode: 'http';
|
|
65
|
+
/** Instrumentation key (iKey) for the ingestion envelope. Required. */
|
|
66
|
+
instrumentationKey: string;
|
|
67
|
+
/**
|
|
68
|
+
* Ingestion endpoint base. Default the public global endpoint
|
|
69
|
+
* `https://dc.services.visualstudio.com`. Region endpoints are accepted.
|
|
70
|
+
* Events POST to `{endpoint}/v2/track`.
|
|
71
|
+
*/
|
|
72
|
+
endpoint?: string;
|
|
73
|
+
/** Injected fetch (defaults to global fetch). */
|
|
74
|
+
fetchImpl?: typeof fetch;
|
|
75
|
+
/** Max retries for 429/5xx (exponential backoff). Default 3. */
|
|
76
|
+
maxRetries?: number;
|
|
77
|
+
/** Base backoff delay in ms. Default 500. */
|
|
78
|
+
backoffBaseMs?: number;
|
|
79
|
+
}
|
|
80
|
+
type AppInsightsSinkOptions = ClientSdkOptions | HttpOptions;
|
|
81
|
+
interface IngestEnvelope {
|
|
82
|
+
name: string;
|
|
83
|
+
time: string;
|
|
84
|
+
iKey: string;
|
|
85
|
+
tags: Record<string, string>;
|
|
86
|
+
data: {
|
|
87
|
+
baseType: 'EventData' | 'PageViewData';
|
|
88
|
+
baseData: {
|
|
89
|
+
ver: 2;
|
|
90
|
+
name: string;
|
|
91
|
+
properties: Record<string, string>;
|
|
92
|
+
measurements: Record<string, number>;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build an App Insights ingestion envelope (Breeze schema) for one canonical
|
|
98
|
+
* event. `tags` carry session/user/operation correlation; `baseData` carries
|
|
99
|
+
* the custom event with the properties/measurements split.
|
|
100
|
+
*/
|
|
101
|
+
declare function buildIngestEnvelope(ev: AnalyticsEvent, iKey: string): IngestEnvelope;
|
|
102
|
+
/**
|
|
103
|
+
* Create an Azure Application Insights {@link AnalyticsSink}.
|
|
104
|
+
*
|
|
105
|
+
* Dual-mode:
|
|
106
|
+
* - `client-sdk` — browser, lazy `@microsoft/applicationinsights-web`
|
|
107
|
+
* (`trackEvent` / `trackPageView`); the vendor lib is an OPTIONAL peer
|
|
108
|
+
* loaded only via dynamic import, never a hard/static dependency.
|
|
109
|
+
* - `http` — App Insights `/v2/track` ingestion endpoint; no browser lib,
|
|
110
|
+
* server-relay friendly.
|
|
111
|
+
*
|
|
112
|
+
* Both modes map the canonical envelope to App Insights custom events with a
|
|
113
|
+
* properties/measurements split and surface identity as
|
|
114
|
+
* `authenticatedUserId` / `anonymous`.
|
|
115
|
+
*/
|
|
116
|
+
declare function createAppInsightsSink(options: AppInsightsSinkOptions): AnalyticsSink;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* App Insights custom-event payload after mapping the canonical envelope.
|
|
120
|
+
*
|
|
121
|
+
* App Insights splits a custom event's bag into two dictionaries:
|
|
122
|
+
* - `properties` — string-valued dimensions (everything non-numeric)
|
|
123
|
+
* - `measurements`— numeric metrics (queryable/aggregatable in KQL)
|
|
124
|
+
*
|
|
125
|
+
* We additionally surface identity:
|
|
126
|
+
* - `authenticatedUserId` — present iff the envelope has `userId`
|
|
127
|
+
* - `anonymous` — `'true'` when no `userId`, else `'false'` (string for the
|
|
128
|
+
* properties bag; App Insights properties are always strings)
|
|
129
|
+
*/
|
|
130
|
+
interface AppInsightsEvent {
|
|
131
|
+
/** App Insights custom-event name. */
|
|
132
|
+
name: string;
|
|
133
|
+
/** String-valued custom dimensions. */
|
|
134
|
+
properties: Record<string, string>;
|
|
135
|
+
/** Numeric custom metrics. */
|
|
136
|
+
measurements: Record<string, number>;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Derive the App Insights custom-event name for a canonical event.
|
|
140
|
+
*
|
|
141
|
+
* - `page` → `Page View: <name|path>` (also routed to `trackPageView` in the
|
|
142
|
+
* client-sdk mode; the name here is the http-mode/event fallback)
|
|
143
|
+
* - `screen`→ `Screen: <name>`
|
|
144
|
+
* - `identify` → `Identify`
|
|
145
|
+
* - `group` → `Group`
|
|
146
|
+
* - `alias` → `Alias`
|
|
147
|
+
* - `track` → the event name (or `track` when absent)
|
|
148
|
+
*/
|
|
149
|
+
declare function eventName(ev: AnalyticsEvent): string;
|
|
150
|
+
/**
|
|
151
|
+
* Map a canonical {@link AnalyticsEvent} to an {@link AppInsightsEvent}.
|
|
152
|
+
*
|
|
153
|
+
* Properties bag composition:
|
|
154
|
+
* - canonical context: `app`, `env`, library name/version, page fields
|
|
155
|
+
* - envelope identity/routing: `messageId`, `anonymousId`, `sessionId`,
|
|
156
|
+
* `type`, `groupId?`, `previousId?`, `timestamp`, `schemaVersion`
|
|
157
|
+
* - identity surface: `authenticatedUserId` (iff `userId`), `anonymous`
|
|
158
|
+
* - the event's `properties` and `traits`, numbers → `measurements`
|
|
159
|
+
*
|
|
160
|
+
* The split is stable and lossless: every scalar lands in exactly one of the
|
|
161
|
+
* two dictionaries; nested objects are dot-flattened.
|
|
162
|
+
*/
|
|
163
|
+
declare function mapEvent(ev: AnalyticsEvent): AppInsightsEvent;
|
|
164
|
+
|
|
165
|
+
export { type AppInsightsEvent, type AppInsightsLike, type AppInsightsSinkMode, type AppInsightsSinkOptions, type ClientSdkOptions, type HttpOptions, buildIngestEnvelope, createAppInsightsSink, eventName, mapEvent };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { AnalyticsSink, AnalyticsEvent } from '../analytics/index.cjs';
|
|
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 };
|