@thead-vantage/react 2.6.0 → 2.8.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.6.0",
3
+ "version": "2.8.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",
@@ -124,7 +124,7 @@ export function AdBanner({
124
124
  rel="noopener noreferrer"
125
125
  className="block"
126
126
  >
127
- {ad.type === 'image' ? (
127
+ {(ad.type === 'image' || ad.type === 'standard') && ad.contentUrl ? (
128
128
  <Image
129
129
  src={ad.contentUrl}
130
130
  alt={ad.name}
package/src/lib/ads.ts CHANGED
@@ -183,6 +183,14 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
183
183
  };
184
184
  };
185
185
 
186
+ // Helper to normalize ad type - "standard" should be treated as "image"
187
+ const normalizeAdType = (ad: Ad): Ad => {
188
+ if (ad.type === 'standard' && ad.contentUrl) {
189
+ return { ...ad, type: 'image' };
190
+ }
191
+ return ad;
192
+ };
193
+
186
194
  // Handle both single ad and array of ads
187
195
  if (data.ads && Array.isArray(data.ads) && data.ads.length > 0) {
188
196
  // If we get an array, use the first ad and ensure it's Ad type
@@ -190,10 +198,13 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
190
198
  console.log('[AdBanner] Processing ads array, first ad:', JSON.stringify(firstAd, null, 2));
191
199
 
192
200
  // Check if it's already an Ad, otherwise convert from AdData
193
- const ad: Ad = isAd(firstAd)
201
+ let ad: Ad = isAd(firstAd)
194
202
  ? firstAd
195
203
  : convertToAd(firstAd as AdData);
196
204
 
205
+ // Normalize the type (standard -> image)
206
+ ad = normalizeAdType(ad);
207
+
197
208
  console.log('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
198
209
 
199
210
  return {
@@ -206,10 +217,19 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
206
217
  }
207
218
 
208
219
  // Handle single ad response - convert AdData to Ad
209
- // Since data.ad is typed as AdData, we always convert it
210
220
  if (data.ad) {
211
221
  console.log('[AdBanner] Processing single ad:', JSON.stringify(data.ad, null, 2));
212
- const ad: Ad = convertToAd(data.ad);
222
+
223
+ // Check if it's already in Ad format or needs conversion
224
+ let ad: Ad;
225
+ if (isAd(data.ad)) {
226
+ // Already in Ad format, normalize the type
227
+ ad = normalizeAdType(data.ad);
228
+ } else {
229
+ // Convert from AdData format
230
+ ad = convertToAd(data.ad);
231
+ }
232
+
213
233
  console.log('[AdBanner] Converted ad:', JSON.stringify(ad, null, 2));
214
234
 
215
235
  return {
@@ -264,14 +284,34 @@ export async function fetchAdBanner(params: FetchAdBannerParams): Promise<AdBann
264
284
 
265
285
  /**
266
286
  * Track an ad impression (when ad is viewed)
267
- * In development mode, this is a no-op (handled by API route)
287
+ * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
268
288
  */
269
289
  export async function trackImpression(adId: string): Promise<void> {
270
290
  try {
271
- const response = await fetch('/api/ads', {
291
+ // Get the API base URL to determine where to send tracking
292
+ const apiBaseUrl = getApiBaseUrl();
293
+
294
+ // Build tracking URL - remove /api/ads if present, then append /api/ads/track
295
+ let trackingUrl = apiBaseUrl.trim();
296
+ if (trackingUrl.endsWith('/api/ads')) {
297
+ trackingUrl = trackingUrl.replace('/api/ads', '/api/ads/track');
298
+ } else {
299
+ // Remove trailing slash if present
300
+ trackingUrl = trackingUrl.replace(/\/$/, '') + '/api/ads/track';
301
+ }
302
+
303
+ // Check if we should skip tracking (dev mode)
304
+ const useDevFlags = shouldUseDevFlags();
305
+ if (useDevFlags) {
306
+ console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
307
+ return;
308
+ }
309
+
310
+ const response = await fetch(trackingUrl, {
272
311
  method: 'POST',
273
312
  headers: {
274
313
  'Content-Type': 'application/json',
314
+ 'Accept': 'application/json',
275
315
  },
276
316
  body: JSON.stringify({
277
317
  action: 'impression',
@@ -279,6 +319,11 @@ export async function trackImpression(adId: string): Promise<void> {
279
319
  }),
280
320
  });
281
321
 
322
+ if (!response.ok) {
323
+ console.warn(`[AdBanner] Tracking impression failed: ${response.status} ${response.statusText}`);
324
+ return;
325
+ }
326
+
282
327
  const data = await response.json();
283
328
  if (data.dev_mode) {
284
329
  console.log(`[DEV] Impression tracking skipped for ad: ${adId}`);
@@ -291,14 +336,34 @@ export async function trackImpression(adId: string): Promise<void> {
291
336
 
292
337
  /**
293
338
  * Track an ad click
294
- * In development mode, this is a no-op (handled by API route)
339
+ * Sends tracking directly to TheAd Vantage API (or skips in dev mode)
295
340
  */
296
341
  export async function trackClick(adId: string): Promise<void> {
297
342
  try {
298
- const response = await fetch('/api/ads', {
343
+ // Get the API base URL to determine where to send tracking
344
+ const apiBaseUrl = getApiBaseUrl();
345
+
346
+ // Build tracking URL - remove /api/ads if present, then append /api/ads/track
347
+ let trackingUrl = apiBaseUrl.trim();
348
+ if (trackingUrl.endsWith('/api/ads')) {
349
+ trackingUrl = trackingUrl.replace('/api/ads', '/api/ads/track');
350
+ } else {
351
+ // Remove trailing slash if present
352
+ trackingUrl = trackingUrl.replace(/\/$/, '') + '/api/ads/track';
353
+ }
354
+
355
+ // Check if we should skip tracking (dev mode)
356
+ const useDevFlags = shouldUseDevFlags();
357
+ if (useDevFlags) {
358
+ console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
359
+ return;
360
+ }
361
+
362
+ const response = await fetch(trackingUrl, {
299
363
  method: 'POST',
300
364
  headers: {
301
365
  'Content-Type': 'application/json',
366
+ 'Accept': 'application/json',
302
367
  },
303
368
  body: JSON.stringify({
304
369
  action: 'click',
@@ -306,6 +371,11 @@ export async function trackClick(adId: string): Promise<void> {
306
371
  }),
307
372
  });
308
373
 
374
+ if (!response.ok) {
375
+ console.warn(`[AdBanner] Tracking click failed: ${response.status} ${response.statusText}`);
376
+ return;
377
+ }
378
+
309
379
  const data = await response.json();
310
380
  if (data.dev_mode) {
311
381
  console.log(`[DEV] Click tracking skipped for ad: ${adId}`);
@@ -31,47 +31,78 @@ function isLocalhost(): boolean {
31
31
  * 5. Production mode (default) → https://thead-vantage.com (full tracking)
32
32
  */
33
33
  export function getApiBaseUrl(explicitApiUrl?: string): string {
34
+ let selectedUrl: string | undefined;
35
+ let reason: string | undefined;
36
+
34
37
  // Priority 1: Explicit API URL override (highest priority)
35
38
  if (explicitApiUrl) {
36
- return explicitApiUrl;
39
+ selectedUrl = explicitApiUrl;
40
+ reason = 'explicit API URL parameter';
37
41
  }
38
-
39
42
  // Priority 2: Environment variable for custom API URL
40
43
  // This allows platform developers to point to their own platform
41
- if (typeof window !== 'undefined') {
44
+ else if (typeof window !== 'undefined') {
42
45
  // Check runtime config first (for browser contexts)
43
46
  const runtimeUrl = (window as any).__THEAD_VANTAGE_API_URL__;
44
- if (runtimeUrl) {
45
- return runtimeUrl;
47
+ if (runtimeUrl && typeof runtimeUrl === 'string' && runtimeUrl.trim()) {
48
+ // Validate that it's not pointing to the current page's origin (would be wrong)
49
+ const currentOrigin = window.location.origin;
50
+ if (!runtimeUrl.startsWith(currentOrigin)) {
51
+ selectedUrl = runtimeUrl;
52
+ reason = 'runtime config (window.__THEAD_VANTAGE_API_URL__)';
53
+ } else {
54
+ console.warn('[TheAd Vantage] Runtime API URL points to current origin, ignoring:', runtimeUrl);
55
+ }
46
56
  }
47
57
  }
48
58
 
49
59
  // Check environment variable (works in both server and client)
50
- const customUrl = process.env.NEXT_PUBLIC_THEAD_VANTAGE_API_URL;
51
- if (customUrl) {
52
- return customUrl;
60
+ if (!selectedUrl) {
61
+ const customUrl = process.env.NEXT_PUBLIC_THEAD_VANTAGE_API_URL;
62
+ if (customUrl && customUrl.trim()) {
63
+ // Validate that it's not pointing to localhost without being TheAd Vantage dev mode
64
+ if (customUrl.includes('localhost') && !customUrl.includes('localhost:3001')) {
65
+ console.warn('[TheAd Vantage] Custom API URL points to localhost (not :3001), this may be incorrect:', customUrl);
66
+ }
67
+ selectedUrl = customUrl;
68
+ reason = 'NEXT_PUBLIC_THEAD_VANTAGE_API_URL environment variable';
69
+ }
53
70
  }
54
71
 
55
72
  // Priority 3: TheAd Vantage dev mode
56
73
  // Only for TheAd Vantage developers testing locally with localhost:3001
57
- const isTheadVantageDevMode = typeof window !== 'undefined'
58
- ? (window as any).__THEAD_VANTAGE_DEV_MODE__ === true ||
59
- process.env.NEXT_PUBLIC_THEAD_VANTAGE_DEV_MODE === 'true'
60
- : process.env.NEXT_PUBLIC_THEAD_VANTAGE_DEV_MODE === 'true';
61
-
62
- if (isTheadVantageDevMode) {
63
- return 'http://localhost:3001/api/ads';
74
+ if (!selectedUrl) {
75
+ const isTheadVantageDevMode = typeof window !== 'undefined'
76
+ ? (window as any).__THEAD_VANTAGE_DEV_MODE__ === true ||
77
+ process.env.NEXT_PUBLIC_THEAD_VANTAGE_DEV_MODE === 'true'
78
+ : process.env.NEXT_PUBLIC_THEAD_VANTAGE_DEV_MODE === 'true';
79
+
80
+ if (isTheadVantageDevMode) {
81
+ selectedUrl = 'http://localhost:3001/api/ads';
82
+ reason = 'TheAd Vantage dev mode (NEXT_PUBLIC_THEAD_VANTAGE_DEV_MODE=true)';
83
+ }
64
84
  }
65
85
 
66
86
  // Priority 4: Localhost development (platform developers on localhost)
67
87
  // Use production API but will add dev flags to prevent tracking
68
- if (isLocalhost()) {
69
- return 'https://thead-vantage.com/api/ads';
88
+ if (!selectedUrl && isLocalhost()) {
89
+ selectedUrl = 'https://thead-vantage.com/api/ads';
90
+ reason = 'localhost detection (production API with dev flags)';
70
91
  }
71
92
 
72
93
  // Priority 5: Production mode (default)
73
94
  // Platform developers use this by default in production
74
- return 'https://thead-vantage.com/api/ads';
95
+ if (!selectedUrl) {
96
+ selectedUrl = 'https://thead-vantage.com/api/ads';
97
+ reason = 'production mode (default)';
98
+ }
99
+
100
+ // Log which URL was selected (helpful for debugging)
101
+ if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
102
+ console.log(`[TheAd Vantage] API Base URL selected: ${selectedUrl} (reason: ${reason})`);
103
+ }
104
+
105
+ return selectedUrl;
75
106
  }
76
107
 
77
108
  /**