@thead-vantage/react 2.14.0 → 2.16.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 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(adId);
200
- trackClick(adId);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thead-vantage/react",
3
- "version": "2.14.0",
3
+ "version": "2.16.0",
4
4
  "description": "React components and utilities for TheAd Vantage ad platform integration",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -58,7 +58,7 @@ export function AdBanner({
58
58
  setDevMode(response.dev_mode || false);
59
59
 
60
60
  // Track impression (will be skipped in dev mode)
61
- trackImpression(response.ad.id);
61
+ trackImpression(response.ad.id, apiKey, apiUrl);
62
62
  } else {
63
63
  // Create a more detailed error message
64
64
  const errorDetails = [];
@@ -106,7 +106,7 @@ export function AdBanner({
106
106
 
107
107
  const handleClick = () => {
108
108
  if (ad) {
109
- trackClick(ad.id);
109
+ trackClick(ad.id, apiKey, apiUrl);
110
110
  // The link will handle navigation
111
111
  }
112
112
  };
@@ -42,7 +42,14 @@ export default function AdDisplay({ position, className = '' }: AdDisplayProps)
42
42
  setDevMode(response.dev_mode || false);
43
43
 
44
44
  // Track impression (will be skipped in dev mode)
45
- trackImpression(response.ad.id);
45
+ // Note: AdDisplay doesn't have apiKey, so tracking will fail gracefully
46
+ // This component may need to be updated to accept apiKey prop if tracking is needed
47
+ if (response.ad.id) {
48
+ // Silently fail if no API key available
49
+ trackImpression(response.ad.id, '').catch(() => {
50
+ // Tracking failed - this is expected without API key
51
+ });
52
+ }
46
53
  } else {
47
54
  setError('No ad available');
48
55
  }
@@ -59,7 +66,11 @@ export default function AdDisplay({ position, className = '' }: AdDisplayProps)
59
66
 
60
67
  const handleClick = () => {
61
68
  if (ad) {
62
- trackClick(ad.id);
69
+ // Note: AdDisplay doesn't have apiKey, so tracking will fail gracefully
70
+ // This component may need to be updated to accept apiKey prop if tracking is needed
71
+ trackClick(ad.id, '').catch(() => {
72
+ // Tracking failed - this is expected without API key
73
+ });
63
74
  // The link will handle navigation
64
75
  }
65
76
  };
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
@@ -360,14 +360,76 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
360
360
  }
361
361
  }
362
362
 
363
+ /**
364
+ * Collect client fingerprinting data for fraud detection
365
+ * This data is used by the server to detect duplicate impressions, bot traffic, and fraudulent clicks
366
+ */
367
+ export interface Fingerprint {
368
+ screenWidth: number | null;
369
+ screenHeight: number | null;
370
+ timezone: string | null;
371
+ language: string | null;
372
+ colorDepth: number | null;
373
+ platform: string | null;
374
+ cookiesEnabled: boolean | null;
375
+ }
376
+
377
+ export function collectFingerprint(): Fingerprint {
378
+ // Only collect fingerprint in browser context
379
+ if (typeof window === 'undefined') {
380
+ return {
381
+ screenWidth: null,
382
+ screenHeight: null,
383
+ timezone: null,
384
+ language: null,
385
+ colorDepth: null,
386
+ platform: null,
387
+ cookiesEnabled: null,
388
+ };
389
+ }
390
+
391
+ try {
392
+ return {
393
+ screenWidth: window.screen?.width ?? null,
394
+ screenHeight: window.screen?.height ?? null,
395
+ timezone: Intl?.DateTimeFormat?.()?.resolvedOptions?.()?.timeZone ?? null,
396
+ language: navigator?.language ?? null,
397
+ colorDepth: window.screen?.colorDepth ?? null,
398
+ platform: navigator?.platform ?? null,
399
+ cookiesEnabled: navigator?.cookieEnabled ?? null,
400
+ };
401
+ } catch (error) {
402
+ console.warn('[AdBanner] Error collecting fingerprint:', error);
403
+ return {
404
+ screenWidth: null,
405
+ screenHeight: null,
406
+ timezone: null,
407
+ language: null,
408
+ colorDepth: null,
409
+ platform: null,
410
+ cookiesEnabled: null,
411
+ };
412
+ }
413
+ }
414
+
363
415
  /**
364
416
  * Track an ad impression (when ad is viewed)
365
417
  * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
418
+ * Includes client fingerprinting data for fraud detection
419
+ *
420
+ * @param adId - The ID of the ad being tracked
421
+ * @param apiKey - The API key for the platform (required for CORS validation and platform identification)
422
+ * @param apiUrl - Optional API base URL override
366
423
  */
367
- export async function trackImpression(adId: string): Promise<void> {
424
+ export async function trackImpression(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
368
425
  try {
426
+ if (!apiKey) {
427
+ console.warn('[AdBanner] Cannot track impression: API key is required');
428
+ return;
429
+ }
430
+
369
431
  // Get the API base URL to determine where to send tracking
370
- const apiBaseUrl = getApiBaseUrl();
432
+ const apiBaseUrl = getApiBaseUrl(apiUrl);
371
433
 
372
434
  // Build tracking URL - remove /api/ads if present, then append /api/ads/track
373
435
  let trackingUrl = apiBaseUrl.trim();
@@ -387,6 +449,10 @@ export async function trackImpression(adId: string): Promise<void> {
387
449
  trackingUrl = trackingUrl.replace('thead-vantage.com', 'www.thead-vantage.com');
388
450
  }
389
451
 
452
+ // Add API key to query string (recommended for CORS preflight validation)
453
+ const url = new URL(trackingUrl);
454
+ url.searchParams.set('api_key', apiKey);
455
+
390
456
  // Check if we should skip tracking (dev mode)
391
457
  const useDevFlags = shouldUseDevFlags();
392
458
  if (useDevFlags) {
@@ -394,7 +460,7 @@ export async function trackImpression(adId: string): Promise<void> {
394
460
  return;
395
461
  }
396
462
 
397
- const response = await fetch(trackingUrl, {
463
+ const response = await fetch(url.toString(), {
398
464
  method: 'POST',
399
465
  mode: 'cors', // Explicitly enable CORS
400
466
  credentials: 'omit', // Don't send cookies
@@ -405,17 +471,23 @@ export async function trackImpression(adId: string): Promise<void> {
405
471
  body: JSON.stringify({
406
472
  action: 'impression',
407
473
  adId,
474
+ fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
408
475
  }),
409
476
  });
410
477
 
411
478
  if (!response.ok) {
412
- console.warn(`[AdBanner] Tracking impression failed: ${response.status} ${response.statusText}`);
479
+ const errorText = await response.text().catch(() => 'Unknown error');
480
+ console.warn(`[AdBanner] Tracking impression failed: ${response.status} ${response.statusText}`, {
481
+ errorText: errorText.substring(0, 200),
482
+ });
413
483
  return;
414
484
  }
415
485
 
416
486
  const data = await response.json();
417
487
  if (data.dev_mode) {
418
488
  console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
489
+ } else {
490
+ console.debug(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
419
491
  }
420
492
  } catch (error) {
421
493
  // Handle CORS errors gracefully (tracking failures shouldn't break the app)
@@ -433,11 +505,20 @@ export async function trackImpression(adId: string): Promise<void> {
433
505
  /**
434
506
  * Track an ad click
435
507
  * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
508
+ *
509
+ * @param adId - The ID of the ad being tracked
510
+ * @param apiKey - The API key for the platform (required for CORS validation and platform identification)
511
+ * @param apiUrl - Optional API base URL override
436
512
  */
437
- export async function trackClick(adId: string): Promise<void> {
513
+ export async function trackClick(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
438
514
  try {
515
+ if (!apiKey) {
516
+ console.warn('[AdBanner] Cannot track click: API key is required');
517
+ return;
518
+ }
519
+
439
520
  // Get the API base URL to determine where to send tracking
440
- const apiBaseUrl = getApiBaseUrl();
521
+ const apiBaseUrl = getApiBaseUrl(apiUrl);
441
522
 
442
523
  // Build tracking URL - remove /api/ads if present, then append /api/ads/track
443
524
  let trackingUrl = apiBaseUrl.trim();
@@ -457,6 +538,10 @@ export async function trackClick(adId: string): Promise<void> {
457
538
  trackingUrl = trackingUrl.replace('thead-vantage.com', 'www.thead-vantage.com');
458
539
  }
459
540
 
541
+ // Add API key to query string (recommended for CORS preflight validation)
542
+ const url = new URL(trackingUrl);
543
+ url.searchParams.set('api_key', apiKey);
544
+
460
545
  // Check if we should skip tracking (dev mode)
461
546
  const useDevFlags = shouldUseDevFlags();
462
547
  if (useDevFlags) {
@@ -464,7 +549,7 @@ export async function trackClick(adId: string): Promise<void> {
464
549
  return;
465
550
  }
466
551
 
467
- const response = await fetch(trackingUrl, {
552
+ const response = await fetch(url.toString(), {
468
553
  method: 'POST',
469
554
  mode: 'cors', // Explicitly enable CORS
470
555
  credentials: 'omit', // Don't send cookies
@@ -475,17 +560,23 @@ export async function trackClick(adId: string): Promise<void> {
475
560
  body: JSON.stringify({
476
561
  action: 'click',
477
562
  adId,
563
+ fingerprint: collectFingerprint(), // Include fingerprint data for fraud detection
478
564
  }),
479
565
  });
480
566
 
481
567
  if (!response.ok) {
482
- console.warn(`[AdBanner] Tracking click failed: ${response.status} ${response.statusText}`);
568
+ const errorText = await response.text().catch(() => 'Unknown error');
569
+ console.warn(`[AdBanner] Tracking click failed: ${response.status} ${response.statusText}`, {
570
+ errorText: errorText.substring(0, 200),
571
+ });
483
572
  return;
484
573
  }
485
574
 
486
575
  const data = await response.json();
487
576
  if (data.dev_mode) {
488
577
  console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
578
+ } else {
579
+ console.debug(`[AdBanner] Click tracked successfully for ad: ${adId}`);
489
580
  }
490
581
  } catch (error) {
491
582
  // Handle CORS errors gracefully (tracking failures shouldn't break the app)