@adstage/web-sdk 2.4.7 → 2.4.9
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 +111 -119
- package/dist/index.esm.js +111 -119
- package/dist/index.standalone.js +111 -119
- package/package.json +1 -1
- package/src/managers/ads/advertisement-event-tracker.ts +8 -18
package/dist/index.cjs.js
CHANGED
|
@@ -33,113 +33,6 @@ var DeviceType;
|
|
|
33
33
|
DeviceType["TABLET"] = "TABLET";
|
|
34
34
|
})(DeviceType || (DeviceType = {}));
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
38
|
-
* - 메모리 기반 중복 확인
|
|
39
|
-
* - 세션 스토리지 기반 영구 추적
|
|
40
|
-
* - 자동 정리 기능
|
|
41
|
-
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
42
|
-
*/
|
|
43
|
-
class ViewableEventTracker {
|
|
44
|
-
/**
|
|
45
|
-
* 중복 viewable 이벤트 여부 확인
|
|
46
|
-
*/
|
|
47
|
-
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
48
|
-
const key = `${adId}_${slotId}`;
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
51
|
-
const cooldownTime = debug ?
|
|
52
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
53
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
54
|
-
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
55
|
-
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
56
|
-
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
57
|
-
if (debug) {
|
|
58
|
-
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
59
|
-
}
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
63
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
64
|
-
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
65
|
-
if (sessionViewable) {
|
|
66
|
-
const sessionTime = parseInt(sessionViewable, 10);
|
|
67
|
-
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
68
|
-
if (debug) {
|
|
69
|
-
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
70
|
-
}
|
|
71
|
-
// 메모리에도 기록하여 이후 요청 최적화
|
|
72
|
-
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
77
|
-
ViewableEventTracker.viewableTracker.set(key, now);
|
|
78
|
-
sessionStorage.setItem(sessionKey, now.toString());
|
|
79
|
-
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
80
|
-
ViewableEventTracker.cleanupOldViewables();
|
|
81
|
-
if (debug) {
|
|
82
|
-
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* 오래된 viewable 추적 데이터 정리
|
|
88
|
-
*/
|
|
89
|
-
static cleanupOldViewables() {
|
|
90
|
-
const now = Date.now();
|
|
91
|
-
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
92
|
-
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
93
|
-
// 세션 스토리지 정리
|
|
94
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
95
|
-
const key = sessionStorage.key(i);
|
|
96
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
97
|
-
const timestamp = sessionStorage.getItem(key);
|
|
98
|
-
if (timestamp) {
|
|
99
|
-
const time = parseInt(timestamp, 10);
|
|
100
|
-
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
101
|
-
sessionStorage.removeItem(key);
|
|
102
|
-
i--; // 인덱스 조정
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// 메모리 정리
|
|
108
|
-
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
109
|
-
if ((now - timestamp) > cleanupThreshold) {
|
|
110
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* 모든 추적 데이터 정리 (디버그용)
|
|
116
|
-
*/
|
|
117
|
-
static clear() {
|
|
118
|
-
ViewableEventTracker.viewableTracker.clear();
|
|
119
|
-
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
120
|
-
const keysToRemove = [];
|
|
121
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
122
|
-
const key = sessionStorage.key(i);
|
|
123
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
124
|
-
keysToRemove.push(key);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
131
|
-
*/
|
|
132
|
-
static clearAdViewable(adId, slotId) {
|
|
133
|
-
const key = `${adId}_${slotId}`;
|
|
134
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
135
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
136
|
-
sessionStorage.removeItem(sessionKey);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
ViewableEventTracker.viewableTracker = new Map();
|
|
140
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
141
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
142
|
-
|
|
143
36
|
/**
|
|
144
37
|
* SSR 안전한 DOM API 래퍼 클래스
|
|
145
38
|
* 서버사이드 렌더링 환경에서 DOM API 접근 시 오류를 방지합니다.
|
|
@@ -579,27 +472,19 @@ class AdvertisementEventTracker {
|
|
|
579
472
|
*/
|
|
580
473
|
async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
|
|
581
474
|
try {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (ViewableEventTracker.isDuplicateViewable(adId, slotId, this.debug)) {
|
|
585
|
-
return; // 중복 viewable 이벤트이므로 추적하지 않음
|
|
586
|
-
}
|
|
475
|
+
if (this.debug) {
|
|
476
|
+
console.log(`🚀 AdvertisementEventTracker: Processing ${eventType} event for ad ${adId} in slot ${slotId}`);
|
|
587
477
|
}
|
|
588
478
|
// 현재 슬롯 정보 가져오기
|
|
589
479
|
const slot = this.slots.get(slotId);
|
|
590
480
|
// 디바이스 정보 수집
|
|
591
481
|
const deviceInfo = DeviceInfoCollector.collectDeviceInfo();
|
|
592
482
|
// 광고 이벤트 데이터 구성 (DTO 구조에 맞춤)
|
|
483
|
+
// 서버에서 자동 설정: orgId (API 키로부터), advertisementId, action (URL 파라미터로부터)
|
|
593
484
|
const eventData = {
|
|
594
|
-
// 서버에서 자동 설정: orgId, advertisementId, action
|
|
595
|
-
// 하지만 DTO 검증을 위해 임시값 제공
|
|
596
|
-
orgId: 'temp', // 서버에서 API 키로부터 덮어씀
|
|
597
|
-
advertisementId: adId, // 서버에서 URL 파라미터로부터 덮어씀
|
|
598
|
-
action: eventType, // 서버에서 URL 파라미터로부터 덮어씀
|
|
599
485
|
// 필수 필드들
|
|
600
486
|
adType: slot?.adType || 'BANNER',
|
|
601
|
-
platform: deviceInfo.platform,
|
|
602
|
-
// 디바이스 정보는 deviceInfo 객체로 래핑
|
|
487
|
+
platform: deviceInfo.platform, // 디바이스 정보는 deviceInfo 객체로 래핑
|
|
603
488
|
deviceInfo: deviceInfo,
|
|
604
489
|
// 페이지 및 슬롯 정보
|
|
605
490
|
pageUrl: DOMUtils.getPageInfo().url,
|
|
@@ -2064,6 +1949,113 @@ class TextTransitionManager {
|
|
|
2064
1949
|
}
|
|
2065
1950
|
}
|
|
2066
1951
|
|
|
1952
|
+
/**
|
|
1953
|
+
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
1954
|
+
* - 메모리 기반 중복 확인
|
|
1955
|
+
* - 세션 스토리지 기반 영구 추적
|
|
1956
|
+
* - 자동 정리 기능
|
|
1957
|
+
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
1958
|
+
*/
|
|
1959
|
+
class ViewableEventTracker {
|
|
1960
|
+
/**
|
|
1961
|
+
* 중복 viewable 이벤트 여부 확인
|
|
1962
|
+
*/
|
|
1963
|
+
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
1964
|
+
const key = `${adId}_${slotId}`;
|
|
1965
|
+
const now = Date.now();
|
|
1966
|
+
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
1967
|
+
const cooldownTime = debug ?
|
|
1968
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
1969
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
1970
|
+
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
1971
|
+
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
1972
|
+
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
1973
|
+
if (debug) {
|
|
1974
|
+
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1975
|
+
}
|
|
1976
|
+
return true;
|
|
1977
|
+
}
|
|
1978
|
+
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
1979
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
1980
|
+
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
1981
|
+
if (sessionViewable) {
|
|
1982
|
+
const sessionTime = parseInt(sessionViewable, 10);
|
|
1983
|
+
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
1984
|
+
if (debug) {
|
|
1985
|
+
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1986
|
+
}
|
|
1987
|
+
// 메모리에도 기록하여 이후 요청 최적화
|
|
1988
|
+
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
1989
|
+
return true;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
1993
|
+
ViewableEventTracker.viewableTracker.set(key, now);
|
|
1994
|
+
sessionStorage.setItem(sessionKey, now.toString());
|
|
1995
|
+
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
1996
|
+
ViewableEventTracker.cleanupOldViewables();
|
|
1997
|
+
if (debug) {
|
|
1998
|
+
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
1999
|
+
}
|
|
2000
|
+
return false;
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* 오래된 viewable 추적 데이터 정리
|
|
2004
|
+
*/
|
|
2005
|
+
static cleanupOldViewables() {
|
|
2006
|
+
const now = Date.now();
|
|
2007
|
+
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
2008
|
+
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
2009
|
+
// 세션 스토리지 정리
|
|
2010
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2011
|
+
const key = sessionStorage.key(i);
|
|
2012
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2013
|
+
const timestamp = sessionStorage.getItem(key);
|
|
2014
|
+
if (timestamp) {
|
|
2015
|
+
const time = parseInt(timestamp, 10);
|
|
2016
|
+
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
2017
|
+
sessionStorage.removeItem(key);
|
|
2018
|
+
i--; // 인덱스 조정
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
// 메모리 정리
|
|
2024
|
+
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
2025
|
+
if ((now - timestamp) > cleanupThreshold) {
|
|
2026
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* 모든 추적 데이터 정리 (디버그용)
|
|
2032
|
+
*/
|
|
2033
|
+
static clear() {
|
|
2034
|
+
ViewableEventTracker.viewableTracker.clear();
|
|
2035
|
+
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
2036
|
+
const keysToRemove = [];
|
|
2037
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2038
|
+
const key = sessionStorage.key(i);
|
|
2039
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2040
|
+
keysToRemove.push(key);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
2047
|
+
*/
|
|
2048
|
+
static clearAdViewable(adId, slotId) {
|
|
2049
|
+
const key = `${adId}_${slotId}`;
|
|
2050
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2051
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
2052
|
+
sessionStorage.removeItem(sessionKey);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
ViewableEventTracker.viewableTracker = new Map();
|
|
2056
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
2057
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
2058
|
+
|
|
2067
2059
|
/**
|
|
2068
2060
|
* AdRenderer - 광고 렌더링 전용 클래스
|
|
2069
2061
|
* AdsModule에서 렌더링 관련 기능을 분리
|
package/dist/index.esm.js
CHANGED
|
@@ -31,113 +31,6 @@ var DeviceType;
|
|
|
31
31
|
DeviceType["TABLET"] = "TABLET";
|
|
32
32
|
})(DeviceType || (DeviceType = {}));
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
36
|
-
* - 메모리 기반 중복 확인
|
|
37
|
-
* - 세션 스토리지 기반 영구 추적
|
|
38
|
-
* - 자동 정리 기능
|
|
39
|
-
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
40
|
-
*/
|
|
41
|
-
class ViewableEventTracker {
|
|
42
|
-
/**
|
|
43
|
-
* 중복 viewable 이벤트 여부 확인
|
|
44
|
-
*/
|
|
45
|
-
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
46
|
-
const key = `${adId}_${slotId}`;
|
|
47
|
-
const now = Date.now();
|
|
48
|
-
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
49
|
-
const cooldownTime = debug ?
|
|
50
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
51
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
52
|
-
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
53
|
-
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
54
|
-
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
55
|
-
if (debug) {
|
|
56
|
-
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
61
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
62
|
-
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
63
|
-
if (sessionViewable) {
|
|
64
|
-
const sessionTime = parseInt(sessionViewable, 10);
|
|
65
|
-
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
66
|
-
if (debug) {
|
|
67
|
-
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
68
|
-
}
|
|
69
|
-
// 메모리에도 기록하여 이후 요청 최적화
|
|
70
|
-
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
75
|
-
ViewableEventTracker.viewableTracker.set(key, now);
|
|
76
|
-
sessionStorage.setItem(sessionKey, now.toString());
|
|
77
|
-
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
78
|
-
ViewableEventTracker.cleanupOldViewables();
|
|
79
|
-
if (debug) {
|
|
80
|
-
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
81
|
-
}
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 오래된 viewable 추적 데이터 정리
|
|
86
|
-
*/
|
|
87
|
-
static cleanupOldViewables() {
|
|
88
|
-
const now = Date.now();
|
|
89
|
-
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
90
|
-
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
91
|
-
// 세션 스토리지 정리
|
|
92
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
93
|
-
const key = sessionStorage.key(i);
|
|
94
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
95
|
-
const timestamp = sessionStorage.getItem(key);
|
|
96
|
-
if (timestamp) {
|
|
97
|
-
const time = parseInt(timestamp, 10);
|
|
98
|
-
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
99
|
-
sessionStorage.removeItem(key);
|
|
100
|
-
i--; // 인덱스 조정
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// 메모리 정리
|
|
106
|
-
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
107
|
-
if ((now - timestamp) > cleanupThreshold) {
|
|
108
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* 모든 추적 데이터 정리 (디버그용)
|
|
114
|
-
*/
|
|
115
|
-
static clear() {
|
|
116
|
-
ViewableEventTracker.viewableTracker.clear();
|
|
117
|
-
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
118
|
-
const keysToRemove = [];
|
|
119
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
120
|
-
const key = sessionStorage.key(i);
|
|
121
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
122
|
-
keysToRemove.push(key);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
129
|
-
*/
|
|
130
|
-
static clearAdViewable(adId, slotId) {
|
|
131
|
-
const key = `${adId}_${slotId}`;
|
|
132
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
133
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
134
|
-
sessionStorage.removeItem(sessionKey);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
ViewableEventTracker.viewableTracker = new Map();
|
|
138
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
139
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
140
|
-
|
|
141
34
|
/**
|
|
142
35
|
* SSR 안전한 DOM API 래퍼 클래스
|
|
143
36
|
* 서버사이드 렌더링 환경에서 DOM API 접근 시 오류를 방지합니다.
|
|
@@ -577,27 +470,19 @@ class AdvertisementEventTracker {
|
|
|
577
470
|
*/
|
|
578
471
|
async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
|
|
579
472
|
try {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (ViewableEventTracker.isDuplicateViewable(adId, slotId, this.debug)) {
|
|
583
|
-
return; // 중복 viewable 이벤트이므로 추적하지 않음
|
|
584
|
-
}
|
|
473
|
+
if (this.debug) {
|
|
474
|
+
console.log(`🚀 AdvertisementEventTracker: Processing ${eventType} event for ad ${adId} in slot ${slotId}`);
|
|
585
475
|
}
|
|
586
476
|
// 현재 슬롯 정보 가져오기
|
|
587
477
|
const slot = this.slots.get(slotId);
|
|
588
478
|
// 디바이스 정보 수집
|
|
589
479
|
const deviceInfo = DeviceInfoCollector.collectDeviceInfo();
|
|
590
480
|
// 광고 이벤트 데이터 구성 (DTO 구조에 맞춤)
|
|
481
|
+
// 서버에서 자동 설정: orgId (API 키로부터), advertisementId, action (URL 파라미터로부터)
|
|
591
482
|
const eventData = {
|
|
592
|
-
// 서버에서 자동 설정: orgId, advertisementId, action
|
|
593
|
-
// 하지만 DTO 검증을 위해 임시값 제공
|
|
594
|
-
orgId: 'temp', // 서버에서 API 키로부터 덮어씀
|
|
595
|
-
advertisementId: adId, // 서버에서 URL 파라미터로부터 덮어씀
|
|
596
|
-
action: eventType, // 서버에서 URL 파라미터로부터 덮어씀
|
|
597
483
|
// 필수 필드들
|
|
598
484
|
adType: slot?.adType || 'BANNER',
|
|
599
|
-
platform: deviceInfo.platform,
|
|
600
|
-
// 디바이스 정보는 deviceInfo 객체로 래핑
|
|
485
|
+
platform: deviceInfo.platform, // 디바이스 정보는 deviceInfo 객체로 래핑
|
|
601
486
|
deviceInfo: deviceInfo,
|
|
602
487
|
// 페이지 및 슬롯 정보
|
|
603
488
|
pageUrl: DOMUtils.getPageInfo().url,
|
|
@@ -2062,6 +1947,113 @@ class TextTransitionManager {
|
|
|
2062
1947
|
}
|
|
2063
1948
|
}
|
|
2064
1949
|
|
|
1950
|
+
/**
|
|
1951
|
+
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
1952
|
+
* - 메모리 기반 중복 확인
|
|
1953
|
+
* - 세션 스토리지 기반 영구 추적
|
|
1954
|
+
* - 자동 정리 기능
|
|
1955
|
+
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
1956
|
+
*/
|
|
1957
|
+
class ViewableEventTracker {
|
|
1958
|
+
/**
|
|
1959
|
+
* 중복 viewable 이벤트 여부 확인
|
|
1960
|
+
*/
|
|
1961
|
+
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
1962
|
+
const key = `${adId}_${slotId}`;
|
|
1963
|
+
const now = Date.now();
|
|
1964
|
+
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
1965
|
+
const cooldownTime = debug ?
|
|
1966
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
1967
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
1968
|
+
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
1969
|
+
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
1970
|
+
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
1971
|
+
if (debug) {
|
|
1972
|
+
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1973
|
+
}
|
|
1974
|
+
return true;
|
|
1975
|
+
}
|
|
1976
|
+
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
1977
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
1978
|
+
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
1979
|
+
if (sessionViewable) {
|
|
1980
|
+
const sessionTime = parseInt(sessionViewable, 10);
|
|
1981
|
+
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
1982
|
+
if (debug) {
|
|
1983
|
+
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1984
|
+
}
|
|
1985
|
+
// 메모리에도 기록하여 이후 요청 최적화
|
|
1986
|
+
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
1987
|
+
return true;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
1991
|
+
ViewableEventTracker.viewableTracker.set(key, now);
|
|
1992
|
+
sessionStorage.setItem(sessionKey, now.toString());
|
|
1993
|
+
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
1994
|
+
ViewableEventTracker.cleanupOldViewables();
|
|
1995
|
+
if (debug) {
|
|
1996
|
+
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
1997
|
+
}
|
|
1998
|
+
return false;
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* 오래된 viewable 추적 데이터 정리
|
|
2002
|
+
*/
|
|
2003
|
+
static cleanupOldViewables() {
|
|
2004
|
+
const now = Date.now();
|
|
2005
|
+
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
2006
|
+
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
2007
|
+
// 세션 스토리지 정리
|
|
2008
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2009
|
+
const key = sessionStorage.key(i);
|
|
2010
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2011
|
+
const timestamp = sessionStorage.getItem(key);
|
|
2012
|
+
if (timestamp) {
|
|
2013
|
+
const time = parseInt(timestamp, 10);
|
|
2014
|
+
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
2015
|
+
sessionStorage.removeItem(key);
|
|
2016
|
+
i--; // 인덱스 조정
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
// 메모리 정리
|
|
2022
|
+
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
2023
|
+
if ((now - timestamp) > cleanupThreshold) {
|
|
2024
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* 모든 추적 데이터 정리 (디버그용)
|
|
2030
|
+
*/
|
|
2031
|
+
static clear() {
|
|
2032
|
+
ViewableEventTracker.viewableTracker.clear();
|
|
2033
|
+
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
2034
|
+
const keysToRemove = [];
|
|
2035
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2036
|
+
const key = sessionStorage.key(i);
|
|
2037
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2038
|
+
keysToRemove.push(key);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
2045
|
+
*/
|
|
2046
|
+
static clearAdViewable(adId, slotId) {
|
|
2047
|
+
const key = `${adId}_${slotId}`;
|
|
2048
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2049
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
2050
|
+
sessionStorage.removeItem(sessionKey);
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
ViewableEventTracker.viewableTracker = new Map();
|
|
2054
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
2055
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
2056
|
+
|
|
2065
2057
|
/**
|
|
2066
2058
|
* AdRenderer - 광고 렌더링 전용 클래스
|
|
2067
2059
|
* AdsModule에서 렌더링 관련 기능을 분리
|
package/dist/index.standalone.js
CHANGED
|
@@ -28,113 +28,6 @@ var DeviceType;
|
|
|
28
28
|
DeviceType["TABLET"] = "TABLET";
|
|
29
29
|
})(DeviceType || (DeviceType = {}));
|
|
30
30
|
|
|
31
|
-
/**
|
|
32
|
-
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
33
|
-
* - 메모리 기반 중복 확인
|
|
34
|
-
* - 세션 스토리지 기반 영구 추적
|
|
35
|
-
* - 자동 정리 기능
|
|
36
|
-
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
37
|
-
*/
|
|
38
|
-
class ViewableEventTracker {
|
|
39
|
-
/**
|
|
40
|
-
* 중복 viewable 이벤트 여부 확인
|
|
41
|
-
*/
|
|
42
|
-
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
43
|
-
const key = `${adId}_${slotId}`;
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
46
|
-
const cooldownTime = debug ?
|
|
47
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
48
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
49
|
-
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
50
|
-
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
51
|
-
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
52
|
-
if (debug) {
|
|
53
|
-
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
54
|
-
}
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
58
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
59
|
-
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
60
|
-
if (sessionViewable) {
|
|
61
|
-
const sessionTime = parseInt(sessionViewable, 10);
|
|
62
|
-
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
63
|
-
if (debug) {
|
|
64
|
-
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
65
|
-
}
|
|
66
|
-
// 메모리에도 기록하여 이후 요청 최적화
|
|
67
|
-
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
72
|
-
ViewableEventTracker.viewableTracker.set(key, now);
|
|
73
|
-
sessionStorage.setItem(sessionKey, now.toString());
|
|
74
|
-
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
75
|
-
ViewableEventTracker.cleanupOldViewables();
|
|
76
|
-
if (debug) {
|
|
77
|
-
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* 오래된 viewable 추적 데이터 정리
|
|
83
|
-
*/
|
|
84
|
-
static cleanupOldViewables() {
|
|
85
|
-
const now = Date.now();
|
|
86
|
-
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
87
|
-
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
88
|
-
// 세션 스토리지 정리
|
|
89
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
90
|
-
const key = sessionStorage.key(i);
|
|
91
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
92
|
-
const timestamp = sessionStorage.getItem(key);
|
|
93
|
-
if (timestamp) {
|
|
94
|
-
const time = parseInt(timestamp, 10);
|
|
95
|
-
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
96
|
-
sessionStorage.removeItem(key);
|
|
97
|
-
i--; // 인덱스 조정
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// 메모리 정리
|
|
103
|
-
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
104
|
-
if ((now - timestamp) > cleanupThreshold) {
|
|
105
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* 모든 추적 데이터 정리 (디버그용)
|
|
111
|
-
*/
|
|
112
|
-
static clear() {
|
|
113
|
-
ViewableEventTracker.viewableTracker.clear();
|
|
114
|
-
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
115
|
-
const keysToRemove = [];
|
|
116
|
-
for (let i = 0; i < sessionStorage.length; i++) {
|
|
117
|
-
const key = sessionStorage.key(i);
|
|
118
|
-
if (key && key.startsWith('adstage_viewable_')) {
|
|
119
|
-
keysToRemove.push(key);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
126
|
-
*/
|
|
127
|
-
static clearAdViewable(adId, slotId) {
|
|
128
|
-
const key = `${adId}_${slotId}`;
|
|
129
|
-
ViewableEventTracker.viewableTracker.delete(key);
|
|
130
|
-
const sessionKey = `adstage_viewable_${key}`;
|
|
131
|
-
sessionStorage.removeItem(sessionKey);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
ViewableEventTracker.viewableTracker = new Map();
|
|
135
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
136
|
-
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
137
|
-
|
|
138
31
|
/**
|
|
139
32
|
* SSR 안전한 DOM API 래퍼 클래스
|
|
140
33
|
* 서버사이드 렌더링 환경에서 DOM API 접근 시 오류를 방지합니다.
|
|
@@ -574,27 +467,19 @@ class AdvertisementEventTracker {
|
|
|
574
467
|
*/
|
|
575
468
|
async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
|
|
576
469
|
try {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (ViewableEventTracker.isDuplicateViewable(adId, slotId, this.debug)) {
|
|
580
|
-
return; // 중복 viewable 이벤트이므로 추적하지 않음
|
|
581
|
-
}
|
|
470
|
+
if (this.debug) {
|
|
471
|
+
console.log(`🚀 AdvertisementEventTracker: Processing ${eventType} event for ad ${adId} in slot ${slotId}`);
|
|
582
472
|
}
|
|
583
473
|
// 현재 슬롯 정보 가져오기
|
|
584
474
|
const slot = this.slots.get(slotId);
|
|
585
475
|
// 디바이스 정보 수집
|
|
586
476
|
const deviceInfo = DeviceInfoCollector.collectDeviceInfo();
|
|
587
477
|
// 광고 이벤트 데이터 구성 (DTO 구조에 맞춤)
|
|
478
|
+
// 서버에서 자동 설정: orgId (API 키로부터), advertisementId, action (URL 파라미터로부터)
|
|
588
479
|
const eventData = {
|
|
589
|
-
// 서버에서 자동 설정: orgId, advertisementId, action
|
|
590
|
-
// 하지만 DTO 검증을 위해 임시값 제공
|
|
591
|
-
orgId: 'temp', // 서버에서 API 키로부터 덮어씀
|
|
592
|
-
advertisementId: adId, // 서버에서 URL 파라미터로부터 덮어씀
|
|
593
|
-
action: eventType, // 서버에서 URL 파라미터로부터 덮어씀
|
|
594
480
|
// 필수 필드들
|
|
595
481
|
adType: slot?.adType || 'BANNER',
|
|
596
|
-
platform: deviceInfo.platform,
|
|
597
|
-
// 디바이스 정보는 deviceInfo 객체로 래핑
|
|
482
|
+
platform: deviceInfo.platform, // 디바이스 정보는 deviceInfo 객체로 래핑
|
|
598
483
|
deviceInfo: deviceInfo,
|
|
599
484
|
// 페이지 및 슬롯 정보
|
|
600
485
|
pageUrl: DOMUtils.getPageInfo().url,
|
|
@@ -2059,6 +1944,113 @@ class TextTransitionManager {
|
|
|
2059
1944
|
}
|
|
2060
1945
|
}
|
|
2061
1946
|
|
|
1947
|
+
/**
|
|
1948
|
+
* VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
|
|
1949
|
+
* - 메모리 기반 중복 확인
|
|
1950
|
+
* - 세션 스토리지 기반 영구 추적
|
|
1951
|
+
* - 자동 정리 기능
|
|
1952
|
+
* - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
|
|
1953
|
+
*/
|
|
1954
|
+
class ViewableEventTracker {
|
|
1955
|
+
/**
|
|
1956
|
+
* 중복 viewable 이벤트 여부 확인
|
|
1957
|
+
*/
|
|
1958
|
+
static isDuplicateViewable(adId, slotId, debug = false) {
|
|
1959
|
+
const key = `${adId}_${slotId}`;
|
|
1960
|
+
const now = Date.now();
|
|
1961
|
+
// 디버그 모드에 따른 쿨다운 시간 결정
|
|
1962
|
+
const cooldownTime = debug ?
|
|
1963
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
|
|
1964
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
|
|
1965
|
+
// 메모리 기반 중복 확인 (새로고침 시 초기화됨)
|
|
1966
|
+
const lastViewable = ViewableEventTracker.viewableTracker.get(key);
|
|
1967
|
+
if (lastViewable && (now - lastViewable) < cooldownTime) {
|
|
1968
|
+
if (debug) {
|
|
1969
|
+
console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1970
|
+
}
|
|
1971
|
+
return true;
|
|
1972
|
+
}
|
|
1973
|
+
// 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
|
|
1974
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
1975
|
+
const sessionViewable = sessionStorage.getItem(sessionKey);
|
|
1976
|
+
if (sessionViewable) {
|
|
1977
|
+
const sessionTime = parseInt(sessionViewable, 10);
|
|
1978
|
+
if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
|
|
1979
|
+
if (debug) {
|
|
1980
|
+
console.log(`Session-based duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - sessionTime)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
|
|
1981
|
+
}
|
|
1982
|
+
// 메모리에도 기록하여 이후 요청 최적화
|
|
1983
|
+
ViewableEventTracker.viewableTracker.set(key, sessionTime);
|
|
1984
|
+
return true;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
// viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
|
|
1988
|
+
ViewableEventTracker.viewableTracker.set(key, now);
|
|
1989
|
+
sessionStorage.setItem(sessionKey, now.toString());
|
|
1990
|
+
// 오래된 세션 스토리지 데이터 정리 (선택적)
|
|
1991
|
+
ViewableEventTracker.cleanupOldViewables();
|
|
1992
|
+
if (debug) {
|
|
1993
|
+
console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
|
|
1994
|
+
}
|
|
1995
|
+
return false;
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* 오래된 viewable 추적 데이터 정리
|
|
1999
|
+
*/
|
|
2000
|
+
static cleanupOldViewables() {
|
|
2001
|
+
const now = Date.now();
|
|
2002
|
+
// 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
|
|
2003
|
+
const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
|
|
2004
|
+
// 세션 스토리지 정리
|
|
2005
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2006
|
+
const key = sessionStorage.key(i);
|
|
2007
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2008
|
+
const timestamp = sessionStorage.getItem(key);
|
|
2009
|
+
if (timestamp) {
|
|
2010
|
+
const time = parseInt(timestamp, 10);
|
|
2011
|
+
if (!isNaN(time) && (now - time) > cleanupThreshold) {
|
|
2012
|
+
sessionStorage.removeItem(key);
|
|
2013
|
+
i--; // 인덱스 조정
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
// 메모리 정리
|
|
2019
|
+
for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
|
|
2020
|
+
if ((now - timestamp) > cleanupThreshold) {
|
|
2021
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
/**
|
|
2026
|
+
* 모든 추적 데이터 정리 (디버그용)
|
|
2027
|
+
*/
|
|
2028
|
+
static clear() {
|
|
2029
|
+
ViewableEventTracker.viewableTracker.clear();
|
|
2030
|
+
// 세션 스토리지에서도 adstage_viewable_ 키들 제거
|
|
2031
|
+
const keysToRemove = [];
|
|
2032
|
+
for (let i = 0; i < sessionStorage.length; i++) {
|
|
2033
|
+
const key = sessionStorage.key(i);
|
|
2034
|
+
if (key && key.startsWith('adstage_viewable_')) {
|
|
2035
|
+
keysToRemove.push(key);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
keysToRemove.forEach(key => sessionStorage.removeItem(key));
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* 특정 광고의 viewable 추적 초기화 (디버그용)
|
|
2042
|
+
*/
|
|
2043
|
+
static clearAdViewable(adId, slotId) {
|
|
2044
|
+
const key = `${adId}_${slotId}`;
|
|
2045
|
+
ViewableEventTracker.viewableTracker.delete(key);
|
|
2046
|
+
const sessionKey = `adstage_viewable_${key}`;
|
|
2047
|
+
sessionStorage.removeItem(sessionKey);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
ViewableEventTracker.viewableTracker = new Map();
|
|
2051
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
|
|
2052
|
+
ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
|
|
2053
|
+
|
|
2062
2054
|
/**
|
|
2063
2055
|
* AdRenderer - 광고 렌더링 전용 클래스
|
|
2064
2056
|
* AdsModule에서 렌더링 관련 기능을 분리
|
package/package.json
CHANGED
|
@@ -40,11 +40,8 @@ export class AdvertisementEventTracker {
|
|
|
40
40
|
}
|
|
41
41
|
): Promise<void> {
|
|
42
42
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (ViewableEventTracker.isDuplicateViewable(adId, slotId, this.debug)) {
|
|
46
|
-
return; // 중복 viewable 이벤트이므로 추적하지 않음
|
|
47
|
-
}
|
|
43
|
+
if (this.debug) {
|
|
44
|
+
console.log(`🚀 AdvertisementEventTracker: Processing ${eventType} event for ad ${adId} in slot ${slotId}`);
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
// 현재 슬롯 정보 가져오기
|
|
@@ -53,19 +50,12 @@ export class AdvertisementEventTracker {
|
|
|
53
50
|
// 디바이스 정보 수집
|
|
54
51
|
const deviceInfo = DeviceInfoCollector.collectDeviceInfo();
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
action: eventType, // 서버에서 URL 파라미터로부터 덮어씀
|
|
63
|
-
|
|
64
|
-
// 필수 필드들
|
|
65
|
-
adType: slot?.adType || 'BANNER',
|
|
66
|
-
platform: deviceInfo.platform,
|
|
67
|
-
|
|
68
|
-
// 디바이스 정보는 deviceInfo 객체로 래핑
|
|
53
|
+
// 광고 이벤트 데이터 구성 (DTO 구조에 맞춤)
|
|
54
|
+
// 서버에서 자동 설정: orgId (API 키로부터), advertisementId, action (URL 파라미터로부터)
|
|
55
|
+
const eventData = {
|
|
56
|
+
// 필수 필드들
|
|
57
|
+
adType: slot?.adType || 'BANNER',
|
|
58
|
+
platform: deviceInfo.platform, // 디바이스 정보는 deviceInfo 객체로 래핑
|
|
69
59
|
deviceInfo: deviceInfo,
|
|
70
60
|
|
|
71
61
|
// 페이지 및 슬롯 정보
|