@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 +54 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +54 -11
- package/dist/index.standalone.js +54 -11
- package/package.json +1 -1
- package/src/constants/endpoints.ts +2 -0
- package/src/managers/ads/advertisement-event-tracker.ts +29 -9
- package/src/modules/ads/AdRenderer.ts +1 -3
- package/src/modules/ads/AdsModule.ts +35 -3
- package/src/renderers/text-renderer.ts +2 -2
package/dist/index.cjs.js
CHANGED
|
@@ -607,13 +607,32 @@ class AdvertisementEventTracker {
|
|
|
607
607
|
riskLevel: additionalData.riskLevel,
|
|
608
608
|
}),
|
|
609
609
|
};
|
|
610
|
-
|
|
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
|
|
621
|
+
headers,
|
|
613
622
|
body: JSON.stringify(eventData),
|
|
614
623
|
});
|
|
615
624
|
if (this.debug) {
|
|
616
|
-
console.log(
|
|
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: '
|
|
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': '
|
|
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 '
|
|
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
|
-
//
|
|
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
package/dist/index.esm.js
CHANGED
|
@@ -605,13 +605,32 @@ class AdvertisementEventTracker {
|
|
|
605
605
|
riskLevel: additionalData.riskLevel,
|
|
606
606
|
}),
|
|
607
607
|
};
|
|
608
|
-
|
|
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
|
|
619
|
+
headers,
|
|
611
620
|
body: JSON.stringify(eventData),
|
|
612
621
|
});
|
|
613
622
|
if (this.debug) {
|
|
614
|
-
console.log(
|
|
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: '
|
|
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': '
|
|
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 '
|
|
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
|
-
//
|
|
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에서 실제 지원하는 필터링 옵션들만 추가
|
package/dist/index.standalone.js
CHANGED
|
@@ -602,13 +602,32 @@ class AdvertisementEventTracker {
|
|
|
602
602
|
riskLevel: additionalData.riskLevel,
|
|
603
603
|
}),
|
|
604
604
|
};
|
|
605
|
-
|
|
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
|
|
616
|
+
headers,
|
|
608
617
|
body: JSON.stringify(eventData),
|
|
609
618
|
});
|
|
610
619
|
if (this.debug) {
|
|
611
|
-
console.log(
|
|
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: '
|
|
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': '
|
|
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 '
|
|
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
|
-
//
|
|
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
|
@@ -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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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(
|
|
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 '
|
|
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
|
-
//
|
|
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: '
|
|
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': '
|
|
55
|
+
'font-size': '14px',
|
|
56
56
|
'font-weight': '500',
|
|
57
57
|
color: '#212529',
|
|
58
58
|
width: '100%', // 전체 너비 사용하여 텍스트 정렬이 적용되도록 함
|