@phygitallabs/tapquest-core 6.7.12 → 6.7.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phygitallabs/tapquest-core",
3
- "version": "6.7.12",
3
+ "version": "6.7.13",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { usePhygitalConsent } from "@phygitallabs/phygital-consent";
4
- import posthog from "posthog-js";
5
4
  import { useEffect } from "react";
6
- import { useSessionReplay } from "../session-replay";
5
+ import { useDataTracking } from "./hooks";
7
6
 
8
7
  type GtagConsent = (
9
8
  command: "consent",
@@ -19,27 +18,6 @@ const GTAG_CONSENT_DENIED = {
19
18
  ad_personalization: "denied",
20
19
  } as const;
21
20
 
22
- // const GTAG_CONSENT_GRANTED_ANALYTICS = {
23
- // analytics_storage: "granted",
24
- // } as const;
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
-
43
21
  /**
44
22
  * Syncs cookie consent to GA, GTM, PostHog, OpenReplay, and Google AdSense.
45
23
  * - On mount: sets gtag/GTM consent default to denied.
@@ -48,14 +26,14 @@ function pushConsentUpdate(params: Record<string, string>) {
48
26
  * Must be used under PhygitalConsentProvider; for OpenReplay also under SessionReplayProvider.
49
27
  * Renders nothing.
50
28
  */
51
- export function GtagConsentSync() {
29
+ export function DataTrackingConsentSync() {
52
30
  const consent = usePhygitalConsent() as {
53
31
  isAnalyticsAllowed?: boolean;
54
32
  isAdvertisingAllowed?: boolean;
55
33
  };
56
34
  const isAnalyticsAllowed = consent.isAnalyticsAllowed ?? false;
57
35
  const isAdvertisingAllowed = consent.isAdvertisingAllowed ?? false;
58
- const { initTracker, startTracking } = useSessionReplay();
36
+ const { togglePostHog, toggleOpenReplay, toggleGoogleAdSense, pauseGoogleAdSense } = useDataTracking();
59
37
 
60
38
  // Default: deny all consent (run once on mount)
61
39
  useEffect(() => {
@@ -69,33 +47,26 @@ export function GtagConsentSync() {
69
47
  }
70
48
  }, []);
71
49
 
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
50
+ // Google AdSense: grant ad consent when allowed, revoke (pause) when denied
79
51
  useEffect(() => {
80
- if (!isAdvertisingAllowed) return;
81
- pushConsentUpdate({ ...GTAG_CONSENT_GRANTED_ADS });
82
- }, [isAdvertisingAllowed]);
52
+ if (isAdvertisingAllowed) {
53
+ toggleGoogleAdSense();
54
+ } else {
55
+ pauseGoogleAdSense();
56
+ }
57
+ }, [isAdvertisingAllowed, toggleGoogleAdSense, pauseGoogleAdSense]);
83
58
 
84
59
  // PostHog: opt in when analytics allowed
85
60
  useEffect(() => {
86
61
  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();
90
- }
91
- }, [isAnalyticsAllowed]);
62
+ togglePostHog();
63
+ }, [isAnalyticsAllowed, togglePostHog]);
92
64
 
93
65
  // OpenReplay: init and start when analytics allowed
94
66
  useEffect(() => {
95
67
  if (!isAnalyticsAllowed) return;
96
- initTracker();
97
- startTracking();
98
- }, [isAnalyticsAllowed, initTracker, startTracking]);
68
+ toggleOpenReplay();
69
+ }, [isAnalyticsAllowed, toggleOpenReplay]);
99
70
 
100
71
  return null;
101
72
  }
@@ -21,6 +21,11 @@ const GTAG_ADS_GRANTED = {
21
21
  ad_user_data: "granted",
22
22
  ad_personalization: "granted",
23
23
  } as const;
24
+ const GTAG_ADS_DENIED = {
25
+ ad_storage: "denied",
26
+ ad_user_data: "denied",
27
+ ad_personalization: "denied",
28
+ } as const;
24
29
 
25
30
  // GTM
