@tagadapay/plugin-sdk 3.0.2 → 3.0.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.
Files changed (49) hide show
  1. package/build-cdn.js +113 -0
  2. package/dist/config/basisTheory.d.ts +26 -0
  3. package/dist/config/basisTheory.js +29 -0
  4. package/dist/external-tracker.js +4947 -0
  5. package/dist/external-tracker.min.js +11 -0
  6. package/dist/external-tracker.min.js.map +7 -0
  7. package/dist/react/config/payment.d.ts +8 -8
  8. package/dist/react/config/payment.js +17 -21
  9. package/dist/react/hooks/useApplePay.js +1 -1
  10. package/dist/react/hooks/usePayment.js +1 -3
  11. package/dist/react/hooks/useThreeds.js +2 -2
  12. package/dist/v2/core/client.d.ts +31 -3
  13. package/dist/v2/core/client.js +234 -9
  14. package/dist/v2/core/config/environment.d.ts +16 -3
  15. package/dist/v2/core/config/environment.js +72 -3
  16. package/dist/v2/core/funnelClient.d.ts +4 -0
  17. package/dist/v2/core/funnelClient.js +106 -4
  18. package/dist/v2/core/resources/funnel.d.ts +22 -0
  19. package/dist/v2/core/resources/offers.d.ts +64 -3
  20. package/dist/v2/core/resources/offers.js +112 -10
  21. package/dist/v2/core/resources/postPurchases.js +4 -1
  22. package/dist/v2/core/utils/configHotReload.d.ts +39 -0
  23. package/dist/v2/core/utils/configHotReload.js +75 -0
  24. package/dist/v2/core/utils/eventBus.d.ts +11 -0
  25. package/dist/v2/core/utils/eventBus.js +34 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +14 -5
  27. package/dist/v2/core/utils/pluginConfig.js +74 -59
  28. package/dist/v2/core/utils/previewMode.d.ts +114 -0
  29. package/dist/v2/core/utils/previewMode.js +379 -0
  30. package/dist/v2/core/utils/sessionStorage.d.ts +5 -0
  31. package/dist/v2/core/utils/sessionStorage.js +22 -0
  32. package/dist/v2/index.d.ts +4 -1
  33. package/dist/v2/index.js +3 -1
  34. package/dist/v2/react/hooks/useOfferQuery.js +50 -17
  35. package/dist/v2/react/hooks/usePaymentQuery.js +1 -3
  36. package/dist/v2/react/hooks/usePreviewOffer.d.ts +84 -0
  37. package/dist/v2/react/hooks/usePreviewOffer.js +290 -0
  38. package/dist/v2/react/hooks/useThreeds.js +2 -2
  39. package/dist/v2/react/index.d.ts +2 -0
  40. package/dist/v2/react/index.js +1 -0
  41. package/dist/v2/react/providers/TagadaProvider.d.ts +1 -2
  42. package/dist/v2/react/providers/TagadaProvider.js +51 -34
  43. package/dist/v2/standalone/external-tracker.d.ts +119 -0
  44. package/dist/v2/standalone/external-tracker.js +260 -0
  45. package/dist/v2/standalone/index.d.ts +2 -0
  46. package/dist/v2/standalone/index.js +6 -0
  47. package/package.json +11 -3
  48. package/dist/v2/react/hooks/useOffersQuery.d.ts +0 -12
  49. package/dist/v2/react/hooks/useOffersQuery.js +0 -404
@@ -17,13 +17,13 @@ export declare const PAYMENT_CONFIGS: Record<string, PaymentConfig>;
17
17
  */
18
18
  export declare function getPaymentConfig(environment?: string): PaymentConfig;
19
19
  /**
20
- * Get BasisTheory API key for current environment
20
+ * Get BasisTheory API key and tenant ID based on environment detection
21
21
  *
22
- * Behavior:
23
- * - LOCAL/DEVELOPMENT: Always uses embedded test key (key_test_us_pub_VExdfbFQARn821iqP8zNaq)
24
- * Environment variables are ignored to prevent accidental production key usage
25
- *
26
- * - PRODUCTION: Uses environment variable if set, falls back to embedded production key
27
- * This allows deployment-time configuration while having safe defaults
22
+ * Uses detectEnvironment() to determine if production or test keys should be used.
23
+ * No environment variables - keys are hardcoded for consistency.
24
+ */
25
+ export declare function getBasisTheoryApiKey(): string;
26
+ /**
27
+ * Get BasisTheory tenant ID based on environment detection
28
28
  */
