@adstage/web-sdk 1.3.3 → 1.4.0
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 +178 -35
- package/dist/index.cjs.js +810 -481
- package/dist/index.d.ts +286 -97
- package/dist/index.esm.js +794 -457
- package/dist/index.standalone.js +794 -457
- package/package.json +2 -2
- package/src/constants/endpoints.ts +93 -0
- package/src/core/AdStage.ts +128 -0
- package/src/index.ts +14 -413
- package/src/managers/{slider-manager.ts → carousel-slider-manager.ts} +9 -8
- package/src/managers/event-tracker.ts +2 -4
- package/src/managers/{fade-slider-manager.ts → text-transition-manager.ts} +7 -7
- package/src/modules/ads/AdsModule.ts +525 -0
- package/src/modules/config/ConfigModule.ts +124 -0
- package/src/modules/deeplinks/DeeplinksModule.ts +0 -0
- package/src/modules/events/EventsModule.ts +106 -0
- package/src/types/config.ts +74 -3
- package/src/types/index.ts +2 -1
- package/src/utils/api-headers.ts +52 -0
- package/src/utils/dom-utils.ts +93 -0
- package/examples/README.md +0 -33
- package/examples/banner-ads.html +0 -512
- package/examples/index.html +0 -338
- package/examples/native-ads.html +0 -634
- package/examples/react-app/README.md +0 -70
- package/examples/react-app/index.html +0 -13
- package/examples/react-app/package-lock.json +0 -3042
- package/examples/react-app/package.json +0 -26
- package/examples/react-app/pnpm-lock.yaml +0 -1857
- package/examples/react-app/public/index.standalone.js +0 -2331
- package/examples/react-app/src/App.tsx +0 -226
- package/examples/react-app/src/index.css +0 -37
- package/examples/react-app/src/main.tsx +0 -10
- package/examples/react-app/tsconfig.json +0 -25
- package/examples/react-app/tsconfig.node.json +0 -10
- package/examples/react-app/vite.config.ts +0 -15
- package/examples/react-nextjs/app/globals.css +0 -200
- package/examples/react-nextjs/app/layout.tsx +0 -27
- package/examples/react-nextjs/app/page.tsx +0 -258
- package/examples/react-nextjs/next.config.js +0 -9
- package/examples/react-nextjs/package.json +0 -22
- package/examples/react-nextjs/pnpm-lock.yaml +0 -343
- package/examples/react-nextjs/tsconfig.json +0 -34
- package/examples/text-ads.html +0 -597
- package/examples/video-ads.html +0 -739
- package/src/react/components/AdErrorBoundary.tsx +0 -75
- package/src/react/components/AdSlot.tsx +0 -144
- package/src/react/components/BannerAd.tsx +0 -24
- package/src/react/components/InterstitialAd.tsx +0 -24
- package/src/react/components/NativeAd.tsx +0 -24
- package/src/react/components/TextAd.tsx +0 -24
- package/src/react/components/VideoAd.tsx +0 -24
- package/src/react/components/index.ts +0 -8
- package/src/react/hooks/index.ts +0 -4
- package/src/react/hooks/useAdSlot.ts +0 -83
- package/src/react/hooks/useAdStage.ts +0 -14
- package/src/react/hooks/useAdTracking.ts +0 -61
- package/src/react/index.ts +0 -4
- package/src/react/providers/AdStageProvider.tsx +0 -86
- package/src/react/providers/index.ts +0 -2
- package/src/utils/sdk-standalone.ts +0 -155
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adstage/web-sdk",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AdStage Web SDK for displaying advertisements",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "AdStage Web SDK for displaying advertisements with auto DOM-ready detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs.js",
|
|
7
7
|
"module": "dist/index.esm.js",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdStage SDK 엔드포인트 상수 관리
|
|
3
|
+
* 모든 API URL을 중앙에서 관리
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 환경별 API 엔드포인트 (읽기 전용)
|
|
8
|
+
*/
|
|
9
|
+
export const API_ENDPOINTS = {
|
|
10
|
+
/** 프로덕션 환경 */
|
|
11
|
+
production: 'https://api.adstage.io',
|
|
12
|
+
|
|
13
|
+
/** 베타 환경 (기본값) */
|
|
14
|
+
beta: 'https://beta-api.adstage.app'
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 기본 엔드포인트 (베타 환경)
|
|
19
|
+
*/
|
|
20
|
+
export const DEFAULT_API_ENDPOINT = API_ENDPOINTS.beta;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* API 경로 상수
|
|
24
|
+
*/
|
|
25
|
+
export const API_PATHS = {
|
|
26
|
+
/** 광고 관련 */
|
|
27
|
+
advertisements: {
|
|
28
|
+
list: '/advertisements/list',
|
|
29
|
+
events: '/advertisements/events'
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/** 이벤트 관련 */
|
|
33
|
+
events: {
|
|
34
|
+
track: '/events/track',
|
|
35
|
+
batch: '/events/batch'
|
|
36
|
+
}
|
|
37
|
+
} as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 완전한 API URL 생성 헬퍼
|
|
41
|
+
*/
|
|
42
|
+
export class EndpointBuilder {
|
|
43
|
+
private baseUrl: string;
|
|
44
|
+
|
|
45
|
+
constructor(baseUrl?: string) {
|
|
46
|
+
// 기본값은 베타 환경 사용
|
|
47
|
+
this.baseUrl = baseUrl || API_ENDPOINTS.beta;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 기본 URL 변경
|
|
52
|
+
*/
|
|
53
|
+
setBaseUrl(url: string): void {
|
|
54
|
+
this.baseUrl = url;
|
|
55
|
+
console.log('🔄 API endpoint changed:', url);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 기본 URL 반환
|
|
60
|
+
*/
|
|
61
|
+
getBaseUrl(): string {
|
|
62
|
+
return this.baseUrl;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 광고 엔드포인트
|
|
67
|
+
*/
|
|
68
|
+
advertisements = {
|
|
69
|
+
list: () => `${this.baseUrl}${API_PATHS.advertisements.list}`,
|
|
70
|
+
events: (adId: string, eventType: string) =>
|
|
71
|
+
`${this.baseUrl}${API_PATHS.advertisements.events}/${adId}/${eventType}`
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 이벤트 엔드포인트
|
|
76
|
+
*/
|
|
77
|
+
events = {
|
|
78
|
+
track: () => `${this.baseUrl}${API_PATHS.events.track}`,
|
|
79
|
+
batch: () => `${this.baseUrl}${API_PATHS.events.batch}`
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 커스텀 경로 생성
|
|
84
|
+
*/
|
|
85
|
+
custom(path: string): string {
|
|
86
|
+
return `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 전역 엔드포인트 빌더 인스턴스 (기본: 베타 환경)
|
|
92
|
+
*/
|
|
93
|
+
export const endpoints = new EndpointBuilder();
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdStage SDK - 메인 네임스페이스 클래스
|
|
3
|
+
* v2.0.0 - 확장 가능한 모듈 아키텍처
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AdStageConfig, ModuleName } from '../types/config';
|
|
7
|
+
import { AdsModule } from '../modules/ads/AdsModule';
|
|
8
|
+
import { ConfigModule } from '../modules/config/ConfigModule';
|
|
9
|
+
import { EventsModule } from '../modules/events/EventsModule';
|
|
10
|
+
|
|
11
|
+
export class AdStage {
|
|
12
|
+
private static instance: AdStage;
|
|
13
|
+
private _isInitialized = false;
|
|
14
|
+
private _config: AdStageConfig | null = null;
|
|
15
|
+
|
|
16
|
+
// 모듈 인스턴스들
|
|
17
|
+
public readonly ads: AdsModule;
|
|
18
|
+
public readonly config: ConfigModule;
|
|
19
|
+
public readonly events: EventsModule;
|
|
20
|
+
|
|
21
|
+
private constructor() {
|
|
22
|
+
// 모듈 초기화 (ads, config는 완전 구현, events는 기본 구조)
|
|
23
|
+
this.config = new ConfigModule();
|
|
24
|
+
this.ads = new AdsModule();
|
|
25
|
+
this.events = new EventsModule();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* AdStage SDK 초기화 (동기)
|
|
30
|
+
*/
|
|
31
|
+
public static init(config: AdStageConfig): void {
|
|
32
|
+
if (!AdStage.instance) {
|
|
33
|
+
AdStage.instance = new AdStage();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const instance = AdStage.instance;
|
|
37
|
+
|
|
38
|
+
// 설정 검증
|
|
39
|
+
if (!config.apiKey) {
|
|
40
|
+
throw new Error('API key is required for AdStage initialization');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 설정 저장 (서버 검증 없음)
|
|
44
|
+
instance._config = {
|
|
45
|
+
timeout: 30000,
|
|
46
|
+
debug: false,
|
|
47
|
+
modules: ['ads', 'events', 'config'],
|
|
48
|
+
validateOnInit: false,
|
|
49
|
+
fallbackMode: true,
|
|
50
|
+
offlineMode: false,
|
|
51
|
+
productionMode: false,
|
|
52
|
+
...config
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// 모듈 동기 초기화
|
|
56
|
+
const enabledModules = instance._config.modules || ['ads', 'events', 'config'];
|
|
57
|
+
|
|
58
|
+
for (const moduleName of enabledModules) {
|
|
59
|
+
const module = instance[moduleName as keyof AdStage] as any;
|
|
60
|
+
if (module && typeof module.init === 'function') {
|
|
61
|
+
module.init(instance._config);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
instance._isInitialized = true;
|
|
66
|
+
|
|
67
|
+
if (config.debug) {
|
|
68
|
+
console.log('🚀 AdStage SDK initialized (sync mode)', {
|
|
69
|
+
version: '2.0.0',
|
|
70
|
+
modules: enabledModules,
|
|
71
|
+
apiKey: config.apiKey.substring(0, 8) + '...',
|
|
72
|
+
mode: config.productionMode ? 'production' : 'development'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* SDK 초기화 상태 확인
|
|
79
|
+
*/
|
|
80
|
+
public static isReady(): boolean {
|
|
81
|
+
return AdStage.instance?._isInitialized || false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 현재 설정 반환
|
|
86
|
+
*/
|
|
87
|
+
public static getConfig(): AdStageConfig | null {
|
|
88
|
+
return AdStage.instance?._config || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* SDK 인스턴스 반환 (공개 메소드로 변경)
|
|
93
|
+
*/
|
|
94
|
+
public static getInstance(): AdStage {
|
|
95
|
+
if (!AdStage.instance) {
|
|
96
|
+
throw new Error('AdStage not initialized. Call AdStage.init() first.');
|
|
97
|
+
}
|
|
98
|
+
return AdStage.instance;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 편의성을 위한 정적 모듈 접근자들
|
|
103
|
+
*/
|
|
104
|
+
public static get ads() {
|
|
105
|
+
return AdStage.getInstance().ads;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static get events() {
|
|
109
|
+
return AdStage.getInstance().events;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public static get config() {
|
|
113
|
+
return AdStage.getInstance().config;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* SDK 리셋 (테스트용)
|
|
118
|
+
*/
|
|
119
|
+
public static reset(): void {
|
|
120
|
+
if (AdStage.instance) {
|
|
121
|
+
AdStage.instance._isInitialized = false;
|
|
122
|
+
AdStage.instance._config = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 전역 네임스페이스로 내보내기
|
|
128
|
+
export default AdStage;
|
package/src/index.ts
CHANGED
|
@@ -1,420 +1,21 @@
|
|
|
1
|
-
import { AdType, AdEventType } from './types/advertisement';
|
|
2
|
-
import type { AdSlot, Advertisement } from './types/advertisement';
|
|
3
|
-
import { AdRendererFactory } from './renderers';
|
|
4
|
-
import { SliderManager } from './managers/slider-manager';
|
|
5
|
-
import { FadeSliderManager } from './managers/fade-slider-manager';
|
|
6
|
-
import { ImpressionTracker } from './managers/impression-tracker';
|
|
7
|
-
import { EventTracker } from './managers/event-tracker';
|
|
8
|
-
import { DeviceInfoCollector } from './managers/device-info-collector';
|
|
9
|
-
import { SDKUtils } from './utils/sdk-utils';
|
|
10
|
-
import { DOMUtils } from './utils/dom-utils';
|
|
11
|
-
|
|
12
1
|
/**
|
|
13
|
-
* AdStage SDK
|
|
2
|
+
* AdStage Web SDK
|
|
3
|
+
* 네임스페이스 아키텍처 기반 SDK
|
|
14
4
|
*/
|
|
15
|
-
export interface AdStageConfig {
|
|
16
|
-
apiKey: string;
|
|
17
|
-
debug?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* AdStage SDK 메인 클래스
|
|
22
|
-
* - 간단한 API Key 기반 초기화
|
|
23
|
-
* - 광고 슬롯 자동 관리
|
|
24
|
-
* - 이벤트 자동 추적
|
|
25
|
-
*/
|
|
26
|
-
export class AdStageSDK {
|
|
27
|
-
private static instance: AdStageSDK | null = null;
|
|
28
|
-
|
|
29
|
-
private config: AdStageConfig;
|
|
30
|
-
private baseUrl = 'https://beta-api.adstage.app';
|
|
31
|
-
private slots = new Map<string, AdSlot>();
|
|
32
|
-
private initialized = false;
|
|
33
|
-
private eventTracker: EventTracker;
|
|
34
|
-
|
|
35
|
-
constructor(config: AdStageConfig) {
|
|
36
|
-
this.config = {
|
|
37
|
-
debug: false,
|
|
38
|
-
...config,
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
this.eventTracker = new EventTracker(
|
|
42
|
-
this.baseUrl,
|
|
43
|
-
this.config.apiKey,
|
|
44
|
-
this.config.debug || false,
|
|
45
|
-
this.slots
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* SDK 초기화 및 인스턴스 반환
|
|
51
|
-
*/
|
|
52
|
-
static init(config: AdStageConfig): AdStageSDK {
|
|
53
|
-
if (!AdStageSDK.instance) {
|
|
54
|
-
AdStageSDK.instance = new AdStageSDK(config);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return AdStageSDK.instance;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* SDK 인스턴스 반환 (이미 초기화된 경우)
|
|
62
|
-
*/
|
|
63
|
-
static getInstance(): AdStageSDK {
|
|
64
|
-
if (!AdStageSDK.instance) {
|
|
65
|
-
throw new Error('AdStageSDK must be initialized first. Call AdStageSDK.init(config)');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return AdStageSDK.instance;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 광고 슬롯 생성 및 로드
|
|
73
|
-
*/
|
|
74
|
-
async createSlot(
|
|
75
|
-
id: string,
|
|
76
|
-
containerId: string,
|
|
77
|
-
adType: AdType = AdType.BANNER,
|
|
78
|
-
options?: {
|
|
79
|
-
width?: number | string; // 숫자(px) 또는 문자열(%, px 등) 지원
|
|
80
|
-
height?: number | string; // 숫자(px) 또는 문자열(%, px 등) 지원
|
|
81
|
-
language?: string;
|
|
82
|
-
deviceType?: string;
|
|
83
|
-
country?: string;
|
|
84
|
-
autoSlideInterval?: number; // 자동 슬라이드 간격 (초), 기본값: 3초
|
|
85
|
-
sliderEffect?: 'slide' | 'fade'; // 슬라이더 효과 선택 (기본값: slide)
|
|
86
|
-
}
|
|
87
|
-
): Promise<void> {
|
|
88
|
-
const container = DOMUtils.safeGetElementById(containerId);
|
|
89
|
-
if (!container) {
|
|
90
|
-
if (DOMUtils.canUseDOM()) {
|
|
91
|
-
console.error(`Container with ID "${containerId}" not found`);
|
|
92
|
-
}
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const slot: AdSlot = {
|
|
97
|
-
id,
|
|
98
|
-
containerId,
|
|
99
|
-
adType,
|
|
100
|
-
width: options?.width || 0, // 문자열도 지원
|
|
101
|
-
height: options?.height || 0, // 문자열도 지원
|
|
102
|
-
isLoaded: false,
|
|
103
|
-
isVisible: false,
|
|
104
|
-
refreshRate: 0,
|
|
105
|
-
lazyLoad: false,
|
|
106
|
-
targeting: {},
|
|
107
|
-
load: async () => { await this.loadSlot(slot, options); return null; },
|
|
108
|
-
render: (ad: Advertisement) => this.renderSlot(slot, ad),
|
|
109
|
-
refresh: () => this.refreshSlot(slot.id),
|
|
110
|
-
destroy: () => this.destroySlot(slot.id),
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
this.slots.set(id, slot);
|
|
114
|
-
await this.loadSlot(slot, options);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 광고 슬롯 로드
|
|
119
|
-
*/
|
|
120
|
-
private async loadSlot(slot: AdSlot, options?: any): Promise<void> {
|
|
121
|
-
try {
|
|
122
|
-
const queryParams = new URLSearchParams({
|
|
123
|
-
adType: slot.adType,
|
|
124
|
-
status: 'ACTIVE', // ACTIVE 상태인 광고만 조회
|
|
125
|
-
...(options?.language && { language: options.language }),
|
|
126
|
-
...(options?.deviceType && { deviceType: options.deviceType }),
|
|
127
|
-
...(options?.country && { country: options.country }),
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const requestUrl = `${this.baseUrl}/advertisements/list?${queryParams}`;
|
|
131
|
-
|
|
132
|
-
if (this.config.debug) {
|
|
133
|
-
console.log(`🌐 광고 API 요청 시작:`, {
|
|
134
|
-
url: requestUrl,
|
|
135
|
-
apiKey: this.config.apiKey.substring(0, 10) + '...',
|
|
136
|
-
slot: slot.id
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const response = await fetch(requestUrl, {
|
|
141
|
-
headers: {
|
|
142
|
-
'x-api-key': this.config.apiKey,
|
|
143
|
-
'Content-Type': 'application/json',
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
if (this.config.debug) {
|
|
148
|
-
console.log(`📡 API 응답 상태:`, {
|
|
149
|
-
status: response.status,
|
|
150
|
-
statusText: response.statusText,
|
|
151
|
-
ok: response.ok
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (!response.ok) {
|
|
156
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const data = await response.json();
|
|
160
|
-
|
|
161
|
-
if (this.config.debug) {
|
|
162
|
-
console.log(`📊 API 응답 데이터:`, {
|
|
163
|
-
data,
|
|
164
|
-
advertisementsCount: data.advertisements ? data.advertisements.length : 0
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const advertisements = data.advertisements || [];
|
|
169
|
-
|
|
170
|
-
if (advertisements.length > 0) {
|
|
171
|
-
if (this.config.debug) {
|
|
172
|
-
console.log(`✅ ${advertisements.length}개 광고 발견:`, advertisements);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// 여러 광고가 있을 경우 슬라이드로 렌더링
|
|
176
|
-
this.renderSlotWithSlider(slot, advertisements, options);
|
|
177
|
-
|
|
178
|
-
// 첫 번째 광고에 대해서만 노출 이벤트 추적
|
|
179
|
-
await this.eventTracker.trackEvent(advertisements[0]._id, slot.id, AdEventType.IMPRESSION);
|
|
180
|
-
} else {
|
|
181
|
-
console.warn(`⚠️ 슬롯 ${slot.id}에 사용 가능한 광고가 없습니다. API 응답:`, data);
|
|
182
|
-
}
|
|
183
|
-
} catch (error) {
|
|
184
|
-
console.error(`❌ 슬롯 ${slot.id} 로드 실패:`, error);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 광고 슬롯 렌더링 (슬라이더 포함)
|
|
190
|
-
*/
|
|
191
|
-
private renderSlotWithSlider(slot: AdSlot, advertisements: Advertisement[], options?: any): void {
|
|
192
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
193
|
-
if (!container) {
|
|
194
|
-
console.error(`❌ 컨테이너를 찾을 수 없습니다: ${slot.containerId}`);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (this.config.debug) {
|
|
199
|
-
console.log(`🎨 광고 렌더링 시작:`, {
|
|
200
|
-
slotId: slot.id,
|
|
201
|
-
containerId: slot.containerId,
|
|
202
|
-
advertisementCount: advertisements.length,
|
|
203
|
-
container: container
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (advertisements.length === 1) {
|
|
208
|
-
// 광고가 하나뿐이면 기본 렌더링
|
|
209
|
-
this.renderSlot(slot, advertisements[0]);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 슬라이더 효과 결정 (옵션으로 지정하거나 텍스트 광고인 경우 fade 기본값)
|
|
214
|
-
const isAllTextAds = advertisements.every(ad => ad.adType === AdType.TEXT);
|
|
215
|
-
const useFadeEffect = options?.sliderEffect === 'fade' ||
|
|
216
|
-
(options?.sliderEffect !== 'slide' && isAllTextAds);
|
|
217
|
-
|
|
218
|
-
let sliderContainer: HTMLElement;
|
|
219
|
-
|
|
220
|
-
if (useFadeEffect) {
|
|
221
|
-
// 페이드 슬라이더 사용
|
|
222
|
-
sliderContainer = FadeSliderManager.createFadeSliderContainer(
|
|
223
|
-
slot,
|
|
224
|
-
advertisements,
|
|
225
|
-
options,
|
|
226
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
// 기본 슬라이더 사용
|
|
230
|
-
sliderContainer = SliderManager.createSliderContainer(
|
|
231
|
-
slot,
|
|
232
|
-
advertisements,
|
|
233
|
-
options,
|
|
234
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
container.innerHTML = '';
|
|
239
|
-
container.appendChild(sliderContainer);
|
|
240
|
-
slot.isLoaded = true;
|
|
241
|
-
|
|
242
|
-
if (this.config.debug) {
|
|
243
|
-
const sliderType = useFadeEffect ? 'fade slider' : 'slider';
|
|
244
|
-
console.log(`✅ ${advertisements.length}개 광고를 ${sliderType}로 렌더링 완료:`, {
|
|
245
|
-
slotId: slot.id,
|
|
246
|
-
container: container,
|
|
247
|
-
sliderContainer: sliderContainer
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* 광고 슬롯 렌더링 (단일 광고용)
|
|
254
|
-
*/
|
|
255
|
-
private renderSlot(slot: AdSlot, ad: Advertisement): void {
|
|
256
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
257
|
-
if (!container) {
|
|
258
|
-
console.error(`❌ 컨테이너를 찾을 수 없습니다: ${slot.containerId}`);
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (this.config.debug) {
|
|
263
|
-
console.log(`🎨 단일 광고 렌더링 시작:`, {
|
|
264
|
-
slotId: slot.id,
|
|
265
|
-
containerId: slot.containerId,
|
|
266
|
-
ad: ad,
|
|
267
|
-
container: container
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 팩토리를 사용해서 적절한 렌더러로 광고 생성
|
|
272
|
-
const adElement = AdRendererFactory.render(
|
|
273
|
-
ad,
|
|
274
|
-
slot,
|
|
275
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
if (this.config.debug) {
|
|
279
|
-
console.log(`🔧 광고 요소 생성됨:`, {
|
|
280
|
-
adElement: adElement,
|
|
281
|
-
tagName: adElement.tagName,
|
|
282
|
-
innerHTML: adElement.innerHTML.substring(0, 200) + '...'
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
container.innerHTML = '';
|
|
287
|
-
container.appendChild(adElement);
|
|
288
|
-
slot.isLoaded = true;
|
|
289
|
-
|
|
290
|
-
if (this.config.debug) {
|
|
291
|
-
console.log(`✅ 단일 광고 렌더링 완료:`, {
|
|
292
|
-
slotId: slot.id,
|
|
293
|
-
ad: ad,
|
|
294
|
-
containerContent: container.innerHTML.substring(0, 200) + '...'
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* 광고 슬롯 새로고침
|
|
301
|
-
*/
|
|
302
|
-
async refreshSlot(slotId: string): Promise<void> {
|
|
303
|
-
const slot = this.slots.get(slotId);
|
|
304
|
-
if (slot) {
|
|
305
|
-
await this.loadSlot(slot);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* 광고 슬롯 제거
|
|
311
|
-
*/
|
|
312
|
-
destroySlot(slotId: string): void {
|
|
313
|
-
const slot = this.slots.get(slotId);
|
|
314
|
-
if (slot) {
|
|
315
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
316
|
-
if (container) {
|
|
317
|
-
DOMUtils.safeSetInnerHTML(container, '');
|
|
318
|
-
}
|
|
319
|
-
this.slots.delete(slotId);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* 자동 슬롯 검색 및 로드 (분리된 SDKUtils 사용)
|
|
325
|
-
*/
|
|
326
|
-
async autoLoadSlots(): Promise<void> {
|
|
327
|
-
const elements = SDKUtils.findAutoSlotElements();
|
|
328
|
-
|
|
329
|
-
for (const element of elements) {
|
|
330
|
-
const slotInfo = SDKUtils.extractSlotInfo(element);
|
|
331
|
-
|
|
332
|
-
if (!slotInfo.slotId || this.slots.has(slotInfo.slotId)) continue;
|
|
333
|
-
|
|
334
|
-
const adType = SDKUtils.parseAdType(slotInfo.adType, AdType);
|
|
335
|
-
|
|
336
|
-
await this.createSlot(slotInfo.slotId, element.id || slotInfo.slotId, adType, {
|
|
337
|
-
width: slotInfo.width,
|
|
338
|
-
height: slotInfo.height,
|
|
339
|
-
language: slotInfo.language,
|
|
340
|
-
deviceType: slotInfo.deviceType,
|
|
341
|
-
country: slotInfo.country,
|
|
342
|
-
sliderEffect: slotInfo.sliderEffect as 'slide' | 'fade' | undefined,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* SDK 정리
|
|
349
|
-
*/
|
|
350
|
-
destroy(): void {
|
|
351
|
-
this.slots.clear();
|
|
352
|
-
ImpressionTracker.clear(); // 노출 추적 데이터도 정리
|
|
353
|
-
this.initialized = false;
|
|
354
|
-
AdStageSDK.instance = null;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* 디바이스 ID 가져오기
|
|
359
|
-
*/
|
|
360
|
-
getDeviceId(): string {
|
|
361
|
-
return DeviceInfoCollector.generateDeviceId();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* 세션 ID 가져오기
|
|
366
|
-
*/
|
|
367
|
-
getSessionId(): string {
|
|
368
|
-
return DeviceInfoCollector.generateSessionId();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* 현재 로드된 슬롯 수 가져오기
|
|
373
|
-
*/
|
|
374
|
-
getLoadedSlotCount(): number {
|
|
375
|
-
return this.slots.size;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* 모든 슬롯 정보 가져오기
|
|
380
|
-
*/
|
|
381
|
-
getAllSlots(): Map<string, AdSlot> {
|
|
382
|
-
return new Map(this.slots);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
async function autoInit() {
|
|
387
|
-
if (DOMUtils.isBrowser() && (window as any).adstageConfig) {
|
|
388
|
-
try {
|
|
389
|
-
const sdk = AdStageSDK.init((window as any).adstageConfig);
|
|
390
|
-
await sdk.autoLoadSlots();
|
|
391
|
-
} catch (error) {
|
|
392
|
-
console.error('Failed to auto-initialize AdStageSDK:', error);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
5
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
// 타입 단언을 사용하여 window 객체에 할당
|
|
400
|
-
(window as any).AdStageSDK = AdStageSDK;
|
|
401
|
-
|
|
402
|
-
// DOM 로드 후 자동 초기화
|
|
403
|
-
if (DOMUtils.isDOMReady()) {
|
|
404
|
-
autoInit();
|
|
405
|
-
} else {
|
|
406
|
-
DOMUtils.waitForDOM().then(autoInit);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
6
|
+
// 메인 네임스페이스 클래스
|
|
7
|
+
export { default as AdStage } from './core/AdStage';
|
|
409
8
|
|
|
410
|
-
|
|
9
|
+
// 설정 및 타입
|
|
10
|
+
export type { AdStageConfig, ModuleName, BaseModule, ApiResponse, OrganizationInfo } from './types/config';
|
|
411
11
|
|
|
412
|
-
//
|
|
413
|
-
export {
|
|
414
|
-
export type {
|
|
12
|
+
// 모듈별 타입
|
|
13
|
+
export type { AdOptions } from './modules/ads/AdsModule';
|
|
14
|
+
export type { EventProperties, PageData } from './modules/events/EventsModule';
|
|
415
15
|
|
|
416
|
-
//
|
|
417
|
-
export
|
|
16
|
+
// 광고 관련 타입
|
|
17
|
+
export type { AdType, AdEventType, Advertisement, AdSlot } from './types/advertisement';
|
|
418
18
|
|
|
419
|
-
//
|
|
420
|
-
|
|
19
|
+
// 버전 정보
|
|
20
|
+
export const SDK_VERSION = '2.0.0';
|
|
21
|
+
export const SUPPORTED_MODULES = ['ads', 'events', 'config'] as const;
|
|
@@ -3,18 +3,19 @@ import type { AdSlot, Advertisement } from '../types/advertisement';
|
|
|
3
3
|
import { AdRendererFactory } from '../renderers';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* 슬라이더 관리 클래스
|
|
7
|
-
* -
|
|
8
|
-
* - 무한 루프
|
|
6
|
+
* 캐러셀 슬라이더 관리 클래스
|
|
7
|
+
* - 배너/비디오 광고용 가로 슬라이드 (횡 스크롤)
|
|
8
|
+
* - 무한 루프 캐러셀 지원
|
|
9
9
|
* - 터치 제스처 및 자동 슬라이드 기능
|
|
10
|
+
* - 도트 인디케이터 포함
|
|
10
11
|
*/
|
|
11
|
-
export class
|
|
12
|
+
export class CarouselSliderManager {
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Create carousel slider container with dot indicators and navigation
|
|
14
15
|
*/
|
|
15
16
|
static createSliderContainer(
|
|
16
17
|
slot: AdSlot,
|
|
17
|
-
advertisements:
|
|
18
|
+
advertisements: any[],
|
|
18
19
|
options: any,
|
|
19
20
|
trackEventCallback: (adId: string, slotId: string, eventType: AdEventType) => void
|
|
20
21
|
): HTMLElement {
|
|
@@ -175,7 +176,7 @@ export class SliderManager {
|
|
|
175
176
|
const isAllTextAds = advertisements.every(ad => ad.adType === AdType.TEXT);
|
|
176
177
|
|
|
177
178
|
// 무채색 도트 인디케이터 생성 (원본 광고 수만큼) - 텍스트 광고가 아닐 때만
|
|
178
|
-
const dotContainer = isAllTextAds ? null :
|
|
179
|
+
const dotContainer = isAllTextAds ? null : this.createMinimalDotIndicator(advertisements.length);
|
|
179
180
|
|
|
180
181
|
// 슬라이더 상태 관리
|
|
181
182
|
let currentSlide = 0;
|
|
@@ -261,7 +262,7 @@ export class SliderManager {
|
|
|
261
262
|
});
|
|
262
263
|
|
|
263
264
|
// 터치 제스처 지원 수정 (무한 루프 지원)
|
|
264
|
-
|
|
265
|
+
this.addTouchSupport(slideContainer, moveToSlide, () => currentSlide, totalSlides, handleInfiniteLoop);
|
|
265
266
|
|
|
266
267
|
// 요소들 조립 (화살표 제거, 도트는 텍스트 광고가 아닐 때만 추가)
|
|
267
268
|
sliderWrapper.appendChild(slideContainer);
|