26
31
  const pushEventToDataLayer = (event: string, data: Record<string, any>) => {
@@ -59,21 +64,75 @@ function useDataTracking() {
59
64
  const isAnalyticsAllowed = consent.isAnalyticsAllowed ?? false;
60
65
  const isAdvertisingAllowed = consent.isAdvertisingAllowed ?? false;
61
66
 
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[] };
67
+ /** Load GA script and grant analytics consent. No-op if analytics consent is denied or script already loaded. */
68
+ const loadGA = (gaId: string) => {
69
+ if (!isAnalyticsAllowed || typeof window === "undefined" || !gaId) return;
70
+ const w = window as Window & {
71
+ gtag?: (...args: unknown[]) => void;
72
+ dataLayer?: unknown[];
73
+ };
74
+
75
+ // Load gtag.js script if not already present
76
+ const scriptId = `ga-script-${gaId}`;
77
+ if (!document.getElementById(scriptId)) {
78
+ const script = document.createElement("script");
79
+ script.id = scriptId;
80
+ script.async = true;
81
+ script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}`;
82
+ document.head.appendChild(script);
83
+
84
+ // Initialize gtag
85
+ w.dataLayer = w.dataLayer || [];
86
+ w.gtag = function () {
87
+ // eslint-disable-next-line prefer-rest-params
88
+ (w.dataLayer as unknown[]).push(arguments);
89
+ };
90
+ w.gtag("js", new Date());
91
+ w.gtag("config", gaId);
92
+ }
93
+
94
+ // Grant analytics consent
66
95
  if (typeof w.gtag === "function") {
67
96
  w.gtag("consent", "update", { ...GTAG_ANALYTICS_GRANTED });
68
- } else if (Array.isArray(w.dataLayer)) {
69
- w.dataLayer.push(["consent", "update", { ...GTAG_ANALYTICS_GRANTED }]);
70
97
  }
71
98
  };
72
99
 
73
- /** Enable GTM consent for analytics. No-op if analytics consent is denied. */
74
- const toggleGTM = () => {
75
- if (!isAnalyticsAllowed || typeof window === "undefined") return;
100
+ /** Load GTM script and grant analytics consent. No-op if analytics consent is denied or script already loaded. */
101
+ const loadGTM = (gtmId: string) => {
102
+ if (!isAnalyticsAllowed || typeof window === "undefined" || !gtmId) return;
76
103
  const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
104
+
105
+ // Load GTM script if not already present
106
+ const scriptId = `gtm-script-${gtmId}`;
107
+ if (!document.getElementById(scriptId)) {
108
+ w.dataLayer = w.dataLayer || [];
109
+ (w.dataLayer as Record<string, unknown>[]).push({
110
+ "gtm.start": new Date().getTime(),
111
+ event: "gtm.js",
112
+ });
113
+ const script = document.createElement("script");
114
+ script.id = scriptId;
115
+ script.async = true;
116
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(gtmId)}`;
117
+ document.head.appendChild(script);
118
+
119
+ // Inject <noscript> iframe fallback into <body>
120
+ const noscriptId = `gtm-noscript-${gtmId}`;
121
+ if (!document.getElementById(noscriptId)) {
122
+ const noscript = document.createElement("noscript");
123
+ noscript.id = noscriptId;
124
+ const iframe = document.createElement("iframe");
125
+ iframe.src = `https://www.googletagmanager.com/ns.html?id=${encodeURIComponent(gtmId)}`;
126
+ iframe.height = "0";
127
+ iframe.width = "0";
128
+ iframe.style.display = "none";
129
+ iframe.style.visibility = "hidden";
130
+ noscript.appendChild(iframe);
131
+ document.body.insertBefore(noscript, document.body.firstChild);
132
+ }
133
+ }
134
+
135
+ // Grant analytics consent
77
136
  if (typeof w.gtag === "function") {
78
137
  w.gtag("consent", "update", { ...GTAG_ANALYTICS_GRANTED });
79
138
  } else {
@@ -108,6 +167,17 @@ function useDataTracking() {
108
167
  }
109
168
  };
110
169
 
170
+ /** Pause Google AdSense by revoking ad consent (sets ad_storage, ad_user_data, ad_personalization to denied). */
171
+ const pauseGoogleAdSense = () => {
172
+ if (typeof window === "undefined") return;
173
+ const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
174
+ if (typeof w.gtag === "function") {
175
+ w.gtag("consent", "update", { ...GTAG_ADS_DENIED });
176
+ } else if (Array.isArray(w.dataLayer)) {
177
+ w.dataLayer.push(["consent", "update", { ...GTAG_ADS_DENIED }]);
178
+ }
179
+ };
180
+
111
181
  const trackEvent = (
112
182
  eventName: string,
113
183
  eventData: Record<string, any>,
@@ -150,11 +220,12 @@ function useDataTracking() {
150
220
  trackEvent,
151
221
  trackUserIdentify,
152
222
  trackLogoutEvent,
153
- toggleGA,
154
- toggleGTM,
223
+ loadGA,
224
+ loadGTM,
155
225
  togglePostHog,
156
226
  toggleOpenReplay,
157
227
  toggleGoogleAdSense,
228
+ pauseGoogleAdSense,
158
229
  };
159
230
  }
160
231
 
@@ -1,3 +1,2 @@
1
- export * from "./GtagConsentSync";
2
- export * from "./GtagGtmScriptToggle";
1
+ export * from "./DataTrackingConsentSync";
3
2
  export * from "./hooks";
@@ -5,8 +5,7 @@ import { RewardServiceProvider } from "@phygitallabs/reward";
5
5
  import { AchievementServiceProvider } from "@phygitallabs/achievement";
6
6
  import { GenerateCertificateServiceProvider } from "@phygitallabs/generate-certificate";
7
7
  import { PhygitalConsentProvider } from "@phygitallabs/phygital-consent";
8
- import { GtagConsentSync } from "../modules/data-tracking";
9
- import { GtagGtmScriptToggleFromConsent } from "../modules/data-tracking";
8
+ import { DataTrackingConsentSync } from "../modules/data-tracking";
10
9
 
11
10
  import serviceApiUrl from "../constants/service";
12
11
  import { APIConfig, ServiceConfig } from "../types/service";
@@ -152,8 +151,7 @@ export const ServicesProvider: React.FC<ServicesProviderProps> = ({
152
151
  },
153
152
  }}
154
153
  >
155
- <GtagConsentSync />
156
- <GtagGtmScriptToggleFromConsent/>
154
+ <DataTrackingConsentSync />
157
155
  {children}
158
156
  </PhygitalConsentProvider>
159
157
  </GenerateCertificateServiceProvider>
@@ -1,73 +0,0 @@
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
- }