@tagadapay/plugin-sdk 3.0.3 → 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.
- package/build-cdn.js +113 -0
- package/dist/config/basisTheory.d.ts +26 -0
- package/dist/config/basisTheory.js +29 -0
- package/dist/external-tracker.js +4947 -0
- package/dist/external-tracker.min.js +11 -0
- package/dist/external-tracker.min.js.map +7 -0
- package/dist/react/config/payment.d.ts +8 -8
- package/dist/react/config/payment.js +17 -21
- package/dist/react/hooks/useApplePay.js +1 -1
- package/dist/react/hooks/usePayment.js +1 -3
- package/dist/react/hooks/useThreeds.js +2 -2
- package/dist/v2/core/client.d.ts +30 -3
- package/dist/v2/core/client.js +219 -8
- package/dist/v2/core/config/environment.d.ts +16 -3
- package/dist/v2/core/config/environment.js +72 -3
- package/dist/v2/core/funnelClient.d.ts +4 -0
- package/dist/v2/core/funnelClient.js +106 -4
- package/dist/v2/core/resources/funnel.d.ts +22 -0
- package/dist/v2/core/resources/offers.d.ts +64 -3
- package/dist/v2/core/resources/offers.js +112 -10
- package/dist/v2/core/resources/postPurchases.js +4 -1
- package/dist/v2/core/utils/configHotReload.d.ts +39 -0
- package/dist/v2/core/utils/configHotReload.js +75 -0
- package/dist/v2/core/utils/eventBus.d.ts +11 -0
- package/dist/v2/core/utils/eventBus.js +34 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +14 -5
- package/dist/v2/core/utils/pluginConfig.js +74 -59
- package/dist/v2/core/utils/previewMode.d.ts +114 -0
- package/dist/v2/core/utils/previewMode.js +379 -0
- package/dist/v2/core/utils/sessionStorage.d.ts +5 -0
- package/dist/v2/core/utils/sessionStorage.js +22 -0
- package/dist/v2/index.d.ts +4 -1
- package/dist/v2/index.js +3 -1
- package/dist/v2/react/hooks/useOfferQuery.js +50 -17
- package/dist/v2/react/hooks/usePaymentQuery.js +1 -3
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +84 -0
- package/dist/v2/react/hooks/usePreviewOffer.js +290 -0
- package/dist/v2/react/hooks/useThreeds.js +2 -2
- package/dist/v2/react/index.d.ts +2 -0
- package/dist/v2/react/index.js +1 -0
- package/dist/v2/react/providers/TagadaProvider.js +49 -32
- package/dist/v2/standalone/external-tracker.d.ts +119 -0
- package/dist/v2/standalone/external-tracker.js +260 -0
- package/dist/v2/standalone/index.d.ts +2 -0
- package/dist/v2/standalone/index.js +6 -0
- package/package.json +11 -3
- package/dist/v2/react/hooks/useOffersQuery.d.ts +0 -12
- 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
|
|
20
|
+
* Get BasisTheory API key and tenant ID based on environment detection
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*
|
|
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
|
|
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
|
|
48
|
+
* Get BasisTheory API key and tenant ID based on environment detection
|
|
47
49
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
97
|
+
const apiKey = getBasisTheoryApiKey(); // Auto-detects environment
|
|
98
98
|
if (!apiKey) {
|
|
99
99
|
throw new Error('BasisTheory API key is not set');
|
|
100
100
|
}
|
package/dist/v2/core/client.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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;
|
|
7
8
|
customApiConfig?: Partial<EnvironmentConfig>;
|
|
@@ -17,8 +18,11 @@ export interface TagadaClientConfig {
|
|
|
17
18
|
/**
|
|
18
19
|
* Funnel session + navigation layer.
|
|
19
20
|
* Set to false to completely disable funnel behaviour.
|
|
21
|
+
* Pass object to configure behavior.
|
|
20
22
|
*/
|
|
21
|
-
funnel?: boolean
|
|
23
|
+
funnel?: boolean | {
|
|
24
|
+
autoRedirect?: boolean;
|
|
25
|
+
};
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
28
|
export interface TagadaState {
|
|
@@ -46,6 +50,10 @@ export declare class TagadaClient {
|
|
|
46
50
|
* low-level logic without instantiating a second client.
|
|
47
51
|
*/
|
|
48
52
|
funnel?: FunnelClient;
|
|
53
|
+
/**
|
|
54
|
+
* Event bus for domain events and coordination
|
|
55
|
+
*/
|
|
56
|
+
bus: EventBus;
|
|
49
57
|
private eventDispatcher;
|
|
50
58
|
private tokenPromise;
|
|
51
59
|
private tokenResolver;
|
|
@@ -112,4 +120,23 @@ export declare class TagadaClient {
|
|
|
112
120
|
private updateSessionState;
|
|
113
121
|
private getCurrencySymbol;
|
|
114
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;
|
|
115
142
|
}
|
package/dist/v2/core/client.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
import { ApiClient } from './resources/apiClient';
|
|
2
1
|
import { detectEnvironment, getEnvironmentConfig } from './config/environment';
|
|
3
|
-
import {
|
|
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,6 +28,12 @@ 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
39
|
let envConfig = getEnvironmentConfig(env);
|
|
@@ -74,13 +86,16 @@ export class TagadaClient {
|
|
|
74
86
|
baseURL: envConfig.apiConfig.baseUrl,
|
|
75
87
|
});
|
|
76
88
|
// Initialize optional funnel client (feature-flagged)
|
|
77
|
-
const
|
|
89
|
+
const funnelFeature = config.features?.funnel;
|
|
90
|
+
const funnelEnabled = funnelFeature !== false;
|
|
78
91
|
if (funnelEnabled) {
|
|
92
|
+
const funnelConfig = typeof funnelFeature === 'object' ? funnelFeature : {};
|
|
79
93
|
this.funnel = new FunnelClient({
|
|
80
94
|
apiClient: this.apiClient,
|
|
81
95
|
debugMode: this.state.debugMode,
|
|
82
96
|
pluginConfig: this.state.pluginConfig,
|
|
83
97
|
environment: this.state.environment,
|
|
98
|
+
autoRedirect: funnelConfig.autoRedirect,
|
|
84
99
|
});
|
|
85
100
|
}
|
|
86
101
|
// Setup token waiting mechanism
|
|
@@ -89,6 +104,8 @@ export class TagadaClient {
|
|
|
89
104
|
if (typeof window !== 'undefined') {
|
|
90
105
|
window.addEventListener('storage', this.boundHandleStorageChange);
|
|
91
106
|
}
|
|
107
|
+
// Setup config hot-reload listener (for live config editing)
|
|
108
|
+
this.setupConfigHotReload();
|
|
92
109
|
// Start initialization
|
|
93
110
|
this.initialize();
|
|
94
111
|
}
|
|
@@ -103,6 +120,7 @@ export class TagadaClient {
|
|
|
103
120
|
console.log(`[TagadaClient ${this.instanceId}] Destroyed`);
|
|
104
121
|
}
|
|
105
122
|
this.eventDispatcher.clear();
|
|
123
|
+
this.bus.clear();
|
|
106
124
|
}
|
|
107
125
|
/**
|
|
108
126
|
* Handle storage changes (e.g. token update in another tab)
|
|
@@ -318,10 +336,17 @@ export class TagadaClient {
|
|
|
318
336
|
return;
|
|
319
337
|
}
|
|
320
338
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
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
|
+
}
|
|
323
344
|
// We use fetch directly or ApiClient with skipAuth to avoid waiting for itself
|
|
324
|
-
const response = await this.apiClient.post('/api/v1/cms/session/anonymous', {
|
|
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 });
|
|
325
350
|
this.setToken(response.token);
|
|
326
351
|
setClientToken(response.token);
|
|
327
352
|
const decodedSession = decodeJWTClient(response.token);
|
|
@@ -355,6 +380,13 @@ export class TagadaClient {
|
|
|
355
380
|
const deviceInfo = collectDeviceInfo();
|
|
356
381
|
const urlParams = getUrlParams();
|
|
357
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
|
+
}
|
|
358
390
|
const sessionInitData = {
|
|
359
391
|
storeId: sessionData.storeId,
|
|
360
392
|
accountId: sessionData.accountId,
|
|
@@ -375,6 +407,7 @@ export class TagadaClient {
|
|
|
375
407
|
screenWidth: deviceInfo.screenResolution.width,
|
|
376
408
|
screenHeight: deviceInfo.screenResolution.height,
|
|
377
409
|
timeZone: deviceInfo.timeZone,
|
|
410
|
+
draft, // 🎯 Pass draft mode to session init
|
|
378
411
|
};
|
|
379
412
|
const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
|
|
380
413
|
// Success - reset error tracking
|
|
@@ -486,4 +519,182 @@ export class TagadaClient {
|
|
|
486
519
|
};
|
|
487
520
|
return names[code] || code;
|
|
488
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
|
+
}
|
|
489
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
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
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
|
-
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
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
|
*/
|