@tagadapay/plugin-sdk 3.0.12 → 3.0.14
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/external-tracker.js +3593 -111
- package/dist/external-tracker.min.js +25 -2
- package/dist/external-tracker.min.js.map +4 -4
- package/dist/react/types.d.ts +2 -0
- package/dist/v2/core/client.d.ts +4 -0
- package/dist/v2/core/client.js +110 -26
- package/dist/v2/core/config/environment.js +6 -0
- package/dist/v2/core/funnelClient.d.ts +18 -1
- package/dist/v2/core/funnelClient.js +90 -17
- package/dist/v2/core/resources/checkout.d.ts +44 -1
- package/dist/v2/core/resources/checkout.js +48 -1
- package/dist/v2/core/resources/funnel.d.ts +44 -4
- package/dist/v2/core/resources/offers.d.ts +26 -0
- package/dist/v2/core/resources/offers.js +37 -0
- package/dist/v2/core/types.d.ts +3 -1
- package/dist/v2/core/utils/authHandoff.d.ts +60 -0
- package/dist/v2/core/utils/authHandoff.js +154 -0
- package/dist/v2/core/utils/deviceInfo.d.ts +20 -3
- package/dist/v2/core/utils/deviceInfo.js +62 -94
- package/dist/v2/core/utils/previewMode.d.ts +4 -0
- package/dist/v2/core/utils/previewMode.js +4 -0
- package/dist/v2/react/hooks/useCheckoutQuery.d.ts +0 -1
- package/dist/v2/react/hooks/useCheckoutQuery.js +12 -4
- package/dist/v2/react/hooks/useFunnelLegacy.js +39 -11
- package/dist/v2/react/hooks/usePreviewOffer.d.ts +3 -3
- package/dist/v2/react/hooks/usePreviewOffer.js +20 -15
- package/dist/v2/react/hooks/useTranslation.js +12 -4
- package/dist/v2/standalone/index.d.ts +2 -1
- package/dist/v2/standalone/index.js +2 -1
- package/package.json +3 -1
|
@@ -343,16 +343,25 @@ export interface FunnelNavigationAction {
|
|
|
343
343
|
data?: any;
|
|
344
344
|
}
|
|
345
345
|
export interface FunnelNavigationResult {
|
|
346
|
-
stepId
|
|
346
|
+
stepId?: string;
|
|
347
347
|
url?: string;
|
|
348
|
-
action
|
|
349
|
-
context
|
|
348
|
+
action?: FunnelNavigationAction;
|
|
349
|
+
context?: SimpleFunnelContext;
|
|
350
350
|
tracking?: {
|
|
351
351
|
from: string;
|
|
352
352
|
to: string;
|
|
353
353
|
event: string;
|
|
354
354
|
timestamp: string;
|
|
355
355
|
};
|
|
356
|
+
/**
|
|
357
|
+
* Fire-and-forget response: indicates navigation was queued
|
|
358
|
+
* When true, stepId, url, action, and context will be undefined
|
|
359
|
+
*/
|
|
360
|
+
queued?: boolean;
|
|
361
|
+
/**
|
|
362
|
+
* Session ID (may be different if session was recovered)
|
|
363
|
+
*/
|
|
364
|
+
sessionId?: string;
|
|
356
365
|
}
|
|
357
366
|
/**
|
|
358
367
|
* Funnel context available to plugins
|
|
@@ -480,11 +489,37 @@ export interface FunnelNavigateRequest {
|
|
|
480
489
|
* If session is not found and funnelId is provided, a new session will be created
|
|
481
490
|
*/
|
|
482
491
|
funnelId?: string;
|
|
492
|
+
/**
|
|
493
|
+
* Funnel step ID (from SDK injection/URL params)
|
|
494
|
+
* Used to sync session state before navigation
|
|
495
|
+
*/
|
|
496
|
+
funnelStepId?: string;
|
|
497
|
+
/**
|
|
498
|
+
* Funnel variant ID (from SDK injection/URL params for A/B testing)
|
|
499
|
+
* Used to sync session state before navigation
|
|
500
|
+
*/
|
|
501
|
+
funnelVariantId?: string;
|
|
502
|
+
/**
|
|
503
|
+
* Fire and forget mode - queues navigation to QStash and returns immediately
|
|
504
|
+
* No response data needed, just acknowledgment that request was queued
|
|
505
|
+
* When true, result will only contain queued status and sessionId (no URL or stepId)
|
|
506
|
+
*/
|
|
507
|
+
fireAndForget?: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* ✅ Customer tags to set (merged with existing customer tags)
|
|
510
|
+
* @example ['segment:vip', 'cart_value:high']
|
|
511
|
+
*/
|
|
512
|
+
customerTags?: string[];
|
|
513
|
+
/**
|
|
514
|
+
* ✅ Device ID for geo/device tag enrichment (optional)
|
|
515
|
+
* @example 'dev_abc123xyz'
|
|
516
|
+
*/
|
|
517
|
+
deviceId?: string;
|
|
483
518
|
}
|
|
484
519
|
export interface FunnelNavigateResponse {
|
|
485
520
|
success: boolean;
|
|
486
521
|
result?: {
|
|
487
|
-
stepId
|
|
522
|
+
stepId?: string;
|
|
488
523
|
url?: string;
|
|
489
524
|
/**
|
|
490
525
|
* New session ID if session was recovered (expired/removed)
|
|
@@ -497,6 +532,11 @@ export interface FunnelNavigateResponse {
|
|
|
497
532
|
event: string;
|
|
498
533
|
timestamp: string;
|
|
499
534
|
};
|
|
535
|
+
/**
|
|
536
|
+
* Fire-and-forget response: indicates navigation was queued
|
|
537
|
+
* When present, stepId and url will be undefined
|
|
538
|
+
*/
|
|
539
|
+
queued?: boolean;
|
|
500
540
|
};
|
|
501
541
|
error?: string;
|
|
502
542
|
}
|
|
@@ -303,6 +303,32 @@ export declare class OffersResource {
|
|
|
303
303
|
checkoutSessionId?: string;
|
|
304
304
|
customerId?: string;
|
|
305
305
|
}>;
|
|
306
|
+
/**
|
|
307
|
+
* Transform offer to checkout session (async mode) ⚡
|
|
308
|
+
* Response time: ~50ms (20-50x faster!)
|
|
309
|
+
*
|
|
310
|
+
* Returns checkoutToken immediately, background job completes processing.
|
|
311
|
+
* Use getCheckout() to fetch full session data - it auto-waits for completion.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* // Fast transform
|
|
315
|
+
* const { checkoutToken } = await offers.toCheckoutAsync(offerId, currency, lineItems, returnUrl, mainOrderId);
|
|
316
|
+
*
|
|
317
|
+
* // Redirect user immediately
|
|
318
|
+
* window.location.href = `/checkout/${checkoutToken}/op`;
|
|
319
|
+
*
|
|
320
|
+
* // By the time page loads, background processing is usually complete
|
|
321
|
+
*/
|
|
322
|
+
toCheckoutAsync(offerId: string, currency?: string, lineItems?: Array<{
|
|
323
|
+
lineItemId?: string;
|
|
324
|
+
productId?: string;
|
|
325
|
+
variantId: string;
|
|
326
|
+
quantity: number;
|
|
327
|
+
}>, returnUrl?: string, mainOrderId?: string): Promise<{
|
|
328
|
+
checkoutToken: string;
|
|
329
|
+
customerId: string;
|
|
330
|
+
status: 'processing';
|
|
331
|
+
}>;
|
|
306
332
|
/**
|
|
307
333
|
* @deprecated Use transformToCheckoutSession instead
|
|
308
334
|
* Transform offer to checkout session with dynamic variant selection
|
|
@@ -202,6 +202,43 @@ export class OffersResource {
|
|
|
202
202
|
customerId: response.customerId,
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Transform offer to checkout session (async mode) ⚡
|
|
207
|
+
* Response time: ~50ms (20-50x faster!)
|
|
208
|
+
*
|
|
209
|
+
* Returns checkoutToken immediately, background job completes processing.
|
|
210
|
+
* Use getCheckout() to fetch full session data - it auto-waits for completion.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Fast transform
|
|
214
|
+
* const { checkoutToken } = await offers.toCheckoutAsync(offerId, currency, lineItems, returnUrl, mainOrderId);
|
|
215
|
+
*
|
|
216
|
+
* // Redirect user immediately
|
|
217
|
+
* window.location.href = `/checkout/${checkoutToken}/op`;
|
|
218
|
+
*
|
|
219
|
+
* // By the time page loads, background processing is usually complete
|
|
220
|
+
*/
|
|
221
|
+
async toCheckoutAsync(offerId, currency = 'USD', lineItems, returnUrl, mainOrderId) {
|
|
222
|
+
console.log('🛒 [OffersResource] Calling to-checkout-async API:', {
|
|
223
|
+
offerId,
|
|
224
|
+
currency,
|
|
225
|
+
lineItems,
|
|
226
|
+
returnUrl,
|
|
227
|
+
mainOrderId,
|
|
228
|
+
endpoint: `/api/v1/offers/${offerId}/to-checkout-async`,
|
|
229
|
+
});
|
|
230
|
+
const response = await this.apiClient.post(`/api/v1/offers/${offerId}/to-checkout-async`, {
|
|
231
|
+
offerId,
|
|
232
|
+
lineItems: lineItems?.map(item => ({
|
|
233
|
+
variantId: item.variantId,
|
|
234
|
+
quantity: item.quantity,
|
|
235
|
+
})) || [],
|
|
236
|
+
returnUrl: returnUrl || (typeof window !== 'undefined' ? window.location.href : ''),
|
|
237
|
+
mainOrderId: mainOrderId || '',
|
|
238
|
+
});
|
|
239
|
+
console.log('📥 [OffersResource] To-checkout-async API response:', response);
|
|
240
|
+
return response;
|
|
241
|
+
}
|
|
205
242
|
/**
|
|
206
243
|
* @deprecated Use transformToCheckoutSession instead
|
|
207
244
|
* Transform offer to checkout session with dynamic variant selection
|
package/dist/v2/core/types.d.ts
CHANGED
|
@@ -7,7 +7,9 @@ export interface ApiConfig {
|
|
|
7
7
|
endpoints: {
|
|
8
8
|
checkout: {
|
|
9
9
|
sessionInit: string;
|
|
10
|
+
sessionInitAsync: string;
|
|
10
11
|
sessionStatus: string;
|
|
12
|
+
asyncStatus: string;
|
|
11
13
|
};
|
|
12
14
|
customer: {
|
|
13
15
|
profile: string;
|
|
@@ -262,7 +264,7 @@ export interface CustomerInfos {
|
|
|
262
264
|
}
|
|
263
265
|
export interface SessionInitResponse {
|
|
264
266
|
store: Store;
|
|
265
|
-
locale
|
|
267
|
+
locale?: string;
|
|
266
268
|
messages?: Record<string, string>;
|
|
267
269
|
customer?: Customer;
|
|
268
270
|
session?: Session;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Domain Auth Handoff Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles automatic resolution of authCode query parameters for seamless
|
|
5
|
+
* cross-domain authentication.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Check URL for authCode parameter
|
|
9
|
+
* 2. If present, resolve it with backend
|
|
10
|
+
* 3. Store the returned token (overrides any existing token)
|
|
11
|
+
* 4. Clean the URL (remove authCode)
|
|
12
|
+
* 5. Return resolved customer and context
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthHandoffResolveResponse {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
token: string;
|
|
17
|
+
customer: {
|
|
18
|
+
id: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
firstName?: string;
|
|
21
|
+
lastName?: string;
|
|
22
|
+
role: 'authenticated' | 'anonymous';
|
|
23
|
+
};
|
|
24
|
+
context: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if authCode is present in URL
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasAuthCode(): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Get authCode from URL
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAuthCode(): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a code has already been resolved
|
|
36
|
+
*/
|
|
37
|
+
export declare function isCodeAlreadyResolved(code: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve auth handoff and return token + customer info
|
|
40
|
+
*
|
|
41
|
+
* This function:
|
|
42
|
+
* 1. Calls POST /api/v1/cms/auth/resolve-handoff
|
|
43
|
+
* 2. Stores the returned token (overrides existing)
|
|
44
|
+
* 3. Cleans the URL (removes authCode)
|
|
45
|
+
* 4. Returns customer and context data
|
|
46
|
+
*
|
|
47
|
+
* 🔒 Deduplication: Multiple calls with the same code will return the same promise
|
|
48
|
+
* to prevent duplicate API requests (e.g., React StrictMode double-mounting)
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveAuthHandoff(authCode: string, storeId: string, apiBaseUrl: string, debugMode?: boolean): Promise<AuthHandoffResolveResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove authCode from URL without page reload
|
|
53
|
+
* Uses history.replaceState to update URL cleanly
|
|
54
|
+
*/
|
|
55
|
+
export declare function cleanAuthCodeFromUrl(debugMode?: boolean): void;
|
|
56
|
+
/**
|
|
57
|
+
* Check if we should resolve authCode
|
|
58
|
+
* Returns true if authCode is present and valid format
|
|
59
|
+
*/
|
|
60
|
+
export declare function shouldResolveAuthCode(): boolean;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Domain Auth Handoff Utilities
|
|
3
|
+
*
|
|
4
|
+
* Handles automatic resolution of authCode query parameters for seamless
|
|
5
|
+
* cross-domain authentication.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Check URL for authCode parameter
|
|
9
|
+
* 2. If present, resolve it with backend
|
|
10
|
+
* 3. Store the returned token (overrides any existing token)
|
|
11
|
+
* 4. Clean the URL (remove authCode)
|
|
12
|
+
* 5. Return resolved customer and context
|
|
13
|
+
*/
|
|
14
|
+
import { setClientToken } from './tokenStorage';
|
|
15
|
+
// Track in-flight and completed resolutions to prevent duplicates
|
|
16
|
+
const resolutionCache = new Map();
|
|
17
|
+
const resolvedCodes = new Set();
|
|
18
|
+
/**
|
|
19
|
+
* Check if authCode is present in URL
|
|
20
|
+
*/
|
|
21
|
+
export function hasAuthCode() {
|
|
22
|
+
if (typeof window === 'undefined')
|
|
23
|
+
return false;
|
|
24
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
25
|
+
return urlParams.has('authCode');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get authCode from URL
|
|
29
|
+
*/
|
|
30
|
+
export function getAuthCode() {
|
|
31
|
+
if (typeof window === 'undefined')
|
|
32
|
+
return null;
|
|
33
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
34
|
+
return urlParams.get('authCode');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if a code has already been resolved
|
|
38
|
+
*/
|
|
39
|
+
export function isCodeAlreadyResolved(code) {
|
|
40
|
+
return resolvedCodes.has(code);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve auth handoff and return token + customer info
|
|
44
|
+
*
|
|
45
|
+
* This function:
|
|
46
|
+
* 1. Calls POST /api/v1/cms/auth/resolve-handoff
|
|
47
|
+
* 2. Stores the returned token (overrides existing)
|
|
48
|
+
* 3. Cleans the URL (removes authCode)
|
|
49
|
+
* 4. Returns customer and context data
|
|
50
|
+
*
|
|
51
|
+
* 🔒 Deduplication: Multiple calls with the same code will return the same promise
|
|
52
|
+
* to prevent duplicate API requests (e.g., React StrictMode double-mounting)
|
|
53
|
+
*/
|
|
54
|
+
export async function resolveAuthHandoff(authCode, storeId, apiBaseUrl, debugMode = false) {
|
|
55
|
+
// Check if already resolved
|
|
56
|
+
if (resolvedCodes.has(authCode)) {
|
|
57
|
+
if (debugMode) {
|
|
58
|
+
console.log('[AuthHandoff] Code already resolved, skipping duplicate request');
|
|
59
|
+
}
|
|
60
|
+
throw new Error('Auth code already resolved');
|
|
61
|
+
}
|
|
62
|
+
// Check if resolution is in-flight
|
|
63
|
+
const inFlightResolution = resolutionCache.get(authCode);
|
|
64
|
+
if (inFlightResolution) {
|
|
65
|
+
if (debugMode) {
|
|
66
|
+
console.log('[AuthHandoff] Resolution already in progress, waiting for existing request');
|
|
67
|
+
}
|
|
68
|
+
return inFlightResolution;
|
|
69
|
+
}
|
|
70
|
+
if (debugMode) {
|
|
71
|
+
console.log('[AuthHandoff] Resolving authCode:', authCode.substring(0, 15) + '...');
|
|
72
|
+
}
|
|
73
|
+
// Create resolution promise
|
|
74
|
+
const resolutionPromise = (async () => {
|
|
75
|
+
try {
|
|
76
|
+
// Call resolve endpoint (no authentication required)
|
|
77
|
+
const response = await fetch(`${apiBaseUrl}/api/v1/cms/auth/resolve-handoff`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
code: authCode,
|
|
84
|
+
storeId,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const errorData = await response.json().catch(() => ({ message: 'Unknown error' }));
|
|
89
|
+
throw new Error(errorData.message || `Failed to resolve auth handoff: ${response.status}`);
|
|
90
|
+
}
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
if (debugMode) {
|
|
93
|
+
console.log('[AuthHandoff] ✅ Resolved successfully:', {
|
|
94
|
+
customerId: data.customer.id,
|
|
95
|
+
role: data.customer.role,
|
|
96
|
+
hasContext: Object.keys(data.context).length > 0,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Store token (overrides any existing token)
|
|
100
|
+
if (debugMode) {
|
|
101
|
+
console.log('[AuthHandoff] Storing new token (overriding existing)');
|
|
102
|
+
}
|
|
103
|
+
setClientToken(data.token);
|
|
104
|
+
// Clean URL (remove authCode parameter)
|
|
105
|
+
cleanAuthCodeFromUrl(debugMode);
|
|
106
|
+
// Mark as resolved
|
|
107
|
+
resolvedCodes.add(authCode);
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('[AuthHandoff] ❌ Failed to resolve:', error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
// Remove from in-flight cache after completion (success or failure)
|
|
116
|
+
resolutionCache.delete(authCode);
|
|
117
|
+
}
|
|
118
|
+
})();
|
|
119
|
+
// Cache the in-flight promise
|
|
120
|
+
resolutionCache.set(authCode, resolutionPromise);
|
|
121
|
+
return resolutionPromise;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Remove authCode from URL without page reload
|
|
125
|
+
* Uses history.replaceState to update URL cleanly
|
|
126
|
+
*/
|
|
127
|
+
export function cleanAuthCodeFromUrl(debugMode = false) {
|
|
128
|
+
if (typeof window === 'undefined')
|
|
129
|
+
return;
|
|
130
|
+
const url = new URL(window.location.href);
|
|
131
|
+
if (url.searchParams.has('authCode')) {
|
|
132
|
+
url.searchParams.delete('authCode');
|
|
133
|
+
// Use replaceState to update URL without reload
|
|
134
|
+
window.history.replaceState({}, '', url.pathname + url.search + url.hash);
|
|
135
|
+
if (debugMode) {
|
|
136
|
+
console.log('[AuthHandoff] Cleaned authCode from URL');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check if we should resolve authCode
|
|
142
|
+
* Returns true if authCode is present and valid format
|
|
143
|
+
*/
|
|
144
|
+
export function shouldResolveAuthCode() {
|
|
145
|
+
const authCode = getAuthCode();
|
|
146
|
+
if (!authCode || !authCode.startsWith('ah_')) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
// Don't resolve if already resolved
|
|
150
|
+
if (resolvedCodes.has(authCode)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
export interface DeviceInfo {
|
|
2
2
|
userAgent: {
|
|
3
|
+
name: string;
|
|
3
4
|
browser: {
|
|
5
|
+
major: string;
|
|
4
6
|
name: string;
|
|
5
7
|
version: string;
|
|
8
|
+
type?: string;
|
|
6
9
|
};
|
|
7
10
|
os: {
|
|
8
11
|
name: string;
|
|
9
12
|
version: string;
|
|
10
13
|
};
|
|
11
14
|
device?: {
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
model?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
vendor?: string;
|
|
18
|
+
};
|
|
19
|
+
engine: {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
};
|
|
23
|
+
cpu: {
|
|
24
|
+
architecture: string;
|
|
14
25
|
};
|
|
15
26
|
};
|
|
16
27
|
screenResolution: {
|
|
@@ -18,13 +29,19 @@ export interface DeviceInfo {
|
|
|
18
29
|
height: number;
|
|
19
30
|
};
|
|
20
31
|
timeZone: string;
|
|
32
|
+
flags?: {
|
|
33
|
+
isBot: boolean;
|
|
34
|
+
isChromeFamily: boolean;
|
|
35
|
+
isStandalonePWA: boolean;
|
|
36
|
+
isAppleSilicon: boolean;
|
|
37
|
+
};
|
|
21
38
|
}
|
|
22
39
|
/**
|
|
23
40
|
* Get browser locale
|
|
24
41
|
*/
|
|
25
42
|
export declare function getBrowserLocale(): string;
|
|
26
43
|
/**
|
|
27
|
-
* Collect all device information
|
|
44
|
+
* Collect all device information using UAParser
|
|
28
45
|
*/
|
|
29
46
|
export declare function collectDeviceInfo(): DeviceInfo;
|
|
30
47
|
/**
|
|
@@ -1,91 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
function getBrowserInfo() {
|
|
5
|
-
const userAgent = navigator.userAgent;
|
|
6
|
-
// Chrome
|
|
7
|
-
if (userAgent.includes('Chrome')) {
|
|
8
|
-
const match = /Chrome\/(\d+)/.exec(userAgent);
|
|
9
|
-
return { name: 'Chrome', version: match ? match[1] : 'unknown' };
|
|
10
|
-
}
|
|
11
|
-
// Firefox
|
|
12
|
-
if (userAgent.includes('Firefox')) {
|
|
13
|
-
const match = /Firefox\/(\d+)/.exec(userAgent);
|
|
14
|
-
return { name: 'Firefox', version: match ? match[1] : 'unknown' };
|
|
15
|
-
}
|
|
16
|
-
// Safari
|
|
17
|
-
if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
|
|
18
|
-
const match = /Version\/(\d+)/.exec(userAgent);
|
|
19
|
-
return { name: 'Safari', version: match ? match[1] : 'unknown' };
|
|
20
|
-
}
|
|
21
|
-
// Edge
|
|
22
|
-
if (userAgent.includes('Edge')) {
|
|
23
|
-
const match = /Edge\/(\d+)/.exec(userAgent);
|
|
24
|
-
return { name: 'Edge', version: match ? match[1] : 'unknown' };
|
|
25
|
-
}
|
|
26
|
-
return { name: 'unknown', version: 'unknown' };
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Get basic OS information from user agent
|
|
30
|
-
*/
|
|
31
|
-
function getOSInfo() {
|
|
32
|
-
const userAgent = navigator.userAgent;
|
|
33
|
-
// Windows
|
|
34
|
-
if (userAgent.includes('Windows')) {
|
|
35
|
-
if (userAgent.includes('Windows NT 10.0'))
|
|
36
|
-
return { name: 'Windows', version: '10' };
|
|
37
|
-
if (userAgent.includes('Windows NT 6.3'))
|
|
38
|
-
return { name: 'Windows', version: '8.1' };
|
|
39
|
-
if (userAgent.includes('Windows NT 6.2'))
|
|
40
|
-
return { name: 'Windows', version: '8' };
|
|
41
|
-
if (userAgent.includes('Windows NT 6.1'))
|
|
42
|
-
return { name: 'Windows', version: '7' };
|
|
43
|
-
return { name: 'Windows', version: 'unknown' };
|
|
44
|
-
}
|
|
45
|
-
// macOS
|
|
46
|
-
if (userAgent.includes('Mac OS X')) {
|
|
47
|
-
const match = /Mac OS X (\d+[._]\d+)/.exec(userAgent);
|
|
48
|
-
return { name: 'macOS', version: match ? match[1].replace('_', '.') : 'unknown' };
|
|
49
|
-
}
|
|
50
|
-
// iOS
|
|
51
|
-
if (userAgent.includes('iPhone') || userAgent.includes('iPad')) {
|
|
52
|
-
const match = /OS (\d+[._]\d+)/.exec(userAgent);
|
|
53
|
-
return { name: 'iOS', version: match ? match[1].replace('_', '.') : 'unknown' };
|
|
54
|
-
}
|
|
55
|
-
// Android
|
|
56
|
-
if (userAgent.includes('Android')) {
|
|
57
|
-
const match = /Android (\d+[.\d]*)/.exec(userAgent);
|
|
58
|
-
return { name: 'Android', version: match ? match[1] : 'unknown' };
|
|
59
|
-
}
|
|
60
|
-
// Linux
|
|
61
|
-
if (userAgent.includes('Linux')) {
|
|
62
|
-
return { name: 'Linux', version: 'unknown' };
|
|
63
|
-
}
|
|
64
|
-
return { name: 'unknown', version: 'unknown' };
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Get device information
|
|
68
|
-
*/
|
|
69
|
-
function getDeviceInfo() {
|
|
70
|
-
const userAgent = navigator.userAgent;
|
|
71
|
-
// Mobile devices
|
|
72
|
-
if (userAgent.includes('iPhone')) {
|
|
73
|
-
return { type: 'mobile', model: 'iPhone' };
|
|
74
|
-
}
|
|
75
|
-
if (userAgent.includes('iPad')) {
|
|
76
|
-
return { type: 'tablet', model: 'iPad' };
|
|
77
|
-
}
|
|
78
|
-
if (userAgent.includes('Android')) {
|
|
79
|
-
if (userAgent.includes('Mobile')) {
|
|
80
|
-
return { type: 'mobile', model: 'Android' };
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
return { type: 'tablet', model: 'Android' };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Desktop (no specific device info)
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
1
|
+
import { UAParser } from '@ua-parser-js/pro-enterprise';
|
|
2
|
+
import { isBot, isChromeFamily, isStandalonePWA, isAppleSilicon, } from '@ua-parser-js/pro-enterprise/helpers';
|
|
89
3
|
/**
|
|
90
4
|
* Get screen resolution
|
|
91
5
|
*/
|
|
@@ -120,28 +34,82 @@ export function getBrowserLocale() {
|
|
|
120
34
|
}
|
|
121
35
|
}
|
|
122
36
|
/**
|
|
123
|
-
* Collect all device information
|
|
37
|
+
* Collect all device information using UAParser
|
|
124
38
|
*/
|
|
125
39
|
export function collectDeviceInfo() {
|
|
126
40
|
if (typeof window === 'undefined') {
|
|
127
41
|
// Server-side fallback
|
|
128
42
|
return {
|
|
129
43
|
userAgent: {
|
|
130
|
-
|
|
131
|
-
|
|
44
|
+
name: '',
|
|
45
|
+
browser: { major: '', name: '', version: '' },
|
|
46
|
+
os: { name: '', version: '' },
|
|
47
|
+
device: undefined,
|
|
48
|
+
engine: { name: '', version: '' },
|
|
49
|
+
cpu: { architecture: '' },
|
|
132
50
|
},
|
|
133
51
|
screenResolution: { width: 0, height: 0 },
|
|
134
52
|
timeZone: 'UTC',
|
|
53
|
+
flags: {
|
|
54
|
+
isBot: false,
|
|
55
|
+
isChromeFamily: false,
|
|
56
|
+
isStandalonePWA: false,
|
|
57
|
+
isAppleSilicon: false,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const parser = new UAParser();
|
|
62
|
+
const result = parser.getResult();
|
|
63
|
+
// Enhanced detection using UAParser official helpers
|
|
64
|
+
let flags;
|
|
65
|
+
try {
|
|
66
|
+
flags = {
|
|
67
|
+
isBot: isBot(result),
|
|
68
|
+
isChromeFamily: isChromeFamily(result),
|
|
69
|
+
isStandalonePWA: isStandalonePWA(),
|
|
70
|
+
isAppleSilicon: isAppleSilicon(result),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error('Failed to compute device flags:', error);
|
|
75
|
+
flags = {
|
|
76
|
+
isBot: false,
|
|
77
|
+
isChromeFamily: false,
|
|
78
|
+
isStandalonePWA: false,
|
|
79
|
+
isAppleSilicon: false,
|
|
135
80
|
};
|
|
136
81
|
}
|
|
137
82
|
return {
|
|
138
83
|
userAgent: {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
84
|
+
name: result.ua,
|
|
85
|
+
browser: {
|
|
86
|
+
major: result.browser.major || '',
|
|
87
|
+
name: result.browser.name || '',
|
|
88
|
+
version: result.browser.version || '',
|
|
89
|
+
type: result.browser.type,
|
|
90
|
+
},
|
|
91
|
+
os: {
|
|
92
|
+
name: result.os.name || '',
|
|
93
|
+
version: result.os.version || '',
|
|
94
|
+
},
|
|
95
|
+
device: result.device.model || result.device.type || result.device.vendor
|
|
96
|
+
? {
|
|
97
|
+
model: result.device.model,
|
|
98
|
+
type: result.device.type,
|
|
99
|
+
vendor: result.device.vendor,
|
|
100
|
+
}
|
|
101
|
+
: undefined,
|
|
102
|
+
engine: {
|
|
103
|
+
name: result.engine.name || '',
|
|
104
|
+
version: result.engine.version || '',
|
|
105
|
+
},
|
|
106
|
+
cpu: {
|
|
107
|
+
architecture: result.cpu.architecture || '',
|
|
108
|
+
},
|
|
142
109
|
},
|
|
143
110
|
screenResolution: getScreenResolution(),
|
|
144
111
|
timeZone: getTimeZone(),
|
|
112
|
+
flags,
|
|
145
113
|
};
|
|
146
114
|
}
|
|
147
115
|
/**
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* - token: Authentication token (URL > localStorage)
|
|
14
14
|
* - funnelSessionId: Active funnel session (URL > cookie)
|
|
15
15
|
*
|
|
16
|
+
* ⚠️ Note: authCode is NOT handled here - it has highest priority and is handled
|
|
17
|
+
* separately in client.ts before all other initialization logic.
|
|
18
|
+
*
|
|
16
19
|
* Usage examples:
|
|
17
20
|
* - Force production API: ?tagadaClientEnv=production
|
|
18
21
|
* - Force development API: ?tagadaClientEnv=development
|
|
@@ -20,6 +23,7 @@
|
|
|
20
23
|
* - Custom API URL: ?tagadaClientBaseUrl=https://tagada.loclx.io
|
|
21
24
|
* - Combined: ?tagadaClientEnv=local&tagadaClientBaseUrl=https://tagada.loclx.io
|
|
22
25
|
* - Hard reset + production: ?forceReset=true&tagadaClientEnv=production
|
|
26
|
+
* - Cross-domain auth: ?authCode=ah_... (automatically handled, highest priority)
|
|
23
27
|
*/
|
|
24
28
|
/**
|
|
25
29
|
* SDK Override Parameters - centralized across all SDK functions
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* - token: Authentication token (URL > localStorage)
|
|
14
14
|
* - funnelSessionId: Active funnel session (URL > cookie)
|
|
15
15
|
*
|
|
16
|
+
* ⚠️ Note: authCode is NOT handled here - it has highest priority and is handled
|
|
17
|
+
* separately in client.ts before all other initialization logic.
|
|
18
|
+
*
|
|
16
19
|
* Usage examples:
|
|
17
20
|
* - Force production API: ?tagadaClientEnv=production
|
|
18
21
|
* - Force development API: ?tagadaClientEnv=development
|
|
@@ -20,6 +23,7 @@
|
|
|
20
23
|
* - Custom API URL: ?tagadaClientBaseUrl=https://tagada.loclx.io
|
|
21
24
|
* - Combined: ?tagadaClientEnv=local&tagadaClientBaseUrl=https://tagada.loclx.io
|
|
22
25
|
* - Hard reset + production: ?forceReset=true&tagadaClientEnv=production
|
|
26
|
+
* - Cross-domain auth: ?authCode=ah_... (automatically handled, highest priority)
|
|
23
27
|
*/
|
|
24
28
|
import { clearClientToken, setClientToken, getClientToken } from './tokenStorage';
|
|
25
29
|
import { clearFunnelSessionCookie } from './sessionStorage';
|