@se-studio/core-ui 1.33.3 → 1.35.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/ANALYTICS.md +33 -5
- package/CHANGELOG.md +18 -0
- package/CONSENT.md +52 -0
- package/dist/analytics/adapters/CompositeAnalyticsAdapter.d.ts +2 -1
- package/dist/analytics/adapters/CompositeAnalyticsAdapter.d.ts.map +1 -1
- package/dist/analytics/adapters/CompositeAnalyticsAdapter.js +6 -5
- package/dist/analytics/adapters/CompositeAnalyticsAdapter.js.map +1 -1
- package/dist/analytics/adapters/ConsentAwareAdapter.d.ts +29 -2
- package/dist/analytics/adapters/ConsentAwareAdapter.d.ts.map +1 -1
- package/dist/analytics/adapters/ConsentAwareAdapter.js +49 -5
- package/dist/analytics/adapters/ConsentAwareAdapter.js.map +1 -1
- package/dist/analytics/adapters/GoogleTagManagerAdapter.d.ts +18 -17
- package/dist/analytics/adapters/GoogleTagManagerAdapter.d.ts.map +1 -1
- package/dist/analytics/adapters/GoogleTagManagerAdapter.js +64 -35
- package/dist/analytics/adapters/GoogleTagManagerAdapter.js.map +1 -1
- package/dist/analytics/adapters/adapterMethods.d.ts +4 -0
- package/dist/analytics/adapters/adapterMethods.d.ts.map +1 -0
- package/dist/analytics/adapters/adapterMethods.js +11 -0
- package/dist/analytics/adapters/adapterMethods.js.map +1 -0
- package/dist/analytics/adapters/index.d.ts +1 -0
- package/dist/analytics/adapters/index.d.ts.map +1 -1
- package/dist/analytics/adapters/index.js.map +1 -1
- package/dist/analytics/consent/resolveAnalyticsConsent.d.ts +39 -0
- package/dist/analytics/consent/resolveAnalyticsConsent.d.ts.map +1 -0
- package/dist/analytics/consent/resolveAnalyticsConsent.js +53 -0
- package/dist/analytics/consent/resolveAnalyticsConsent.js.map +1 -0
- package/dist/analytics/form/createFormAnalyticsSession.d.ts +36 -0
- package/dist/analytics/form/createFormAnalyticsSession.d.ts.map +1 -0
- package/dist/analytics/form/createFormAnalyticsSession.js +62 -0
- package/dist/analytics/form/createFormAnalyticsSession.js.map +1 -0
- package/dist/analytics/form/formAnalyticsTypes.d.ts +43 -0
- package/dist/analytics/form/formAnalyticsTypes.d.ts.map +1 -0
- package/dist/analytics/form/formAnalyticsTypes.js +40 -0
- package/dist/analytics/form/formAnalyticsTypes.js.map +1 -0
- package/dist/analytics/form/index.d.ts +5 -0
- package/dist/analytics/form/index.d.ts.map +1 -0
- package/dist/analytics/form/index.js +5 -0
- package/dist/analytics/form/index.js.map +1 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.d.ts +9 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.d.ts.map +1 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.js +62 -0
- package/dist/analytics/form/sanitizeFormAnalyticsProperties.js.map +1 -0
- package/dist/analytics/form/useFormAnalytics.d.ts +13 -0
- package/dist/analytics/form/useFormAnalytics.d.ts.map +1 -0
- package/dist/analytics/form/useFormAnalytics.js +67 -0
- package/dist/analytics/form/useFormAnalytics.js.map +1 -0
- package/dist/analytics/formAnalyticsEntry.d.ts +10 -0
- package/dist/analytics/formAnalyticsEntry.d.ts.map +1 -0
- package/dist/analytics/formAnalyticsEntry.js +9 -0
- package/dist/analytics/formAnalyticsEntry.js.map +1 -0
- package/dist/analytics/types.d.ts +20 -0
- package/dist/analytics/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/docs/PACKAGE_EXPORT_BOUNDARIES.md +1 -0
- package/docs/llms.md +34 -8
- package/package.json +5 -1
package/ANALYTICS.md
CHANGED
|
@@ -81,14 +81,36 @@ Target names (same property names where the site already uses them):
|
|
|
81
81
|
|-------|------|
|
|
82
82
|
| `page_view` | Full and soft navigations |
|
|
83
83
|
| `click` | Tracked UI clicks (`trackClick`) |
|
|
84
|
-
| `form_view` | Form
|
|
84
|
+
| `form_view` | Form component mount (not viewport impression; see below) |
|
|
85
85
|
| `form_start` | First interaction with a form field |
|
|
86
|
-
| `form_submit` | Successful submit |
|
|
86
|
+
| `form_submit` | Successful submit (HubSpot/API accept) |
|
|
87
87
|
| `form_error` | Submit failed (validation or HubSpot/API error shown to the user) |
|
|
88
88
|
| `video_play` | Video starts |
|
|
89
89
|
|
|
90
90
|
Sites may add extra events. Do not rename or drop an existing site event only to converge. Policy: `docs/PHILOSOPHY.md` R7.5. Form failure handling: `@se-studio/hubspot` `docs/llms.md`.
|
|
91
91
|
|
|
92
|
+
### Form analytics (non-PII)
|
|
93
|
+
|
|
94
|
+
`useFormAnalytics` / `createFormAnalyticsSession` emit the four events through `useAnalytics().trackEvent`. Import them from `@se-studio/core-ui/form-analytics` (not the root `@se-studio/core-ui` barrel). `@se-studio/hubspot` `useHubspotSubmit` and `HubspotDynamicForm` emit only when you pass `formAnalytics={true}` (off by default so upgrades are a no-op).
|
|
95
|
+
|
|
96
|
+
`form_view` fires **on mount**, not when the form enters the viewport. A form below the fold still counts as a view, so view-to-submit will sit close to page-view rates. That is v1; do not treat it as an impression rate. A visibility option can come later.
|
|
97
|
+
|
|
98
|
+
Allowed parameters only:
|
|
99
|
+
|
|
100
|
+
| Parameter | Meaning |
|
|
101
|
+
|-----------|---------|
|
|
102
|
+
| `form_id` | HubSpot form GUID or site form id. Identifier token (`SAFE_TOKEN`); digits-only ids pass validation. |
|
|
103
|
+
| `form_name` | Form name (not a person name) |
|
|
104
|
+
| `form_type` | e.g. `hubspot` |
|
|
105
|
+
| `cms_entry_id` | Contentful entry id when the renderer passes it |
|
|
106
|
+
| `step` | Multi-step index or id |
|
|
107
|
+
| `error_code` | Token such as `hubspot_reject`, `too_fast`, `constraint` |
|
|
108
|
+
| `error_category` | `validation` \| `api` \| `too_fast` \| `network` \| `redirect` |
|
|
109
|
+
|
|
110
|
+
Never send field values, emails, names, phone numbers, or raw error messages. Payloads are allowlisted in `sanitizeFormAnalyticsProperties`. Custom site forms should import `useFormAnalytics` from `@se-studio/core-ui/form-analytics` instead of inventing their own event names.
|
|
111
|
+
|
|
112
|
+
HubSpot / form packages must import from `@se-studio/core-ui/form-analytics` (slim entry: provider + form events, no Visual/Lottie). Do not re-export that module from the root `@se-studio/core-ui` barrel.
|
|
113
|
+
|
|
92
114
|
## Google Tag Manager (GTM)
|
|
93
115
|
|
|
94
116
|
The `GoogleTagManagerAdapter` supports both GTM Container ID and GA4 Measurement ID.
|
|
@@ -98,9 +120,12 @@ const adapter = new GoogleTagManagerAdapter({
|
|
|
98
120
|
containerId: 'GTM-XXXXXXX', // Loads GTM script
|
|
99
121
|
// OR
|
|
100
122
|
measurementId: 'G-XXXXXXXX', // Loads gtag.js directly
|
|
123
|
+
consentDefault: 'granted', // omit unless the site (not the CMP) should set Consent Mode default
|
|
101
124
|
});
|
|
102
125
|
```
|
|
103
126
|
|
|
127
|
+
Consent Mode v2: `initialize()` pushes `gtag('consent', 'default', {…})` **only when `consentDefault` is set**, before loading GTM (as an `arguments` object, not a dataLayer array). Omit `consentDefault` so the CMP decides (R7.7) — the adapter must not grant `ad_storage` / `ad_user_data` / `ad_personalization` by implication. Pass `consentDefault: 'granted'` for US opt-out sites without a CMP, or `'denied'` for adapter-owned opt-in. `updateConsent({ analytics, advertising })` pushes `gtag('consent', 'update', {…})`. If `updateConsent` runs before `initialize`, the update is stashed and flushed after any default command, before the container or gtag.js.
|
|
128
|
+
|
|
104
129
|
### GTM Data Layer Events
|
|
105
130
|
|
|
106
131
|
The adapter pushes events to `window.dataLayer`:
|
|
@@ -147,10 +172,13 @@ import {
|
|
|
147
172
|
import { HubSpotAnalyticsAdapter } from '@se-studio/hubspot';
|
|
148
173
|
|
|
149
174
|
const composite = new CompositeAnalyticsAdapter([
|
|
150
|
-
new GoogleTagManagerAdapter({ containerId: gtmId }),
|
|
151
|
-
new HubSpotAnalyticsAdapter({ portalId: hubspotPortalId }),
|
|
175
|
+
new GoogleTagManagerAdapter({ containerId: gtmId, consentDefault: 'granted' }),
|
|
176
|
+
new HubSpotAnalyticsAdapter({ portalId: hubspotPortalId, getConsent: hasConsent }),
|
|
152
177
|
]);
|
|
153
|
-
const adapter = new ConsentAwareAdapter(composite, 'analytics', hasConsent
|
|
178
|
+
const adapter = new ConsentAwareAdapter(composite, 'analytics', hasConsent, {
|
|
179
|
+
syncConsent: true,
|
|
180
|
+
});
|
|
181
|
+
adapter.initialize();
|
|
154
182
|
```
|
|
155
183
|
|
|
156
184
|
Add `AnalyticsPageTracker` inside the provider for soft-navigation page views (HubSpot SPA Mode B). Load HubSpot's bootstrap script in the root layout with `createHubSpotBootstrapScript()` (no args — uses `window.location.pathname` at script run time) from `@se-studio/hubspot`. Do not use `headers()` in the root layout for pathname; that forces dynamic rendering and breaks on-demand SSG pages in production.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @se-studio/core-ui
|
|
2
2
|
|
|
3
|
+
## 1.35.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e746884: Add generic non-PII form analytics (`form_view`, `form_start`, `form_submit`, `form_error`) via `useAnalytics`. HubSpot forms do **not** emit these on upgrade; opt in with `formAnalytics={true}` on `HubspotDynamicForm` / `useHubspotSubmit` / `createHubspotFormExternalRenderer`. Import from `@se-studio/core-ui/form-analytics` only (not the root barrel). Payloads are allowlisted and never include field values or raw error text.
|
|
8
|
+
|
|
9
|
+
## 1.34.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 7415484: Add a shared analytics consent helper and make GTM / HubSpot adapters consent-aware.
|
|
14
|
+
|
|
15
|
+
`resolveAnalyticsConsent` / `createConsentGetter` read a site-supplied source plus optional Global Privacy Control and Do Not Track (both off by default). `HubSpotAnalyticsAdapter` skips the tracking script and `_hsq` events while `getConsent` is false, and pushes `doNotTrack` on a mid-session deny. `ConsentAwareAdapter` vendor sync (`updateConsent` / late initialize) is **opt-in** via `{ syncConsent: true }` and forwards **analytics only** (never advertising).
|
|
16
|
+
|
|
17
|
+
**GTM Consent Mode (Array → Arguments):** `GoogleTagManagerAdapter` now issues real `gtag('consent', …)` commands (an Arguments object, flushed before `gtm.js` / gtag.js). GA4-only setups (`measurementId`) also send `gtag('js')` and `config` as Arguments. Consent Mode `default` is pushed **only when `consentDefault` is set**; otherwise the adapter does not grant `ad_*` keys, and denying or undecided visitors never get advertising `granted` from the adapter.
|
|
18
|
+
|
|
19
|
+
Sites using Orejime now have Google tags respect denied consent. Sites without a CMP integration are unchanged. GA4 and Ads volumes may drop where those denies start to apply. Sites that do not load GTM via this adapter are unaffected. HubSpot script gating does not change unless a site passes `getConsent` / `{ syncConsent: true }`.
|
|
20
|
+
|
|
3
21
|
## 1.33.3
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/CONSENT.md
CHANGED
|
@@ -76,6 +76,58 @@ function AnalyticsSetup() {
|
|
|
76
76
|
}
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
+
Call `adapter.initialize()` once on the client only if you need the inner adapters’ `initialize` (GTM script load). Vendor `updateConsent` / HubSpot script gating through the wrapper is **opt-in**: pass `{ syncConsent: true }`. The wrapper never sets GTM `advertising` from the analytics getter.
|
|
80
|
+
|
|
81
|
+
### Consent state helper (no CMP)
|
|
82
|
+
|
|
83
|
+
Sites with a cookie or other source, plus optional Global Privacy Control / Do Not Track, should use `resolveAnalyticsConsent` / `createConsentGetter`. Unconfigured options keep tracking on (`opt-out`).
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import {
|
|
87
|
+
ConsentAwareAdapter,
|
|
88
|
+
CompositeAnalyticsAdapter,
|
|
89
|
+
GoogleTagManagerAdapter,
|
|
90
|
+
createConsentGetter,
|
|
91
|
+
} from '@se-studio/core-ui';
|
|
92
|
+
import { HubSpotAnalyticsAdapter } from '@se-studio/hubspot';
|
|
93
|
+
|
|
94
|
+
const hasAnalyticsConsent = createConsentGetter({
|
|
95
|
+
model: 'opt-out', // US default: track until the visitor opts out
|
|
96
|
+
honorGlobalPrivacyControl: true,
|
|
97
|
+
honorDoNotTrack: true,
|
|
98
|
+
source: () => {
|
|
99
|
+
if (typeof document === 'undefined') return undefined;
|
|
100
|
+
const match = document.cookie.match(/(?:^|; )YOUR_OPT_OUT_COOKIE=([^;]*)/);
|
|
101
|
+
if (!match?.[1]) return undefined;
|
|
102
|
+
return decodeURIComponent(match[1]) !== 'true';
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const gtm = new GoogleTagManagerAdapter({
|
|
107
|
+
containerId: process.env.NEXT_PUBLIC_GTM_ID!,
|
|
108
|
+
consentDefault: 'granted', // omit when a CMP owns Consent Mode defaults
|
|
109
|
+
});
|
|
110
|
+
const hubspot = new HubSpotAnalyticsAdapter({
|
|
111
|
+
portalId: process.env.NEXT_PUBLIC_HUBSPOT_PORTAL_ID!,
|
|
112
|
+
getConsent: hasAnalyticsConsent,
|
|
113
|
+
});
|
|
114
|
+
const adapter = new ConsentAwareAdapter(
|
|
115
|
+
new CompositeAnalyticsAdapter([gtm, hubspot]),
|
|
116
|
+
'analytics',
|
|
117
|
+
() => hasAnalyticsConsent(),
|
|
118
|
+
{ syncConsent: true },
|
|
119
|
+
);
|
|
120
|
+
adapter.initialize();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`honorGlobalPrivacyControl` and `honorDoNotTrack` default to `false`. `model: 'opt-in'` denies tracking until `source` grants it.
|
|
124
|
+
|
|
125
|
+
After a mid-session opt-out, call `adapter.syncConsent()` so HubSpot receives `doNotTrack` and GTM receives `gtag('consent', 'update', …)` for **analytics** only. `track*` does not sync vendors unless `{ syncConsent: true }`.
|
|
126
|
+
|
|
127
|
+
### Separate consent categories
|
|
128
|
+
|
|
129
|
+
`ConsentAwareAdapter` never writes GTM advertising flags. Distinct categories (analytics granted, advertising denied) stay on `GoogleTagManagerAdapter.updateConsent({ analytics, advertising })`.
|
|
130
|
+
|
|
79
131
|
## Styling
|
|
80
132
|
|
|
81
133
|
Orejime styles can be customized via CSS variables or the `theme` prop.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnalyticsAdapter, ClickTrackingProperties, PageTrackingProperties } from '../types';
|
|
1
|
+
import type { AnalyticsAdapter, AnalyticsConsentState, ClickTrackingProperties, PageTrackingProperties } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* Fans out analytics calls to multiple adapters (e.g. GTM + HubSpot).
|
|
4
4
|
*/
|
|
@@ -6,6 +6,7 @@ export declare class CompositeAnalyticsAdapter implements AnalyticsAdapter {
|
|
|
6
6
|
private readonly adapters;
|
|
7
7
|
constructor(adapters: AnalyticsAdapter[]);
|
|
8
8
|
initialize(): void;
|
|
9
|
+
updateConsent(consent: AnalyticsConsentState): void;
|
|
9
10
|
trackEvent(event: string, properties: Record<string, unknown>): void;
|
|
10
11
|
trackPage(url: string, properties: PageTrackingProperties): void;
|
|
11
12
|
trackClick(properties: ClickTrackingProperties): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositeAnalyticsAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/CompositeAnalyticsAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"CompositeAnalyticsAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/CompositeAnalyticsAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAGlB;;GAEG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IACpD,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAArC,YAA6B,QAAQ,EAAE,gBAAgB,EAAE,EAAI;IAE7D,UAAU,IAAI,IAAI,CAIjB;IAED,aAAa,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIlD;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAInE;IAED,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAI/D;IAED,UAAU,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAIpD;CACF"}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
if ('initialize' in adapter && typeof adapter.initialize === 'function') {
|
|
3
|
-
adapter.initialize();
|
|
4
|
-
}
|
|
5
|
-
}
|
|
1
|
+
import { callInitialize, callUpdateConsent } from './adapterMethods';
|
|
6
2
|
/**
|
|
7
3
|
* Fans out analytics calls to multiple adapters (e.g. GTM + HubSpot).
|
|
8
4
|
*/
|
|
@@ -16,6 +12,11 @@ export class CompositeAnalyticsAdapter {
|
|
|
16
12
|
callInitialize(adapter);
|
|
17
13
|
}
|
|
18
14
|
}
|
|
15
|
+
updateConsent(consent) {
|
|
16
|
+
for (const adapter of this.adapters) {
|
|
17
|
+
callUpdateConsent(adapter, consent);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
19
20
|
trackEvent(event, properties) {
|
|
20
21
|
for (const adapter of this.adapters) {
|
|
21
22
|
adapter.trackEvent(event, properties);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositeAnalyticsAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/CompositeAnalyticsAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CompositeAnalyticsAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/CompositeAnalyticsAdapter.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErE;;GAEG;AACH,MAAM,OAAO,yBAAyB;IACP,QAAQ;IAArC,YAA6B,QAA4B;wBAA5B,QAAQ;IAAuB,CAAC;IAE7D,UAAU;QACR,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,cAAc,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,aAAa,CAAC,OAA8B;QAC1C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,UAAU,CAAC,KAAa,EAAE,UAAmC;QAC3D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,UAAkC;QACvD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED,UAAU,CAAC,UAAmC;QAC5C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACpC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,18 +1,45 @@
|
|
|
1
1
|
import type { AnalyticsAdapter, ClickTrackingProperties, PageTrackingProperties } from '../types';
|
|
2
|
+
export interface ConsentAwareAdapterOptions {
|
|
3
|
+
/**
|
|
4
|
+
* When true, `initialize()` / `track*` / `syncConsent()` push
|
|
5
|
+
* `updateConsent({ analytics })` (never `advertising`) and re-init the inner
|
|
6
|
+
* adapter after a later grant. Default `false`: existing sites only gate
|
|
7
|
+
* events, matching the pre-consent-sync wrapper.
|
|
8
|
+
*/
|
|
9
|
+
syncConsent?: boolean;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* Adapter that wraps another analytics adapter and checks for consent before tracking.
|
|
4
13
|
*
|
|
14
|
+
* Vendor sync (`updateConsent` / late `initialize`) is opt-in via
|
|
15
|
+
* `{ syncConsent: true }`. GTM Consent Mode advertising flags are never set
|
|
16
|
+
* from this wrapper — call `GoogleTagManagerAdapter.updateConsent` for ads.
|
|
17
|
+
*
|
|
5
18
|
* @example
|
|
6
19
|
* ```tsx
|
|
7
20
|
* const gtmAdapter = new GoogleTagManagerAdapter(...);
|
|
8
|
-
* const consentAwareAdapter = new ConsentAwareAdapter(
|
|
21
|
+
* const consentAwareAdapter = new ConsentAwareAdapter(
|
|
22
|
+
* gtmAdapter,
|
|
23
|
+
* 'google-tag-manager',
|
|
24
|
+
* hasConsent,
|
|
25
|
+
* );
|
|
9
26
|
* ```
|
|
10
27
|
*/
|
|
11
28
|
export declare class ConsentAwareAdapter implements AnalyticsAdapter {
|
|
12
29
|
private adapter;
|
|
13
30
|
private consentName;
|
|
14
31
|
private getConsent;
|
|
15
|
-
|
|
32
|
+
private lastConsent;
|
|
33
|
+
private readonly syncConsentEnabled;
|
|
34
|
+
constructor(adapter: AnalyticsAdapter, consentName: string, getConsent: (name: string) => boolean, options?: ConsentAwareAdapterOptions);
|
|
35
|
+
private allowed;
|
|
36
|
+
/**
|
|
37
|
+
* Re-read the site consent source and, when `{ syncConsent: true }`, push
|
|
38
|
+
* `updateConsent({ analytics })` when it changes.
|
|
39
|
+
* Safe to call from a cookie-change listener. Returns whether tracking is allowed.
|
|
40
|
+
*/
|
|
41
|
+
syncConsent(): boolean;
|
|
42
|
+
initialize(): void;
|
|
16
43
|
trackEvent(event: string, properties: Record<string, unknown>): void;
|
|
17
44
|
trackPage(url: string, properties: PageTrackingProperties): void;
|
|
18
45
|
trackClick(properties: ClickTrackingProperties): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsentAwareAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/ConsentAwareAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"ConsentAwareAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/ConsentAwareAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAGlG,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IAKxD,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,UAAU;IANpB,OAAO,CAAC,WAAW,CAAsB;IACzC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAE7C,YACU,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EAC7C,OAAO,CAAC,EAAE,0BAA0B,EAGrC;IAED,OAAO,CAAC,OAAO;IAIf;;;;OAIG;IACH,WAAW,IAAI,OAAO,CAerB;IAED,UAAU,IAAI,IAAI,CAOjB;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAInE;IAED,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAI/D;IAED,UAAU,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAIpD;CACF"}
|
|
@@ -1,33 +1,77 @@
|
|
|
1
|
+
import { callInitialize, callUpdateConsent } from './adapterMethods';
|
|
1
2
|
/**
|
|
2
3
|
* Adapter that wraps another analytics adapter and checks for consent before tracking.
|
|
3
4
|
*
|
|
5
|
+
* Vendor sync (`updateConsent` / late `initialize`) is opt-in via
|
|
6
|
+
* `{ syncConsent: true }`. GTM Consent Mode advertising flags are never set
|
|
7
|
+
* from this wrapper — call `GoogleTagManagerAdapter.updateConsent` for ads.
|
|
8
|
+
*
|
|
4
9
|
* @example
|
|
5
10
|
* ```tsx
|
|
6
11
|
* const gtmAdapter = new GoogleTagManagerAdapter(...);
|
|
7
|
-
* const consentAwareAdapter = new ConsentAwareAdapter(
|
|
12
|
+
* const consentAwareAdapter = new ConsentAwareAdapter(
|
|
13
|
+
* gtmAdapter,
|
|
14
|
+
* 'google-tag-manager',
|
|
15
|
+
* hasConsent,
|
|
16
|
+
* );
|
|
8
17
|
* ```
|
|
9
18
|
*/
|
|
10
19
|
export class ConsentAwareAdapter {
|
|
11
20
|
adapter;
|
|
12
21
|
consentName;
|
|
13
22
|
getConsent;
|
|
14
|
-
|
|
23
|
+
lastConsent;
|
|
24
|
+
syncConsentEnabled;
|
|
25
|
+
constructor(adapter, consentName, getConsent, options) {
|
|
15
26
|
this.adapter = adapter;
|
|
16
27
|
this.consentName = consentName;
|
|
17
28
|
this.getConsent = getConsent;
|
|
29
|
+
this.syncConsentEnabled = options?.syncConsent === true;
|
|
30
|
+
}
|
|
31
|
+
allowed() {
|
|
32
|
+
return this.getConsent(this.consentName);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Re-read the site consent source and, when `{ syncConsent: true }`, push
|
|
36
|
+
* `updateConsent({ analytics })` when it changes.
|
|
37
|
+
* Safe to call from a cookie-change listener. Returns whether tracking is allowed.
|
|
38
|
+
*/
|
|
39
|
+
syncConsent() {
|
|
40
|
+
const allowed = this.allowed();
|
|
41
|
+
if (!this.syncConsentEnabled) {
|
|
42
|
+
return allowed;
|
|
43
|
+
}
|
|
44
|
+
if (this.lastConsent === allowed) {
|
|
45
|
+
return allowed;
|
|
46
|
+
}
|
|
47
|
+
const previous = this.lastConsent;
|
|
48
|
+
this.lastConsent = allowed;
|
|
49
|
+
callUpdateConsent(this.adapter, { analytics: allowed });
|
|
50
|
+
if (allowed && previous === false) {
|
|
51
|
+
callInitialize(this.adapter);
|
|
52
|
+
}
|
|
53
|
+
return allowed;
|
|
54
|
+
}
|
|
55
|
+
initialize() {
|
|
56
|
+
if (this.syncConsentEnabled) {
|
|
57
|
+
const allowed = this.allowed();
|
|
58
|
+
callUpdateConsent(this.adapter, { analytics: allowed });
|
|
59
|
+
this.lastConsent = allowed;
|
|
60
|
+
}
|
|
61
|
+
callInitialize(this.adapter);
|
|
18
62
|
}
|
|
19
63
|
trackEvent(event, properties) {
|
|
20
|
-
if (this.
|
|
64
|
+
if (this.syncConsentEnabled ? this.syncConsent() : this.allowed()) {
|
|
21
65
|
this.adapter.trackEvent(event, properties);
|
|
22
66
|
}
|
|
23
67
|
}
|
|
24
68
|
trackPage(url, properties) {
|
|
25
|
-
if (this.
|
|
69
|
+
if (this.syncConsentEnabled ? this.syncConsent() : this.allowed()) {
|
|
26
70
|
this.adapter.trackPage(url, properties);
|
|
27
71
|
}
|
|
28
72
|
}
|
|
29
73
|
trackClick(properties) {
|
|
30
|
-
if (this.
|
|
74
|
+
if (this.syncConsentEnabled ? this.syncConsent() : this.allowed()) {
|
|
31
75
|
this.adapter.trackClick(properties);
|
|
32
76
|
}
|
|
33
77
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConsentAwareAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/ConsentAwareAdapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConsentAwareAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/ConsentAwareAdapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAYrE;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,mBAAmB;IAKpB,OAAO;IACP,WAAW;IACX,UAAU;IANZ,WAAW,CAAsB;IACxB,kBAAkB,CAAU;IAE7C,YACU,OAAyB,EACzB,WAAmB,EACnB,UAAqC,EAC7C,OAAoC;uBAH5B,OAAO;2BACP,WAAW;0BACX,UAAU;QAGlB,IAAI,CAAC,kBAAkB,GAAG,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1D,CAAC;IAEO,OAAO;QACb,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,KAAK,OAAO,EAAE,CAAC;YACjC,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC;QAClC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC3B,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;QACxD,IAAI,OAAO,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YAClC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YACxD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;QAC7B,CAAC;QACD,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED,UAAU,CAAC,KAAa,EAAE,UAAmC;QAC3D,IAAI,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAClE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,UAAkC;QACvD,IAAI,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAClE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,UAAU,CAAC,UAAmC;QAC5C,IAAI,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAClE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
|
-
import type { AnalyticsAdapter, ClickTrackingProperties, PageTrackingProperties } from '../types';
|
|
1
|
+
import type { AnalyticsAdapter, AnalyticsConsentState, ClickTrackingProperties, PageTrackingProperties } from '../types';
|
|
2
2
|
declare global {
|
|
3
3
|
interface Window {
|
|
4
4
|
dataLayer: unknown[];
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
* Consent state for GTM Consent Mode v2
|
|
9
|
-
*/
|
|
10
|
-
interface ConsentState {
|
|
11
|
-
/** Controls analytics_storage */
|
|
12
|
-
analytics?: boolean;
|
|
13
|
-
/** Controls ad_storage, ad_user_data, ad_personalization */
|
|
14
|
-
advertising?: boolean;
|
|
15
|
-
}
|
|
7
|
+
type ConsentModeValue = 'granted' | 'denied';
|
|
16
8
|
interface GoogleTagManagerConfig {
|
|
17
9
|
/**
|
|
18
10
|
* GTM Container ID (e.g., GTM-XXXXXXX)
|
|
@@ -24,27 +16,36 @@ interface GoogleTagManagerConfig {
|
|
|
24
16
|
* or can be used alongside GTM if needed.
|
|
25
17
|
*/
|
|
26
18
|
measurementId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Consent Mode v2 default, pushed via `gtag('consent', 'default', …)` before
|
|
21
|
+
* GTM/gtag.js loads. Omit this so the CMP (or Google’s own default) decides
|
|
22
|
+
* (R7.7). Set `'granted'` only for US opt-out sites without a CMP, or
|
|
23
|
+
* `'denied'` for GDPR opt-in sites that want the adapter to set the default.
|
|
24
|
+
*/
|
|
25
|
+
consentDefault?: ConsentModeValue;
|
|
27
26
|
}
|
|
28
27
|
export declare class GoogleTagManagerAdapter implements AnalyticsAdapter {
|
|
29
28
|
private initialized;
|
|
30
29
|
private config;
|
|
30
|
+
private pendingConsent;
|
|
31
31
|
constructor(config: GoogleTagManagerConfig);
|
|
32
32
|
/**
|
|
33
|
-
* Push
|
|
34
|
-
|
|
35
|
-
private gtag;
|
|
36
|
-
/**
|
|
37
|
-
* Initialize consent mode with all consent types granted by default
|
|
33
|
+
* Push Consent Mode `default` only when the site set `consentDefault`.
|
|
34
|
+
* Unset means the CMP decides — do not grant advertising keys implicitly.
|
|
38
35
|
*/
|
|
39
36
|
private initializeConsentMode;
|
|
37
|
+
private pushConsentUpdate;
|
|
38
|
+
/** Apply a stashed update immediately after `default`, before GTM/gtag.js. */
|
|
39
|
+
private flushPendingConsent;
|
|
40
40
|
initialize(): void;
|
|
41
41
|
/**
|
|
42
42
|
* Update consent state for specific purposes.
|
|
43
43
|
* Call this when user grants or revokes consent.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
45
|
+
* If `initialize()` has not run yet, the update is stashed and flushed
|
|
46
|
+
* after any Consent Mode `default`, before GTM / gtag.js load.
|
|
46
47
|
*/
|
|
47
|
-
updateConsent(consent:
|
|
48
|
+
updateConsent(consent: AnalyticsConsentState): void;
|
|
48
49
|
trackEvent(event: string, properties: Record<string, unknown>): void;
|
|
49
50
|
trackPage(url: string, properties: PageTrackingProperties): void;
|
|
50
51
|
trackClick(properties: ClickTrackingProperties): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleTagManagerAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/GoogleTagManagerAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"GoogleTagManagerAdapter.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/GoogleTagManagerAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,SAAS,EAAE,OAAO,EAAE,CAAC;KACtB;CACF;AAED,KAAK,gBAAgB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7C,UAAU,sBAAsB;IAC9B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC;AAmBD,qBAAa,uBAAwB,YAAW,gBAAgB;IAC9D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,cAAc,CAAoC;IAE1D,YAAY,MAAM,EAAE,sBAAsB,EAEzC;IAED;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAa7B,OAAO,CAAC,iBAAiB;IAgBzB,8EAA8E;IAC9E,OAAO,CAAC,mBAAmB;IAQ3B,UAAU,SAsDT;IAED;;;;;;OAMG;IACH,aAAa,CAAC,OAAO,EAAE,qBAAqB,QAc3C;IAED,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWnE;IAED,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,sBAAsB,GAAG,IAAI,CAe/D;IAED,UAAU,CAAC,UAAU,EAAE,uBAAuB,GAAG,IAAI,CAYpD;CACF"}
|
|
@@ -1,30 +1,64 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
function toConsentMode(allowed) {
|
|
3
|
+
return allowed ? 'granted' : 'denied';
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Push a gtag command. Consent Mode only honours the `arguments` object
|
|
7
|
+
* (`function gtag(){ dataLayer.push(arguments); }`), not a real Array.
|
|
8
|
+
*/
|
|
9
|
+
const gtag = function gtag() {
|
|
10
|
+
if (typeof window === 'undefined') {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
window.dataLayer = window.dataLayer || [];
|
|
14
|
+
// biome-ignore lint/complexity/noArguments: Google Consent Mode requires Arguments
|
|
15
|
+
window.dataLayer.push(arguments);
|
|
16
|
+
};
|
|
2
17
|
export class GoogleTagManagerAdapter {
|
|
3
18
|
initialized = false;
|
|
4
19
|
config;
|
|
20
|
+
pendingConsent;
|
|
5
21
|
constructor(config) {
|
|
6
22
|
this.config = config;
|
|
7
23
|
}
|
|
8
24
|
/**
|
|
9
|
-
* Push
|
|
10
|
-
|
|
11
|
-
gtag(...args) {
|
|
12
|
-
if (typeof window === 'undefined')
|
|
13
|
-
return;
|
|
14
|
-
window.dataLayer = window.dataLayer || [];
|
|
15
|
-
window.dataLayer.push(args);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Initialize consent mode with all consent types granted by default
|
|
25
|
+
* Push Consent Mode `default` only when the site set `consentDefault`.
|
|
26
|
+
* Unset means the CMP decides — do not grant advertising keys implicitly.
|
|
19
27
|
*/
|
|
20
28
|
initializeConsentMode() {
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
const value = this.config.consentDefault;
|
|
30
|
+
if (value === undefined) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
gtag('consent', 'default', {
|
|
34
|
+
analytics_storage: value,
|
|
35
|
+
ad_storage: value,
|
|
36
|
+
ad_user_data: value,
|
|
37
|
+
ad_personalization: value,
|
|
26
38
|
});
|
|
27
39
|
}
|
|
40
|
+
pushConsentUpdate(consent) {
|
|
41
|
+
if (consent.analytics !== undefined) {
|
|
42
|
+
gtag('consent', 'update', {
|
|
43
|
+
analytics_storage: toConsentMode(consent.analytics),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (consent.advertising !== undefined) {
|
|
47
|
+
gtag('consent', 'update', {
|
|
48
|
+
ad_storage: toConsentMode(consent.advertising),
|
|
49
|
+
ad_user_data: toConsentMode(consent.advertising),
|
|
50
|
+
ad_personalization: toConsentMode(consent.advertising),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Apply a stashed update immediately after `default`, before GTM/gtag.js. */
|
|
55
|
+
flushPendingConsent() {
|
|
56
|
+
if (!this.pendingConsent) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.pushConsentUpdate(this.pendingConsent);
|
|
60
|
+
this.pendingConsent = undefined;
|
|
61
|
+
}
|
|
28
62
|
initialize() {
|
|
29
63
|
if (typeof window === 'undefined' || this.initialized)
|
|
30
64
|
return;
|
|
@@ -33,19 +67,18 @@ export class GoogleTagManagerAdapter {
|
|
|
33
67
|
console.warn('[GTM Adapter] No container ID or measurement ID provided');
|
|
34
68
|
return;
|
|
35
69
|
}
|
|
36
|
-
// Initialize dataLayer
|
|
37
70
|
window.dataLayer = window.dataLayer || [];
|
|
38
|
-
//
|
|
71
|
+
// Optional Consent Mode `default` (only if `consentDefault` is set), then
|
|
72
|
+
// any stashed `update`, THEN GTM / gtag.js.
|
|
39
73
|
this.initializeConsentMode();
|
|
74
|
+
this.flushPendingConsent();
|
|
40
75
|
if (containerId) {
|
|
41
|
-
// Check if GTM script already exists
|
|
42
76
|
const existingScript = document.querySelector(`script[src*="googletagmanager.com/gtm.js?id=${containerId}"]`);
|
|
43
77
|
if (existingScript) {
|
|
44
78
|
console.warn(`[GTM Adapter] GTM script with ID ${containerId} already exists in document.`);
|
|
45
79
|
this.initialized = true;
|
|
46
80
|
return;
|
|
47
81
|
}
|
|
48
|
-
// Load GTM
|
|
49
82
|
window.dataLayer.push({ 'gtm.start': Date.now(), event: 'gtm.js' });
|
|
50
83
|
const script = document.createElement('script');
|
|
51
84
|
script.async = true;
|
|
@@ -53,20 +86,18 @@ export class GoogleTagManagerAdapter {
|
|
|
53
86
|
document.head.appendChild(script);
|
|
54
87
|
}
|
|
55
88
|
else if (measurementId) {
|
|
56
|
-
// Check if GA4 script already exists
|
|
57
89
|
const existingScript = document.querySelector(`script[src*="googletagmanager.com/gtag/js?id=${measurementId}"]`);
|
|
58
90
|
if (existingScript) {
|
|
59
91
|
console.warn(`[GTM Adapter] GA4 script with ID ${measurementId} already exists in document.`);
|
|
60
92
|
this.initialized = true;
|
|
61
93
|
return;
|
|
62
94
|
}
|
|
63
|
-
// Load GA4 directly (gtag.js)
|
|
64
95
|
const script = document.createElement('script');
|
|
65
96
|
script.async = true;
|
|
66
97
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
|
|
67
98
|
document.head.appendChild(script);
|
|
68
|
-
|
|
69
|
-
|
|
99
|
+
gtag('js', new Date());
|
|
100
|
+
gtag('config', measurementId);
|
|
70
101
|
}
|
|
71
102
|
this.initialized = true;
|
|
72
103
|
}
|
|
@@ -74,23 +105,21 @@ export class GoogleTagManagerAdapter {
|
|
|
74
105
|
* Update consent state for specific purposes.
|
|
75
106
|
* Call this when user grants or revokes consent.
|
|
76
107
|
*
|
|
77
|
-
*
|
|
108
|
+
* If `initialize()` has not run yet, the update is stashed and flushed
|
|
109
|
+
* after any Consent Mode `default`, before GTM / gtag.js load.
|
|
78
110
|
*/
|
|
79
111
|
updateConsent(consent) {
|
|
80
112
|
if (typeof window === 'undefined')
|
|
81
113
|
return;
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.gtag('consent', 'update', {
|
|
89
|
-
ad_storage: consent.advertising ? 'granted' : 'denied',
|
|
90
|
-
ad_user_data: consent.advertising ? 'granted' : 'denied',
|
|
91
|
-
ad_personalization: consent.advertising ? 'granted' : 'denied',
|
|
92
|
-
});
|
|
114
|
+
this.pendingConsent = {
|
|
115
|
+
...this.pendingConsent,
|
|
116
|
+
...consent,
|
|
117
|
+
};
|
|
118
|
+
if (!this.initialized) {
|
|
119
|
+
return;
|
|
93
120
|
}
|
|
121
|
+
this.pushConsentUpdate(consent);
|
|
122
|
+
this.pendingConsent = undefined;
|
|
94
123
|
}
|
|
95
124
|
trackEvent(event, properties) {
|
|
96
125
|
if (typeof window === 'undefined')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleTagManagerAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/GoogleTagManagerAdapter.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"GoogleTagManagerAdapter.js","sourceRoot":"","sources":["../../../src/analytics/adapters/GoogleTagManagerAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnalyticsAdapter, AnalyticsConsentState } from '../types';
|
|
2
|
+
export declare function callInitialize(adapter: AnalyticsAdapter): void;
|
|
3
|
+
export declare function callUpdateConsent(adapter: AnalyticsAdapter, consent: AnalyticsConsentState): void;
|
|
4
|
+
//# sourceMappingURL=adapterMethods.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapterMethods.d.ts","sourceRoot":"","sources":["../../../src/analytics/adapters/adapterMethods.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAExE,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAI9D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,qBAAqB,GAAG,IAAI,CAIjG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function callInitialize(adapter) {
|
|
2
|
+
if ('initialize' in adapter && typeof adapter.initialize === 'function') {
|
|
3
|
+
adapter.initialize();
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
export function callUpdateConsent(adapter, consent) {
|
|
7
|
+
if ('updateConsent' in adapter && typeof adapter.updateConsent === 'function') {
|
|
8
|
+
adapter.updateConsent(consent);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=adapterMethods.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapterMethods.js","sourceRoot":"","sources":["../../../src/analytics/adapters/adapterMethods.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAAC,OAAyB;IACtD,IAAI,YAAY,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QACxE,OAAO,CAAC,UAAU,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAyB,EAAE,OAA8B;IACzF,IAAI,eAAe,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;QAC9E,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;AACH,CAAC"}
|