@tagadapay/plugin-sdk 2.7.14 → 2.7.18
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/v2/core/__tests__/pathRemapping.test.d.ts +11 -0
- package/dist/v2/core/__tests__/pathRemapping.test.js +776 -0
- package/dist/v2/core/pathRemapping.d.ts +92 -0
- package/dist/v2/core/pathRemapping.js +295 -21
- package/dist/v2/core/resources/funnel.d.ts +29 -1
- package/dist/v2/core/resources/funnel.js +6 -2
- package/dist/v2/core/utils/env.d.ts +5 -0
- package/dist/v2/core/utils/env.js +20 -0
- package/dist/v2/core/utils/order.d.ts +1 -0
- package/dist/v2/core/utils/pluginConfig.d.ts +1 -4
- package/dist/v2/core/utils/pluginConfig.js +14 -24
- package/dist/v2/index.d.ts +1 -1
- package/dist/v2/index.js +1 -1
- package/dist/v2/react/hooks/useApiClient.d.ts +19 -0
- package/dist/v2/react/hooks/useApiClient.js +22 -0
- package/dist/v2/react/hooks/useCredits.js +0 -1
- package/dist/v2/react/hooks/useFunnel.js +31 -4
- package/dist/v2/react/hooks/useRemappableParams.d.ts +21 -0
- package/dist/v2/react/hooks/useRemappableParams.js +104 -0
- package/dist/v2/react/index.d.ts +1 -0
- package/dist/v2/react/index.js +1 -0
- package/dist/v2/react/providers/TagadaProvider.js +15 -2
- package/dist/v2/react/utils/sessionWaiter.d.ts +26 -0
- package/dist/v2/react/utils/sessionWaiter.js +72 -0
- package/package.json +8 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Plugin Configuration Utility Functions
|
|
3
3
|
* Pure functions for plugin configuration management
|
|
4
4
|
*/
|
|
5
|
+
import { resolveEnvValue } from './env';
|
|
5
6
|
/**
|
|
6
7
|
* Load local development configuration
|
|
7
8
|
*/
|
|
@@ -138,7 +139,6 @@ const loadProductionConfig = async () => {
|
|
|
138
139
|
export const loadPluginConfig = async (configVariant = 'default', rawConfig) => {
|
|
139
140
|
// If raw config is provided, use it directly
|
|
140
141
|
if (rawConfig) {
|
|
141
|
-
console.log('🛠️ Using raw plugin config:', rawConfig);
|
|
142
142
|
return {
|
|
143
143
|
storeId: rawConfig.storeId,
|
|
144
144
|
accountId: rawConfig.accountId,
|
|
@@ -146,6 +146,12 @@ export const loadPluginConfig = async (configVariant = 'default', rawConfig) =>
|
|
|
146
146
|
config: rawConfig.config ?? {},
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
else {
|
|
150
|
+
const rawConfig = await createRawPluginConfig();
|
|
151
|
+
if (rawConfig) {
|
|
152
|
+
return rawConfig;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
149
155
|
// Try local development config
|
|
150
156
|
const localConfig = await loadLocalDevConfig(configVariant);
|
|
151
157
|
if (localConfig) {
|
|
@@ -202,33 +208,17 @@ export async function loadLocalConfig(configName = 'default', defaultConfig) {
|
|
|
202
208
|
* @param options - Configuration options including storeId, accountId, basePath, configName, or a direct config object
|
|
203
209
|
* @returns A RawPluginConfig object or undefined if required fields are missing
|
|
204
210
|
*/
|
|
205
|
-
export async function createRawPluginConfig(
|
|
211
|
+
export async function createRawPluginConfig() {
|
|
206
212
|
try {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return process.env[envKey];
|
|
213
|
-
}
|
|
214
|
-
if (typeof import.meta !== 'undefined' && import.meta?.env?.[envKey]) {
|
|
215
|
-
return import.meta.env[envKey];
|
|
216
|
-
}
|
|
217
|
-
if (typeof window !== 'undefined' && window?.__TAGADA_ENV__?.[envKey]) {
|
|
218
|
-
return window.__TAGADA_ENV__[envKey];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
return undefined;
|
|
222
|
-
};
|
|
223
|
-
const storeId = resolveEnv('TAGADA_STORE_ID');
|
|
224
|
-
const accountId = resolveEnv('TAGADA_ACCOUNT_ID');
|
|
225
|
-
const basePath = resolveEnv('TAGADA_BASE_PATH');
|
|
226
|
-
const configName = resolveEnv('TAGADA_CONFIG_NAME');
|
|
227
|
-
if (!storeId) {
|
|
213
|
+
const storeId = resolveEnvValue('TAGADA_STORE_ID');
|
|
214
|
+
const accountId = resolveEnvValue('TAGADA_ACCOUNT_ID');
|
|
215
|
+
const basePath = resolveEnvValue('TAGADA_BASE_PATH');
|
|
216
|
+
const configName = resolveEnvValue('TAGADA_CONFIG_NAME');
|
|
217
|
+
if (!storeId || !accountId) {
|
|
228
218
|
console.warn('[createRawPluginConfig] No storeId provided');
|
|
229
219
|
return undefined;
|
|
230
220
|
}
|
|
231
|
-
const resolvedConfig = await loadLocalConfig(configName
|
|
221
|
+
const resolvedConfig = await loadLocalConfig(configName);
|
|
232
222
|
if (!resolvedConfig) {
|
|
233
223
|
console.warn('[createRawPluginConfig] No config found');
|
|
234
224
|
return undefined;
|
package/dist/v2/index.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type { ApplyDiscountResponse, Discount, DiscountCodeValidation, RemoveDis
|
|
|
22
22
|
export type { ToggleOrderBumpResponse, VipOffer, VipPreviewResponse } from './core/resources/vipOffers';
|
|
23
23
|
export type { StoreConfig } from './core/resources/storeConfig';
|
|
24
24
|
export type { FunnelContextUpdateRequest, FunnelContextUpdateResponse, FunnelEvent, FunnelInitializeRequest, FunnelInitializeResponse, FunnelNavigateRequest, FunnelNavigateResponse, FunnelNavigationAction, FunnelNavigationResult, SimpleFunnelContext } from './core/resources/funnel';
|
|
25
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
25
|
+
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
26
26
|
export type { TranslateFunction, TranslationText, UseTranslationOptions, UseTranslationResult } from './react/hooks/useTranslation';
|
|
27
27
|
export type { ClubOffer, ClubOfferItem, ClubOfferLineItem, ClubOfferSummary, UseClubOffersOptions, UseClubOffersResult } from './react/hooks/useClubOffers';
|
|
28
28
|
export type { UseLoginOptions, UseLoginResult } from './react/hooks/useLogin';
|
package/dist/v2/index.js
CHANGED
|
@@ -14,4 +14,4 @@ export * from './core/utils/products';
|
|
|
14
14
|
// Path remapping helpers (framework-agnostic)
|
|
15
15
|
export * from './core/pathRemapping';
|
|
16
16
|
// React exports (hooks and components only, types are exported above)
|
|
17
|
-
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
17
|
+
export { ApplePayButton, ExpressPaymentMethodsProvider, formatMoney, getAvailableLanguages, GooglePayButton, queryKeys, TagadaProvider, useApiMutation, useApiQuery, useAuth, useCheckout, useCheckoutToken, useClubOffers, useCountryOptions, useCurrency, useCustomer, useCustomerInfos, useCustomerOrders, useCustomerSubscriptions, useDiscounts, useExpressPaymentMethods, useFunnel, useGeoLocation, useGoogleAutocomplete, useInvalidateQuery, useISOData, useLanguageImport, useLogin, useOffers, useOrder, useOrderBump, usePayment, usePluginConfig, usePostPurchases, usePreloadQuery, useProducts, usePromotions, useRegionOptions, useRemappableParams, useShippingRates, useSimpleFunnel, useStoreConfig, useTagadaContext, useThreeds, useThreedsModal, useTranslation, useVipOffers } from './react';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useApiClient Hook - Provides access to the authenticated API client
|
|
3
|
+
*
|
|
4
|
+
* This hook returns the ApiClient instance from the TagadaProvider context.
|
|
5
|
+
* The client is guaranteed to have the latest authentication token.
|
|
6
|
+
*/
|
|
7
|
+
import type { ApiService } from '../../../react/services/apiService';
|
|
8
|
+
/**
|
|
9
|
+
* Hook to get the authenticated API service from context
|
|
10
|
+
*
|
|
11
|
+
* @returns The ApiService instance with the current authentication token
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const apiService = useApiClient();
|
|
16
|
+
* const response = await apiService.get('/api/endpoint');
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function useApiClient(): ApiService;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useApiClient Hook - Provides access to the authenticated API client
|
|
3
|
+
*
|
|
4
|
+
* This hook returns the ApiClient instance from the TagadaProvider context.
|
|
5
|
+
* The client is guaranteed to have the latest authentication token.
|
|
6
|
+
*/
|
|
7
|
+
import { useTagadaContext } from '../providers/TagadaProvider';
|
|
8
|
+
/**
|
|
9
|
+
* Hook to get the authenticated API service from context
|
|
10
|
+
*
|
|
11
|
+
* @returns The ApiService instance with the current authentication token
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const apiService = useApiClient();
|
|
16
|
+
* const response = await apiService.get('/api/endpoint');
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function useApiClient() {
|
|
20
|
+
const { apiService } = useTagadaContext();
|
|
21
|
+
return apiService;
|
|
22
|
+
}
|
|
@@ -33,7 +33,6 @@ export function useCredits(options = {}) {
|
|
|
33
33
|
if (!customerId || !storeId) {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
-
console.log('customerId', customerId, 'storeId', storeId);
|
|
37
36
|
return await creditsResource.getCustomerCredits(customerId, storeId);
|
|
38
37
|
},
|
|
39
38
|
staleTime: 30 * 1000, // 30 seconds - fresher than most resources
|
|
@@ -42,8 +42,13 @@ export function useFunnel(options) {
|
|
|
42
42
|
console.warn('🍪 Funnel: No session ID available for query');
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
+
// ✅ Automatically include currentUrl for session sync on page load/refresh
|
|
46
|
+
const currentUrl = typeof window !== 'undefined' ? window.location.href : undefined;
|
|
45
47
|
console.log(`🍪 Funnel: Fetching session data for ID: ${context.sessionId}`);
|
|
46
|
-
|
|
48
|
+
if (currentUrl) {
|
|
49
|
+
console.log(`🍪 Funnel: Including current URL for sync: ${currentUrl}`);
|
|
50
|
+
}
|
|
51
|
+
const response = await funnelResource.getSession(context.sessionId, currentUrl);
|
|
47
52
|
console.log(`🍪 Funnel: Session fetch response:`, response);
|
|
48
53
|
if (response.success && response.context) {
|
|
49
54
|
return response.context;
|
|
@@ -133,17 +138,26 @@ export function useFunnel(options) {
|
|
|
133
138
|
if (!context.sessionId) {
|
|
134
139
|
throw new Error('Funnel session ID missing - session may be corrupted');
|
|
135
140
|
}
|
|
141
|
+
// ✅ Automatically include currentUrl for URL-to-Step mapping
|
|
142
|
+
// User can override by providing event.currentUrl explicitly
|
|
143
|
+
const currentUrl = event.currentUrl || (typeof window !== 'undefined' ? window.location.href : undefined);
|
|
136
144
|
console.log(`🍪 Funnel: Navigating with session ID: ${context.sessionId}`);
|
|
145
|
+
if (currentUrl) {
|
|
146
|
+
console.log(`🍪 Funnel: Current URL for sync: ${currentUrl}`);
|
|
147
|
+
}
|
|
137
148
|
const requestBody = {
|
|
138
149
|
sessionId: context.sessionId,
|
|
139
150
|
event: {
|
|
140
151
|
type: event.type,
|
|
141
152
|
data: event.data,
|
|
142
|
-
timestamp: event.timestamp || new Date().toISOString()
|
|
153
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
154
|
+
currentUrl: event.currentUrl // Preserve user override if provided
|
|
143
155
|
},
|
|
144
156
|
contextUpdates: {
|
|
145
157
|
lastActivityAt: Date.now()
|
|
146
|
-
}
|
|
158
|
+
},
|
|
159
|
+
currentUrl, // ✅ Send to backend for URL→Step mapping
|
|
160
|
+
funnelId: effectiveFunnelId || options.funnelId // ✅ Send for session recovery
|
|
147
161
|
};
|
|
148
162
|
const response = await funnelResource.navigate(requestBody);
|
|
149
163
|
if (response.success && response.result) {
|
|
@@ -156,19 +170,32 @@ export function useFunnel(options) {
|
|
|
156
170
|
onSuccess: (result) => {
|
|
157
171
|
if (!context)
|
|
158
172
|
return;
|
|
173
|
+
// 🔄 Handle session recovery (if backend created a new session)
|
|
174
|
+
let recoveredSessionId;
|
|
175
|
+
if (result.sessionId && result.sessionId !== context.sessionId) {
|
|
176
|
+
console.warn(`🔄 Funnel: Session recovered! Old: ${context.sessionId}, New: ${result.sessionId}`);
|
|
177
|
+
recoveredSessionId = result.sessionId;
|
|
178
|
+
}
|
|
159
179
|
// Update local context
|
|
160
180
|
const newContext = {
|
|
161
181
|
...context,
|
|
182
|
+
sessionId: recoveredSessionId || context.sessionId, // ✅ Use recovered session ID if provided
|
|
162
183
|
currentStepId: result.stepId,
|
|
163
184
|
previousStepId: context.currentStepId,
|
|
164
185
|
lastActivityAt: Date.now(),
|
|
165
186
|
metadata: {
|
|
166
187
|
...context.metadata,
|
|
167
188
|
lastEvent: 'navigation',
|
|
168
|
-
lastTransition: new Date().toISOString()
|
|
189
|
+
lastTransition: new Date().toISOString(),
|
|
190
|
+
...(recoveredSessionId ? { recovered: true, oldSessionId: context.sessionId } : {})
|
|
169
191
|
}
|
|
170
192
|
};
|
|
171
193
|
setContext(newContext);
|
|
194
|
+
// Update cookie with new session ID if recovered
|
|
195
|
+
if (recoveredSessionId) {
|
|
196
|
+
document.cookie = `funnelSessionId=${recoveredSessionId}; path=/; max-age=86400; SameSite=Lax`;
|
|
197
|
+
console.log(`🍪 Funnel: Updated cookie with recovered session ID: ${recoveredSessionId}`);
|
|
198
|
+
}
|
|
172
199
|
// Create typed navigation result
|
|
173
200
|
const navigationResult = {
|
|
174
201
|
stepId: result.stepId,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to extract URL parameters that works with both remapped and non-remapped paths
|
|
3
|
+
*
|
|
4
|
+
* This hook automatically detects if the current path is remapped and extracts
|
|
5
|
+
* parameters correctly in both cases, so your components don't need to know
|
|
6
|
+
* about path remapping at all.
|
|
7
|
+
*
|
|
8
|
+
* @param internalPath - The internal path pattern (e.g., "/hello-with-param/:myparam")
|
|
9
|
+
* @returns Parameters object with extracted values
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* function HelloWithParamPage() {
|
|
14
|
+
* // Works for both /hello-with-param/test AND /myremap/test
|
|
15
|
+
* const { myparam } = useRemappableParams<{ myparam: string }>('/hello-with-param/:myparam');
|
|
16
|
+
*
|
|
17
|
+
* return <div>Parameter: {myparam}</div>;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function useRemappableParams<T extends Record<string, string>>(internalPath: string): Partial<T>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useParams } from 'react-router-dom';
|
|
2
|
+
import { getPathInfo } from '../../core/pathRemapping';
|
|
3
|
+
import { match } from 'path-to-regexp';
|
|
4
|
+
/**
|
|
5
|
+
* Hook to extract URL parameters that works with both remapped and non-remapped paths
|
|
6
|
+
*
|
|
7
|
+
* This hook automatically detects if the current path is remapped and extracts
|
|
8
|
+
* parameters correctly in both cases, so your components don't need to know
|
|
9
|
+
* about path remapping at all.
|
|
10
|
+
*
|
|
11
|
+
* @param internalPath - The internal path pattern (e.g., "/hello-with-param/:myparam")
|
|
12
|
+
* @returns Parameters object with extracted values
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* function HelloWithParamPage() {
|
|
17
|
+
* // Works for both /hello-with-param/test AND /myremap/test
|
|
18
|
+
* const { myparam } = useRemappableParams<{ myparam: string }>('/hello-with-param/:myparam');
|
|
19
|
+
*
|
|
20
|
+
* return <div>Parameter: {myparam}</div>;
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function useRemappableParams(internalPath) {
|
|
25
|
+
const routerParams = useParams();
|
|
26
|
+
const pathInfo = getPathInfo();
|
|
27
|
+
// If not remapped, just return router params
|
|
28
|
+
if (!pathInfo.isRemapped) {
|
|
29
|
+
return routerParams;
|
|
30
|
+
}
|
|
31
|
+
// If remapped, extract params from the external URL
|
|
32
|
+
try {
|
|
33
|
+
// Get the external pattern from localStorage (for testing) or from meta tag
|
|
34
|
+
let externalPattern = null;
|
|
35
|
+
// Check localStorage for explicit remapping (development/testing)
|
|
36
|
+
if (typeof localStorage !== 'undefined') {
|
|
37
|
+
try {
|
|
38
|
+
const remapData = localStorage.getItem('tagadapay-remap');
|
|
39
|
+
if (remapData) {
|
|
40
|
+
const parsed = JSON.parse(remapData);
|
|
41
|
+
if (parsed.externalPath && parsed.internalPath === internalPath) {
|
|
42
|
+
externalPattern = parsed.externalPath;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
// Ignore parsing errors
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Check meta tag for production remapping
|
|
51
|
+
if (!externalPattern && typeof document !== 'undefined') {
|
|
52
|
+
const metaTag = document.querySelector('meta[name="tagadapay-path-remap-pattern"]');
|
|
53
|
+
if (metaTag) {
|
|
54
|
+
externalPattern = metaTag.getAttribute('content');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// If we have an external pattern, extract params from it
|
|
58
|
+
if (externalPattern) {
|
|
59
|
+
const matchFn = match(externalPattern, { decode: decodeURIComponent });
|
|
60
|
+
const result = matchFn(pathInfo.externalPath);
|
|
61
|
+
if (result && typeof result !== 'boolean') {
|
|
62
|
+
// We have extracted params from external URL
|
|
63
|
+
// Now we need to map them to internal param names
|
|
64
|
+
// Extract param names from both patterns
|
|
65
|
+
const externalParamNames = extractParamNames(externalPattern);
|
|
66
|
+
const internalParamNames = extractParamNames(internalPath);
|
|
67
|
+
// Map external params to internal params (by position)
|
|
68
|
+
const mappedParams = {};
|
|
69
|
+
externalParamNames.forEach((externalName, index) => {
|
|
70
|
+
const internalName = internalParamNames[index];
|
|
71
|
+
if (internalName && result.params[externalName] !== undefined) {
|
|
72
|
+
mappedParams[internalName] = result.params[externalName];
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return mappedParams;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Fallback: try to extract from internal path directly
|
|
79
|
+
const matchFn = match(internalPath, { decode: decodeURIComponent });
|
|
80
|
+
const result = matchFn(pathInfo.externalPath);
|
|
81
|
+
if (result && typeof result !== 'boolean') {
|
|
82
|
+
return result.params;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error('[useRemappableParams] Failed to extract params:', error);
|
|
87
|
+
}
|
|
88
|
+
// Fallback to router params
|
|
89
|
+
return routerParams;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Extract parameter names from a path pattern
|
|
93
|
+
* @param pattern - Path pattern like "/hello/:param1/:param2"
|
|
94
|
+
* @returns Array of parameter names like ["param1", "param2"]
|
|
95
|
+
*/
|
|
96
|
+
function extractParamNames(pattern) {
|
|
97
|
+
const paramRegex = /:([^/]+)/g;
|
|
98
|
+
const matches = [];
|
|
99
|
+
let match;
|
|
100
|
+
while ((match = paramRegex.exec(pattern)) !== null) {
|
|
101
|
+
matches.push(match[1]);
|
|
102
|
+
}
|
|
103
|
+
return matches;
|
|
104
|
+
}
|
package/dist/v2/react/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { useGoogleAutocomplete } from './hooks/useGoogleAutocomplete';
|
|
|
20
20
|
export { getAvailableLanguages, useCountryOptions, useISOData, useLanguageImport, useRegionOptions } from './hooks/useISOData';
|
|
21
21
|
export { useLogin } from './hooks/useLogin';
|
|
22
22
|
export { usePluginConfig } from './hooks/usePluginConfig';
|
|
23
|
+
export { useRemappableParams } from './hooks/useRemappableParams';
|
|
23
24
|
export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQuery } from './hooks/useApiQuery';
|
|
24
25
|
export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
|
|
25
26
|
export { useCurrency } from './hooks/useCurrency';
|
package/dist/v2/react/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export { useGoogleAutocomplete } from './hooks/useGoogleAutocomplete';
|
|
|
23
23
|
export { getAvailableLanguages, useCountryOptions, useISOData, useLanguageImport, useRegionOptions } from './hooks/useISOData';
|
|
24
24
|
export { useLogin } from './hooks/useLogin';
|
|
25
25
|
export { usePluginConfig } from './hooks/usePluginConfig';
|
|
26
|
+
export { useRemappableParams } from './hooks/useRemappableParams';
|
|
26
27
|
// TanStack Query hooks (recommended)
|
|
27
28
|
export { queryKeys, useApiMutation, useApiQuery, useInvalidateQuery, usePreloadQuery } from './hooks/useApiQuery';
|
|
28
29
|
export { useCheckoutQuery as useCheckout } from './hooks/useCheckoutQuery';
|
|
@@ -12,6 +12,7 @@ import { decodeJWTClient, isTokenExpired } from '../../../react/utils/jwtDecoder
|
|
|
12
12
|
import { convertCurrency, formatMoney, formatMoneyWithoutSymbol, formatSimpleMoney, getCurrencyInfo, minorUnitsToMajorUnits, moneyStringOrNumberToMinorUnits, } from '../../../react/utils/money';
|
|
13
13
|
import { clearClientToken, getClientToken, setClientToken } from '../../../react/utils/tokenStorage';
|
|
14
14
|
import { ApiClient } from '../../core/resources/apiClient';
|
|
15
|
+
import { resolveEnvValue } from '../../core/utils/env';
|
|
15
16
|
import { loadPluginConfig } from '../../core/utils/pluginConfig';
|
|
16
17
|
import { default as DebugDrawer } from '../components/DebugDrawer';
|
|
17
18
|
import { setGlobalApiClient } from '../hooks/useApiQuery';
|
|
@@ -47,6 +48,15 @@ const InitializationLoader = () => (_jsxs("div", { style: {
|
|
|
47
48
|
100% { transform: rotate(360deg); }
|
|
48
49
|
}
|
|
49
50
|
` })] }));
|
|
51
|
+
const isEnvironment = (value) => value === 'production' || value === 'development' || value === 'local';
|
|
52
|
+
const resolveEnvironmentFromVariables = () => {
|
|
53
|
+
const envValue = resolveEnvValue('TAGADA_ENV') ?? resolveEnvValue('TAGADA_ENVIRONMENT');
|
|
54
|
+
if (!envValue) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const normalized = envValue.trim().toLowerCase();
|
|
58
|
+
return isEnvironment(normalized) ? normalized : undefined;
|
|
59
|
+
};
|
|
50
60
|
const TagadaContext = createContext(null);
|
|
51
61
|
// Global instance tracking for TagadaProvider
|
|
52
62
|
let globalTagadaInstance = null;
|
|
@@ -154,7 +164,6 @@ rawPluginConfig, }) {
|
|
|
154
164
|
}, [configVariant, rawPluginConfig]);
|
|
155
165
|
// Extract store/account IDs from plugin config (only source now)
|
|
156
166
|
const storeId = pluginConfig.storeId;
|
|
157
|
-
const _accountId = pluginConfig.accountId;
|
|
158
167
|
const [isLoading, setIsLoading] = useState(true);
|
|
159
168
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
160
169
|
const [token, setToken] = useState(null);
|
|
@@ -163,12 +172,16 @@ rawPluginConfig, }) {
|
|
|
163
172
|
const isInitializing = useRef(false);
|
|
164
173
|
// Initialize environment configuration
|
|
165
174
|
const [environmentConfig, _setEnvironmentConfig] = useState(() => {
|
|
166
|
-
const
|
|
175
|
+
const envFromVariables = resolveEnvironmentFromVariables();
|
|
176
|
+
const detectedEnv = environment || envFromVariables || detectEnvironment();
|
|
167
177
|
const config = getEnvironmentConfig(detectedEnv);
|
|
168
178
|
// Log environment detection for debugging
|
|
169
179
|
if (environment) {
|
|
170
180
|
console.log(`[TagadaSDK] Using explicit environment: ${environment}`);
|
|
171
181
|
}
|
|
182
|
+
else if (envFromVariables) {
|
|
183
|
+
console.log(`[TagadaSDK] Using environment from env variables: ${envFromVariables}`);
|
|
184
|
+
}
|
|
172
185
|
else {
|
|
173
186
|
console.log(`[TagadaSDK] Auto-detected environment: ${detectedEnv} (${typeof window !== 'undefined' ? window.location.hostname : 'SSR'})`);
|
|
174
187
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for waiting until session is initialized
|
|
3
|
+
* Optimized with smart polling and fast path
|
|
4
|
+
*/
|
|
5
|
+
export interface SessionWaiterOptions {
|
|
6
|
+
checkReady: () => boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
pollIntervalMs?: number;
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface SessionReadyCheckOptions {
|
|
13
|
+
isSessionInitialized: boolean;
|
|
14
|
+
token?: string | null;
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if session is fully ready (initialized + token available)
|
|
19
|
+
* Checks token from context (synchronous and reliable source of truth)
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSessionFullyReady(options: SessionReadyCheckOptions): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Wait for a condition to be true with smart polling
|
|
24
|
+
* Fast path: returns immediately if condition is already true
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitForCondition(options: SessionWaiterOptions): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for waiting until session is initialized
|
|
3
|
+
* Optimized with smart polling and fast path
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if session is fully ready (initialized + token available)
|
|
7
|
+
* Checks token from context (synchronous and reliable source of truth)
|
|
8
|
+
*/
|
|
9
|
+
export function isSessionFullyReady(options) {
|
|
10
|
+
const { isSessionInitialized, token, debug = false } = options;
|
|
11
|
+
if (!isSessionInitialized) {
|
|
12
|
+
if (debug)
|
|
13
|
+
console.log('🔍 [SessionWaiter] Session not initialized yet');
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
const hasToken = !!token;
|
|
17
|
+
if (debug) {
|
|
18
|
+
if (hasToken) {
|
|
19
|
+
console.log(`✅ [SessionWaiter] Session fully ready - Token: ${token.substring(0, 8)}...`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log('⚠️ [SessionWaiter] Session initialized but NO TOKEN in context');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return hasToken;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Wait for a condition to be true with smart polling
|
|
29
|
+
* Fast path: returns immediately if condition is already true
|
|
30
|
+
*/
|
|
31
|
+
export async function waitForCondition(options) {
|
|
32
|
+
const { checkReady, label = 'Condition', timeoutMs = 10000, pollIntervalMs = 50, debug = false } = options;
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
// Fast path: if already ready, return immediately
|
|
35
|
+
if (checkReady()) {
|
|
36
|
+
console.log(`⚡ [${label}] Already ready (fast path)`);
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
}
|
|
39
|
+
console.log(`⏳ [${label}] Waiting... (timeout: ${timeoutMs}ms, poll interval: ${pollIntervalMs}ms)`);
|
|
40
|
+
// Smart polling
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const maxAttempts = Math.floor(timeoutMs / pollIntervalMs);
|
|
43
|
+
let attempts = 0;
|
|
44
|
+
let lastLogTime = startTime;
|
|
45
|
+
const check = () => {
|
|
46
|
+
attempts++;
|
|
47
|
+
const elapsed = Date.now() - startTime;
|
|
48
|
+
if (debug) {
|
|
49
|
+
console.log(`🔍 [${label}] Check #${attempts} (${elapsed}ms elapsed)`);
|
|
50
|
+
}
|
|
51
|
+
// Log every 1 second to show we're still waiting
|
|
52
|
+
if (elapsed - (lastLogTime - startTime) >= 1000) {
|
|
53
|
+
console.log(`⏳ [${label}] Still waiting... (${elapsed}ms elapsed, ${attempts} checks)`);
|
|
54
|
+
lastLogTime = Date.now();
|
|
55
|
+
}
|
|
56
|
+
if (checkReady()) {
|
|
57
|
+
console.log(`✅ [${label}] Ready! (took ${elapsed}ms, ${attempts} checks)`);
|
|
58
|
+
resolve();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (attempts >= maxAttempts) {
|
|
62
|
+
console.error(`❌ [${label}] Timeout after ${timeoutMs}ms (${attempts} checks)`);
|
|
63
|
+
reject(new Error(`${label} timeout after ${timeoutMs}ms. Please refresh the page and try again.`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Continue polling
|
|
67
|
+
setTimeout(check, pollIntervalMs);
|
|
68
|
+
};
|
|
69
|
+
// Start checking
|
|
70
|
+
check();
|
|
71
|
+
});
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tagadapay/plugin-sdk",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.18",
|
|
4
4
|
"description": "Modern React SDK for building Tagada Pay plugins",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"clean": "rm -rf dist",
|
|
27
27
|
"lint": "echo \"No linting configured\"",
|
|
28
|
-
"test": "
|
|
28
|
+
"test": "jest --no-watchman",
|
|
29
|
+
"test:watch": "jest --watch --no-watchman",
|
|
30
|
+
"test:coverage": "jest --coverage --no-watchman",
|
|
29
31
|
"dev": "tsc --watch",
|
|
30
32
|
"prepublishOnly": "npm run clean && npm run build",
|
|
31
33
|
"publish:patch": "npm version patch && npm publish",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"@basis-theory/basis-theory-react": "^1.32.5",
|
|
61
63
|
"@basis-theory/web-threeds": "^1.0.1",
|
|
62
64
|
"@google-pay/button-react": "^3.0.10",
|
|
65
|
+
"@tagadapay/plugin-sdk": "link:",
|
|
63
66
|
"@tanstack/react-query": "^5.90.2",
|
|
64
67
|
"axios": "^1.10.0",
|
|
65
68
|
"iso3166-2-db": "^2.3.11",
|
|
@@ -68,9 +71,12 @@
|
|
|
68
71
|
"swr": "^2.3.6"
|
|
69
72
|
},
|
|
70
73
|
"devDependencies": {
|
|
74
|
+
"@types/jest": "^29.5.0",
|
|
71
75
|
"@types/node": "^18.0.0",
|
|
72
76
|
"@types/react": "^19",
|
|
73
77
|
"@types/react-dom": "^19",
|
|
78
|
+
"jest": "^29.5.0",
|
|
79
|
+
"ts-jest": "^29.1.0",
|
|
74
80
|
"typescript": "^5.0.0"
|
|
75
81
|
},
|
|
76
82
|
"peerDependencies": {
|