@adstage/web-sdk 2.6.1 → 2.6.3

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