@adstage/web-sdk 2.4.4 → 2.4.5

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/dist/index.cjs.js CHANGED
@@ -607,13 +607,32 @@ class AdvertisementEventTracker {
607
607
  riskLevel: additionalData.riskLevel,
608
608
  }),
609
609
  };
610
- await fetch(`${this.baseUrl}/advertisements/events/${adId}/${eventType}`, {
610
+ const url = `${this.baseUrl}/advertisements/events/${adId}/${eventType}`;
611
+ const headers = ApiHeaders.createForEvents(this.apiKey, eventData);
612
+ if (this.debug) {
613
+ console.log(`🚀 Sending advertisement event: ${eventType} for ad ${adId}`, {
614
+ url,
615
+ headers,
616
+ eventData
617
+ });
618
+ }
619
+ const response = await fetch(url, {
611
620
  method: 'POST',
612
- headers: ApiHeaders.createForEvents(this.apiKey, eventData),
621
+ headers,
613
622
  body: JSON.stringify(eventData),
614
623
  });
615
624
  if (this.debug) {
616
- console.log(`Tracked advertisement event: ${eventType} for ad ${adId}`, eventData);
625
+ console.log(`📡 API Response Status: ${response.status} ${response.statusText}`, {
626
+ url,
627
+ ok: response.ok
628
+ });
629
+ }
630
+ if (!response.ok) {
631
+ const errorText = await response.text();
632
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
633
+ }
634
+ if (this.debug) {
635
+ console.log(`✅ Successfully tracked advertisement event: ${eventType} for ad ${adId}`);
617
636
  }
618
637
  }
619
638
  catch (error) {
@@ -948,6 +967,7 @@ const API_PATHS = {
948
967
  /** 광고 관련 */
949
968
  advertisements: {
950
969
  list: '/advertisements/list',
970
+ detail: '/advertisements',
951
971
  events: '/advertisements/events'
952
972
  },
953
973
  /** 이벤트 관련 */
@@ -966,6 +986,7 @@ class EndpointBuilder {
966
986
  */
967
987
  this.advertisements = {
968
988
  list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
989
+ detail: (adId) => `${this.baseUrl}${API_PATHS.advertisements.detail}/${adId}`,
969
990
  events: (adId, eventType) => `${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
970
991
  };
971
992
  /**
@@ -1206,7 +1227,7 @@ class TextAdRenderer extends BaseAdRenderer {
1206
1227
  // 기본 컨테이너 스타일
1207
1228
  const containerStyles = {
1208
1229
  ...this.getBaseContainerStyles(slot),
1209
- padding: '20px',
1230
+ padding: '4px',
1210
1231
  background: 'transparent',
1211
1232
  display: 'flex',
1212
1233
  'align-items': 'center',
@@ -1234,7 +1255,7 @@ class TextAdRenderer extends BaseAdRenderer {
1234
1255
  }
1235
1256
  // 텍스트 콘텐츠 생성
1236
1257
  const textContent = this.createTextElement(ad.textContent, 'div', {
1237
- 'font-size': '16px',
1258
+ 'font-size': '14px',
1238
1259
  'font-weight': '500',
1239
1260
  color: '#212529',
1240
1261
  width: '100%', // 전체 너비 사용하여 텍스트 정렬이 적용되도록 함
@@ -2285,8 +2306,6 @@ class AdRenderer {
2285
2306
  case AdType.TEXT: {
2286
2307
  const textDiv = document.createElement('div');
2287
2308
  textDiv.innerHTML = `
2288
- <h3>${ad.title}</h3>
2289
- ${ad.description ? `<p>${ad.description}</p>` : ''}
2290
2309
  ${ad.textContent ? `<div>${ad.textContent}</div>` : ''}
2291
2310
  `;
2292
2311
  adElement.appendChild(textDiv);
@@ -2412,7 +2431,7 @@ class AdRenderer {
2412
2431
  case AdType.BANNER:
2413
2432
  return '250px'; // 일반 배너
2414
2433
  case AdType.TEXT:
2415
- return '120px'; // 텍스트는 좀 더 작게
2434
+ return '60px'; // 텍스트는 좀 더 작게
2416
2435
  case AdType.VIDEO:
2417
2436
  return '360px'; // 비디오는 16:9 비율 고려
2418
2437
  case AdType.NATIVE:
@@ -2682,6 +2701,8 @@ class AdsModule {
2682
2701
  autoSlide: options?.autoSlide || false,
2683
2702
  slideInterval: options?.slideInterval || 5000,
2684
2703
  onClick: options?.onClick,
2704
+ // 특정 광고 ID
2705
+ adId: options?.adId,
2685
2706
  // 필터링 옵션들 전달
2686
2707
  language: options?.language,
2687
2708
  deviceType: options?.deviceType,
@@ -2698,6 +2719,8 @@ class AdsModule {
2698
2719
  maxLines: options?.maxLines || 3,
2699
2720
  style: options?.style || 'default',
2700
2721
  onClick: options?.onClick,
2722
+ // 특정 광고 ID
2723
+ adId: options?.adId,
2701
2724
  // 필터링 옵션들 전달
2702
2725
  language: options?.language,
2703
2726
  deviceType: options?.deviceType,
@@ -2897,8 +2920,8 @@ class AdsModule {
2897
2920
  // 기본 fraud 검사
2898
2921
  const fraudDetector = new BasicFraudDetector();
2899
2922
  // viewability 추적
2900
- const tracker = new ViewabilityTracker(element, slot.adType, (metrics) => {
2901
- this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2923
+ const tracker = new ViewabilityTracker(element, slot.adType, async (metrics) => {
2924
+ await this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2902
2925
  });
2903
2926
  // 정리를 위해 저장
2904
2927
  slot.viewabilityTracker = tracker;
@@ -2952,7 +2975,27 @@ class AdsModule {
2952
2975
  if (!this._config?.apiKey) {
2953
2976
  throw new Error('API key not configured');
2954
2977
  }
2955
- // GET 요청용 query parameters 구성
2978
+ // 특정 광고 ID가 지정된 경우, 해당 광고만 요청
2979
+ if (options.adId) {
2980
+ const url = `${endpoints.advertisements.detail(options.adId)}`;
2981
+ const response = await fetch(url, {
2982
+ method: 'GET',
2983
+ headers: ApiHeaders.create(this._config.apiKey)
2984
+ });
2985
+ if (!response.ok) {
2986
+ if (response.status === 404) {
2987
+ console.warn(`🚫 Advertisement not found with ID: ${options.adId}`);
2988
+ return [];
2989
+ }
2990
+ throw new Error(`Failed to fetch ad data: ${response.status}`);
2991
+ }
2992
+ const advertisement = await response.json();
2993
+ if (this._config?.debug) {
2994
+ console.log(`📊 Fetched specific ad with ID: ${options.adId}`, advertisement);
2995
+ }
2996
+ return [advertisement];
2997
+ }
2998
+ // 일반적인 광고 목록 요청
2956
2999
  const params = new URLSearchParams();
2957
3000
  params.append('adType', type);
2958
3001
  // 백엔드 API에서 실제 지원하는 필터링 옵션들만 추가
package/dist/index.d.ts CHANGED
@@ -220,6 +220,7 @@ interface AdOptions {
220
220
  autoplay?: boolean;
221
221
  muted?: boolean;
222
222
  onClick?: (adData: any) => void;
223
+ adId?: string;
223
224
  language?: 'ko' | 'en' | 'ja' | 'zh';
224
225
  deviceType?: 'MOBILE' | 'DESKTOP';
225
226
  country?: 'KR' | 'US' | 'JP' | 'CN' | 'DE';
package/dist/index.esm.js CHANGED
@@ -605,13 +605,32 @@ class AdvertisementEventTracker {
605
605
  riskLevel: additionalData.riskLevel,
606
606
  }),
607
607
  };
608
- await fetch(`${this.baseUrl}/advertisements/events/${adId}/${eventType}`, {
608
+ const url = `${this.baseUrl}/advertisements/events/${adId}/${eventType}`;
609
+ const headers = ApiHeaders.createForEvents(this.apiKey, eventData);
610
+ if (this.debug) {
611
+ console.log(`🚀 Sending advertisement event: ${eventType} for ad ${adId}`, {
612
+ url,
613
+ headers,
614
+ eventData
615
+ });
616
+ }
617
+ const response = await fetch(url, {
609
618
  method: 'POST',
610
- headers: ApiHeaders.createForEvents(this.apiKey, eventData),
619
+ headers,
611
620
  body: JSON.stringify(eventData),
612
621
  });
613
622
  if (this.debug) {
614
- console.log(`Tracked advertisement event: ${eventType} for ad ${adId}`, eventData);
623
+ console.log(`📡 API Response Status: ${response.status} ${response.statusText}`, {
624
+ url,
625
+ ok: response.ok
626
+ });
627
+ }
628
+ if (!response.ok) {
629
+ const errorText = await response.text();
630
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
631
+ }
632
+ if (this.debug) {
633
+ console.log(`✅ Successfully tracked advertisement event: ${eventType} for ad ${adId}`);
615
634
  }
616
635
  }
617
636
  catch (error) {
@@ -946,6 +965,7 @@ const API_PATHS = {
946
965
  /** 광고 관련 */
947
966
  advertisements: {
948
967
  list: '/advertisements/list',
968
+ detail: '/advertisements',
949
969
  events: '/advertisements/events'
950
970
  },
951
971
  /** 이벤트 관련 */
@@ -964,6 +984,7 @@ class EndpointBuilder {
964
984
  */
965
985
  this.advertisements = {
966
986
  list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
987
+ detail: (adId) => `${this.baseUrl}${API_PATHS.advertisements.detail}/${adId}`,
967
988
  events: (adId, eventType) => `${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
968
989
  };
969
990
  /**
@@ -1204,7 +1225,7 @@ class TextAdRenderer extends BaseAdRenderer {
1204
1225
  // 기본 컨테이너 스타일
1205
1226
  const containerStyles = {
1206
1227
  ...this.getBaseContainerStyles(slot),
1207
- padding: '20px',
1228
+ padding: '4px',
1208
1229
  background: 'transparent',
1209
1230
  display: 'flex',
1210
1231
  'align-items': 'center',
@@ -1232,7 +1253,7 @@ class TextAdRenderer extends BaseAdRenderer {
1232
1253
  }
1233
1254
  // 텍스트 콘텐츠 생성
1234
1255
  const textContent = this.createTextElement(ad.textContent, 'div', {
1235
- 'font-size': '16px',
1256
+ 'font-size': '14px',
1236
1257
  'font-weight': '500',
1237
1258
  color: '#212529',
1238
1259
  width: '100%', // 전체 너비 사용하여 텍스트 정렬이 적용되도록 함
@@ -2283,8 +2304,6 @@ class AdRenderer {
2283
2304
  case AdType.TEXT: {
2284
2305
  const textDiv = document.createElement('div');
2285
2306
  textDiv.innerHTML = `
2286
- <h3>${ad.title}</h3>
2287
- ${ad.description ? `<p>${ad.description}</p>` : ''}
2288
2307
  ${ad.textContent ? `<div>${ad.textContent}</div>` : ''}
2289
2308
  `;
2290
2309
  adElement.appendChild(textDiv);
@@ -2410,7 +2429,7 @@ class AdRenderer {
2410
2429
  case AdType.BANNER:
2411
2430
  return '250px'; // 일반 배너
2412
2431
  case AdType.TEXT:
2413
- return '120px'; // 텍스트는 좀 더 작게
2432
+ return '60px'; // 텍스트는 좀 더 작게
2414
2433
  case AdType.VIDEO:
2415
2434
  return '360px'; // 비디오는 16:9 비율 고려
2416
2435
  case AdType.NATIVE:
@@ -2680,6 +2699,8 @@ class AdsModule {
2680
2699
  autoSlide: options?.autoSlide || false,
2681
2700
  slideInterval: options?.slideInterval || 5000,
2682
2701
  onClick: options?.onClick,
2702
+ // 특정 광고 ID
2703
+ adId: options?.adId,
2683
2704
  // 필터링 옵션들 전달
2684
2705
  language: options?.language,
2685
2706
  deviceType: options?.deviceType,
@@ -2696,6 +2717,8 @@ class AdsModule {
2696
2717
  maxLines: options?.maxLines || 3,
2697
2718
  style: options?.style || 'default',
2698
2719
  onClick: options?.onClick,
2720
+ // 특정 광고 ID
2721
+ adId: options?.adId,
2699
2722
  // 필터링 옵션들 전달
2700
2723
  language: options?.language,
2701
2724
  deviceType: options?.deviceType,
@@ -2895,8 +2918,8 @@ class AdsModule {
2895
2918
  // 기본 fraud 검사
2896
2919
  const fraudDetector = new BasicFraudDetector();
2897
2920
  // viewability 추적
2898
- const tracker = new ViewabilityTracker(element, slot.adType, (metrics) => {
2899
- this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2921
+ const tracker = new ViewabilityTracker(element, slot.adType, async (metrics) => {
2922
+ await this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2900
2923
  });
2901
2924
  // 정리를 위해 저장
2902
2925
  slot.viewabilityTracker = tracker;
@@ -2950,7 +2973,27 @@ class AdsModule {
2950
2973
  if (!this._config?.apiKey) {
2951
2974
  throw new Error('API key not configured');
2952
2975
  }
2953
- // GET 요청용 query parameters 구성
2976
+ // 특정 광고 ID가 지정된 경우, 해당 광고만 요청
2977
+ if (options.adId) {
2978
+ const url = `${endpoints.advertisements.detail(options.adId)}`;
2979
+ const response = await fetch(url, {
2980
+ method: 'GET',
2981
+ headers: ApiHeaders.create(this._config.apiKey)
2982
+ });
2983
+ if (!response.ok) {
2984
+ if (response.status === 404) {
2985
+ console.warn(`🚫 Advertisement not found with ID: ${options.adId}`);
2986
+ return [];
2987
+ }
2988
+ throw new Error(`Failed to fetch ad data: ${response.status}`);
2989
+ }
2990
+ const advertisement = await response.json();
2991
+ if (this._config?.debug) {
2992
+ console.log(`📊 Fetched specific ad with ID: ${options.adId}`, advertisement);
2993
+ }
2994
+ return [advertisement];
2995
+ }
2996
+ // 일반적인 광고 목록 요청
2954
2997
  const params = new URLSearchParams();
2955
2998
  params.append('adType', type);
2956
2999
  // 백엔드 API에서 실제 지원하는 필터링 옵션들만 추가
@@ -602,13 +602,32 @@ class AdvertisementEventTracker {
602
602
  riskLevel: additionalData.riskLevel,
603
603
  }),
604
604
  };
605
- await fetch(`${this.baseUrl}/advertisements/events/${adId}/${eventType}`, {
605
+ const url = `${this.baseUrl}/advertisements/events/${adId}/${eventType}`;
606
+ const headers = ApiHeaders.createForEvents(this.apiKey, eventData);
607
+ if (this.debug) {
608
+ console.log(`🚀 Sending advertisement event: ${eventType} for ad ${adId}`, {
609
+ url,
610
+ headers,
611
+ eventData
612
+ });
613
+ }
614
+ const response = await fetch(url, {
606
615
  method: 'POST',
607
- headers: ApiHeaders.createForEvents(this.apiKey, eventData),
616
+ headers,
608
617
  body: JSON.stringify(eventData),
609
618
  });
610
619
  if (this.debug) {
611
- console.log(`Tracked advertisement event: ${eventType} for ad ${adId}`, eventData);
620
+ console.log(`📡 API Response Status: ${response.status} ${response.statusText}`, {
621
+ url,
622
+ ok: response.ok
623
+ });
624
+ }
625
+ if (!response.ok) {
626
+ const errorText = await response.text();
627
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
628
+ }
629
+ if (this.debug) {
630
+ console.log(`✅ Successfully tracked advertisement event: ${eventType} for ad ${adId}`);
612
631
  }
613
632
  }
614
633
  catch (error) {
@@ -943,6 +962,7 @@ const API_PATHS = {
943
962
  /** 광고 관련 */
944
963
  advertisements: {
945
964
  list: '/advertisements/list',
965
+ detail: '/advertisements',
946
966
  events: '/advertisements/events'
947
967
  },
948
968
  /** 이벤트 관련 */
@@ -961,6 +981,7 @@ class EndpointBuilder {
961
981
  */
962
982
  this.advertisements = {
963
983
  list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
984
+ detail: (adId) => `${this.baseUrl}${API_PATHS.advertisements.detail}/${adId}`,
964
985
  events: (adId, eventType) => `${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
965
986
  };
966
987
  /**
@@ -1201,7 +1222,7 @@ class TextAdRenderer extends BaseAdRenderer {
1201
1222
  // 기본 컨테이너 스타일
1202
1223
  const containerStyles = {
1203
1224
  ...this.getBaseContainerStyles(slot),
1204
- padding: '20px',
1225
+ padding: '4px',
1205
1226
  background: 'transparent',
1206
1227
  display: 'flex',
1207
1228
  'align-items': 'center',
@@ -1229,7 +1250,7 @@ class TextAdRenderer extends BaseAdRenderer {
1229
1250
  }
1230
1251
  // 텍스트 콘텐츠 생성
1231
1252
  const textContent = this.createTextElement(ad.textContent, 'div', {
1232
- 'font-size': '16px',
1253
+ 'font-size': '14px',
1233
1254
  'font-weight': '500',
1234
1255
  color: '#212529',
1235
1256
  width: '100%', // 전체 너비 사용하여 텍스트 정렬이 적용되도록 함
@@ -2280,8 +2301,6 @@ class AdRenderer {
2280
2301
  case AdType.TEXT: {
2281
2302
  const textDiv = document.createElement('div');
2282
2303
  textDiv.innerHTML = `
2283
- <h3>${ad.title}</h3>
2284
- ${ad.description ? `<p>${ad.description}</p>` : ''}
2285
2304
  ${ad.textContent ? `<div>${ad.textContent}</div>` : ''}
2286
2305
  `;
2287
2306
  adElement.appendChild(textDiv);
@@ -2407,7 +2426,7 @@ class AdRenderer {
2407
2426
  case AdType.BANNER:
2408
2427
  return '250px'; // 일반 배너
2409
2428
  case AdType.TEXT:
2410
- return '120px'; // 텍스트는 좀 더 작게
2429
+ return '60px'; // 텍스트는 좀 더 작게
2411
2430
  case AdType.VIDEO:
2412
2431
  return '360px'; // 비디오는 16:9 비율 고려
2413
2432
  case AdType.NATIVE:
@@ -2677,6 +2696,8 @@ class AdsModule {
2677
2696
  autoSlide: options?.autoSlide || false,
2678
2697
  slideInterval: options?.slideInterval || 5000,
2679
2698
  onClick: options?.onClick,
2699
+ // 특정 광고 ID
2700
+ adId: options?.adId,
2680
2701
  // 필터링 옵션들 전달
2681
2702
  language: options?.language,
2682
2703
  deviceType: options?.deviceType,
@@ -2693,6 +2714,8 @@ class AdsModule {
2693
2714
  maxLines: options?.maxLines || 3,
2694
2715
  style: options?.style || 'default',
2695
2716
  onClick: options?.onClick,
2717
+ // 특정 광고 ID
2718
+ adId: options?.adId,
2696
2719
  // 필터링 옵션들 전달
2697
2720
  language: options?.language,
2698
2721
  deviceType: options?.deviceType,
@@ -2892,8 +2915,8 @@ class AdsModule {
2892
2915
  // 기본 fraud 검사
2893
2916
  const fraudDetector = new BasicFraudDetector();
2894
2917
  // viewability 추적
2895
- const tracker = new ViewabilityTracker(element, slot.adType, (metrics) => {
2896
- this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2918
+ const tracker = new ViewabilityTracker(element, slot.adType, async (metrics) => {
2919
+ await this.handleViewableEvent(ad, slot, metrics, fraudDetector);
2897
2920
  });
2898
2921
  // 정리를 위해 저장
2899
2922
  slot.viewabilityTracker = tracker;
@@ -2947,7 +2970,27 @@ class AdsModule {
2947
2970
  if (!this._config?.apiKey) {
2948
2971
  throw new Error('API key not configured');
2949
2972
  }
2950
- // GET 요청용 query parameters 구성
2973
+ // 특정 광고 ID가 지정된 경우, 해당 광고만 요청
2974
+ if (options.adId) {
2975
+ const url = `${endpoints.advertisements.detail(options.adId)}`;
2976
+ const response = await fetch(url, {
2977
+ method: 'GET',
2978
+ headers: ApiHeaders.create(this._config.apiKey)
2979
+ });
2980
+ if (!response.ok) {
2981
+ if (response.status === 404) {
2982
+ console.warn(`🚫 Advertisement not found with ID: ${options.adId}`);
2983
+ return [];
2984
+ }
2985
+ throw new Error(`Failed to fetch ad data: ${response.status}`);
2986
+ }
2987
+ const advertisement = await response.json();
2988
+ if (this._config?.debug) {
2989
+ console.log(`📊 Fetched specific ad with ID: ${options.adId}`, advertisement);
2990
+ }
2991
+ return [advertisement];
2992
+ }
2993
+ // 일반적인 광고 목록 요청
2951
2994
  const params = new URLSearchParams();
2952
2995
  params.append('adType', type);
2953
2996
  // 백엔드 API에서 실제 지원하는 필터링 옵션들만 추가
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "2.4.4",
3
+ "version": "2.4.5",
4
4
  "description": "AdStage Web SDK - Production-ready marketing platform SDK with React Provider support for seamless integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -26,6 +26,7 @@ export const API_PATHS = {
26
26
  /** 광고 관련 */
27
27
  advertisements: {
28
28
  list: '/advertisements/list',
29
+ detail: '/advertisements',
29
30
  events: '/advertisements/events'
30
31
  },
31
32
 
@@ -67,6 +68,7 @@ export class EndpointBuilder {
67
68
  */
68
69
  advertisements = {
69
70
  list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
71
+ detail: (adId: string) => `${this.baseUrl}${API_PATHS.advertisements.detail}/${adId}`,
70
72
  events: (adId: string, eventType: string) =>
71
73
  `${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
72
74
  };
@@ -107,17 +107,37 @@ export class AdvertisementEventTracker {
107
107
  };
108
108
 
109
109
 
110
- await fetch(
111
- `${this.baseUrl}/advertisements/events/${adId}/${eventType}`,
112
- {
113
- method: 'POST',
114
- headers: ApiHeaders.createForEvents(this.apiKey, eventData),
115
- body: JSON.stringify(eventData),
116
- }
117
- );
110
+ const url = `${this.baseUrl}/advertisements/events/${adId}/${eventType}`;
111
+ const headers = ApiHeaders.createForEvents(this.apiKey, eventData);
112
+
113
+ if (this.debug) {
114
+ console.log(`🚀 Sending advertisement event: ${eventType} for ad ${adId}`, {
115
+ url,
116
+ headers,
117
+ eventData
118
+ });
119
+ }
120
+
121
+ const response = await fetch(url, {
122
+ method: 'POST',
123
+ headers,
124
+ body: JSON.stringify(eventData),
125
+ });
126
+
127
+ if (this.debug) {
128
+ console.log(`📡 API Response Status: ${response.status} ${response.statusText}`, {
129
+ url,
130
+ ok: response.ok
131
+ });
132
+ }
133
+
134
+ if (!response.ok) {
135
+ const errorText = await response.text();
136
+ throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
137
+ }
118
138
 
119
139
  if (this.debug) {
120
- console.log(`Tracked advertisement event: ${eventType} for ad ${adId}`, eventData);
140
+ console.log(`✅ Successfully tracked advertisement event: ${eventType} for ad ${adId}`);
121
141
  }
122
142
  } catch (error) {
123
143
  console.error('Failed to track advertisement event:', error);
@@ -345,8 +345,6 @@ export class AdRenderer {
345
345
  case AdType.TEXT: {
346
346
  const textDiv = document.createElement('div');
347
347
  textDiv.innerHTML = `
348
- <h3>${ad.title}</h3>
349
- ${ad.description ? `<p>${ad.description}</p>` : ''}
350
348
  ${ad.textContent ? `<div>${ad.textContent}</div>` : ''}
351
349
  `;
352
350
  adElement.appendChild(textDiv);
@@ -480,7 +478,7 @@ export class AdRenderer {
480
478
  case AdType.BANNER:
481
479
  return '250px'; // 일반 배너
482
480
  case AdType.TEXT:
483
- return '120px'; // 텍스트는 좀 더 작게
481
+ return '60px'; // 텍스트는 좀 더 작게
484
482
  case AdType.VIDEO:
485
483
  return '360px'; // 비디오는 16:9 비율 고려
486
484
  case AdType.NATIVE:
@@ -26,6 +26,8 @@ export interface AdOptions {
26
26
  autoplay?: boolean;
27
27
  muted?: boolean;
28
28
  onClick?: (adData: any) => void;
29
+ // 특정 광고 ID 지정
30
+ adId?: string;
29
31
  // 광고 필터링 옵션 (백엔드 API에서 실제 지원하는 것들만)
30
32
  language?: 'ko' | 'en' | 'ja' | 'zh';
31
33
  deviceType?: 'MOBILE' | 'DESKTOP';
@@ -91,6 +93,8 @@ export class AdsModule implements BaseModule {
91
93
  autoSlide: options?.autoSlide || false,
92
94
  slideInterval: options?.slideInterval || 5000,
93
95
  onClick: options?.onClick,
96
+ // 특정 광고 ID
97
+ adId: options?.adId,
94
98
  // 필터링 옵션들 전달
95
99
  language: options?.language,
96
100
  deviceType: options?.deviceType,
@@ -110,6 +114,8 @@ export class AdsModule implements BaseModule {
110
114
  maxLines: options?.maxLines || 3,
111
115
  style: options?.style || 'default',
112
116
  onClick: options?.onClick,
117
+ // 특정 광고 ID
118
+ adId: options?.adId,
113
119
  // 필터링 옵션들 전달
114
120
  language: options?.language,
115
121
  deviceType: options?.deviceType,
@@ -351,8 +357,8 @@ export class AdsModule implements BaseModule {
351
357
  const fraudDetector = new BasicFraudDetector();
352
358
 
353
359
  // viewability 추적
354
- const tracker = new ViewabilityTracker(element, slot.adType, (metrics) => {
355
- this.handleViewableEvent(ad, slot, metrics, fraudDetector);
360
+ const tracker = new ViewabilityTracker(element, slot.adType, async (metrics) => {
361
+ await this.handleViewableEvent(ad, slot, metrics, fraudDetector);
356
362
  });
357
363
 
358
364
  // 정리를 위해 저장
@@ -426,7 +432,33 @@ export class AdsModule implements BaseModule {
426
432
  throw new Error('API key not configured');
427
433
  }
428
434
 
429
- // GET 요청용 query parameters 구성
435
+ // 특정 광고 ID가 지정된 경우, 해당 광고만 요청
436
+ if (options.adId) {
437
+ const url = `${endpoints.advertisements.detail(options.adId)}`;
438
+
439
+ const response = await fetch(url, {
440
+ method: 'GET',
441
+ headers: ApiHeaders.create(this._config.apiKey)
442
+ });
443
+
444
+ if (!response.ok) {
445
+ if (response.status === 404) {
446
+ console.warn(`🚫 Advertisement not found with ID: ${options.adId}`);
447
+ return [];
448
+ }
449
+ throw new Error(`Failed to fetch ad data: ${response.status}`);
450
+ }
451
+
452
+ const advertisement = await response.json();
453
+
454
+ if (this._config?.debug) {
455
+ console.log(`📊 Fetched specific ad with ID: ${options.adId}`, advertisement);
456
+ }
457
+
458
+ return [advertisement];
459
+ }
460
+
461
+ // 일반적인 광고 목록 요청
430
462
  const params = new URLSearchParams();
431
463
  params.append('adType', type);
432
464
 
@@ -16,7 +16,7 @@ export class TextAdRenderer extends BaseAdRenderer {
16
16
  // 기본 컨테이너 스타일
17
17
  const containerStyles: Record<string, string> = {
18
18
  ...this.getBaseContainerStyles(slot),
19
- padding: '20px',
19
+ padding: '4px',
20
20
  background: 'transparent',
21
21
  display: 'flex',
22
22
  'align-items': 'center',
@@ -52,7 +52,7 @@ export class TextAdRenderer extends BaseAdRenderer {
52
52
  ad.textContent,
53
53
  'div',
54
54
  {
55
- 'font-size': '16px',
55
+ 'font-size': '14px',
56
56
  'font-weight': '500',
57
57
  color: '#212529',
58
58
  width: '100%', // 전체 너비 사용하여 텍스트 정렬이 적용되도록 함