mytart 0.2.4 → 0.4.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/README.md +109 -3
- package/dist/index.d.mts +125 -19
- package/dist/index.d.ts +125 -19
- package/dist/index.js +135 -46
- package/dist/index.mjs +135 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,10 +50,100 @@ await analytics.page({ url: 'https://example.com/pricing', name: 'Pricing' });
|
|
|
50
50
|
|
|
51
51
|
### Google Analytics 4
|
|
52
52
|
|
|
53
|
+
GA4 supports two modes via the `appType` option:
|
|
54
|
+
|
|
55
|
+
#### Server mode (default)
|
|
56
|
+
|
|
57
|
+
Uses the [GA4 Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4) — direct HTTP calls, no browser APIs. Use this for Node.js, API routes, serverless functions, etc.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
{
|
|
61
|
+
provider: 'google-analytics',
|
|
62
|
+
measurementId: 'G-XXXXXXXXXX',
|
|
63
|
+
apiSecret: 'YOUR_SECRET',
|
|
64
|
+
enabled: true,
|
|
65
|
+
// appType defaults to 'server'
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Browser mode
|
|
70
|
+
|
|
71
|
+
Injects Google's official [gtag.js snippet](https://developers.google.com/tag-platform/gtagjs) into the page. Use this for client-side tracking in any framework (React, Vue, Svelte, plain HTML, etc.). No `apiSecret` needed.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
{
|
|
75
|
+
provider: 'google-analytics',
|
|
76
|
+
measurementId: 'G-XXXXXXXXXX',
|
|
77
|
+
appType: 'browser',
|
|
78
|
+
enabled: true,
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
When `appType: 'browser'` is set:
|
|
83
|
+
|
|
84
|
+
- The gtag.js script is loaded once on the first `track()`, `identify()`, or `page()` call
|
|
85
|
+
- All calls use the standard `gtag()` API — compatible with Google Tag Tester and Tag Assistant
|
|
86
|
+
- SSR-safe: silently succeeds when `window` is undefined (e.g. during server-side rendering)
|
|
87
|
+
- `apiSecret` is not required (and not used)
|
|
88
|
+
|
|
89
|
+
#### Google Signals (demographics)
|
|
90
|
+
|
|
91
|
+
To enable demographic data (age, gender, interests) in GA4 reports, set `signals: true`:
|
|
92
|
+
|
|
53
93
|
```typescript
|
|
54
|
-
{
|
|
94
|
+
{
|
|
95
|
+
provider: 'google-analytics',
|
|
96
|
+
measurementId: 'G-XXXXXXXXXX',
|
|
97
|
+
appType: 'browser',
|
|
98
|
+
enabled: true,
|
|
99
|
+
signals: true,
|
|
100
|
+
}
|
|
55
101
|
```
|
|
56
102
|
|
|
103
|
+
This does two things automatically:
|
|
104
|
+
|
|
105
|
+
1. Passes `allow_google_signals: true` and `allow_ad_personalization_signals: true` to `gtag('config')`
|
|
106
|
+
2. Sets Consent Mode v2 defaults granting `ad_personalization`, `ad_user_data`, `ad_storage`, and `analytics_storage`
|
|
107
|
+
|
|
108
|
+
Set `signals: false` to explicitly disable Google Signals. Omit the flag entirely to use Google's default behaviour.
|
|
109
|
+
|
|
110
|
+
> **Note**: You must also enable Google Signals in the GA4 admin panel (Admin > Data Settings > Data Collection) for demographic data to appear.
|
|
111
|
+
|
|
112
|
+
#### Consent Mode v2
|
|
113
|
+
|
|
114
|
+
For GDPR/privacy compliance you can control Consent Mode v2 directly. Use `defaultConsent` to set the initial consent state (emitted before `gtag('config')`), and `updateConsent()` to change it at runtime when the user interacts with a cookie banner.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
const analytics = new Mytart({
|
|
118
|
+
providers: [{
|
|
119
|
+
provider: 'google-analytics',
|
|
120
|
+
measurementId: 'G-XXXXXXXXXX',
|
|
121
|
+
appType: 'browser',
|
|
122
|
+
enabled: true,
|
|
123
|
+
signals: true,
|
|
124
|
+
defaultConsent: {
|
|
125
|
+
ad_storage: 'denied',
|
|
126
|
+
analytics_storage: 'denied',
|
|
127
|
+
ad_user_data: 'denied',
|
|
128
|
+
ad_personalization: 'denied',
|
|
129
|
+
},
|
|
130
|
+
consentWaitForUpdate: 500, // wait 500ms for consent banner
|
|
131
|
+
}],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// After the user accepts the cookie banner:
|
|
135
|
+
await analytics.updateConsent({
|
|
136
|
+
ad_storage: 'granted',
|
|
137
|
+
analytics_storage: 'granted',
|
|
138
|
+
ad_user_data: 'granted',
|
|
139
|
+
ad_personalization: 'granted',
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
When both `signals: true` and `defaultConsent` are set, the explicit `defaultConsent` takes precedence over the auto-consent that `signals` would generate. This lets you combine `signals: true` (for the config flags) with a GDPR-safe denied-by-default consent flow.
|
|
144
|
+
|
|
145
|
+
Consent Mode is a no-op in server mode (the Measurement Protocol does not support it).
|
|
146
|
+
|
|
57
147
|
### Mixpanel
|
|
58
148
|
|
|
59
149
|
```typescript
|
|
@@ -148,6 +238,22 @@ interface PageOptions {
|
|
|
148
238
|
}
|
|
149
239
|
```
|
|
150
240
|
|
|
241
|
+
### `analytics.updateConsent(consent: ConsentSettings): Promise<void>`
|
|
242
|
+
|
|
243
|
+
Updates Google Consent Mode v2 state at runtime. Call this when the user interacts with a cookie/consent banner. Only affects Google Analytics in browser mode; all other providers ignore it.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
interface ConsentSettings {
|
|
247
|
+
ad_storage?: 'granted' | 'denied';
|
|
248
|
+
analytics_storage?: 'granted' | 'denied';
|
|
249
|
+
ad_user_data?: 'granted' | 'denied';
|
|
250
|
+
ad_personalization?: 'granted' | 'denied';
|
|
251
|
+
functionality_storage?: 'granted' | 'denied';
|
|
252
|
+
personalization_storage?: 'granted' | 'denied';
|
|
253
|
+
security_storage?: 'granted' | 'denied';
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
151
257
|
### `analytics.addProvider(config: ProviderConfig): void`
|
|
152
258
|
|
|
153
259
|
Dynamically add a provider at runtime.
|
|
@@ -187,8 +293,8 @@ All types are exported:
|
|
|
187
293
|
```typescript
|
|
188
294
|
import type {
|
|
189
295
|
MytartConfig, BaseProviderConfig, ProviderConfig, TrackOptions, IdentifyOptions, PageOptions,
|
|
190
|
-
TrackResult, MytartError, EventContext, ProviderName,
|
|
191
|
-
GoogleAnalyticsConfig, MixpanelConfig, SegmentConfig,
|
|
296
|
+
TrackResult, MytartError, EventContext, ProviderName, GoogleAnalyticsAppType,
|
|
297
|
+
GoogleAnalyticsConfig, ConsentSettings, ConsentState, MixpanelConfig, SegmentConfig,
|
|
192
298
|
AmplitudeConfig, PlausibleConfig, PostHogConfig,
|
|
193
299
|
} from 'mytart';
|
|
194
300
|
```
|
package/dist/index.d.mts
CHANGED
|
@@ -8,6 +8,33 @@ interface BaseProviderConfig {
|
|
|
8
8
|
/** Whether this provider is active. Defaults to `false` when omitted. */
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
}
|
|
11
|
+
type ConsentState = 'granted' | 'denied';
|
|
12
|
+
/**
|
|
13
|
+
* Google Consent Mode v2 settings.
|
|
14
|
+
* Controls how Google tags behave based on user consent.
|
|
15
|
+
*
|
|
16
|
+
* Key fields for Google Signals demographics (age, gender, interests):
|
|
17
|
+
* - `ad_personalization` — must be `'granted'` for Google Signals to attribute
|
|
18
|
+
* demographic data to sessions.
|
|
19
|
+
* - `ad_user_data` — must be `'granted'` for user data to be sent to Google
|
|
20
|
+
* for advertising purposes.
|
|
21
|
+
*/
|
|
22
|
+
interface ConsentSettings {
|
|
23
|
+
/** Controls storage of advertising-related cookies. */
|
|
24
|
+
ad_storage?: ConsentState;
|
|
25
|
+
/** Controls storage of analytics-related cookies. */
|
|
26
|
+
analytics_storage?: ConsentState;
|
|
27
|
+
/** Controls whether user data can be sent to Google for advertising. */
|
|
28
|
+
ad_user_data?: ConsentState;
|
|
29
|
+
/** Controls whether data can be used for personalized advertising. */
|
|
30
|
+
ad_personalization?: ConsentState;
|
|
31
|
+
/** Controls storage for functional purposes (e.g. language settings). */
|
|
32
|
+
functionality_storage?: ConsentState;
|
|
33
|
+
/** Controls storage for personalization (e.g. video recommendations). */
|
|
34
|
+
personalization_storage?: ConsentState;
|
|
35
|
+
/** Controls storage for security purposes (e.g. authentication). */
|
|
36
|
+
security_storage?: ConsentState;
|
|
37
|
+
}
|
|
11
38
|
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog';
|
|
12
39
|
type GoogleAnalyticsAppType = 'browser' | 'server';
|
|
13
40
|
interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
@@ -17,6 +44,44 @@ interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
|
17
44
|
clientId?: string;
|
|
18
45
|
debug?: boolean;
|
|
19
46
|
appType?: GoogleAnalyticsAppType;
|
|
47
|
+
/**
|
|
48
|
+
* Default consent state set before gtag('config'). In browser mode this
|
|
49
|
+
* emits `gtag('consent', 'default', ...)` so Google tags respect user
|
|
50
|
+
* consent from the very first hit. Use `Mytart.updateConsent()` to change
|
|
51
|
+
* consent at runtime (e.g. after a cookie banner interaction).
|
|
52
|
+
*
|
|
53
|
+
* For Google Signals demographics, `ad_personalization` and `ad_user_data`
|
|
54
|
+
* must eventually be set to `'granted'`.
|
|
55
|
+
*/
|
|
56
|
+
defaultConsent?: ConsentSettings;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, sets `wait_for_update` (in milliseconds) on the default
|
|
59
|
+
* consent command. This tells Google tags to wait the specified number of
|
|
60
|
+
* milliseconds for a consent update before sending the first hit. Useful
|
|
61
|
+
* when a consent management platform loads asynchronously.
|
|
62
|
+
* Defaults to `undefined` (no wait).
|
|
63
|
+
*/
|
|
64
|
+
consentWaitForUpdate?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Convenience flag to enable or disable Google Signals for demographics
|
|
67
|
+
* (age, gender, interests).
|
|
68
|
+
*
|
|
69
|
+
* - `true` — passes `allow_google_signals: true` and
|
|
70
|
+
* `allow_ad_personalization_signals: true` in the `gtag('config')`
|
|
71
|
+
* call. If `defaultConsent` is not explicitly set, automatically
|
|
72
|
+
* configures Consent Mode v2 to grant `ad_personalization`,
|
|
73
|
+
* `ad_user_data`, `ad_storage`, and `analytics_storage`.
|
|
74
|
+
* - `false` — passes `allow_google_signals: false` and
|
|
75
|
+
* `allow_ad_personalization_signals: false` to explicitly disable
|
|
76
|
+
* Google Signals.
|
|
77
|
+
* - `undefined` (default) — uses Google's default behaviour (Signals
|
|
78
|
+
* enabled when the GA4 property has it turned on in Admin).
|
|
79
|
+
*
|
|
80
|
+
* **Note**: Google Signals must also be enabled in the GA4 admin panel
|
|
81
|
+
* (Admin › Data Settings › Data Collection) for demographic data to
|
|
82
|
+
* appear. This flag controls the client-side consent and config only.
|
|
83
|
+
*/
|
|
84
|
+
signals?: boolean;
|
|
20
85
|
}
|
|
21
86
|
interface MixpanelConfig extends BaseProviderConfig {
|
|
22
87
|
provider: 'mixpanel';
|
|
@@ -106,6 +171,16 @@ declare class Mytart {
|
|
|
106
171
|
track(options: TrackOptions): Promise<TrackResult[]>;
|
|
107
172
|
identify(options: IdentifyOptions): Promise<TrackResult[]>;
|
|
108
173
|
page(options: PageOptions): Promise<TrackResult[]>;
|
|
174
|
+
/**
|
|
175
|
+
* Update consent state across all providers that support consent management.
|
|
176
|
+
* Currently this is meaningful for Google Analytics (Consent Mode v2) in
|
|
177
|
+
* browser mode. Call this when the user interacts with a cookie/consent
|
|
178
|
+
* banner.
|
|
179
|
+
*
|
|
180
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
181
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
182
|
+
*/
|
|
183
|
+
updateConsent(consent: ConsentSettings): Promise<void>;
|
|
109
184
|
addProvider(config: ProviderConfig): void;
|
|
110
185
|
removeProvider(name: string): void;
|
|
111
186
|
getProviders(): string[];
|
|
@@ -116,6 +191,11 @@ declare abstract class BaseProvider {
|
|
|
116
191
|
abstract track(options: TrackOptions): Promise<TrackResult>;
|
|
117
192
|
abstract identify(options: IdentifyOptions): Promise<TrackResult>;
|
|
118
193
|
abstract page(options: PageOptions): Promise<TrackResult>;
|
|
194
|
+
/**
|
|
195
|
+
* Update consent state. Only meaningful for providers that support consent
|
|
196
|
+
* management (e.g. Google Analytics Consent Mode v2). Default is a no-op.
|
|
197
|
+
*/
|
|
198
|
+
updateConsent(_consent: ConsentSettings): Promise<void>;
|
|
119
199
|
protected buildError(message: string, code: string, originalError?: unknown): TrackResult;
|
|
120
200
|
protected buildSuccess(statusCode?: number): TrackResult;
|
|
121
201
|
}
|
|
@@ -126,34 +206,60 @@ declare global {
|
|
|
126
206
|
dataLayer: unknown[];
|
|
127
207
|
}
|
|
128
208
|
}
|
|
129
|
-
|
|
130
|
-
(command: 'config', measurementId: string, config?: GtagConfig): void;
|
|
131
|
-
(command: 'event', eventName: string, params?: Record<string, unknown>): void;
|
|
132
|
-
(command: 'set', config: Record<string, unknown>): void;
|
|
133
|
-
(command: string, ...args: unknown[]): void;
|
|
134
|
-
}
|
|
135
|
-
interface GtagConfig {
|
|
136
|
-
send_page_view?: boolean;
|
|
137
|
-
page_title?: string;
|
|
138
|
-
page_location?: string;
|
|
139
|
-
page_referrer?: string;
|
|
140
|
-
user_id?: string;
|
|
141
|
-
[key: string]: unknown;
|
|
142
|
-
}
|
|
209
|
+
type GtagFn = (...args: unknown[]) => void;
|
|
143
210
|
declare class GoogleAnalyticsProvider extends BaseProvider {
|
|
144
211
|
readonly name = "google-analytics";
|
|
145
212
|
private readonly config;
|
|
146
213
|
private readonly http;
|
|
147
214
|
private readonly endpoint;
|
|
148
215
|
private readonly isBrowser;
|
|
149
|
-
private
|
|
216
|
+
private gtagReady;
|
|
150
217
|
constructor(config: GoogleAnalyticsConfig);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Initializes the gtag.js snippet exactly as Google's official documentation
|
|
220
|
+
* specifies. This mirrors the standard snippet:
|
|
221
|
+
*
|
|
222
|
+
* <script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
|
|
223
|
+
* <script>
|
|
224
|
+
* window.dataLayer = window.dataLayer || [];
|
|
225
|
+
* function gtag(){dataLayer.push(arguments);}
|
|
226
|
+
* gtag('js', new Date());
|
|
227
|
+
* gtag('config', 'TAG_ID');
|
|
228
|
+
* </script>
|
|
229
|
+
*
|
|
230
|
+
* When `defaultConsent` is configured, a `gtag('consent', 'default', ...)`
|
|
231
|
+
* call is emitted **before** `gtag('js')` and `gtag('config')`, as required
|
|
232
|
+
* by Google's Consent Mode v2 specification.
|
|
233
|
+
*
|
|
234
|
+
* Key details:
|
|
235
|
+
* - The script URL MUST include ?id=TAG_ID for Google Tag Tester detection
|
|
236
|
+
* - dataLayer and the gtag shim are set up BEFORE the script loads
|
|
237
|
+
* - gtag('js') and gtag('config') are called synchronously — they queue
|
|
238
|
+
* into dataLayer and are processed once the real script loads
|
|
239
|
+
* - The returned promise resolves when the script finishes loading
|
|
240
|
+
*/
|
|
241
|
+
private initGtag;
|
|
242
|
+
/**
|
|
243
|
+
* Ensures gtag is initialized exactly once. Subsequent calls return the
|
|
244
|
+
* same promise so the script is never injected twice.
|
|
245
|
+
*/
|
|
246
|
+
private ensureGtag;
|
|
154
247
|
private trackBrowser;
|
|
155
248
|
private identifyBrowser;
|
|
156
249
|
private pageBrowser;
|
|
250
|
+
private buildGtagResult;
|
|
251
|
+
/**
|
|
252
|
+
* Updates the consent state at runtime. In browser mode this emits
|
|
253
|
+
* `gtag('consent', 'update', ...)`. Call this when the user interacts
|
|
254
|
+
* with a cookie/consent banner.
|
|
255
|
+
*
|
|
256
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
257
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
258
|
+
*
|
|
259
|
+
* In server mode this is a no-op — the Measurement Protocol does not
|
|
260
|
+
* support Consent Mode.
|
|
261
|
+
*/
|
|
262
|
+
updateConsent(consent: ConsentSettings): Promise<void>;
|
|
157
263
|
track({ event, properties, userId, anonymousId, timestamp }: TrackOptions): Promise<TrackResult>;
|
|
158
264
|
identify({ userId, traits }: IdentifyOptions): Promise<TrackResult>;
|
|
159
265
|
page({ name, url, referrer, userId, anonymousId }: PageOptions): Promise<TrackResult>;
|
|
@@ -216,4 +322,4 @@ declare class PostHogProvider extends BaseProvider {
|
|
|
216
322
|
page({ name, url, userId, anonymousId, properties }: PageOptions): Promise<TrackResult>;
|
|
217
323
|
}
|
|
218
324
|
|
|
219
|
-
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
|
325
|
+
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,33 @@ interface BaseProviderConfig {
|
|
|
8
8
|
/** Whether this provider is active. Defaults to `false` when omitted. */
|
|
9
9
|
enabled?: boolean;
|
|
10
10
|
}
|
|
11
|
+
type ConsentState = 'granted' | 'denied';
|
|
12
|
+
/**
|
|
13
|
+
* Google Consent Mode v2 settings.
|
|
14
|
+
* Controls how Google tags behave based on user consent.
|
|
15
|
+
*
|
|
16
|
+
* Key fields for Google Signals demographics (age, gender, interests):
|
|
17
|
+
* - `ad_personalization` — must be `'granted'` for Google Signals to attribute
|
|
18
|
+
* demographic data to sessions.
|
|
19
|
+
* - `ad_user_data` — must be `'granted'` for user data to be sent to Google
|
|
20
|
+
* for advertising purposes.
|
|
21
|
+
*/
|
|
22
|
+
interface ConsentSettings {
|
|
23
|
+
/** Controls storage of advertising-related cookies. */
|
|
24
|
+
ad_storage?: ConsentState;
|
|
25
|
+
/** Controls storage of analytics-related cookies. */
|
|
26
|
+
analytics_storage?: ConsentState;
|
|
27
|
+
/** Controls whether user data can be sent to Google for advertising. */
|
|
28
|
+
ad_user_data?: ConsentState;
|
|
29
|
+
/** Controls whether data can be used for personalized advertising. */
|
|
30
|
+
ad_personalization?: ConsentState;
|
|
31
|
+
/** Controls storage for functional purposes (e.g. language settings). */
|
|
32
|
+
functionality_storage?: ConsentState;
|
|
33
|
+
/** Controls storage for personalization (e.g. video recommendations). */
|
|
34
|
+
personalization_storage?: ConsentState;
|
|
35
|
+
/** Controls storage for security purposes (e.g. authentication). */
|
|
36
|
+
security_storage?: ConsentState;
|
|
37
|
+
}
|
|
11
38
|
type ProviderName = 'google-analytics' | 'mixpanel' | 'segment' | 'amplitude' | 'plausible' | 'posthog';
|
|
12
39
|
type GoogleAnalyticsAppType = 'browser' | 'server';
|
|
13
40
|
interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
@@ -17,6 +44,44 @@ interface GoogleAnalyticsConfig extends BaseProviderConfig {
|
|
|
17
44
|
clientId?: string;
|
|
18
45
|
debug?: boolean;
|
|
19
46
|
appType?: GoogleAnalyticsAppType;
|
|
47
|
+
/**
|
|
48
|
+
* Default consent state set before gtag('config'). In browser mode this
|
|
49
|
+
* emits `gtag('consent', 'default', ...)` so Google tags respect user
|
|
50
|
+
* consent from the very first hit. Use `Mytart.updateConsent()` to change
|
|
51
|
+
* consent at runtime (e.g. after a cookie banner interaction).
|
|
52
|
+
*
|
|
53
|
+
* For Google Signals demographics, `ad_personalization` and `ad_user_data`
|
|
54
|
+
* must eventually be set to `'granted'`.
|
|
55
|
+
*/
|
|
56
|
+
defaultConsent?: ConsentSettings;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, sets `wait_for_update` (in milliseconds) on the default
|
|
59
|
+
* consent command. This tells Google tags to wait the specified number of
|
|
60
|
+
* milliseconds for a consent update before sending the first hit. Useful
|
|
61
|
+
* when a consent management platform loads asynchronously.
|
|
62
|
+
* Defaults to `undefined` (no wait).
|
|
63
|
+
*/
|
|
64
|
+
consentWaitForUpdate?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Convenience flag to enable or disable Google Signals for demographics
|
|
67
|
+
* (age, gender, interests).
|
|
68
|
+
*
|
|
69
|
+
* - `true` — passes `allow_google_signals: true` and
|
|
70
|
+
* `allow_ad_personalization_signals: true` in the `gtag('config')`
|
|
71
|
+
* call. If `defaultConsent` is not explicitly set, automatically
|
|
72
|
+
* configures Consent Mode v2 to grant `ad_personalization`,
|
|
73
|
+
* `ad_user_data`, `ad_storage`, and `analytics_storage`.
|
|
74
|
+
* - `false` — passes `allow_google_signals: false` and
|
|
75
|
+
* `allow_ad_personalization_signals: false` to explicitly disable
|
|
76
|
+
* Google Signals.
|
|
77
|
+
* - `undefined` (default) — uses Google's default behaviour (Signals
|
|
78
|
+
* enabled when the GA4 property has it turned on in Admin).
|
|
79
|
+
*
|
|
80
|
+
* **Note**: Google Signals must also be enabled in the GA4 admin panel
|
|
81
|
+
* (Admin › Data Settings › Data Collection) for demographic data to
|
|
82
|
+
* appear. This flag controls the client-side consent and config only.
|
|
83
|
+
*/
|
|
84
|
+
signals?: boolean;
|
|
20
85
|
}
|
|
21
86
|
interface MixpanelConfig extends BaseProviderConfig {
|
|
22
87
|
provider: 'mixpanel';
|
|
@@ -106,6 +171,16 @@ declare class Mytart {
|
|
|
106
171
|
track(options: TrackOptions): Promise<TrackResult[]>;
|
|
107
172
|
identify(options: IdentifyOptions): Promise<TrackResult[]>;
|
|
108
173
|
page(options: PageOptions): Promise<TrackResult[]>;
|
|
174
|
+
/**
|
|
175
|
+
* Update consent state across all providers that support consent management.
|
|
176
|
+
* Currently this is meaningful for Google Analytics (Consent Mode v2) in
|
|
177
|
+
* browser mode. Call this when the user interacts with a cookie/consent
|
|
178
|
+
* banner.
|
|
179
|
+
*
|
|
180
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
181
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
182
|
+
*/
|
|
183
|
+
updateConsent(consent: ConsentSettings): Promise<void>;
|
|
109
184
|
addProvider(config: ProviderConfig): void;
|
|
110
185
|
removeProvider(name: string): void;
|
|
111
186
|
getProviders(): string[];
|
|
@@ -116,6 +191,11 @@ declare abstract class BaseProvider {
|
|
|
116
191
|
abstract track(options: TrackOptions): Promise<TrackResult>;
|
|
117
192
|
abstract identify(options: IdentifyOptions): Promise<TrackResult>;
|
|
118
193
|
abstract page(options: PageOptions): Promise<TrackResult>;
|
|
194
|
+
/**
|
|
195
|
+
* Update consent state. Only meaningful for providers that support consent
|
|
196
|
+
* management (e.g. Google Analytics Consent Mode v2). Default is a no-op.
|
|
197
|
+
*/
|
|
198
|
+
updateConsent(_consent: ConsentSettings): Promise<void>;
|
|
119
199
|
protected buildError(message: string, code: string, originalError?: unknown): TrackResult;
|
|
120
200
|
protected buildSuccess(statusCode?: number): TrackResult;
|
|
121
201
|
}
|
|
@@ -126,34 +206,60 @@ declare global {
|
|
|
126
206
|
dataLayer: unknown[];
|
|
127
207
|
}
|
|
128
208
|
}
|
|
129
|
-
|
|
130
|
-
(command: 'config', measurementId: string, config?: GtagConfig): void;
|
|
131
|
-
(command: 'event', eventName: string, params?: Record<string, unknown>): void;
|
|
132
|
-
(command: 'set', config: Record<string, unknown>): void;
|
|
133
|
-
(command: string, ...args: unknown[]): void;
|
|
134
|
-
}
|
|
135
|
-
interface GtagConfig {
|
|
136
|
-
send_page_view?: boolean;
|
|
137
|
-
page_title?: string;
|
|
138
|
-
page_location?: string;
|
|
139
|
-
page_referrer?: string;
|
|
140
|
-
user_id?: string;
|
|
141
|
-
[key: string]: unknown;
|
|
142
|
-
}
|
|
209
|
+
type GtagFn = (...args: unknown[]) => void;
|
|
143
210
|
declare class GoogleAnalyticsProvider extends BaseProvider {
|
|
144
211
|
readonly name = "google-analytics";
|
|
145
212
|
private readonly config;
|
|
146
213
|
private readonly http;
|
|
147
214
|
private readonly endpoint;
|
|
148
215
|
private readonly isBrowser;
|
|
149
|
-
private
|
|
216
|
+
private gtagReady;
|
|
150
217
|
constructor(config: GoogleAnalyticsConfig);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Initializes the gtag.js snippet exactly as Google's official documentation
|
|
220
|
+
* specifies. This mirrors the standard snippet:
|
|
221
|
+
*
|
|
222
|
+
* <script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
|
|
223
|
+
* <script>
|
|
224
|
+
* window.dataLayer = window.dataLayer || [];
|
|
225
|
+
* function gtag(){dataLayer.push(arguments);}
|
|
226
|
+
* gtag('js', new Date());
|
|
227
|
+
* gtag('config', 'TAG_ID');
|
|
228
|
+
* </script>
|
|
229
|
+
*
|
|
230
|
+
* When `defaultConsent` is configured, a `gtag('consent', 'default', ...)`
|
|
231
|
+
* call is emitted **before** `gtag('js')` and `gtag('config')`, as required
|
|
232
|
+
* by Google's Consent Mode v2 specification.
|
|
233
|
+
*
|
|
234
|
+
* Key details:
|
|
235
|
+
* - The script URL MUST include ?id=TAG_ID for Google Tag Tester detection
|
|
236
|
+
* - dataLayer and the gtag shim are set up BEFORE the script loads
|
|
237
|
+
* - gtag('js') and gtag('config') are called synchronously — they queue
|
|
238
|
+
* into dataLayer and are processed once the real script loads
|
|
239
|
+
* - The returned promise resolves when the script finishes loading
|
|
240
|
+
*/
|
|
241
|
+
private initGtag;
|
|
242
|
+
/**
|
|
243
|
+
* Ensures gtag is initialized exactly once. Subsequent calls return the
|
|
244
|
+
* same promise so the script is never injected twice.
|
|
245
|
+
*/
|
|
246
|
+
private ensureGtag;
|
|
154
247
|
private trackBrowser;
|
|
155
248
|
private identifyBrowser;
|
|
156
249
|
private pageBrowser;
|
|
250
|
+
private buildGtagResult;
|
|
251
|
+
/**
|
|
252
|
+
* Updates the consent state at runtime. In browser mode this emits
|
|
253
|
+
* `gtag('consent', 'update', ...)`. Call this when the user interacts
|
|
254
|
+
* with a cookie/consent banner.
|
|
255
|
+
*
|
|
256
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
257
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
258
|
+
*
|
|
259
|
+
* In server mode this is a no-op — the Measurement Protocol does not
|
|
260
|
+
* support Consent Mode.
|
|
261
|
+
*/
|
|
262
|
+
updateConsent(consent: ConsentSettings): Promise<void>;
|
|
157
263
|
track({ event, properties, userId, anonymousId, timestamp }: TrackOptions): Promise<TrackResult>;
|
|
158
264
|
identify({ userId, traits }: IdentifyOptions): Promise<TrackResult>;
|
|
159
265
|
page({ name, url, referrer, userId, anonymousId }: PageOptions): Promise<TrackResult>;
|
|
@@ -216,4 +322,4 @@ declare class PostHogProvider extends BaseProvider {
|
|
|
216
322
|
page({ name, url, userId, anonymousId, properties }: PageOptions): Promise<TrackResult>;
|
|
217
323
|
}
|
|
218
324
|
|
|
219
|
-
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
|
325
|
+
export { type AmplitudeConfig, AmplitudeProvider, BaseProvider, type BaseProviderConfig, type ConsentSettings, type ConsentState, type EventContext, type GoogleAnalyticsAppType, type GoogleAnalyticsConfig, GoogleAnalyticsProvider, type IdentifyOptions, type MixpanelConfig, MixpanelProvider, Mytart, type MytartConfig, type MytartError, type PageOptions, type PlausibleConfig, PlausibleProvider, type PostHogConfig, PostHogProvider, type ProviderConfig, type ProviderName, type SegmentConfig, SegmentProvider, type TrackOptions, type TrackResult };
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
43
|
|
|
44
44
|
// src/providers/base.ts
|
|
45
45
|
var BaseProvider = class {
|
|
46
|
+
/**
|
|
47
|
+
* Update consent state. Only meaningful for providers that support consent
|
|
48
|
+
* management (e.g. Google Analytics Consent Mode v2). Default is a no-op.
|
|
49
|
+
*/
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
+
async updateConsent(_consent) {
|
|
52
|
+
}
|
|
46
53
|
buildError(message, code, originalError) {
|
|
47
54
|
return {
|
|
48
55
|
provider: this.name,
|
|
@@ -82,75 +89,116 @@ function isAxiosError(error) {
|
|
|
82
89
|
// src/providers/google-analytics.ts
|
|
83
90
|
var GA4_ENDPOINT = "https://www.google-analytics.com/mp/collect";
|
|
84
91
|
var GA4_DEBUG_ENDPOINT = "https://www.google-analytics.com/debug/mp/collect";
|
|
85
|
-
var
|
|
92
|
+
var GTAG_SCRIPT_URL = "https://www.googletagmanager.com/gtag/js";
|
|
86
93
|
var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
87
94
|
constructor(config) {
|
|
88
95
|
super();
|
|
89
96
|
this.name = "google-analytics";
|
|
90
|
-
this.
|
|
97
|
+
this.gtagReady = null;
|
|
91
98
|
this.config = config;
|
|
92
99
|
this.http = createHttpClient();
|
|
93
100
|
this.endpoint = config.debug ? GA4_DEBUG_ENDPOINT : GA4_ENDPOINT;
|
|
94
101
|
this.isBrowser = config.appType === "browser";
|
|
95
102
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Initializes the gtag.js snippet exactly as Google's official documentation
|
|
105
|
+
* specifies. This mirrors the standard snippet:
|
|
106
|
+
*
|
|
107
|
+
* <script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
|
|
108
|
+
* <script>
|
|
109
|
+
* window.dataLayer = window.dataLayer || [];
|
|
110
|
+
* function gtag(){dataLayer.push(arguments);}
|
|
111
|
+
* gtag('js', new Date());
|
|
112
|
+
* gtag('config', 'TAG_ID');
|
|
113
|
+
* </script>
|
|
114
|
+
*
|
|
115
|
+
* When `defaultConsent` is configured, a `gtag('consent', 'default', ...)`
|
|
116
|
+
* call is emitted **before** `gtag('js')` and `gtag('config')`, as required
|
|
117
|
+
* by Google's Consent Mode v2 specification.
|
|
118
|
+
*
|
|
119
|
+
* Key details:
|
|
120
|
+
* - The script URL MUST include ?id=TAG_ID for Google Tag Tester detection
|
|
121
|
+
* - dataLayer and the gtag shim are set up BEFORE the script loads
|
|
122
|
+
* - gtag('js') and gtag('config') are called synchronously — they queue
|
|
123
|
+
* into dataLayer and are processed once the real script loads
|
|
124
|
+
* - The returned promise resolves when the script finishes loading
|
|
125
|
+
*/
|
|
126
|
+
initGtag() {
|
|
127
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
128
|
+
return Promise.resolve();
|
|
99
129
|
}
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
window.dataLayer = window.dataLayer || [];
|
|
131
|
+
window.gtag = function gtag() {
|
|
132
|
+
window.dataLayer.push(arguments);
|
|
133
|
+
};
|
|
134
|
+
const resolvedConsent = this.config.defaultConsent ?? (this.config.signals === true ? {
|
|
135
|
+
ad_storage: "granted",
|
|
136
|
+
analytics_storage: "granted",
|
|
137
|
+
ad_user_data: "granted",
|
|
138
|
+
ad_personalization: "granted"
|
|
139
|
+
} : void 0);
|
|
140
|
+
if (resolvedConsent) {
|
|
141
|
+
const consentParams = { ...resolvedConsent };
|
|
142
|
+
if (this.config.consentWaitForUpdate !== void 0) {
|
|
143
|
+
consentParams["wait_for_update"] = this.config.consentWaitForUpdate;
|
|
144
|
+
}
|
|
145
|
+
window.gtag("consent", "default", consentParams);
|
|
146
|
+
}
|
|
147
|
+
window.gtag("js", /* @__PURE__ */ new Date());
|
|
148
|
+
const configParams = {};
|
|
149
|
+
if (this.config.signals === true) {
|
|
150
|
+
configParams["allow_google_signals"] = true;
|
|
151
|
+
configParams["allow_ad_personalization_signals"] = true;
|
|
152
|
+
} else if (this.config.signals === false) {
|
|
153
|
+
configParams["allow_google_signals"] = false;
|
|
154
|
+
configParams["allow_ad_personalization_signals"] = false;
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(configParams).length > 0) {
|
|
157
|
+
window.gtag("config", this.config.measurementId, configParams);
|
|
158
|
+
} else {
|
|
159
|
+
window.gtag("config", this.config.measurementId);
|
|
102
160
|
}
|
|
103
|
-
await this.injectGtagScript();
|
|
104
|
-
}
|
|
105
|
-
injectGtagScript() {
|
|
106
161
|
return new Promise((resolve) => {
|
|
107
|
-
window.dataLayer = window.dataLayer || [];
|
|
108
|
-
window.gtag = window.gtag || function gtag(...args) {
|
|
109
|
-
window.dataLayer.push(args);
|
|
110
|
-
};
|
|
111
162
|
const script = document.createElement("script");
|
|
112
163
|
script.async = true;
|
|
113
|
-
script.src =
|
|
114
|
-
script.onload = () =>
|
|
115
|
-
window.gtag("js", /* @__PURE__ */ new Date());
|
|
116
|
-
window.gtag("config", this.config.measurementId, {
|
|
117
|
-
send_page_view: false
|
|
118
|
-
});
|
|
119
|
-
resolve();
|
|
120
|
-
};
|
|
164
|
+
script.src = `${GTAG_SCRIPT_URL}?id=${this.config.measurementId}`;
|
|
165
|
+
script.onload = () => resolve();
|
|
121
166
|
script.onerror = () => resolve();
|
|
122
|
-
|
|
123
|
-
firstScript.parentNode?.insertBefore(script, firstScript);
|
|
167
|
+
document.head.appendChild(script);
|
|
124
168
|
});
|
|
125
169
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Ensures gtag is initialized exactly once. Subsequent calls return the
|
|
172
|
+
* same promise so the script is never injected twice.
|
|
173
|
+
*/
|
|
174
|
+
ensureGtag() {
|
|
175
|
+
if (!this.gtagReady) {
|
|
176
|
+
this.gtagReady = this.initGtag();
|
|
177
|
+
}
|
|
178
|
+
return this.gtagReady;
|
|
132
179
|
}
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Browser mode methods — delegate to gtag()
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
133
183
|
async trackBrowser({ event, properties, userId }) {
|
|
134
184
|
if (typeof window === "undefined") {
|
|
135
185
|
return this.buildGtagResult();
|
|
136
186
|
}
|
|
137
|
-
await this.
|
|
187
|
+
await this.ensureGtag();
|
|
138
188
|
if (userId) {
|
|
139
189
|
window.gtag("set", { user_id: userId });
|
|
140
190
|
}
|
|
141
|
-
window.gtag("event", event, properties
|
|
191
|
+
window.gtag("event", event, properties ?? {});
|
|
142
192
|
return this.buildGtagResult();
|
|
143
193
|
}
|
|
144
194
|
async identifyBrowser({ userId, traits }) {
|
|
145
195
|
if (typeof window === "undefined") {
|
|
146
196
|
return this.buildGtagResult();
|
|
147
197
|
}
|
|
148
|
-
await this.
|
|
198
|
+
await this.ensureGtag();
|
|
149
199
|
window.gtag("set", { user_id: userId });
|
|
150
200
|
if (traits) {
|
|
151
|
-
|
|
152
|
-
window.gtag("set", { [key]: value });
|
|
153
|
-
});
|
|
201
|
+
window.gtag("set", "user_properties", traits);
|
|
154
202
|
}
|
|
155
203
|
return this.buildGtagResult();
|
|
156
204
|
}
|
|
@@ -158,31 +206,60 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
158
206
|
if (typeof window === "undefined") {
|
|
159
207
|
return this.buildGtagResult();
|
|
160
208
|
}
|
|
161
|
-
await this.
|
|
162
|
-
|
|
209
|
+
await this.ensureGtag();
|
|
210
|
+
if (userId) {
|
|
211
|
+
window.gtag("set", { user_id: userId });
|
|
212
|
+
}
|
|
213
|
+
window.gtag("event", "page_view", {
|
|
163
214
|
page_title: name,
|
|
164
215
|
page_location: url,
|
|
165
|
-
page_referrer: referrer
|
|
166
|
-
|
|
216
|
+
page_referrer: referrer
|
|
217
|
+
});
|
|
218
|
+
return this.buildGtagResult();
|
|
219
|
+
}
|
|
220
|
+
buildGtagResult() {
|
|
221
|
+
return {
|
|
222
|
+
provider: this.name,
|
|
223
|
+
success: true,
|
|
224
|
+
statusCode: 200
|
|
167
225
|
};
|
|
168
|
-
|
|
169
|
-
|
|
226
|
+
}
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Consent Mode v2
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
/**
|
|
231
|
+
* Updates the consent state at runtime. In browser mode this emits
|
|
232
|
+
* `gtag('consent', 'update', ...)`. Call this when the user interacts
|
|
233
|
+
* with a cookie/consent banner.
|
|
234
|
+
*
|
|
235
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
236
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
237
|
+
*
|
|
238
|
+
* In server mode this is a no-op — the Measurement Protocol does not
|
|
239
|
+
* support Consent Mode.
|
|
240
|
+
*/
|
|
241
|
+
async updateConsent(consent) {
|
|
242
|
+
if (!this.isBrowser || typeof window === "undefined") {
|
|
243
|
+
return;
|
|
170
244
|
}
|
|
171
|
-
|
|
172
|
-
|
|
245
|
+
await this.ensureGtag();
|
|
246
|
+
window.gtag("consent", "update", consent);
|
|
173
247
|
}
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
// Public API — routes to browser or server mode
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
174
251
|
async track({ event, properties, userId, anonymousId, timestamp }) {
|
|
175
252
|
if (this.isBrowser) {
|
|
176
253
|
return this.trackBrowser({ event, properties, userId, anonymousId, timestamp });
|
|
177
254
|
}
|
|
178
255
|
try {
|
|
179
|
-
const
|
|
256
|
+
const clientId = anonymousId ?? this.config.clientId ?? "anonymous";
|
|
180
257
|
const params = { ...properties };
|
|
181
258
|
if (timestamp) {
|
|
182
259
|
params["timestamp_micros"] = timestamp.getTime() * 1e3;
|
|
183
260
|
}
|
|
184
261
|
const body = {
|
|
185
|
-
client_id,
|
|
262
|
+
client_id: clientId,
|
|
186
263
|
events: [{ name: event, params }]
|
|
187
264
|
};
|
|
188
265
|
if (userId) {
|
|
@@ -728,6 +805,18 @@ var Mytart = class {
|
|
|
728
805
|
};
|
|
729
806
|
return Promise.all(this.providers.map((p) => p.page(enriched)));
|
|
730
807
|
}
|
|
808
|
+
/**
|
|
809
|
+
* Update consent state across all providers that support consent management.
|
|
810
|
+
* Currently this is meaningful for Google Analytics (Consent Mode v2) in
|
|
811
|
+
* browser mode. Call this when the user interacts with a cookie/consent
|
|
812
|
+
* banner.
|
|
813
|
+
*
|
|
814
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
815
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
816
|
+
*/
|
|
817
|
+
async updateConsent(consent) {
|
|
818
|
+
await Promise.all(this.providers.map((p) => p.updateConsent(consent)));
|
|
819
|
+
}
|
|
731
820
|
addProvider(config) {
|
|
732
821
|
if (config.enabled !== true) return;
|
|
733
822
|
this.providers.push(createProvider(config));
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
// src/providers/base.ts
|
|
2
2
|
var BaseProvider = class {
|
|
3
|
+
/**
|
|
4
|
+
* Update consent state. Only meaningful for providers that support consent
|
|
5
|
+
* management (e.g. Google Analytics Consent Mode v2). Default is a no-op.
|
|
6
|
+
*/
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
async updateConsent(_consent) {
|
|
9
|
+
}
|
|
3
10
|
buildError(message, code, originalError) {
|
|
4
11
|
return {
|
|
5
12
|
provider: this.name,
|
|
@@ -39,75 +46,116 @@ function isAxiosError(error) {
|
|
|
39
46
|
// src/providers/google-analytics.ts
|
|
40
47
|
var GA4_ENDPOINT = "https://www.google-analytics.com/mp/collect";
|
|
41
48
|
var GA4_DEBUG_ENDPOINT = "https://www.google-analytics.com/debug/mp/collect";
|
|
42
|
-
var
|
|
49
|
+
var GTAG_SCRIPT_URL = "https://www.googletagmanager.com/gtag/js";
|
|
43
50
|
var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
44
51
|
constructor(config) {
|
|
45
52
|
super();
|
|
46
53
|
this.name = "google-analytics";
|
|
47
|
-
this.
|
|
54
|
+
this.gtagReady = null;
|
|
48
55
|
this.config = config;
|
|
49
56
|
this.http = createHttpClient();
|
|
50
57
|
this.endpoint = config.debug ? GA4_DEBUG_ENDPOINT : GA4_ENDPOINT;
|
|
51
58
|
this.isBrowser = config.appType === "browser";
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Initializes the gtag.js snippet exactly as Google's official documentation
|
|
62
|
+
* specifies. This mirrors the standard snippet:
|
|
63
|
+
*
|
|
64
|
+
* <script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
|
|
65
|
+
* <script>
|
|
66
|
+
* window.dataLayer = window.dataLayer || [];
|
|
67
|
+
* function gtag(){dataLayer.push(arguments);}
|
|
68
|
+
* gtag('js', new Date());
|
|
69
|
+
* gtag('config', 'TAG_ID');
|
|
70
|
+
* </script>
|
|
71
|
+
*
|
|
72
|
+
* When `defaultConsent` is configured, a `gtag('consent', 'default', ...)`
|
|
73
|
+
* call is emitted **before** `gtag('js')` and `gtag('config')`, as required
|
|
74
|
+
* by Google's Consent Mode v2 specification.
|
|
75
|
+
*
|
|
76
|
+
* Key details:
|
|
77
|
+
* - The script URL MUST include ?id=TAG_ID for Google Tag Tester detection
|
|
78
|
+
* - dataLayer and the gtag shim are set up BEFORE the script loads
|
|
79
|
+
* - gtag('js') and gtag('config') are called synchronously — they queue
|
|
80
|
+
* into dataLayer and are processed once the real script loads
|
|
81
|
+
* - The returned promise resolves when the script finishes loading
|
|
82
|
+
*/
|
|
83
|
+
initGtag() {
|
|
84
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
85
|
+
return Promise.resolve();
|
|
56
86
|
}
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
window.dataLayer = window.dataLayer || [];
|
|
88
|
+
window.gtag = function gtag() {
|
|
89
|
+
window.dataLayer.push(arguments);
|
|
90
|
+
};
|
|
91
|
+
const resolvedConsent = this.config.defaultConsent ?? (this.config.signals === true ? {
|
|
92
|
+
ad_storage: "granted",
|
|
93
|
+
analytics_storage: "granted",
|
|
94
|
+
ad_user_data: "granted",
|
|
95
|
+
ad_personalization: "granted"
|
|
96
|
+
} : void 0);
|
|
97
|
+
if (resolvedConsent) {
|
|
98
|
+
const consentParams = { ...resolvedConsent };
|
|
99
|
+
if (this.config.consentWaitForUpdate !== void 0) {
|
|
100
|
+
consentParams["wait_for_update"] = this.config.consentWaitForUpdate;
|
|
101
|
+
}
|
|
102
|
+
window.gtag("consent", "default", consentParams);
|
|
103
|
+
}
|
|
104
|
+
window.gtag("js", /* @__PURE__ */ new Date());
|
|
105
|
+
const configParams = {};
|
|
106
|
+
if (this.config.signals === true) {
|
|
107
|
+
configParams["allow_google_signals"] = true;
|
|
108
|
+
configParams["allow_ad_personalization_signals"] = true;
|
|
109
|
+
} else if (this.config.signals === false) {
|
|
110
|
+
configParams["allow_google_signals"] = false;
|
|
111
|
+
configParams["allow_ad_personalization_signals"] = false;
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(configParams).length > 0) {
|
|
114
|
+
window.gtag("config", this.config.measurementId, configParams);
|
|
115
|
+
} else {
|
|
116
|
+
window.gtag("config", this.config.measurementId);
|
|
59
117
|
}
|
|
60
|
-
await this.injectGtagScript();
|
|
61
|
-
}
|
|
62
|
-
injectGtagScript() {
|
|
63
118
|
return new Promise((resolve) => {
|
|
64
|
-
window.dataLayer = window.dataLayer || [];
|
|
65
|
-
window.gtag = window.gtag || function gtag(...args) {
|
|
66
|
-
window.dataLayer.push(args);
|
|
67
|
-
};
|
|
68
119
|
const script = document.createElement("script");
|
|
69
120
|
script.async = true;
|
|
70
|
-
script.src =
|
|
71
|
-
script.onload = () =>
|
|
72
|
-
window.gtag("js", /* @__PURE__ */ new Date());
|
|
73
|
-
window.gtag("config", this.config.measurementId, {
|
|
74
|
-
send_page_view: false
|
|
75
|
-
});
|
|
76
|
-
resolve();
|
|
77
|
-
};
|
|
121
|
+
script.src = `${GTAG_SCRIPT_URL}?id=${this.config.measurementId}`;
|
|
122
|
+
script.onload = () => resolve();
|
|
78
123
|
script.onerror = () => resolve();
|
|
79
|
-
|
|
80
|
-
firstScript.parentNode?.insertBefore(script, firstScript);
|
|
124
|
+
document.head.appendChild(script);
|
|
81
125
|
});
|
|
82
126
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Ensures gtag is initialized exactly once. Subsequent calls return the
|
|
129
|
+
* same promise so the script is never injected twice.
|
|
130
|
+
*/
|
|
131
|
+
ensureGtag() {
|
|
132
|
+
if (!this.gtagReady) {
|
|
133
|
+
this.gtagReady = this.initGtag();
|
|
134
|
+
}
|
|
135
|
+
return this.gtagReady;
|
|
89
136
|
}
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// Browser mode methods — delegate to gtag()
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
90
140
|
async trackBrowser({ event, properties, userId }) {
|
|
91
141
|
if (typeof window === "undefined") {
|
|
92
142
|
return this.buildGtagResult();
|
|
93
143
|
}
|
|
94
|
-
await this.
|
|
144
|
+
await this.ensureGtag();
|
|
95
145
|
if (userId) {
|
|
96
146
|
window.gtag("set", { user_id: userId });
|
|
97
147
|
}
|
|
98
|
-
window.gtag("event", event, properties
|
|
148
|
+
window.gtag("event", event, properties ?? {});
|
|
99
149
|
return this.buildGtagResult();
|
|
100
150
|
}
|
|
101
151
|
async identifyBrowser({ userId, traits }) {
|
|
102
152
|
if (typeof window === "undefined") {
|
|
103
153
|
return this.buildGtagResult();
|
|
104
154
|
}
|
|
105
|
-
await this.
|
|
155
|
+
await this.ensureGtag();
|
|
106
156
|
window.gtag("set", { user_id: userId });
|
|
107
157
|
if (traits) {
|
|
108
|
-
|
|
109
|
-
window.gtag("set", { [key]: value });
|
|
110
|
-
});
|
|
158
|
+
window.gtag("set", "user_properties", traits);
|
|
111
159
|
}
|
|
112
160
|
return this.buildGtagResult();
|
|
113
161
|
}
|
|
@@ -115,31 +163,60 @@ var GoogleAnalyticsProvider = class extends BaseProvider {
|
|
|
115
163
|
if (typeof window === "undefined") {
|
|
116
164
|
return this.buildGtagResult();
|
|
117
165
|
}
|
|
118
|
-
await this.
|
|
119
|
-
|
|
166
|
+
await this.ensureGtag();
|
|
167
|
+
if (userId) {
|
|
168
|
+
window.gtag("set", { user_id: userId });
|
|
169
|
+
}
|
|
170
|
+
window.gtag("event", "page_view", {
|
|
120
171
|
page_title: name,
|
|
121
172
|
page_location: url,
|
|
122
|
-
page_referrer: referrer
|
|
123
|
-
|
|
173
|
+
page_referrer: referrer
|
|
174
|
+
});
|
|
175
|
+
return this.buildGtagResult();
|
|
176
|
+
}
|
|
177
|
+
buildGtagResult() {
|
|
178
|
+
return {
|
|
179
|
+
provider: this.name,
|
|
180
|
+
success: true,
|
|
181
|
+
statusCode: 200
|
|
124
182
|
};
|
|
125
|
-
|
|
126
|
-
|
|
183
|
+
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Consent Mode v2
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
/**
|
|
188
|
+
* Updates the consent state at runtime. In browser mode this emits
|
|
189
|
+
* `gtag('consent', 'update', ...)`. Call this when the user interacts
|
|
190
|
+
* with a cookie/consent banner.
|
|
191
|
+
*
|
|
192
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
193
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
194
|
+
*
|
|
195
|
+
* In server mode this is a no-op — the Measurement Protocol does not
|
|
196
|
+
* support Consent Mode.
|
|
197
|
+
*/
|
|
198
|
+
async updateConsent(consent) {
|
|
199
|
+
if (!this.isBrowser || typeof window === "undefined") {
|
|
200
|
+
return;
|
|
127
201
|
}
|
|
128
|
-
|
|
129
|
-
|
|
202
|
+
await this.ensureGtag();
|
|
203
|
+
window.gtag("consent", "update", consent);
|
|
130
204
|
}
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Public API — routes to browser or server mode
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
131
208
|
async track({ event, properties, userId, anonymousId, timestamp }) {
|
|
132
209
|
if (this.isBrowser) {
|
|
133
210
|
return this.trackBrowser({ event, properties, userId, anonymousId, timestamp });
|
|
134
211
|
}
|
|
135
212
|
try {
|
|
136
|
-
const
|
|
213
|
+
const clientId = anonymousId ?? this.config.clientId ?? "anonymous";
|
|
137
214
|
const params = { ...properties };
|
|
138
215
|
if (timestamp) {
|
|
139
216
|
params["timestamp_micros"] = timestamp.getTime() * 1e3;
|
|
140
217
|
}
|
|
141
218
|
const body = {
|
|
142
|
-
client_id,
|
|
219
|
+
client_id: clientId,
|
|
143
220
|
events: [{ name: event, params }]
|
|
144
221
|
};
|
|
145
222
|
if (userId) {
|
|
@@ -685,6 +762,18 @@ var Mytart = class {
|
|
|
685
762
|
};
|
|
686
763
|
return Promise.all(this.providers.map((p) => p.page(enriched)));
|
|
687
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Update consent state across all providers that support consent management.
|
|
767
|
+
* Currently this is meaningful for Google Analytics (Consent Mode v2) in
|
|
768
|
+
* browser mode. Call this when the user interacts with a cookie/consent
|
|
769
|
+
* banner.
|
|
770
|
+
*
|
|
771
|
+
* To enable Google Signals demographics (age, gender, interests), grant
|
|
772
|
+
* at least `ad_personalization` and `ad_user_data`.
|
|
773
|
+
*/
|
|
774
|
+
async updateConsent(consent) {
|
|
775
|
+
await Promise.all(this.providers.map((p) => p.updateConsent(consent)));
|
|
776
|
+
}
|
|
688
777
|
addProvider(config) {
|
|
689
778
|
if (config.enabled !== true) return;
|
|
690
779
|
this.providers.push(createProvider(config));
|