@tagadapay/plugin-sdk 3.1.12 → 3.1.22

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.
Files changed (106) hide show
  1. package/build-cdn.js +129 -11
  2. package/dist/data/iso3166.d.ts +23 -33
  3. package/dist/data/iso3166.js +134 -198
  4. package/dist/data/languages.d.ts +5 -64
  5. package/dist/data/languages.js +23 -143
  6. package/dist/external-tracker.js +968 -101
  7. package/dist/external-tracker.min.js +2 -2
  8. package/dist/external-tracker.min.js.map +4 -4
  9. package/dist/react/hooks/useISOData.js +1 -1
  10. package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
  11. package/dist/tagada-sdk.js +12066 -0
  12. package/dist/tagada-sdk.min.js +50 -0
  13. package/dist/tagada-sdk.min.js.map +7 -0
  14. package/dist/v2/core/client.d.ts +4 -2
  15. package/dist/v2/core/client.js +4 -3
  16. package/dist/v2/core/errors.d.ts +75 -0
  17. package/dist/v2/core/errors.js +104 -0
  18. package/dist/v2/core/funnelClient.d.ts +2 -0
  19. package/dist/v2/core/index.d.ts +1 -0
  20. package/dist/v2/core/index.js +2 -0
  21. package/dist/v2/core/pixelMapping.d.ts +49 -0
  22. package/dist/v2/core/pixelMapping.js +325 -0
  23. package/dist/v2/core/resources/apiClient.d.ts +2 -0
  24. package/dist/v2/core/resources/apiClient.js +52 -9
  25. package/dist/v2/core/resources/checkout.d.ts +89 -30
  26. package/dist/v2/core/resources/checkout.js +8 -0
  27. package/dist/v2/core/resources/customer.d.ts +20 -19
  28. package/dist/v2/core/resources/funnel.d.ts +17 -17
  29. package/dist/v2/core/resources/payments.d.ts +84 -13
  30. package/dist/v2/core/resources/payments.js +26 -9
  31. package/dist/v2/core/types.d.ts +50 -12
  32. package/dist/v2/core/types.js +0 -3
  33. package/dist/v2/core/utils/checkout.d.ts +2 -2
  34. package/dist/v2/core/utils/checkout.js +7 -2
  35. package/dist/v2/core/utils/currency.d.ts +14 -0
  36. package/dist/v2/core/utils/currency.js +40 -0
  37. package/dist/v2/core/utils/index.d.ts +1 -0
  38. package/dist/v2/core/utils/index.js +2 -0
  39. package/dist/v2/core/utils/order.d.ts +11 -9
  40. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  41. package/dist/v2/core/utils/pluginConfig.js +28 -0
  42. package/dist/v2/index.d.ts +3 -1
  43. package/dist/v2/index.js +1 -1
  44. package/dist/v2/react/components/FunnelScriptInjector.js +21 -0
  45. package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
  46. package/dist/v2/react/components/WhopCheckout.js +231 -0
  47. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
  48. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
  49. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
  50. package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
  51. package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
  52. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
  53. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
  54. package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
  55. package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
  56. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
  57. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
  58. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
  59. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
  60. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
  61. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +13 -0
  62. package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
  63. package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
  64. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
  65. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
  66. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
  67. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
  68. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
  69. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
  70. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
  71. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
  72. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
  73. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
  74. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
  75. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
  76. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
  77. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
  78. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +6 -0
  79. package/dist/v2/react/hooks/useCheckoutQuery.js +45 -0
  80. package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
  81. package/dist/v2/react/hooks/useGeoLocation.js +4 -2
  82. package/dist/v2/react/hooks/useISOData.js +1 -1
  83. package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
  84. package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
  85. package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
  86. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
  87. package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
  88. package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
  89. package/dist/v2/react/hooks/usePixelTracking.js +212 -514
  90. package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
  91. package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
  92. package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
  93. package/dist/v2/react/index.d.ts +7 -0
  94. package/dist/v2/react/index.js +4 -0
  95. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +2 -1
  96. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +3 -1
  97. package/dist/v2/react/providers/TagadaProvider.js +71 -2
  98. package/dist/v2/standalone/external-tracker.d.ts +52 -46
  99. package/dist/v2/standalone/external-tracker.js +205 -98
  100. package/dist/v2/standalone/index.d.ts +22 -0
  101. package/dist/v2/standalone/index.js +126 -1
  102. package/package.json +3 -3
  103. package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
  104. package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
  105. package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
  106. package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
@@ -4,18 +4,16 @@ import { jsx as _jsx } from "react/jsx-runtime";
4
4
  * usePixelTracking Hook & Provider
5
5
  *
