@tagadapay/plugin-sdk 2.8.8 → 2.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/config/environment.d.ts +1 -22
- package/dist/react/config/environment.js +1 -132
- package/dist/react/utils/deviceInfo.d.ts +1 -39
- package/dist/react/utils/deviceInfo.js +1 -163
- package/dist/react/utils/jwtDecoder.d.ts +1 -14
- package/dist/react/utils/jwtDecoder.js +1 -86
- package/dist/react/utils/tokenStorage.d.ts +1 -16
- package/dist/react/utils/tokenStorage.js +1 -53
- package/dist/v2/core/client.d.ts +96 -0
- package/dist/v2/core/client.js +430 -0
- package/dist/v2/core/config/environment.d.ts +36 -0
- package/dist/v2/core/config/environment.js +155 -0
- package/dist/v2/core/pathRemapping.js +61 -3
- package/dist/v2/core/resources/apiClient.d.ts +13 -0
- package/dist/v2/core/resources/apiClient.js +77 -9
- package/dist/v2/core/resources/funnel.d.ts +21 -0
- package/dist/v2/core/resources/payments.d.ts +23 -0
- package/dist/v2/core/types.d.ts +271 -0
- package/dist/v2/core/types.js +4 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +39 -0
- package/dist/v2/core/utils/deviceInfo.js +162 -0
- package/dist/v2/core/utils/eventDispatcher.d.ts +10 -0
- package/dist/v2/core/utils/eventDispatcher.js +24 -0
- package/dist/v2/core/utils/jwtDecoder.d.ts +14 -0
- package/dist/v2/core/utils/jwtDecoder.js +85 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.js +64 -8
- package/dist/v2/core/utils/tokenStorage.d.ts +19 -0
- package/dist/v2/core/utils/tokenStorage.js +52 -0
- package/dist/v2/react/components/ApplePayButton.js +1 -1
- package/dist/v2/react/components/DebugDrawer.js +90 -1
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.d.ts +12 -0
- package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +54 -0
- package/dist/v2/react/hooks/useFunnel.d.ts +2 -1
- package/dist/v2/react/hooks/useFunnel.js +245 -69
- package/dist/v2/react/hooks/useGoogleAutocomplete.js +26 -18
- package/dist/v2/react/hooks/useISOData.js +4 -2
- package/dist/v2/react/hooks/useOffersQuery.d.ts +42 -29
- package/dist/v2/react/hooks/useOffersQuery.js +266 -204
- package/dist/v2/react/hooks/usePaymentQuery.js +99 -6
- package/dist/v2/react/providers/TagadaProvider.d.ts +13 -21
- package/dist/v2/react/providers/TagadaProvider.js +79 -673
- package/package.json +1 -1
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { ApiClient } from './resources/apiClient';
|
|
2
|
+
import { detectEnvironment, getEnvironmentConfig } from './config/environment';
|
|
3
|
+
import { loadPluginConfig } from './utils/pluginConfig';
|
|
4
|
+
import { collectDeviceInfo, getBrowserLocale, getUrlParams } from './utils/deviceInfo';
|
|
5
|
+
import { decodeJWTClient, isTokenExpired } from './utils/jwtDecoder';
|
|
6
|
+
import { getClientToken, setClientToken } from './utils/tokenStorage';
|
|
7
|
+
import { EventDispatcher } from './utils/eventDispatcher';
|
|
8
|
+
export class TagadaClient {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.eventDispatcher = new EventDispatcher();
|
|
11
|
+
this.tokenPromise = null;
|
|
12
|
+
this.tokenResolver = null;
|
|
13
|
+
// Track initialization state to prevent infinite loops
|
|
14
|
+
this.isInitializingSession = false;
|
|
15
|
+
this.lastSessionInitError = null;
|
|
16
|
+
this.sessionInitRetryCount = 0;
|
|
17
|
+
this.MAX_SESSION_INIT_RETRIES = 3;
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.instanceId = Math.random().toString(36).substr(2, 9);
|
|
20
|
+
this.boundHandleStorageChange = this.handleStorageChange.bind(this);
|
|
21
|
+
if (this.config.debugMode) {
|
|
22
|
+
console.log(`[TagadaClient ${this.instanceId}] Initializing...`);
|
|
23
|
+
}
|
|
24
|
+
// Initialize default state
|
|
25
|
+
const env = this.resolveEnvironment();
|
|
26
|
+
const envConfig = getEnvironmentConfig(env);
|
|
27
|
+
this.state = {
|
|
28
|
+
auth: {
|
|
29
|
+
isAuthenticated: false,
|
|
30
|
+
isLoading: false,
|
|
31
|
+
customer: null,
|
|
32
|
+
session: null,
|
|
33
|
+
},
|
|
34
|
+
session: null,
|
|
35
|
+
customer: null,
|
|
36
|
+
locale: {
|
|
37
|
+
locale: 'en-US',
|
|
38
|
+
language: 'en',
|
|
39
|
+
region: 'US',
|
|
40
|
+
messages: {},
|
|
41
|
+
},
|
|
42
|
+
currency: {
|
|
43
|
+
code: 'USD',
|
|
44
|
+
symbol: '$',
|
|
45
|
+
name: 'US Dollar',
|
|
46
|
+
},
|
|
47
|
+
store: null,
|
|
48
|
+
environment: envConfig,
|
|
49
|
+
isLoading: true,
|
|
50
|
+
isInitialized: false,
|
|
51
|
+
isSessionInitialized: false,
|
|
52
|
+
pluginConfig: { basePath: '/', config: {} },
|
|
53
|
+
pluginConfigLoading: !config.rawPluginConfig,
|
|
54
|
+
debugMode: config.debugMode ?? env !== 'production',
|
|
55
|
+
token: null,
|
|
56
|
+
};
|
|
57
|
+
// Initialize API Client
|
|
58
|
+
this.apiClient = new ApiClient({
|
|
59
|
+
baseURL: envConfig.apiConfig.baseUrl,
|
|
60
|
+
});
|
|
61
|
+
// Setup token waiting mechanism
|
|
62
|
+
this.apiClient.setTokenProvider(this.waitForToken.bind(this));
|
|
63
|
+
// Listen for storage changes (cross-tab sync)
|
|
64
|
+
if (typeof window !== 'undefined') {
|
|
65
|
+
window.addEventListener('storage', this.boundHandleStorageChange);
|
|
66
|
+
}
|
|
67
|
+
// Start initialization
|
|
68
|
+
this.initialize();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Cleanup client resources
|
|
72
|
+
*/
|
|
73
|
+
destroy() {
|
|
74
|
+
if (typeof window !== 'undefined') {
|
|
75
|
+
window.removeEventListener('storage', this.boundHandleStorageChange);
|
|
76
|
+
}
|
|
77
|
+
if (this.state.debugMode) {
|
|
78
|
+
console.log(`[TagadaClient ${this.instanceId}] Destroyed`);
|
|
79
|
+
}
|
|
80
|
+
this.eventDispatcher.clear();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Handle storage changes (e.g. token update in another tab)
|
|
84
|
+
*/
|
|
85
|
+
handleStorageChange() {
|
|
86
|
+
const storedToken = getClientToken();
|
|
87
|
+
// Avoid unnecessary re-initialization if token hasn't changed
|
|
88
|
+
if (storedToken === this.state.token) {
|
|
89
|
+
if (this.state.debugMode) {
|
|
90
|
+
console.log(`[TagadaClient ${this.instanceId}] Token unchanged (ignoring event)`);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Prevent infinite loop: Don't re-initialize if we're currently initializing
|
|
95
|
+
if (this.isInitializingSession) {
|
|
96
|
+
if (this.state.debugMode) {
|
|
97
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization in progress, skipping storage change`);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Prevent infinite loop: Don't retry if we've hit max retries
|
|
102
|
+
if (this.sessionInitRetryCount >= this.MAX_SESSION_INIT_RETRIES && this.lastSessionInitError) {
|
|
103
|
+
if (this.state.debugMode) {
|
|
104
|
+
console.error(`[TagadaClient ${this.instanceId}] Max session init retries reached, giving up`, this.lastSessionInitError);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Re-run initialization when token may have changed
|
|
109
|
+
if (this.state.debugMode) {
|
|
110
|
+
console.log(`[TagadaClient ${this.instanceId}] Storage changed, re-initializing token...`);
|
|
111
|
+
}
|
|
112
|
+
this.initializeToken();
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe to state changes
|
|
116
|
+
*/
|
|
117
|
+
subscribe(listener) {
|
|
118
|
+
return this.eventDispatcher.subscribe(listener);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get current state
|
|
122
|
+
*/
|
|
123
|
+
getState() {
|
|
124
|
+
return this.state;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Update state and notify listeners
|
|
128
|
+
*/
|
|
129
|
+
updateState(updates) {
|
|
130
|
+
this.state = { ...this.state, ...updates };
|
|
131
|
+
this.eventDispatcher.notify(this.state);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Resolve environment
|
|
135
|
+
*/
|
|
136
|
+
resolveEnvironment() {
|
|
137
|
+
if (this.config.environment)
|
|
138
|
+
return this.config.environment;
|
|
139
|
+
return detectEnvironment();
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Main initialization flow
|
|
143
|
+
*/
|
|
144
|
+
async initialize() {
|
|
145
|
+
try {
|
|
146
|
+
// 1. Load Plugin Config
|
|
147
|
+
await this.initializePluginConfig();
|
|
148
|
+
// 2. Initialize Token (Background or Blocking based on config)
|
|
149
|
+
if (this.state.pluginConfig.storeId) {
|
|
150
|
+
await this.initializeToken();
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.warn('[TagadaClient] No store ID found in plugin config. Skipping token initialization.');
|
|
154
|
+
this.updateState({ isLoading: false, isInitialized: true });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error('[TagadaClient] Initialization failed:', error);
|
|
159
|
+
this.updateState({ isLoading: false, isInitialized: true });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Load plugin configuration
|
|
164
|
+
*/
|
|
165
|
+
async initializePluginConfig() {
|
|
166
|
+
if (!this.state.pluginConfigLoading)
|
|
167
|
+
return;
|
|
168
|
+
try {
|
|
169
|
+
const configVariant = this.config.localConfig || 'default';
|
|
170
|
+
const config = await loadPluginConfig(configVariant, this.config.rawPluginConfig);
|
|
171
|
+
this.updateState({
|
|
172
|
+
pluginConfig: config,
|
|
173
|
+
pluginConfigLoading: false,
|
|
174
|
+
});
|
|
175
|
+
if (this.state.debugMode) {
|
|
176
|
+
console.log('[TagadaClient] Plugin config loaded:', config);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
console.error('[TagadaClient] Failed to load plugin config:', error);
|
|
181
|
+
this.updateState({
|
|
182
|
+
pluginConfig: { basePath: '/', config: {} },
|
|
183
|
+
pluginConfigLoading: false,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Initialize token and session
|
|
189
|
+
*/
|
|
190
|
+
async initializeToken() {
|
|
191
|
+
// Check for existing token in URL or storage
|
|
192
|
+
const existingToken = getClientToken();
|
|
193
|
+
const urlParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : '');
|
|
194
|
+
const queryToken = urlParams.get('token');
|
|
195
|
+
if (this.state.debugMode) {
|
|
196
|
+
console.log(`[TagadaClient ${this.instanceId}] Initializing token...`, {
|
|
197
|
+
hasExistingToken: !!existingToken,
|
|
198
|
+
hasQueryToken: !!queryToken
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
let tokenToUse = null;
|
|
202
|
+
let shouldPersist = false;
|
|
203
|
+
if (queryToken) {
|
|
204
|
+
tokenToUse = queryToken;
|
|
205
|
+
shouldPersist = true;
|
|
206
|
+
}
|
|
207
|
+
else if (existingToken && !isTokenExpired(existingToken)) {
|
|
208
|
+
tokenToUse = existingToken;
|
|
209
|
+
}
|
|
210
|
+
if (tokenToUse) {
|
|
211
|
+
this.setToken(tokenToUse);
|
|
212
|
+
// Persist token if it came from query (updates localStorage and fires event)
|
|
213
|
+
// We do this AFTER setToken so state is updated and handleStorageChange sees match
|
|
214
|
+
if (shouldPersist) {
|
|
215
|
+
if (this.state.debugMode) {
|
|
216
|
+
console.log(`[TagadaClient ${this.instanceId}] Persisting query token to storage...`);
|
|
217
|
+
}
|
|
218
|
+
setClientToken(tokenToUse);
|
|
219
|
+
}
|
|
220
|
+
const decodedSession = decodeJWTClient(tokenToUse);
|
|
221
|
+
if (decodedSession) {
|
|
222
|
+
this.updateState({ session: decodedSession });
|
|
223
|
+
await this.initializeSession(decodedSession);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
console.error('[TagadaClient] Failed to decode token');
|
|
227
|
+
this.updateState({ isInitialized: true, isLoading: false });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Create anonymous token
|
|
232
|
+
const storeId = this.state.pluginConfig.storeId;
|
|
233
|
+
if (storeId) {
|
|
234
|
+
if (this.state.debugMode) {
|
|
235
|
+
console.log(`[TagadaClient ${this.instanceId}] Creating anonymous token for store:`, storeId);
|
|
236
|
+
}
|
|
237
|
+
await this.createAnonymousToken(storeId);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
this.updateState({ isInitialized: true, isLoading: false });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Set token and resolve waiting requests
|
|
246
|
+
*/
|
|
247
|
+
setToken(token) {
|
|
248
|
+
this.apiClient.updateToken(token);
|
|
249
|
+
this.updateState({ token });
|
|
250
|
+
// Notify waiting requests
|
|
251
|
+
if (this.tokenResolver) {
|
|
252
|
+
this.tokenResolver(token);
|
|
253
|
+
this.tokenPromise = null;
|
|
254
|
+
this.tokenResolver = null;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// If we set a token but no one was waiting, ensure future requests don't wait unnecessarily
|
|
258
|
+
// Actually, if we have a token, the provider won't be called by ApiClient logic if we handle it right.
|
|
259
|
+
// But to be safe, if we set a token, we can pre-resolve the promise if it exists.
|
|
260
|
+
// Wait, if tokenPromise exists, tokenResolver must exist.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Wait for token to be available
|
|
265
|
+
*/
|
|
266
|
+
waitForToken() {
|
|
267
|
+
if (this.apiClient.getCurrentToken()) {
|
|
268
|
+
return Promise.resolve(this.apiClient.getCurrentToken());
|
|
269
|
+
}
|
|
270
|
+
if (!this.tokenPromise) {
|
|
271
|
+
this.tokenPromise = new Promise((resolve) => {
|
|
272
|
+
this.tokenResolver = resolve;
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return this.tokenPromise;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Create anonymous token
|
|
279
|
+
*/
|
|
280
|
+
async createAnonymousToken(storeId) {
|
|
281
|
+
// Prevent concurrent anonymous token creation
|
|
282
|
+
if (this.isInitializingSession) {
|
|
283
|
+
if (this.state.debugMode) {
|
|
284
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization in progress, skipping anonymous token creation`);
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
if (this.state.debugMode)
|
|
290
|
+
console.log('[TagadaClient] Creating anonymous token for store:', storeId);
|
|
291
|
+
// We use fetch directly or ApiClient with skipAuth to avoid waiting for itself
|
|
292
|
+
const response = await this.apiClient.post('/api/v1/cms/session/anonymous', { storeId, role: 'anonymous' }, { skipAuth: true });
|
|
293
|
+
this.setToken(response.token);
|
|
294
|
+
setClientToken(response.token);
|
|
295
|
+
const decodedSession = decodeJWTClient(response.token);
|
|
296
|
+
if (decodedSession) {
|
|
297
|
+
this.updateState({ session: decodedSession });
|
|
298
|
+
await this.initializeSession(decodedSession);
|
|
299
|
+
}
|
|
300
|
+
this.updateState({ isSessionInitialized: true });
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
console.error('[TagadaClient] Failed to create anonymous token:', error);
|
|
304
|
+
this.updateState({ isInitialized: true, isLoading: false });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Initialize session
|
|
309
|
+
*/
|
|
310
|
+
async initializeSession(sessionData) {
|
|
311
|
+
// Prevent concurrent initialization attempts
|
|
312
|
+
if (this.isInitializingSession) {
|
|
313
|
+
if (this.state.debugMode) {
|
|
314
|
+
console.log(`[TagadaClient ${this.instanceId}] Session initialization already in progress, skipping`);
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.isInitializingSession = true;
|
|
319
|
+
try {
|
|
320
|
+
if (this.state.debugMode) {
|
|
321
|
+
console.log(`[TagadaClient ${this.instanceId}] Initializing session...`, { sessionId: sessionData.sessionId });
|
|
322
|
+
}
|
|
323
|
+
const deviceInfo = collectDeviceInfo();
|
|
324
|
+
const urlParams = getUrlParams();
|
|
325
|
+
const browserLocale = getBrowserLocale();
|
|
326
|
+
const sessionInitData = {
|
|
327
|
+
storeId: sessionData.storeId,
|
|
328
|
+
accountId: sessionData.accountId,
|
|
329
|
+
customerId: sessionData.customerId,
|
|
330
|
+
role: sessionData.role,
|
|
331
|
+
browserLocale,
|
|
332
|
+
queryLocale: urlParams.locale,
|
|
333
|
+
queryCurrency: urlParams.currency,
|
|
334
|
+
utmSource: urlParams.utmSource,
|
|
335
|
+
utmMedium: urlParams.utmMedium,
|
|
336
|
+
utmCampaign: urlParams.utmCampaign,
|
|
337
|
+
browser: deviceInfo.userAgent.browser.name,
|
|
338
|
+
browserVersion: deviceInfo.userAgent.browser.version,
|
|
339
|
+
os: deviceInfo.userAgent.os.name,
|
|
340
|
+
osVersion: deviceInfo.userAgent.os.version,
|
|
341
|
+
deviceType: deviceInfo.userAgent.device?.type,
|
|
342
|
+
deviceModel: deviceInfo.userAgent.device?.model,
|
|
343
|
+
screenWidth: deviceInfo.screenResolution.width,
|
|
344
|
+
screenHeight: deviceInfo.screenResolution.height,
|
|
345
|
+
timeZone: deviceInfo.timeZone,
|
|
346
|
+
};
|
|
347
|
+
const response = await this.apiClient.post('/api/v1/cms/session/init', sessionInitData);
|
|
348
|
+
// Success - reset error tracking
|
|
349
|
+
this.lastSessionInitError = null;
|
|
350
|
+
this.sessionInitRetryCount = 0;
|
|
351
|
+
// Update state with session data
|
|
352
|
+
this.updateSessionState(response, sessionData);
|
|
353
|
+
this.updateState({
|
|
354
|
+
isInitialized: true,
|
|
355
|
+
isSessionInitialized: true,
|
|
356
|
+
isLoading: false,
|
|
357
|
+
});
|
|
358
|
+
if (this.state.debugMode)
|
|
359
|
+
console.log('[TagadaClient] Session initialized successfully');
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
// Track error and increment retry count
|
|
363
|
+
this.lastSessionInitError = error;
|
|
364
|
+
this.sessionInitRetryCount++;
|
|
365
|
+
console.error(`[TagadaClient] Error initializing session (attempt ${this.sessionInitRetryCount}/${this.MAX_SESSION_INIT_RETRIES}):`, error);
|
|
366
|
+
this.updateState({
|
|
367
|
+
isInitialized: true,
|
|
368
|
+
isLoading: false,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
finally {
|
|
372
|
+
// Always release the lock
|
|
373
|
+
this.isInitializingSession = false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
updateSessionState(response, sessionData) {
|
|
377
|
+
// Update Store
|
|
378
|
+
if (response.store) {
|
|
379
|
+
const storeData = response.store;
|
|
380
|
+
const storeConfig = {
|
|
381
|
+
...response.store,
|
|
382
|
+
presentmentCurrencies: storeData.presentmentCurrencies || [response.store.currency || 'USD'],
|
|
383
|
+
chargeCurrencies: storeData.chargeCurrencies || [response.store.currency || 'USD'],
|
|
384
|
+
};
|
|
385
|
+
this.updateState({ store: storeConfig });
|
|
386
|
+
}
|
|
387
|
+
// Update Locale
|
|
388
|
+
const localeConfig = {
|
|
389
|
+
locale: response.locale,
|
|
390
|
+
language: response.locale.split('-')[0],
|
|
391
|
+
region: response.locale.split('-')[1] ?? 'US',
|
|
392
|
+
messages: response.messages ?? {},
|
|
393
|
+
};
|
|
394
|
+
this.updateState({ locale: localeConfig });
|
|
395
|
+
// Update Currency
|
|
396
|
+
if (response.store) {
|
|
397
|
+
const currencyConfig = {
|
|
398
|
+
code: response.store.currency,
|
|
399
|
+
symbol: this.getCurrencySymbol(response.store.currency),
|
|
400
|
+
name: this.getCurrencyName(response.store.currency),
|
|
401
|
+
};
|
|
402
|
+
this.updateState({ currency: currencyConfig });
|
|
403
|
+
}
|
|
404
|
+
// Update Customer & Auth
|
|
405
|
+
const authState = {
|
|
406
|
+
isAuthenticated: response.customer?.isAuthenticated ?? false,
|
|
407
|
+
isLoading: false,
|
|
408
|
+
customer: response.customer ?? null,
|
|
409
|
+
session: sessionData,
|
|
410
|
+
};
|
|
411
|
+
this.updateState({
|
|
412
|
+
customer: response.customer ?? null,
|
|
413
|
+
auth: authState
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// Helper methods
|
|
417
|
+
getCurrencySymbol(code) {
|
|
418
|
+
const symbols = {
|
|
419
|
+
USD: '$', EUR: '€', GBP: '£', JPY: '¥', CAD: 'C$', AUD: 'A$',
|
|
420
|
+
};
|
|
421
|
+
return symbols[code] || code;
|
|
422
|
+
}
|
|
423
|
+
getCurrencyName(code) {
|
|
424
|
+
const names = {
|
|
425
|
+
USD: 'US Dollar', EUR: 'Euro', GBP: 'British Pound',
|
|
426
|
+
JPY: 'Japanese Yen', CAD: 'Canadian Dollar', AUD: 'Australian Dollar',
|
|
427
|
+
};
|
|
428
|
+
return names[code] || code;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Environment configurations for different deployment environments
|
|
17
|
+
*/
|
|
18
|
+
export declare const ENVIRONMENT_CONFIGS: Record<Environment, ApiConfig>;
|
|
19
|
+
/**
|
|
20
|
+
* Get the environment configuration based on the current environment
|
|
21
|
+
*/
|
|
22
|
+
export declare function getEnvironmentConfig(environment?: Environment): EnvironmentConfig;
|
|
23
|
+
/**
|
|
24
|
+
* Build a complete API URL from environment config and endpoint path
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildApiUrl(config: EnvironmentConfig, endpointPath: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Get a specific endpoint URL
|
|
29
|
+
*/
|
|
30
|
+
export declare function getEndpointUrl(config: EnvironmentConfig, category: keyof ApiConfig['endpoints'], endpoint: string): string;
|
|
31
|
+
/**
|
|
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__
|
|
35
|
+
*/
|
|
36
|
+
export declare function detectEnvironment(): Environment;
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Environment configurations for different deployment environments
|
|
16
|
+
*/
|
|
17
|
+
export const ENVIRONMENT_CONFIGS = {
|
|
18
|
+
production: {
|
|
19
|
+
baseUrl: 'https://app.tagadapay.com',
|
|
20
|
+
endpoints: {
|
|
21
|
+
checkout: {
|
|
22
|
+
sessionInit: '/api/v1/checkout/session/init',
|
|
23
|
+
sessionStatus: '/api/v1/checkout/session/status',
|
|
24
|
+
},
|
|
25
|
+
customer: {
|
|
26
|
+
profile: '/api/v1/customer/profile',
|
|
27
|
+
session: '/api/v1/customer/session',
|
|
28
|
+
},
|
|
29
|
+
store: {
|
|
30
|
+
config: '/api/v1/store/config',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
development: {
|
|
35
|
+
baseUrl: 'https://app.tagadapay.dev',
|
|
36
|
+
endpoints: {
|
|
37
|
+
checkout: {
|
|
38
|
+
sessionInit: '/api/v1/checkout/session/init',
|
|
39
|
+
sessionStatus: '/api/v1/checkout/session/status',
|
|
40
|
+
},
|
|
41
|
+
customer: {
|
|
42
|
+
profile: '/api/v1/customer/profile',
|
|
43
|
+
session: '/api/v1/customer/session',
|
|
44
|
+
},
|
|
45
|
+
store: {
|
|
46
|
+
config: '/api/v1/store/config',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
local: {
|
|
51
|
+
baseUrl: 'http://app.localhost:3000',
|
|
52
|
+
endpoints: {
|
|
53
|
+
checkout: {
|
|
54
|
+
sessionInit: '/api/v1/checkout/session/init',
|
|
55
|
+
sessionStatus: '/api/v1/checkout/session/status',
|
|
56
|
+
},
|
|
57
|
+
customer: {
|
|
58
|
+
profile: '/api/v1/customer/profile',
|
|
59
|
+
session: '/api/v1/customer/session',
|
|
60
|
+
},
|
|
61
|
+
store: {
|
|
62
|
+
config: '/api/v1/store/config',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Get the environment configuration based on the current environment
|
|
69
|
+
*/
|
|
70
|
+
export function getEnvironmentConfig(environment = 'local') {
|
|
71
|
+
const apiConfig = ENVIRONMENT_CONFIGS[environment];
|
|
72
|
+
if (!apiConfig) {
|
|
73
|
+
console.warn(`Unknown environment: ${environment}. Falling back to local.`);
|
|
74
|
+
return {
|
|
75
|
+
environment: 'local',
|
|
76
|
+
apiConfig: ENVIRONMENT_CONFIGS.local,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
environment,
|
|
81
|
+
apiConfig,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build a complete API URL from environment config and endpoint path
|
|
86
|
+
*/
|
|
87
|
+
export function buildApiUrl(config, endpointPath) {
|
|
88
|
+
return `${config.apiConfig.baseUrl}${endpointPath}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific endpoint URL
|
|
92
|
+
*/
|
|
93
|
+
export function getEndpointUrl(config, category, endpoint) {
|
|
94
|
+
const categoryEndpoints = config.apiConfig.endpoints[category];
|
|
95
|
+
const endpointPath = categoryEndpoints[endpoint];
|
|
96
|
+
if (!endpointPath) {
|
|
97
|
+
throw new Error(`Endpoint not found: ${category}.${endpoint}`);
|
|
98
|
+
}
|
|
99
|
+
return buildApiUrl(config, endpointPath);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
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__
|
|
105
|
+
*/
|
|
106
|
+
export function detectEnvironment() {
|
|
107
|
+
// Check if we're in browser
|
|
108
|
+
if (typeof window === 'undefined') {
|
|
109
|
+
return 'local'; // SSR fallback
|
|
110
|
+
}
|
|
111
|
+
const hostname = window.location.hostname;
|
|
112
|
+
const href = window.location.href;
|
|
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
|
|
133
|
+
if (hostname === 'app.tagadapay.com' ||
|
|
134
|
+
hostname.includes('tagadapay.com') ||
|
|
135
|
+
hostname.includes('yourproductiondomain.com')) {
|
|
136
|
+
return 'production';
|
|
137
|
+
}
|
|
138
|
+
// 3. Development: deployed to staging/dev domains or has dev indicators
|
|
139
|
+
if (hostname === 'app.tagadapay.dev' ||
|
|
140
|
+
hostname.includes('tagadapay.dev') ||
|
|
141
|
+
hostname.includes('vercel.app') ||
|
|
142
|
+
hostname.includes('netlify.app') ||
|
|
143
|
+
hostname.includes('surge.sh') ||
|
|
144
|
+
hostname.includes('github.io') ||
|
|
145
|
+
hostname.includes('herokuapp.com') ||
|
|
146
|
+
hostname.includes('railway.app') ||
|
|
147
|
+
href.includes('?env=dev') ||
|
|
148
|
+
href.includes('?dev=true') ||
|
|
149
|
+
href.includes('#dev')) {
|
|
150
|
+
return 'development';
|
|
151
|
+
}
|
|
152
|
+
// 4. Default fallback for unknown domains (production is safest)
|
|
153
|
+
console.warn(`[SDK] Unknown domain: ${hostname}, defaulting to production`);
|
|
154
|
+
return 'production';
|
|
155
|
+
}
|