@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
|
@@ -34,6 +34,8 @@ const STORAGE_KEYS = {
|
|
|
34
34
|
FORCE_RESET: 'tgd_force_reset',
|
|
35
35
|
CLIENT_ENV: 'tgd_client_env',
|
|
36
36
|
CLIENT_BASE_URL: 'tgd_client_base_url',
|
|
37
|
+
CURRENCY: 'tgd_currency',
|
|
38
|
+
LOCALE: 'tgd_locale',
|
|
37
39
|
};
|
|
38
40
|
/**
|
|
39
41
|
* Check if force reset is active (simulates hard refresh)
|
|
@@ -82,10 +84,16 @@ function getFromCookie(key) {
|
|
|
82
84
|
}
|
|
83
85
|
/**
|
|
84
86
|
* Set value in cookie
|
|
87
|
+
* ⚠️ NEVER allows tgd_draft to be set as a cookie (localStorage-only)
|
|
85
88
|
*/
|
|
86
89
|
function setInCookie(key, value, maxAge = 86400) {
|
|
87
90
|
if (typeof document === 'undefined')
|
|
88
91
|
return;
|
|
92
|
+
// 🛡️ SAFETY: Block tgd_draft from EVER being written to cookies
|
|
93
|
+
if (key === 'tgd_draft' || key === 'tgd-draft') {
|
|
94
|
+
console.warn(`🛡️ [SDK] Blocked attempt to set ${key} as cookie - this should only be in localStorage`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
89
97
|
document.cookie = `${key}=${value}; path=/; max-age=${maxAge}`;
|
|
90
98
|
}
|
|
91
99
|
/**
|
|
@@ -105,14 +113,14 @@ export function getSDKParams() {
|
|
|
105
113
|
return {};
|
|
106
114
|
}
|
|
107
115
|
const urlParams = new URLSearchParams(window.location.search);
|
|
108
|
-
// Get draft mode (URL > localStorage
|
|
116
|
+
// Get draft mode (URL > localStorage only - no cookie fallback to avoid resurrection)
|
|
109
117
|
let draft;
|
|
110
118
|
const urlDraft = urlParams.get('draft');
|
|
111
119
|
if (urlDraft !== null) {
|
|
112
120
|
draft = urlDraft === 'true';
|
|
113
121
|
}
|
|
114
122
|
else {
|
|
115
|
-
const storageDraft = getFromStorage(STORAGE_KEYS.DRAFT)
|
|
123
|
+
const storageDraft = getFromStorage(STORAGE_KEYS.DRAFT);
|
|
116
124
|
if (storageDraft !== null) {
|
|
117
125
|
draft = storageDraft === 'true';
|
|
118
126
|
}
|
|
@@ -135,6 +143,12 @@ export function getSDKParams() {
|
|
|
135
143
|
const funnelSessionId = urlParams.get('funnelSessionId') || null;
|
|
136
144
|
// Get funnel ID (URL only)
|
|
137
145
|
const funnelId = urlParams.get('funnelId') || null;
|
|
146
|
+
// Get funnel environment (URL only - not persisted, set on entry)
|
|
147
|
+
let funnelEnv;
|
|
148
|
+
const urlFunnelEnv = urlParams.get('funnelEnv');
|
|
149
|
+
if (urlFunnelEnv && (urlFunnelEnv === 'staging' || urlFunnelEnv === 'production')) {
|
|
150
|
+
funnelEnv = urlFunnelEnv;
|
|
151
|
+
}
|
|
138
152
|
// Force reset is URL-only (not persisted)
|
|
139
153
|
const forceReset = urlParams.get('forceReset') === 'true';
|
|
140
154
|
// Get client environment override (URL > localStorage > cookie)
|
|
@@ -161,6 +175,30 @@ export function getSDKParams() {
|
|
|
161
175
|
tagadaClientBaseUrl = storageBaseUrl;
|
|
162
176
|
}
|
|
163
177
|
}
|
|
178
|
+
// Get display currency (URL > localStorage > cookie)
|
|
179
|
+
let currency;
|
|
180
|
+
const urlCurrency = urlParams.get('currency');
|
|
181
|
+
if (urlCurrency) {
|
|
182
|
+
currency = urlCurrency;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const storageCurrency = getFromStorage(STORAGE_KEYS.CURRENCY) || getFromCookie(STORAGE_KEYS.CURRENCY);
|
|
186
|
+
if (storageCurrency) {
|
|
187
|
+
currency = storageCurrency;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Get display locale (URL > localStorage > cookie)
|
|
191
|
+
let locale;
|
|
192
|
+
const urlLocale = urlParams.get('locale');
|
|
193
|
+
if (urlLocale) {
|
|
194
|
+
locale = urlLocale;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const storageLocale = getFromStorage(STORAGE_KEYS.LOCALE) || getFromCookie(STORAGE_KEYS.LOCALE);
|
|
198
|
+
if (storageLocale) {
|
|
199
|
+
locale = storageLocale;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
164
202
|
return {
|
|
165
203
|
forceReset,
|
|
166
204
|
token,
|
|
@@ -168,8 +206,11 @@ export function getSDKParams() {
|
|
|
168
206
|
funnelId,
|
|
169
207
|
draft,
|
|
170
208
|
funnelTracking,
|
|
209
|
+
funnelEnv,
|
|
171
210
|
tagadaClientEnv,
|
|
172
211
|
tagadaClientBaseUrl,
|
|
212
|
+
currency,
|
|
213
|
+
locale,
|
|
173
214
|
};
|
|
174
215
|
}
|
|
175
216
|
/**
|
|
@@ -189,17 +230,21 @@ export function isDraftMode() {
|
|
|
189
230
|
}
|
|
190
231
|
/**
|
|
191
232
|
* Set draft mode in storage for persistence
|
|
233
|
+
* ⚠️ ONLY writes to localStorage (not cookies) to avoid resurrection issues
|
|
192
234
|
*/
|
|
193
235
|
export function setDraftMode(draft) {
|
|
194
236
|
if (typeof window === 'undefined')
|
|
195
237
|
return;
|
|
196
238
|
if (draft) {
|
|
197
239
|
setInStorage(STORAGE_KEYS.DRAFT, 'true');
|
|
198
|
-
setInCookie(STORAGE_KEYS.DRAFT, 'true', 86400); // 24 hours
|
|
199
240
|
}
|
|
200
241
|
else {
|
|
201
|
-
|
|
202
|
-
|
|
242
|
+
try {
|
|
243
|
+
localStorage.removeItem(STORAGE_KEYS.DRAFT);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// Storage not available
|
|
247
|
+
}
|
|
203
248
|
}
|
|
204
249
|
}
|
|
205
250
|
/**
|
|
@@ -214,6 +259,13 @@ export function setDraftMode(draft) {
|
|
|
214
259
|
* @returns True if force reset was activated and state was cleared
|
|
215
260
|
*/
|
|
216
261
|
export function handlePreviewMode(debugMode = false) {
|
|
262
|
+
// 🔒 CRITICAL: Read URL token DIRECTLY first before any getSDKParams()
|
|
263
|
+
// This ensures we have the URL token before any clearing happens
|
|
264
|
+
let urlToken = null;
|
|
265
|
+
if (typeof window !== 'undefined') {
|
|
266
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
267
|
+
urlToken = urlParams.get('token');
|
|
268
|
+
}
|
|
217
269
|
const params = getSDKParams();
|
|
218
270
|
const shouldReset = params.forceReset || false;
|
|
219
271
|
if (!shouldReset && !params.token) {
|
|
@@ -224,6 +276,7 @@ export function handlePreviewMode(debugMode = false) {
|
|
|
224
276
|
}
|
|
225
277
|
if (debugMode) {
|
|
226
278
|
console.log('[SDK] Detected params:', params);
|
|
279
|
+
console.log('[SDK] URL token (direct read):', urlToken ? urlToken.substring(0, 20) + '...' : 'none');
|
|
227
280
|
}
|
|
228
281
|
// CASE 1: Force reset - clear all state (simulates hard refresh)
|
|
229
282
|
if (shouldReset) {
|
|
@@ -246,23 +299,28 @@ export function handlePreviewMode(debugMode = false) {
|
|
|
246
299
|
}
|
|
247
300
|
}
|
|
248
301
|
// CASE 2: Token in URL - override stored token
|
|
249
|
-
|
|
302
|
+
// 🔒 CRITICAL: Use the directly-read URL token to avoid any race conditions
|
|
303
|
+
const tokenToSet = urlToken || params.token;
|
|
304
|
+
if (tokenToSet !== null && tokenToSet !== undefined) {
|
|
250
305
|
if (debugMode) {
|
|
251
|
-
console.log('[SDK]
|
|
306
|
+
console.log('[SDK] Setting token from URL:', tokenToSet.substring(0, 20) + '...');
|
|
252
307
|
}
|
|
253
|
-
if (
|
|
308
|
+
if (tokenToSet === '' || tokenToSet === 'null') {
|
|
254
309
|
// Explicitly cleared token
|
|
255
310
|
clearClientToken();
|
|
256
311
|
}
|
|
257
312
|
else {
|
|
258
|
-
// Set token from URL
|
|
259
|
-
setClientToken(
|
|
313
|
+
// Set token from URL IMMEDIATELY after clearing
|
|
314
|
+
setClientToken(tokenToSet);
|
|
315
|
+
if (debugMode) {
|
|
316
|
+
console.log('[SDK] ✅ Token set in localStorage immediately after clear');
|
|
317
|
+
}
|
|
260
318
|
}
|
|
261
319
|
}
|
|
262
320
|
else if (shouldReset) {
|
|
263
321
|
// Force reset but no token = clear stored token
|
|
264
322
|
if (debugMode) {
|
|
265
|
-
console.log('[SDK] Force reset mode (no token)');
|
|
323
|
+
console.log('[SDK] Force reset mode (no token in URL)');
|
|
266
324
|
}
|
|
267
325
|
clearClientToken();
|
|
268
326
|
}
|
|
@@ -311,7 +369,7 @@ export function setFunnelTracking(enabled) {
|
|
|
311
369
|
return;
|
|
312
370
|
const value = enabled ? 'true' : 'false';
|
|
313
371
|
setInStorage(STORAGE_KEYS.FUNNEL_TRACKING, value);
|
|
314
|
-
setInCookie(STORAGE_KEYS.FUNNEL_TRACKING, value, 86400);
|
|
372
|
+
setInCookie(STORAGE_KEYS.FUNNEL_TRACKING, value, 86400);
|
|
315
373
|
}
|
|
316
374
|
/**
|
|
317
375
|
* Set client environment override in storage for persistence
|
|
@@ -320,7 +378,7 @@ export function setClientEnvironment(env) {
|
|
|
320
378
|
if (typeof window === 'undefined')
|
|
321
379
|
return;
|
|
322
380
|
setInStorage(STORAGE_KEYS.CLIENT_ENV, env);
|
|
323
|
-
setInCookie(STORAGE_KEYS.CLIENT_ENV, env, 86400);
|
|
381
|
+
setInCookie(STORAGE_KEYS.CLIENT_ENV, env, 86400);
|
|
324
382
|
}
|
|
325
383
|
/**
|
|
326
384
|
* Clear client environment override
|
|
@@ -343,7 +401,7 @@ export function setClientBaseUrl(baseUrl) {
|
|
|
343
401
|
if (typeof window === 'undefined')
|
|
344
402
|
return;
|
|
345
403
|
setInStorage(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl);
|
|
346
|
-
setInCookie(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl, 86400);
|
|
404
|
+
setInCookie(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl, 86400);
|
|
347
405
|
}
|
|
348
406
|
/**
|
|
349
407
|
* Clear custom API base URL override
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Mode Indicator - Auto-injected DOM element
|
|
3
|
+
*
|
|
4
|
+
* Automatically injects a visual indicator when SDK is in preview/draft mode
|
|
5
|
+
* Works with vanilla JS/DOM - no React required
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Inject the preview mode indicator into the DOM
|
|
9
|
+
* Called automatically during SDK initialization if preview mode is active
|
|
10
|
+
*/
|
|
11
|
+
export declare function injectPreviewModeIndicator(): void;
|
|
12
|
+
/**
|
|
13
|
+
* Remove the preview mode indicator from the DOM
|
|
14
|
+
*/
|
|
15
|
+
export declare function removePreviewModeIndicator(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Check if indicator is currently injected
|
|
18
|
+
*/
|
|
19
|
+
export declare function isIndicatorInjected(): boolean;
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview Mode Indicator - Auto-injected DOM element
|
|
3
|
+
*
|
|
4
|
+
* Automatically injects a visual indicator when SDK is in preview/draft mode
|
|
5
|
+
* Works with vanilla JS/DOM - no React required
|
|
6
|
+
*/
|
|
7
|
+
import { isDraftMode, isFunnelTrackingEnabled, getSDKParams, clearClientEnvironment, clearClientBaseUrl } from './previewMode';
|
|
8
|
+
import { clearClientToken } from './tokenStorage';
|
|
9
|
+
import { clearFunnelSessionCookie } from './sessionStorage';
|
|
10
|
+
let indicatorElement = null;
|
|
11
|
+
let isInjected = false;
|
|
12
|
+
/**
|
|
13
|
+
* Helper to clear a specific cookie with all possible domain/path combinations
|
|
14
|
+
*/
|
|
15
|
+
function clearSpecificCookie(cookieName) {
|
|
16
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
17
|
+
return;
|
|
18
|
+
const hostname = window.location.hostname;
|
|
19
|
+
const parts = hostname.split('.');
|
|
20
|
+
// All possible domains
|
|
21
|
+
const domains = [
|
|
22
|
+
'', // No domain (current)
|
|
23
|
+
hostname, // Full hostname
|
|
24
|
+
'.' + hostname, // Wildcard current
|
|
25
|
+
];
|
|
26
|
+
// Add parent domains
|
|
27
|
+
for (let i = 1; i < parts.length; i++) {
|
|
28
|
+
const domain = parts.slice(i).join('.');
|
|
29
|
+
domains.push(domain);
|
|
30
|
+
domains.push('.' + domain);
|
|
31
|
+
}
|
|
32
|
+
// All possible paths
|
|
33
|
+
const pathParts = window.location.pathname.split('/').filter(p => p);
|
|
34
|
+
const paths = ['/'];
|
|
35
|
+
let currentPath = '';
|
|
36
|
+
pathParts.forEach(part => {
|
|
37
|
+
currentPath += '/' + part;
|
|
38
|
+
paths.push(currentPath);
|
|
39
|
+
});
|
|
40
|
+
// Try all combinations
|
|
41
|
+
domains.forEach(domain => {
|
|
42
|
+
paths.forEach(path => {
|
|
43
|
+
const baseDelete = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
|
|
44
|
+
const domainPart = domain ? `; domain=${domain}` : '';
|
|
45
|
+
// Try all security flag variations
|
|
46
|
+
document.cookie = baseDelete + domainPart;
|
|
47
|
+
document.cookie = baseDelete + domainPart + '; secure';
|
|
48
|
+
document.cookie = baseDelete + domainPart + '; SameSite=None; secure';
|
|
49
|
+
document.cookie = baseDelete + domainPart + '; SameSite=Lax';
|
|
50
|
+
document.cookie = baseDelete + domainPart + '; SameSite=Strict';
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Leave preview mode - Clear ALL storage (including Shopify cookies, all localStorage, etc.) and reload
|
|
56
|
+
* ⚠️ Nuclear option - clears everything to exit preview mode completely
|
|
57
|
+
*/
|
|
58
|
+
function leavePreviewMode() {
|
|
59
|
+
if (typeof window === 'undefined')
|
|
60
|
+
return;
|
|
61
|
+
const confirmed = confirm('🚪 Leave Preview Mode?\n\nThis will clear ALL cookies and localStorage (including Shopify session, cart, and preview settings) and reload the page.\n\nAre you sure?');
|
|
62
|
+
if (!confirmed)
|
|
63
|
+
return;
|
|
64
|
+
// STEP 0: Remove ALL preview params from URL immediately
|
|
65
|
+
const url = new URL(window.location.href);
|
|
66
|
+
const previewParams = [
|
|
67
|
+
'draft', 'funnelEnv', 'funnelTracking', 'tagadaClientEnv',
|
|
68
|
+
'tagadaClientBaseUrl', 'forceReset', 'funnelId', 'funnelSessionId', 'token',
|
|
69
|
+
];
|
|
70
|
+
previewParams.forEach(param => url.searchParams.delete(param));
|
|
71
|
+
window.history.replaceState({}, '', url.href);
|
|
72
|
+
// STEP 0.5: Install global cookie blocker for tgd_draft
|
|
73
|
+
try {
|
|
74
|
+
const cookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') ||
|
|
75
|
+
Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie');
|
|
76
|
+
if (cookieDescriptor && cookieDescriptor.set) {
|
|
77
|
+
Object.defineProperty(document, 'cookie', {
|
|
78
|
+
configurable: true,
|
|
79
|
+
enumerable: true,
|
|
80
|
+
get: function () { return cookieDescriptor.get ? cookieDescriptor.get.call(this) : ''; },
|
|
81
|
+
set: function (val) {
|
|
82
|
+
if (typeof val === 'string') {
|
|
83
|
+
const normalizedVal = val.toLowerCase();
|
|
84
|
+
if (normalizedVal.includes('tgd_draft') ||
|
|
85
|
+
normalizedVal.includes('tgd-draft') ||
|
|
86
|
+
normalizedVal.includes('tgd.draft') ||
|
|
87
|
+
normalizedVal.includes('tgddraft')) {
|
|
88
|
+
// Allow deletion attempts
|
|
89
|
+
if (normalizedVal.includes('max-age=0') ||
|
|
90
|
+
normalizedVal.includes('expires=thu, 01 jan 1970')) {
|
|
91
|
+
if (cookieDescriptor.set) {
|
|
92
|
+
cookieDescriptor.set.call(this, val);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Block all creation/update attempts
|
|
97
|
+
console.warn('🛡️ [TagadaPay] BLOCKED: tgd_draft should never be a cookie');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (cookieDescriptor.set) {
|
|
102
|
+
cookieDescriptor.set.call(this, val);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
console.warn('[TagadaPay] Could not install cookie blocker:', e);
|
|
110
|
+
}
|
|
111
|
+
// STEP 1: Clear TagadaPay localStorage keys
|
|
112
|
+
if (window.localStorage) {
|
|
113
|
+
try {
|
|
114
|
+
localStorage.removeItem('tgd_draft');
|
|
115
|
+
localStorage.removeItem('tgd_funnel_tracking');
|
|
116
|
+
localStorage.removeItem('tgd_client_env');
|
|
117
|
+
localStorage.removeItem('tgd_client_base_url');
|
|
118
|
+
localStorage.removeItem('cms_token');
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
console.warn('[TagadaPay] Failed to clear some localStorage keys:', e);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Clear SDK utilities
|
|
125
|
+
clearClientToken();
|
|
126
|
+
clearFunnelSessionCookie();
|
|
127
|
+
clearClientEnvironment();
|
|
128
|
+
clearClientBaseUrl();
|
|
129
|
+
// STEP 2: Clear ALL storage
|
|
130
|
+
if (window.localStorage) {
|
|
131
|
+
localStorage.clear();
|
|
132
|
+
}
|
|
133
|
+
if (window.sessionStorage) {
|
|
134
|
+
sessionStorage.clear();
|
|
135
|
+
}
|
|
136
|
+
// STEP 3: Clear known TagadaPay cookies
|
|
137
|
+
const tagadaCookies = [
|
|
138
|
+
'tgd-funnel-session-id', 'tgd_draft', 'tgd_funnel_tracking',
|
|
139
|
+
'tgd_client_env', 'tgd_client_base_url', 'cms_token', 'tagadapay_session',
|
|
140
|
+
];
|
|
141
|
+
tagadaCookies.forEach(cookieName => clearSpecificCookie(cookieName));
|
|
142
|
+
// STEP 3.5: Clear alternative cookie name formats
|
|
143
|
+
const alternativeNames = ['tgd-draft', 'tgd_draft', 'tgd.draft', 'tgddraft'];
|
|
144
|
+
alternativeNames.forEach(cookieName => clearSpecificCookie(cookieName));
|
|
145
|
+
// STEP 4: Clear ALL remaining cookies aggressively
|
|
146
|
+
if (document.cookie) {
|
|
147
|
+
const cookies = document.cookie.split(';');
|
|
148
|
+
cookies.forEach(cookie => {
|
|
149
|
+
const cookieName = cookie.split('=')[0].trim();
|
|
150
|
+
if (!cookieName)
|
|
151
|
+
return;
|
|
152
|
+
const hostname = window.location.hostname;
|
|
153
|
+
const parts = hostname.split('.');
|
|
154
|
+
const domains = ['', hostname, '.' + hostname];
|
|
155
|
+
for (let i = 1; i < parts.length; i++) {
|
|
156
|
+
const domain = parts.slice(i).join('.');
|
|
157
|
+
domains.push(domain);
|
|
158
|
+
domains.push('.' + domain);
|
|
159
|
+
}
|
|
160
|
+
const pathParts = window.location.pathname.split('/').filter(p => p);
|
|
161
|
+
const paths = ['/'];
|
|
162
|
+
let currentPath = '';
|
|
163
|
+
pathParts.forEach(part => {
|
|
164
|
+
currentPath += '/' + part;
|
|
165
|
+
paths.push(currentPath);
|
|
166
|
+
});
|
|
167
|
+
domains.forEach(domain => {
|
|
168
|
+
paths.forEach(path => {
|
|
169
|
+
const baseDelete = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
|
|
170
|
+
const domainPart = domain ? `; domain=${domain}` : '';
|
|
171
|
+
document.cookie = baseDelete + domainPart;
|
|
172
|
+
document.cookie = baseDelete + domainPart + '; secure';
|
|
173
|
+
document.cookie = baseDelete + domainPart + '; SameSite=None; secure';
|
|
174
|
+
document.cookie = baseDelete + domainPart + '; SameSite=Lax';
|
|
175
|
+
document.cookie = baseDelete + domainPart + '; SameSite=Strict';
|
|
176
|
+
document.cookie = baseDelete + domainPart + '; secure; SameSite=None';
|
|
177
|
+
document.cookie = baseDelete + domainPart + '; secure; SameSite=Lax';
|
|
178
|
+
document.cookie = baseDelete + domainPart + '; secure; SameSite=Strict';
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Final cleanup and reload
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
if (window.localStorage && localStorage.length > 0) {
|
|
186
|
+
localStorage.clear();
|
|
187
|
+
}
|
|
188
|
+
clearSpecificCookie('tgd_draft');
|
|
189
|
+
if (typeof window.stop === 'function') {
|
|
190
|
+
window.stop();
|
|
191
|
+
}
|
|
192
|
+
window.location.replace(url.href);
|
|
193
|
+
}, 10);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Inject the preview mode indicator into the DOM
|
|
197
|
+
* Called automatically during SDK initialization if preview mode is active
|
|
198
|
+
*/
|
|
199
|
+
export function injectPreviewModeIndicator() {
|
|
200
|
+
// Skip if already injected or not in browser
|
|
201
|
+
if (isInjected || typeof window === 'undefined' || typeof document === 'undefined') {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const params = getSDKParams();
|
|
205
|
+
const draftMode = isDraftMode();
|
|
206
|
+
const trackingDisabled = !isFunnelTrackingEnabled();
|
|
207
|
+
const hasCustomEnv = !!(params.tagadaClientEnv || params.tagadaClientBaseUrl);
|
|
208
|
+
// Only inject if in preview/dev mode
|
|
209
|
+
if (!draftMode && !trackingDisabled && !hasCustomEnv) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// Create container
|
|
213
|
+
const container = document.createElement('div');
|
|
214
|
+
container.id = 'tgd-preview-indicator';
|
|
215
|
+
container.style.cssText = `
|
|
216
|
+
position: fixed;
|
|
217
|
+
bottom: 16px;
|
|
218
|
+
right: 16px;
|
|
219
|
+
z-index: 999999;
|
|
220
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
221
|
+
`;
|
|
222
|
+
// Create badge
|
|
223
|
+
const badge = document.createElement('div');
|
|
224
|
+
badge.style.cssText = `
|
|
225
|
+
background: ${draftMode ? '#ff9500' : '#007aff'};
|
|
226
|
+
color: white;
|
|
227
|
+
padding: 8px 12px;
|
|
228
|
+
border-radius: 8px;
|
|
229
|
+
font-size: 13px;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
232
|
+
cursor: pointer;
|
|
233
|
+
transition: all 0.2s ease;
|
|
234
|
+
display: flex;
|
|
235
|
+
align-items: center;
|
|
236
|
+
gap: 6px;
|
|
237
|
+
`;
|
|
238
|
+
badge.innerHTML = `
|
|
239
|
+
<span style="font-size: 16px;">🔍</span>
|
|
240
|
+
<span>${draftMode ? 'Preview Mode' : 'Dev Mode'}</span>
|
|
241
|
+
`;
|
|
242
|
+
// Create details popup (with padding-top to bridge gap with badge)
|
|
243
|
+
const details = document.createElement('div');
|
|
244
|
+
details.style.cssText = `
|
|
245
|
+
position: absolute;
|
|
246
|
+
bottom: calc(100% + 8px);
|
|
247
|
+
right: 0;
|
|
248
|
+
background: white;
|
|
249
|
+
border: 1px solid #e5e5e5;
|
|
250
|
+
border-radius: 8px;
|
|
251
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
252
|
+
padding: 12px;
|
|
253
|
+
min-width: 250px;
|
|
254
|
+
font-size: 12px;
|
|
255
|
+
line-height: 1.5;
|
|
256
|
+
display: none;
|
|
257
|
+
`;
|
|
258
|
+
details.style.paddingTop = '20px'; // Extra padding to bridge the gap
|
|
259
|
+
// Add invisible bridge between badge and popup to prevent flickering
|
|
260
|
+
const bridge = document.createElement('div');
|
|
261
|
+
bridge.style.cssText = `
|
|
262
|
+
position: absolute;
|
|
263
|
+
bottom: 100%;
|
|
264
|
+
left: 0;
|
|
265
|
+
right: 0;
|
|
266
|
+
height: 8px;
|
|
267
|
+
display: none;
|
|
268
|
+
`;
|
|
269
|
+
// Build details content
|
|
270
|
+
let detailsHTML = '<div style="margin-bottom: 8px; font-weight: 600; color: #1d1d1f;">Current Environment</div>';
|
|
271
|
+
detailsHTML += '<div style="display: flex; flex-direction: column; gap: 6px;">';
|
|
272
|
+
if (draftMode) {
|
|
273
|
+
detailsHTML += `
|
|
274
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
275
|
+
<span>Draft Mode:</span>
|
|
276
|
+
<span style="color: #ff9500; font-weight: 600;">ON</span>
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
if (trackingDisabled) {
|
|
281
|
+
detailsHTML += `
|
|
282
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
283
|
+
<span>Tracking:</span>
|
|
284
|
+
<span style="color: #ff3b30; font-weight: 600;">DISABLED</span>
|
|
285
|
+
</div>
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
if (params.funnelEnv) {
|
|
289
|
+
detailsHTML += `
|
|
290
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
291
|
+
<span>Funnel Env:</span>
|
|
292
|
+
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
293
|
+
${params.funnelEnv}
|
|
294
|
+
</span>
|
|
295
|
+
</div>
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
if (params.tagadaClientEnv) {
|
|
299
|
+
detailsHTML += `
|
|
300
|
+
<div style="display: flex; justify-content: space-between; color: #86868b;">
|
|
301
|
+
<span>API Env:</span>
|
|
302
|
+
<span style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 11px;">
|
|
303
|
+
${params.tagadaClientEnv}
|
|
304
|
+
</span>
|
|
305
|
+
</div>
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
if (params.tagadaClientBaseUrl) {
|
|
309
|
+
detailsHTML += `
|
|
310
|
+
<div style="color: #86868b;">
|
|
311
|
+
<div style="margin-bottom: 4px;">API URL:</div>
|
|
312
|
+
<div style="color: #1d1d1f; font-weight: 600; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
313
|
+
${params.tagadaClientBaseUrl}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
if (params.funnelId) {
|
|
319
|
+
detailsHTML += `
|
|
320
|
+
<div style="color: #86868b; margin-top: 4px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
321
|
+
<div style="margin-bottom: 4px;">Funnel ID:</div>
|
|
322
|
+
<div style="color: #1d1d1f; font-family: monospace; font-size: 10px; word-break: break-all; background: #f5f5f7; padding: 4px 6px; border-radius: 4px;">
|
|
323
|
+
${params.funnelId}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
`;
|
|
327
|
+
}
|
|
328
|
+
detailsHTML += '</div>';
|
|
329
|
+
detailsHTML += `
|
|
330
|
+
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5; font-size: 11px; color: #86868b; text-align: center;">
|
|
331
|
+
Add <code style="background: #f5f5f7; padding: 2px 4px; border-radius: 3px;">?forceReset=true</code> to reset
|
|
332
|
+
</div>
|
|
333
|
+
`;
|
|
334
|
+
// Add action button
|
|
335
|
+
detailsHTML += `
|
|
336
|
+
<div style="margin-top: 12px; padding-top: 8px; border-top: 1px solid #e5e5e5;">
|
|
337
|
+
<button id="tgd-leave-preview" style="
|
|
338
|
+
background: #ff3b30;
|
|
339
|
+
color: white;
|
|
340
|
+
border: none;
|
|
341
|
+
border-radius: 6px;
|
|
342
|
+
padding: 10px 12px;
|
|
343
|
+
font-size: 13px;
|
|
344
|
+
font-weight: 600;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
transition: opacity 0.2s;
|
|
347
|
+
width: 100%;
|
|
348
|
+
">
|
|
349
|
+
🚪 Leave Preview Mode
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
`;
|
|
353
|
+
details.innerHTML = detailsHTML;
|
|
354
|
+
// Hover behavior - keep popup visible when hovering over badge, bridge, or popup
|
|
355
|
+
let isHovering = false;
|
|
356
|
+
const showDetails = () => {
|
|
357
|
+
isHovering = true;
|
|
358
|
+
details.style.display = 'block';
|
|
359
|
+
bridge.style.display = 'block';
|
|
360
|
+
};
|
|
361
|
+
const hideDetails = () => {
|
|
362
|
+
isHovering = false;
|
|
363
|
+
setTimeout(() => {
|
|
364
|
+
if (!isHovering) {
|
|
365
|
+
details.style.display = 'none';
|
|
366
|
+
bridge.style.display = 'none';
|
|
367
|
+
}
|
|
368
|
+
}, 100); // Small delay to allow moving between elements
|
|
369
|
+
};
|
|
370
|
+
badge.addEventListener('mouseenter', showDetails);
|
|
371
|
+
badge.addEventListener('mouseleave', hideDetails);
|
|
372
|
+
bridge.addEventListener('mouseenter', showDetails);
|
|
373
|
+
bridge.addEventListener('mouseleave', hideDetails);
|
|
374
|
+
details.addEventListener('mouseenter', showDetails);
|
|
375
|
+
details.addEventListener('mouseleave', hideDetails);
|
|
376
|
+
// Button: Leave preview mode
|
|
377
|
+
const leavePreviewBtn = details.querySelector('#tgd-leave-preview');
|
|
378
|
+
if (leavePreviewBtn) {
|
|
379
|
+
leavePreviewBtn.addEventListener('mouseenter', () => {
|
|
380
|
+
leavePreviewBtn.style.opacity = '0.8';
|
|
381
|
+
});
|
|
382
|
+
leavePreviewBtn.addEventListener('mouseleave', () => {
|
|
383
|
+
leavePreviewBtn.style.opacity = '1';
|
|
384
|
+
});
|
|
385
|
+
leavePreviewBtn.addEventListener('click', (e) => {
|
|
386
|
+
e.stopPropagation();
|
|
387
|
+
leavePreviewMode();
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
// Assemble
|
|
391
|
+
container.appendChild(badge);
|
|
392
|
+
container.appendChild(bridge); // Add invisible bridge
|
|
393
|
+
container.appendChild(details);
|
|
394
|
+
// Inject into DOM
|
|
395
|
+
document.body.appendChild(container);
|
|
396
|
+
indicatorElement = container;
|
|
397
|
+
isInjected = true;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Remove the preview mode indicator from the DOM
|
|
401
|
+
*/
|
|
402
|
+
export function removePreviewModeIndicator() {
|
|
403
|
+
if (indicatorElement && indicatorElement.parentNode) {
|
|
404
|
+
indicatorElement.parentNode.removeChild(indicatorElement);
|
|
405
|
+
indicatorElement = null;
|
|
406
|
+
isInjected = false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Check if indicator is currently injected
|
|
411
|
+
*/
|
|
412
|
+
export function isIndicatorInjected() {
|
|
413
|
+
return isInjected;
|
|
414
|
+
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Set the CMS token in localStorage
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Browser's native 'storage' event ONLY fires for changes in OTHER tabs/windows.
|
|
8
|
+
* For same-tab updates to trigger storage listeners, we must manually dispatch the event.
|
|
9
|
+
* We only dispatch if the token actually changed to minimize unnecessary events.
|
|
6
10
|
*/
|
|
7
11
|
export declare function setClientToken(token: string): void;
|
|
8
12
|
/**
|
|
@@ -4,12 +4,26 @@
|
|
|
4
4
|
const TOKEN_KEY = 'cms_token';
|
|
5
5
|
/**
|
|
6
6
|
* Set the CMS token in localStorage
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: Browser's native 'storage' event ONLY fires for changes in OTHER tabs/windows.
|
|
9
|
+
* For same-tab updates to trigger storage listeners, we must manually dispatch the event.
|
|
10
|
+
* We only dispatch if the token actually changed to minimize unnecessary events.
|
|
7
11
|
*/
|
|
8
12
|
export function setClientToken(token) {
|
|
9
13
|
if (typeof window !== 'undefined') {
|
|
10
14
|
try {
|
|
15
|
+
// Check if token actually changed to avoid unnecessary events and potential loops
|
|
16
|
+
const currentToken = localStorage.getItem(TOKEN_KEY);
|
|
17
|
+
const tokenChanged = currentToken !== token;
|
|
11
18
|
localStorage.setItem(TOKEN_KEY, token);
|
|
12
|
-
|
|
19
|
+
// Only dispatch if token actually changed
|
|
20
|
+
// The storage event listeners have guards to prevent infinite loops:
|
|
21
|
+
// - They check if token hasn't changed (client.ts line 261)
|
|
22
|
+
// - They check if initialization is in progress (client.ts line 269)
|
|
23
|
+
// - They check retry limits (client.ts line 277)
|
|
24
|
+
if (tokenChanged) {
|
|
25
|
+
window.dispatchEvent(new Event('storage'));
|
|
26
|
+
}
|
|
13
27
|
}
|
|
14
28
|
catch (error) {
|
|
15
29
|
console.error('Failed to save token to localStorage:', error);
|