6
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.
7
+ * by the CRM. Uses core/pixelMapping for event mapping and gating,
8
+ * keeping browser-specific init/fire logic here.
10
9
  */
11
10
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
12
- import { minorUnitsToMajorUnits } from '../../../react/utils/money';
11
+ import { resolvePixelEvents, } from '../../core/pixelMapping';
13
12
  import { useStepConfig } from './useStepConfig';
14
13
  const PixelTrackingContext = createContext(null);
15
- /**
16
- * Simple per-page duplicate guard (time-window based)
17
- * Avoids accidental double-fires during rerenders.
18
- */
14
+ // ---------------------------------------------------------------------------
15
+ // Duplicate guard (time-window based)
16
+ // ---------------------------------------------------------------------------
19
17
  function createDuplicateGuard(windowMs) {
20
18
  const lastEvents = new Map();
21
19
  return (eventName, parameters) => {
@@ -29,588 +27,288 @@ function createDuplicateGuard(windowMs) {
29
27
  return true;
30
28
  }
31
29
  catch {
32
- // If hashing fails for any reason, just allow the event
33
30
  return true;
34
31
  }
35
32
  };
36
33
  }
37
34
  const shouldTrackEvent = createDuplicateGuard(5000);
38
- /**
39
- * Provider that initializes pixels based on stepConfig.pixels
40
- * and exposes a simple track(event, params) API.
41
- */
35
+ // ---------------------------------------------------------------------------
36
+ // Provider
37
+ // ---------------------------------------------------------------------------
42
38
  export function PixelTrackingProvider({ children }) {
43
39
  const { pixels } = useStepConfig();
44
40
  const [pixelsInitialized, setPixelsInitialized] = useState(false);
45
41
  const isMountedRef = useRef(true);
46
42
  useEffect(() => {
47
43
  isMountedRef.current = true;
48
- return () => {
49
- isMountedRef.current = false;
50
- };
44
+ return () => { isMountedRef.current = false; };
51
45
  }, []);
52
- // Initialize pixels once when configuration is available
46
+ // ---- Initialize pixel scripts once ----
53
47
  useEffect(() => {
54
48
  if (!pixels || pixelsInitialized || !isMountedRef.current)
55
49
  return;
56
50
  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
- // Pinterest - support multiple pixels
91
- const pinterestPixels = pixels.pinterest;
92
- if (pinterestPixels) {
93
- if (Array.isArray(pinterestPixels)) {
94
- pinterestPixels.forEach((pixel) => {
95
- if (pixel.enabled && pixel.pixelId) {
96
- initPinterestPixel(pixel.pixelId);
97
- }
98
- });
99
- }
100
- }
101
- // GTM - support multiple containers
102
- const gtmPixels = pixels.gtm;
103
- if (gtmPixels) {
104
- if (Array.isArray(gtmPixels)) {
105
- gtmPixels.forEach((pixel) => {
106
- if (pixel.enabled && pixel.containerId) {
107
- initGTM(pixel.containerId);
108
- }
109
- });
110
- }
111
- }
112
- if (isMountedRef.current) {
51
+ pixels.facebook?.forEach((px) => { if (px.enabled && px.pixelId)
52
+ initMetaPixel(px.pixelId); });
53
+ pixels.tiktok?.forEach((px) => { if (px.enabled && px.pixelId)
54
+ initTikTokPixel(px.pixelId); });
55
+ pixels.snapchat?.forEach((px) => { if (px.enabled && px.pixelId)
56
+ initSnapchatPixel(px.pixelId); });
57
+ pixels.pinterest?.forEach((px) => { if (px.enabled && px.pixelId)
58
+ initPinterestPixel(px.pixelId); });
59
+ pixels.gtm?.forEach((px) => { if (px.enabled && px.containerId)
60
+ initGTM(px.containerId); });
61
+ if (isMountedRef.current)
113
62
  setPixelsInitialized(true);
114
- }
115
63
  }
116
64
  catch (error) {
117
- // Fail-safe: never break the host page because of pixel issues
118
- if (typeof console !== 'undefined') {
119
- console.error('[SDK Pixels] Error during pixel initialization:', error);
120
- }
65
+ console.error('[SDK Pixels] Initialization error:', error);
121
66
  }
122
67
  }, [pixels, pixelsInitialized]);
