@tagadapay/plugin-sdk 3.1.5 → 3.1.9
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 +1129 -1129
- package/build-cdn.js +220 -113
- package/dist/external-tracker.js +1225 -558
- package/dist/external-tracker.min.js +2 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/hooks/useApplePay.js +25 -36
- package/dist/react/hooks/usePaymentPolling.d.ts +9 -3
- package/dist/react/providers/TagadaProvider.js +5 -5
- package/dist/react/utils/money.d.ts +4 -3
- package/dist/react/utils/money.js +39 -6
- package/dist/react/utils/trackingUtils.js +1 -0
- package/dist/tagada-sdk.js +10142 -0
- package/dist/tagada-sdk.min.js +43 -0
- package/dist/tagada-sdk.min.js.map +7 -0
- package/dist/v2/core/client.js +34 -2
- package/dist/v2/core/config/environment.js +9 -2
- package/dist/v2/core/funnelClient.d.ts +180 -2
- package/dist/v2/core/funnelClient.js +289 -6
- package/dist/v2/core/resources/apiClient.js +1 -1
- package/dist/v2/core/resources/checkout.d.ts +68 -0
- package/dist/v2/core/resources/funnel.d.ts +25 -0
- package/dist/v2/core/resources/payments.d.ts +70 -3
- package/dist/v2/core/resources/payments.js +72 -7
- package/dist/v2/core/utils/index.d.ts +1 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
- package/dist/v2/core/utils/pluginConfig.js +68 -5
- package/dist/v2/core/utils/previewMode.d.ts +7 -0
- package/dist/v2/core/utils/previewMode.js +72 -14
- package/dist/v2/core/utils/previewModeIndicator.d.ts +19 -0
- package/dist/v2/core/utils/previewModeIndicator.js +414 -0
- package/dist/v2/core/utils/tokenStorage.d.ts +4 -0
- package/dist/v2/core/utils/tokenStorage.js +15 -1
- package/dist/v2/index.d.ts +9 -3
- package/dist/v2/index.js +8 -3
- package/dist/v2/react/components/ApplePayButton.d.ts +22 -123
- package/dist/v2/react/components/ApplePayButton.js +247 -317
- package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
- package/dist/v2/react/components/FunnelScriptInjector.js +255 -162
- package/dist/v2/react/components/GooglePayButton.d.ts +2 -0
- package/dist/v2/react/components/GooglePayButton.js +80 -64
- package/dist/v2/react/components/PreviewModeIndicator.d.ts +46 -0
- package/dist/v2/react/components/PreviewModeIndicator.js +113 -0
- package/dist/v2/react/hooks/useApplePayCheckout.d.ts +16 -0
- package/dist/v2/react/hooks/useApplePayCheckout.js +193 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +48 -6
- package/dist/v2/react/hooks/useFunnel.js +25 -5
- package/dist/v2/react/hooks/useGoogleAutocomplete.d.ts +10 -0
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +48 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.d.ts +21 -0
- package/dist/v2/react/hooks/useGooglePayCheckout.js +198 -0
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +15 -3
- package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +34 -2
- package/dist/v2/react/hooks/usePaymentQuery.js +731 -7
- package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +26 -0
- package/dist/v2/react/hooks/usePaymentRetrieve.js +175 -0
- package/dist/v2/react/hooks/usePixelTracking.d.ts +56 -0
- package/dist/v2/react/hooks/usePixelTracking.js +508 -0
- package/dist/v2/react/hooks/useStepConfig.d.ts +64 -0
- package/dist/v2/react/hooks/useStepConfig.js +53 -0
- package/dist/v2/react/index.d.ts +15 -5
- package/dist/v2/react/index.js +8 -2
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +1 -0
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +41 -13
- package/dist/v2/react/providers/TagadaProvider.js +24 -23
- package/dist/v2/standalone/external-tracker.d.ts +2 -0
- package/dist/v2/standalone/external-tracker.js +6 -3
- package/package.json +112 -112
- package/dist/v2/react/hooks/useApplePay.d.ts +0 -16
- package/dist/v2/react/hooks/useApplePay.js +0 -247
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* usePixelTracking Hook & Provider
|
|
5
|
+
*
|
|
6
|
+
* SDK-level pixel tracking based on runtime stepConfig.pixels injected
|
|
7
|
+
* by the CRM. This mirrors the CMS pixel context pattern but uses the
|
|
8
|
+
* funnel step configuration as the source of truth instead of store
|
|
9
|
+
* integrations.
|
|
10
|
+
*/
|
|
11
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
|
|
12
|
+
import { minorUnitsToMajorUnits } from '../../../react/utils/money';
|
|
13
|
+
import { useStepConfig } from './useStepConfig';
|
|
14
|
+
const PixelTrackingContext = createContext(null);
|
|
15
|
+
/**
|
|
16
|
+
* Simple per-page duplicate guard (time-window based)
|
|
17
|
+
* Avoids accidental double-fires during rerenders.
|
|
18
|
+
*/
|
|
19
|
+
function createDuplicateGuard(windowMs) {
|
|
20
|
+
const lastEvents = new Map();
|
|
21
|
+
return (eventName, parameters) => {
|
|
22
|
+
try {
|
|
23
|
+
const key = JSON.stringify({ eventName, parameters });
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const last = lastEvents.get(key);
|
|
26
|
+
if (last && now - last < windowMs)
|
|
27
|
+
return false;
|
|
28
|
+
lastEvents.set(key, now);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// If hashing fails for any reason, just allow the event
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const shouldTrackEvent = createDuplicateGuard(5000);
|
|
38
|
+
/**
|
|
39
|
+
* Provider that initializes pixels based on stepConfig.pixels
|
|
40
|
+
* and exposes a simple track(event, params) API.
|
|
41
|
+
*/
|
|
42
|
+
export function PixelTrackingProvider({ children }) {
|
|
43
|
+
const { pixels } = useStepConfig();
|
|
44
|
+
const [pixelsInitialized, setPixelsInitialized] = useState(false);
|
|
45
|
+
const isMountedRef = useRef(true);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
isMountedRef.current = true;
|
|
48
|
+
return () => {
|
|
49
|
+
isMountedRef.current = false;
|
|
50
|
+
};
|
|
51
|
+
}, []);
|
|
52
|
+
// Initialize pixels once when configuration is available
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!pixels || pixelsInitialized || !isMountedRef.current)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
// Facebook / Meta - support multiple pixels
|
|
58
|
+
const facebookPixels = pixels.facebook; // Support both 'facebook' and legacy 'meta'
|
|
59
|
+
if (facebookPixels) {
|
|
60
|
+
if (Array.isArray(facebookPixels)) {
|
|
61
|
+
facebookPixels.forEach((pixel) => {
|
|
62
|
+
if (pixel.enabled && pixel.pixelId) {
|
|
63
|
+
initMetaPixel(pixel.pixelId);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// TikTok - support multiple pixels
|
|
69
|
+
const tiktokPixels = pixels.tiktok;
|
|
70
|
+
if (tiktokPixels) {
|
|
71
|
+
if (Array.isArray(tiktokPixels)) {
|
|
72
|
+
tiktokPixels.forEach((pixel) => {
|
|
73
|
+
if (pixel.enabled && pixel.pixelId) {
|
|
74
|
+
initTikTokPixel(pixel.pixelId);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Snapchat - support multiple pixels
|
|
80
|
+
const snapchatPixels = pixels.snapchat;
|
|
81
|
+
if (snapchatPixels) {
|
|
82
|
+
if (Array.isArray(snapchatPixels)) {
|
|
83
|
+
snapchatPixels.forEach((pixel) => {
|
|
84
|
+
if (pixel.enabled && pixel.pixelId) {
|
|
85
|
+
initSnapchatPixel(pixel.pixelId);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// GTM - support multiple containers
|
|
91
|
+
const gtmPixels = pixels.gtm;
|
|
92
|
+
if (gtmPixels) {
|
|
93
|
+
if (Array.isArray(gtmPixels)) {
|
|
94
|
+
gtmPixels.forEach((pixel) => {
|
|
95
|
+
if (pixel.enabled && pixel.containerId) {
|
|
96
|
+
initGTM(pixel.containerId);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (isMountedRef.current) {
|
|
102
|
+
setPixelsInitialized(true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Fail-safe: never break the host page because of pixel issues
|
|
107
|
+
if (typeof console !== 'undefined') {
|
|
108
|
+
console.error('[SDK Pixels] Error during pixel initialization:', error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}, [pixels, pixelsInitialized]);
|
|
112
|
+
const track = useCallback((eventName, parameters = {}) => {
|
|
113
|
+
if (!pixels || !pixelsInitialized || !isMountedRef.current) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Duplicate guard
|
|
117
|
+
if (!shouldTrackEvent(eventName, parameters)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
// Facebook / Meta - track to all enabled pixels
|
|
122
|
+
const facebookPixels = pixels.facebook; // Support both 'facebook' and legacy 'meta'
|
|
123
|
+
if (facebookPixels) {
|
|
124
|
+
const pixelArray = Array.isArray(facebookPixels)
|
|
125
|
+
? facebookPixels
|
|
126
|
+
: [facebookPixels];
|
|
127
|
+
const enabledPixels = pixelArray.filter((p) => p.enabled);
|
|
128
|
+
if (enabledPixels.length > 0) {
|
|
129
|
+
const { name, params } = mapMetaEvent(eventName, parameters);
|
|
130
|
+
// Track to all enabled Facebook pixels
|
|
131
|
+
enabledPixels.forEach(() => {
|
|
132
|
+
trackMetaEvent(name, params);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// TikTok - track to all enabled pixels
|
|
137
|
+
const tiktokPixels = pixels.tiktok;
|
|
138
|
+
if (tiktokPixels) {
|
|
139
|
+
const pixelArray = Array.isArray(tiktokPixels)
|
|
140
|
+
? tiktokPixels
|
|
141
|
+
: [tiktokPixels];
|
|
142
|
+
const enabledPixels = pixelArray.filter((p) => p.enabled);
|
|
143
|
+
if (enabledPixels.length > 0) {
|
|
144
|
+
const { name, params } = mapTikTokEvent(eventName, parameters);
|
|
145
|
+
// Track to all enabled TikTok pixels
|
|
146
|
+
enabledPixels.forEach(() => {
|
|
147
|
+
trackTikTokEvent(name, params);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Snapchat - track to all enabled pixels
|
|
152
|
+
const snapchatPixels = pixels.snapchat;
|
|
153
|
+
if (snapchatPixels) {
|
|
154
|
+
const pixelArray = Array.isArray(snapchatPixels)
|
|
155
|
+
? snapchatPixels
|
|
156
|
+
: [snapchatPixels];
|
|
157
|
+
const enabledPixels = pixelArray.filter((p) => p.enabled);
|
|
158
|
+
if (enabledPixels.length > 0) {
|
|
159
|
+
const { name, params } = mapSnapchatEvent(eventName, parameters);
|
|
160
|
+
// Track to all enabled Snapchat pixels
|
|
161
|
+
enabledPixels.forEach(() => {
|
|
162
|
+
trackSnapchatEvent(name, params);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// GTM - track to all enabled containers
|
|
167
|
+
const gtmPixels = pixels.gtm;
|
|
168
|
+
if (gtmPixels) {
|
|
169
|
+
const pixelArray = Array.isArray(gtmPixels)
|
|
170
|
+
? gtmPixels
|
|
171
|
+
: [gtmPixels];
|
|
172
|
+
const enabledPixels = pixelArray.filter((p) => p.enabled);
|
|
173
|
+
if (enabledPixels.length > 0) {
|
|
174
|
+
const { name, params } = mapGTMEvent(eventName, parameters);
|
|
175
|
+
// Track to all enabled GTM containers
|
|
176
|
+
enabledPixels.forEach(() => {
|
|
177
|
+
console.log('trackGTMEvent', name, params);
|
|
178
|
+
trackGTMEvent(name, params);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (typeof console !== 'undefined') {
|
|
185
|
+
console.error('[SDK Pixels] Error tracking pixel event:', error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}, [pixels, pixelsInitialized]);
|
|
189
|
+
// Track page views automatically when pixels are initialized
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!pixelsInitialized || !isMountedRef.current)
|
|
192
|
+
return;
|
|
193
|
+
// Small delay to ensure we don't double fire during strict mode remounts
|
|
194
|
+
const pageViewTimeoutId = setTimeout(() => {
|
|
195
|
+
if (isMountedRef.current) {
|
|
196
|
+
track('PageView', {
|
|
197
|
+
path: typeof window !== 'undefined' ? window.location.pathname : '',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}, 0);
|
|
201
|
+
return () => {
|
|
202
|
+
if (pageViewTimeoutId) {
|
|
203
|
+
clearTimeout(pageViewTimeoutId);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}, [pixelsInitialized, track]);
|
|
207
|
+
const value = useMemo(() => ({
|
|
208
|
+
track,
|
|
209
|
+
pixelsInitialized,
|
|
210
|
+
}), [track, pixelsInitialized]);
|
|
211
|
+
return _jsx(PixelTrackingContext.Provider, { value: value, children: children });
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Hook to access SDK pixel tracking.
|
|
215
|
+
* Must be used within TagadaProvider (which wraps PixelTrackingProvider).
|
|
216
|
+
*/
|
|
217
|
+
export function usePixelTracking() {
|
|
218
|
+
const context = useContext(PixelTrackingContext);
|
|
219
|
+
if (!context) {
|
|
220
|
+
throw new Error('usePixelTracking must be used within a PixelTrackingProvider');
|
|
221
|
+
}
|
|
222
|
+
return context;
|
|
223
|
+
}
|
|
224
|
+
function initMetaPixel(pixelId) {
|
|
225
|
+
if (typeof window === 'undefined')
|
|
226
|
+
return;
|
|
227
|
+
if (window.fbq)
|
|
228
|
+
return;
|
|
229
|
+
// Standard Facebook Pixel bootstrap
|
|
230
|
+
(function (f, b, e) {
|
|
231
|
+
if (f.fbq)
|
|
232
|
+
return;
|
|
233
|
+
const n = function (...args) {
|
|
234
|
+
if (n.callMethod) {
|
|
235
|
+
n.callMethod(...args);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
n.queue.push(args);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
f.fbq = n;
|
|
242
|
+
if (!f._fbq)
|
|
243
|
+
f._fbq = n;
|
|
244
|
+
n.queue = n.queue ?? [];
|
|
245
|
+
n.loaded = true;
|
|
246
|
+
n.version = '2.0';
|
|
247
|
+
n.queue = [];
|
|
248
|
+
const t = b.createElement(e);
|
|
249
|
+
t.async = true;
|
|
250
|
+
t.src = 'https://connect.facebook.net/en_US/fbevents.js';
|
|
251
|
+
const s = b.getElementsByTagName(e)[0];
|
|
252
|
+
s.parentNode?.insertBefore(t, s);
|
|
253
|
+
})(window, document, 'script');
|
|
254
|
+
const fbq = window.fbq;
|
|
255
|
+
if (fbq) {
|
|
256
|
+
fbq('init', pixelId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function trackMetaEvent(name, params) {
|
|
260
|
+
if (typeof window === 'undefined' || !window.fbq)
|
|
261
|
+
return;
|
|
262
|
+
const fbq = window.fbq;
|
|
263
|
+
fbq?.('track', name, params);
|
|
264
|
+
}
|
|
265
|
+
function initTikTokPixel(pixelId) {
|
|
266
|
+
if (typeof window === 'undefined')
|
|
267
|
+
return;
|
|
268
|
+
if (window.ttq)
|
|
269
|
+
return;
|
|
270
|
+
(function (w, d, t) {
|
|
271
|
+
const ttq = w.ttq ||
|
|
272
|
+
function (...args) {
|
|
273
|
+
ttq.queue.push(args);
|
|
274
|
+
};
|
|
275
|
+
if (!ttq.queue) {
|
|
276
|
+
ttq.queue = [];
|
|
277
|
+
}
|
|
278
|
+
w.ttq = ttq;
|
|
279
|
+
ttq.methods = [
|
|
280
|
+
'page',
|
|
281
|
+
'track',
|
|
282
|
+
'identify',
|
|
283
|
+
'instances',
|
|
284
|
+
'debug',
|
|
285
|
+
'on',
|
|
286
|
+
'off',
|
|
287
|
+
'once',
|
|
288
|
+
'ready',
|
|
289
|
+
'alias',
|
|
290
|
+
'group',
|
|
291
|
+
'enableCookie',
|
|
292
|
+
'disableCookie',
|
|
293
|
+
];
|
|
294
|
+
ttq.setAndDefer = function (target, method) {
|
|
295
|
+
target[method] = function (...args) {
|
|
296
|
+
target.queue.push([method, ...args]);
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
for (const method of ttq.methods) {
|
|
300
|
+
ttq.setAndDefer(ttq, method);
|
|
301
|
+
}
|
|
302
|
+
ttq.load = function (e) {
|
|
303
|
+
const n = 'https://analytics.tiktok.com/i18n/pixel/events.js';
|
|
304
|
+
const a = d.createElement(t);
|
|
305
|
+
a.type = 'text/javascript';
|
|
306
|
+
a.async = true;
|
|
307
|
+
a.src = n + '?sdkid=' + e + '&lib=ttq';
|
|
308
|
+
const s = d.getElementsByTagName(t)[0];
|
|
309
|
+
s.parentNode.insertBefore(a, s);
|
|
310
|
+
};
|
|
311
|
+
})(window, document, 'script');
|
|
312
|
+
const ttqInstance = window.ttq;
|
|
313
|
+
if (!ttqInstance)
|
|
314
|
+
return;
|
|
315
|
+
ttqInstance.load?.(pixelId);
|
|
316
|
+
ttqInstance.page?.();
|
|
317
|
+
}
|
|
318
|
+
function trackTikTokEvent(name, params) {
|
|
319
|
+
if (typeof window === 'undefined' || !window.ttq)
|
|
320
|
+
return;
|
|
321
|
+
const ttq = window.ttq;
|
|
322
|
+
ttq?.track?.(name, params);
|
|
323
|
+
}
|
|
324
|
+
function initSnapchatPixel(pixelId) {
|
|
325
|
+
if (typeof window === 'undefined')
|
|
326
|
+
return;
|
|
327
|
+
if (window.snaptr)
|
|
328
|
+
return;
|
|
329
|
+
(function (w, d, tagName) {
|
|
330
|
+
if (w.snaptr)
|
|
331
|
+
return;
|
|
332
|
+
const a = (function (...args) {
|
|
333
|
+
if (a.handleRequest) {
|
|
334
|
+
a.handleRequest(...args);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
a.queue.push(args);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
a.queue = [];
|
|
341
|
+
w.snaptr = a;
|
|
342
|
+
const s = 'script';
|
|
343
|
+
const r = d.createElement(s);
|
|
344
|
+
r.async = true;
|
|
345
|
+
r.src = 'https://sc-static.net/scevent.min.js';
|
|
346
|
+
const u = d.getElementsByTagName(tagName)[0];
|
|
347
|
+
u.parentNode?.insertBefore(r, u);
|
|
348
|
+
})(window, document, 'script');
|
|
349
|
+
const snaptr = window.snaptr;
|
|
350
|
+
snaptr?.('init', pixelId);
|
|
351
|
+
}
|
|
352
|
+
function trackSnapchatEvent(name, params) {
|
|
353
|
+
if (typeof window === 'undefined' || !window.snaptr)
|
|
354
|
+
return;
|
|
355
|
+
const snaptr = window.snaptr;
|
|
356
|
+
snaptr?.('track', name, params);
|
|
357
|
+
}
|
|
358
|
+
function initGTM(containerId) {
|
|
359
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
360
|
+
return;
|
|
361
|
+
if (!containerId)
|
|
362
|
+
return;
|
|
363
|
+
// Check if GTM script is already loaded for this container
|
|
364
|
+
const existingScript = document.querySelector(`script[src*="googletagmanager.com/gtm.js?id=${containerId}"]`);
|
|
365
|
+
if (existingScript) {
|
|
366
|
+
return; // GTM already initialized for this container
|
|
367
|
+
}
|
|
368
|
+
// Initialize dataLayer before GTM script (Google's official pattern)
|
|
369
|
+
window.dataLayer = window.dataLayer || [];
|
|
370
|
+
// Push gtm.start event (must be done before script loads)
|
|
371
|
+
window.dataLayer.push({
|
|
372
|
+
'gtm.start': new Date().getTime(),
|
|
373
|
+
event: 'gtm.js',
|
|
374
|
+
});
|
|
375
|
+
// Create and inject GTM script (Google's official pattern)
|
|
376
|
+
// This matches Google's exact implementation from their documentation
|
|
377
|
+
(function (w, d, s, l, i) {
|
|
378
|
+
const f = d.getElementsByTagName(s)[0];
|
|
379
|
+
const j = d.createElement(s);
|
|
380
|
+
const dl = l != 'dataLayer' ? '&l=' + l : '';
|
|
381
|
+
j.async = true;
|
|
382
|
+
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
|
|
383
|
+
// Insert before first script tag (typically in head)
|
|
384
|
+
if (f && f.parentNode) {
|
|
385
|
+
f.parentNode.insertBefore(j, f);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
// Fallback: append to head if no script tags found
|
|
389
|
+
const head = d.head || d.getElementsByTagName('head')[0];
|
|
390
|
+
if (head) {
|
|
391
|
+
head.appendChild(j);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
})(window, document, 'script', 'dataLayer', containerId);
|
|
395
|
+
// Also add noscript fallback in body (Google's official pattern)
|
|
396
|
+
// This ensures GTM works even if JavaScript is disabled
|
|
397
|
+
const noscript = document.createElement('noscript');
|
|
398
|
+
const iframe = document.createElement('iframe');
|
|
399
|
+
iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
|
|
400
|
+
iframe.height = '0';
|
|
401
|
+
iframe.width = '0';
|
|
402
|
+
iframe.style.display = 'none';
|
|
403
|
+
iframe.style.visibility = 'hidden';
|
|
404
|
+
noscript.appendChild(iframe);
|
|
405
|
+
// Insert noscript at the beginning of body
|
|
406
|
+
const body = document.body || document.getElementsByTagName('body')[0];
|
|
407
|
+
if (body) {
|
|
408
|
+
body.insertBefore(noscript, body.firstChild);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function trackGTMEvent(event, params = {}) {
|
|
412
|
+
if (typeof window === 'undefined') {
|
|
413
|
+
console.warn('[SDK GTM] Window not available, event not tracked:', event);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
// Ensure dataLayer exists (should be initialized by initGTM, but double-check)
|
|
417
|
+
if (!window.dataLayer) {
|
|
418
|
+
console.warn('[SDK GTM] dataLayer not initialized, initializing now...');
|
|
419
|
+
window.dataLayer = [];
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const eventData = {
|
|
423
|
+
event,
|
|
424
|
+
...params,
|
|
425
|
+
};
|
|
426
|
+
console.log('[SDK GTM] Pushing event to dataLayer:', eventData);
|
|
427
|
+
window.dataLayer.push(eventData);
|
|
428
|
+
console.log('[SDK GTM] Event pushed successfully. Current dataLayer length:', window.dataLayer.length);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
if (typeof console !== 'undefined') {
|
|
432
|
+
console.error('[SDK GTM] Error tracking event:', error);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// --- Basic event mapping (can be extended later if needed) ---
|
|
437
|
+
function mapMetaEvent(eventName, parameters) {
|
|
438
|
+
return { name: eventName, params: parameters };
|
|
439
|
+
}
|
|
440
|
+
function mapTikTokEvent(eventName, parameters) {
|
|
441
|
+
// TikTok naming is usually aligned; adjust here if needed later
|
|
442
|
+
return { name: eventName, params: parameters };
|
|
443
|
+
}
|
|
444
|
+
function mapSnapchatEvent(eventName, parameters) {
|
|
445
|
+
return { name: eventName, params: parameters };
|
|
446
|
+
}
|
|
447
|
+
function mapGTMEvent(eventName, parameters) {
|
|
448
|
+
// Map standard event names to GTM event names
|
|
449
|
+
const gtmEventMap = {
|
|
450
|
+
PageView: 'page_view',
|
|
451
|
+
AddPaymentInfo: 'add_payment_info',
|
|
452
|
+
AddToCart: 'add_to_cart',
|
|
453
|
+
InitiateCheckout: 'begin_checkout',
|
|
454
|
+
Purchase: 'purchase',
|
|
455
|
+
ViewContent: 'view_item',
|
|
456
|
+
CompleteRegistration: 'sign_up',
|
|
457
|
+
};
|
|
458
|
+
const gtmEventName = gtmEventMap[eventName] ?? eventName.toLowerCase();
|
|
459
|
+
// Transform parameters for GTM
|
|
460
|
+
const gtmParams = transformGTMParameters(eventName, parameters);
|
|
461
|
+
return { name: gtmEventName, params: gtmParams };
|
|
462
|
+
}
|
|
463
|
+
function transformGTMParameters(eventName, parameters) {
|
|
464
|
+
const gtmParameters = { ...parameters };
|
|
465
|
+
// Ensure currency is uppercase
|
|
466
|
+
if (gtmParameters.currency) {
|
|
467
|
+
gtmParameters.currency = String(gtmParameters.currency).toUpperCase();
|
|
468
|
+
}
|
|
469
|
+
// Transform currency values to major units if needed
|
|
470
|
+
if (gtmParameters.currency && gtmParameters.value) {
|
|
471
|
+
try {
|
|
472
|
+
gtmParameters.value = minorUnitsToMajorUnits(Number(gtmParameters.value), String(gtmParameters.currency));
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
// If conversion fails, try simple division by 100 as fallback
|
|
476
|
+
if (typeof console !== 'undefined') {
|
|
477
|
+
console.warn('[SDK GTM] Currency conversion failed, using fallback:', error);
|
|
478
|
+
}
|
|
479
|
+
gtmParameters.value = Number(gtmParameters.value) / 100;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// Ensure numeric values
|
|
483
|
+
if (gtmParameters.value !== undefined) {
|
|
484
|
+
gtmParameters.value = Number(gtmParameters.value);
|
|
485
|
+
}
|
|
486
|
+
if (gtmParameters.num_items !== undefined) {
|
|
487
|
+
gtmParameters.num_items = Number(gtmParameters.num_items);
|
|
488
|
+
}
|
|
489
|
+
// Transform contents to a format better suited for GTM
|
|
490
|
+
if (gtmParameters.contents && Array.isArray(gtmParameters.contents)) {
|
|
491
|
+
gtmParameters.items = gtmParameters.contents.map((item) => ({
|
|
492
|
+
item_id: item.content_id,
|
|
493
|
+
item_name: item.content_name,
|
|
494
|
+
item_category: item.content_category,
|
|
495
|
+
price: item.price ? Number(item.price) : undefined,
|
|
496
|
+
quantity: item.quantity ? Number(item.quantity) : undefined,
|
|
497
|
+
}));
|
|
498
|
+
// Keep original contents for compatibility
|
|
499
|
+
gtmParameters.contents = gtmParameters.contents.map((item) => ({
|
|
500
|
+
id: item.content_id,
|
|
501
|
+
name: item.content_name,
|
|
502
|
+
category: item.content_category,
|
|
503
|
+
price: item.price ? Number(item.price) : undefined,
|
|
504
|
+
quantity: item.quantity ? Number(item.quantity) : undefined,
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
return gtmParameters;
|
|
508
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStepConfig Hook - Access runtime step configuration from HTML injection
|
|
3
|
+
*
|
|
4
|
+
* This hook provides access to step-specific configuration that is injected
|
|
5
|
+
* into the HTML by the CRM when the page is served. This includes:
|
|
6
|
+
* - Payment flow overrides
|
|
7
|
+
* - Static resources (offer, product IDs for A/B variants)
|
|
8
|
+
* - Custom scripts
|
|
9
|
+
* - Pixel tracking configuration
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { stepConfig, paymentFlowId, staticResources } = useStepConfig();
|
|
14
|
+
*
|
|
15
|
+
* // Access payment flow override
|
|
16
|
+
* if (paymentFlowId) {
|
|
17
|
+
* console.log('Using custom payment flow:', paymentFlowId);
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* // Access static resources (e.g., offer ID for current A/B variant)
|
|
21
|
+
* const offerId = staticResources?.offer;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { GTMTrackingConfig, MetaConversionTrackingConfig, PixelTrackingConfig, RuntimeStepConfig, SnapchatTrackingConfig, TrackingProvider } from '../../core/funnelClient';
|
|
25
|
+
export interface UseStepConfigResult {
|
|
26
|
+
/**
|
|
27
|
+
* Full step configuration object
|
|
28
|
+
* Contains payment, staticResources, scripts, and pixels
|
|
29
|
+
*/
|
|
30
|
+
stepConfig: RuntimeStepConfig | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Payment flow ID override for this step
|
|
33
|
+
* If set, this payment flow should be used instead of the store default
|
|
34
|
+
*/
|
|
35
|
+
paymentFlowId: string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Static resources assigned to this step/variant
|
|
38
|
+
* For A/B tests, this contains the resources for the specific variant the user landed on
|
|
39
|
+
* e.g., { offer: 'offer_xxx', product: 'product_xxx' }
|
|
40
|
+
*/
|
|
41
|
+
staticResources: Record<string, string> | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Get scripts for a specific injection position
|
|
44
|
+
* Only returns enabled scripts
|
|
45
|
+
* @param position - Where the scripts should be injected
|
|
46
|
+
*/
|
|
47
|
+
getScripts: (position?: 'head-start' | 'head-end' | 'body-start' | 'body-end') => RuntimeStepConfig['scripts'];
|
|
48
|
+
pixels: {
|
|
49
|
+
[TrackingProvider.FACEBOOK]?: PixelTrackingConfig[];
|
|
50
|
+
[TrackingProvider.TIKTOK]?: PixelTrackingConfig[];
|
|
51
|
+
[TrackingProvider.SNAPCHAT]?: SnapchatTrackingConfig[];
|
|
52
|
+
[TrackingProvider.META_CONVERSION]?: MetaConversionTrackingConfig[];
|
|
53
|
+
[TrackingProvider.GTM]?: GTMTrackingConfig[];
|
|
54
|
+
} | undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Hook to access runtime step configuration injected via HTML
|
|
58
|
+
*
|
|
59
|
+
* The step config is read from window.__TGD_STEP_CONFIG__ or the x-step-config meta tag.
|
|
60
|
+
* Values are memoized and only re-computed on mount.
|
|
61
|
+
*
|
|
62
|
+
* @returns Step configuration values and helpers
|
|
63
|
+
*/
|
|
64
|
+
export declare function useStepConfig(): UseStepConfigResult;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useStepConfig Hook - Access runtime step configuration from HTML injection
|
|
3
|
+
*
|
|
4
|
+
* This hook provides access to step-specific configuration that is injected
|
|
5
|
+
* into the HTML by the CRM when the page is served. This includes:
|
|
6
|
+
* - Payment flow overrides
|
|
7
|
+
* - Static resources (offer, product IDs for A/B variants)
|
|
8
|
+
* - Custom scripts
|
|
9
|
+
* - Pixel tracking configuration
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { stepConfig, paymentFlowId, staticResources } = useStepConfig();
|
|
14
|
+
*
|
|
15
|
+
* // Access payment flow override
|
|
16
|
+
* if (paymentFlowId) {
|
|
17
|
+
* console.log('Using custom payment flow:', paymentFlowId);
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* // Access static resources (e.g., offer ID for current A/B variant)
|
|
21
|
+
* const offerId = staticResources?.offer;
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import { useMemo } from 'react';
|
|
25
|
+
import { getAssignedPaymentFlowId, getAssignedPixels, getAssignedScripts, getAssignedStaticResources, getAssignedStepConfig, TrackingProvider } from '../../core/funnelClient';
|
|
26
|
+
/**
|
|
27
|
+
* Hook to access runtime step configuration injected via HTML
|
|
28
|
+
*
|
|
29
|
+
* The step config is read from window.__TGD_STEP_CONFIG__ or the x-step-config meta tag.
|
|
30
|
+
* Values are memoized and only re-computed on mount.
|
|
31
|
+
*
|
|
32
|
+
* @returns Step configuration values and helpers
|
|
33
|
+
*/
|
|
34
|
+
export function useStepConfig() {
|
|
35
|
+
// Read once on mount - these values don't change during the page lifecycle
|
|
36
|
+
const stepConfig = useMemo(() => getAssignedStepConfig(), []);
|
|
37
|
+
const paymentFlowId = useMemo(() => getAssignedPaymentFlowId(), []);
|
|
38
|
+
const staticResources = useMemo(() => getAssignedStaticResources(), []);
|
|
39
|
+
const pixels = useMemo(() => getAssignedPixels(), []);
|
|
40
|
+
// Create a stable reference for getScripts
|
|
41
|
+
const getScripts = useMemo(() => {
|
|
42
|
+
return (position) => {
|
|
43
|
+
return getAssignedScripts(position);
|
|
44
|
+
};
|
|
45
|
+
}, []);
|
|
46
|
+
return {
|
|
47
|
+
stepConfig,
|
|
48
|
+
paymentFlowId,
|
|
49
|
+
staticResources,
|
|
50
|
+
getScripts,
|
|
51
|
+
pixels
|
|
52
|
+
};
|
|
53
|
+
}
|