@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 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
- // 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);
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
- // 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);
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에서 렌더링 관련 기능을 분리
@@ -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
- // 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);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "2.4.7",
3
+ "version": "2.4.9",
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
  // 현재 슬롯 정보 가져오기
@@ -53,19 +50,12 @@ export class AdvertisementEventTracker {
53
50
  // 디바이스 정보 수집
54
51
  const deviceInfo = DeviceInfoCollector.collectDeviceInfo();
55
52
 
56
- // 광고 이벤트 데이터 구성 (DTO 구조에 맞춤)
57
- const eventData = {
58
- // 서버에서 자동 설정: orgId, advertisementId, action
59
- // 하지만 DTO 검증을 위해 임시값 제공
60
- orgId: 'temp', // 서버에서 API 키로부터 덮어씀
61
- advertisementId: adId, // 서버에서 URL 파라미터로부터 덮어씀
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
  // 페이지 및 슬롯 정보