68
+ // ---- Track function ----
123
69
  const track = useCallback((eventName, parameters = {}) => {
124
- if (!pixels || !pixelsInitialized || !isMountedRef.current) {
70
+ if (!pixels || !pixelsInitialized || !isMountedRef.current)
125
71
  return;
126
- }
127
- // Duplicate guard
128
- if (!shouldTrackEvent(eventName, parameters)) {
72
+ if (!shouldTrackEvent(eventName, parameters))
129
73
  return;
130
- }
131
74
  try {
132
- // Facebook / Meta - track to all enabled pixels
133
- const facebookPixels = pixels.facebook; // Support both 'facebook' and legacy 'meta'
134
- if (facebookPixels) {
135
- const pixelArray = Array.isArray(facebookPixels)
136
- ? facebookPixels
137
- : [facebookPixels];
138
- const enabledPixels = pixelArray.filter((p) => p.enabled);
139
- if (enabledPixels.length > 0) {
140
- const { name, params } = mapMetaEvent(eventName, parameters);
141
- // Track to all enabled Facebook pixels
142
- enabledPixels.forEach(() => {
143
- trackMetaEvent(name, params);
144
- });
145
- }
146
- }
147
- // TikTok - track to all enabled pixels
148
- const tiktokPixels = pixels.tiktok;
149
- if (tiktokPixels) {
150
- const pixelArray = Array.isArray(tiktokPixels)
151
- ? tiktokPixels
152
- : [tiktokPixels];
153
- const enabledPixels = pixelArray.filter((p) => p.enabled);
154
- if (enabledPixels.length > 0) {
155
- const { name, params } = mapTikTokEvent(eventName, parameters);
156
- // Track to all enabled TikTok pixels
157
- enabledPixels.forEach(() => {
158
- trackTikTokEvent(name, params);
159
- });
160
- }
161
- }
162
- // Snapchat - track to all enabled pixels
163
- const snapchatPixels = pixels.snapchat;
164
- if (snapchatPixels) {
165
- const pixelArray = Array.isArray(snapchatPixels)
166
- ? snapchatPixels
167
- : [snapchatPixels];
168
- const enabledPixels = pixelArray.filter((p) => p.enabled);
169
- if (enabledPixels.length > 0) {
170
- const { name, params } = mapSnapchatEvent(eventName, parameters);
171
- // Track to all enabled Snapchat pixels
172
- enabledPixels.forEach(() => {
173
- trackSnapchatEvent(name, params);
174
- });
175
- }
176
- }
177
- // Pinterest - track to all enabled pixels
178
- const pinterestPixels = pixels.pinterest;
179
- if (pinterestPixels) {
180
- const pixelArray = Array.isArray(pinterestPixels)
181
- ? pinterestPixels
182
- : [pinterestPixels];
183
- const enabledPixels = pixelArray.filter((p) => p.enabled);
184
- if (enabledPixels.length > 0) {
185
- const { name, params } = mapPinterestEvent(eventName, parameters);
186
- // Track to all enabled Pinterest pixels
187
- enabledPixels.forEach(() => {
188
- trackPinterestEvent(name, params);
189
- });
190
- }
191
- }
192
- // GTM - track to all enabled containers
193
- const gtmPixels = pixels.gtm;
194
- if (gtmPixels) {
195
- const pixelArray = Array.isArray(gtmPixels)
196
- ? gtmPixels
197
- : [gtmPixels];
198
- const enabledPixels = pixelArray.filter((p) => p.enabled);
199
- if (enabledPixels.length > 0) {
200
- const { name, params } = mapGTMEvent(eventName, parameters);
201
- // Track to all enabled GTM containers
202
- enabledPixels.forEach(() => {
203
- console.log('trackGTMEvent', name, params);
204
- trackGTMEvent(name, params);
205
- });
206
- }
75
+ const events = resolvePixelEvents(pixels, eventName, parameters);
76
+ for (const { provider, mapped } of events) {
77
+ fire(provider, mapped.name, mapped.params);
207
78
  }
208
79
  }
209
80
  catch (error) {
210
- if (typeof console !== 'undefined') {
211
- console.error('[SDK Pixels] Error tracking pixel event:', error);
212
- }
81
+ console.error('[SDK Pixels] Tracking error:', error);
213
82
  }
214
83
  }, [pixels, pixelsInitialized]);
215
- // Track page views automatically when pixels are initialized
84
+ // ---- Auto page-view ----
216
85
  useEffect(() => {
217
86
  if (!pixelsInitialized || !isMountedRef.current)
218
87
  return;
219
- // Small delay to ensure we don't double fire during strict mode remounts
220
- const pageViewTimeoutId = setTimeout(() => {
88
+ const id = setTimeout(() => {
221
89
  if (isMountedRef.current) {
222
- track('PageView', {
223
- path: typeof window !== 'undefined' ? window.location.pathname : '',
224
- });
90
+ track('PageView', { path: typeof window !== 'undefined' ? window.location.pathname : '' });
225
91
  }
226
92
  }, 0);
227
- return () => {
228
- if (pageViewTimeoutId) {
229
- clearTimeout(pageViewTimeoutId);
230
- }
231
- };
93
+ return () => clearTimeout(id);
232
94
  }, [pixelsInitialized, track]);
233
- const value = useMemo(() => ({
234
- track,
235
- pixelsInitialized,
236
- }), [track, pixelsInitialized]);
95
+ const value = useMemo(() => ({ track, pixelsInitialized }), [track, pixelsInitialized]);
237
96
  return _jsx(PixelTrackingContext.Provider, { value: value, children: children });
238
97
  }
239
- /**
240
- * Hook to access SDK pixel tracking.
241
- * Must be used within TagadaProvider (which wraps PixelTrackingProvider).
242
- */
243
98
  export function usePixelTracking() {
244
99
  const context = useContext(PixelTrackingContext);
245
- if (!context) {
100
+ if (!context)
246
101
  throw new Error('usePixelTracking must be used within a PixelTrackingProvider');
247
- }
248
102
  return context;
249
103
  }
250
- function initMetaPixel(pixelId) {
104
+ // ---------------------------------------------------------------------------
105
+ // Browser-specific: fire an event to the correct global pixel function
106
+ // ---------------------------------------------------------------------------
107
+ /* eslint-disable @typescript-eslint/no-explicit-any */
108
+ function fire(provider, name, params) {
251
109
  if (typeof window === 'undefined')
252
110
  return;
253
- if (window.fbq)
254
- return;
255
- // Standard Facebook Pixel bootstrap
256
- (function (f, b, e) {
257
- if (f.fbq)
258
- return;
259
- const n = function (...args) {
260
- if (n.callMethod) {
261
- n.callMethod(...args);
111
+ const w = window;
112
+ switch (provider) {
113
+ case 'facebook':
114
+ w.fbq?.('track', name, params);
115
+ break;
116
+ case 'tiktok':
117
+ // TikTok handles page views via ttq.page(), not ttq.track('Pageview')
118
+ if (name === 'Pageview') {
119
+ w.ttq?.page?.();
262
120
  }
263
121
  else {
264
- n.queue.push(args);
122
+ w.ttq?.track?.(name, params);
265
123
  }
266
- };
267
- f.fbq = n;
268
- if (!f._fbq)
269
- f._fbq = n;
270
- n.queue = n.queue ?? [];
271
- n.loaded = true;
272
- n.version = '2.0';
273
- n.queue = [];
274
- const t = b.createElement(e);
275
- t.async = true;
276
- t.src = 'https://connect.facebook.net/en_US/fbevents.js';
277
- const s = b.getElementsByTagName(e)[0];
278
- s.parentNode?.insertBefore(t, s);
279
- })(window, document, 'script');
280
- const fbq = window.fbq;
281
- if (fbq) {
282
- fbq('init', pixelId);
124
+ break;
125
+ case 'snapchat':
126
+ w.snaptr?.('track', name, params);
127
+ break;
128
+ case 'pinterest':
129
+ // Pinterest handles page views via pintrk('page'), not pintrk('track', 'pagevisit')
130
+ if (name === 'pagevisit') {
131
+ w.pintrk?.('page');
132
+ }
133
+ else {
134
+ w.pintrk?.('track', name, params);
135
+ }
136
+ break;
137
+ case 'gtm':
138
+ fireGTM(name, params);
139
+ break;
283
140
  }
284
141
  }
285
- function trackMetaEvent(name, params) {
286
- if (typeof window === 'undefined' || !window.fbq)
142
+ function fireGTM(event, params) {
143
+ try {
144
+ const w = window;
145
+ if (w.gtag) {
146
+ w.gtag('event', event, params);
147
+ }
148
+ else if (w.dataLayer) {
149
+ w.dataLayer.push({ event, ...params });
150
+ }
151
+ }
152
+ catch (error) {
153
+ console.error('[SDK GTM] Error:', error);
154
+ }
155
+ }
156
+ /* eslint-enable @typescript-eslint/no-explicit-any */
157
+ // ===========================================================================
158
+ // Pixel initialization (browser-only)
159
+ // All pixel globals are accessed via `win` typed as `any` to avoid
160
+ // `declare global` augmentation issues across tsconfig scopes.
161
+ // ===========================================================================
162
+ /* eslint-disable @typescript-eslint/no-explicit-any */
163
+ const win = (typeof window !== 'undefined' ? window : undefined);
164
+ function initMetaPixel(pixelId) {
165
+ if (!win || win.fbq)
287
166
  return;
288
- const fbq = window.fbq;
289
- fbq?.('track', name, params);
167
+ const n = function (...args) {
168
+ if (n.callMethod)
169
+ n.callMethod(...args);
170
+ else
171
+ n.queue.push(args);
172
+ };
173
+ n.queue = [];
174
+ n.loaded = true;
175
+ n.version = '2.0';
176
+ win.fbq = n;
177
+ if (!win._fbq)
178
+ win._fbq = n;
179
+ const t = document.createElement('script');
180
+ t.async = true;
181
+ t.src = 'https://connect.facebook.net/en_US/fbevents.js';
182
+ const s = document.getElementsByTagName('script')[0];
183
+ s?.parentNode?.insertBefore(t, s);
184
+ win.fbq('init', pixelId);
290
185
  }
291
186
  function initTikTokPixel(pixelId) {
292
- if (typeof window === 'undefined')
187
+ if (!win)
293
188
  return;
294
- if (window.ttq)
295
- return;
296
- (function (w, d, t) {
297
- const ttq = w.ttq ||
298
- function (...args) {
299
- ttq.queue.push(args);
300
- };
301
- if (!ttq.queue) {
302
- ttq.queue = [];
303
- }
304
- w.ttq = ttq;
189
+ // Initialize TikTok base code once (mirrors official TikTok snippet)
190
+ if (!win.ttq) {
191
+ win.TiktokAnalyticsObject = 'ttq';
192
+ const ttq = (win.ttq = win.ttq || []);
305
193
  ttq.methods = [
306
- 'page',
307
- 'track',
308
- 'identify',
309
- 'instances',
310
- 'debug',
311
- 'on',
312
- 'off',
313
- 'once',
314
- 'ready',
315
- 'alias',
316
- 'group',
317
- 'enableCookie',
318
- 'disableCookie',
194
+ 'page', 'track', 'identify', 'instances', 'debug', 'on', 'off',
195
+ 'once', 'ready', 'alias', 'group', 'enableCookie', 'disableCookie',
196
+ 'holdConsent', 'revokeConsent', 'grantConsent',
319
197
  ];
320
- ttq.setAndDefer = function (target, method) {
321
- target[method] = function (...args) {
322
- target.queue.push([method, ...args]);
323
- };
198
+ ttq.setAndDefer = function (t, e) {
199
+ t[e] = function (...args) { t.push([e, ...args]); };
324
200
  };
325
201
  for (const method of ttq.methods) {
326
202
  ttq.setAndDefer(ttq, method);
327
203
  }
328
- ttq.load = function (e) {
329
- const n = 'https://analytics.tiktok.com/i18n/pixel/events.js';
330
- const a = d.createElement(t);
331
- a.type = 'text/javascript';
332
- a.async = true;
333
- a.src = n + '?sdkid=' + e + '&lib=ttq';
334
- const s = d.getElementsByTagName(t)[0];
335
- s.parentNode.insertBefore(a, s);
204
+ ttq.instance = function (t) {
205
+ const e = ttq._i[t] || [];
206
+ for (const method of ttq.methods) {
207
+ ttq.setAndDefer(e, method);
208
+ }
209
+ return e;
336
210
  };
337
- })(window, document, 'script');
338
- const ttqInstance = window.ttq;
339
- if (!ttqInstance)
340
- return;
341
- ttqInstance.load?.(pixelId);
342
- ttqInstance.page?.();
343
- }
344
- function trackTikTokEvent(name, params) {
345
- if (typeof window === 'undefined' || !window.ttq)
211
+ ttq.load = function (e, n) {
212
+ const r = 'https://analytics.tiktok.com/i18n/pixel/events.js';
213
+ ttq._i = ttq._i || {};
214
+ ttq._i[e] = [];
215
+ ttq._i[e]._u = r;
216
+ ttq._t = ttq._t || {};
217
+ ttq._t[e] = +new Date();
218
+ ttq._o = ttq._o || {};
219
+ ttq._o[e] = n || {};
220
+ const s = document.createElement('script');
221
+ s.type = 'text/javascript';
222
+ s.async = true;
223
+ s.src = r + '?sdkid=' + e + '&lib=ttq';
224
+ const p = document.getElementsByTagName('script')[0];
225
+ p?.parentNode?.insertBefore(s, p);
226
+ };
227
+ }
228
+ // Skip if this specific pixel ID is already loaded
229
+ if (win.ttq?._i?.[pixelId])
346
230
  return;
347
- const ttq = window.ttq;
348
- ttq?.track?.(name, params);
231
+ win.ttq.load(pixelId);
232
+ // Note: ttq.page() is NOT called here — the Provider's auto page-view
233
+ // effect fires track('PageView') which handles it. Calling both would
234
+ // double-count page views in TikTok Ads Manager.
349
235
  }
350
236
  function initSnapchatPixel(pixelId) {
351
- if (typeof window === 'undefined')
237
+ if (!win || win.snaptr)
352
238
  return;
353
- if (window.snaptr)
354
- return;
355
- (function (w, d, tagName) {
356
- if (w.snaptr)
357
- return;
358
- const a = (function (...args) {
359
- if (a.handleRequest) {
360
- a.handleRequest(...args);
361
- }
362
- else {
363
- a.queue.push(args);
364
- }
365
- });
366
- a.queue = [];
367
- w.snaptr = a;
368
- const s = 'script';
369
- const r = d.createElement(s);
370
- r.async = true;
371
- r.src = 'https://sc-static.net/scevent.min.js';
372
- const u = d.getElementsByTagName(tagName)[0];
373
- u.parentNode?.insertBefore(r, u);
374
- })(window, document, 'script');
375
- const snaptr = window.snaptr;
376
- snaptr?.('init', pixelId);
377
- }
378
- function trackSnapchatEvent(name, params) {
379
- if (typeof window === 'undefined' || !window.snaptr)
380
- return;
381
- const snaptr = window.snaptr;
382
- snaptr?.('track', name, params);
383
- }
384
- function initPinterestPixel(pixelId) {
385
- if (typeof window === 'undefined')
386
- return;
387
- if (window.pintrk)
388
- return;
389
- (function (w, d, tagName) {
390
- if (w.pintrk)
391
- return;
392
- const a = (function (...args) {
239
+ const a = function (...args) {
240
+ if (a.handleRequest)
241
+ a.handleRequest(...args);
242
+ else
393
243
  a.queue.push(args);
394
- });
395
- a.queue = [];
396
- w.pintrk = a;
397
- const s = d.createElement(tagName);
398
- s.async = true;
399
- s.src = 'https://s.pinimg.com/ct/core.js';
400
- const u = d.getElementsByTagName(tagName)[0];
401
- u.parentNode?.insertBefore(s, u);
402
- })(window, document, 'script');
403
- const pintrk = window.pintrk;
404
- pintrk?.('load', pixelId);
405
- pintrk?.('page');
244
+ };
245
+ a.queue = [];
246
+ win.snaptr = a;
247
+ const r = document.createElement('script');
248
+ r.async = true;
249
+ r.src = 'https://sc-static.net/scevent.min.js';
250
+ const u = document.getElementsByTagName('script')[0];
251
+ u?.parentNode?.insertBefore(r, u);
252
+ win.snaptr('init', pixelId);
406
253
  }
407
- function trackPinterestEvent(name, params) {
408
- if (typeof window === 'undefined' || !window.pintrk)
254
+ function initPinterestPixel(pixelId) {
255
+ if (!win || win.pintrk)
409
256
  return;
410
- const pintrk = window.pintrk;
411
- pintrk?.('track', name, params);
257
+ const a = function (...args) { a.queue.push(args); };
258
+ a.queue = [];
259
+ win.pintrk = a;
260
+ const s = document.createElement('script');
261
+ s.async = true;
262
+ s.src = 'https://s.pinimg.com/ct/core.js';
263
+ const u = document.getElementsByTagName('script')[0];
264
+ u?.parentNode?.insertBefore(s, u);
265
+ win.pintrk('load', pixelId);
266
+ // Note: pintrk('page') is NOT called here — the Provider's auto page-view
267
+ // effect handles it via fire(). Calling both would double-count page views.
412
268
  }
413
269
  function initGTM(containerId) {
414
- if (typeof window === 'undefined' || typeof document === 'undefined')
270
+ if (!win || !containerId)
415
271
  return;
416
- if (!containerId)
272
+ const isGtmContainer = containerId.startsWith('GTM-');
273
+ const scriptUrlPart = isGtmContainer
274
+ ? `googletagmanager.com/gtm.js?id=${containerId}`
275
+ : `googletagmanager.com/gtag/js?id=${containerId}`;
276
+ if (document.querySelector(`script[src*="${scriptUrlPart}"]`))
417
277
  return;
418
- // Check if GTM script is already loaded for this container
419
- const existingScript = document.querySelector(`script[src*="googletagmanager.com/gtm.js?id=${containerId}"]`);
420
- if (existingScript) {
421
- return; // GTM already initialized for this container
422
- }
423
- // Initialize dataLayer before GTM script (Google's official pattern)
424
- window.dataLayer = window.dataLayer || [];
425
- // Push gtm.start event (must be done before script loads)
426
- window.dataLayer.push({
427
- 'gtm.start': new Date().getTime(),
428
- event: 'gtm.js',
429
- });
430
- // Create and inject GTM script (Google's official pattern)
431
- // This matches Google's exact implementation from their documentation
432
- (function (w, d, s, l, i) {
433
- const f = d.getElementsByTagName(s)[0];
434
- const j = d.createElement(s);
435
- const dl = l != 'dataLayer' ? '&l=' + l : '';
278
+ win.dataLayer = win.dataLayer || [];
279
+ if (isGtmContainer) {
280
+ win.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
281
+ const f = document.getElementsByTagName('script')[0];
282
+ const j = document.createElement('script');
436
283
  j.async = true;
437
- j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
438
- // Insert before first script tag (typically in head)
439
- if (f && f.parentNode) {
284
+ j.src = 'https://www.googletagmanager.com/gtm.js?id=' + containerId;
285
+ if (f?.parentNode)
440
286
  f.parentNode.insertBefore(j, f);
441
- }
442
- else {
443
- // Fallback: append to head if no script tags found
444
- const head = d.head || d.getElementsByTagName('head')[0];
445
- if (head) {
446
- head.appendChild(j);
447
- }
448
- }
449
- })(window, document, 'script', 'dataLayer', containerId);
450
- // Also add noscript fallback in body (Google's official pattern)
451
- // This ensures GTM works even if JavaScript is disabled
452
- const noscript = document.createElement('noscript');
453
- const iframe = document.createElement('iframe');
454
- iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
455
- iframe.height = '0';
456
- iframe.width = '0';
457
- iframe.style.display = 'none';
458
- iframe.style.visibility = 'hidden';
459
- noscript.appendChild(iframe);
460
- // Insert noscript at the beginning of body
461
- const body = document.body || document.getElementsByTagName('body')[0];
462
- if (body) {
463
- body.insertBefore(noscript, body.firstChild);
464
- }
465
- }
466
- function trackGTMEvent(event, params = {}) {
467
- if (typeof window === 'undefined') {
468
- console.warn('[SDK GTM] Window not available, event not tracked:', event);
469
- return;
470
- }
471
- // Ensure dataLayer exists (should be initialized by initGTM, but double-check)
472
- if (!window.dataLayer) {
473
- console.warn('[SDK GTM] dataLayer not initialized, initializing now...');
474
- window.dataLayer = [];
475
- }
476
- try {
477
- const eventData = {
478
- event,
479
- ...params,
480
- };
481
- console.log('[SDK GTM] Pushing event to dataLayer:', eventData);
482
- window.dataLayer.push(eventData);
483
- console.log('[SDK GTM] Event pushed successfully. Current dataLayer length:', window.dataLayer.length);
484
- }
485
- catch (error) {
486
- if (typeof console !== 'undefined') {
487
- console.error('[SDK GTM] Error tracking event:', error);
488
- }
287
+ else
288
+ (document.head || document.getElementsByTagName('head')[0])?.appendChild(j);
289
+ const noscript = document.createElement('noscript');
290
+ const iframe = document.createElement('iframe');
291
+ iframe.src = `https://www.googletagmanager.com/ns.html?id=${containerId}`;
292
+ iframe.height = '0';
293
+ iframe.width = '0';
294
+ iframe.style.display = 'none';
295
+ iframe.style.visibility = 'hidden';
296
+ noscript.appendChild(iframe);
297
+ (document.body || document.getElementsByTagName('body')[0])?.insertBefore(noscript, document.body?.firstChild ?? null);
489
298
  }
490
- }
491
- // --- Basic event mapping (can be extended later if needed) ---
492
- function mapMetaEvent(eventName, parameters) {
493
- return { name: eventName, params: parameters };
494
- }
495
- function mapTikTokEvent(eventName, parameters) {
496
- // TikTok naming is usually aligned; adjust here if needed later
497
- return { name: eventName, params: parameters };
498
- }
499
- function mapSnapchatEvent(eventName, parameters) {
500
- return { name: eventName, params: parameters };
501
- }
502
- function mapPinterestEvent(eventName, parameters) {
503
- // Pinterest uses lowercase event names
504
- const pinterestEventMap = {
505
- PageView: 'pagevisit',
506
- ViewContent: 'viewcontent',
507
- AddToCart: 'addtocart',
508
- InitiateCheckout: 'initiatecheckout',
509
- AddPaymentInfo: 'addpaymentinfo',
510
- Purchase: 'checkout',
511
- Lead: 'lead',
512
- CompleteRegistration: 'signup',
513
- };
514
- const pinterestEventName = pinterestEventMap[eventName] ?? eventName.toLowerCase();
515
- // Transform parameters for Pinterest
516
- const pinterestParams = transformPinterestParameters(eventName, parameters);
517
- return { name: pinterestEventName, params: pinterestParams };
518
- }
519
- function transformPinterestParameters(eventName, parameters) {
520
- const pinParams = { ...parameters };
521
- // Pinterest uses 'value' and 'order_quantity' as primary event data
522
- // Ensure currency is uppercase
523
- if (pinParams.currency) {
524
- pinParams.currency = String(pinParams.currency).toUpperCase();
525
- }
526
- // Convert to major units if currency is present
527
- if (pinParams.currency && pinParams.value) {
528
- try {
529
- pinParams.value = minorUnitsToMajorUnits(Number(pinParams.value), String(pinParams.currency));
530
- }
531
- catch {
532
- // Fallback to simple division by 100
533
- pinParams.value = Number(pinParams.value) / 100;
534
- }
535
- }
536
- // Ensure numeric values
537
- if (pinParams.value !== undefined) {
538
- pinParams.value = Number(pinParams.value);
539
- }
540
- if (pinParams.order_quantity !== undefined) {
541
- pinParams.order_quantity = Number(pinParams.order_quantity);
542
- }
543
- // Transform contents to Pinterest format (product_id array)
544
- if (pinParams.contents && Array.isArray(pinParams.contents)) {
545
- pinParams.line_items = pinParams.contents.map((item) => ({
546
- product_id: item.content_id,
547
- product_name: item.content_name,
548
- product_category: item.content_category,
549
- product_price: item.price ? Number(item.price) : undefined,
550
- product_quantity: item.quantity ? Number(item.quantity) : undefined,
551
- }));
552
- }
553
- return pinParams;
554
- }
555
- function mapGTMEvent(eventName, parameters) {
556
- // Map standard event names to GTM event names
557
- const gtmEventMap = {
558
- PageView: 'page_view',
559
- AddPaymentInfo: 'add_payment_info',
560
- AddToCart: 'add_to_cart',
561
- InitiateCheckout: 'begin_checkout',
562
- Purchase: 'purchase',
563
- ViewContent: 'view_item',
564
- CompleteRegistration: 'sign_up',
565
- };
566
- const gtmEventName = gtmEventMap[eventName] ?? eventName.toLowerCase();
567
- // Transform parameters for GTM
568
- const gtmParams = transformGTMParameters(eventName, parameters);
569
- return { name: gtmEventName, params: gtmParams };
570
- }
571
- function transformGTMParameters(eventName, parameters) {
572
- const gtmParameters = { ...parameters };
573
- // Ensure currency is uppercase
574
- if (gtmParameters.currency) {
575
- gtmParameters.currency = String(gtmParameters.currency).toUpperCase();
576
- }
577
- // Transform currency values to major units if needed
578
- if (gtmParameters.currency && gtmParameters.value) {
579
- try {
580
- gtmParameters.value = minorUnitsToMajorUnits(Number(gtmParameters.value), String(gtmParameters.currency));
299
+ else {
300
+ if (!win.gtag) {
301
+ win.gtag = function (..._args) { win.dataLayer.push(arguments); };
581
302
  }
582
- catch (error) {
583
- // If conversion fails, try simple division by 100 as fallback
584
- if (typeof console !== 'undefined') {
585
- console.warn('[SDK GTM] Currency conversion failed, using fallback:', error);
586
- }
587
- gtmParameters.value = Number(gtmParameters.value) / 100;
588
- }
589
- }
590
- // Ensure numeric values
591
- if (gtmParameters.value !== undefined) {
592
- gtmParameters.value = Number(gtmParameters.value);
593
- }
594
- if (gtmParameters.num_items !== undefined) {
595
- gtmParameters.num_items = Number(gtmParameters.num_items);
596
- }
597
- // Transform contents to a format better suited for GTM
598
- if (gtmParameters.contents && Array.isArray(gtmParameters.contents)) {
599
- gtmParameters.items = gtmParameters.contents.map((item) => ({
600
- item_id: item.content_id,
601
- item_name: item.content_name,
602
- item_category: item.content_category,
603
- price: item.price ? Number(item.price) : undefined,
604
- quantity: item.quantity ? Number(item.quantity) : undefined,
605
- }));
606
- // Keep original contents for compatibility
607
- gtmParameters.contents = gtmParameters.contents.map((item) => ({
608
- id: item.content_id,
609
- name: item.content_name,
610
- category: item.content_category,
611
- price: item.price ? Number(item.price) : undefined,
612
- quantity: item.quantity ? Number(item.quantity) : undefined,
613
- }));
303
+ win.gtag('js', new Date());
304
+ win.gtag('config', containerId);
305
+ const script = document.createElement('script');
306
+ script.async = true;
307
+ script.src = 'https://www.googletagmanager.com/gtag/js?id=' + containerId;
308
+ const firstScript = document.getElementsByTagName('script')[0];
309
+ if (firstScript?.parentNode)
310
+ firstScript.parentNode.insertBefore(script, firstScript);
311
+ else
312
+ (document.head || document.getElementsByTagName('head')[0])?.appendChild(script);
614
313
  }
615
- return gtmParameters;
616
314
  }