@phygitallabs/tapquest-core 6.7.9 → 6.7.11
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/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +35 -3
- package/dist/index.d.ts +35 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/modules/data-tracking/GtagConsentSync.tsx +62 -15
- package/src/modules/data-tracking/GtagGtmScriptToggle.tsx +73 -0
- package/src/modules/data-tracking/hooks/index.ts +133 -67
- package/src/modules/data-tracking/index.ts +2 -1
- package/src/modules/session-replay/providers/SessionReplayProvider.tsx +6 -6
- package/src/providers/ServicesProvider.tsx +132 -132
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { usePhygitalConsent } from "@phygitallabs/phygital-consent";
|
|
4
|
+
import posthog from "posthog-js";
|
|
4
5
|
import { useEffect } from "react";
|
|
6
|
+
import { useSessionReplay } from "../session-replay";
|
|
5
7
|
|
|
6
8
|
type GtagConsent = (
|
|
7
9
|
command: "consent",
|
|
@@ -9,46 +11,91 @@ type GtagConsent = (
|
|
|
9
11
|
params: Record<string, string>
|
|
10
12
|
) => void;
|
|
11
13
|
|
|
14
|
+
/** Consent Mode v2: block analytics and ad tags until user accepts. */
|
|
12
15
|
const GTAG_CONSENT_DENIED = {
|
|
13
16
|
analytics_storage: "denied",
|
|
14
17
|
ad_storage: "denied",
|
|
18
|
+
ad_user_data: "denied",
|
|
19
|
+
ad_personalization: "denied",
|
|
15
20
|
} as const;
|
|
16
21
|
|
|
17
22
|
const GTAG_CONSENT_GRANTED_ANALYTICS = {
|
|
18
23
|
analytics_storage: "granted",
|
|
19
24
|
} as const;
|
|
20
25
|
|
|
26
|
+
const GTAG_CONSENT_GRANTED_ADS = {
|
|
27
|
+
ad_storage: "granted",
|
|
28
|
+
ad_user_data: "granted",
|
|
29
|
+
ad_personalization: "granted",
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
function pushConsentUpdate(params: Record<string, string>) {
|
|
33
|
+
if (typeof window === "undefined") return;
|
|
34
|
+
const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
|
|
35
|
+
if (typeof w.gtag === "function") {
|
|
36
|
+
w.gtag("consent", "update", params);
|
|
37
|
+
} else {
|
|
38
|
+
w.dataLayer = w.dataLayer || [];
|
|
39
|
+
(w.dataLayer as unknown[]).push(["consent", "update", params]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
44
|
+
* Syncs cookie consent to GA, GTM, PostHog, OpenReplay, and Google AdSense.
|
|
45
|
+
* - On mount: sets gtag/GTM consent default to denied.
|
|
46
|
+
* - When analytics allowed: grants analytics_storage (GA/GTM), opts in PostHog, starts OpenReplay.
|
|
47
|
+
* - When advertising allowed: grants ad_storage, ad_user_data, ad_personalization (AdSense).
|
|
48
|
+
* Must be used under PhygitalConsentProvider; for OpenReplay also under SessionReplayProvider.
|
|
24
49
|
* Renders nothing.
|
|
25
50
|
*/
|
|
26
51
|
export function GtagConsentSync() {
|
|
27
|
-
const consent = usePhygitalConsent()
|
|
28
|
-
|
|
52
|
+
const consent = usePhygitalConsent() as {
|
|
53
|
+
isAnalyticsAllowed?: boolean;
|
|
54
|
+
isAdvertisingAllowed?: boolean;
|
|
55
|
+
};
|
|
56
|
+
const isAnalyticsAllowed = consent.isAnalyticsAllowed ?? false;
|
|
57
|
+
const isAdvertisingAllowed = consent.isAdvertisingAllowed ?? false;
|
|
58
|
+
const { initTracker, startTracking } = useSessionReplay();
|
|
29
59
|
|
|
60
|
+
// Default: deny all consent (run once on mount)
|
|
30
61
|
useEffect(() => {
|
|
31
62
|
if (typeof window === "undefined") return;
|
|
32
63
|
const w = window as Window & { dataLayer?: unknown[]; gtag?: GtagConsent };
|
|
33
64
|
w.dataLayer = w.dataLayer || [];
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
gtag("consent", "default", { ...GTAG_CONSENT_DENIED });
|
|
65
|
+
if (typeof w.gtag === "function") {
|
|
66
|
+
w.gtag("consent", "default", { ...GTAG_CONSENT_DENIED });
|
|
37
67
|
} else {
|
|
38
|
-
w.dataLayer.push(["consent", "default", { ...GTAG_CONSENT_DENIED }]);
|
|
68
|
+
(w.dataLayer as unknown[]).push(["consent", "default", { ...GTAG_CONSENT_DENIED }]);
|
|
39
69
|
}
|
|
40
70
|
}, []);
|
|
41
71
|
|
|
72
|
+
// GA / GTM: grant analytics when user accepted analytics
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!isAnalyticsAllowed) return;
|
|
75
|
+
pushConsentUpdate({ ...GTAG_CONSENT_GRANTED_ANALYTICS });
|
|
76
|
+
}, [isAnalyticsAllowed]);
|
|
77
|
+
|
|
78
|
+
// Google AdSense: grant ad consent when user accepted advertising
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (!isAdvertisingAllowed) return;
|
|
81
|
+
pushConsentUpdate({ ...GTAG_CONSENT_GRANTED_ADS });
|
|
82
|
+
}, [isAdvertisingAllowed]);
|
|
83
|
+
|
|
84
|
+
// PostHog: opt in when analytics allowed
|
|
42
85
|
useEffect(() => {
|
|
43
|
-
if (!isAnalyticsAllowed
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
gtag("consent", "update", { ...GTAG_CONSENT_GRANTED_ANALYTICS });
|
|
48
|
-
} else if (Array.isArray(w.dataLayer)) {
|
|
49
|
-
w.dataLayer.push(["consent", "update", { ...GTAG_CONSENT_GRANTED_ANALYTICS }]);
|
|
86
|
+
if (!isAnalyticsAllowed) return;
|
|
87
|
+
const ph = posthog as { opt_in_capturing?: () => void };
|
|
88
|
+
if (typeof ph.opt_in_capturing === "function") {
|
|
89
|
+
ph.opt_in_capturing();
|
|
50
90
|
}
|
|
51
91
|
}, [isAnalyticsAllowed]);
|
|
52
92
|
|
|
93
|
+
// OpenReplay: init and start when analytics allowed
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!isAnalyticsAllowed) return;
|
|
96
|
+
initTracker();
|
|
97
|
+
startTracking();
|
|
98
|
+
}, [isAnalyticsAllowed, initTracker, startTracking]);
|
|
99
|
+
|
|
53
100
|
return null;
|
|
54
101
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePhygitalConsent } from "@phygitallabs/phygital-consent";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
/** Selectors for gtag / GTM scripts in the DOM */
|
|
7
|
+
const SCRIPT_SELECTORS =
|
|
8
|
+
'script[src*="googletagmanager.com"], script[src*="google-analytics.com"], script[src*="gtag/js"]';
|
|
9
|
+
|
|
10
|
+
const GTM_SCRIPT_SELECTOR = 'script[src*="googletagmanager.com/gtm.js"]';
|
|
11
|
+
const GTAG_SCRIPT_SELECTOR =
|
|
12
|
+
'script[src*="gtag/js"], script[src*="googletagmanager.com/gtag"]';
|
|
13
|
+
|
|
14
|
+
/** Detect if gtag/GTM scripts are present in the DOM */
|
|
15
|
+
export function detectGtagGtmInDom(): {
|
|
16
|
+
gtag: boolean;
|
|
17
|
+
gtm: boolean;
|
|
18
|
+
any: boolean;
|
|
19
|
+
gtagScripts: HTMLScriptElement[];
|
|
20
|
+
gtmScripts: HTMLScriptElement[];
|
|
21
|
+
} {
|
|
22
|
+
if (typeof document === "undefined") {
|
|
23
|
+
return { gtag: false, gtm: false, any: false, gtagScripts: [], gtmScripts: [] };
|
|
24
|
+
}
|
|
25
|
+
const gtagScripts = Array.from(document.querySelectorAll<HTMLScriptElement>(GTAG_SCRIPT_SELECTOR));
|
|
26
|
+
const gtmScripts = Array.from(document.querySelectorAll<HTMLScriptElement>(GTM_SCRIPT_SELECTOR));
|
|
27
|
+
return {
|
|
28
|
+
gtag: gtagScripts.length > 0,
|
|
29
|
+
gtm: gtmScripts.length > 0,
|
|
30
|
+
any: gtagScripts.length > 0 || gtmScripts.length > 0,
|
|
31
|
+
gtagScripts,
|
|
32
|
+
gtmScripts,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GtagGtmScriptToggleProps {
|
|
37
|
+
/** When true, scripts stay. When false, detected GA/GTM scripts are removed from DOM. */
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detects GA/GTM scripts in the DOM and removes them when enabled is false.
|
|
43
|
+
* When enabled is true, does nothing (scripts remain if already in the page).
|
|
44
|
+
* No config: only detect and toggle (remove) based on condition.
|
|
45
|
+
* Renders nothing.
|
|
46
|
+
*/
|
|
47
|
+
export function GtagGtmScriptToggle({ enabled = false }: GtagGtmScriptToggleProps) {
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (typeof document === "undefined") return;
|
|
50
|
+
|
|
51
|
+
if (!enabled) {
|
|
52
|
+
const toRemove = document.querySelectorAll<HTMLScriptElement>(SCRIPT_SELECTORS);
|
|
53
|
+
toRemove.forEach((el) => {
|
|
54
|
+
el.parentNode?.removeChild(el);
|
|
55
|
+
});
|
|
56
|
+
const w = window as Window & { gtag?: unknown; dataLayer?: unknown[] };
|
|
57
|
+
if (w.dataLayer && Array.isArray(w.dataLayer)) {
|
|
58
|
+
w.dataLayer.length = 0;
|
|
59
|
+
}
|
|
60
|
+
w.gtag = undefined;
|
|
61
|
+
}
|
|
62
|
+
}, [enabled]);
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Reads consent from PhygitalConsentProvider; removes GA/GTM scripts when analytics not allowed. */
|
|
68
|
+
export function GtagGtmScriptToggleFromConsent() {
|
|
69
|
+
const consent = usePhygitalConsent() as { isAnalyticsAllowed?: boolean };
|
|
70
|
+
const enabled = consent.isAnalyticsAllowed ?? false;
|
|
71
|
+
|
|
72
|
+
return <GtagGtmScriptToggle enabled={enabled} />;
|
|
73
|
+
}
|
|
@@ -3,93 +3,159 @@ import posthog from "posthog-js";
|
|
|
3
3
|
import { useSessionReplay } from "../../../modules/session-replay";
|
|
4
4
|
|
|
5
5
|
declare global {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
interface Window {
|
|
7
|
+
dataLayer: unknown[];
|
|
8
|
+
gtag?: GtagConsent;
|
|
9
|
+
}
|
|
9
10
|
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
12
|
+
type GtagConsent = (
|
|
13
|
+
command: "consent",
|
|
14
|
+
action: "default" | "update",
|
|
15
|
+
params: Record<string, string>
|
|
16
|
+
) => void;
|
|
16
17
|
|
|
18
|
+
const GTAG_ANALYTICS_GRANTED = { analytics_storage: "granted" } as const;
|
|
19
|
+
const GTAG_ADS_GRANTED = {
|
|
20
|
+
ad_storage: "granted",
|
|
21
|
+
ad_user_data: "granted",
|
|
22
|
+
ad_personalization: "granted",
|
|
23
|
+
} as const;
|
|
17
24
|
|
|
18
25
|
// GTM
|
|
19
26
|
const pushEventToDataLayer = (event: string, data: Record<string, any>) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error(error);
|
|
30
|
-
}
|
|
27
|
+
try {
|
|
28
|
+
if (typeof window === "undefined") return;
|
|
29
|
+
window.dataLayer = window.dataLayer || [];
|
|
30
|
+
(window.dataLayer as Record<string, unknown>[]).push({ event, ...data });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(error);
|
|
33
|
+
}
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
// GA
|
|
36
|
+
// GA event (gtag may be loaded by GA script)
|
|
34
37
|
const pushEventToGA = (eventName: string, eventData: Record<string, any>) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
if (typeof window === "undefined") return;
|
|
39
|
+
const w = window as Window & { gtag?: (a: string, b: string, c?: Record<string, any>) => void };
|
|
40
|
+
if (typeof w.gtag === "function") {
|
|
41
|
+
w.gtag("event", eventName, eventData);
|
|
42
|
+
}
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
// Posthog
|
|
43
46
|
const pushEventToPosthog = (eventName: string, eventData: Record<string, any>) => {
|
|
44
|
-
|
|
47
|
+
posthog.capture(eventName, eventData);
|
|
45
48
|
};
|
|
46
49
|
|
|
50
|
+
/** Consent flags from PhygitalConsentProvider (isAdvertisingAllowed may be added in newer versions). */
|
|
51
|
+
interface ConsentFlags {
|
|
52
|
+
isAnalyticsAllowed: boolean;
|
|
53
|
+
isAdvertisingAllowed?: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
function useDataTracking() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
uid: userInfo.uid,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
setUserId(userInfo.id);
|
|
77
|
-
|
|
78
|
-
setMetadata({
|
|
79
|
-
user_email: userInfo.email
|
|
80
|
-
})
|
|
57
|
+
const { initTracker, startTracking, setUserId, setMetadata } = useSessionReplay();
|
|
58
|
+
const consent = usePhygitalConsent() as unknown as ConsentFlags;
|
|
59
|
+
const isAnalyticsAllowed = consent.isAnalyticsAllowed ?? false;
|
|
60
|
+
const isAdvertisingAllowed = consent.isAdvertisingAllowed ?? false;
|
|
61
|
+
|
|
62
|
+
/** Enable GA (gtag analytics_storage). No-op if analytics consent is denied. */
|
|
63
|
+
const toggleGA = () => {
|
|
64
|
+
if (!isAnalyticsAllowed || typeof window === "undefined") return;
|
|
65
|
+
const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
|
|
66
|
+
if (typeof w.gtag === "function") {
|
|
67
|
+
w.gtag("consent", "update", { ...GTAG_ANALYTICS_GRANTED });
|
|
68
|
+
} else if (Array.isArray(w.dataLayer)) {
|
|
69
|
+
w.dataLayer.push(["consent", "update", { ...GTAG_ANALYTICS_GRANTED }]);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Enable GTM consent for analytics. No-op if analytics consent is denied. */
|
|
74
|
+
const toggleGTM = () => {
|
|
75
|
+
if (!isAnalyticsAllowed || typeof window === "undefined") return;
|
|
76
|
+
const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
|
|
77
|
+
if (typeof w.gtag === "function") {
|
|
78
|
+
w.gtag("consent", "update", { ...GTAG_ANALYTICS_GRANTED });
|
|
79
|
+
} else {
|
|
80
|
+
w.dataLayer = w.dataLayer || [];
|
|
81
|
+
(w.dataLayer as unknown[]).push(["consent", "update", { ...GTAG_ANALYTICS_GRANTED }]);
|
|
81
82
|
}
|
|
83
|
+
};
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
/** Opt-in PostHog capturing. No-op if analytics consent is denied. */
|
|
86
|
+
const togglePostHog = () => {
|
|
87
|
+
if (!isAnalyticsAllowed) return;
|
|
88
|
+
if (typeof (posthog as { opt_in_capturing?: () => void }).opt_in_capturing === "function") {
|
|
89
|
+
(posthog as { opt_in_capturing: () => void }).opt_in_capturing();
|
|
85
90
|
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Init and start OpenReplay. No-op if analytics consent is denied. */
|
|
94
|
+
const toggleOpenReplay = () => {
|
|
95
|
+
if (!isAnalyticsAllowed) return;
|
|
96
|
+
initTracker();
|
|
97
|
+
startTracking();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** Enable Google AdSense (gtag ad consent). No-op if advertising consent is denied. */
|
|
101
|
+
const toggleGoogleAdSense = () => {
|
|
102
|
+
if (!isAdvertisingAllowed || typeof window === "undefined") return;
|
|
103
|
+
const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
|
|
104
|
+
if (typeof w.gtag === "function") {
|
|
105
|
+
w.gtag("consent", "update", { ...GTAG_ADS_GRANTED });
|
|
106
|
+
} else if (Array.isArray(w.dataLayer)) {
|
|
107
|
+
w.dataLayer.push(["consent", "update", { ...GTAG_ADS_GRANTED }]);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
const trackEvent = (
|
|
112
|
+
eventName: string,
|
|
113
|
+
eventData: Record<string, any>,
|
|
114
|
+
useTools?: ("gtm" | "ga" | "posthog")[]
|
|
115
|
+
) => {
|
|
116
|
+
useTools = useTools || ["gtm"];
|
|
117
|
+
if (useTools.includes("gtm") && typeof window !== "undefined") {
|
|
118
|
+
pushEventToDataLayer(eventName, eventData);
|
|
119
|
+
}
|
|
120
|
+
if (useTools.includes("ga") && isAnalyticsAllowed) {
|
|
121
|
+
pushEventToGA(eventName, eventData);
|
|
122
|
+
}
|
|
123
|
+
if (useTools.includes("posthog") && isAnalyticsAllowed && typeof posthog?.capture === "function") {
|
|
124
|
+
pushEventToPosthog(eventName, eventData);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const trackUserIdentify = (userInfo: Record<string, any>) => {
|
|
129
|
+
if (!isAnalyticsAllowed) return;
|
|
130
|
+
if (typeof posthog?.identify === "function") {
|
|
131
|
+
posthog.identify(userInfo.email, {
|
|
132
|
+
email: userInfo.email,
|
|
133
|
+
name: userInfo.name,
|
|
134
|
+
avatar: userInfo.avatar,
|
|
135
|
+
uid: userInfo.uid,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
setUserId(userInfo.id);
|
|
139
|
+
setMetadata({ user_email: userInfo.email });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const trackLogoutEvent = () => {
|
|
143
|
+
if (!isAnalyticsAllowed) return;
|
|
144
|
+
if (typeof posthog?.capture === "function") {
|
|
145
|
+
posthog.capture("user_signed_out");
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
trackEvent,
|
|
151
|
+
trackUserIdentify,
|
|
152
|
+
trackLogoutEvent,
|
|
153
|
+
toggleGA,
|
|
154
|
+
toggleGTM,
|
|
155
|
+
togglePostHog,
|
|
156
|
+
toggleOpenReplay,
|
|
157
|
+
toggleGoogleAdSense,
|
|
158
|
+
};
|
|
92
159
|
}
|
|
93
160
|
|
|
94
161
|
export { useDataTracking };
|
|
95
|
-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext,
|
|
1
|
+
import React, { createContext, useReducer } from "react";
|
|
2
2
|
import { v4 as uuidV4 } from "uuid";
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -172,11 +172,11 @@ export const SessionReplayProvider: React.FC<SessionReplayProviderProps> = ({
|
|
|
172
172
|
const setUserId = (userId: string) => dispatch({ type: "setUserId", payload: userId })
|
|
173
173
|
const setMetadata = (metadata: Record<string, any>) => dispatch({ type: "setMetadata", payload: metadata })
|
|
174
174
|
|
|
175
|
-
// init and start tracker
|
|
176
|
-
useEffect(() => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}, []);
|
|
175
|
+
// // init and start tracker
|
|
176
|
+
// useEffect(() => {
|
|
177
|
+
// initTracker();
|
|
178
|
+
// startTracking();
|
|
179
|
+
// }, []);
|
|
180
180
|
|
|
181
181
|
return <TrackerContext.Provider value={{
|
|
182
182
|
initTracker,
|