@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/dist/index.cjs.js CHANGED
@@ -319,6 +319,80 @@ class DOMUtils {
319
319
  }
320
320
  }
321
321
 
322
+ /**
323
+ * AdStage SDK - 버전 정보 유틸리티
324
+ */
325
+ // package.json에서 버전 정보 가져오기 (빌드 시 자동으로 교체됨)
326
+ const SDK_VERSION$1 = '"2.6.2"';
327
+ /**
328
+ * SDK 버전 정보 반환
329
+ */
330
+ function getSDKVersion() {
331
+ return SDK_VERSION$1;
332
+ }
333
+
334
+ /**
335
+ * AdStage SDK - 설정 유틸리티
336
+ * 전역 설정에 대한 접근 기능 제공
337
+ */
338
+ class ConfigUtils {
339
+ /**
340
+ * AdStage 전역 설정 반환
341
+ */
342
+ static getConfig() {
343
+ // AdStage 클래스 동적 임포트로 순환 참조 방지
344
+ try {
345
+ const { AdStage } = require('../core/adstage');
346
+ return AdStage.getConfig();
347
+ }
348
+ catch {
349
+ return null;
350
+ }
351
+ }
352
+ /**
353
+ * 고객 앱 버전 반환
354
+ */
355
+ static getAppVersion() {
356
+ const config = ConfigUtils.getConfig();
357
+ return config?.appVersion || '1.0.0';
358
+ }
359
+ /**
360
+ * 디버그 모드 여부 확인
361
+ */
362
+ static isDebugMode() {
363
+ const config = ConfigUtils.getConfig();
364
+ return config?.debug || false;
365
+ }
366
+ /**
367
+ * API 키 반환
368
+ */
369
+ static getApiKey() {
370
+ const config = ConfigUtils.getConfig();
371
+ return config?.apiKey;
372
+ }
373
+ /**
374
+ * 타임아웃 값 반환
375
+ */
376
+ static getTimeout() {
377
+ const config = ConfigUtils.getConfig();
378
+ return config?.timeout || 30000;
379
+ }
380
+ /**
381
+ * 플레이스홀더 모드 반환
382
+ */
383
+ static getPlaceholderMode() {
384
+ const config = ConfigUtils.getConfig();
385
+ return config?.placeholderMode || 'subtle';
386
+ }
387
+ /**
388
+ * 활성화된 모듈 목록 반환
389
+ */
390
+ static getEnabledModules() {
391
+ const config = ConfigUtils.getConfig();
392
+ return config?.modules || ['ads', 'events', 'config'];
393
+ }
394
+ }
395
+
322
396
  /**
323
397
  * 디바이스 정보 수집 클래스
324
398
  * - 브라우저 환경 정보 수집
@@ -353,15 +427,19 @@ class DeviceInfoCollector {
353
427
  return sessionId;
354
428
  }
355
429
  /**
356
- * 모바일 디바이스 여부 확인
430
+ * 모바일 디바이스 여부 확인 (SSR 안전)
357
431
  */
358
432
  static isMobile() {
433
+ if (!DOMUtils.isBrowser())
434
+ return false;
359
435
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
360
436
  }
361
437
  /**
362
- * 플랫폼 타입 반환 (서버 enum에 맞춤)
438
+ * 플랫폼 타입 반환 (서버 enum에 맞춤, SSR 안전)
363
439
  */
