@tagadapay/plugin-sdk 2.8.9 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -14
- package/dist/index.js +1 -1
- package/dist/react/hooks/usePluginConfig.d.ts +1 -0
- package/dist/react/hooks/usePluginConfig.js +69 -18
- package/dist/react/providers/TagadaProvider.js +1 -4
- package/dist/v2/core/client.d.ts +22 -0
- package/dist/v2/core/client.js +90 -1
- package/dist/v2/core/config/environment.d.ts +24 -2
- package/dist/v2/core/config/environment.js +58 -25
- package/dist/v2/core/funnelClient.d.ts +84 -0
- package/dist/v2/core/funnelClient.js +252 -0
- package/dist/v2/core/index.d.ts +2 -0
- package/dist/v2/core/index.js +3 -0
- package/dist/v2/core/resources/apiClient.d.ts +5 -0
- package/dist/v2/core/resources/apiClient.js +47 -0
- package/dist/v2/core/resources/funnel.d.ts +8 -0
- package/dist/v2/core/resources/offers.d.ts +182 -8
- package/dist/v2/core/resources/offers.js +25 -0
- package/dist/v2/core/resources/products.d.ts +5 -0
- package/dist/v2/core/resources/products.js +15 -1
- package/dist/v2/core/types.d.ts +1 -0
- package/dist/v2/core/utils/funnelQueryKeys.d.ts +23 -0
- package/dist/v2/core/utils/funnelQueryKeys.js +23 -0
- package/dist/v2/core/utils/index.d.ts +2 -0
- package/dist/v2/core/utils/index.js +2 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.js +84 -22
- package/dist/v2/core/utils/sessionStorage.d.ts +20 -0
- package/dist/v2/core/utils/sessionStorage.js +39 -0
- package/dist/v2/index.d.ts +3 -2
- package/dist/v2/index.js +1 -1
- package/dist/v2/react/components/ApplePayButton.js +1 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +3 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +4 -3
- package/dist/v2/react/hooks/useClubOffers.d.ts +2 -2
- package/dist/v2/react/hooks/useFunnel.d.ts +27 -38
- package/dist/v2/react/hooks/useFunnel.js +22 -660
- package/dist/v2/react/hooks/useFunnelLegacy.d.ts +52 -0
- package/dist/v2/react/hooks/useFunnelLegacy.js +733 -0
- package/dist/v2/react/hooks/useOfferQuery.d.ts +109 -0
- package/dist/v2/react/hooks/useOfferQuery.js +483 -0
- package/dist/v2/react/hooks/useOffersQuery.d.ts +10 -58
- package/dist/v2/react/hooks/useOffersQuery.js +110 -8
- package/dist/v2/react/hooks/useProductsQuery.d.ts +1 -0
- package/dist/v2/react/hooks/useProductsQuery.js +10 -6
- package/dist/v2/react/index.d.ts +7 -4
- package/dist/v2/react/index.js +4 -2
- package/dist/v2/react/providers/TagadaProvider.d.ts +45 -2
- package/dist/v2/react/providers/TagadaProvider.js +116 -3
- package/dist/v2/standalone/index.d.ts +20 -0
- package/dist/v2/standalone/index.js +22 -0
- package/dist/v2/vue/index.d.ts +6 -0
- package/dist/v2/vue/index.js +10 -0
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A comprehensive React SDK for building plugins on the TagadaPay platform. Create
|
|
|
4
4
|
|
|
5
5
|
> **🚀 V2 Now Available!** The new V2 architecture features TanStack Query integration, improved TypeScript support, and better performance. [See V2 Architecture](#-v2-architecture) for details.
|
|
6
6
|
>
|
|
7
|
-
> **Recommended**: Use `@tagadapay/plugin-sdk/
|
|
7
|
+
> **Recommended**: Use `@tagadapay/plugin-sdk/v3` for new projects. V1 (`/react`) is still supported for existing projects.
|
|
8
8
|
|
|
9
9
|
## 📚 Documentation
|
|
10
10
|
|
|
@@ -146,7 +146,7 @@ import {
|
|
|
146
146
|
useISOData,
|
|
147
147
|
useCheckout,
|
|
148
148
|
formatMoney,
|
|
149
|
-
} from '@tagadapay/plugin-sdk/
|
|
149
|
+
} from '@tagadapay/plugin-sdk/v3';
|
|
150
150
|
|
|
151
151
|
function MyPlugin() {
|
|
152
152
|
const { config, storeId, accountId, basePath, loading } = usePluginConfig();
|
|
@@ -199,7 +199,7 @@ export default App;
|
|
|
199
199
|
|
|
200
200
|
### What's New in V2
|
|
201
201
|
|
|
202
|
-
The TagadaPay Plugin SDK
|
|
202
|
+
The TagadaPay Plugin SDK v3 introduces a clean architecture with significant improvements:
|
|
203
203
|
|
|
204
204
|
#### **🔄 TanStack Query Integration**
|
|
205
205
|
|
|
@@ -218,7 +218,7 @@ The TagadaPay Plugin SDK v2 introduces a clean architecture with significant imp
|
|
|
218
218
|
|
|
219
219
|
```tsx
|
|
220
220
|
// V2 (Recommended)
|
|
221
|
-
import { useCheckout, useOffers, TagadaProvider } from '@tagadapay/plugin-sdk/
|
|
221
|
+
import { useCheckout, useOffers, TagadaProvider } from '@tagadapay/plugin-sdk/v3';
|
|
222
222
|
|
|
223
223
|
// Legacy (Still supported)
|
|
224
224
|
import { useCheckout, useOffers, TagadaProvider } from '@tagadapay/plugin-sdk/react';
|
|
@@ -257,7 +257,7 @@ import {
|
|
|
257
257
|
useProducts,
|
|
258
258
|
usePluginConfig,
|
|
259
259
|
formatMoney,
|
|
260
|
-
} from '@tagadapay/plugin-sdk/
|
|
260
|
+
} from '@tagadapay/plugin-sdk/v3';
|
|
261
261
|
|
|
262
262
|
function CheckoutPage() {
|
|
263
263
|
const [checkoutToken, setCheckoutToken] = useState<string>();
|
|
@@ -355,7 +355,7 @@ function App() {
|
|
|
355
355
|
|
|
356
356
|
```tsx
|
|
357
357
|
import React from 'react';
|
|
358
|
-
import { TagadaProvider, useCheckout, useOffers, formatMoney } from '@tagadapay/plugin-sdk/
|
|
358
|
+
import { TagadaProvider, useCheckout, useOffers, formatMoney } from '@tagadapay/plugin-sdk/v3';
|
|
359
359
|
|
|
360
360
|
// Component 1: Cart Summary
|
|
361
361
|
function CartSummary({ checkoutToken }: { checkoutToken: string }) {
|
|
@@ -411,7 +411,7 @@ function CheckoutWithOffers() {
|
|
|
411
411
|
|
|
412
412
|
```tsx
|
|
413
413
|
import React from 'react';
|
|
414
|
-
import { useCheckout } from '@tagadapay/plugin-sdk/
|
|
414
|
+
import { useCheckout } from '@tagadapay/plugin-sdk/v3';
|
|
415
415
|
|
|
416
416
|
function QuantitySelector({
|
|
417
417
|
checkoutToken,
|
|
@@ -555,7 +555,7 @@ interface TagadaProviderProps {
|
|
|
555
555
|
#### V2 Enhanced Provider Features
|
|
556
556
|
|
|
557
557
|
```tsx
|
|
558
|
-
import { TagadaProvider } from '@tagadapay/plugin-sdk/
|
|
558
|
+
import { TagadaProvider } from '@tagadapay/plugin-sdk/v3';
|
|
559
559
|
|
|
560
560
|
function App() {
|
|
561
561
|
return (
|
|
@@ -577,7 +577,7 @@ function App() {
|
|
|
577
577
|
}
|
|
578
578
|
```
|
|
579
579
|
|
|
580
|
-
> **Version Compatibility:** V2 features require `@tagadapay/plugin-sdk/
|
|
580
|
+
> **Version Compatibility:** V2 features require `@tagadapay/plugin-sdk/v3`. The `blockUntilSessionReady` option was added in v2.3.0. For older versions, the blocking behavior was the default and only option.
|
|
581
581
|
|
|
582
582
|
### Development vs Production
|
|
583
583
|
|
|
@@ -729,7 +729,7 @@ const {
|
|
|
729
729
|
**Example:**
|
|
730
730
|
|
|
731
731
|
```tsx
|
|
732
|
-
import { useStoreConfig } from '@tagadapay/plugin-sdk/
|
|
732
|
+
import { useStoreConfig } from '@tagadapay/plugin-sdk/v3';
|
|
733
733
|
|
|
734
734
|
function StoreInfo() {
|
|
735
735
|
const { storeConfig, isLoading } = useStoreConfig();
|
|
@@ -808,7 +808,7 @@ import {
|
|
|
808
808
|
useInvalidateQuery,
|
|
809
809
|
usePreloadQuery,
|
|
810
810
|
queryKeys,
|
|
811
|
-
} from '@tagadapay/plugin-sdk/
|
|
811
|
+
} from '@tagadapay/plugin-sdk/v3';
|
|
812
812
|
|
|
813
813
|
// Custom API queries
|
|
814
814
|
const { data, isLoading } = useApiQuery({
|
|
@@ -838,7 +838,7 @@ preloadCheckout({
|
|
|
838
838
|
Enhanced money formatting with better TypeScript support:
|
|
839
839
|
|
|
840
840
|
```typescript
|
|
841
|
-
import { formatMoney, convertCurrency, getCurrencyInfo } from '@tagadapay/plugin-sdk/
|
|
841
|
+
import { formatMoney, convertCurrency, getCurrencyInfo } from '@tagadapay/plugin-sdk/v3';
|
|
842
842
|
|
|
843
843
|
// Format money with automatic currency detection
|
|
844
844
|
const formatted = formatMoney(2999, 'USD'); // "$29.99"
|
|
@@ -867,7 +867,7 @@ import {
|
|
|
867
867
|
ProductsResource,
|
|
868
868
|
PaymentsResource,
|
|
869
869
|
PluginConfigUtils,
|
|
870
|
-
} from '@tagadapay/plugin-sdk/
|
|
870
|
+
} from '@tagadapay/plugin-sdk/v3';
|
|
871
871
|
|
|
872
872
|
// Use core functions directly (useful for server-side or non-React contexts)
|
|
873
873
|
const checkoutResource = new CheckoutResource(apiClient);
|
|
@@ -883,7 +883,7 @@ const isValid = PluginConfigUtils.validateConfig(config);
|
|
|
883
883
|
V2 includes built-in debugging tools:
|
|
884
884
|
|
|
885
885
|
```tsx
|
|
886
|
-
import { TagadaProvider } from '@tagadapay/plugin-sdk/
|
|
886
|
+
import { TagadaProvider } from '@tagadapay/plugin-sdk/v3';
|
|
887
887
|
|
|
888
888
|
function App() {
|
|
889
889
|
return (
|
package/dist/index.js
CHANGED
|
@@ -15,5 +15,5 @@ export { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMon
|
|
|
15
15
|
// Export data utilities
|
|
16
16
|
export * from './data/iso3166';
|
|
17
17
|
export * from './data/languages';
|
|
18
|
-
// V2 exports -
|
|
18
|
+
// V2 exports - production-ready clean architecture
|
|
19
19
|
export * from './v2';
|
|
@@ -23,6 +23,7 @@ export interface PluginConfig<TConfig = Record<string, any>> {
|
|
|
23
23
|
accountId?: string;
|
|
24
24
|
basePath?: string;
|
|
25
25
|
config?: TConfig;
|
|
26
|
+
staticResources?: Record<string, any>;
|
|
26
27
|
}
|
|
27
28
|
export interface RawPluginConfig<TConfig = Record<string, any>> {
|
|
28
29
|
storeId?: string;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* - Headers for store/account info
|
|
19
19
|
* - Meta tags for deployment config
|
|
20
20
|
*/
|
|
21
|
+
import { isLocalEnvironment } from '../../v2/core/config/environment';
|
|
21
22
|
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
22
23
|
// Simple cache for plugin configuration
|
|
23
24
|
let cachedConfig = null;
|
|
@@ -29,13 +30,7 @@ let configPromise = null;
|
|
|
29
30
|
const loadLocalDevConfig = async (configVariant = 'default') => {
|
|
30
31
|
try {
|
|
31
32
|
// Only try to load local config in development
|
|
32
|
-
|
|
33
|
-
const isLocalDev = typeof window !== 'undefined' &&
|
|
34
|
-
(window.location.hostname === 'localhost' ||
|
|
35
|
-
window.location.hostname.includes('ngrok-free.app') ||
|
|
36
|
-
window.location.hostname.includes('.localhost') ||
|
|
37
|
-
window.location.hostname.includes('127.0.0.1'));
|
|
38
|
-
if (!isLocalDev) {
|
|
33
|
+
if (!isLocalEnvironment()) {
|
|
39
34
|
return null;
|
|
40
35
|
}
|
|
41
36
|
// Load local store/account config
|
|
@@ -141,28 +136,89 @@ const loadProductionConfig = async () => {
|
|
|
141
136
|
return { basePath: '/', config: {} };
|
|
142
137
|
}
|
|
143
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* Load static resources for local development
|
|
141
|
+
* Loads /config/resources.static.json
|
|
142
|
+
*/
|
|
143
|
+
const loadStaticResources = async () => {
|
|
144
|
+
try {
|
|
145
|
+
// Only try to load in TRUE local development (not deployed CDN instances)
|
|
146
|
+
// Exclude CDN subdomains (e.g., instance-id.cdn.localhost)
|
|
147
|
+
if (!isLocalEnvironment(true)) {
|
|
148
|
+
console.log('🛠️ Not in local dev environment, skipping static resources loading');
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
// Load static resources file
|
|
152
|
+
console.log('🛠️ [V1] Attempting to load /config/resources.static.json...');
|
|
153
|
+
const response = await fetch('/config/resources.static.json');
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
console.log('🛠️ [V1] resources.static.json not found or failed to load');
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
const staticResources = await response.json();
|
|
159
|
+
console.log('🛠️ [V1] ✅ Loaded local static resources:', staticResources);
|
|
160
|
+
return staticResources;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
console.error('🛠️ ❌ Error loading static resources:', error);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
144
167
|
/**
|
|
145
168
|
* Load plugin configuration (cached)
|
|
146
169
|
* Tries raw config first, then local dev config, then production config
|
|
147
170
|
*/
|
|
148
171
|
export const loadPluginConfig = async (configVariant = 'default', rawConfig) => {
|
|
172
|
+
console.log('🔧 [V1] loadPluginConfig called with variant:', configVariant);
|
|
173
|
+
// Load static resources first (only in local dev)
|
|
174
|
+
const staticResources = await loadStaticResources();
|
|
175
|
+
console.log('🔧 [V1] Static resources loaded:', {
|
|
176
|
+
hasStaticResources: !!staticResources,
|
|
177
|
+
staticResourcesKeys: staticResources ? Object.keys(staticResources) : [],
|
|
178
|
+
});
|
|
149
179
|
// If raw config is provided, use it directly
|
|
150
180
|
if (rawConfig) {
|
|
151
|
-
console.log('🛠️ Using raw plugin config:', rawConfig);
|
|
152
|
-
|
|
181
|
+
console.log('🛠️ [V1] Using raw plugin config:', rawConfig);
|
|
182
|
+
const result = {
|
|
153
183
|
storeId: rawConfig.storeId,
|
|
154
184
|
accountId: rawConfig.accountId,
|
|
155
185
|
basePath: rawConfig.basePath ?? '/',
|
|
156
186
|
config: rawConfig.config ?? {},
|
|
187
|
+
staticResources: staticResources ?? undefined,
|
|
157
188
|
};
|
|
189
|
+
console.log('🔧 [V1] Final config (raw):', {
|
|
190
|
+
hasStoreId: !!result.storeId,
|
|
191
|
+
hasStaticResources: !!result.staticResources,
|
|
192
|
+
staticResourcesKeys: result.staticResources ? Object.keys(result.staticResources) : [],
|
|
193
|
+
});
|
|
194
|
+
return result;
|
|
158
195
|
}
|
|
159
196
|
// Try local development config
|
|
160
197
|
const localConfig = await loadLocalDevConfig(configVariant);
|
|
161
198
|
if (localConfig) {
|
|
162
|
-
|
|
199
|
+
const result = {
|
|
200
|
+
...localConfig,
|
|
201
|
+
staticResources: staticResources ?? undefined,
|
|
202
|
+
};
|
|
203
|
+
console.log('🔧 [V1] Final config (local):', {
|
|
204
|
+
hasStoreId: !!result.storeId,
|
|
205
|
+
hasStaticResources: !!result.staticResources,
|
|
206
|
+
staticResourcesKeys: result.staticResources ? Object.keys(result.staticResources) : [],
|
|
207
|
+
});
|
|
208
|
+
return result;
|
|
163
209
|
}
|
|
164
210
|
// Fall back to production config
|
|
165
|
-
|
|
211
|
+
const productionConfig = await loadProductionConfig();
|
|
212
|
+
const result = {
|
|
213
|
+
...productionConfig,
|
|
214
|
+
staticResources: staticResources ?? undefined,
|
|
215
|
+
};
|
|
216
|
+
console.log('🔧 [V1] Final config (production):', {
|
|
217
|
+
hasStoreId: !!result.storeId,
|
|
218
|
+
hasStaticResources: !!result.staticResources,
|
|
219
|
+
staticResourcesKeys: result.staticResources ? Object.keys(result.staticResources) : [],
|
|
220
|
+
});
|
|
221
|
+
return result;
|
|
166
222
|
};
|
|
167
223
|
/**
|
|
168
224
|
* Main hook for plugin configuration
|
|
@@ -212,13 +268,8 @@ export const clearPluginConfigCache = () => {
|
|
|
212
268
|
* Development helper to log current configuration
|
|
213
269
|
*/
|
|
214
270
|
export const debugPluginConfig = async (configVariant = 'default', rawConfig) => {
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
(window.location.hostname === 'localhost' ||
|
|
218
|
-
window.location.hostname.includes('ngrok-free.app') ||
|
|
219
|
-
window.location.hostname.includes('.localhost') ||
|
|
220
|
-
window.location.hostname.includes('127.0.0.1'));
|
|
221
|
-
if (!isLocalDev) {
|
|
271
|
+
// Check if we're in local development
|
|
272
|
+
if (!isLocalEnvironment()) {
|
|
222
273
|
return;
|
|
223
274
|
}
|
|
224
275
|
const config = await getPluginConfig(configVariant, rawConfig);
|
|
@@ -49,10 +49,7 @@ export function TagadaProvider({ children, environment, customApiConfig, debugMo
|
|
|
49
49
|
localConfig, blockUntilSessionReady = false, // Default to new non-blocking behavior
|
|
50
50
|
rawPluginConfig, }) {
|
|
51
51
|
// LOCAL DEV ONLY: Use localConfig override if in local development, otherwise use default
|
|
52
|
-
const isLocalDev =
|
|
53
|
-
(window.location.hostname === 'localhost' ||
|
|
54
|
-
window.location.hostname.includes('.localhost') ||
|
|
55
|
-
window.location.hostname.includes('127.0.0.1'));
|
|
52
|
+
const isLocalDev = detectEnvironment() === 'local';
|
|
56
53
|
const configVariant = isLocalDev ? localConfig || 'default' : 'default';
|
|
57
54
|
// Debug logging (only log once during initial render)
|
|
58
55
|
const hasLoggedRef = useRef(false);
|
package/dist/v2/core/client.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { ApiClient } from './resources/apiClient';
|
|
2
2
|
import { PluginConfig, RawPluginConfig } from './utils/pluginConfig';
|
|
3
3
|
import { Environment, EnvironmentConfig, Session, AuthState, Customer, Store, Locale, Currency } from './types';
|
|
4
|
+
import { FunnelClient } from './funnelClient';
|
|
4
5
|
export interface TagadaClientConfig {
|
|
5
6
|
environment?: Environment;
|
|
6
7
|
debugMode?: boolean;
|
|
7
8
|
localConfig?: string;
|
|
8
9
|
rawPluginConfig?: RawPluginConfig;
|
|
9
10
|
blockUntilSessionReady?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Optional feature flags to enable/disable subsystems.
|
|
13
|
+
* By default all features are enabled.
|
|
14
|
+
*/
|
|
15
|
+
features?: {
|
|
16
|
+
/**
|
|
17
|
+
* Funnel session + navigation layer.
|
|
18
|
+
* Set to false to completely disable funnel behaviour.
|
|
19
|
+
*/
|
|
20
|
+
funnel?: boolean;
|
|
21
|
+
};
|
|
10
22
|
}
|
|
11
23
|
export interface TagadaState {
|
|
12
24
|
auth: AuthState;
|
|
@@ -27,12 +39,22 @@ export interface TagadaState {
|
|
|
27
39
|
export declare class TagadaClient {
|
|
28
40
|
apiClient: ApiClient;
|
|
29
41
|
state: TagadaState;
|
|
42
|
+
/**
|
|
43
|
+
* Optional funnel client.
|
|
44
|
+
* Exposed so higher-level SDKs (React, Standalone, Vue) can reuse the same
|
|
45
|
+
* low-level logic without instantiating a second client.
|
|
46
|
+
*/
|
|
47
|
+
funnel?: FunnelClient;
|
|
30
48
|
private eventDispatcher;
|
|
31
49
|
private tokenPromise;
|
|
32
50
|
private tokenResolver;
|
|
33
51
|
private boundHandleStorageChange;
|
|
34
52
|
private readonly config;
|
|
35
53
|
private instanceId;
|
|
54
|
+
private isInitializingSession;
|
|
55
|
+
private lastSessionInitError;
|
|
56
|
+
private sessionInitRetryCount;
|
|
57
|
+
private readonly MAX_SESSION_INIT_RETRIES;
|
|
36
58
|
constructor(config?: TagadaClientConfig);
|
|
37
59
|
/**
|
|
38
60
|
* Cleanup client resources
|
package/dist/v2/core/client.js
CHANGED
|
@@ -5,11 +5,17 @@ import { collectDeviceInfo, getBrowserLocale, getUrlParams } from './utils/devic
|
|
|
5
5
|
import { decodeJWTClient, isTokenExpired } from './utils/jwtDecoder';
|
|
6
6
|
import { getClientToken, setClientToken } from './utils/tokenStorage';
|
|
7
7
|
import { EventDispatcher } from './utils/eventDispatcher';
|
|
8
|
+
import { FunnelClient } from './funnelClient';
|
|
8
9
|
export class TagadaClient {
|
|
9
10
|
constructor(config = {}) {
|
|
10
11
|
this.eventDispatcher = new EventDispatcher();
|
|
11
12
|
this.tokenPromise = null;
|
|
12
13
|
this.tokenResolver = null;
|
|
14
|
+
// Track initialization state to prevent infinite loops
|
|
15
|
+
this.isInitializingSession = false;
|
|
16
|
+
this.lastSessionInitError = null;
|
|
17
|
+
this.sessionInitRetryCount = 0;
|
|
18
|
+
this.MAX_SESSION_INIT_RETRIES = 3;
|
|
13
19
|
this.config = config;
|
|
14
20
|
this.instanceId = Math.random().toString(36).substr(2, 9);
|
|
15
21
|
this.boundHandleStorageChange = this.handleStorageChange.bind(this);
|
|
@@ -53,6 +59,16 @@ export class TagadaClient {
|
|
|
53
59
|
this.apiClient = new ApiClient({
|
|
54
60
|
baseURL: envConfig.apiConfig.baseUrl,
|
|
55
61
|
});
|
|
62
|
+
// Initialize optional funnel client (feature-flagged)
|
|
63
|
+
const funnelEnabled = config.features?.funnel !== false;
|
|
64
|
+
if (funnelEnabled) {
|
|
65
|
+
this.funnel = new FunnelClient({
|
|
66
|
+
apiClient: this.apiClient,
|
|
67
|
+
debugMode: this.state.debugMode,
|
|
68
|
+
pluginConfig: this.state.pluginConfig,
|
|
69
|
+
environment: this.state.environment,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
56
72
|
// Setup token waiting mechanism
|
|
57
73
|
this.apiClient.setTokenProvider(this.waitForToken.bind(this));
|
|
58
74
|
// Listen for storage changes (cross-tab sync)
|
|
@@ -86,6 +102,20 @@ export class TagadaClient {
|
|
|
86
102
|
}
|
|
87
103
|
return;
|
|
88
104
|
}
|
|
105
|
+
// Prevent infinite loop: Don't re-initialize if we're currently initializing
|
|
106
|
+
if (this.isInitializingSession) {
|
|
107
|
+
if (this.state.debugMode) {
|
|
108
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization in progress, skipping storage change`);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Prevent infinite loop: Don't retry if we've hit max retries
|
|
113
|
+
if (this.sessionInitRetryCount >= this.MAX_SESSION_INIT_RETRIES && this.lastSessionInitError) {
|
|
114
|
+
if (this.state.debugMode) {
|
|
115
|
+
console.error(`[TagadaClient ${this.instanceId}] Max session init retries reached, giving up`, this.lastSessionInitError);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
89
119
|
// Re-run initialization when token may have changed
|
|
90
120
|
if (this.state.debugMode) {
|
|
91
121
|
console.log(`[TagadaClient ${this.instanceId}] Storage changed, re-initializing token...`);
|
|
@@ -153,6 +183,13 @@ export class TagadaClient {
|
|
|
153
183
|
pluginConfig: config,
|
|
154
184
|
pluginConfigLoading: false,
|
|
155
185
|
});
|
|
186
|
+
// Keep funnel client in sync with latest plugin config / environment
|
|
187
|
+
if (this.funnel) {
|
|
188
|
+
this.funnel.setConfig({
|
|
189
|
+
pluginConfig: config,
|
|
190
|
+
environment: this.state.environment,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
156
193
|
if (this.state.debugMode) {
|
|
157
194
|
console.log('[TagadaClient] Plugin config loaded:', config);
|
|
158
195
|
}
|
|
@@ -259,6 +296,13 @@ export class TagadaClient {
|
|
|
259
296
|
* Create anonymous token
|
|
260
297
|
*/
|
|
261
298
|
async createAnonymousToken(storeId) {
|
|
299
|
+
// Prevent concurrent anonymous token creation
|
|
300
|
+
if (this.isInitializingSession) {
|
|
301
|
+
if (this.state.debugMode) {
|
|
302
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization in progress, skipping anonymous token creation`);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
262
306
|
try {
|
|
263
307
|
if (this.state.debugMode)
|
|
264
308
|
console.log('[TagadaClient] Creating anonymous token for store:', storeId);
|
|
@@ -282,6 +326,14 @@ export class TagadaClient {
|
|
|
282
326
|
* Initialize session
|
|
283
327
|
*/
|
|
284
328
|
async initializeSession(sessionData) {
|
|
329
|
+
// Prevent concurrent initialization attempts
|
|
330
|
+
if (this.isInitializingSession) {
|
|
331
|
+
if (this.state.debugMode) {
|
|
332
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization already in progress, skipping`);
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
this.isInitializingSession = true;
|
|
285
337
|
try {
|
|
286
338
|
if (this.state.debugMode) {
|
|
287
339
|
console.log(`[TagadaClient ${this.instanceId}] Initializing session...`, { sessionId: sessionData.sessionId });
|
|
@@ -311,6 +363,9 @@ export class TagadaClient {
|
|
|
311
363
|
timeZone: deviceInfo.timeZone,
|
|
312
364
|
};
|
|
313
365
|
const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
|
|
366
|
+
// Success - reset error tracking
|
|
367
|
+
this.lastSessionInitError = null;
|
|
368
|
+
this.sessionInitRetryCount = 0;
|
|
314
369
|
// Update state with session data
|
|
315
370
|
this.updateSessionState(response, sessionData);
|
|
316
371
|
this.updateState({
|
|
@@ -322,12 +377,19 @@ export class TagadaClient {
|
|
|
322
377
|
console.log('[TagadaClient] Session initialized successfully');
|
|
323
378
|
}
|
|
324
379
|
catch (error) {
|
|
325
|
-
|
|
380
|
+
// Track error and increment retry count
|
|
381
|
+
this.lastSessionInitError = error;
|
|
382
|
+
this.sessionInitRetryCount++;
|
|
383
|
+
console.error(`[TagadaClient] Error initializing session (attempt ${this.sessionInitRetryCount}/${this.MAX_SESSION_INIT_RETRIES}):`, error);
|
|
326
384
|
this.updateState({
|
|
327
385
|
isInitialized: true,
|
|
328
386
|
isLoading: false,
|
|
329
387
|
});
|
|
330
388
|
}
|
|
389
|
+
finally {
|
|
390
|
+
// Always release the lock
|
|
391
|
+
this.isInitializingSession = false;
|
|
392
|
+
}
|
|
331
393
|
}
|
|
332
394
|
updateSessionState(response, sessionData) {
|
|
333
395
|
// Update Store
|
|
@@ -335,6 +397,8 @@ export class TagadaClient {
|
|
|
335
397
|
const storeData = response.store;
|
|
336
398
|
const storeConfig = {
|
|
337
399
|
...response.store,
|
|
400
|
+
// Ensure accountId is included - fallback to plugin config or session accountId
|
|
401
|
+
accountId: storeData.accountId || this.state.pluginConfig?.accountId || sessionData.accountId || '',
|
|
338
402
|
presentmentCurrencies: storeData.presentmentCurrencies || [response.store.currency || 'USD'],
|
|
339
403
|
chargeCurrencies: storeData.chargeCurrencies || [response.store.currency || 'USD'],
|
|
340
404
|
};
|
|
@@ -368,6 +432,31 @@ export class TagadaClient {
|
|
|
368
432
|
customer: response.customer ?? null,
|
|
369
433
|
auth: authState
|
|
370
434
|
});
|
|
435
|
+
// Auto-initialize funnel if enabled
|
|
436
|
+
// This runs after we have all required data: auth.session, store, accountId
|
|
437
|
+
if (this.funnel && sessionData.customerId && response.store?.id) {
|
|
438
|
+
const accountId = response.store.accountId || this.state.pluginConfig?.accountId || sessionData.accountId || '';
|
|
439
|
+
if (accountId) {
|
|
440
|
+
// Get funnelId from URL or config
|
|
441
|
+
const urlParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
|
|
442
|
+
const funnelId = urlParams.get('funnelId') || undefined;
|
|
443
|
+
if (this.state.debugMode) {
|
|
444
|
+
console.log('[TagadaClient] Auto-initializing funnel...', {
|
|
445
|
+
customerId: sessionData.customerId,
|
|
446
|
+
storeId: response.store.id,
|
|
447
|
+
accountId,
|
|
448
|
+
funnelId: funnelId || 'auto-detect',
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
// Auto-initialize funnel in background (don't block session init)
|
|
452
|
+
this.funnel.autoInitialize({ customerId: sessionData.customerId, sessionId: sessionData.sessionId }, { id: response.store.id, accountId }, funnelId).catch((err) => {
|
|
453
|
+
console.error('[TagadaClient] Funnel auto-initialization failed:', err);
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
console.warn('[TagadaClient] Cannot auto-initialize funnel: accountId is missing');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
371
460
|
}
|
|
372
461
|
// Helper methods
|
|
373
462
|
getCurrencySymbol(code) {
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { ApiConfig, Environment, EnvironmentConfig } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* ⚠️ IMPORTANT: Runtime Environment Detection
|
|
4
|
+
*
|
|
5
|
+
* This SDK uses RUNTIME hostname detection, NOT build-time environment variables.
|
|
6
|
+
* This ensures the SDK always connects to the correct API based on where it's deployed.
|
|
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__)
|
|
11
|
+
*
|
|
12
|
+
* Build-time .env variables (VITE_*, REACT_APP_*, NEXT_PUBLIC_*) are IGNORED
|
|
13
|
+
* to prevent incorrect API connections when plugins are deployed to different environments.
|
|
14
|
+
*/
|
|
2
15
|
/**
|
|
3
16
|
* Environment configurations for different deployment environments
|
|
4
17
|
*/
|
|
@@ -16,7 +29,16 @@ export declare function buildApiUrl(config: EnvironmentConfig, endpointPath: str
|
|
|
16
29
|
*/
|
|
17
30
|
export declare function getEndpointUrl(config: EnvironmentConfig, category: keyof ApiConfig['endpoints'], endpoint: string): string;
|
|
18
31
|
/**
|
|
19
|
-
* Auto-detect environment based on hostname
|
|
20
|
-
*
|
|
32
|
+
* Auto-detect environment based on hostname and URL patterns at RUNTIME
|
|
33
|
+
* ⚠️ IMPORTANT: Ignores build-time .env variables to ensure correct detection in all environments
|
|
34
|
+
* .env variables are ONLY used for local development via window.__TAGADA_ENV__
|
|
21
35
|
*/
|
|
22
36
|
export declare function detectEnvironment(): Environment;
|
|
37
|
+
/**
|
|
38
|
+
* Check if we're running in local development environment
|
|
39
|
+
* Uses centralized detectEnvironment() for consistency
|
|
40
|
+
*
|
|
41
|
+
* @param excludeCdn - If true, excludes CDN subdomains (e.g., instance-id.cdn.localhost)
|
|
42
|
+
* @returns true if in local environment (excluding CDN if specified)
|
|
43
|
+
*/
|
|
44
|
+
export declare function isLocalEnvironment(excludeCdn?: boolean): boolean;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ⚠️ IMPORTANT: Runtime Environment Detection
|
|
3
|
+
*
|
|
4
|
+
* This SDK uses RUNTIME hostname detection, NOT build-time environment variables.
|
|
5
|
+
* This ensures the SDK always connects to the correct API based on where it's deployed.
|
|
6
|
+
*
|
|
7
|
+
* - Production domains → production API
|
|
8
|
+
* - Dev/staging domains → development API
|
|
9
|
+
* - Localhost/local IPs → local API (with optional override via window.__TAGADA_ENV__)
|
|
10
|
+
*
|
|
11
|
+
* Build-time .env variables (VITE_*, REACT_APP_*, NEXT_PUBLIC_*) are IGNORED
|
|
12
|
+
* to prevent incorrect API connections when plugins are deployed to different environments.
|
|
13
|
+
*/
|
|
2
14
|
/**
|
|
3
15
|
* Environment configurations for different deployment environments
|
|
4
16
|
*/
|
|
@@ -87,33 +99,45 @@ export function getEndpointUrl(config, category, endpoint) {
|
|
|
87
99
|
return buildApiUrl(config, endpointPath);
|
|
88
100
|
}
|
|
89
101
|
/**
|
|
90
|
-
* Auto-detect environment based on hostname
|
|
91
|
-
*
|
|
102
|
+
* Auto-detect environment based on hostname and URL patterns at RUNTIME
|
|
103
|
+
* ⚠️ IMPORTANT: Ignores build-time .env variables to ensure correct detection in all environments
|
|
104
|
+
* .env variables are ONLY used for local development via window.__TAGADA_ENV__
|
|
92
105
|
*/
|
|
93
106
|
export function detectEnvironment() {
|
|
94
|
-
// 1. Check environment variables first
|
|
95
|
-
const envVar = resolveEnvValue('TAGADA_ENVIRONMENT') || resolveEnvValue('TAGADA_ENV');
|
|
96
|
-
if (envVar) {
|
|
97
|
-
const normalized = envVar.toLowerCase();
|
|
98
|
-
if (normalized === 'production' || normalized === 'development' || normalized === 'local') {
|
|
99
|
-
return normalized;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
107
|
// Check if we're in browser
|
|
103
108
|
if (typeof window === 'undefined') {
|
|
104
109
|
return 'local'; // SSR fallback
|
|
105
110
|
}
|
|
106
111
|
const hostname = window.location.hostname;
|
|
107
112
|
const href = window.location.href;
|
|
108
|
-
//
|
|
113
|
+
// 1. Check for LOCAL environment first (highest priority for dev)
|
|
114
|
+
// Local: localhost, local IPs, or local domains
|
|
115
|
+
if (hostname === 'localhost' ||
|
|
116
|
+
hostname.startsWith('127.') ||
|
|
117
|
+
hostname.startsWith('192.168.') ||
|
|
118
|
+
hostname.startsWith('10.') ||
|
|
119
|
+
hostname.includes('.local') ||
|
|
120
|
+
hostname === '' ||
|
|
121
|
+
hostname === '0.0.0.0') {
|
|
122
|
+
// For local development, allow override via window.__TAGADA_ENV__ (injected by dev server)
|
|
123
|
+
if (typeof window !== 'undefined' && window?.__TAGADA_ENV__?.TAGADA_ENVIRONMENT) {
|
|
124
|
+
const override = window.__TAGADA_ENV__.TAGADA_ENVIRONMENT.toLowerCase();
|
|
125
|
+
if (override === 'production' || override === 'development' || override === 'local') {
|
|
126
|
+
console.log(`[SDK] Local override detected: ${override}`);
|
|
127
|
+
return override;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return 'local';
|
|
131
|
+
}
|
|
132
|
+
// 2. Production: deployed to production domains
|
|
109
133
|
if (hostname === 'app.tagadapay.com' ||
|
|
110
134
|
hostname.includes('tagadapay.com') ||
|
|
111
135
|
hostname.includes('yourproductiondomain.com')) {
|
|
112
136
|
return 'production';
|
|
113
137
|
}
|
|
114
|
-
// Development: deployed to staging/dev domains or has dev indicators
|
|
138
|
+
// 3. Development: deployed to staging/dev domains or has dev indicators
|
|
115
139
|
if (hostname === 'app.tagadapay.dev' ||
|
|
116
|
-
hostname.includes('tagadapay.dev') ||
|
|
140
|
+
hostname.includes('tagadapay.dev') ||
|
|
117
141
|
hostname.includes('vercel.app') ||
|
|
118
142
|
hostname.includes('netlify.app') ||
|
|
119
143
|
hostname.includes('surge.sh') ||
|
|
@@ -125,16 +149,25 @@ export function detectEnvironment() {
|
|
|
125
149
|
href.includes('#dev')) {
|
|
126
150
|
return 'development';
|
|
127
151
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
hostname.startsWith('127.') ||
|
|
131
|
-
hostname.startsWith('192.168.') ||
|
|
132
|
-
hostname.startsWith('10.') ||
|
|
133
|
-
hostname.includes('.local') ||
|
|
134
|
-
hostname === '' ||
|
|
135
|
-
hostname === '0.0.0.0') {
|
|
136
|
-
return 'local';
|
|
137
|
-
}
|
|
138
|
-
// Default fallback for unknown domains (safer to use development)
|
|
152
|
+
// 4. Default fallback for unknown domains (production is safest)
|
|
153
|
+
console.warn(`[SDK] Unknown domain: ${hostname}, defaulting to production`);
|
|
139
154
|
return 'production';
|
|
140
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if we're running in local development environment
|
|
158
|
+
* Uses centralized detectEnvironment() for consistency
|
|
159
|
+
*
|
|
160
|
+
* @param excludeCdn - If true, excludes CDN subdomains (e.g., instance-id.cdn.localhost)
|
|
161
|
+
* @returns true if in local environment (excluding CDN if specified)
|
|
162
|
+
*/
|
|
163
|
+
export function isLocalEnvironment(excludeCdn = false) {
|
|
164
|
+
const env = detectEnvironment();
|
|
165
|
+
if (env !== 'local') {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
// If we need to exclude CDN subdomains
|
|
169
|
+
if (excludeCdn && typeof window !== 'undefined') {
|
|
170
|
+
return !window.location.hostname.includes('.cdn.');
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|