@adstage/web-sdk 1.3.4 → 2.0.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 +539 -34
- package/dist/index.cjs.js +753 -509
- package/dist/index.d.ts +286 -97
- package/dist/index.esm.js +737 -485
- package/dist/index.standalone.js +737 -485
- package/package.json +12 -13
- package/src/constants/endpoints.ts +93 -0
- package/src/core/AdStage.ts +128 -0
- package/src/index.ts +14 -432
- 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/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 +1 -1
- 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/src/index.ts
CHANGED
|
@@ -1,439 +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
|
-
try {
|
|
89
|
-
// 💡 사용자 친화적 개선: 컨테이너가 나타날 때까지 자동으로 기다림
|
|
90
|
-
if (this.config.debug) {
|
|
91
|
-
console.log(`🔍 컨테이너 검색 시작: ${containerId}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const container = await DOMUtils.waitForElement(containerId, {
|
|
95
|
-
timeout: 5000, // 최대 5초 대기
|
|
96
|
-
retryInterval: 50, // 50ms마다 체크 (부드러운 사용자 경험)
|
|
97
|
-
debug: this.config.debug
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (this.config.debug) {
|
|
101
|
-
console.log(`✅ 컨테이너 확인됨: ${containerId}`, container);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const slot: AdSlot = {
|
|
105
|
-
id,
|
|
106
|
-
containerId,
|
|
107
|
-
adType,
|
|
108
|
-
width: options?.width || 0, // 문자열도 지원
|
|
109
|
-
height: options?.height || 0, // 문자열도 지원
|
|
110
|
-
isLoaded: false,
|
|
111
|
-
isVisible: false,
|
|
112
|
-
refreshRate: 0,
|
|
113
|
-
lazyLoad: false,
|
|
114
|
-
targeting: {},
|
|
115
|
-
load: async () => { await this.loadSlot(slot, options); return null; },
|
|
116
|
-
render: (ad: Advertisement) => this.renderSlot(slot, ad),
|
|
117
|
-
refresh: () => this.refreshSlot(slot.id),
|
|
118
|
-
destroy: () => this.destroySlot(slot.id),
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
this.slots.set(id, slot);
|
|
122
|
-
await this.loadSlot(slot, options);
|
|
123
|
-
} catch (error) {
|
|
124
|
-
// 친절한 에러 메시지로 사용자 가이드
|
|
125
|
-
if (error instanceof Error && error.message.includes('컨테이너를 찾을 수 없습니다')) {
|
|
126
|
-
console.error(error.message);
|
|
127
|
-
throw error;
|
|
128
|
-
} else {
|
|
129
|
-
console.error(`❌ 광고 슬롯 생성 실패 (${id}):`, error);
|
|
130
|
-
throw new Error(`광고 슬롯 생성에 실패했습니다: ${error}`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 광고 슬롯 로드
|
|
137
|
-
*/
|
|
138
|
-
private async loadSlot(slot: AdSlot, options?: any): Promise<void> {
|
|
139
|
-
try {
|
|
140
|
-
const queryParams = new URLSearchParams({
|
|
141
|
-
adType: slot.adType,
|
|
142
|
-
status: 'ACTIVE', // ACTIVE 상태인 광고만 조회
|
|
143
|
-
...(options?.language && { language: options.language }),
|
|
144
|
-
...(options?.deviceType && { deviceType: options.deviceType }),
|
|
145
|
-
...(options?.country && { country: options.country }),
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const requestUrl = `${this.baseUrl}/advertisements/list?${queryParams}`;
|
|
149
|
-
|
|
150
|
-
if (this.config.debug) {
|
|
151
|
-
console.log(`🌐 광고 API 요청 시작:`, {
|
|
152
|
-
url: requestUrl,
|
|
153
|
-
apiKey: this.config.apiKey.substring(0, 10) + '...',
|
|
154
|
-
slot: slot.id
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const response = await fetch(requestUrl, {
|
|
159
|
-
headers: {
|
|
160
|
-
'x-api-key': this.config.apiKey,
|
|
161
|
-
'Content-Type': 'application/json',
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (this.config.debug) {
|
|
166
|
-
console.log(`📡 API 응답 상태:`, {
|
|
167
|
-
status: response.status,
|
|
168
|
-
statusText: response.statusText,
|
|
169
|
-
ok: response.ok
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!response.ok) {
|
|
174
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const data = await response.json();
|
|
178
|
-
|
|
179
|
-
if (this.config.debug) {
|
|
180
|
-
console.log(`📊 API 응답 데이터:`, {
|
|
181
|
-
data,
|
|
182
|
-
advertisementsCount: data.advertisements ? data.advertisements.length : 0
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const advertisements = data.advertisements || [];
|
|
187
|
-
|
|
188
|
-
if (advertisements.length > 0) {
|
|
189
|
-
if (this.config.debug) {
|
|
190
|
-
console.log(`✅ ${advertisements.length}개 광고 발견:`, advertisements);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// 여러 광고가 있을 경우 슬라이드로 렌더링
|
|
194
|
-
this.renderSlotWithSlider(slot, advertisements, options);
|
|
195
|
-
|
|
196
|
-
// 첫 번째 광고에 대해서만 노출 이벤트 추적
|
|
197
|
-
await this.eventTracker.trackEvent(advertisements[0]._id, slot.id, AdEventType.IMPRESSION);
|
|
198
|
-
} else {
|
|
199
|
-
console.warn(`⚠️ 슬롯 ${slot.id}에 사용 가능한 광고가 없습니다. API 응답:`, data);
|
|
200
|
-
}
|
|
201
|
-
} catch (error) {
|
|
202
|
-
console.error(`❌ 슬롯 ${slot.id} 로드 실패:`, error);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* 광고 슬롯 렌더링 (슬라이더 포함)
|
|
208
|
-
*/
|
|
209
|
-
private renderSlotWithSlider(slot: AdSlot, advertisements: Advertisement[], options?: any): void {
|
|
210
|
-
// 💡 한 번 더 안전하게 컨테이너 확인
|
|
211
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
212
|
-
if (!container) {
|
|
213
|
-
console.error(`❌ 렌더링 시점에 컨테이너를 찾을 수 없습니다: ${slot.containerId}`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (this.config.debug) {
|
|
218
|
-
console.log(`🎨 광고 렌더링 시작:`, {
|
|
219
|
-
slotId: slot.id,
|
|
220
|
-
containerId: slot.containerId,
|
|
221
|
-
advertisementCount: advertisements.length,
|
|
222
|
-
container: container
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (advertisements.length === 1) {
|
|
227
|
-
// 광고가 하나뿐이면 기본 렌더링
|
|
228
|
-
this.renderSlot(slot, advertisements[0]);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 슬라이더 효과 결정 (옵션으로 지정하거나 텍스트 광고인 경우 fade 기본값)
|
|
233
|
-
const isAllTextAds = advertisements.every(ad => ad.adType === AdType.TEXT);
|
|
234
|
-
const useFadeEffect = options?.sliderEffect === 'fade' ||
|
|
235
|
-
(options?.sliderEffect !== 'slide' && isAllTextAds);
|
|
236
|
-
|
|
237
|
-
let sliderContainer: HTMLElement;
|
|
238
|
-
|
|
239
|
-
if (useFadeEffect) {
|
|
240
|
-
// 페이드 슬라이더 사용
|
|
241
|
-
sliderContainer = FadeSliderManager.createFadeSliderContainer(
|
|
242
|
-
slot,
|
|
243
|
-
advertisements,
|
|
244
|
-
options,
|
|
245
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
246
|
-
);
|
|
247
|
-
} else {
|
|
248
|
-
// 기본 슬라이더 사용
|
|
249
|
-
sliderContainer = SliderManager.createSliderContainer(
|
|
250
|
-
slot,
|
|
251
|
-
advertisements,
|
|
252
|
-
options,
|
|
253
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
container.innerHTML = '';
|
|
258
|
-
container.appendChild(sliderContainer);
|
|
259
|
-
slot.isLoaded = true;
|
|
260
|
-
|
|
261
|
-
if (this.config.debug) {
|
|
262
|
-
const sliderType = useFadeEffect ? 'fade slider' : 'slider';
|
|
263
|
-
console.log(`✅ ${advertisements.length}개 광고를 ${sliderType}로 렌더링 완료:`, {
|
|
264
|
-
slotId: slot.id,
|
|
265
|
-
container: container,
|
|
266
|
-
sliderContainer: sliderContainer
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* 광고 슬롯 렌더링 (단일 광고용)
|
|
273
|
-
*/
|
|
274
|
-
private renderSlot(slot: AdSlot, ad: Advertisement): void {
|
|
275
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
276
|
-
if (!container) {
|
|
277
|
-
console.error(`❌ 컨테이너를 찾을 수 없습니다: ${slot.containerId}`);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.config.debug) {
|
|
282
|
-
console.log(`🎨 단일 광고 렌더링 시작:`, {
|
|
283
|
-
slotId: slot.id,
|
|
284
|
-
containerId: slot.containerId,
|
|
285
|
-
ad: ad,
|
|
286
|
-
container: container
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// 팩토리를 사용해서 적절한 렌더러로 광고 생성
|
|
291
|
-
const adElement = AdRendererFactory.render(
|
|
292
|
-
ad,
|
|
293
|
-
slot,
|
|
294
|
-
(adId, slotId, eventType) => this.eventTracker.trackEvent(adId, slotId, eventType)
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
if (this.config.debug) {
|
|
298
|
-
console.log(`🔧 광고 요소 생성됨:`, {
|
|
299
|
-
adElement: adElement,
|
|
300
|
-
tagName: adElement.tagName,
|
|
301
|
-
innerHTML: adElement.innerHTML.substring(0, 200) + '...'
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
container.innerHTML = '';
|
|
306
|
-
container.appendChild(adElement);
|
|
307
|
-
slot.isLoaded = true;
|
|
308
|
-
|
|
309
|
-
if (this.config.debug) {
|
|
310
|
-
console.log(`✅ 단일 광고 렌더링 완료:`, {
|
|
311
|
-
slotId: slot.id,
|
|
312
|
-
ad: ad,
|
|
313
|
-
containerContent: container.innerHTML.substring(0, 200) + '...'
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* 광고 슬롯 새로고침
|
|
320
|
-
*/
|
|
321
|
-
async refreshSlot(slotId: string): Promise<void> {
|
|
322
|
-
const slot = this.slots.get(slotId);
|
|
323
|
-
if (slot) {
|
|
324
|
-
await this.loadSlot(slot);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* 광고 슬롯 제거
|
|
330
|
-
*/
|
|
331
|
-
destroySlot(slotId: string): void {
|
|
332
|
-
const slot = this.slots.get(slotId);
|
|
333
|
-
if (slot) {
|
|
334
|
-
const container = DOMUtils.safeGetElementById(slot.containerId);
|
|
335
|
-
if (container) {
|
|
336
|
-
DOMUtils.safeSetInnerHTML(container, '');
|
|
337
|
-
}
|
|
338
|
-
this.slots.delete(slotId);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* 자동 슬롯 검색 및 로드 (분리된 SDKUtils 사용)
|
|
344
|
-
*/
|
|
345
|
-
async autoLoadSlots(): Promise<void> {
|
|
346
|
-
const elements = SDKUtils.findAutoSlotElements();
|
|
347
|
-
|
|
348
|
-
for (const element of elements) {
|
|
349
|
-
const slotInfo = SDKUtils.extractSlotInfo(element);
|
|
350
|
-
|
|
351
|
-
if (!slotInfo.slotId || this.slots.has(slotInfo.slotId)) continue;
|
|
352
|
-
|
|
353
|
-
const adType = SDKUtils.parseAdType(slotInfo.adType, AdType);
|
|
354
|
-
|
|
355
|
-
await this.createSlot(slotInfo.slotId, element.id || slotInfo.slotId, adType, {
|
|
356
|
-
width: slotInfo.width,
|
|
357
|
-
height: slotInfo.height,
|
|
358
|
-
language: slotInfo.language,
|
|
359
|
-
deviceType: slotInfo.deviceType,
|
|
360
|
-
country: slotInfo.country,
|
|
361
|
-
sliderEffect: slotInfo.sliderEffect as 'slide' | 'fade' | undefined,
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* SDK 정리
|
|
368
|
-
*/
|
|
369
|
-
destroy(): void {
|
|
370
|
-
this.slots.clear();
|
|
371
|
-
ImpressionTracker.clear(); // 노출 추적 데이터도 정리
|
|
372
|
-
this.initialized = false;
|
|
373
|
-
AdStageSDK.instance = null;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* 디바이스 ID 가져오기
|
|
378
|
-
*/
|
|
379
|
-
getDeviceId(): string {
|
|
380
|
-
return DeviceInfoCollector.generateDeviceId();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* 세션 ID 가져오기
|
|
385
|
-
*/
|
|
386
|
-
getSessionId(): string {
|
|
387
|
-
return DeviceInfoCollector.generateSessionId();
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* 현재 로드된 슬롯 수 가져오기
|
|
392
|
-
*/
|
|
393
|
-
getLoadedSlotCount(): number {
|
|
394
|
-
return this.slots.size;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* 모든 슬롯 정보 가져오기
|
|
399
|
-
*/
|
|
400
|
-
getAllSlots(): Map<string, AdSlot> {
|
|
401
|
-
return new Map(this.slots);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
async function autoInit() {
|
|
406
|
-
if (DOMUtils.isBrowser() && (window as any).adstageConfig) {
|
|
407
|
-
try {
|
|
408
|
-
const sdk = AdStageSDK.init((window as any).adstageConfig);
|
|
409
|
-
await sdk.autoLoadSlots();
|
|
410
|
-
} catch (error) {
|
|
411
|
-
console.error('Failed to auto-initialize AdStageSDK:', error);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
5
|
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
// 타입 단언을 사용하여 window 객체에 할당
|
|
419
|
-
(window as any).AdStageSDK = AdStageSDK;
|
|
420
|
-
|
|
421
|
-
// DOM 로드 후 자동 초기화
|
|
422
|
-
if (DOMUtils.isDOMReady()) {
|
|
423
|
-
autoInit();
|
|
424
|
-
} else {
|
|
425
|
-
DOMUtils.waitForDOM().then(autoInit);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
6
|
+
// 메인 네임스페이스 클래스
|
|
7
|
+
export { default as AdStage } from './core/AdStage';
|
|
428
8
|
|
|
429
|
-
|
|
9
|
+
// 설정 및 타입
|
|
10
|
+
export type { AdStageConfig, ModuleName, BaseModule, ApiResponse, OrganizationInfo } from './types/config';
|
|
430
11
|
|
|
431
|
-
//
|
|
432
|
-
export {
|
|
433
|
-
export type {
|
|
12
|
+
// 모듈별 타입
|
|
13
|
+
export type { AdOptions } from './modules/ads/AdsModule';
|
|
14
|
+
export type { EventProperties, PageData } from './modules/events/EventsModule';
|
|
434
15
|
|
|
435
|
-
//
|
|
436
|
-
export
|
|
16
|
+
// 광고 관련 타입
|
|
17
|
+
export type { AdType, AdEventType, Advertisement, AdSlot } from './types/advertisement';
|
|
437
18
|
|
|
438
|
-
//
|
|
439
|
-
|
|
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);
|
|
@@ -3,6 +3,7 @@ import type { AdSlot } from '../types/advertisement';
|
|
|
3
3
|
import { ImpressionTracker } from './impression-tracker';
|
|
4
4
|
import { DeviceInfoCollector } from './device-info-collector';
|
|
5
5
|
import { DOMUtils } from '../utils/dom-utils';
|
|
6
|
+
import { ApiHeaders } from '../utils/api-headers';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* 이벤트 추적 관리 클래스
|
|
@@ -96,10 +97,7 @@ export class EventTracker {
|
|
|
96
97
|
`${this.baseUrl}/advertisements/events/${adId}/${eventType}`,
|
|
97
98
|
{
|
|
98
99
|
method: 'POST',
|
|
99
|
-
headers:
|
|
100
|
-
'x-api-key': this.apiKey,
|
|
101
|
-
'Content-Type': 'application/json',
|
|
102
|
-
},
|
|
100
|
+
headers: ApiHeaders.createForEvents(this.apiKey, eventData),
|
|
103
101
|
body: JSON.stringify(eventData),
|
|
104
102
|
}
|
|
105
103
|
);
|
|
@@ -3,16 +3,16 @@ import type { AdSlot, Advertisement } from '../types/advertisement';
|
|
|
3
3
|
import { AdRendererFactory } from '../renderers';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* - 텍스트 광고 전용 페이드 인/아웃
|
|
8
|
-
* -
|
|
6
|
+
* 텍스트 전환 효과 관리 클래스
|
|
7
|
+
* - 텍스트 광고 전용 페이드 인/아웃 + 상하 움직임 효과
|
|
8
|
+
* - 부드러운 전환 애니메이션 (vertical transition)
|
|
9
9
|
* - 무한 루프 지원
|
|
10
10
|
*/
|
|
11
|
-
export class
|
|
11
|
+
export class TextTransitionManager {
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* 텍스트 전환 슬라이더 컨테이너 생성
|
|
14
14
|
*/
|
|
15
|
-
static
|
|
15
|
+
static createTextTransitionContainer(
|
|
16
16
|
slot: AdSlot,
|
|
17
17
|
advertisements: Advertisement[],
|
|
18
18
|
options: any,
|
|
@@ -219,7 +219,7 @@ export class FadeSliderManager {
|
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
// 터치 제스처 지원
|
|
222
|
-
|
|
222
|
+
TextTransitionManager.addTouchSupport(sliderWrapper, moveToSlide, () => currentSlide, totalSlides);
|
|
223
223
|
|
|
224
224
|
// 요소들 조립
|
|
225
225
|
sliderWrapper.appendChild(slideContainer);
|