364
440
  static getPlatform() {
441
+ if (!DOMUtils.isBrowser())
442
+ return 'web';
365
443
  const userAgent = navigator.userAgent.toLowerCase();
366
444
  if (/iphone|ipad|ipod/.test(userAgent)) {
367
445
  return 'ios';
@@ -384,8 +462,8 @@ class DeviceInfoCollector {
384
462
  sessionId: DeviceInfoCollector.generateSessionId(),
385
463
  osVersion: DOMUtils.isBrowser() ? navigator.platform : 'SSR',
386
464
  deviceModel: DOMUtils.isBrowser() ? navigator.platform : 'SSR',
387
- appVersion: '1.0.0',
388
- sdkVersion: '1.0.0',
465
+ appVersion: ConfigUtils.getAppVersion(), // AdStage.init()에서 설정 또는 기본값 '1.0.0'
466
+ sdkVersion: getSDKVersion(), // package.json에서 동적 로드
389
467
  language: DOMUtils.isBrowser() ? (navigator.language || 'ko') : 'ko',
390
468
  country: 'KR', // 기본값
391
469
  ipAddress: '', // 서버에서 자동으로 설정됨
@@ -454,18 +532,6 @@ class ApiHeaders {
454
532
  }
455
533
  }
456
534
 
457
- /**
458
- * AdStage SDK - 버전 정보 유틸리티
459
- */
460
- // package.json에서 버전 정보 가져오기 (빌드 시 자동으로 교체됨)
461
- const SDK_VERSION$1 = '"2.6.1"';
462
- /**
463
- * SDK 버전 정보 반환
464
- */
465
- function getSDKVersion() {
466
- return SDK_VERSION$1;
467
- }
468
-
469
535
  /**
470
536
  * 광고 이벤트 추적 관리 클래스
471
537
  * - 광고 전용 이벤트 추적 및 전송
@@ -657,7 +723,6 @@ const API_PATHS = {
657
723
  /** 이벤트 관련 */
658
724
  events: {
659
725
  track: '/events/track',
660
- batch: '/events/batch'
661
726
  }
662
727
  };
663
728
  /**
@@ -678,7 +743,6 @@ class EndpointBuilder {
678
743
  */
679
744
  this.events = {
680
745
  track: () => `${this.baseUrl}${API_PATHS.events.track}`,
681
- batch: () => `${this.baseUrl}${API_PATHS.events.batch}`
682
746
  };
683
747
  // 기본값은 베타 환경 사용
684
748
  this.baseUrl = baseUrl || API_ENDPOINTS.production;
@@ -3868,31 +3932,358 @@ class ConfigModule {
3868
3932
  }
3869
3933
 
3870
3934
  /**
3871
- * AdStage SDK - Events 모듈 (기본 구조)
3872
- * 이벤트 추적 시스템 - Q1 2025 구현 예정
3935
+ * AdStage SDK - 이벤트용 디바이스 정보 수집기
3936
+ * Events API 스키마에 최적화된 디바이스 정보 수집
3937
+ * DeviceInfoCollector를 재사용하여 중복 제거
3938
+ */
3939
+ class EventDeviceCollector {
3940
+ /**
3941
+ * Events API용 디바이스 정보 반환
3942
+ * TrackEventDto.DeviceInfoInput 형태에 맞춤
3943
+ */
3944
+ static getDeviceInfo() {
3945
+ if (!DOMUtils.isBrowser()) {
3946
+ return {
3947
+ category: 'other',
3948
+ platform: 'SSR',
3949
+ model: 'SSR',
3950
+ appVersion: ConfigUtils.getAppVersion(), // AdStage.init()에서 설정 또는 기본값 '1.0.0'
3951
+ osVersion: 'SSR'
3952
+ };
3953
+ }
3954
+ const userAgent = navigator.userAgent.toLowerCase();
3955
+ let category = 'desktop';
3956
+ // 디바이스 카테고리 판별 (DeviceInfoCollector 재사용)
3957
+ if (/tablet|ipad/.test(userAgent)) {
3958
+ category = 'tablet';
3959
+ }
3960
+ else if (DeviceInfoCollector.isMobile()) {
3961
+ category = 'mobile';
3962
+ }
3963
+ // 플랫폼 정보 매핑 (Events API용)
3964
+ const platformType = DeviceInfoCollector.getPlatform();
3965
+ const platformString = EventDeviceCollector.mapPlatformForEvents(platformType);
3966
+ return {
3967
+ category,
3968
+ platform: platformString,
3969
+ model: navigator.platform,
3970
+ appVersion: ConfigUtils.getAppVersion(), // AdStage.init()에서 설정 또는 기본값 '1.0.0'
3971
+ osVersion: navigator.platform
3972
+ };
3973
+ }
3974
+ /**
3975
+ * 플랫폼 타입을 Events API용 문자열로 매핑
3976
+ */
3977
+ static mapPlatformForEvents(platformType) {
3978
+ switch (platformType) {
3979
+ case 'ios': return 'ios';
3980
+ case 'android': return 'android';
3981
+ case 'web': return 'mobile-web';
3982
+ case 'desktop': return 'desktop-web';
3983
+ default: return 'unknown';
3984
+ }
3985
+ }
3986
+ /**
3987
+ * 디바이스 상세 정보 (디버깅용)
3988
+ */
3989
+ static getDetailedInfo() {
3990
+ if (!DOMUtils.isBrowser()) {
3991
+ return {
3992
+ userAgent: 'SSR',
3993
+ language: 'ko-KR',
3994
+ platform: 'SSR',
3995
+ cookieEnabled: false,
3996
+ onLine: true,
3997
+ screenResolution: '0x0',
3998
+ viewportSize: '0x0'
3999
+ };
4000
+ }
4001
+ const viewportInfo = DOMUtils.getViewportInfo();
4002
+ return {
4003
+ userAgent: navigator.userAgent,
4004
+ language: navigator.language || 'ko-KR',
4005
+ platform: navigator.platform,
4006
+ cookieEnabled: navigator.cookieEnabled,
4007
+ onLine: navigator.onLine,
4008
+ screenResolution: `${screen.width}x${screen.height}`,
4009
+ viewportSize: `${viewportInfo.width}x${viewportInfo.height}`
4010
+ };
4011
+ }
4012
+ }
4013
+
4014
+ /**
4015
+ * AdStage SDK - 이벤트용 사용자 정보 수집기
4016
+ * Events API 스키마에 최적화된 사용자 정보 수집
4017
+ */
4018
+ class EventUserCollector {
4019
+ /**
4020
+ * Events API용 사용자 정보 반환
4021
+ * TrackEventDto.UserAttributesInput 형태에 맞춤
4022
+ */
4023
+ static getUserInfo() {
4024
+ const baseInfo = EventUserCollector.getBaseUserInfo();
4025
+ // 설정된 사용자 속성과 병합
4026
+ return {
4027
+ ...baseInfo,
4028
+ ...EventUserCollector._userProperties
4029
+ };
4030
+ }
4031
+ /**
4032
+ * 기본 사용자 정보 수집 (브라우저 기반)
4033
+ */
4034
+ static getBaseUserInfo() {
4035
+ if (!DOMUtils.isBrowser()) {
4036
+ return {
4037
+ language: 'ko-KR',
4038
+ country: 'KR'
4039
+ };
4040
+ }
4041
+ // 브라우저 언어 설정에서 국가 추출
4042
+ const language = navigator.language || 'ko-KR';
4043
+ const country = EventUserCollector.extractCountryFromLanguage(language);
4044
+ return {
4045
+ language,
4046
+ country
4047
+ };
4048
+ }
4049
+ /**
4050
+ * 언어 코드에서 국가 추출
4051
+ */
4052
+ static extractCountryFromLanguage(language) {
4053
+ const countryMap = {
4054
+ 'ko': 'KR',
4055
+ 'ko-KR': 'KR',
4056
+ 'en': 'US',
4057
+ 'en-US': 'US',
4058
+ 'en-GB': 'GB',
4059
+ 'ja': 'JP',
4060
+ 'ja-JP': 'JP',
4061
+ 'zh': 'CN',
4062
+ 'zh-CN': 'CN',
4063
+ 'zh-TW': 'TW',
4064
+ 'de': 'DE',
4065
+ 'de-DE': 'DE',
4066
+ 'fr': 'FR',
4067
+ 'fr-FR': 'FR'
4068
+ };
4069
+ // 정확한 매칭 시도
4070
+ if (countryMap[language]) {
4071
+ return countryMap[language];
4072
+ }
4073
+ // 언어 코드만 추출해서 매칭
4074
+ const langCode = language.split('-')[0];
4075
+ return countryMap[langCode] || 'KR';
4076
+ }
4077
+ /**
4078
+ * 사용자 속성 설정
4079
+ */
4080
+ static setUserProperties(properties) {
4081
+ EventUserCollector._userProperties = {
4082
+ ...EventUserCollector._userProperties,
4083
+ ...properties
4084
+ };
4085
+ }
4086
+ /**
4087
+ * 특정 사용자 속성 설정
4088
+ */
4089
+ static setUserProperty(key, value) {
4090
+ EventUserCollector._userProperties[key] = value;
4091
+ }
4092
+ /**
4093
+ * 사용자 속성 초기화
4094
+ */
4095
+ static clearUserProperties() {
4096
+ EventUserCollector._userProperties = {};
4097
+ }
4098
+ /**
4099
+ * 현재 설정된 사용자 속성 반환
4100
+ */
4101
+ static getCurrentUserProperties() {
4102
+ return { ...EventUserCollector._userProperties };
4103
+ }
4104
+ /**
4105
+ * 사용자 지역 정보 추정 (타임존 기반)
4106
+ */
4107
+ static estimateLocation() {
4108
+ if (!DOMUtils.isBrowser()) {
4109
+ return {
4110
+ timezone: 'UTC',
4111
+ estimatedCountry: 'KR'
4112
+ };
4113
+ }
4114
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
4115
+ // 타임존 기반 국가 추정
4116
+ const timezoneCountryMap = {
4117
+ 'Asia/Seoul': 'KR',
4118
+ 'Asia/Tokyo': 'JP',
4119
+ 'Asia/Shanghai': 'CN',
4120
+ 'Asia/Hong_Kong': 'HK',
4121
+ 'Asia/Taipei': 'TW',
4122
+ 'America/New_York': 'US',
4123
+ 'America/Los_Angeles': 'US',
4124
+ 'Europe/London': 'GB',
4125
+ 'Europe/Berlin': 'DE',
4126
+ 'Europe/Paris': 'FR'
4127
+ };
4128
+ return {
4129
+ timezone,
4130
+ estimatedCountry: timezoneCountryMap[timezone] || 'KR'
4131
+ };
4132
+ }
4133
+ }
4134
+ EventUserCollector._userProperties = {};
4135
+
4136
+ /**
4137
+ * AdStage SDK - 이벤트용 세션 관리자
4138
+ * 세션 ID 생성 및 사용자 ID 관리
4139
+ */
4140
+ class EventSessionManager {
4141
+ /**
4142
+ * 세션 ID 생성 및 반환 (SSR 안전)
4143
+ */
4144
+ static getSessionId() {
4145
+ if (!DOMUtils.isBrowser()) {
4146
+ return 'ssr_session_' + Date.now();
4147
+ }
4148
+ const stored = sessionStorage.getItem('adstage_session_id');
4149
+ if (stored) {
4150
+ return stored;
4151
+ }
4152
+ const sessionId = 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
4153
+ sessionStorage.setItem('adstage_session_id', sessionId);
4154
+ // 세션 시작 시간 기록
4155
+ if (!EventSessionManager._sessionStartTime) {
4156
+ EventSessionManager._sessionStartTime = Date.now();
4157
+ if (DOMUtils.isBrowser()) {
4158
+ sessionStorage.setItem('adstage_session_start', String(EventSessionManager._sessionStartTime));
4159
+ }
4160
+ }
4161
+ return sessionId;
4162
+ }
4163
+ /**
4164
+ * 사용자 ID 설정
4165
+ */
4166
+ static setUserId(userId) {
4167
+ EventSessionManager._userId = userId;
4168
+ if (DOMUtils.isBrowser()) {
4169
+ localStorage.setItem('adstage_user_id', userId);
4170
+ }
4171
+ }
4172
+ /**
4173
+ * 현재 사용자 ID 반환
4174
+ */
4175
+ static getUserId() {
4176
+ // 메모리에 있는 값 우선 반환
4177
+ if (EventSessionManager._userId) {
4178
+ return EventSessionManager._userId;
4179
+ }
4180
+ // 로컬 스토리지에서 복원
4181
+ if (DOMUtils.isBrowser()) {
4182
+ const stored = localStorage.getItem('adstage_user_id');
4183
+ if (stored) {
4184
+ EventSessionManager._userId = stored;
4185
+ return stored;
4186
+ }
4187
+ }
4188
+ return undefined;
4189
+ }
4190
+ /**
4191
+ * 사용자 ID 제거
4192
+ */
4193
+ static clearUserId() {
4194
+ EventSessionManager._userId = undefined;
4195
+ if (DOMUtils.isBrowser()) {
4196
+ localStorage.removeItem('adstage_user_id');
4197
+ }
4198
+ }
4199
+ /**
4200
+ * 세션 정보 전체 반환
4201
+ */
4202
+ static getSessionInfo() {
4203
+ const sessionId = EventSessionManager.getSessionId();
4204
+ const userId = EventSessionManager.getUserId();
4205
+ const isNewSession = EventSessionManager.isNewSession();
4206
+ const sessionDuration = EventSessionManager.getSessionDuration();
4207
+ return {
4208
+ sessionId,
4209
+ userId,
4210
+ sessionDuration,
4211
+ isNewSession
4212
+ };
4213
+ }
4214
+ /**
4215
+ * 새 세션인지 확인
4216
+ */
4217
+ static isNewSession() {
4218
+ if (!DOMUtils.isBrowser())
4219
+ return true;
4220
+ const sessionId = sessionStorage.getItem('adstage_session_id');
4221
+ const sessionStart = sessionStorage.getItem('adstage_session_start');
4222
+ return !sessionId || !sessionStart;
4223
+ }
4224
+ /**
4225
+ * 세션 지속 시간 반환 (ms)
4226
+ */
4227
+ static getSessionDuration() {
4228
+ if (!DOMUtils.isBrowser())
4229
+ return 0;
4230
+ const sessionStart = sessionStorage.getItem('adstage_session_start');
4231
+ if (!sessionStart)
4232
+ return 0;
4233
+ return Date.now() - parseInt(sessionStart, 10);
4234
+ }
4235
+ /**
4236
+ * 세션 새로고침 (새로운 세션 ID 생성)
4237
+ */
4238
+ static refreshSession() {
4239
+ if (DOMUtils.isBrowser()) {
4240
+ sessionStorage.removeItem('adstage_session_id');
4241
+ sessionStorage.removeItem('adstage_session_start');
4242
+ }
4243
+ EventSessionManager._sessionStartTime = undefined;
4244
+ return EventSessionManager.getSessionId();
4245
+ }
4246
+ /**
4247
+ * 세션 만료 확인 (24시간 기준)
4248
+ */
4249
+ static isSessionExpired(maxDurationHours = 24) {
4250
+ const duration = EventSessionManager.getSessionDuration();
4251
+ const maxDuration = maxDurationHours * 60 * 60 * 1000; // ms 변환
4252
+ return duration > maxDuration;
4253
+ }
4254
+ /**
4255
+ * 세션 통계 반환 (디버깅용)
4256
+ */
4257
+ static getSessionStats() {
4258
+ const sessionId = EventSessionManager.getSessionId();
4259
+ const userId = EventSessionManager.getUserId();
4260
+ const duration = EventSessionManager.getSessionDuration();
4261
+ const isExpired = EventSessionManager.isSessionExpired();
4262
+ const isNewSession = EventSessionManager.isNewSession();
4263
+ let startTime;
4264
+ if (DOMUtils.isBrowser()) {
4265
+ const stored = sessionStorage.getItem('adstage_session_start');
4266
+ startTime = stored ? parseInt(stored, 10) : undefined;
4267
+ }
4268
+ return {
4269
+ sessionId,
4270
+ userId,
4271
+ startTime,
4272
+ duration,
4273
+ isExpired,
4274
+ isNewSession
4275
+ };
4276
+ }
4277
+ }
4278
+
4279
+ /**
4280
+ * AdStage SDK - Events 모듈
4281
+ * 이벤트 추적 시스템
3873
4282
  */
3874
4283
  class EventsModule {
3875
4284
  constructor() {
3876
4285
  this._isReady = false;
3877
4286
  this._config = null;
3878
- // === 배치 처리 (향후 구현) ===
3879
- this.batch = {
3880
- start: () => {
3881
- console.log('🚧 [TODO] Batch events start');
3882
- },
3883
- add: (eventName, properties) => {
3884
- console.log('🚧 [TODO] Batch events add:', { eventName, properties });
3885
- },
3886
- flush: async () => {
3887
- console.log('🚧 [TODO] Batch events flush');
3888
- }
3889
- };
3890
- // === 실시간 이벤트 (향후 구현) ===
3891
- this.realtime = {
3892
- track: async (eventName, properties) => {
3893
- console.log('🚧 [TODO] Realtime event tracking:', { eventName, properties });
3894
- }
3895
- };
3896
4287
  }
3897
4288
  /**
3898
4289
  * Events 모듈 초기화 (동기)
@@ -3901,7 +4292,7 @@ class EventsModule {
3901
4292
  this._config = config;
3902
4293
  this._isReady = true;
3903
4294
  if (config.debug) {
3904
- console.log('📊 Events module initialized (sync mode)');
4295
+ console.log('📊 Events module initialized');
3905
4296
  }
3906
4297
  }
3907
4298
  /**
@@ -3916,38 +4307,101 @@ class EventsModule {
3916
4307
  getConfig() {
3917
4308
  return this._config;
3918
4309
  }
3919
- // === 향후 구현 예정 메소드들 ===
3920
4310
  /**
3921
- * 커스텀 이벤트 추적
3922
- * @example AdStage.events.track('page_view', { page: '/products' })
4311
+ * 사용자 ID 설정
3923
4312
  */
3924
- async track(eventName, properties) {
3925
- console.log('🚧 [TODO] Event tracking:', { eventName, properties });
3926
- // TODO: Q1 2025 구현 예정
4313
+ setUserId(userId) {
4314
+ EventSessionManager.setUserId(userId);
4315
+ if (this._config?.debug) {
4316
+ console.log('👤 User ID set:', userId);
4317
+ }
3927
4318
  }
3928
4319
  /**
3929
- * 페이지 이벤트
3930
- * @example AdStage.events.pageView({ page: '/home', title: 'Homepage' })
4320
+ * 현재 사용자 ID 반환
3931
4321
  */
3932
- async pageView(pageData) {
3933
- console.log('🚧 [TODO] Page view tracking:', pageData);
3934
- // TODO: Q1 2025 구현 예정
4322
+ getUserId() {
4323
+ return EventSessionManager.getUserId();
4324
+ }
4325
+ /**
4326
+ * 이벤트 추적
4327
+ * @param eventName 이벤트 이름
4328
+ * @param properties 이벤트 속성
4329
+ */
4330
+ async track(eventName, properties) {
4331
+ if (!this._isReady) {
4332
+ console.warn('Events module not initialized. Call AdStage.init() first.');
4333
+ return;
4334
+ }
4335
+ if (!this._config?.apiKey) {
4336
+ console.warn('API key not configured for event tracking.');
4337
+ return;
4338
+ }
4339
+ try {
4340
+ const eventData = {
4341
+ eventName,
4342
+ userId: EventSessionManager.getUserId(),
4343
+ sessionId: EventSessionManager.getSessionId(),
4344
+ device: EventDeviceCollector.getDeviceInfo(),
4345
+ user: EventUserCollector.getUserInfo(),
4346
+ params: properties || {}
4347
+ };
4348
+ await this.sendEventToServer(eventData);
4349
+ if (this._config.debug) {
4350
+ console.log('✅ Event tracked:', eventName, properties);
4351
+ }
4352
+ }
4353
+ catch (error) {
4354
+ console.error('❌ Failed to track event:', error);
4355
+ if (this._config.debug) {
4356
+ console.error('Event data:', { eventName, properties });
4357
+ }
4358
+ }
3935
4359
  }
3936
4360
  /**
3937
- * 사용자 액션 이벤트
3938
- * @example AdStage.events.userAction('button_click', { button_id: 'cta' })
4361
+ * 페이지 이벤트 (track의 편의 메소드)
3939
4362
  */
3940
- async userAction(actionType, metadata) {
3941
- console.log('🚧 [TODO] User action tracking:', { actionType, metadata });
3942
- // TODO: Q1 2025 구현 예정
4363
+ async pageView(pageData) {
4364
+ const properties = {};
4365
+ if (pageData?.page)
4366
+ properties.page = pageData.page;
4367
+ if (pageData?.title)
4368
+ properties.title = pageData.title;
4369
+ if (pageData?.category)
4370
+ properties.category = pageData.category;
4371
+ // pageData의 다른 속성들도 포함
4372
+ if (pageData) {
4373
+ Object.keys(pageData).forEach(key => {
4374
+ if (key !== 'page' && key !== 'title' && key !== 'category') {
4375
+ properties[key] = pageData[key];
4376
+ }
4377
+ });
4378
+ }
4379
+ // 현재 페이지 정보 자동 수집
4380
+ if (typeof window !== 'undefined') {
4381
+ if (!properties.page)
4382
+ properties.page = window.location.pathname;
4383
+ if (!properties.title)
4384
+ properties.title = document.title;
4385
+ properties.url = window.location.href;
4386
+ properties.referrer = document.referrer;
4387
+ }
4388
+ await this.track('page_view', properties);
3943
4389
  }
3944
4390
  /**
3945
- * 컨버전 이벤트
3946
- * @example AdStage.events.conversion({ type: 'purchase', value: 99.99 })
4391
+ * 서버에 이벤트 전송
3947
4392
  */
3948
- async conversion(conversionData) {
3949
- console.log('🚧 [TODO] Conversion tracking:', conversionData);
3950
- // TODO: Q1 2025 구현 예정
4393
+ async sendEventToServer(eventData) {
4394
+ const response = await fetch(endpoints.events.track(), {
4395
+ method: 'POST',
4396
+ headers: {
4397
+ ...ApiHeaders.create(this._config.apiKey),
4398
+ 'Content-Type': 'application/json'
4399
+ },
4400
+ body: JSON.stringify(eventData)
4401
+ });
4402
+ if (!response.ok) {
4403
+ throw new Error(`Event tracking failed: ${response.status} ${response.statusText}`);
4404
+ }
3951
4405
  }
3952
4406
  }
3953
4407
 
@@ -4080,6 +4534,68 @@ AdStage.debug = {
4080
4534
  }
4081
4535
  };
4082
4536
 
4537
+ /**
4538
+ * AdStage SDK - 전역 이벤트 함수들
4539
+ * Firebase Analytics와 유사한 간단한 API 제공
4540
+ */
4541
+ /**
4542
+ * 이벤트 추적 (메인 함수)
4543
+ * @param eventName 이벤트 이름
4544
+ * @param properties 이벤트 속성
4545
+ *
4546
+ * @example
4547
+ * track('purchase', {
4548
+ * transaction_id: 'T123',
4549
+ * value: 99.99,
4550
+ * currency: 'USD'
4551
+ * });
4552
+ */
4553
+ function track(eventName, properties) {
4554
+ if (!AdStage.isReady()) {
4555
+ console.warn('AdStage not initialized. Call AdStage.init() first.');
4556
+ return Promise.resolve();
4557
+ }
4558
+ return AdStage.events.track(eventName, properties);
4559
+ }
4560
+ /**
4561
+ * 페이지 뷰 추적
4562
+ * @param pageData 페이지 정보
4563
+ *
4564
+ * @example
4565
+ * pageView({ page: '/products', title: 'Products Page' });
4566
+ */
4567
+ function pageView(pageData) {
4568
+ if (!AdStage.isReady()) {
4569
+ console.warn('AdStage not initialized. Call AdStage.init() first.');
4570
+ return Promise.resolve();
4571
+ }
4572
+ return AdStage.events.pageView(pageData);
4573
+ }
4574
+ /**
4575
+ * 사용자 ID 설정
4576
+ * @param userId 사용자 ID
4577
+ *
4578
+ * @example
4579
+ * setUserId('user123');
4580
+ */
4581
+ function setUserId(userId) {
4582
+ if (!AdStage.isReady()) {
4583
+ console.warn('AdStage not initialized. Call AdStage.init() first.');
4584
+ return;
4585
+ }
4586
+ AdStage.events.setUserId(userId);
4587
+ }
4588
+ /**
4589
+ * 현재 사용자 ID 반환
4590
+ */
4591
+ function getUserId() {
4592
+ if (!AdStage.isReady()) {
4593
+ console.warn('AdStage not initialized. Call AdStage.init() first.');
4594
+ return undefined;
4595
+ }
4596
+ return AdStage.events.getUserId();
4597
+ }
4598
+
4083
4599
  const AdStageContext = react.createContext(null);
4084
4600
  function AdStageProvider({ children, config }) {
4085
4601
  const [isInitialized, setIsInitialized] = react.useState(false);
@@ -4174,5 +4690,9 @@ exports.AdStage = AdStage;
4174
4690
  exports.AdStageProvider = AdStageProvider;
4175
4691
  exports.SDK_VERSION = SDK_VERSION;
4176
4692
  exports.SUPPORTED_MODULES = SUPPORTED_MODULES;
4693
+ exports.getUserId = getUserId;
4694
+ exports.pageView = pageView;
4695
+ exports.setUserId = setUserId;
4696
+ exports.track = track;
4177
4697
  exports.useAdStageContext = useAdStageContext;
4178
4698
  exports.useAdStageInstance = useAdStageInstance;