@adstage/web-sdk 2.6.1 → 2.6.2
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/README.md +1 -1
- package/dist/index.cjs.js +580 -60
- package/dist/index.d.ts +58 -23
- package/dist/index.esm.js +577 -61
- package/dist/index.standalone.js +577 -61
- package/dist/index.umd.js +4776 -0
- package/package.json +4 -2
- package/src/constants/endpoints.ts +0 -2
- package/src/events/global-events.ts +72 -0
- package/src/example/events/README.md +125 -0
- package/src/example/events/javascript/index-esm.html +159 -0
- package/src/example/events/javascript/index.html +138 -0
- package/src/example/events/nextjs/README.md +54 -0
- package/src/example/events/nextjs/next.config.js +8 -0
- package/src/example/events/nextjs/package.json +16 -0
- package/src/example/events/nextjs/pages/_app.js +15 -0
- package/src/example/events/nextjs/pages/index.js +139 -0
- package/src/example/events/react/.env +0 -0
- package/src/example/events/react/README.md +57 -0
- package/src/example/events/react/package.json +27 -0
- package/src/example/events/react/public/index.html +11 -0
- package/src/example/events/react/src/App.js +162 -0
- package/src/example/events/react/src/index.js +6 -0
- package/src/index.ts +3 -0
- package/src/managers/device-info-collector.ts +9 -4
- package/src/managers/events/event-device-collector.ts +106 -0
- package/src/managers/events/event-session-manager.ts +183 -0
- package/src/managers/events/event-user-collector.ts +166 -0
- package/src/modules/events/events-module.ts +95 -42
- package/src/types/config.ts +3 -0
- package/src/utils/config-utils.ts +69 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdStage SDK - 이벤트용 세션 관리자
|
|
3
|
+
* 세션 ID 생성 및 사용자 ID 관리
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DOMUtils } from '../../utils/dom-utils';
|
|
7
|
+
|
|
8
|
+
export class EventSessionManager {
|
|
9
|
+
private static _userId?: string;
|
|
10
|
+
private static _sessionStartTime?: number;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 세션 ID 생성 및 반환 (SSR 안전)
|
|
14
|
+
*/
|
|
15
|
+
static getSessionId(): string {
|
|
16
|
+
if (!DOMUtils.isBrowser()) {
|
|
17
|
+
return 'ssr_session_' + Date.now();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const stored = sessionStorage.getItem('adstage_session_id');
|
|
21
|
+
if (stored) {
|
|
22
|
+
return stored;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sessionId = 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
|
|
26
|
+
sessionStorage.setItem('adstage_session_id', sessionId);
|
|
27
|
+
|
|
28
|
+
// 세션 시작 시간 기록
|
|
29
|
+
if (!EventSessionManager._sessionStartTime) {
|
|
30
|
+
EventSessionManager._sessionStartTime = Date.now();
|
|
31
|
+
if (DOMUtils.isBrowser()) {
|
|
32
|
+
sessionStorage.setItem('adstage_session_start', String(EventSessionManager._sessionStartTime));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return sessionId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 사용자 ID 설정
|
|
41
|
+
*/
|
|
42
|
+
static setUserId(userId: string): void {
|
|
43
|
+
EventSessionManager._userId = userId;
|
|
44
|
+
|
|
45
|
+
if (DOMUtils.isBrowser()) {
|
|
46
|
+
localStorage.setItem('adstage_user_id', userId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 현재 사용자 ID 반환
|
|
52
|
+
*/
|
|
53
|
+
static getUserId(): string | undefined {
|
|
54
|
+
// 메모리에 있는 값 우선 반환
|
|
55
|
+
if (EventSessionManager._userId) {
|
|
56
|
+
return EventSessionManager._userId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 로컬 스토리지에서 복원
|
|
60
|
+
if (DOMUtils.isBrowser()) {
|
|
61
|
+
const stored = localStorage.getItem('adstage_user_id');
|
|
62
|
+
if (stored) {
|
|
63
|
+
EventSessionManager._userId = stored;
|
|
64
|
+
return stored;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 사용자 ID 제거
|
|
73
|
+
*/
|
|
74
|
+
static clearUserId(): void {
|
|
75
|
+
EventSessionManager._userId = undefined;
|
|
76
|
+
|
|
77
|
+
if (DOMUtils.isBrowser()) {
|
|
78
|
+
localStorage.removeItem('adstage_user_id');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 세션 정보 전체 반환
|
|
84
|
+
*/
|
|
85
|
+
static getSessionInfo(): {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
userId?: string;
|
|
88
|
+
sessionDuration?: number;
|
|
89
|
+
isNewSession: boolean;
|
|
90
|
+
} {
|
|
91
|
+
const sessionId = EventSessionManager.getSessionId();
|
|
92
|
+
const userId = EventSessionManager.getUserId();
|
|
93
|
+
const isNewSession = EventSessionManager.isNewSession();
|
|
94
|
+
const sessionDuration = EventSessionManager.getSessionDuration();
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
sessionId,
|
|
98
|
+
userId,
|
|
99
|
+
sessionDuration,
|
|
100
|
+
isNewSession
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 새 세션인지 확인
|
|
106
|
+
*/
|
|
107
|
+
static isNewSession(): boolean {
|
|
108
|
+
if (!DOMUtils.isBrowser()) return true;
|
|
109
|
+
|
|
110
|
+
const sessionId = sessionStorage.getItem('adstage_session_id');
|
|
111
|
+
const sessionStart = sessionStorage.getItem('adstage_session_start');
|
|
112
|
+
|
|
113
|
+
return !sessionId || !sessionStart;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 세션 지속 시간 반환 (ms)
|
|
118
|
+
*/
|
|
119
|
+
static getSessionDuration(): number {
|
|
120
|
+
if (!DOMUtils.isBrowser()) return 0;
|
|
121
|
+
|
|
122
|
+
const sessionStart = sessionStorage.getItem('adstage_session_start');
|
|
123
|
+
if (!sessionStart) return 0;
|
|
124
|
+
|
|
125
|
+
return Date.now() - parseInt(sessionStart, 10);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 세션 새로고침 (새로운 세션 ID 생성)
|
|
130
|
+
*/
|
|
131
|
+
static refreshSession(): string {
|
|
132
|
+
if (DOMUtils.isBrowser()) {
|
|
133
|
+
sessionStorage.removeItem('adstage_session_id');
|
|
134
|
+
sessionStorage.removeItem('adstage_session_start');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
EventSessionManager._sessionStartTime = undefined;
|
|
138
|
+
return EventSessionManager.getSessionId();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 세션 만료 확인 (24시간 기준)
|
|
143
|
+
*/
|
|
144
|
+
static isSessionExpired(maxDurationHours: number = 24): boolean {
|
|
145
|
+
const duration = EventSessionManager.getSessionDuration();
|
|
146
|
+
const maxDuration = maxDurationHours * 60 * 60 * 1000; // ms 변환
|
|
147
|
+
|
|
148
|
+
return duration > maxDuration;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* 세션 통계 반환 (디버깅용)
|
|
153
|
+
*/
|
|
154
|
+
static getSessionStats(): {
|
|
155
|
+
sessionId: string;
|
|
156
|
+
userId?: string;
|
|
157
|
+
startTime?: number;
|
|
158
|
+
duration: number;
|
|
159
|
+
isExpired: boolean;
|
|
160
|
+
isNewSession: boolean;
|
|
161
|
+
} {
|
|
162
|
+
const sessionId = EventSessionManager.getSessionId();
|
|
163
|
+
const userId = EventSessionManager.getUserId();
|
|
164
|
+
const duration = EventSessionManager.getSessionDuration();
|
|
165
|
+
const isExpired = EventSessionManager.isSessionExpired();
|
|
166
|
+
const isNewSession = EventSessionManager.isNewSession();
|
|
167
|
+
|
|
168
|
+
let startTime: number | undefined;
|
|
169
|
+
if (DOMUtils.isBrowser()) {
|
|
170
|
+
const stored = sessionStorage.getItem('adstage_session_start');
|
|
171
|
+
startTime = stored ? parseInt(stored, 10) : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
sessionId,
|
|
176
|
+
userId,
|
|
177
|
+
startTime,
|
|
178
|
+
duration,
|
|
179
|
+
isExpired,
|
|
180
|
+
isNewSession
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdStage SDK - 이벤트용 사용자 정보 수집기
|
|
3
|
+
* Events API 스키마에 최적화된 사용자 정보 수집
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DOMUtils } from '../../utils/dom-utils';
|
|
7
|
+
|
|
8
|
+
export class EventUserCollector {
|
|
9
|
+
private static _userProperties: {
|
|
10
|
+
gender?: 'male' | 'female' | 'other' | 'unknown';
|
|
11
|
+
country?: string;
|
|
12
|
+
city?: string;
|
|
13
|
+
age?: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
} = {};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Events API용 사용자 정보 반환
|
|
20
|
+
* TrackEventDto.UserAttributesInput 형태에 맞춤
|
|
21
|
+
*/
|
|
22
|
+
static getUserInfo(): {
|
|
23
|
+
gender?: 'male' | 'female' | 'other' | 'unknown';
|
|
24
|
+
country?: string;
|
|
25
|
+
city?: string;
|
|
26
|
+
age?: string;
|
|
27
|
+
language?: string;
|
|
28
|
+
} {
|
|
29
|
+
const baseInfo = EventUserCollector.getBaseUserInfo();
|
|
30
|
+
|
|
31
|
+
// 설정된 사용자 속성과 병합
|
|
32
|
+
return {
|
|
33
|
+
...baseInfo,
|
|
34
|
+
...EventUserCollector._userProperties
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 기본 사용자 정보 수집 (브라우저 기반)
|
|
40
|
+
*/
|
|
41
|
+
private static getBaseUserInfo(): {
|
|
42
|
+
language?: string;
|
|
43
|
+
country?: string;
|
|
44
|
+
} {
|
|
45
|
+
if (!DOMUtils.isBrowser()) {
|
|
46
|
+
return {
|
|
47
|
+
language: 'ko-KR',
|
|
48
|
+
country: 'KR'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 브라우저 언어 설정에서 국가 추출
|
|
53
|
+
const language = navigator.language || 'ko-KR';
|
|
54
|
+
const country = EventUserCollector.extractCountryFromLanguage(language);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
language,
|
|
58
|
+
country
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 언어 코드에서 국가 추출
|
|
64
|
+
*/
|
|
65
|
+
private static extractCountryFromLanguage(language: string): string {
|
|
66
|
+
const countryMap: { [key: string]: string } = {
|
|
67
|
+
'ko': 'KR',
|
|
68
|
+
'ko-KR': 'KR',
|
|
69
|
+
'en': 'US',
|
|
70
|
+
'en-US': 'US',
|
|
71
|
+
'en-GB': 'GB',
|
|
72
|
+
'ja': 'JP',
|
|
73
|
+
'ja-JP': 'JP',
|
|
74
|
+
'zh': 'CN',
|
|
75
|
+
'zh-CN': 'CN',
|
|
76
|
+
'zh-TW': 'TW',
|
|
77
|
+
'de': 'DE',
|
|
78
|
+
'de-DE': 'DE',
|
|
79
|
+
'fr': 'FR',
|
|
80
|
+
'fr-FR': 'FR'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// 정확한 매칭 시도
|
|
84
|
+
if (countryMap[language]) {
|
|
85
|
+
return countryMap[language];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 언어 코드만 추출해서 매칭
|
|
89
|
+
const langCode = language.split('-')[0];
|
|
90
|
+
return countryMap[langCode] || 'KR';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 사용자 속성 설정
|
|
95
|
+
*/
|
|
96
|
+
static setUserProperties(properties: {
|
|
97
|
+
gender?: 'male' | 'female' | 'other' | 'unknown';
|
|
98
|
+
country?: string;
|
|
99
|
+
city?: string;
|
|
100
|
+
age?: string;
|
|
101
|
+
language?: string;
|
|
102
|
+
[key: string]: any;
|
|
103
|
+
}): void {
|
|
104
|
+
EventUserCollector._userProperties = {
|
|
105
|
+
...EventUserCollector._userProperties,
|
|
106
|
+
...properties
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 특정 사용자 속성 설정
|
|
112
|
+
*/
|
|
113
|
+
static setUserProperty(key: string, value: any): void {
|
|
114
|
+
EventUserCollector._userProperties[key] = value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 사용자 속성 초기화
|
|
119
|
+
*/
|
|
120
|
+
static clearUserProperties(): void {
|
|
121
|
+
EventUserCollector._userProperties = {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 현재 설정된 사용자 속성 반환
|
|
126
|
+
*/
|
|
127
|
+
static getCurrentUserProperties(): typeof EventUserCollector._userProperties {
|
|
128
|
+
return { ...EventUserCollector._userProperties };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 사용자 지역 정보 추정 (타임존 기반)
|
|
133
|
+
*/
|
|
134
|
+
static estimateLocation(): {
|
|
135
|
+
timezone: string;
|
|
136
|
+
estimatedCountry?: string;
|
|
137
|
+
} {
|
|
138
|
+
if (!DOMUtils.isBrowser()) {
|
|
139
|
+
return {
|
|
140
|
+
timezone: 'UTC',
|
|
141
|
+
estimatedCountry: 'KR'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
146
|
+
|
|
147
|
+
// 타임존 기반 국가 추정
|
|
148
|
+
const timezoneCountryMap: { [key: string]: string } = {
|
|
149
|
+
'Asia/Seoul': 'KR',
|
|
150
|
+
'Asia/Tokyo': 'JP',
|
|
151
|
+
'Asia/Shanghai': 'CN',
|
|
152
|
+
'Asia/Hong_Kong': 'HK',
|
|
153
|
+
'Asia/Taipei': 'TW',
|
|
154
|
+
'America/New_York': 'US',
|
|
155
|
+
'America/Los_Angeles': 'US',
|
|
156
|
+
'Europe/London': 'GB',
|
|
157
|
+
'Europe/Berlin': 'DE',
|
|
158
|
+
'Europe/Paris': 'FR'
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
timezone,
|
|
163
|
+
estimatedCountry: timezoneCountryMap[timezone] || 'KR'
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AdStage SDK - Events 모듈
|
|
3
|
-
* 이벤트 추적 시스템
|
|
2
|
+
* AdStage SDK - Events 모듈
|
|
3
|
+
* 이벤트 추적 시스템
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { AdStageConfig, BaseModule } from '../../types/config';
|
|
7
|
+
import { endpoints } from '../../constants/endpoints';
|
|
8
|
+
import { ApiHeaders } from '../../utils/api-headers';
|
|
9
|
+
import { EventDeviceCollector } from '../../managers/events/event-device-collector';
|
|
10
|
+
import { EventUserCollector } from '../../managers/events/event-user-collector';
|
|
11
|
+
import { EventSessionManager } from '../../managers/events/event-session-manager';
|
|
7
12
|
|
|
8
13
|
export interface EventProperties {
|
|
9
14
|
[key: string]: any;
|
|
@@ -28,7 +33,7 @@ export class EventsModule implements BaseModule {
|
|
|
28
33
|
this._isReady = true;
|
|
29
34
|
|
|
30
35
|
if (config.debug) {
|
|
31
|
-
console.log('📊 Events module initialized
|
|
36
|
+
console.log('📊 Events module initialized');
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -46,61 +51,109 @@ export class EventsModule implements BaseModule {
|
|
|
46
51
|
return this._config;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
// === 향후 구현 예정 메소드들 ===
|
|
50
|
-
|
|
51
54
|
/**
|
|
52
|
-
*
|
|
53
|
-
* @example AdStage.events.track('page_view', { page: '/products' })
|
|
55
|
+
* 사용자 ID 설정
|
|
54
56
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
setUserId(userId: string): void {
|
|
58
|
+
EventSessionManager.setUserId(userId);
|
|
59
|
+
|
|
60
|
+
if (this._config?.debug) {
|
|
61
|
+
console.log('👤 User ID set:', userId);
|
|
62
|
+
}
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
/**
|
|
61
|
-
*
|
|
62
|
-
* @example AdStage.events.pageView({ page: '/home', title: 'Homepage' })
|
|
66
|
+
* 현재 사용자 ID 반환
|
|
63
67
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// TODO: Q1 2025 구현 예정
|
|
68
|
+
getUserId(): string | undefined {
|
|
69
|
+
return EventSessionManager.getUserId();
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
/**
|
|
70
|
-
*
|
|
71
|
-
* @
|
|
73
|
+
* 이벤트 추적
|
|
74
|
+
* @param eventName 이벤트 이름
|
|
75
|
+
* @param properties 이벤트 속성
|
|
72
76
|
*/
|
|
73
|
-
async
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
async track(eventName: string, properties?: EventProperties): Promise<void> {
|
|
78
|
+
if (!this._isReady) {
|
|
79
|
+
console.warn('Events module not initialized. Call AdStage.init() first.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!this._config?.apiKey) {
|
|
84
|
+
console.warn('API key not configured for event tracking.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const eventData = {
|
|
90
|
+
eventName,
|
|
91
|
+
userId: EventSessionManager.getUserId(),
|
|
92
|
+
sessionId: EventSessionManager.getSessionId(),
|
|
93
|
+
device: EventDeviceCollector.getDeviceInfo(),
|
|
94
|
+
user: EventUserCollector.getUserInfo(),
|
|
95
|
+
params: properties || {}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await this.sendEventToServer(eventData);
|
|
99
|
+
|
|
100
|
+
if (this._config.debug) {
|
|
101
|
+
console.log('✅ Event tracked:', eventName, properties);
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('❌ Failed to track event:', error);
|
|
105
|
+
|
|
106
|
+
if (this._config.debug) {
|
|
107
|
+
console.error('Event data:', { eventName, properties });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
76
110
|
}
|
|
77
111
|
|
|
78
112
|
/**
|
|
79
|
-
*
|
|
80
|
-
* @example AdStage.events.conversion({ type: 'purchase', value: 99.99 })
|
|
113
|
+
* 페이지 뷰 이벤트 (track의 편의 메소드)
|
|
81
114
|
*/
|
|
82
|
-
async
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
async pageView(pageData?: PageData): Promise<void> {
|
|
116
|
+
const properties: EventProperties = {};
|
|
117
|
+
|
|
118
|
+
if (pageData?.page) properties.page = pageData.page;
|
|
119
|
+
if (pageData?.title) properties.title = pageData.title;
|
|
120
|
+
if (pageData?.category) properties.category = pageData.category;
|
|
121
|
+
|
|
122
|
+
// pageData의 다른 속성들도 포함
|
|
123
|
+
if (pageData) {
|
|
124
|
+
Object.keys(pageData).forEach(key => {
|
|
125
|
+
if (key !== 'page' && key !== 'title' && key !== 'category') {
|
|
126
|
+
properties[key] = pageData[key];
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 현재 페이지 정보 자동 수집
|
|
132
|
+
if (typeof window !== 'undefined') {
|
|
133
|
+
if (!properties.page) properties.page = window.location.pathname;
|
|
134
|
+
if (!properties.title) properties.title = document.title;
|
|
135
|
+
properties.url = window.location.href;
|
|
136
|
+
properties.referrer = document.referrer;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await this.track('page_view', properties);
|
|
85
140
|
}
|
|
86
141
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
142
|
+
/**
|
|
143
|
+
* 서버에 이벤트 전송
|
|
144
|
+
*/
|
|
145
|
+
private async sendEventToServer(eventData: any): Promise<void> {
|
|
146
|
+
const response = await fetch(endpoints.events.track(), {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
...ApiHeaders.create(this._config!.apiKey),
|
|
150
|
+
'Content-Type': 'application/json'
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify(eventData)
|
|
153
|
+
});
|
|
99
154
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
track: async (eventName: string, properties: EventProperties) => {
|
|
103
|
-
console.log('🚧 [TODO] Realtime event tracking:', { eventName, properties });
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
throw new Error(`Event tracking failed: ${response.status} ${response.statusText}`);
|
|
104
157
|
}
|
|
105
|
-
}
|
|
158
|
+
}
|
|
106
159
|
}
|
package/src/types/config.ts
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdStage SDK - 설정 유틸리티
|
|
3
|
+
* 전역 설정에 대한 접근 기능 제공
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AdStageConfig } from '../types/config';
|
|
7
|
+
|
|
8
|
+
export class ConfigUtils {
|
|
9
|
+
/**
|
|
10
|
+
* AdStage 전역 설정 반환
|
|
11
|
+
*/
|
|
12
|
+
static getConfig(): AdStageConfig | null {
|
|
13
|
+
// AdStage 클래스 동적 임포트로 순환 참조 방지
|
|
14
|
+
try {
|
|
15
|
+
const { AdStage } = require('../core/adstage');
|
|
16
|
+
return AdStage.getConfig();
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 고객 앱 버전 반환
|
|
24
|
+
*/
|
|
25
|
+
static getAppVersion(): string {
|
|
26
|
+
const config = ConfigUtils.getConfig();
|
|
27
|
+
return config?.appVersion || '1.0.0';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 디버그 모드 여부 확인
|
|
32
|
+
*/
|
|
33
|
+
static isDebugMode(): boolean {
|
|
34
|
+
const config = ConfigUtils.getConfig();
|
|
35
|
+
return config?.debug || false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* API 키 반환
|
|
40
|
+
*/
|
|
41
|
+
static getApiKey(): string | undefined {
|
|
42
|
+
const config = ConfigUtils.getConfig();
|
|
43
|
+
return config?.apiKey;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 타임아웃 값 반환
|
|
48
|
+
*/
|
|
49
|
+
static getTimeout(): number {
|
|
50
|
+
const config = ConfigUtils.getConfig();
|
|
51
|
+
return config?.timeout || 30000;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 플레이스홀더 모드 반환
|
|
56
|
+
*/
|
|
57
|
+
static getPlaceholderMode(): 'invisible' | 'transparent' | 'subtle' | 'minimal' | 'debug' | 'legacy' {
|
|
58
|
+
const config = ConfigUtils.getConfig();
|
|
59
|
+
return config?.placeholderMode || 'subtle';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 활성화된 모듈 목록 반환
|
|
64
|
+
*/
|
|
65
|
+
static getEnabledModules(): string[] {
|
|
66
|
+
const config = ConfigUtils.getConfig();
|
|
67
|
+
return config?.modules || ['ads', 'events', 'config'];
|
|
68
|
+
}
|
|
69
|
+
}
|