@thead-vantage/react 2.15.0 → 2.17.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/README.md +12 -5
- package/package.json +1 -1
- package/src/components/AdBanner.tsx +14 -9
- package/src/index.ts +2 -0
- package/src/lib/ads.ts +107 -42
- package/src/lib/thead-vantage-config.ts +3 -9
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ export default function ArticlePage() {
|
|
|
184
184
|
You can also use the utility functions directly:
|
|
185
185
|
|
|
186
186
|
```tsx
|
|
187
|
-
import { fetchAdBanner, trackImpression, trackClick } from '@/lib/ads';
|
|
187
|
+
import { fetchAdBanner, trackImpression, trackClick, collectFingerprint } from '@/lib/ads';
|
|
188
188
|
|
|
189
189
|
// Fetch an ad
|
|
190
190
|
const response = await fetchAdBanner({
|
|
@@ -196,8 +196,14 @@ const response = await fetchAdBanner({
|
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
// Track events (automatically skipped in TheAd Vantage dev mode)
|
|
199
|
-
trackImpression
|
|
200
|
-
|
|
199
|
+
// Note: trackImpression and trackClick now require apiKey parameter
|
|
200
|
+
// They automatically include client fingerprinting data for fraud detection
|
|
201
|
+
trackImpression(adId, apiKey);
|
|
202
|
+
trackClick(adId, apiKey);
|
|
203
|
+
|
|
204
|
+
// You can also collect fingerprint data manually if needed
|
|
205
|
+
const fingerprint = collectFingerprint();
|
|
206
|
+
console.log('Client fingerprint:', fingerprint);
|
|
201
207
|
```
|
|
202
208
|
|
|
203
209
|
---
|
|
@@ -335,8 +341,9 @@ The TheAd Vantage integration uses a smart mode detection system to support diff
|
|
|
335
341
|
### Utilities
|
|
336
342
|
- **`src/lib/ads.ts`**: Utility functions for fetching and tracking ads
|
|
337
343
|
- `fetchAdBanner(params)`: Fetches ads with full parameter support
|
|
338
|
-
- `trackImpression(adId)`: Tracks ad impressions (skipped in dev mode)
|
|
339
|
-
- `trackClick(adId)`: Tracks ad clicks (skipped in dev mode)
|
|
344
|
+
- `trackImpression(adId, apiKey, apiUrl?)`: Tracks ad impressions with fingerprinting (skipped in dev mode)
|
|
345
|
+
- `trackClick(adId, apiKey, apiUrl?)`: Tracks ad clicks with fingerprinting (skipped in dev mode)
|
|
346
|
+
- `collectFingerprint()`: Collects client fingerprinting data for fraud detection
|
|
340
347
|
|
|
341
348
|
## Implementation Instructions for AI Agents
|
|
342
349
|
|
package/package.json
CHANGED
|
@@ -43,17 +43,22 @@ export function AdBanner({
|
|
|
43
43
|
userSegment,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
// Only log in development mode
|
|
47
|
+
if (process.env.NODE_ENV === 'development') {
|
|
48
|
+
console.log('[AdBanner] Processed response:', {
|
|
49
|
+
success: response.success,
|
|
50
|
+
hasAd: !!response.ad,
|
|
51
|
+
devMode: response.dev_mode,
|
|
52
|
+
message: response.message,
|
|
53
|
+
_dev_note: response._dev_note,
|
|
54
|
+
fullResponse: response,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
if (response.success && response.ad) {
|
|
56
|
-
|
|
59
|
+
if (process.env.NODE_ENV === 'development') {
|
|
60
|
+
console.log('[AdBanner] Setting ad:', response.ad);
|
|
61
|
+
}
|
|
57
62
|
setAd(response.ad);
|
|
58
63
|
setDevMode(response.dev_mode || false);
|
|
59
64
|
|
package/src/index.ts
CHANGED
|
@@ -14,11 +14,13 @@ export {
|
|
|
14
14
|
fetchAdBanner,
|
|
15
15
|
trackImpression,
|
|
16
16
|
trackClick,
|
|
17
|
+
collectFingerprint,
|
|
17
18
|
type Ad,
|
|
18
19
|
type AdData,
|
|
19
20
|
type AdsResponse,
|
|
20
21
|
type AdBannerResponse,
|
|
21
22
|
type FetchAdBannerParams,
|
|
23
|
+
type Fingerprint,
|
|
22
24
|
} from './lib/ads';
|
|
23
25
|
|
|
24
26
|
// Export configuration utilities
|
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,
|
|
@@ -360,9 +370,62 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
|
|
|
360
370
|
}
|
|
361
371
|
}
|
|
362
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Collect client fingerprinting data for fraud detection
|
|
375
|
+
* This data is used by the server to detect duplicate impressions, bot traffic, and fraudulent clicks
|
|
376
|
+
*/
|
|
377
|
+
export interface Fingerprint {
|
|
378
|
+
screenWidth: number | null;
|
|
379
|
+
screenHeight: number | null;
|
|
380
|
+
timezone: string | null;
|
|
381
|
+
language: string | null;
|
|
382
|
+
colorDepth: number | null;
|
|
383
|
+
platform: string | null;
|
|
384
|
+
cookiesEnabled: boolean | null;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function collectFingerprint(): Fingerprint {
|
|
388
|
+
// Only collect fingerprint in browser context
|
|
389
|
+
if (typeof window === 'undefined') {
|
|
390
|
+
return {
|
|
391
|
+
screenWidth: null,
|
|
392
|
+
screenHeight: null,
|
|
393
|
+
timezone: null,
|
|
394
|
+
language: null,
|
|
395
|
+
colorDepth: null,
|
|
396
|
+
platform: null,
|
|
397
|
+
cookiesEnabled: null,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
return {
|
|
403
|
+
screenWidth: window.screen?.width ?? null,
|
|
404
|
+
screenHeight: window.screen?.height ?? null,
|
|
405
|
+
timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone ?? null,
|
|
406
|
+
language: navigator?.language ?? null,
|
|
407
|
+
colorDepth: window.screen?.colorDepth ?? null,
|
|
408
|
+
platform: navigator?.platform ?? null,
|
|
409
|
+
cookiesEnabled: navigator?.cookieEnabled ?? null,
|
|
410
|
+
};
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.warn('[AdBanner] Error collecting fingerprint:', error);
|
|
413
|
+
return {
|
|
414
|
+
screenWidth: null,
|
|
415
|
+
screenHeight: null,
|
|
416
|
+
timezone: null,
|
|
417
|
+
language: null,
|
|
418
|
+
colorDepth: null,
|
|
419
|
+
platform: null,
|
|
420
|
+
cookiesEnabled: null,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
363
425
|
/**
|
|
364
426
|
* Track an ad impression (when ad is viewed)
|
|
365
427
|
* Sends tracking directly to TheAd Vantage API (or skips in dev mode)
|
|
428
|
+
* Includes client fingerprinting data for fraud detection
|
|
366
429
|
*
|
|
367
430
|
* @param adId - The ID of the ad being tracked
|
|
368
431
|
* @param apiKey - The API key for the platform (required for CORS validation and platform identification)
|
|
@@ -403,7 +466,7 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
403
466
|
// Check if we should skip tracking (dev mode)
|
|
404
467
|
const useDevFlags = shouldUseDevFlags();
|
|
405
468
|
if (useDevFlags) {
|
|
406
|
-
|
|
469
|
+
devLog(`[DEV] Impression tracking skipped for ad: ${adId}`);
|
|
407
470
|
return;
|
|
408
471
|
}
|
|
409
472
|
|
|
@@ -418,6 +481,7 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
418
481
|
body: JSON.stringify({
|
|
419
482
|
action: 'impression',
|
|
420
483
|
adId,
|
|
484
|
+
fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
|
|
421
485
|
}),
|
|
422
486
|
});
|
|
423
487
|
|
|
@@ -431,16 +495,16 @@ export async function trackImpression(adId: string, apiKey: string, apiUrl?: str
|
|
|
431
495
|
|
|
432
496
|
const data = await response.json();
|
|
433
497
|
if (data.dev_mode) {
|
|
434
|
-
|
|
498
|
+
devLog(`[DEV] Impression tracking skipped for ad: ${adId}`);
|
|
435
499
|
} else {
|
|
436
|
-
|
|
500
|
+
devLog(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
|
|
437
501
|
}
|
|
438
502
|
} catch (error) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
503
|
+
// Handle CORS errors gracefully (tracking failures shouldn't break the app)
|
|
504
|
+
if (error instanceof TypeError && (error.message.includes('CORS') || error.message.includes('Failed to fetch'))) {
|
|
505
|
+
// This is expected until the server configures CORS for /api/ads/track endpoint
|
|
506
|
+
// The ad still displays correctly, tracking just won't work until server-side CORS is fixed
|
|
507
|
+
devLog(`[AdBanner] Tracking impression failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still displayed successfully.`);
|
|
444
508
|
} else {
|
|
445
509
|
console.error('Error tracking impression:', error);
|
|
446
510
|
}
|
|
@@ -491,7 +555,7 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
491
555
|
// Check if we should skip tracking (dev mode)
|
|
492
556
|
const useDevFlags = shouldUseDevFlags();
|
|
493
557
|
if (useDevFlags) {
|
|
494
|
-
|
|
558
|
+
devLog(`[DEV] Click tracking skipped for ad: ${adId}`);
|
|
495
559
|
return;
|
|
496
560
|
}
|
|
497
561
|
|
|
@@ -506,6 +570,7 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
506
570
|
body: JSON.stringify({
|
|
507
571
|
action: 'click',
|
|
508
572
|
adId,
|
|
573
|
+
fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
|
|
509
574
|
}),
|
|
510
575
|
});
|
|
511
576
|
|
|
@@ -519,16 +584,16 @@ export async function trackClick(adId: string, apiKey: string, apiUrl?: string):
|
|
|
519
584
|
|
|
520
585
|
const data = await response.json();
|
|
521
586
|
if (data.dev_mode) {
|
|
522
|
-
|
|
587
|
+
devLog(`[DEV] Click tracking skipped for ad: ${adId}`);
|
|
523
588
|
} else {
|
|
524
|
-
|
|
589
|
+
devLog(`[AdBanner] Click tracked successfully for ad: ${adId}`);
|
|
525
590
|
}
|
|
526
591
|
} catch (error) {
|
|
527
592
|
// Handle CORS errors gracefully (tracking failures shouldn't break the app)
|
|
528
593
|
if (error instanceof TypeError && (error.message.includes('CORS') || error.message.includes('Failed to fetch'))) {
|
|
529
594
|
// This is expected until the server configures CORS for /api/ads/track endpoint
|
|
530
595
|
// The ad still works correctly, click tracking just won't work until server-side CORS is fixed
|
|
531
|
-
|
|
596
|
+
devLog(`[AdBanner] Tracking click failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still works correctly.`);
|
|
532
597
|
} else {
|
|
533
598
|
console.error('Error tracking click:', error);
|
|
534
599
|
}
|
|
@@ -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;
|