@thead-vantage/react 2.16.0 → 2.18.0
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/package.json +1 -1
- package/src/components/AdBanner.tsx +20 -11
- package/src/lib/ads.ts +58 -44
- package/src/lib/thead-vantage-config.ts +3 -9
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@ export interface AdBannerProps {
|
|
|
12
12
|
userId?: string | null;
|
|
13
13
|
userSegment?: string | null;
|
|
14
14
|
className?: string;
|
|
15
|
+
clickMetadata?: Record<string, unknown>; // Optional metadata to send with click events
|
|
16
|
+
impressionMetadata?: Record<string, unknown>; // Optional metadata to send with impression events
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export function AdBanner({
|
|
@@ -22,6 +24,8 @@ export function AdBanner({
|
|
|
22
24
|
userId = null,
|
|
23
25
|
userSegment = null,
|
|
24
26
|
className = '',
|
|
27
|
+
clickMetadata,
|
|
28
|
+
impressionMetadata,
|
|
25
29
|
}: AdBannerProps) {
|
|
26
30
|
const [ad, setAd] = useState<Ad | null>(null);
|
|
27
31
|
const [loading, setLoading] = useState(true);
|
|
@@ -43,22 +47,27 @@ export function AdBanner({
|
|
|
43
47
|
userSegment,
|
|
44
48
|
});
|
|
45
49
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// Only log in development mode
|
|
51
|
+
if (process.env.NODE_ENV === 'development') {
|
|
52
|
+
console.log('[AdBanner] Processed response:', {
|
|
53
|
+
success: response.success,
|
|
54
|
+
hasAd: !!response.ad,
|
|
55
|
+
devMode: response.dev_mode,
|
|
56
|
+
message: response.message,
|
|
57
|
+
_dev_note: response._dev_note,
|
|
58
|
+
fullResponse: response,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
54
61
|
|
|
55
62
|
if (response.success && response.ad) {
|
|
56
|
-
|
|
63
|
+
if (process.env.NODE_ENV === 'development') {
|
|
64
|
+
console.log('[AdBanner] Setting ad:', response.ad);
|
|
65
|
+
}
|
|
57
66
|
setAd(response.ad);
|
|
58
67
|
setDevMode(response.dev_mode || false);
|
|
59
68
|
|
|
60
69
|
// Track impression (will be skipped in dev mode)
|
|
61
|
-
trackImpression(response.ad.id, apiKey, apiUrl);
|
|
70
|
+
trackImpression(response.ad.id, apiKey, apiUrl, impressionMetadata);
|
|
62
71
|
} else {
|
|
63
72
|
// Create a more detailed error message
|
|
64
73
|
const errorDetails = [];
|
|
@@ -106,7 +115,7 @@ export function AdBanner({
|
|
|
106
115
|
|
|
107
116
|
const handleClick = () => {
|
|
108
117
|
if (ad) {
|
|
109
|
-
trackClick(ad.id, apiKey, apiUrl);
|
|
118
|
+
trackClick(ad.id, apiKey, apiUrl, clickMetadata);
|
|
110
119
|
// The link will handle navigation
|
|
111
120
|
}
|
|
112
121
|
};
|
package/src/lib/ads.ts
CHANGED
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
* Automatically handles development mode to prevent tracking
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { getApiBaseUrl, shouldUseDevFlags } from './thead-vantage-config';
|
|
6
|
+
import { getApiBaseUrl, shouldUseDevFlags, isDevelopmentMode } from './thead-vantage-config';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Helper function to log only in development mode
|
|
10
|
+
* Errors and warnings are always logged
|
|
11
|
+
*/
|
|
12
|
+
function devLog(...args: unknown[]): void {
|
|
13
|
+
if (process.env.NODE_ENV === 'development' || isDevelopmentMode()) {
|
|
14
|
+
console.log(...args);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
7
17
|
|
|
8
18
|
export interface AdData {
|
|
9
19
|
id: string;
|
|
@@ -147,7 +157,7 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
|
|
|
147
157
|
|
|
148
158
|
// Log the request (without exposing the API key)
|
|
149
159
|
const logUrl = url.replace(new RegExp(`api_key=${params.apiKey}`, 'g'), 'api_key=***');
|
|
150
|
-
|
|
160
|
+
devLog('[AdBanner] Fetching ad from:', logUrl);
|
|
151
161
|
|
|
152
162
|
// Make direct request to thead-vantage.com
|
|
153
163
|
// Try to make a "simple request" that doesn't trigger preflight
|
|
@@ -205,8 +215,8 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
|
|
|
205
215
|
const data: AdsResponse = await response.json();
|
|
206
216
|
|
|
207
217
|
// Debug logging - log the FULL response to see exactly what we're getting
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
devLog('[AdBanner] Full API Response:', JSON.stringify(data, null, 2));
|
|
219
|
+
devLog('[AdBanner] API Response Summary:', {
|
|
210
220
|
success: data.success,
|
|
211
221
|
hasAd: !!data.ad,
|
|
212
222
|
hasAds: !!(data.ads && Array.isArray(data.ads)),
|
|
@@ -246,19 +256,19 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
|
|
|
246
256
|
|
|
247
257
|
// Handle both single ad and array of ads
|
|
248
258
|
if (data.ads && Array.isArray(data.ads) && data.ads.length > 0) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
// If we get an array, use the first ad and ensure it's Ad type
|
|
260
|
+
const firstAd = data.ads[0];
|
|
261
|
+
devLog('[AdBanner] Processing ads array, first ad:', JSON.stringify(firstAd, null, 2));
|
|
262
|
+
|
|
263
|
+
// Check if it's already an Ad, otherwise convert from AdData
|
|
264
|
+
let ad: Ad = isAd(firstAd)
|
|
265
|
+
? firstAd
|
|
266
|
+
: convertToAd(firstAd as AdData);
|
|
267
|
+
|
|
268
|
+
// Normalize the type (standard -> image)
|
|
269
|
+
ad = normalizeAdType(ad);
|
|
270
|
+
|
|
271
|
+
devLog('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
|
|
262
272
|
|
|
263
273
|
return {
|
|
264
274
|
success: data.success,
|
|
@@ -271,19 +281,19 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
|
|
|
271
281
|
|
|
272
282
|
// Handle single ad response - convert AdData to Ad
|
|
273
283
|
if (data.ad) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
devLog('[AdBanner] Processing single ad:', JSON.stringify(data.ad, null, 2));
|
|
285
|
+
|
|
286
|
+
// Check if it's already in Ad format or needs conversion
|
|
287
|
+
let ad: Ad;
|
|
288
|
+
if (isAd(data.ad)) {
|
|
289
|
+
// Already in Ad format, normalize the type
|
|
290
|
+
ad = normalizeAdType(data.ad);
|
|
291
|
+
} else {
|
|
292
|
+
// Convert from AdData format
|
|
293
|
+
ad = convertToAd(data.ad);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
devLog('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
|
|
287
297
|
|
|
288
298
|
return {
|
|
289
299
|
success: data.success,
|
|
@@ -420,8 +430,9 @@ export function collectFingerprint(): Fingerprint {
|
|
|
420
430
|
* @param adId - The ID of the ad being tracked
|
|
421
431
|
* @param apiKey - The API key for the platform (required for CORS validation and platform identification)
|
|
422
432
|
* @param apiUrl - Optional API base URL override
|
|
433
|
+
* @param metadata - Optional metadata object to send with the impression event
|
|
423
434
|
*/
|
|
424
|
-
export async function trackImpression(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
|
|
435
|
+
export async function trackImpression(adId: string, apiKey: string, apiUrl?: string, metadata?: Record<string, unknown>): Promise<void> {
|
|
425
436
|
try {
|
|
426
437
|
if (!apiKey) {
|
|
427
438
|
console.warn('[AdBanner] Cannot track impression: API key is required');
|
|
@@ -456,7 +467,7 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
456
467
|
// Check if we should skip tracking (dev mode)
|
|
457
468
|
const useDevFlags = shouldUseDevFlags();
|
|
458
469
|
if (useDevFlags) {
|
|
459
|
-
|
|
470
|
+
devLog(`[DEV] Impression tracking skipped for ad: ${adId}`);
|
|
460
471
|
return;
|
|
461
472
|
}
|
|
462
473
|
|
|
@@ -472,6 +483,7 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
472
483
|
action: 'impression',
|
|
473
484
|
adId,
|
|
474
485
|
fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
|
|
486
|
+
...(metadata && { metadata }), // Include metadata if provided
|
|
475
487
|
}),
|
|
476
488
|
});
|
|
477
489
|
|
|
@@ -485,16 +497,16 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
485
497
|
|
|
486
498
|
const data = await response.json();
|
|
487
499
|
if (data.dev_mode) {
|
|
488
|
-
|
|
500
|
+
devLog(`[DEV] Impression tracking skipped for ad: ${adId}`);
|
|
489
501
|
} else {
|
|
490
|
-
|
|
502
|
+
devLog(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
|
|
491
503
|
}
|
|
492
504
|
} catch (error) {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
505
|
+
// Handle CORS errors gracefully (tracking failures shouldn't break the app)
|
|
506
|
+
if (error instanceof TypeError && (error.message.includes('CORS') || error.message.includes('Failed to fetch'))) {
|
|
507
|
+
// This is expected until the server configures CORS for /api/ads/track endpoint
|
|
508
|
+
// The ad still displays correctly, tracking just won't work until server-side CORS is fixed
|
|
509
|
+
devLog(`[AdBanner] Tracking impression failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still displayed successfully.`);
|
|
498
510
|
} else {
|
|
499
511
|
console.error('Error tracking impression:', error);
|
|
500
512
|
}
|
|
@@ -509,8 +521,9 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
509
521
|
* @param adId - The ID of the ad being tracked
|
|
510
522
|
* @param apiKey - The API key for the platform (required for CORS validation and platform identification)
|
|
511
523
|
* @param apiUrl - Optional API base URL override
|
|
524
|
+
* @param metadata - Optional metadata object to send with the click event
|
|
512
525
|
*/
|
|
513
|
-
export async function trackClick(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
|
|
526
|
+
export async function trackClick(adId: string, apiKey: string, apiUrl?: string, metadata?: Record<string, unknown>): Promise<void> {
|
|
514
527
|
try {
|
|
515
528
|
if (!apiKey) {
|
|
516
529
|
console.warn('[AdBanner] Cannot track click: API key is required');
|
|
@@ -545,7 +558,7 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
545
558
|
// Check if we should skip tracking (dev mode)
|
|
546
559
|
const useDevFlags = shouldUseDevFlags();
|
|
547
560
|
if (useDevFlags) {
|
|
548
|
-
|
|
561
|
+
devLog(`[DEV] Click tracking skipped for ad: ${adId}`);
|
|
549
562
|
return;
|
|
550
563
|
}
|
|
551
564
|
|
|
@@ -561,6 +574,7 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
561
574
|
action: 'click',
|
|
562
575
|
adId,
|
|
563
576
|
fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
|
|
577
|
+
...(metadata && { metadata }), // Include metadata if provided
|
|
564
578
|
}),
|
|
565
579
|
});
|
|
566
580
|
|
|
@@ -574,16 +588,16 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
574
588
|
|
|
575
589
|
const data = await response.json();
|
|
576
590
|
if (data.dev_mode) {
|
|
577
|
-
|
|
591
|
+
devLog(`[DEV] Click tracking skipped for ad: ${adId}`);
|
|
578
592
|
} else {
|
|
579
|
-
|
|
593
|
+
devLog(`[AdBanner] Click tracked successfully for ad: ${adId}`);
|
|
580
594
|
}
|
|
581
595
|
} catch (error) {
|
|
582
596
|
// Handle CORS errors gracefully (tracking failures shouldn't break the app)
|
|
583
597
|
if (error instanceof TypeError && (error.message.includes('CORS') || error.message.includes('Failed to fetch'))) {
|
|
584
598
|
// This is expected until the server configures CORS for /api/ads/track endpoint
|
|
585
599
|
// The ad still works correctly, click tracking just won't work until server-side CORS is fixed
|
|
586
|
-
|
|
600
|
+
devLog(`[AdBanner] Tracking click failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still works correctly.`);
|
|
587
601
|
} else {
|
|
588
602
|
console.error('Error tracking click:', error);
|
|
589
603
|
}
|
|
@@ -116,15 +116,9 @@ export function getApiBaseUrl(explicitApiUrl?: string): string {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Log which URL was selected (helpful for debugging)
|
|
119
|
-
//
|
|
120
|
-
if (typeof window !== 'undefined') {
|
|
121
|
-
|
|
122
|
-
console.log(`[TheAd Vantage] API Base URL selected: ${selectedUrl} (reason: ${reason})`);
|
|
123
|
-
} else {
|
|
124
|
-
// In production, log to console but only if there's an issue (helps with debugging)
|
|
125
|
-
// We'll log errors separately, but this helps identify configuration issues
|
|
126
|
-
console.debug(`[TheAd Vantage] API Base URL: ${selectedUrl}`);
|
|
127
|
-
}
|
|
119
|
+
// Only log in development mode
|
|
120
|
+
if (typeof window !== 'undefined' && (process.env.NODE_ENV === 'development' || isDevelopmentMode())) {
|
|
121
|
+
console.log(`[TheAd Vantage] API Base URL selected: ${selectedUrl} (reason: ${reason})`);
|
|
128
122
|
}
|
|
129
123
|
|
|
130
124
|
return selectedUrl;
|