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