@adstage/web-sdk 2.4.7 → 2.4.8

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
@@ -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,11 +472,8 @@ class AdvertisementEventTracker {
579
472
  */
580
473
  async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
581
474
  try {
582
- // VIEWABLE 이벤트의 경우 중복 확인
583
- if (eventType === AdEventType.VIEWABLE) {
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);
@@ -2064,6 +1954,113 @@ class TextTransitionManager {
2064
1954
  }
2065
1955
  }
2066
1956
 
1957
+ /**
1958
+ * VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
1959
+ * - 메모리 기반 중복 확인
1960
+ * - 세션 스토리지 기반 영구 추적
1961
+ * - 자동 정리 기능
1962
+ * - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
1963
+ */
1964
+ class ViewableEventTracker {
1965
+ /**
1966
+ * 중복 viewable 이벤트 여부 확인
1967
+ */
1968
+ static isDuplicateViewable(adId, slotId, debug = false) {
1969
+ const key = `${adId}_${slotId}`;
1970
+ const now = Date.now();
1971
+ // 디버그 모드에 따른 쿨다운 시간 결정
1972
+ const cooldownTime = debug ?
1973
+ ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
1974
+ ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
1975
+ // 메모리 기반 중복 확인 (새로고침 시 초기화됨)
1976
+ const lastViewable = ViewableEventTracker.viewableTracker.get(key);
1977
+ if (lastViewable && (now - lastViewable) < cooldownTime) {
1978
+ if (debug) {
1979
+ console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
1980
+ }
1981
+ return true;
1982
+ }
1983
+ // 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
1984
+ const sessionKey = `adstage_viewable_${key}`;
1985
+ const sessionViewable = sessionStorage.getItem(sessionKey);
1986
+ if (sessionViewable) {
1987
+ const sessionTime = parseInt(sessionViewable, 10);
1988
+ if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
1989
+ if (debug) {
1990
+ 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)`);
1991
+ }
1992
+ // 메모리에도 기록하여 이후 요청 최적화
1993
+ ViewableEventTracker.viewableTracker.set(key, sessionTime);
1994
+ return true;
1995
+ }
1996
+ }
1997
+ // viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
1998
+ ViewableEventTracker.viewableTracker.set(key, now);
1999
+ sessionStorage.setItem(sessionKey, now.toString());
2000
+ // 오래된 세션 스토리지 데이터 정리 (선택적)
2001
+ ViewableEventTracker.cleanupOldViewables();
2002
+ if (debug) {
2003
+ console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
2004
+ }
2005
+ return false;
2006
+ }
2007
+ /**
2008
+ * 오래된 viewable 추적 데이터 정리
2009
+ */
2010
+ static cleanupOldViewables() {
2011
+ const now = Date.now();
2012
+ // 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
2013
+ const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
2014
+ // 세션 스토리지 정리
2015
+ for (let i = 0; i < sessionStorage.length; i++) {
2016
+ const key = sessionStorage.key(i);
2017
+ if (key && key.startsWith('adstage_viewable_')) {
2018
+ const timestamp = sessionStorage.getItem(key);
2019
+ if (timestamp) {
2020
+ const time = parseInt(timestamp, 10);
2021
+ if (!isNaN(time) && (now - time) > cleanupThreshold) {
2022
+ sessionStorage.removeItem(key);
2023
+ i--; // 인덱스 조정
2024
+ }
2025
+ }
2026
+ }
2027
+ }
2028
+ // 메모리 정리
2029
+ for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
2030
+ if ((now - timestamp) > cleanupThreshold) {
2031
+ ViewableEventTracker.viewableTracker.delete(key);
2032
+ }
2033
+ }
2034
+ }
2035
+ /**
2036
+ * 모든 추적 데이터 정리 (디버그용)
2037
+ */
2038
+ static clear() {
2039
+ ViewableEventTracker.viewableTracker.clear();
2040
+ // 세션 스토리지에서도 adstage_viewable_ 키들 제거
2041
+ const keysToRemove = [];
2042
+ for (let i = 0; i < sessionStorage.length; i++) {
2043
+ const key = sessionStorage.key(i);
2044
+ if (key && key.startsWith('adstage_viewable_')) {
2045
+ keysToRemove.push(key);
2046
+ }
2047
+ }
2048
+ keysToRemove.forEach(key => sessionStorage.removeItem(key));
2049
+ }
2050
+ /**
2051
+ * 특정 광고의 viewable 추적 초기화 (디버그용)
2052
+ */
2053
+ static clearAdViewable(adId, slotId) {
2054
+ const key = `${adId}_${slotId}`;
2055
+ ViewableEventTracker.viewableTracker.delete(key);
2056
+ const sessionKey = `adstage_viewable_${key}`;
2057
+ sessionStorage.removeItem(sessionKey);
2058
+ }
2059
+ }
2060
+ ViewableEventTracker.viewableTracker = new Map();
2061
+ ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
2062
+ ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
2063
+
2067
2064
  /**
2068
2065
  * AdRenderer - 광고 렌더링 전용 클래스
2069
2066
  * 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,11 +470,8 @@ class AdvertisementEventTracker {
577
470
  */
578
471
  async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
579
472
  try {
580
- // VIEWABLE 이벤트의 경우 중복 확인
581
- if (eventType === AdEventType.VIEWABLE) {
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);
@@ -2062,6 +1952,113 @@ class TextTransitionManager {
2062
1952
  }
2063
1953
  }
2064
1954
 
1955
+ /**
1956
+ * VIEWABLE 이벤트 추적 및 중복 방지 관리 클래스
1957
+ * - 메모리 기반 중복 확인
1958
+ * - 세션 스토리지 기반 영구 추적
1959
+ * - 자동 정리 기능
1960
+ * - ViewabilityTracker와 함께 사용하여 중복 viewable 이벤트 방지
1961
+ */
1962
+ class ViewableEventTracker {
1963
+ /**
1964
+ * 중복 viewable 이벤트 여부 확인
1965
+ */
1966
+ static isDuplicateViewable(adId, slotId, debug = false) {
1967
+ const key = `${adId}_${slotId}`;
1968
+ const now = Date.now();
1969
+ // 디버그 모드에 따른 쿨다운 시간 결정
1970
+ const cooldownTime = debug ?
1971
+ ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG :
1972
+ ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION;
1973
+ // 메모리 기반 중복 확인 (새로고침 시 초기화됨)
1974
+ const lastViewable = ViewableEventTracker.viewableTracker.get(key);
1975
+ if (lastViewable && (now - lastViewable) < cooldownTime) {
1976
+ if (debug) {
1977
+ console.log(`Duplicate viewable blocked for ad ${adId} in slot ${slotId}. Cooldown: ${Math.round((cooldownTime - (now - lastViewable)) / 1000)}s remaining (${debug ? 'debug' : 'production'} mode)`);
1978
+ }
1979
+ return true;
1980
+ }
1981
+ // 세션 스토리지 기반 중복 확인 (새로고침 시에도 유지)
1982
+ const sessionKey = `adstage_viewable_${key}`;
1983
+ const sessionViewable = sessionStorage.getItem(sessionKey);
1984
+ if (sessionViewable) {
1985
+ const sessionTime = parseInt(sessionViewable, 10);
1986
+ if (!isNaN(sessionTime) && (now - sessionTime) < cooldownTime) {
1987
+ if (debug) {
1988
+ 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)`);
1989
+ }
1990
+ // 메모리에도 기록하여 이후 요청 최적화
1991
+ ViewableEventTracker.viewableTracker.set(key, sessionTime);
1992
+ return true;
1993
+ }
1994
+ }
1995
+ // viewable 이벤트 시점 기록 (메모리 + 세션 스토리지)
1996
+ ViewableEventTracker.viewableTracker.set(key, now);
1997
+ sessionStorage.setItem(sessionKey, now.toString());
1998
+ // 오래된 세션 스토리지 데이터 정리 (선택적)
1999
+ ViewableEventTracker.cleanupOldViewables();
2000
+ if (debug) {
2001
+ console.log(`✅ New viewable recorded for ad ${adId} in slot ${slotId} (${debug ? 'debug' : 'production'} mode)`);
2002
+ }
2003
+ return false;
2004
+ }
2005
+ /**
2006
+ * 오래된 viewable 추적 데이터 정리
2007
+ */
2008
+ static cleanupOldViewables() {
2009
+ const now = Date.now();
2010
+ // 프로덕션 쿨다운의 2배 시간이 지난 데이터 정리
2011
+ const cleanupThreshold = ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION * 2;
2012
+ // 세션 스토리지 정리
2013
+ for (let i = 0; i < sessionStorage.length; i++) {
2014
+ const key = sessionStorage.key(i);
2015
+ if (key && key.startsWith('adstage_viewable_')) {
2016
+ const timestamp = sessionStorage.getItem(key);
2017
+ if (timestamp) {
2018
+ const time = parseInt(timestamp, 10);
2019
+ if (!isNaN(time) && (now - time) > cleanupThreshold) {
2020
+ sessionStorage.removeItem(key);
2021
+ i--; // 인덱스 조정
2022
+ }
2023
+ }
2024
+ }
2025
+ }
2026
+ // 메모리 정리
2027
+ for (const [key, timestamp] of ViewableEventTracker.viewableTracker.entries()) {
2028
+ if ((now - timestamp) > cleanupThreshold) {
2029
+ ViewableEventTracker.viewableTracker.delete(key);
2030
+ }
2031
+ }
2032
+ }
2033
+ /**
2034
+ * 모든 추적 데이터 정리 (디버그용)
2035
+ */
2036
+ static clear() {
2037
+ ViewableEventTracker.viewableTracker.clear();
2038
+ // 세션 스토리지에서도 adstage_viewable_ 키들 제거
2039
+ const keysToRemove = [];
2040
+ for (let i = 0; i < sessionStorage.length; i++) {
2041
+ const key = sessionStorage.key(i);
2042
+ if (key && key.startsWith('adstage_viewable_')) {
2043
+ keysToRemove.push(key);
2044
+ }
2045
+ }
2046
+ keysToRemove.forEach(key => sessionStorage.removeItem(key));
2047
+ }
2048
+ /**
2049
+ * 특정 광고의 viewable 추적 초기화 (디버그용)
2050
+ */
2051
+ static clearAdViewable(adId, slotId) {
2052
+ const key = `${adId}_${slotId}`;
2053
+ ViewableEventTracker.viewableTracker.delete(key);
2054
+ const sessionKey = `adstage_viewable_${key}`;
2055
+ sessionStorage.removeItem(sessionKey);
2056
+ }
2057
+ }
2058
+ ViewableEventTracker.viewableTracker = new Map();
2059
+ ViewableEventTracker.VIEWABLE_COOLDOWN_PRODUCTION = 300000; // 5분 쿨다운 (프로덕션)
2060
+ ViewableEventTracker.VIEWABLE_COOLDOWN_DEBUG = 30000; // 30초 쿨다운 (디버그)
2061
+
2065
2062
  /**
2066
2063
  * AdRenderer - 광고 렌더링 전용 클래스
2067
2064
  * AdsModule에서 렌더링 관련 기능을 분리
@@ -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,11 +467,8 @@ class AdvertisementEventTracker {
574
467
  */
575
468
  async trackAdvertisementEvent(adId, slotId, eventType, additionalData) {
576
469
  try {
577
- // VIEWABLE 이벤트의 경우 중복 확인
578
- if (eventType === AdEventType.VIEWABLE) {
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);
@@ -2059,6 +1949,113 @@ class TextTransitionManager {
2059
1949
  }
2060
1950
  }
2061
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
+
2062
2059
  /**
2063
2060
  * AdRenderer - 광고 렌더링 전용 클래스
2064
2061
  * AdsModule에서 렌더링 관련 기능을 분리
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "2.4.7",
3
+ "version": "2.4.8",
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",
@@ -40,11 +40,8 @@ export class AdvertisementEventTracker {
40
40
  }
41
41
  ): Promise<void> {
42
42
  try {
43
- // VIEWABLE 이벤트의 경우 중복 확인
44
- if (eventType === AdEventType.VIEWABLE) {
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
  // 현재 슬롯 정보 가져오기