@tagadapay/plugin-sdk 3.0.9 → 3.0.14
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/dist/external-tracker.js +3802 -195
- package/dist/external-tracker.min.js +25 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/types.d.ts +2 -0
- package/dist/v2/core/client.d.ts +4 -0
- package/dist/v2/core/client.js +314 -123
- package/dist/v2/core/config/environment.js +6 -0
- package/dist/v2/core/funnelClient.d.ts +18 -1
- package/dist/v2/core/funnelClient.js +90 -17
- package/dist/v2/core/resources/checkout.d.ts +44 -1
- package/dist/v2/core/resources/checkout.js +48 -1
- package/dist/v2/core/resources/funnel.d.ts +44 -4
- package/dist/v2/core/resources/offers.d.ts +26 -0
- package/dist/v2/core/resources/offers.js +37 -0
- package/dist/v2/core/types.d.ts +3 -1
- package/dist/v2/core/utils/authHandoff.d.ts +60 -0
- package/dist/v2/core/utils/authHandoff.js +154 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +20 -3
- package/dist/v2/core/utils/deviceInfo.js +62 -94
- package/dist/v2/core/utils/previewMode.d.ts +4 -0
- package/dist/v2/core/utils/previewMode.js +4 -0
- package/dist/v2/react/components/DebugDrawer.js +68 -46
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +0 -1
- package/dist/v2/react/hooks/useCheckoutQuery.js +12 -4
- package/dist/v2/react/hooks/useFunnelLegacy.js +39 -11
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -3
- package/dist/v2/react/hooks/usePreviewOffer.js +20 -15
- package/dist/v2/react/hooks/useTranslation.js +12 -4
- package/dist/v2/standalone/index.d.ts +2 -1
- package/dist/v2/standalone/index.js +2 -1
- package/package.json +3 -1
package/dist/react/types.d.ts
CHANGED
package/dist/v2/core/client.d.ts
CHANGED
|
@@ -101,6 +101,10 @@ export declare class TagadaClient {
|
|
|
101
101
|
* Initialize token and session
|
|
102
102
|
*/
|
|
103
103
|
private initializeToken;
|
|
104
|
+
/**
|
|
105
|
+
* Normal token initialization flow (no cross-domain handoff)
|
|
106
|
+
*/
|
|
107
|
+
private fallbackToNormalFlow;
|
|
104
108
|
/**
|
|
105
109
|
* Set token and resolve waiting requests
|
|
106
110
|
*/
|
package/dist/v2/core/client.js
CHANGED
|
@@ -8,6 +8,7 @@ import { decodeJWTClient, isTokenExpired } from './utils/jwtDecoder';
|
|
|
8
8
|
import { loadPluginConfig } from './utils/pluginConfig';
|
|
9
9
|
import { handlePreviewMode, isDraftMode, setDraftMode } from './utils/previewMode';
|
|
10
10
|
import { getClientToken, setClientToken } from './utils/tokenStorage';
|
|
11
|
+
import { shouldResolveAuthCode, resolveAuthHandoff } from './utils/authHandoff';
|
|
11
12
|
export class TagadaClient {
|
|
12
13
|
constructor(config = {}) {
|
|
13
14
|
/**
|
|
@@ -25,9 +26,13 @@ export class TagadaClient {
|
|
|
25
26
|
this.config = config;
|
|
26
27
|
this.instanceId = Math.random().toString(36).substr(2, 9);
|
|
27
28
|
this.boundHandleStorageChange = this.handleStorageChange.bind(this);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
console.log(`[TagadaClient ${this.instanceId}] Initializing...`);
|
|
30
|
+
console.log(`[TagadaClient ${this.instanceId}] Config:`, {
|
|
31
|
+
debugMode: config.debugMode,
|
|
32
|
+
hasRawPluginConfig: !!config.rawPluginConfig,
|
|
33
|
+
rawPluginConfig: config.rawPluginConfig,
|
|
34
|
+
features: config.features,
|
|
35
|
+
});
|
|
31
36
|
// Handle preview mode FIRST - clears state if needed
|
|
32
37
|
// This ensures clean state when CRM previews pages
|
|
33
38
|
const previewModeActive = handlePreviewMode(this.config.debugMode);
|
|
@@ -77,10 +82,14 @@ export class TagadaClient {
|
|
|
77
82
|
isInitialized: false,
|
|
78
83
|
isSessionInitialized: false,
|
|
79
84
|
pluginConfig: { basePath: '/', config: {} },
|
|
80
|
-
pluginConfigLoading:
|
|
85
|
+
pluginConfigLoading: true, // Always true - loadPluginConfig will process rawPluginConfig
|
|
81
86
|
debugMode: config.debugMode ?? env !== 'production',
|
|
82
87
|
token: null,
|
|
83
88
|
};
|
|
89
|
+
console.log(`[TagadaClient ${this.instanceId}] Initial state:`, {
|
|
90
|
+
pluginConfigLoading: this.state.pluginConfigLoading,
|
|
91
|
+
hasRawPluginConfig: !!config.rawPluginConfig,
|
|
92
|
+
});
|
|
84
93
|
// Initialize API Client
|
|
85
94
|
this.apiClient = new ApiClient({
|
|
86
95
|
baseURL: envConfig.apiConfig.baseUrl,
|
|
@@ -96,6 +105,9 @@ export class TagadaClient {
|
|
|
96
105
|
pluginConfig: this.state.pluginConfig,
|
|
97
106
|
environment: this.state.environment,
|
|
98
107
|
autoRedirect: funnelConfig.autoRedirect,
|
|
108
|
+
// Pass funnelId and stepId from rawPluginConfig to enable config-based initialization
|
|
109
|
+
funnelId: config.rawPluginConfig?.funnelId,
|
|
110
|
+
stepId: config.rawPluginConfig?.stepId,
|
|
99
111
|
});
|
|
100
112
|
}
|
|
101
113
|
// Setup token waiting mechanism
|
|
@@ -206,11 +218,19 @@ export class TagadaClient {
|
|
|
206
218
|
* Load plugin configuration
|
|
207
219
|
*/
|
|
208
220
|
async initializePluginConfig() {
|
|
209
|
-
|
|
221
|
+
console.log(`[TagadaClient ${this.instanceId}] initializePluginConfig called`, {
|
|
222
|
+
pluginConfigLoading: this.state.pluginConfigLoading,
|
|
223
|
+
hasRawPluginConfig: !!this.config.rawPluginConfig,
|
|
224
|
+
});
|
|
225
|
+
if (!this.state.pluginConfigLoading) {
|
|
226
|
+
console.log(`[TagadaClient ${this.instanceId}] Plugin config already loading or loaded, skipping...`);
|
|
210
227
|
return;
|
|
228
|
+
}
|
|
211
229
|
try {
|
|
212
230
|
const configVariant = this.config.localConfig || 'default';
|
|
231
|
+
console.log(`[TagadaClient ${this.instanceId}] Loading plugin config with variant: ${configVariant}`);
|
|
213
232
|
const config = await loadPluginConfig(configVariant, this.config.rawPluginConfig);
|
|
233
|
+
console.log(`[TagadaClient ${this.instanceId}] Plugin config loaded:`, config);
|
|
214
234
|
this.updateState({
|
|
215
235
|
pluginConfig: config,
|
|
216
236
|
pluginConfigLoading: false,
|
|
@@ -222,9 +242,6 @@ export class TagadaClient {
|
|
|
222
242
|
environment: this.state.environment,
|
|
223
243
|
});
|
|
224
244
|
}
|
|
225
|
-
if (this.state.debugMode) {
|
|
226
|
-
console.log('[TagadaClient] Plugin config loaded:', config);
|
|
227
|
-
}
|
|
228
245
|
}
|
|
229
246
|
catch (error) {
|
|
230
247
|
console.error('[TagadaClient] Failed to load plugin config:', error);
|
|
@@ -238,16 +255,69 @@ export class TagadaClient {
|
|
|
238
255
|
* Initialize token and session
|
|
239
256
|
*/
|
|
240
257
|
async initializeToken() {
|
|
258
|
+
// 🔐 PRIORITY 1: Check for authCode (cross-domain handoff)
|
|
259
|
+
// This ALWAYS takes precedence over existing tokens
|
|
260
|
+
if (shouldResolveAuthCode()) {
|
|
261
|
+
const storeId = this.state.pluginConfig.storeId;
|
|
262
|
+
if (!storeId) {
|
|
263
|
+
console.error('[TagadaClient] Cannot resolve authCode: storeId not found in config');
|
|
264
|
+
return this.fallbackToNormalFlow();
|
|
265
|
+
}
|
|
266
|
+
console.log(`[TagadaClient ${this.instanceId}] 🔐 Cross-domain auth detected, resolving...`);
|
|
267
|
+
try {
|
|
268
|
+
const authCode = new URLSearchParams(window.location.search).get('authCode');
|
|
269
|
+
if (!authCode) {
|
|
270
|
+
return this.fallbackToNormalFlow();
|
|
271
|
+
}
|
|
272
|
+
// Resolve the handoff
|
|
273
|
+
const handoffData = await resolveAuthHandoff(authCode, storeId, this.state.environment.apiConfig.baseUrl, this.state.debugMode);
|
|
274
|
+
console.log(`[TagadaClient ${this.instanceId}] ✅ Auth handoff resolved:`, {
|
|
275
|
+
customerId: handoffData.customer.id,
|
|
276
|
+
role: handoffData.customer.role,
|
|
277
|
+
hasContext: Object.keys(handoffData.context).length > 0,
|
|
278
|
+
});
|
|
279
|
+
// Set the new token (already stored by resolveAuthHandoff)
|
|
280
|
+
this.setToken(handoffData.token);
|
|
281
|
+
// Decode session from token
|
|
282
|
+
const decodedSession = decodeJWTClient(handoffData.token);
|
|
283
|
+
if (decodedSession) {
|
|
284
|
+
this.updateState({ session: decodedSession });
|
|
285
|
+
await this.initializeSession(decodedSession);
|
|
286
|
+
// If context has funnelSessionId, restore it
|
|
287
|
+
if (handoffData.context?.funnelSessionId && this.funnel) {
|
|
288
|
+
if (this.state.debugMode) {
|
|
289
|
+
console.log(`[TagadaClient ${this.instanceId}] Restoring funnel session from handoff context:`, handoffData.context.funnelSessionId);
|
|
290
|
+
}
|
|
291
|
+
// The funnel client will pick this up during auto-initialization
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.error('[TagadaClient] Failed to decode token from handoff');
|
|
296
|
+
this.updateState({ isInitialized: true, isLoading: false });
|
|
297
|
+
}
|
|
298
|
+
return; // ✅ Auth handoff resolved successfully, exit early
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error(`[TagadaClient ${this.instanceId}] ❌ Auth handoff failed, falling back to normal flow:`, error);
|
|
302
|
+
// Fall through to normal initialization
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Continue with normal flow if no authCode or resolution failed
|
|
306
|
+
await this.fallbackToNormalFlow();
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Normal token initialization flow (no cross-domain handoff)
|
|
310
|
+
*/
|
|
311
|
+
async fallbackToNormalFlow() {
|
|
241
312
|
// Check for existing token in URL or storage
|
|
242
313
|
const existingToken = getClientToken();
|
|
243
314
|
const urlParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
|
|
244
315
|
const queryToken = urlParams.get('token');
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
316
|
+
console.log(`[TagadaClient ${this.instanceId}] Initializing token (normal flow)...`, {
|
|
317
|
+
hasExistingToken: !!existingToken,
|
|
318
|
+
hasQueryToken: !!queryToken,
|
|
319
|
+
storeId: this.state.pluginConfig.storeId,
|
|
320
|
+
});
|
|
251
321
|
let tokenToUse = null;
|
|
252
322
|
let shouldPersist = false;
|
|
253
323
|
if (queryToken) {
|
|
@@ -280,13 +350,15 @@ export class TagadaClient {
|
|
|
280
350
|
else {
|
|
281
351
|
// Create anonymous token
|
|
282
352
|
const storeId = this.state.pluginConfig.storeId;
|
|
353
|
+
console.log(`[TagadaClient ${this.instanceId}] No existing token, creating anonymous token...`, {
|
|
354
|
+
hasStoreId: !!storeId,
|
|
355
|
+
storeId,
|
|
356
|
+
});
|
|
283
357
|
if (storeId) {
|
|
284
|
-
if (this.state.debugMode) {
|
|
285
|
-
console.log(`[TagadaClient ${this.instanceId}] Creating anonymous token for store:`, storeId);
|
|
286
|
-
}
|
|
287
358
|
await this.createAnonymousToken(storeId);
|
|
288
359
|
}
|
|
289
360
|
else {
|
|
361
|
+
console.warn(`[TagadaClient ${this.instanceId}] No storeId in plugin config, skipping anonymous token creation`);
|
|
290
362
|
this.updateState({ isInitialized: true, isLoading: false });
|
|
291
363
|
}
|
|
292
364
|
}
|
|
@@ -404,12 +476,22 @@ export class TagadaClient {
|
|
|
404
476
|
osVersion: deviceInfo.userAgent.os.version,
|
|
405
477
|
deviceType: deviceInfo.userAgent.device?.type,
|
|
406
478
|
deviceModel: deviceInfo.userAgent.device?.model,
|
|
479
|
+
deviceVendor: deviceInfo.userAgent.device?.vendor,
|
|
480
|
+
userAgent: deviceInfo.userAgent.name,
|
|
481
|
+
engineName: deviceInfo.userAgent.engine.name,
|
|
482
|
+
engineVersion: deviceInfo.userAgent.engine.version,
|
|
483
|
+
cpuArchitecture: deviceInfo.userAgent.cpu.architecture,
|
|
484
|
+
isBot: deviceInfo.flags?.isBot ?? false,
|
|
485
|
+
isChromeFamily: deviceInfo.flags?.isChromeFamily ?? false,
|
|
486
|
+
isStandalonePWA: deviceInfo.flags?.isStandalonePWA ?? false,
|
|
487
|
+
isAppleSilicon: deviceInfo.flags?.isAppleSilicon ?? false,
|
|
407
488
|
screenWidth: deviceInfo.screenResolution.width,
|
|
408
489
|
screenHeight: deviceInfo.screenResolution.height,
|
|
409
490
|
timeZone: deviceInfo.timeZone,
|
|
410
491
|
draft, // 🎯 Pass draft mode to session init
|
|
492
|
+
fetchMessages: false,
|
|
411
493
|
};
|
|
412
|
-
const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
|
|
494
|
+
const response = await this.apiClient.post('/api/v1/cms/session/v2/init', sessionInitData);
|
|
413
495
|
// Success - reset error tracking
|
|
414
496
|
this.lastSessionInitError = null;
|
|
415
497
|
this.sessionInitRetryCount = 0;
|
|
@@ -451,14 +533,16 @@ export class TagadaClient {
|
|
|
451
533
|
};
|
|
452
534
|
this.updateState({ store: storeConfig });
|
|
453
535
|
}
|
|
454
|
-
// Update Locale
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
536
|
+
// Update Locale (only if provided - V2 endpoint doesn't return locale)
|
|
537
|
+
if (response.locale) {
|
|
538
|
+
const localeConfig = {
|
|
539
|
+
locale: response.locale,
|
|
540
|
+
language: response.locale.split('-')[0],
|
|
541
|
+
region: response.locale.split('-')[1] ?? 'US',
|
|
542
|
+
messages: response.messages ?? {},
|
|
543
|
+
};
|
|
544
|
+
this.updateState({ locale: localeConfig });
|
|
545
|
+
}
|
|
462
546
|
// Update Currency
|
|
463
547
|
if (response.store) {
|
|
464
548
|
const currencyConfig = {
|
|
@@ -588,113 +672,220 @@ export class TagadaClient {
|
|
|
588
672
|
applyStylesToElement(elementId, styles) {
|
|
589
673
|
if (typeof document === 'undefined')
|
|
590
674
|
return;
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
595
|
-
|
|
596
|
-
console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found`);
|
|
597
|
-
}
|
|
598
|
-
return;
|
|
675
|
+
// Always remove any existing highlight, even if the target element is not found
|
|
676
|
+
const staticHighlightId = 'tagada-editor-highlight';
|
|
677
|
+
const existingHighlight = document.getElementById(staticHighlightId);
|
|
678
|
+
if (existingHighlight) {
|
|
679
|
+
existingHighlight.remove();
|
|
599
680
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
681
|
+
const applyToElement = (element) => {
|
|
682
|
+
// List of void/self-closing elements that don't accept children
|
|
683
|
+
const voidElements = new Set([
|
|
684
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
685
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
686
|
+
]);
|
|
687
|
+
const isVoidElement = voidElements.has(element.tagName.toLowerCase());
|
|
688
|
+
// For void elements, wrap them in a container div
|
|
689
|
+
let targetElement = element;
|
|
690
|
+
if (isVoidElement) {
|
|
691
|
+
// Check if element is already wrapped (from a previous highlight)
|
|
692
|
+
const parent = element.parentElement;
|
|
693
|
+
const existingWrapper = parent?.getAttribute('data-tagada-highlight-wrapper');
|
|
694
|
+
if (existingWrapper === 'true' && parent instanceof HTMLElement) {
|
|
695
|
+
// Reuse existing wrapper
|
|
696
|
+
targetElement = parent;
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
// Create a new wrapper
|
|
700
|
+
const newWrapper = document.createElement('div');
|
|
701
|
+
newWrapper.setAttribute('data-tagada-highlight-wrapper', 'true');
|
|
702
|
+
// Preserve the element's layout behavior by matching its display style
|
|
703
|
+
// This minimizes the visual impact of the wrapper
|
|
704
|
+
const computedStyle = window.getComputedStyle(element);
|
|
705
|
+
const elementDisplay = computedStyle.display;
|
|
706
|
+
// Match the display type to preserve layout
|
|
707
|
+
// For inline elements, use inline-block (to support position: relative)
|
|
708
|
+
// For all others, use the same display value
|
|
709
|
+
if (elementDisplay === 'inline') {
|
|
710
|
+
newWrapper.style.display = 'inline-block';
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
newWrapper.style.display = elementDisplay;
|
|
714
|
+
}
|
|
715
|
+
newWrapper.style.position = 'relative';
|
|
716
|
+
// Preserve vertical alignment for inline elements
|
|
717
|
+
if (elementDisplay === 'inline' || elementDisplay.includes('inline')) {
|
|
718
|
+
const verticalAlign = computedStyle.verticalAlign;
|
|
719
|
+
if (verticalAlign && verticalAlign !== 'baseline') {
|
|
720
|
+
newWrapper.style.verticalAlign = verticalAlign;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Preserve spacing and layout properties from the original element
|
|
724
|
+
// List of CSS properties that affect spacing and layout
|
|
725
|
+
const spacingProperties = [
|
|
726
|
+
// Width and height
|
|
727
|
+
'width', 'height', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight',
|
|
728
|
+
// Flex properties
|
|
729
|
+
'flex', 'flexGrow', 'flexShrink', 'flexBasis',
|
|
730
|
+
// Grid properties
|
|
731
|
+
'gridColumn', 'gridRow', 'gridColumnStart', 'gridColumnEnd',
|
|
732
|
+
'gridRowStart', 'gridRowEnd', 'gridArea',
|
|
733
|
+
// Alignment properties
|
|
734
|
+
'alignSelf', 'justifySelf',
|
|
735
|
+
// Box sizing
|
|
736
|
+
'boxSizing',
|
|
737
|
+
// Gap (for grid/flex)
|
|
738
|
+
'gap', 'rowGap', 'columnGap',
|
|
739
|
+
// Order (for flex/grid)
|
|
740
|
+
'order',
|
|
741
|
+
// Aspect ratio
|
|
742
|
+
'aspectRatio',
|
|
743
|
+
];
|
|
744
|
+
spacingProperties.forEach(prop => {
|
|
745
|
+
const camelProp = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
746
|
+
const value = computedStyle.getPropertyValue(camelProp);
|
|
747
|
+
// Only copy if the value is not empty
|
|
748
|
+
// For most properties, we want to preserve the value even if it's 'auto' or '0px'
|
|
749
|
+
// as these might be intentional layout choices
|
|
750
|
+
if (value && value.trim() !== '') {
|
|
751
|
+
// Handle special cases
|
|
752
|
+
if (prop === 'flex' && value !== 'none' && value !== '0 1 auto') {
|
|
753
|
+
newWrapper.style.flex = value;
|
|
754
|
+
}
|
|
755
|
+
else if (prop === 'boxSizing') {
|
|
756
|
+
newWrapper.style.boxSizing = value;
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
// Use setProperty for kebab-case properties
|
|
760
|
+
// Preserve all values including percentages, auto, etc.
|
|
761
|
+
newWrapper.style.setProperty(camelProp, value);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
// Also check inline styles for spacing properties
|
|
766
|
+
// This handles cases where styles are set directly on the element
|
|
767
|
+
if (element.style) {
|
|
768
|
+
const inlineSpacingProps = [
|
|
769
|
+
'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
|
|
770
|
+
'flex', 'flex-grow', 'flex-shrink', 'flex-basis',
|
|
771
|
+
'grid-column', 'grid-row', 'grid-column-start', 'grid-column-end',
|
|
772
|
+
'grid-row-start', 'grid-row-end', 'grid-area',
|
|
773
|
+
'align-self', 'justify-self',
|
|
774
|
+
'box-sizing', 'gap', 'row-gap', 'column-gap', 'order', 'aspect-ratio'
|
|
775
|
+
];
|
|
776
|
+
inlineSpacingProps.forEach(prop => {
|
|
777
|
+
const inlineValue = element.style.getPropertyValue(prop);
|
|
778
|
+
if (inlineValue) {
|
|
779
|
+
newWrapper.style.setProperty(prop, inlineValue);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
// Insert wrapper before element
|
|
784
|
+
element.parentNode?.insertBefore(newWrapper, element);
|
|
785
|
+
// Move element into wrapper
|
|
786
|
+
newWrapper.appendChild(element);
|
|
787
|
+
targetElement = newWrapper;
|
|
788
|
+
}
|
|
616
789
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
790
|
+
// Ensure element has position relative for absolute child positioning
|
|
791
|
+
const computedStyle = getComputedStyle(isVoidElement ? element : targetElement);
|
|
792
|
+
if (targetElement.style.position === 'static' || !targetElement.style.position) {
|
|
793
|
+
targetElement.style.position = 'relative';
|
|
794
|
+
}
|
|
795
|
+
// Create new highlight div with static ID
|
|
796
|
+
const highlightDiv = document.createElement('div');
|
|
797
|
+
highlightDiv.id = staticHighlightId;
|
|
798
|
+
highlightDiv.style.position = 'absolute';
|
|
799
|
+
highlightDiv.style.inset = '0';
|
|
800
|
+
highlightDiv.style.pointerEvents = 'none';
|
|
801
|
+
highlightDiv.style.zIndex = '9999';
|
|
802
|
+
highlightDiv.style.boxSizing = 'border-box';
|
|
803
|
+
highlightDiv.style.background = 'none';
|
|
804
|
+
highlightDiv.style.border = 'none';
|
|
805
|
+
highlightDiv.style.outline = 'none';
|
|
806
|
+
highlightDiv.style.margin = '0';
|
|
807
|
+
highlightDiv.style.padding = '0';
|
|
808
|
+
// Inherit border radius from parent
|
|
809
|
+
const borderRadius = computedStyle.borderRadius;
|
|
810
|
+
if (borderRadius) {
|
|
811
|
+
highlightDiv.style.borderRadius = borderRadius;
|
|
812
|
+
}
|
|
813
|
+
// Append the new highlight div to the target element
|
|
814
|
+
targetElement.appendChild(highlightDiv);
|
|
815
|
+
// Apply CSS properties to the highlight div
|
|
816
|
+
const stylesToApply = styles || { boxShadow: '0 0 0 2px rgb(239 68 68)' }; // Default red ring
|
|
817
|
+
Object.entries(stylesToApply).forEach(([property, value]) => {
|
|
818
|
+
// Convert kebab-case to camelCase for style object
|
|
819
|
+
const camelProperty = property.includes('-')
|
|
820
|
+
? property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
821
|
+
: property;
|
|
822
|
+
// Use setProperty for kebab-case or direct assignment for camelCase
|
|
823
|
+
if (property.includes('-')) {
|
|
824
|
+
highlightDiv.style.setProperty(property, value);
|
|
630
825
|
}
|
|
631
826
|
else {
|
|
632
|
-
|
|
827
|
+
highlightDiv.style[camelProperty] = value;
|
|
633
828
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
829
|
+
});
|
|
830
|
+
// Scroll element into view
|
|
831
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
832
|
+
if (this.state.debugMode) {
|
|
833
|
+
const appliedStyles = Object.entries(stylesToApply).map(([k, v]) => `${k}: ${v}`).join('; ');
|
|
834
|
+
console.log(`[TagadaClient] Applied styles to highlight div of element #${elementId}:`, appliedStyles);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
// Support multiple IDs in editor-id attribute (space-separated)
|
|
838
|
+
// Use ~= selector to match elementId as a space-separated value
|
|
839
|
+
const maxAttempts = 5;
|
|
840
|
+
const intervalMs = 1000;
|
|
841
|
+
let attempts = 0;
|
|
842
|
+
const isElementHidden = (element) => {
|
|
843
|
+
const style = window.getComputedStyle(element);
|
|
844
|
+
return (style.display === 'none' ||
|
|
845
|
+
style.visibility === 'hidden' ||
|
|
846
|
+
style.opacity === '0' ||
|
|
847
|
+
element.hidden ||
|
|
848
|
+
element.offsetWidth === 0 ||
|
|
849
|
+
element.offsetHeight === 0);
|
|
850
|
+
};
|
|
851
|
+
const findAndApply = () => {
|
|
852
|
+
const elements = document.querySelectorAll(`[editor-id~="${elementId}"]`);
|
|
853
|
+
if (elements.length === 0) {
|
|
854
|
+
attempts += 1;
|
|
855
|
+
if (attempts >= maxAttempts) {
|
|
856
|
+
if (this.state.debugMode) {
|
|
857
|
+
console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found after ${maxAttempts} attempts`);
|
|
640
858
|
}
|
|
859
|
+
return;
|
|
641
860
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
861
|
+
if (this.state.debugMode) {
|
|
862
|
+
console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found (attempt ${attempts}/${maxAttempts}), retrying in ${intervalMs / 1000}s`);
|
|
863
|
+
}
|
|
864
|
+
setTimeout(findAndApply, intervalMs);
|
|
865
|
+
return;
|
|
647
866
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
targetElement.style.position = 'relative';
|
|
653
|
-
}
|
|
654
|
-
// Remove any existing highlight div before creating a new one
|
|
655
|
-
const staticHighlightId = 'tagada-editor-highlight';
|
|
656
|
-
const existingHighlight = document.getElementById(staticHighlightId);
|
|
657
|
-
if (existingHighlight) {
|
|
658
|
-
existingHighlight.remove();
|
|
659
|
-
}
|
|
660
|
-
// Create new highlight div with static ID
|
|
661
|
-
const highlightDiv = document.createElement('div');
|
|
662
|
-
highlightDiv.id = staticHighlightId;
|
|
663
|
-
highlightDiv.style.position = 'absolute';
|
|
664
|
-
highlightDiv.style.inset = '0';
|
|
665
|
-
highlightDiv.style.pointerEvents = 'none';
|
|
666
|
-
highlightDiv.style.zIndex = '9999';
|
|
667
|
-
highlightDiv.style.boxSizing = 'border-box';
|
|
668
|
-
highlightDiv.style.background = 'none';
|
|
669
|
-
highlightDiv.style.border = 'none';
|
|
670
|
-
highlightDiv.style.outline = 'none';
|
|
671
|
-
// Inherit border radius from parent
|
|
672
|
-
const borderRadius = computedStyle.borderRadius;
|
|
673
|
-
if (borderRadius) {
|
|
674
|
-
highlightDiv.style.borderRadius = borderRadius;
|
|
675
|
-
}
|
|
676
|
-
// Append the new highlight div to the target element
|
|
677
|
-
targetElement.appendChild(highlightDiv);
|
|
678
|
-
// Apply CSS properties to the highlight div
|
|
679
|
-
const stylesToApply = styles || { boxShadow: '0 0 0 2px rgb(239 68 68)' }; // Default red ring
|
|
680
|
-
Object.entries(stylesToApply).forEach(([property, value]) => {
|
|
681
|
-
// Convert kebab-case to camelCase for style object
|
|
682
|
-
const camelProperty = property.includes('-')
|
|
683
|
-
? property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
684
|
-
: property;
|
|
685
|
-
// Use setProperty for kebab-case or direct assignment for camelCase
|
|
686
|
-
if (property.includes('-')) {
|
|
687
|
-
highlightDiv.style.setProperty(property, value);
|
|
867
|
+
// If multiple elements found, prioritize the one that is not hidden
|
|
868
|
+
let element = null;
|
|
869
|
+
if (elements.length === 1) {
|
|
870
|
+
element = elements[0];
|
|
688
871
|
}
|
|
689
872
|
else {
|
|
690
|
-
|
|
873
|
+
// Find the first non-hidden element
|
|
874
|
+
for (let i = 0; i < elements.length; i++) {
|
|
875
|
+
if (!isElementHidden(elements[i])) {
|
|
876
|
+
element = elements[i];
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// If all are hidden, use the first one
|
|
881
|
+
if (!element) {
|
|
882
|
+
element = elements[0];
|
|
883
|
+
}
|
|
691
884
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
console.log(`[TagadaClient] Applied styles to highlight div of element #${elementId}:`, appliedStyles);
|
|
698
|
-
}
|
|
885
|
+
if (element) {
|
|
886
|
+
applyToElement(element);
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
findAndApply();
|
|
699
890
|
}
|
|
700
891
|
}
|
|
@@ -37,7 +37,9 @@ export const ENVIRONMENT_CONFIGS = {
|
|
|
37
37
|
endpoints: {
|
|
38
38
|
checkout: {
|
|
39
39
|
sessionInit: '/api/v1/checkout/session/init',
|
|
40
|
+
sessionInitAsync: '/api/v1/checkout/session/init-async',
|
|
40
41
|
sessionStatus: '/api/v1/checkout/session/status',
|
|
42
|
+
asyncStatus: '/api/public/v1/checkout/async-status',
|
|
41
43
|
},
|
|
42
44
|
customer: {
|
|
43
45
|
profile: '/api/v1/customer/profile',
|
|
@@ -53,7 +55,9 @@ export const ENVIRONMENT_CONFIGS = {
|
|
|
53
55
|
endpoints: {
|
|
54
56
|
checkout: {
|
|
55
57
|
sessionInit: '/api/v1/checkout/session/init',
|
|
58
|
+
sessionInitAsync: '/api/v1/checkout/session/init-async',
|
|
56
59
|
sessionStatus: '/api/v1/checkout/session/status',
|
|
60
|
+
asyncStatus: '/api/public/v1/checkout/async-status',
|
|
57
61
|
},
|
|
58
62
|
customer: {
|
|
59
63
|
profile: '/api/v1/customer/profile',
|
|
@@ -69,7 +73,9 @@ export const ENVIRONMENT_CONFIGS = {
|
|
|
69
73
|
endpoints: {
|
|
70
74
|
checkout: {
|
|
71
75
|
sessionInit: '/api/v1/checkout/session/init',
|
|
76
|
+
sessionInitAsync: '/api/v1/checkout/session/init-async',
|
|
72
77
|
sessionStatus: '/api/v1/checkout/session/status',
|
|
78
|
+
asyncStatus: '/api/public/v1/checkout/async-status',
|
|
73
79
|
},
|
|
74
80
|
customer: {
|
|
75
81
|
profile: '/api/v1/customer/profile',
|
|
@@ -13,6 +13,14 @@ export interface FunnelClientConfig {
|
|
|
13
13
|
* Set to false if you want to handle navigation manually
|
|
14
14
|
*/
|
|
15
15
|
autoRedirect?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Override funnelId from rawPluginConfig
|
|
18
|
+
*/
|
|
19
|
+
funnelId?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Override stepId from rawPluginConfig
|
|
22
|
+
*/
|
|
23
|
+
stepId?: string;
|
|
16
24
|
}
|
|
17
25
|
export interface FunnelState {
|
|
18
26
|
context: SimpleFunnelContext | null;
|
|
@@ -64,8 +72,17 @@ export declare class FunnelClient {
|
|
|
64
72
|
}, funnelId?: string, entryStepId?: string): Promise<SimpleFunnelContext<{}>>;
|
|
65
73
|
/**
|
|
66
74
|
* Navigate
|
|
75
|
+
* @param event - Navigation event/action
|
|
76
|
+
* @param options - Navigation options
|
|
77
|
+
* @param options.fireAndForget - If true, queues navigation to QStash and returns immediately without waiting for result
|
|
78
|
+
* @param options.customerTags - Customer tags to set (merged with existing customer tags)
|
|
79
|
+
* @param options.deviceId - Device ID for geo/device tag enrichment (optional, rarely needed)
|
|
67
80
|
*/
|
|
68
|
-
navigate(event: FunnelAction
|
|
81
|
+
navigate(event: FunnelAction, options?: {
|
|
82
|
+
fireAndForget?: boolean;
|
|
83
|
+
customerTags?: string[];
|
|
84
|
+
deviceId?: string;
|
|
85
|
+
}): Promise<FunnelNavigationResult>;
|
|
69
86
|
/**
|
|
70
87
|
* Go to a specific step (direct navigation)
|
|
71
88
|
*/
|