29
- export declare function getBasisTheoryApiKey(environment?: string): string;
29
+ export declare function getBasisTheoryTenantId(): string;
@@ -42,28 +42,24 @@ export const PAYMENT_CONFIGS = {
42
42
  export function getPaymentConfig(environment = 'local') {
43
43
  return PAYMENT_CONFIGS[environment] || PAYMENT_CONFIGS.default || PAYMENT_CONFIGS.local;
44
44
  }
45
+ import { detectEnvironment } from '../../v2/core/config/environment';
46
+ import { getBasisTheoryKeys } from '../../config/basisTheory';
45
47
  /**
46
- * Get BasisTheory API key for current environment
48
+ * Get BasisTheory API key and tenant ID based on environment detection
47
49
  *
48
- * Behavior:
49
- * - LOCAL/DEVELOPMENT: Always uses embedded test key (key_test_us_pub_VExdfbFQARn821iqP8zNaq)
50
- * Environment variables are ignored to prevent accidental production key usage
51
- *
52
- * - PRODUCTION: Uses environment variable if set, falls back to embedded production key
53
- * This allows deployment-time configuration while having safe defaults
50
+ * Uses detectEnvironment() to determine if production or test keys should be used.
51
+ * No environment variables - keys are hardcoded for consistency.
52
+ */
53
+ export function getBasisTheoryApiKey() {
54
+ const isProduction = detectEnvironment() === 'production';
55
+ const keys = getBasisTheoryKeys(isProduction);
56
+ return keys.apiKey;
57
+ }
58
+ /**
59
+ * Get BasisTheory tenant ID based on environment detection
54
60
  */
55
- export function getBasisTheoryApiKey(environment = 'local') {
56
- // For production environment, allow environment variable override
57
- if (environment === 'production') {
58
- if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY) {
59
- return process.env.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY;
60
- }
61
- if (typeof process !== 'undefined' && process.env?.VITE_BASIS_THEORY_PUBLIC_API_KEY) {
62
- return process.env.VITE_BASIS_THEORY_PUBLIC_API_KEY;
63
- }
64
- }
65
- // For local/development, always use embedded test key to prevent accidental production key usage
66
- // Fall back to embedded configuration based on environment
67
- const config = getPaymentConfig(environment);
68
- return config.basisTheory.publicApiKey;
61
+ export function getBasisTheoryTenantId() {
62
+ const isProduction = detectEnvironment() === 'production';
63
+ const keys = getBasisTheoryKeys(isProduction);
64
+ return keys.tenantId;
69
65
  }
@@ -9,7 +9,7 @@ export function useApplePay(options = {}) {
9
9
  const { createApplePayPaymentInstrument, processApplePayPayment } = usePayment();
10
10
  const { environment, apiService } = useTagadaContext();
11
11
  // Get API key from environment
12
- const apiKey = getBasisTheoryApiKey(environment?.environment || 'local');
12
+ const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
13
13
  // Check Apple Pay availability on mount
14
14
  useEffect(() => {
15
15
  const checkApplePayAvailability = () => {
@@ -24,10 +24,8 @@ export function usePayment() {
24
24
  const { createSession, startChallenge } = useThreeds();
25
25
  // Track challenge in progress to prevent multiple challenges
26
26
  const challengeInProgressRef = useRef(false);
27
- // Stabilize environment value to prevent re-renders
28
- const currentEnvironment = useMemo(() => environment?.environment || 'local', [environment?.environment]);
29
27
  // Get API key from embedded configuration with proper environment detection
30
- const apiKey = useMemo(() => getBasisTheoryApiKey(currentEnvironment), [currentEnvironment]);
28
+ const apiKey = useMemo(() => getBasisTheoryApiKey(), []); // Auto-detects environment
31
29
  // Initialize BasisTheory using React wrapper
32
30
  const { bt: basisTheory, error: btError } = useBasisTheory(apiKey, {
33
31
  elements: false,
@@ -41,7 +41,7 @@ export function useThreeds(options = {}) {
41
41
  throw new Error('BasisTheory3ds not loaded yet');
42
42
  }
43
43
  // Use the same API key approach as the working CMS version
44
- const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
44
+ const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
45
45
  const bt3ds = basisTheory3ds(apiKey);
46
46
  console.log('paymentInstrument paymentInstrument', paymentInstrument?.token);
47
47
  const session = await bt3ds.createSession({
@@ -94,7 +94,7 @@ export function useThreeds(options = {}) {
94
94
  throw new Error('BasisTheory3ds not loaded yet');
95
95
  }
96
96
  // Use the same API key approach as the working CMS version
97
- const apiKey = getBasisTheoryApiKey('production'); // Use production config for now
97
+ const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
98
98
  if (!apiKey) {
99
99
  throw new Error('BasisTheory API key is not set');
100
100
  }
@@ -1,9 +1,11 @@
1
+ import { FunnelClient } from './funnelClient';
1
2
  import { ApiClient } from './resources/apiClient';
3
+ import { AuthState, Currency, Customer, Environment, EnvironmentConfig, Locale, Session, Store } from './types';
4
+ import { EventBus } from './utils/eventBus';
2
5
  import { PluginConfig, RawPluginConfig } from './utils/pluginConfig';
3
- import { Environment, EnvironmentConfig, Session, AuthState, Customer, Store, Locale, Currency } from './types';
4
- import { FunnelClient } from './funnelClient';
5
6
  export interface TagadaClientConfig {
6
7
  environment?: Environment;
8
+ customApiConfig?: Partial<EnvironmentConfig>;
7
9
  debugMode?: boolean;
8
10
  localConfig?: string;
9
11
  rawPluginConfig?: RawPluginConfig;
@@ -16,8 +18,11 @@ export interface TagadaClientConfig {
16
18
  /**
17
19
  * Funnel session + navigation layer.
18
20
  * Set to false to completely disable funnel behaviour.
21
+ * Pass object to configure behavior.
19
22
  */
20
- funnel?: boolean;
23
+ funnel?: boolean | {
24
+ autoRedirect?: boolean;
25
+ };
21
26
  };
22
27
  }
23
28
  export interface TagadaState {
@@ -45,6 +50,10 @@ export declare class TagadaClient {
45
50
  * low-level logic without instantiating a second client.
46
51
  */
47
52
  funnel?: FunnelClient;
53
+ /**
54
+ * Event bus for domain events and coordination
55
+ */
56
+ bus: EventBus;
48
57
  private eventDispatcher;
49
58
  private tokenPromise;
50
59
  private tokenResolver;
@@ -111,4 +120,23 @@ export declare class TagadaClient {
111
120
  private updateSessionState;
112
121
  private getCurrencySymbol;
113
122
  private getCurrencyName;
123
+ /**
124
+ * Helper to get accountId with fallbacks
125
+ */
126
+ getAccountId(): string;
127
+ /**
128
+ * Update plugin config dynamically (hot-reload)
129
+ * Used for live config editing without page reload
130
+ */
131
+ updatePluginConfig(newConfig: Partial<PluginConfig['config']>): void;
132
+ /**
133
+ * Setup listener for config updates from parent window (config editor)
134
+ * Enables live config editing via postMessage
135
+ */
136
+ private setupConfigHotReload;
137
+ /**
138
+ * Apply CSS properties to a real div child element for highlighting
139
+ * Used by config editor to highlight elements in preview without altering the element's style
140
+ */
141
+ private applyStylesToElement;
114
142
  }
@@ -1,13 +1,19 @@
1
- import { ApiClient } from './resources/apiClient';
2
1
  import { detectEnvironment, getEnvironmentConfig } from './config/environment';
3
- import { loadPluginConfig } from './utils/pluginConfig';
2
+ import { FunnelClient } from './funnelClient';
3
+ import { ApiClient } from './resources/apiClient';
4
4
  import { collectDeviceInfo, getBrowserLocale, getUrlParams } from './utils/deviceInfo';
5
+ import { EventBus } from './utils/eventBus';
6
+ import { EventDispatcher } from './utils/eventDispatcher';
5
7
  import { decodeJWTClient, isTokenExpired } from './utils/jwtDecoder';
8
+ import { loadPluginConfig } from './utils/pluginConfig';
9
+ import { handlePreviewMode, isDraftMode, setDraftMode } from './utils/previewMode';
6
10
  import { getClientToken, setClientToken } from './utils/tokenStorage';
7
- import { EventDispatcher } from './utils/eventDispatcher';
8
- import { FunnelClient } from './funnelClient';
9
11
  export class TagadaClient {
10
12
  constructor(config = {}) {
13
+ /**
14
+ * Event bus for domain events and coordination
15
+ */
16
+ this.bus = new EventBus();
11
17
  this.eventDispatcher = new EventDispatcher();
12
18
  this.tokenPromise = null;
13
19
  this.tokenResolver = null;
@@ -22,9 +28,29 @@ export class TagadaClient {
22
28
  if (this.config.debugMode) {
23
29
  console.log(`[TagadaClient ${this.instanceId}] Initializing...`);
24
30
  }
31
+ // Handle preview mode FIRST - clears state if needed
32
+ // This ensures clean state when CRM previews pages
33
+ const previewModeActive = handlePreviewMode(this.config.debugMode);
34
+ if (previewModeActive && this.config.debugMode) {
35
+ console.log(`[TagadaClient ${this.instanceId}] Preview mode active - state cleared`);
36
+ }
25
37
  // Initialize default state
26
38
  const env = this.resolveEnvironment();
27
- const envConfig = getEnvironmentConfig(env);
39
+ let envConfig = getEnvironmentConfig(env);
40
+ // Apply custom API config if provided
41
+ if (config.customApiConfig) {
42
+ envConfig = {
43
+ ...envConfig,
44
+ ...config.customApiConfig,
45
+ apiConfig: {
46
+ ...envConfig.apiConfig,
47
+ ...config.customApiConfig.apiConfig,
48
+ },
49
+ };
50
+ if (this.config.debugMode) {
51
+ console.log(`[TagadaClient ${this.instanceId}] Applied custom API config:`, envConfig.apiConfig.baseUrl);
52
+ }
53
+ }
28
54
  this.state = {
29
55
  auth: {
30
56
  isAuthenticated: false,
@@ -60,13 +86,16 @@ export class TagadaClient {
60
86
  baseURL: envConfig.apiConfig.baseUrl,
61
87
  });
62
88
  // Initialize optional funnel client (feature-flagged)
63
- const funnelEnabled = config.features?.funnel !== false;
89
+ const funnelFeature = config.features?.funnel;
90
+ const funnelEnabled = funnelFeature !== false;
64
91
  if (funnelEnabled) {
92
+ const funnelConfig = typeof funnelFeature === 'object' ? funnelFeature : {};
65
93
  this.funnel = new FunnelClient({
66
94
  apiClient: this.apiClient,
67
95
  debugMode: this.state.debugMode,
68
96
  pluginConfig: this.state.pluginConfig,
69
97
  environment: this.state.environment,
98
+ autoRedirect: funnelConfig.autoRedirect,
70
99
  });
71
100
  }
72
101
  // Setup token waiting mechanism
@@ -75,6 +104,8 @@ export class TagadaClient {
75
104
  if (typeof window !== 'undefined') {
76
105
  window.addEventListener('storage', this.boundHandleStorageChange);
77
106
  }
107
+ // Setup config hot-reload listener (for live config editing)
108
+ this.setupConfigHotReload();
78
109
  // Start initialization
79
110
  this.initialize();
80
111
  }
@@ -89,6 +120,7 @@ export class TagadaClient {
89
120
  console.log(`[TagadaClient ${this.instanceId}] Destroyed`);
90
121
  }
91
122
  this.eventDispatcher.clear();
123
+ this.bus.clear();
92
124
  }
93
125
  /**
94
126
  * Handle storage changes (e.g. token update in another tab)
@@ -304,10 +336,17 @@ export class TagadaClient {
304
336
  return;
305
337
  }
306
338
  try {
307
- if (this.state.debugMode)
308
- console.log('[TagadaClient] Creating anonymous token for store:', storeId);
339
+ // 🎯 Check draft mode from URL, localStorage, or cookie
340
+ const draft = isDraftMode();
341
+ if (this.state.debugMode) {
342
+ console.log('[TagadaClient] Creating anonymous token for store:', storeId, { draft });
343
+ }
309
344
  // We use fetch directly or ApiClient with skipAuth to avoid waiting for itself
310
- const response = await this.apiClient.post('/api/v1/cms/session/anonymous', { storeId, role: 'anonymous' }, { skipAuth: true });
345
+ const response = await this.apiClient.post('/api/v1/cms/session/anonymous', {
346
+ storeId,
347
+ role: 'anonymous',
348
+ draft, // 🎯 Pass draft mode to anonymous login
349
+ }, { skipAuth: true });
311
350
  this.setToken(response.token);
312
351
  setClientToken(response.token);
313
352
  const decodedSession = decodeJWTClient(response.token);
@@ -341,6 +380,13 @@ export class TagadaClient {
341
380
  const deviceInfo = collectDeviceInfo();
342
381
  const urlParams = getUrlParams();
343
382
  const browserLocale = getBrowserLocale();
383
+ // 🎯 Check draft mode from URL, localStorage, or cookie
384
+ const draft = isDraftMode();
385
+ // Store draft mode if detected from URL (urlParams may have draft from query string)
386
+ const draftParam = new URLSearchParams(window.location.search).get('draft');
387
+ if (draftParam !== null) {
388
+ setDraftMode(draftParam === 'true');
389
+ }
344
390
  const sessionInitData = {
345
391
  storeId: sessionData.storeId,
346
392
  accountId: sessionData.accountId,
@@ -361,6 +407,7 @@ export class TagadaClient {
361
407
  screenWidth: deviceInfo.screenResolution.width,
362
408
  screenHeight: deviceInfo.screenResolution.height,
363
409
  timeZone: deviceInfo.timeZone,
410
+ draft, // 🎯 Pass draft mode to session init
364
411
  };
365
412
  const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
366
413
  // Success - reset error tracking
@@ -472,4 +519,182 @@ export class TagadaClient {
472
519
  };
473
520
  return names[code] || code;
474
521
  }
522
+ /**
523
+ * Helper to get accountId with fallbacks
524
+ */
525
+ getAccountId() {
526
+ return this.state.store?.accountId || this.state.pluginConfig?.accountId || this.state.session?.accountId || '';
527
+ }
528
+ /**
529
+ * Update plugin config dynamically (hot-reload)
530
+ * Used for live config editing without page reload
531
+ */
532
+ updatePluginConfig(newConfig) {
533
+ if (this.state.debugMode) {
534
+ console.log('[TagadaClient] Hot-reloading config:', newConfig);
535
+ }
536
+ // Merge new config with existing
537
+ const updatedConfig = {
538
+ ...this.state.pluginConfig.config,
539
+ ...newConfig,
540
+ };
541
+ // Update state
542
+ this.updateState({
543
+ pluginConfig: {
544
+ ...this.state.pluginConfig,
545
+ config: updatedConfig,
546
+ },
547
+ });
548
+ // Emit config update event
549
+ this.bus.emit('CONFIG_UPDATED', updatedConfig);
550
+ if (this.state.debugMode) {
551
+ console.log('[TagadaClient] Config updated successfully');
552
+ }
553
+ }
554
+ /**
555
+ * Setup listener for config updates from parent window (config editor)
556
+ * Enables live config editing via postMessage
557
+ */
558
+ setupConfigHotReload() {
559
+ if (typeof window === 'undefined')
560
+ return;
561
+ const handleMessage = (event) => {
562
+ // Security: Only accept messages from same origin or trusted origins
563
+ // In production, you might want to check event.origin more strictly
564
+ if (event.data?.type === 'TAGADAPAY_CONFIG_UPDATE') {
565
+ const { config } = event.data;
566
+ if (this.state.debugMode) {
567
+ console.log('[TagadaClient] Received config update from parent:', config);
568
+ }
569
+ this.updatePluginConfig(config);
570
+ }
571
+ else if (event.data?.type === 'APPLY_STYLES_TO_ELEMENT') {
572
+ const { elementId, styles } = event.data;
573
+ if (this.state.debugMode) {
574
+ console.log('[TagadaClient] Received style application request:', { elementId, styles });
575
+ }
576
+ this.applyStylesToElement(elementId, styles);
577
+ }
578
+ };
579
+ window.addEventListener('message', handleMessage);
580
+ if (this.state.debugMode) {
581
+ console.log('[TagadaClient] Config hot-reload and style manipulation listeners enabled');
582
+ }
583
+ }
584
+ /**
585
+ * Apply CSS properties to a real div child element for highlighting
586
+ * Used by config editor to highlight elements in preview without altering the element's style
587
+ */
588
+ applyStylesToElement(elementId, styles) {
589
+ if (typeof document === 'undefined')
590
+ return;
591
+ // Support multiple IDs in editor-id attribute (space-separated)
592
+ // Use ~= selector to match elementId as a space-separated value
593
+ const element = document.querySelector(`[editor-id~="${elementId}"]`);
594
+ if (!element) {
595
+ if (this.state.debugMode) {
596
+ console.warn(`[TagadaClient] Element with editor-id containing "${elementId}" not found`);
597
+ }
598
+ return;
599
+ }
600
+ // List of void/self-closing elements that don't accept children
601
+ const voidElements = new Set([
602
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
603
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'
604
+ ]);
605
+ const isVoidElement = voidElements.has(element.tagName.toLowerCase());
606
+ // For void elements, wrap them in a container div
607
+ let targetElement = element;
608
+ let wrapper = null;
609
+ if (isVoidElement) {
610
+ // Check if element is already wrapped (from a previous highlight)
611
+ const parent = element.parentElement;
612
+ const existingWrapper = parent?.getAttribute('data-tagada-highlight-wrapper');
613
+ if (existingWrapper === 'true') {
614
+ // Reuse existing wrapper
615
+ targetElement = parent;
616
+ }
617
+ else {
618
+ // Create a new wrapper
619
+ wrapper = document.createElement('div');
620
+ wrapper.setAttribute('data-tagada-highlight-wrapper', 'true');
621
+ // Preserve the element's layout behavior by matching its display style
622
+ // This minimizes the visual impact of the wrapper
623
+ const computedStyle = window.getComputedStyle(element);
624
+ const elementDisplay = computedStyle.display;
625
+ // Match the display type to preserve layout
626
+ // For inline elements, use inline-block (to support position: relative)
627
+ // For all others, use the same display value
628
+ if (elementDisplay === 'inline') {
629
+ wrapper.style.display = 'inline-block';
630
+ }
631
+ else {
632
+ wrapper.style.display = elementDisplay;
633
+ }
634
+ wrapper.style.position = 'relative';
635
+ // Preserve vertical alignment for inline elements
636
+ if (elementDisplay === 'inline' || elementDisplay.includes('inline')) {
637
+ const verticalAlign = computedStyle.verticalAlign;
638
+ if (verticalAlign && verticalAlign !== 'baseline') {
639
+ wrapper.style.verticalAlign = verticalAlign;
640
+ }
641
+ }
642
+ // Insert wrapper before element
643
+ element.parentNode?.insertBefore(wrapper, element);
644
+ // Move element into wrapper
645
+ wrapper.appendChild(element);
646
+ targetElement = wrapper;
647
+ }
648
+ }
649
+ // Ensure element has position relative for absolute child positioning
650
+ const computedStyle = getComputedStyle(isVoidElement ? element : targetElement);
651
+ if (targetElement.style.position === 'static' || !targetElement.style.position) {
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);
688
+ }
689
+ else {
690
+ highlightDiv.style[camelProperty] = value;
691
+ }
692
+ });
693
+ // Scroll element into view
694
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
695
+ if (this.state.debugMode) {
696
+ const appliedStyles = Object.entries(stylesToApply).map(([k, v]) => `${k}: ${v}`).join('; ');
697
+ console.log(`[TagadaClient] Applied styles to highlight div of element #${elementId}:`, appliedStyles);
698
+ }
699
+ }
475
700
  }
@@ -5,9 +5,14 @@ import { ApiConfig, Environment, EnvironmentConfig } from '../types';
5
5
  * This SDK uses RUNTIME hostname detection, NOT build-time environment variables.
6
6
  * This ensures the SDK always connects to the correct API based on where it's deployed.
7
7
  *
8
- * - Production domains production API
9
- * - Dev/staging domains development API
10
- * - Localhost/local IPs → local API (with optional override via window.__TAGADA_ENV__)
8
+ * Environment detection priority (highest to lowest):
9
+ * 1. **tagadaClientEnv** - Explicit override via URL param, localStorage, or cookie
10
+ * Example: ?tagadaClientEnv=production
11
+ * 2. **Production domains** → production API (app.tagadapay.com)
12
+ * 3. **Dev/staging domains** → development API (app.tagadapay.dev, vercel.app, etc.)
13
+ * 4. **Localhost/local IPs** → local API (localhost, 127.0.0.1, etc.)
14
+ * - Can be overridden via window.__TAGADA_ENV__.TAGADA_ENVIRONMENT
15
+ * 5. **Default fallback** → production API (safest for unknown domains)
11
16
  *
12
17
  * Build-time .env variables (VITE_*, REACT_APP_*, NEXT_PUBLIC_*) are IGNORED
13
18
  * to prevent incorrect API connections when plugins are deployed to different environments.
@@ -18,6 +23,8 @@ import { ApiConfig, Environment, EnvironmentConfig } from '../types';
18
23
  export declare const ENVIRONMENT_CONFIGS: Record<Environment, ApiConfig>;
19
24
  /**
20
25
  * Get the environment configuration based on the current environment
26
+ *
27
+ * Checks for custom base URL override via tagadaClientBaseUrl parameter
21
28
  */
22
29
  export declare function getEnvironmentConfig(environment?: Environment): EnvironmentConfig;
23
30
  /**
@@ -32,6 +39,12 @@ export declare function getEndpointUrl(config: EnvironmentConfig, category: keyo
32
39
  * Auto-detect environment based on hostname and URL patterns at RUNTIME
33
40
  * ⚠️ IMPORTANT: Ignores build-time .env variables to ensure correct detection in all environments
34
41
  * .env variables are ONLY used for local development via window.__TAGADA_ENV__
42
+ *
43
+ * Priority (highest to lowest):
44
+ * 1. tagadaClientEnv - Explicit override via URL/localStorage/cookie
45
+ * 2. __TAGADA_ENV__ - Local development override
46
+ * 3. Hostname-based detection - Production/staging domains
47
+ * 4. Default fallback - Production (safest)
35
48
  */
36
49
  export declare function detectEnvironment(): Environment;
37
50
  /**
@@ -1,12 +1,29 @@
1
+ /**
2
+ * Get cookie value by name
3
+ */
4
+ function getCookie(name) {
5
+ if (typeof document === 'undefined')
6
+ return null;
7
+ const value = `; ${document.cookie}`;
8
+ const parts = value.split(`; ${name}=`);
9
+ if (parts.length === 2)
10
+ return parts.pop()?.split(';').shift() || null;
11
+ return null;
12
+ }
1
13
  /**
2
14
  * ⚠️ IMPORTANT: Runtime Environment Detection
3
15
  *
4
16
  * This SDK uses RUNTIME hostname detection, NOT build-time environment variables.
5
17
  * This ensures the SDK always connects to the correct API based on where it's deployed.
6
18
  *
7
- * - Production domains production API
8
- * - Dev/staging domains development API
9
- * - Localhost/local IPs → local API (with optional override via window.__TAGADA_ENV__)
19
+ * Environment detection priority (highest to lowest):
20
+ * 1. **tagadaClientEnv** - Explicit override via URL param, localStorage, or cookie
21
+ * Example: ?tagadaClientEnv=production
22
+ * 2. **Production domains** → production API (app.tagadapay.com)
23
+ * 3. **Dev/staging domains** → development API (app.tagadapay.dev, vercel.app, etc.)
24
+ * 4. **Localhost/local IPs** → local API (localhost, 127.0.0.1, etc.)
25
+ * - Can be overridden via window.__TAGADA_ENV__.TAGADA_ENVIRONMENT
26
+ * 5. **Default fallback** → production API (safest for unknown domains)
10
27
  *
11
28
  * Build-time .env variables (VITE_*, REACT_APP_*, NEXT_PUBLIC_*) are IGNORED
12
29
  * to prevent incorrect API connections when plugins are deployed to different environments.
@@ -66,6 +83,8 @@ export const ENVIRONMENT_CONFIGS = {
66
83
  };
67
84
  /**
68
85
  * Get the environment configuration based on the current environment
86
+ *
87
+ * Checks for custom base URL override via tagadaClientBaseUrl parameter
69
88
  */
70
89
  export function getEnvironmentConfig(environment = 'local') {
71
90
  const apiConfig = ENVIRONMENT_CONFIGS[environment];
@@ -76,6 +95,31 @@ export function getEnvironmentConfig(environment = 'local') {
76
95
  apiConfig: ENVIRONMENT_CONFIGS.local,
77
96
  };
78
97
  }
98
+ // 🎯 Check for custom base URL override (URL > localStorage > cookie)
99
+ let customBaseUrl = null;
100
+ if (typeof window !== 'undefined') {
101
+ const urlParams = new URLSearchParams(window.location.search);
102
+ customBaseUrl = urlParams.get('tagadaClientBaseUrl');
103
+ if (!customBaseUrl) {
104
+ try {
105
+ customBaseUrl = localStorage.getItem('tgd_client_base_url') || getCookie('tgd_client_base_url');
106
+ }
107
+ catch {
108
+ // Storage not available
109
+ }
110
+ }
111
+ }
112
+ // If custom base URL is set, override the apiConfig.baseUrl
113
+ if (customBaseUrl) {
114
+ console.log(`[SDK] Using custom API base URL override: ${customBaseUrl}`);
115
+ return {
116
+ environment,
117
+ apiConfig: {
118
+ ...apiConfig,
119
+ baseUrl: customBaseUrl,
120
+ },
121
+ };
122
+ }
79
123
  return {
80
124
  environment,
81
125
  apiConfig,
@@ -102,12 +146,37 @@ export function getEndpointUrl(config, category, endpoint) {
102
146
  * Auto-detect environment based on hostname and URL patterns at RUNTIME
103
147
  * ⚠️ IMPORTANT: Ignores build-time .env variables to ensure correct detection in all environments
104
148
  * .env variables are ONLY used for local development via window.__TAGADA_ENV__
149
+ *
150
+ * Priority (highest to lowest):
151
+ * 1. tagadaClientEnv - Explicit override via URL/localStorage/cookie
152
+ * 2. __TAGADA_ENV__ - Local development override
153
+ * 3. Hostname-based detection - Production/staging domains
154
+ * 4. Default fallback - Production (safest)
105
155
  */
106
156
  export function detectEnvironment() {
107
157
  // Check if we're in browser
108
158
  if (typeof window === 'undefined') {
109
159
  return 'local'; // SSR fallback
110
160
  }
161
+ // 🎯 PRIORITY 1: Check for explicit tagadaClientEnv override (URL > localStorage > cookie)
162
+ // This allows forcing environment regardless of hostname
163
+ const urlParams = new URLSearchParams(window.location.search);
164
+ const urlEnv = urlParams.get('tagadaClientEnv');
165
+ if (urlEnv && (urlEnv === 'production' || urlEnv === 'development' || urlEnv === 'local')) {
166
+ console.log(`[SDK] Using explicit environment override: ${urlEnv}`);
167
+ return urlEnv;
168
+ }
169
+ // Check localStorage/cookie for persisted override
170
+ try {
171
+ const storageEnv = localStorage.getItem('tgd_client_env') || getCookie('tgd_client_env');
172
+ if (storageEnv && (storageEnv === 'production' || storageEnv === 'development' || storageEnv === 'local')) {
173
+ console.log(`[SDK] Using persisted environment override: ${storageEnv}`);
174
+ return storageEnv;
175
+ }
176
+ }
177
+ catch {
178
+ // Storage not available
179
+ }
111
180
  const hostname = window.location.hostname;
112
181
  const href = window.location.href;
113
182
  // 1. Check for LOCAL environment first (highest priority for dev)
@@ -66,6 +66,10 @@ export declare class FunnelClient {
66
66
  * Navigate
67
67
  */
68
68
  navigate(event: FunnelAction): Promise<FunnelNavigationResult>;
69
+ /**
70
+ * Go to a specific step (direct navigation)
71
+ */
72
+ goToStep(stepId: string): Promise<FunnelNavigationResult>;
69
73
  /**
70
74
  * Refresh session data
71
75
  */