@adstage/web-sdk 3.0.8 → 3.0.10
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 +1 -1
- package/dist/index.d.ts +143 -22
- package/dist/index.esm.js +1 -1
- package/dist/index.standalone.js +2 -2
- package/dist/index.umd.js +2 -2
- package/package.json +1 -1
- package/src/constants/endpoints.ts +8 -7
- package/src/core/adstage.ts +141 -11
- package/src/events/global-events.ts +5 -35
- package/src/index.ts +3 -1
- package/src/modules/ads/ads-module.ts +10 -1
- package/src/modules/events/events-module.ts +174 -39
- package/src/modules/tracking/tracking-params.ts +317 -0
- package/src/types/events.ts +0 -2
- package/src/managers/events/event-session-manager.ts +0 -183
package/package.json
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
export const API_ENDPOINTS = {
|
|
10
10
|
/** 프로덕션 환경 */
|
|
11
11
|
production: 'https://api.adstage.app',
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
/** 베타 환경 (기본값) */
|
|
14
|
-
beta: 'https://beta-api.adstage.app'
|
|
14
|
+
beta: 'https://beta-api.adstage.app',
|
|
15
15
|
} as const;
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -27,13 +27,14 @@ export const API_PATHS = {
|
|
|
27
27
|
advertisements: {
|
|
28
28
|
list: '/advertisements/list',
|
|
29
29
|
detail: '/advertisements',
|
|
30
|
-
events: '/advertisements/events'
|
|
30
|
+
events: '/advertisements/events',
|
|
31
31
|
},
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
/** 이벤트 관련 */
|
|
34
34
|
events: {
|
|
35
35
|
track: '/events/track',
|
|
36
|
-
|
|
36
|
+
registerAttribution: '/events/register/attribution',
|
|
37
|
+
},
|
|
37
38
|
} as const;
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -67,8 +68,7 @@ export class EndpointBuilder {
|
|
|
67
68
|
advertisements = {
|
|
68
69
|
list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
|
|
69
70
|
detail: (adId: string) => `${this.baseUrl}${API_PATHS.advertisements.detail}/${adId}`,
|
|
70
|
-
events: (adId: string, eventType: string) =>
|
|
71
|
-
`${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
|
|
71
|
+
events: (adId: string, eventType: string) => `${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`,
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -76,6 +76,7 @@ export class EndpointBuilder {
|
|
|
76
76
|
*/
|
|
77
77
|
events = {
|
|
78
78
|
track: () => `${this.baseUrl}${API_PATHS.events.track}`,
|
|
79
|
+
registerAttribution: () => `${this.baseUrl}${API_PATHS.events.registerAttribution}`,
|
|
79
80
|
};
|
|
80
81
|
|
|
81
82
|
/**
|
package/src/core/adstage.ts
CHANGED
|
@@ -9,12 +9,13 @@ import { ConfigModule } from '../modules/config/config-module';
|
|
|
9
9
|
import { EventsModule } from '../modules/events/events-module';
|
|
10
10
|
import { ViewableEventTracker } from '../managers/ads/viewable-event-tracker';
|
|
11
11
|
import { endpoints } from '../constants/endpoints';
|
|
12
|
+
import { TrackingParamsModule } from '../modules/tracking/tracking-params';
|
|
12
13
|
|
|
13
14
|
export class AdStage {
|
|
14
15
|
private static instance: AdStage;
|
|
15
16
|
private _isInitialized = false;
|
|
16
17
|
private _config: AdStageConfig | null = null;
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
// 모듈 인스턴스들
|
|
19
20
|
public readonly ads: AdsModule;
|
|
20
21
|
public readonly config: ConfigModule;
|
|
@@ -36,7 +37,7 @@ export class AdStage {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const instance = AdStage.instance;
|
|
39
|
-
|
|
40
|
+
|
|
40
41
|
// 설정 검증
|
|
41
42
|
if (!config.apiKey) {
|
|
42
43
|
throw new Error('API key is required for AdStage initialization');
|
|
@@ -47,7 +48,7 @@ export class AdStage {
|
|
|
47
48
|
timeout: 30000,
|
|
48
49
|
debug: false,
|
|
49
50
|
modules: ['ads', 'events', 'config'],
|
|
50
|
-
...config
|
|
51
|
+
...config,
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
// 🔧 baseUrl이 설정된 경우 전역 endpoints 객체 업데이트
|
|
@@ -60,7 +61,7 @@ export class AdStage {
|
|
|
60
61
|
|
|
61
62
|
// 모듈 동기 초기화
|
|
62
63
|
const enabledModules = instance._config.modules || ['ads', 'events', 'config'];
|
|
63
|
-
|
|
64
|
+
|
|
64
65
|
for (const moduleName of enabledModules) {
|
|
65
66
|
const module = instance[moduleName as keyof AdStage] as any;
|
|
66
67
|
if (module && typeof module.init === 'function') {
|
|
@@ -70,12 +71,86 @@ export class AdStage {
|
|
|
70
71
|
|
|
71
72
|
instance._isInitialized = true;
|
|
72
73
|
|
|
74
|
+
// 🎯 URL에서 추적 파라미터 자동 추출 (브라우저 환경에만)
|
|
75
|
+
if (typeof window !== 'undefined') {
|
|
76
|
+
const trackingParams = TrackingParamsModule.captureFromUrl();
|
|
77
|
+
if (trackingParams && config.debug) {
|
|
78
|
+
console.log('📊 Tracking parameters captured:', trackingParams);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// � 서버에 Attribution 자동 등록 (클릭 ID가 있는 경우)
|
|
82
|
+
if (trackingParams && TrackingParamsModule.hasClickId()) {
|
|
83
|
+
setTimeout(async () => {
|
|
84
|
+
try {
|
|
85
|
+
// Device ID 생성 (브라우저 fingerprint 또는 랜덤)
|
|
86
|
+
const deviceId = instance.generateDeviceId();
|
|
87
|
+
|
|
88
|
+
const attribution = await instance.events.registerAttribution(deviceId);
|
|
89
|
+
|
|
90
|
+
if (attribution && config.debug) {
|
|
91
|
+
console.log('✅ Attribution registered:', {
|
|
92
|
+
attributionId: attribution.attributionId,
|
|
93
|
+
channel: attribution.channel,
|
|
94
|
+
campaign: attribution.campaign,
|
|
95
|
+
expiresAt: attribution.expiresAt,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error('❌ Failed to register attribution:', err);
|
|
100
|
+
}
|
|
101
|
+
}, 100);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// �🎯 Click 이벤트 자동 전송 (새로운 클릭 ID 발견 시)
|
|
105
|
+
const shouldSend = TrackingParamsModule.shouldSendClickEvent();
|
|
106
|
+
if (config.debug) {
|
|
107
|
+
console.log('🔍 Should send click event:', shouldSend);
|
|
108
|
+
if (!shouldSend) {
|
|
109
|
+
console.log('⏭️ Skipping click event (duplicate or no click ID)');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (shouldSend) {
|
|
114
|
+
// EventsModule 초기화 완료 대기
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
try {
|
|
117
|
+
const trackingParams = TrackingParamsModule.get();
|
|
118
|
+
const attribution = TrackingParamsModule.toAttributionObject();
|
|
119
|
+
|
|
120
|
+
// Click 이벤트 전송 (추적 파라미터는 EventsModule에서 자동 주입)
|
|
121
|
+
instance.events
|
|
122
|
+
.track('click', {
|
|
123
|
+
_autoGenerated: true,
|
|
124
|
+
_source: 'tracking_params',
|
|
125
|
+
_timestamp: new Date().toISOString(),
|
|
126
|
+
_platform: 'web',
|
|
127
|
+
})
|
|
128
|
+
.then(() => {
|
|
129
|
+
TrackingParamsModule.markClickEventSent();
|
|
130
|
+
|
|
131
|
+
if (config.debug) {
|
|
132
|
+
console.log('✅ Auto-tracked click event with tracking params:', {
|
|
133
|
+
attribution,
|
|
134
|
+
trackingParams,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch((err) => {
|
|
139
|
+
console.error('❌ Failed to auto-track click event:', err);
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error('❌ Error in click event auto-tracking:', err);
|
|
143
|
+
}
|
|
144
|
+
}, 200); // Attribution 등록 후 실행
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
73
148
|
if (config.debug) {
|
|
74
149
|
console.log('🚀 AdStage SDK initialized (sync mode)', {
|
|
75
150
|
version: '2.0.0',
|
|
76
151
|
modules: enabledModules,
|
|
77
152
|
apiKey: config.apiKey.substring(0, 8) + '...',
|
|
78
|
-
mode: 'development'
|
|
153
|
+
mode: 'development',
|
|
79
154
|
});
|
|
80
155
|
}
|
|
81
156
|
}
|
|
@@ -105,18 +180,33 @@ export class AdStage {
|
|
|
105
180
|
}
|
|
106
181
|
|
|
107
182
|
/**
|
|
108
|
-
* 편의성을 위한 정적 모듈 접근자들
|
|
183
|
+
* 편의성을 위한 정적 모듈 접근자들 (안전장치 포함)
|
|
109
184
|
*/
|
|
110
185
|
public static get ads() {
|
|
111
|
-
|
|
186
|
+
try {
|
|
187
|
+
return AdStage.getInstance().ads;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.warn('AdStage.ads accessed before initialization');
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
112
192
|
}
|
|
113
193
|
|
|
114
194
|
public static get events() {
|
|
115
|
-
|
|
195
|
+
try {
|
|
196
|
+
return AdStage.getInstance().events;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.warn('AdStage.events accessed before initialization');
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
116
201
|
}
|
|
117
202
|
|
|
118
203
|
public static get config() {
|
|
119
|
-
|
|
204
|
+
try {
|
|
205
|
+
return AdStage.getInstance().config;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.warn('AdStage.config accessed before initialization');
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
120
210
|
}
|
|
121
211
|
|
|
122
212
|
/**
|
|
@@ -129,6 +219,46 @@ export class AdStage {
|
|
|
129
219
|
}
|
|
130
220
|
}
|
|
131
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Device ID 생성 (브라우저 fingerprint 기반)
|
|
224
|
+
* localStorage에 저장하여 재사용
|
|
225
|
+
*/
|
|
226
|
+
private generateDeviceId(): string {
|
|
227
|
+
if (typeof window === 'undefined') {
|
|
228
|
+
return 'server-' + Math.random().toString(36).substring(2, 15);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 기존 Device ID 확인
|
|
232
|
+
const existingId = localStorage.getItem('adstage_device_id');
|
|
233
|
+
if (existingId) {
|
|
234
|
+
return existingId;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 새 Device ID 생성 (브라우저 fingerprint 기반)
|
|
238
|
+
const fingerprint = [
|
|
239
|
+
navigator.userAgent,
|
|
240
|
+
navigator.language,
|
|
241
|
+
screen.width + 'x' + screen.height,
|
|
242
|
+
new Date().getTimezoneOffset(),
|
|
243
|
+
navigator.hardwareConcurrency || 0,
|
|
244
|
+
].join('|');
|
|
245
|
+
|
|
246
|
+
// 간단한 해시 생성
|
|
247
|
+
let hash = 0;
|
|
248
|
+
for (let i = 0; i < fingerprint.length; i++) {
|
|
249
|
+
const char = fingerprint.charCodeAt(i);
|
|
250
|
+
hash = (hash << 5) - hash + char;
|
|
251
|
+
hash = hash & hash;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const deviceId = 'web-' + Math.abs(hash).toString(36) + '-' + Date.now().toString(36);
|
|
255
|
+
|
|
256
|
+
// localStorage에 저장
|
|
257
|
+
localStorage.setItem('adstage_device_id', deviceId);
|
|
258
|
+
|
|
259
|
+
return deviceId;
|
|
260
|
+
}
|
|
261
|
+
|
|
132
262
|
/**
|
|
133
263
|
* 디버그용 메서드들
|
|
134
264
|
*/
|
|
@@ -155,9 +285,9 @@ export class AdStage {
|
|
|
155
285
|
getViewableStatus: (): void => {
|
|
156
286
|
console.log('📊 AdStage Debug: 현재 viewable 추적 상태', {
|
|
157
287
|
trackedCount: (ViewableEventTracker as any).viewableTracker.size,
|
|
158
|
-
trackedItems: Array.from((ViewableEventTracker as any).viewableTracker)
|
|
288
|
+
trackedItems: Array.from((ViewableEventTracker as any).viewableTracker),
|
|
159
289
|
});
|
|
160
|
-
}
|
|
290
|
+
},
|
|
161
291
|
};
|
|
162
292
|
}
|
|
163
293
|
|
|
@@ -10,7 +10,7 @@ import type { EventProperties } from '../modules/events/events-module';
|
|
|
10
10
|
* 이벤트 추적 (메인 함수)
|
|
11
11
|
* @param eventName 이벤트 이름
|
|
12
12
|
* @param properties 이벤트 속성
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* @example
|
|
15
15
|
* track('purchase', {
|
|
16
16
|
* transaction_id: 'T123',
|
|
@@ -23,44 +23,14 @@ export function track(eventName: string, properties?: EventProperties): Promise<
|
|
|
23
23
|
console.warn('AdStage not initialized. Call AdStage.init() first.');
|
|
24
24
|
return Promise.resolve();
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
return AdStage.events.track(eventName, properties);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 사용자 ID 설정
|
|
34
|
-
* @param userId 사용자 ID
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* setUserId('user123');
|
|
38
|
-
*/
|
|
39
|
-
export function setUserId(userId: string): void {
|
|
40
|
-
if (!AdStage.isReady()) {
|
|
41
|
-
console.warn('AdStage not initialized. Call AdStage.init() first.');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
AdStage.events.setUserId(userId);
|
|
46
|
-
}
|
|
47
26
|
|
|
48
|
-
|
|
49
|
-
* 현재 사용자 ID 반환
|
|
50
|
-
*/
|
|
51
|
-
export function getUserId(): string | undefined {
|
|
52
|
-
if (!AdStage.isReady()) {
|
|
53
|
-
console.warn('AdStage not initialized. Call AdStage.init() first.');
|
|
54
|
-
return undefined;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return AdStage.events.getUserId();
|
|
27
|
+
return AdStage.events.track(eventName, properties);
|
|
58
28
|
}
|
|
59
29
|
|
|
60
30
|
/**
|
|
61
31
|
* 사용자 속성 설정
|
|
62
32
|
* @param properties 사용자 속성 객체
|
|
63
|
-
*
|
|
33
|
+
*
|
|
64
34
|
* @example
|
|
65
35
|
* setUserProperties({
|
|
66
36
|
* gender: 'male',
|
|
@@ -73,7 +43,7 @@ export function setUserProperties(properties: import('../types/events').UserProp
|
|
|
73
43
|
console.warn('AdStage not initialized. Call AdStage.init() first.');
|
|
74
44
|
return;
|
|
75
45
|
}
|
|
76
|
-
|
|
46
|
+
|
|
77
47
|
AdStage.events.setUserProperties(properties);
|
|
78
48
|
}
|
|
79
49
|
|
|
@@ -85,6 +55,6 @@ export function getUserProperties(): any {
|
|
|
85
55
|
console.warn('AdStage not initialized. Call AdStage.init() first.');
|
|
86
56
|
return undefined;
|
|
87
57
|
}
|
|
88
|
-
|
|
58
|
+
|
|
89
59
|
return AdStage.events.getUserProperties();
|
|
90
60
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { default as AdStage } from './core/adstage';
|
|
|
8
8
|
import AdStageCore from './core/adstage';
|
|
9
9
|
|
|
10
10
|
// 전역 이벤트 함수들 (Firebase 스타일)
|
|
11
|
-
export { track,
|
|
11
|
+
export { track, setUserProperties, getUserProperties } from './events/global-events';
|
|
12
12
|
|
|
13
13
|
// React 통합
|
|
14
14
|
export { AdStageProvider, useAdStageContext, useAdStageInstance } from './react';
|
|
@@ -19,6 +19,8 @@ export type { AdStageConfig, ModuleName, BaseModule, ApiResponse, OrganizationIn
|
|
|
19
19
|
// 모듈별 타입
|
|
20
20
|
export type { AdOptions } from './modules/ads/ads-module';
|
|
21
21
|
export type { EventProperties, UserProperties } from './modules/events/events-module';
|
|
22
|
+
export type { TrackingParams } from './modules/tracking/tracking-params';
|
|
23
|
+
export { TrackingParamsModule } from './modules/tracking/tracking-params';
|
|
22
24
|
|
|
23
25
|
// 광고 관련 타입
|
|
24
26
|
export type { AdType, AdEventType, Advertisement, AdSlot } from './types/advertisement';
|
|
@@ -211,7 +211,11 @@ export class AdsModule implements BaseModule {
|
|
|
211
211
|
|
|
212
212
|
const slot = this.slots.get(slotId);
|
|
213
213
|
if (!slot) {
|
|
214
|
-
|
|
214
|
+
// 🔧 이미 정리된 슬롯인 경우 조용히 처리 (에러 발생하지 않음)
|
|
215
|
+
if (this._config?.debug) {
|
|
216
|
+
console.log(`🔍 Ad slot already cleaned up: ${slotId}`);
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
215
219
|
}
|
|
216
220
|
|
|
217
221
|
// SimpleViewabilityTracker 정리
|
|
@@ -637,6 +641,11 @@ export class AdsModule implements BaseModule {
|
|
|
637
641
|
const slot = this.slots.get(slotId);
|
|
638
642
|
if (slot) {
|
|
639
643
|
try {
|
|
644
|
+
// SimpleViewabilityTracker 정리
|
|
645
|
+
if ((slot as any).viewabilityTracker) {
|
|
646
|
+
(slot as any).viewabilityTracker.destroy();
|
|
647
|
+
}
|
|
648
|
+
|
|
640
649
|
// 슬롯 정리 (로그 출력 최소화)
|
|
641
650
|
this.slots.delete(slotId);
|
|
642
651
|
|
|
@@ -9,7 +9,7 @@ import { ApiHeaders } from '../../utils/api-headers';
|
|
|
9
9
|
import { getSDKVersion } from '../../utils/version';
|
|
10
10
|
import { EventDeviceCollector } from '../../managers/events/event-device-collector';
|
|
11
11
|
import { EventUserCollector } from '../../managers/events/event-user-collector';
|
|
12
|
-
import {
|
|
12
|
+
import { TrackingParamsModule } from '../tracking/tracking-params';
|
|
13
13
|
import type { UserProperties, EventProperties } from '../../types/events';
|
|
14
14
|
|
|
15
15
|
export type { EventProperties, UserProperties };
|
|
@@ -44,30 +44,12 @@ export class EventsModule implements BaseModule {
|
|
|
44
44
|
return this._config;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
48
|
-
* 사용자 ID 설정
|
|
49
|
-
*/
|
|
50
|
-
setUserId(userId: string): void {
|
|
51
|
-
EventSessionManager.setUserId(userId);
|
|
52
|
-
|
|
53
|
-
if (this._config?.debug) {
|
|
54
|
-
console.log('👤 User ID set:', userId);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 현재 사용자 ID 반환
|
|
60
|
-
*/
|
|
61
|
-
getUserId(): string | undefined {
|
|
62
|
-
return EventSessionManager.getUserId();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
47
|
/**
|
|
66
48
|
* 사용자 속성 설정
|
|
67
49
|
*/
|
|
68
50
|
setUserProperties(properties: UserProperties): void {
|
|
69
51
|
EventUserCollector.setUserProperties(properties);
|
|
70
|
-
|
|
52
|
+
|
|
71
53
|
if (this._config?.debug) {
|
|
72
54
|
console.log('👤 User properties set:', properties);
|
|
73
55
|
}
|
|
@@ -83,15 +65,17 @@ export class EventsModule implements BaseModule {
|
|
|
83
65
|
/**
|
|
84
66
|
* 디바이스 정보 설정
|
|
85
67
|
*/
|
|
86
|
-
setDeviceInfo(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
68
|
+
setDeviceInfo(
|
|
69
|
+
deviceInfo: Partial<{
|
|
70
|
+
category: 'mobile' | 'desktop' | 'tablet' | 'other';
|
|
71
|
+
platform: string;
|
|
72
|
+
model: string;
|
|
73
|
+
appVersion: string;
|
|
74
|
+
osVersion: string;
|
|
75
|
+
}>,
|
|
76
|
+
): void {
|
|
93
77
|
EventDeviceCollector.setDeviceInfo(deviceInfo);
|
|
94
|
-
|
|
78
|
+
|
|
95
79
|
if (this._config?.debug) {
|
|
96
80
|
console.log('📱 Device info set:', deviceInfo);
|
|
97
81
|
}
|
|
@@ -102,7 +86,7 @@ export class EventsModule implements BaseModule {
|
|
|
102
86
|
*/
|
|
103
87
|
setDeviceProperty(key: 'category' | 'platform' | 'model' | 'appVersion' | 'osVersion', value: string): void {
|
|
104
88
|
EventDeviceCollector.setDeviceProperty(key, value);
|
|
105
|
-
|
|
89
|
+
|
|
106
90
|
if (this._config?.debug) {
|
|
107
91
|
console.log('📱 Device property set:', key, value);
|
|
108
92
|
}
|
|
@@ -113,7 +97,7 @@ export class EventsModule implements BaseModule {
|
|
|
113
97
|
*/
|
|
114
98
|
clearDeviceInfo(): void {
|
|
115
99
|
EventDeviceCollector.clearDeviceInfo();
|
|
116
|
-
|
|
100
|
+
|
|
117
101
|
if (this._config?.debug) {
|
|
118
102
|
console.log('📱 User provided device info cleared');
|
|
119
103
|
}
|
|
@@ -164,47 +148,198 @@ export class EventsModule implements BaseModule {
|
|
|
164
148
|
}
|
|
165
149
|
|
|
166
150
|
try {
|
|
151
|
+
// 🆔 저장된 Attribution ID 확인
|
|
152
|
+
const attributionId = this.getAttributionId();
|
|
153
|
+
|
|
154
|
+
// Attribution ID가 있으면 서버에서 조회하도록 전달
|
|
155
|
+
if (attributionId) {
|
|
156
|
+
const eventData = {
|
|
157
|
+
eventName,
|
|
158
|
+
sdkVersion: getSDKVersion(),
|
|
159
|
+
device: EventDeviceCollector.getDeviceInfo(),
|
|
160
|
+
user: EventUserCollector.getUserInfo(),
|
|
161
|
+
attributionId, // 서버가 이 ID로 attribution 조회
|
|
162
|
+
params: properties || {},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
await this.sendEventToServer(eventData);
|
|
166
|
+
|
|
167
|
+
if (this._config.debug) {
|
|
168
|
+
console.log('✅ Event tracked with attribution ID:', eventName, attributionId);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Attribution ID가 없으면 기존 방식 (URL 파라미터 직접 전달)
|
|
174
|
+
const trackingParams = TrackingParamsModule.toAttributionObject();
|
|
175
|
+
|
|
167
176
|
const eventData = {
|
|
168
177
|
eventName,
|
|
169
|
-
userId: EventSessionManager.getUserId(),
|
|
170
|
-
sessionId: EventSessionManager.getSessionId(),
|
|
171
178
|
sdkVersion: getSDKVersion(),
|
|
172
179
|
device: EventDeviceCollector.getDeviceInfo(),
|
|
173
180
|
user: EventUserCollector.getUserInfo(),
|
|
174
|
-
|
|
181
|
+
|
|
182
|
+
// attribution 필드에 추적 파라미터 자동 포함
|
|
183
|
+
...(trackingParams && { attribution: trackingParams }),
|
|
184
|
+
|
|
185
|
+
params: properties || {},
|
|
175
186
|
};
|
|
176
187
|
|
|
177
188
|
await this.sendEventToServer(eventData);
|
|
178
189
|
|
|
179
190
|
if (this._config.debug) {
|
|
180
191
|
console.log('✅ Event tracked:', eventName, properties);
|
|
192
|
+
if (trackingParams) {
|
|
193
|
+
console.log('📊 Attribution included:', trackingParams);
|
|
194
|
+
}
|
|
181
195
|
}
|
|
182
196
|
} catch (error) {
|
|
183
197
|
console.error('❌ Failed to track event:', error);
|
|
184
|
-
|
|
198
|
+
|
|
185
199
|
if (this._config.debug) {
|
|
186
200
|
console.error('Event data:', { eventName, properties });
|
|
187
201
|
}
|
|
188
202
|
}
|
|
189
203
|
}
|
|
190
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Attribution 등록 (서버 기반 Attribution ID 패턴)
|
|
207
|
+
*
|
|
208
|
+
* 사용 사례:
|
|
209
|
+
* 1. 앱 설치 시 Install Referrer 전송
|
|
210
|
+
* 2. 웹 첫 방문 시 URL 파라미터 전송
|
|
211
|
+
*
|
|
212
|
+
* @param deviceId 디바이스 고유 ID
|
|
213
|
+
* @returns Attribution ID 및 파싱된 정보
|
|
214
|
+
*/
|
|
215
|
+
async registerAttribution(deviceId: string): Promise<{
|
|
216
|
+
attributionId: string;
|
|
217
|
+
channel: string;
|
|
218
|
+
campaign?: string;
|
|
219
|
+
attribution: Record<string, any>;
|
|
220
|
+
installedAt: Date;
|
|
221
|
+
expiresAt: Date;
|
|
222
|
+
} | null> {
|
|
223
|
+
if (!this._isReady) {
|
|
224
|
+
console.warn('Events module not initialized. Call AdStage.init() first.');
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!this._config?.apiKey) {
|
|
229
|
+
console.warn('API key not configured for attribution registration.');
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// 현재 URL의 추적 파라미터 가져오기
|
|
235
|
+
const trackingParams = TrackingParamsModule.get();
|
|
236
|
+
|
|
237
|
+
if (!trackingParams || Object.keys(trackingParams).length === 0) {
|
|
238
|
+
if (this._config.debug) {
|
|
239
|
+
console.log('⏭️ No tracking parameters found, skipping attribution registration');
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 서버에 Attribution 등록
|
|
245
|
+
const response = await this.sendAttributionToServer({
|
|
246
|
+
deviceId,
|
|
247
|
+
referrerParams: trackingParams,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if (this._config.debug) {
|
|
251
|
+
console.log('✅ Attribution registered:', response);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Attribution ID를 sessionStorage에 저장
|
|
255
|
+
if (response.attributionId) {
|
|
256
|
+
sessionStorage.setItem('adstage_attribution_id', response.attributionId);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return response;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
console.error('❌ Failed to register attribution:', error);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 저장된 Attribution ID 반환
|
|
268
|
+
*/
|
|
269
|
+
getAttributionId(): string | null {
|
|
270
|
+
if (typeof window === 'undefined' || !window.sessionStorage) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
191
273
|
|
|
274
|
+
return sessionStorage.getItem('adstage_attribution_id');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Attribution ID로 이벤트 전송 (별칭 메서드)
|
|
279
|
+
* track() 메서드가 자동으로 Attribution ID를 사용하므로 동일하게 동작
|
|
280
|
+
* @param eventName 이벤트 이름
|
|
281
|
+
* @param properties 이벤트 속성
|
|
282
|
+
*/
|
|
283
|
+
async trackWithAttributionId(eventName: string, properties?: EventProperties): Promise<void> {
|
|
284
|
+
// track() 메서드로 위임 (자동으로 Attribution ID 사용)
|
|
285
|
+
return this.track(eventName, properties);
|
|
286
|
+
}
|
|
192
287
|
|
|
193
288
|
/**
|
|
194
289
|
* 서버에 이벤트 전송
|
|
195
290
|
*/
|
|
196
291
|
private async sendEventToServer(eventData: any): Promise<void> {
|
|
292
|
+
const headers = {
|
|
293
|
+
...ApiHeaders.create(this._config!.apiKey),
|
|
294
|
+
'Content-Type': 'application/json',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (this._config?.debug) {
|
|
298
|
+
console.log('🔑 Request headers:', headers);
|
|
299
|
+
console.log('📡 Request URL:', endpoints.events.track());
|
|
300
|
+
console.log('📦 Request body:', eventData);
|
|
301
|
+
}
|
|
302
|
+
|
|
197
303
|
const response = await fetch(endpoints.events.track(), {
|
|
198
304
|
method: 'POST',
|
|
199
|
-
headers
|
|
200
|
-
|
|
201
|
-
'Content-Type': 'application/json'
|
|
202
|
-
},
|
|
203
|
-
body: JSON.stringify(eventData)
|
|
305
|
+
headers,
|
|
306
|
+
body: JSON.stringify(eventData),
|
|
204
307
|
});
|
|
205
308
|
|
|
206
309
|
if (!response.ok) {
|
|
310
|
+
const errorText = await response.text();
|
|
311
|
+
console.error('❌ API Error Response:', errorText);
|
|
207
312
|
throw new Error(`Event tracking failed: ${response.status} ${response.statusText}`);
|
|
208
313
|
}
|
|
209
314
|
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* 서버에 Attribution 등록
|
|
318
|
+
*/
|
|
319
|
+
private async sendAttributionToServer(data: { deviceId: string; referrerParams: Record<string, any> }): Promise<any> {
|
|
320
|
+
const headers = {
|
|
321
|
+
...ApiHeaders.create(this._config!.apiKey),
|
|
322
|
+
'Content-Type': 'application/json',
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (this._config?.debug) {
|
|
326
|
+
console.log('🔑 Attribution request headers:', headers);
|
|
327
|
+
console.log('📡 Attribution request URL:', endpoints.events.registerAttribution());
|
|
328
|
+
console.log('📦 Attribution request body:', data);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const response = await fetch(endpoints.events.registerAttribution(), {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers,
|
|
334
|
+
body: JSON.stringify(data),
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
if (!response.ok) {
|
|
338
|
+
const errorText = await response.text();
|
|
339
|
+
console.error('❌ API Error Response:', errorText);
|
|
340
|
+
throw new Error(`Attribution registration failed: ${response.status} ${response.statusText}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return response.json();
|
|
344
|
+
}
|
|
210
345
|
}
|