@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phygitallabs/tapquest-core",
3
- "version": "6.7.9",
3
+ "version": "6.7.11",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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
- * Sets gtag consent default to denied on mount and updates to granted
23
- * when user has accepted analytics (must be under PhygitalConsentProvider).
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
- const isAnalyticsAllowed = (consent as { isAnalyticsAllowed?: boolean }).isAnalyticsAllowed ?? false;
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
- const gtag = w.gtag;
35
- if (typeof gtag === "function") {
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 || typeof window === "undefined") return;
44
- const w = window as Window & { gtag?: GtagConsent; dataLayer?: unknown[] };
45
- const gtag = w.gtag;
46
- if (typeof gtag === "function") {
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
- interface Window {
7
- dataLayer: unknown[];
8
- }
6
+ interface Window {
7
+ dataLayer: unknown[];
8
+ gtag?: GtagConsent;
9
+ }
9
10
  }
10
11
 
11
- declare function gtag(
12
- command: "event",
13
- eventName: string,
14
- eventParameters: Record<string, any>
15
- ): void;
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
- try {
21
- window.dataLayer = window.dataLayer || [];
22
-
23
- (window.dataLayer as Record<string, unknown>[]).push({
24
- event,
25
- ...data,
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
- if (typeof gtag != "function") {
36
- console.error("gtag is not a function");
37
- return;
38
- }
39
- gtag("event", eventName, eventData);
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
- posthog.capture(eventName, eventData);
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
- const { setUserId, setMetadata } = useSessionReplay();
49
- const consent = usePhygitalConsent();
50
- const isAnalyticsAllowed = (consent as { isAnalyticsAllowed?: boolean }).isAnalyticsAllowed ?? false;
51
-
52
- const trackEvent = (eventName: string, eventData: Record<string, any>, useTools?: ("gtm" | "ga" | "posthog")[]) => {
53
- console.log("trackEvent ", eventName, eventData);
54
-
55
- useTools = useTools || ["gtm"];
56
-
57
- if (useTools.includes("gtm") && typeof window !== "undefined") {
58
- pushEventToDataLayer(eventName, eventData);
59
- }
60
- if (useTools.includes("ga") && isAnalyticsAllowed && typeof gtag == "function") {
61
- pushEventToGA(eventName, eventData);
62
- }
63
- if (useTools.includes("posthog") && typeof posthog == "function") {
64
- pushEventToPosthog(eventName, eventData);
65
- }
66
- };
67
-
68
- const trackUserIdentify = (userInfo: Record<string, any>) => {
69
- posthog.identify(userInfo.email, {
70
- email: userInfo.email,
71
- name: userInfo.name,
72
- avatar: userInfo.avatar,
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
- const trackLogoutEvent = () => {
84
- posthog.capture("user_signed_out");
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
- return {
88
- trackEvent,
89
- trackUserIdentify,
90
- trackLogoutEvent
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,2 +1,3 @@
1
1
  export * from "./GtagConsentSync";
2
- export * from "./hooks";
2
+ export * from "./GtagGtmScriptToggle";
3
+ export * from "./hooks";
@@ -1,4 +1,4 @@
1
- import React, { createContext, useEffect, useReducer } from "react";
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
- initTracker();
178
- startTracking();
179
- }, []);
175
+ // // init and start tracker
176
+ // useEffect(() => {
177
+ // initTracker();
178
+ // startTracking();
179
+ // }, []);
180
180
 
181
181
  return <TrackerContext.Provider value={{
182
182
  initTracker,