@tagadapay/plugin-sdk 3.1.2 → 3.1.8
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 +113 -113
- package/dist/external-tracker.js +1104 -491
- 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/v2/core/client.js +34 -2
- package/dist/v2/core/config/environment.js +9 -2
- package/dist/v2/core/funnelClient.d.ts +92 -1
- package/dist/v2/core/funnelClient.js +247 -3
- 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 +15 -0
- package/dist/v2/core/resources/payments.d.ts +50 -3
- package/dist/v2/core/resources/payments.js +38 -7
- package/dist/v2/core/utils/pluginConfig.js +40 -5
- package/dist/v2/core/utils/previewMode.d.ts +3 -0
- package/dist/v2/core/utils/previewMode.js +44 -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 +6 -1
- package/dist/v2/index.js +6 -1
- package/dist/v2/react/components/ApplePayButton.d.ts +21 -121
- package/dist/v2/react/components/ApplePayButton.js +221 -290
- package/dist/v2/react/components/FunnelScriptInjector.d.ts +3 -1
- package/dist/v2/react/components/FunnelScriptInjector.js +128 -24
- 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 +42 -6
- package/dist/v2/react/hooks/useFunnel.js +25 -5
- package/dist/v2/react/hooks/usePaymentPolling.d.ts +9 -3
- package/dist/v2/react/hooks/usePaymentPolling.js +31 -9
- package/dist/v2/react/hooks/usePaymentQuery.d.ts +32 -2
- package/dist/v2/react/hooks/usePaymentQuery.js +304 -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/useStepConfig.d.ts +62 -0
- package/dist/v2/react/hooks/useStepConfig.js +52 -0
- package/dist/v2/react/index.d.ts +9 -3
- package/dist/v2/react/index.js +5 -1
- package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +27 -19
- package/dist/v2/react/providers/TagadaProvider.js +7 -7
- 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
|
@@ -82,10 +82,16 @@ function getFromCookie(key) {
|
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Set value in cookie
|
|
85
|
+
* ⚠️ NEVER allows tgd_draft to be set as a cookie (localStorage-only)
|
|
85
86
|
*/
|
|
86
87
|
function setInCookie(key, value, maxAge = 86400) {
|
|
87
88
|
if (typeof document === 'undefined')
|
|
88
89
|
return;
|
|
90
|
+
// 🛡️ SAFETY: Block tgd_draft from EVER being written to cookies
|
|
91
|
+
if (key === 'tgd_draft' || key === 'tgd-draft') {
|
|
92
|
+
console.warn(`🛡️ [SDK] Blocked attempt to set ${key} as cookie - this should only be in localStorage`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
89
95
|
document.cookie = `${key}=${value}; path=/; max-age=${maxAge}`;
|
|
90
96
|
}
|
|
91
97
|
/**
|
|
@@ -105,14 +111,14 @@ export function getSDKParams() {
|
|
|
105
111
|
return {};
|
|
106
112
|
}
|
|
107
113
|
const urlParams = new URLSearchParams(window.location.search);
|
|
108
|
-
// Get draft mode (URL > localStorage
|
|
114
|
+
// Get draft mode (URL > localStorage only - no cookie fallback to avoid resurrection)
|
|
109
115
|
let draft;
|
|
110
116
|
const urlDraft = urlParams.get('draft');
|
|
111
117
|
if (urlDraft !== null) {
|
|
112
118
|
draft = urlDraft === 'true';
|
|
113
119
|
}
|
|
114
120
|
else {
|
|
115
|
-
const storageDraft = getFromStorage(STORAGE_KEYS.DRAFT)
|
|
121
|
+
const storageDraft = getFromStorage(STORAGE_KEYS.DRAFT);
|
|
116
122
|
if (storageDraft !== null) {
|
|
117
123
|
draft = storageDraft === 'true';
|
|
118
124
|
}
|
|
@@ -135,6 +141,12 @@ export function getSDKParams() {
|
|
|
135
141
|
const funnelSessionId = urlParams.get('funnelSessionId') || null;
|
|
136
142
|
// Get funnel ID (URL only)
|
|
137
143
|
const funnelId = urlParams.get('funnelId') || null;
|
|
144
|
+
// Get funnel environment (URL only - not persisted, set on entry)
|
|
145
|
+
let funnelEnv;
|
|
146
|
+
const urlFunnelEnv = urlParams.get('funnelEnv');
|
|
147
|
+
if (urlFunnelEnv && (urlFunnelEnv === 'staging' || urlFunnelEnv === 'production')) {
|
|
148
|
+
funnelEnv = urlFunnelEnv;
|
|
149
|
+
}
|
|
138
150
|
// Force reset is URL-only (not persisted)
|
|
139
151
|
const forceReset = urlParams.get('forceReset') === 'true';
|
|
140
152
|
// Get client environment override (URL > localStorage > cookie)
|
|
@@ -168,6 +180,7 @@ export function getSDKParams() {
|
|
|
168
180
|
funnelId,
|
|
169
181
|
draft,
|
|
170
182
|
funnelTracking,
|
|
183
|
+
funnelEnv,
|
|
171
184
|
tagadaClientEnv,
|
|
172
185
|
tagadaClientBaseUrl,
|
|
173
186
|
};
|
|
@@ -189,17 +202,21 @@ export function isDraftMode() {
|
|
|
189
202
|
}
|
|
190
203
|
/**
|
|
191
204
|
* Set draft mode in storage for persistence
|
|
205
|
+
* ⚠️ ONLY writes to localStorage (not cookies) to avoid resurrection issues
|
|
192
206
|
*/
|
|
193
207
|
export function setDraftMode(draft) {
|
|
194
208
|
if (typeof window === 'undefined')
|
|
195
209
|
return;
|
|
196
210
|
if (draft) {
|
|
197
211
|
setInStorage(STORAGE_KEYS.DRAFT, 'true');
|
|
198
|
-
setInCookie(STORAGE_KEYS.DRAFT, 'true', 86400); // 24 hours
|
|
199
212
|
}
|
|
200
213
|
else {
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
try {
|
|
215
|
+
localStorage.removeItem(STORAGE_KEYS.DRAFT);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Storage not available
|
|
219
|
+
}
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
/**
|
|
@@ -214,6 +231,13 @@ export function setDraftMode(draft) {
|
|
|
214
231
|
* @returns True if force reset was activated and state was cleared
|
|
215
232
|
*/
|
|
216
233
|
export function handlePreviewMode(debugMode = false) {
|
|
234
|
+
// 🔒 CRITICAL: Read URL token DIRECTLY first before any getSDKParams()
|
|
235
|
+
// This ensures we have the URL token before any clearing happens
|
|
236
|
+
let urlToken = null;
|
|
237
|
+
if (typeof window !== 'undefined') {
|
|
238
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
239
|
+
urlToken = urlParams.get('token');
|
|
240
|
+
}
|
|
217
241
|
const params = getSDKParams();
|
|
218
242
|
const shouldReset = params.forceReset || false;
|
|
219
243
|
if (!shouldReset && !params.token) {
|
|
@@ -224,6 +248,7 @@ export function handlePreviewMode(debugMode = false) {
|
|
|
224
248
|
}
|
|
225
249
|
if (debugMode) {
|
|
226
250
|
console.log('[SDK] Detected params:', params);
|
|
251
|
+
console.log('[SDK] URL token (direct read):', urlToken ? urlToken.substring(0, 20) + '...' : 'none');
|
|
227
252
|
}
|
|
228
253
|
// CASE 1: Force reset - clear all state (simulates hard refresh)
|
|
229
254
|
if (shouldReset) {
|
|
@@ -246,23 +271,28 @@ export function handlePreviewMode(debugMode = false) {
|
|
|
246
271
|
}
|
|
247
272
|
}
|
|
248
273
|
// CASE 2: Token in URL - override stored token
|
|
249
|
-
|
|
274
|
+
// 🔒 CRITICAL: Use the directly-read URL token to avoid any race conditions
|
|
275
|
+
const tokenToSet = urlToken || params.token;
|
|
276
|
+
if (tokenToSet !== null && tokenToSet !== undefined) {
|
|
250
277
|
if (debugMode) {
|
|
251
|
-
console.log('[SDK]
|
|
278
|
+
console.log('[SDK] Setting token from URL:', tokenToSet.substring(0, 20) + '...');
|
|
252
279
|
}
|
|
253
|
-
if (
|
|
280
|
+
if (tokenToSet === '' || tokenToSet === 'null') {
|
|
254
281
|
// Explicitly cleared token
|
|
255
282
|
clearClientToken();
|
|
256
283
|
}
|
|
257
284
|
else {
|
|
258
|
-
// Set token from URL
|
|
259
|
-
setClientToken(
|
|
285
|
+
// Set token from URL IMMEDIATELY after clearing
|
|
286
|
+
setClientToken(tokenToSet);
|
|
287
|
+
if (debugMode) {
|
|
288
|
+
console.log('[SDK] ✅ Token set in localStorage immediately after clear');
|
|
289
|
+
}
|
|
260
290
|
}
|
|
261
291
|
}
|
|
262
292
|
else if (shouldReset) {
|
|
263
293
|
// Force reset but no token = clear stored token
|
|
264
294
|
if (debugMode) {
|
|
265
|
-
console.log('[SDK] Force reset mode (no token)');
|
|
295
|
+
console.log('[SDK] Force reset mode (no token in URL)');
|
|
266
296
|
}
|
|
267
297
|
clearClientToken();
|
|
268
298
|
}
|
|
@@ -311,7 +341,7 @@ export function setFunnelTracking(enabled) {
|
|
|
311
341
|
return;
|
|
312
342
|
const value = enabled ? 'true' : 'false';
|
|
313
343
|
setInStorage(STORAGE_KEYS.FUNNEL_TRACKING, value);
|
|
314
|
-
setInCookie(STORAGE_KEYS.FUNNEL_TRACKING, value, 86400);
|
|
344
|
+
setInCookie(STORAGE_KEYS.FUNNEL_TRACKING, value, 86400);
|
|
315
345
|
}
|
|
316
346
|
/**
|
|
317
347
|
* Set client environment override in storage for persistence
|
|
@@ -320,7 +350,7 @@ export function setClientEnvironment(env) {
|
|
|
320
350
|
if (typeof window === 'undefined')
|
|
321
351
|
return;
|
|
322
352
|
setInStorage(STORAGE_KEYS.CLIENT_ENV, env);
|
|
323
|
-
setInCookie(STORAGE_KEYS.CLIENT_ENV, env, 86400);
|
|
353
|
+
setInCookie(STORAGE_KEYS.CLIENT_ENV, env, 86400);
|
|
324
354
|
}
|
|
325
355
|
/**
|
|
326
356
|
* Clear client environment override
|
|
@@ -343,7 +373,7 @@ export function setClientBaseUrl(baseUrl) {
|
|
|
343
373
|
if (typeof window === 'undefined')
|
|
344
374
|
return;
|
|
345
375
|
setInStorage(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl);
|
|
346
|
-
setInCookie(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl, 86400);
|
|
376
|
+
setInCookie(STORAGE_KEYS.CLIENT_BASE_URL, baseUrl, 86400);
|
|
347
377
|
}
|
|
348
378
|
/**
|
|
349
379
|
* 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);
|
package/dist/v2/index.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ export * from './core/utils/pluginConfig';
|
|
|
12
12
|
export * from './core/utils/products';
|
|
13
13
|
export * from './core/utils/previewMode';
|
|
14
14
|
export * from './core/utils/configHotReload';
|
|
15
|
+
export { injectPreviewModeIndicator, removePreviewModeIndicator, isIndicatorInjected } from './core/utils/previewModeIndicator';
|
|
16
|
+
export { getAssignedStepConfig, getAssignedPaymentFlowId, getAssignedStaticResources, getAssignedScripts, loadLocalFunnelConfig, getLocalFunnelConfig, } from './core/funnelClient';
|
|
17
|
+
export type { RuntimeStepConfig, LocalFunnelConfig } from './core/funnelClient';
|
|
15
18
|
export * from './core/pathRemapping';
|
|
16
19
|
export type { CheckoutData, CheckoutInitParams, CheckoutLineItem, CheckoutSession, CheckoutSessionPreview, Promotion } from './core/resources/checkout';
|
|
17
20
|
export type { Order, OrderLineItem } from './core/utils/order';
|
|
@@ -25,10 +28,11 @@ export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './co
|
|
|
25
28
|
export type { StoreConfig } from './core/resources/storeConfig';
|
|
26
29
|
export { FunnelActionType } from './core/resources/funnel';
|
|
27
30
|
export type { BackNavigationActionData, CartUpdatedActionData, DirectNavigationActionData, FormSubmitActionData, FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelAction as FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, NextAction, OfferAcceptedActionData, OfferDeclinedActionData, PaymentFailedActionData, PaymentSuccessActionData, SimpleFunnelContext } from './core/resources/funnel';
|
|
28
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
31
|
+
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, PreviewModeIndicator, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useApplePayCheckout, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCredits, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useFunnelLegacy, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffer, useOrder, useOrderBump, usePayment, usePaymentRetrieve, usePluginConfig, usePostPurchases, usePreloadQuery, usePreviewOffer, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStepConfig, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
29
32
|
export type { DebugScript } from './react';
|
|
30
33
|
export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react/hooks/useTranslation';
|
|
31
34
|
export type { FunnelContextValue } from './react/hooks/useFunnel';
|
|
35
|
+
export type { UseApplePayCheckoutOptions } from './react/hooks/useApplePayCheckout';
|
|
32
36
|
export type { ClubOffer, ClubOfferItem, ClubOfferLineItem, ClubOfferSummary, UseClubOffersOptions, UseClubOffersResult } from './react/hooks/useClubOffers';
|
|
33
37
|
export type { UseCreditsOptions, UseCreditsResult } from './react/hooks/useCredits';
|
|
34
38
|
export type { UseLoginOptions, UseLoginResult } from './react/hooks/useLogin';
|
|
@@ -37,5 +41,6 @@ export type { UseCustomerResult } from './react/hooks/useCustomer';
|
|
|
37
41
|
export type { UseCustomerInfosOptions, UseCustomerInfosResult } from './react/hooks/useCustomerInfos';
|
|
38
42
|
export type { UseCustomerOrdersOptions, UseCustomerOrdersResult } from './react/hooks/useCustomerOrders';
|
|
39
43
|
export type { UseCustomerSubscriptionsOptions, UseCustomerSubscriptionsResult } from './react/hooks/useCustomerSubscriptions';
|
|
44
|
+
export type { PaymentRetrieveHook as UsePaymentRetrieveResult } from './react/hooks/usePaymentRetrieve';
|
|
40
45
|
export type { UseShippingRatesQueryOptions, UseShippingRatesQueryResult } from './react/hooks/useShippingRatesQuery';
|
|
41
46
|
export type { PreviewOfferSummary, UsePreviewOfferOptions, UsePreviewOfferResult } from './react/hooks/usePreviewOffer';
|