@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 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.15.0",
3
+ "version": "2.17.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",
@@ -43,17 +43,22 @@ export function AdBanner({
43
43
  userSegment,
44
44
  });
45
45
 
46
- console.log('[AdBanner] Processed response:', {
47
- success: response.success,
48
- hasAd: !!response.ad,
49
- devMode: response.dev_mode,
50
- message: response.message,
51
- _dev_note: response._dev_note,
52
- fullResponse: response,
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
- console.log('[AdBanner] Setting ad:', response.ad);
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
- console.log('[AdBanner] Fetching ad from:', logUrl);
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
- console.log('[AdBanner] Full API Response:', JSON.stringify(data, null, 2));
209
- console.log('[AdBanner] API Response Summary:', {
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
- // If we get an array, use the first ad and ensure it's Ad type
250
- const firstAd = data.ads[0];
251
- console.log('[AdBanner] Processing ads array, first ad:', JSON.stringify(firstAd, null, 2));
252
-
253
- // Check if it's already an Ad, otherwise convert from AdData
254
- let ad: Ad = isAd(firstAd)
255
- ? firstAd
256
- : convertToAd(firstAd as AdData);
257
-
258
- // Normalize the type (standard -> image)
259
- ad = normalizeAdType(ad);
260
-
261
- console.log('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
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
- console.log('[AdBanner] Processing single ad:', JSON.stringify(data.ad, null, 2));
275
-
276
- // Check if it's already in Ad format or needs conversion
277
- let ad: Ad;
278
- if (isAd(data.ad)) {
279
- // Already in Ad format, normalize the type
280
- ad = normalizeAdType(data.ad);
281
- } else {
282
- // Convert from AdData format
283
- ad = convertToAd(data.ad);
284
- }
285
-
286
- console.log('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
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
- console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
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
- console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
498
+ devLog(`[DEV] Impression tracking skipped for ad: ${adId}`);
435
499
  } else {
436
- console.debug(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
500
+ devLog(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
437
501
  }
438
502
  } catch (error) {
439
- // Handle CORS errors gracefully (tracking failures shouldn't break the app)
440
- if (error instanceof TypeError && (error.message.includes('CORS') || error.message.includes('Failed to fetch'))) {
441
- // This is expected until the server configures CORS for /api/ads/track endpoint
442
- // The ad still displays correctly, tracking just won't work until server-side CORS is fixed
443
- console.debug(`[AdBanner] Tracking impression failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still displayed successfully.`);
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
- console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
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
- console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
587
+ devLog(`[DEV] Click tracking skipped for ad: ${adId}`);
523
588
  } else {
524
- console.debug(`[AdBanner] Click tracked successfully for ad: ${adId}`);
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
- console.debug(`[AdBanner] Tracking click failed (CORS): The /api/ads/track endpoint needs CORS headers configured on the server. Ad still works correctly.`);
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
- // Always log in browser context, but only in development for server-side
120
- if (typeof window !== 'undefined') {
121
- if (process.env.NODE_ENV === 'development') {
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;