@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.
Files changed (54) hide show
  1. package/README.md +14 -14
  2. package/dist/index.js +1 -1
  3. package/dist/react/hooks/usePluginConfig.d.ts +1 -0
  4. package/dist/react/hooks/usePluginConfig.js +69 -18
  5. package/dist/react/providers/TagadaProvider.js +1 -4
  6. package/dist/v2/core/client.d.ts +22 -0
  7. package/dist/v2/core/client.js +90 -1
  8. package/dist/v2/core/config/environment.d.ts +24 -2
  9. package/dist/v2/core/config/environment.js +58 -25
  10. package/dist/v2/core/funnelClient.d.ts +84 -0
  11. package/dist/v2/core/funnelClient.js +252 -0
  12. package/dist/v2/core/index.d.ts +2 -0
  13. package/dist/v2/core/index.js +3 -0
  14. package/dist/v2/core/resources/apiClient.d.ts +5 -0
  15. package/dist/v2/core/resources/apiClient.js +47 -0
  16. package/dist/v2/core/resources/funnel.d.ts +8 -0
  17. package/dist/v2/core/resources/offers.d.ts +182 -8
  18. package/dist/v2/core/resources/offers.js +25 -0
  19. package/dist/v2/core/resources/products.d.ts +5 -0
  20. package/dist/v2/core/resources/products.js +15 -1
  21. package/dist/v2/core/types.d.ts +1 -0
  22. package/dist/v2/core/utils/funnelQueryKeys.d.ts +23 -0
  23. package/dist/v2/core/utils/funnelQueryKeys.js +23 -0
  24. package/dist/v2/core/utils/index.d.ts +2 -0
  25. package/dist/v2/core/utils/index.js +2 -0
  26. package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
  27. package/dist/v2/core/utils/pluginConfig.js +84 -22
  28. package/dist/v2/core/utils/sessionStorage.d.ts +20 -0
  29. package/dist/v2/core/utils/sessionStorage.js +39 -0
  30. package/dist/v2/index.d.ts +3 -2
  31. package/dist/v2/index.js +1 -1
  32. package/dist/v2/react/components/ApplePayButton.js +1 -1
  33. package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +3 -0
  34. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +4 -3
  35. package/dist/v2/react/hooks/useClubOffers.d.ts +2 -2
  36. package/dist/v2/react/hooks/useFunnel.d.ts +27 -38
  37. package/dist/v2/react/hooks/useFunnel.js +22 -660
  38. package/dist/v2/react/hooks/useFunnelLegacy.d.ts +52 -0
  39. package/dist/v2/react/hooks/useFunnelLegacy.js +733 -0
  40. package/dist/v2/react/hooks/useOfferQuery.d.ts +109 -0
  41. package/dist/v2/react/hooks/useOfferQuery.js +483 -0
  42. package/dist/v2/react/hooks/useOffersQuery.d.ts +10 -58
  43. package/dist/v2/react/hooks/useOffersQuery.js +110 -8
  44. package/dist/v2/react/hooks/useProductsQuery.d.ts +1 -0
  45. package/dist/v2/react/hooks/useProductsQuery.js +10 -6
  46. package/dist/v2/react/index.d.ts +7 -4
  47. package/dist/v2/react/index.js +4 -2
  48. package/dist/v2/react/providers/TagadaProvider.d.ts +45 -2
  49. package/dist/v2/react/providers/TagadaProvider.js +116 -3
  50. package/dist/v2/standalone/index.d.ts +20 -0
  51. package/dist/v2/standalone/index.js +22 -0
  52. package/dist/v2/vue/index.d.ts +6 -0
  53. package/dist/v2/vue/index.js +10 -0
  54. 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/v2` for new projects. V1 (`/react`) is still supported for existing projects.
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/v2';
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 v2 introduces a clean architecture with significant improvements:
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/v2';
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/v2';
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/v2';
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/v2';
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/v2';
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/v2`. The `blockUntilSessionReady` option was added in v2.3.0. For older versions, the blocking behavior was the default and only option.
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/v2';
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/v2';
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/v2';
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/v2';
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/v2';
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 - new clean architecture
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
- // Use hostname-based detection for better Vite compatibility
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
- return {
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
- return localConfig;
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
- return loadProductionConfig();
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
- // Use hostname-based detection for better Vite compatibility
216
- const isLocalDev = typeof window !== 'undefined' &&
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 = typeof window !== 'undefined' &&
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);
@@ -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
@@ -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
- console.error('[TagadaClient] Error initializing session:', error);
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, URL patterns, and deployment context
20
- * Works with any build tool or deployment system
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
- import { resolveEnvValue } from '../utils/env';
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, URL patterns, and deployment context
91
- * Works with any build tool or deployment system
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
- // Production: deployed to production domains
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') || // ✅ app.tagadapay.dev and subdomains
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
- // Local: localhost, local IPs, or local domains
129
- if (hostname === 'localhost' ||
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
+ }