@thead-vantage/react 2.14.0 → 2.15.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thead-vantage/react",
3
- "version": "2.14.0",
3
+ "version": "2.15.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/lib/ads.ts CHANGED
@@ -363,11 +363,20 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
363
363
  /**
364
364
  * Track an ad impression (when ad is viewed)
365
365
  * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
366
+ *
367
+ * @param adId - The ID of the ad being tracked
368
+ * @param apiKey - The API key for the platform (required for CORS validation and platform identification)
369
+ * @param apiUrl - Optional API base URL override
366
370
  */
367
- export async function trackImpression(adId: string): Promise<void> {
371
+ export async function trackImpression(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
368
372
  try {
373
+ if (!apiKey) {
374
+ console.warn('[AdBanner] Cannot track impression: API key is required');
375
+ return;
376
+ }
377
+
369
378
  // Get the API base URL to determine where to send tracking
370
- const apiBaseUrl = getApiBaseUrl();
379
+ const apiBaseUrl = getApiBaseUrl(apiUrl);
371
380
 
372
381
  // Build tracking URL - remove /api/ads if present, then append /api/ads/track
373
382
  let trackingUrl = apiBaseUrl.trim();
@@ -387,6 +396,10 @@ export async function trackImpression(adId: string): Promise<void> {
387
396
  trackingUrl = trackingUrl.replace('thead-vantage.com', 'www.thead-vantage.com');
388
397
  }
389
398
 
399
+ // Add API key to query string (recommended for CORS preflight validation)
400
+ const url = new URL(trackingUrl);
401
+ url.searchParams.set('api_key', apiKey);
402
+
390
403
  // Check if we should skip tracking (dev mode)
391
404
  const useDevFlags = shouldUseDevFlags();
392
405
  if (useDevFlags) {
@@ -394,7 +407,7 @@ export async function trackImpression(adId: string): Promise<void> {
394
407
  return;
395
408
  }
396
409
 
397
- const response = await fetch(trackingUrl, {
410
+ const response = await fetch(url.toString(), {
398
411
  method: 'POST',
399
412
  mode: 'cors', // Explicitly enable CORS
400
413
  credentials: 'omit', // Don't send cookies
@@ -409,13 +422,18 @@ export async function trackImpression(adId: string): Promise<void> {
409
422
  });
410
423
 
411
424
  if (!response.ok) {
412
- console.warn(`[AdBanner] Tracking impression failed: ${response.status} ${response.statusText}`);
425
+ const errorText = await response.text().catch(() => 'Unknown error');
426
+ console.warn(`[AdBanner] Tracking impression failed: ${response.status} ${response.statusText}`, {
427
+ errorText: errorText.substring(0, 200),
428
+ });
413
429
  return;
414
430
  }
415
431
 
416
432
  const data = await response.json();
417
433
  if (data.dev_mode) {
418
434
  console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
435
+ } else {
436
+ console.debug(`[AdBanner] Impression tracked successfully for ad: ${adId}`);
419
437
  }
420
438
  } catch (error) {
421
439
  // Handle CORS errors gracefully (tracking failures shouldn't break the app)
@@ -433,11 +451,20 @@ export async function trackImpression(adId: string): Promise<void> {
433
451
  /**
434
452
  * Track an ad click
435
453
  * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
454
+ *
455
+ * @param adId - The ID of the ad being tracked
456
+ * @param apiKey - The API key for the platform (required for CORS validation and platform identification)
457
+ * @param apiUrl - Optional API base URL override
436
458
  */
437
- export async function trackClick(adId: string): Promise<void> {
459
+ export async function trackClick(adId: string, apiKey: string, apiUrl?: string): Promise<void> {
438
460
  try {
461
+ if (!apiKey) {
462
+ console.warn('[AdBanner] Cannot track click: API key is required');
463
+ return;
464
+ }
465
+
439
466
  // Get the API base URL to determine where to send tracking
440
- const apiBaseUrl = getApiBaseUrl();
467
+ const apiBaseUrl = getApiBaseUrl(apiUrl);
441
468
 
442
469
  // Build tracking URL - remove /api/ads if present, then append /api/ads/track
443
470
  let trackingUrl = apiBaseUrl.trim();
@@ -457,6 +484,10 @@ export async function trackClick(adId: string): Promise<void> {
457
484
  trackingUrl = trackingUrl.replace('thead-vantage.com', 'www.thead-vantage.com');
458
485
  }
459
486
 
487
+ // Add API key to query string (recommended for CORS preflight validation)
488
+ const url = new URL(trackingUrl);
489
+ url.searchParams.set('api_key', apiKey);
490
+
460
491
  // Check if we should skip tracking (dev mode)
461
492
  const useDevFlags = shouldUseDevFlags();
462
493
  if (useDevFlags) {
@@ -464,7 +495,7 @@ export async function trackClick(adId: string): Promise<void> {
464
495
  return;
465
496
  }
466
497
 
467
- const response = await fetch(trackingUrl, {
498
+ const response = await fetch(url.toString(), {
468
499
  method: 'POST',
469
500
  mode: 'cors', // Explicitly enable CORS
470
501
  credentials: 'omit', // Don't send cookies
@@ -479,13 +510,18 @@ export async function trackClick(adId: string): Promise<void> {
479
510
  });
480
511
 
481
512
  if (!response.ok) {
482
- console.warn(`[AdBanner] Tracking click failed: ${response.status} ${response.statusText}`);
513
+ const errorText = await response.text().catch(() => 'Unknown error');
514
+ console.warn(`[AdBanner] Tracking click failed: ${response.status} ${response.statusText}`, {
515
+ errorText: errorText.substring(0, 200),
516
+ });
483
517
  return;
484
518
  }
485
519
 
486
520
  const data = await response.json();
487
521
  if (data.dev_mode) {
488
522
  console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
523
+ } else {
524
+ console.debug(`[AdBanner] Click tracked successfully for ad: ${adId}`);
489
525
  }
490
526
  } catch (error) {
491
527
  // Handle CORS errors gracefully (tracking failures shouldn't break the app)