@adstage/web-sdk 3.0.13 → 3.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.standalone.js +2 -2
- package/dist/index.umd.js +2 -2
- package/package.json +1 -1
- package/src/core/adstage.ts +5 -2
- package/src/modules/tracking/tracking-params.ts +39 -11
- package/src/utils/api-headers.ts +13 -22
package/package.json
CHANGED
package/src/core/adstage.ts
CHANGED
|
@@ -109,8 +109,11 @@ export class AdStage {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
|
|
112
|
+
// 🎯 서버에 Attribution 자동 등록 (클릭 ID 또는 channel이 있는 경우, 단 URL로 이미 전달된 경우는 생략)
|
|
113
|
+
const hasClickId = TrackingParamsModule.hasClickId();
|
|
114
|
+
const hasChannel = !!(trackingParams && trackingParams.channel);
|
|
115
|
+
|
|
116
|
+
if (!aidFromUrl && trackingParams && (hasClickId || hasChannel)) {
|
|
114
117
|
// Attribution 등록을 Promise로 처리
|
|
115
118
|
const attributionPromise = (async () => {
|
|
116
119
|
try {
|
|
@@ -33,6 +33,14 @@ export interface TrackingParams {
|
|
|
33
33
|
creative_id?: string; // 광고소재 ID
|
|
34
34
|
term?: string; // 키워드
|
|
35
35
|
|
|
36
|
+
// ========== 카카오 키워드광고 파라미터 ==========
|
|
37
|
+
k_campaign?: string; // 카카오 키워드광고 캠페인 ID
|
|
38
|
+
k_adgroup?: string; // 카카오 키워드광고 광고그룹 ID
|
|
39
|
+
k_keyword?: string; // 카카오 키워드광고 등록 키워드
|
|
40
|
+
k_keyword_id?: string; // 카카오 키워드광고 키워드 ID
|
|
41
|
+
k_creative?: string; // 카카오 키워드광고 소재 ID
|
|
42
|
+
k_rank?: string; // 카카오 키워드광고 노출 순위
|
|
43
|
+
|
|
36
44
|
// ========== UTM 파라미터 ==========
|
|
37
45
|
utm_source?: string; // 트래픽 소스 (예: google, facebook, newsletter)
|
|
38
46
|
utm_medium?: string; // 마케팅 매체 (예: cpc, banner, email)
|
|
@@ -51,7 +59,7 @@ export class TrackingParamsModule {
|
|
|
51
59
|
// 추출할 파라미터 키 정의
|
|
52
60
|
private static readonly CLICK_ID_KEYS = ['gclid', 'fbclid', 'ttclid', 'nclid', 'liclid', 'msclkid', 'twclid'];
|
|
53
61
|
|
|
54
|
-
private static readonly CAMPAIGN_KEYS = ['channel', 'campaign', 'campaign_id', 'ad_group', 'ad_group_id', 'ad_creative', 'creative_id', 'term'];
|
|
62
|
+
private static readonly CAMPAIGN_KEYS = ['channel', 'campaign', 'campaign_id', 'ad_group', 'ad_group_id', 'ad_creative', 'creative_id', 'term', 'k_campaign', 'k_adgroup', 'k_keyword', 'k_keyword_id', 'k_creative', 'k_rank'];
|
|
55
63
|
|
|
56
64
|
private static readonly UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
|
|
57
65
|
|
|
@@ -130,6 +138,9 @@ export class TrackingParamsModule {
|
|
|
130
138
|
else if (params.liclid) params.channel = 'linkedin.ads';
|
|
131
139
|
else if (params.msclkid) params.channel = 'microsoft.ads';
|
|
132
140
|
else if (params.twclid) params.channel = 'twitter.ads';
|
|
141
|
+
// 카카오 키워드광고: utm_source=kakao & utm_medium=keyword 또는 k_ 파라미터
|
|
142
|
+
else if (params.utm_source === 'kakao' && params.utm_medium === 'keyword') params.channel = 'kakao.keyword';
|
|
143
|
+
else if (params.k_campaign || params.k_keyword) params.channel = 'kakao.keyword';
|
|
133
144
|
}
|
|
134
145
|
|
|
135
146
|
TrackingParamsModule.store(params);
|
|
@@ -217,7 +228,7 @@ export class TrackingParamsModule {
|
|
|
217
228
|
|
|
218
229
|
/**
|
|
219
230
|
* Click 이벤트 자동 전송 여부 확인
|
|
220
|
-
* 새로운 클릭 ID
|
|
231
|
+
* 새로운 클릭 ID 또는 channel 파라미터가 발견되면 true 반환
|
|
221
232
|
*/
|
|
222
233
|
public static shouldSendClickEvent(): boolean {
|
|
223
234
|
if (typeof window === 'undefined') {
|
|
@@ -225,24 +236,33 @@ export class TrackingParamsModule {
|
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
const params = TrackingParamsModule.get();
|
|
228
|
-
if (!params
|
|
239
|
+
if (!params) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 클릭 ID 또는 channel 파라미터가 있어야 함
|
|
244
|
+
const hasClickId = TrackingParamsModule.hasClickId();
|
|
245
|
+
const hasChannel = !!params.channel;
|
|
246
|
+
|
|
247
|
+
if (!hasClickId && !hasChannel) {
|
|
229
248
|
return false;
|
|
230
249
|
}
|
|
231
250
|
|
|
232
|
-
// 현재
|
|
233
|
-
const
|
|
234
|
-
params.gclid || params.fbclid || params.ttclid || params.nclid || params.liclid || params.msclkid || params.twclid
|
|
251
|
+
// 현재 식별자 추출 (clickId 우선, 없으면 channel+term 조합)
|
|
252
|
+
const currentIdentifier = hasClickId
|
|
253
|
+
? (params.gclid || params.fbclid || params.ttclid || params.nclid || params.liclid || params.msclkid || params.twclid)
|
|
254
|
+
: `${params.channel || ''}:${params.term || ''}`;
|
|
235
255
|
|
|
236
|
-
if (!
|
|
256
|
+
if (!currentIdentifier) {
|
|
237
257
|
return false;
|
|
238
258
|
}
|
|
239
259
|
|
|
240
|
-
// 이미 처리된
|
|
260
|
+
// 이미 처리된 식별자인지 확인 (중복 방지)
|
|
241
261
|
const lastClickId = sessionStorage.getItem('adstage_last_click_id');
|
|
242
262
|
|
|
243
|
-
// 새로운
|
|
244
|
-
if (
|
|
245
|
-
sessionStorage.setItem('adstage_last_click_id',
|
|
263
|
+
// 새로운 식별자면 전송 필요
|
|
264
|
+
if (currentIdentifier !== lastClickId) {
|
|
265
|
+
sessionStorage.setItem('adstage_last_click_id', currentIdentifier);
|
|
246
266
|
return true;
|
|
247
267
|
}
|
|
248
268
|
|
|
@@ -306,6 +326,14 @@ export class TrackingParamsModule {
|
|
|
306
326
|
if (params.creative_id) attribution.creativeId = params.creative_id;
|
|
307
327
|
if (params.term) attribution.term = params.term;
|
|
308
328
|
|
|
329
|
+
// ========== 카카오 키워드광고 파라미터 ==========
|
|
330
|
+
if (params.k_campaign) attribution.campaignId = attribution.campaignId || params.k_campaign;
|
|
331
|
+
if (params.k_adgroup) attribution.adGroupId = attribution.adGroupId || params.k_adgroup;
|
|
332
|
+
if (params.k_keyword) attribution.keyword = params.k_keyword;
|
|
333
|
+
if (params.k_keyword_id) attribution.keywordId = params.k_keyword_id;
|
|
334
|
+
if (params.k_creative) attribution.creativeId = attribution.creativeId || params.k_creative;
|
|
335
|
+
if (params.k_rank) attribution.adRank = params.k_rank;
|
|
336
|
+
|
|
309
337
|
// ========== UTM 파라미터 (앱/웹 공통 - snake_case 유지) ==========
|
|
310
338
|
if (params.utm_source) attribution.utm_source = params.utm_source;
|
|
311
339
|
if (params.utm_medium) attribution.utm_medium = params.utm_medium;
|
package/src/utils/api-headers.ts
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
export class ApiHeaders {
|
|
7
7
|
/**
|
|
8
8
|
* 표준 API 헤더 생성
|
|
9
|
+
*
|
|
10
|
+
* 주의: `User-Agent`는 브라우저 forbidden header라 fetch에서 수동 설정하면 안 된다.
|
|
11
|
+
* (Chromium은 무시하지만 Safari/WebKit은 설정값을 CORS preflight에 포함시켜
|
|
12
|
+
* 서버가 허용하지 않으면 요청 자체가 차단됨.) 브라우저가 모든 요청에 진짜
|
|
13
|
+
* `User-Agent`를 자동 첨부하므로, 서버는 `req.headers['user-agent']`로 동일한 값을 받는다.
|
|
9
14
|
*/
|
|
10
15
|
static create(apiKey: string, options?: {
|
|
11
16
|
contentType?: string;
|
|
12
|
-
userAgent?: string;
|
|
13
|
-
currentUrl?: string;
|
|
14
17
|
}): Record<string, string> {
|
|
15
18
|
if (!apiKey) {
|
|
16
19
|
throw new Error('API key is required');
|
|
@@ -21,32 +24,20 @@ export class ApiHeaders {
|
|
|
21
24
|
'Content-Type': options?.contentType || 'application/json'
|
|
22
25
|
};
|
|
23
26
|
|
|
24
|
-
//
|
|
25
|
-
if (typeof navigator !== 'undefined') {
|
|
26
|
-
headers['User-Agent'] = options?.userAgent || navigator.userAgent;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// X-Current-URL은 현재 서버에서 사용하지 않으므로 제거
|
|
30
|
-
// 필요시 이벤트 데이터 body에 포함
|
|
27
|
+
// X-Current-URL 등 부가 정보는 HTTP 헤더가 아닌 이벤트 데이터 body에 포함
|
|
31
28
|
|
|
32
29
|
return headers;
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
33
|
* 이벤트 추적용 헤더 생성
|
|
37
|
-
*
|
|
34
|
+
*
|
|
35
|
+
* 디바이스 `userAgent`는 HTTP 헤더가 아니라 요청 body(eventData) 또는 브라우저가
|
|
36
|
+
* 자동 첨부하는 `User-Agent`로 전달된다. (서버 advertisement 컨트롤러는
|
|
37
|
+
* `req.headers['user-agent']`를 직접 사용.) 헤더 수동 override는 Safari CORS
|
|
38
|
+
* preflight를 깨뜨리므로 하지 않는다. 시그니처는 호출처 호환을 위해 유지.
|
|
38
39
|
*/
|
|
39
|
-
static createForEvents(apiKey: string,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// User-Agent 오버라이드 (서버에서 실제 사용)
|
|
43
|
-
if (eventData?.userAgent) {
|
|
44
|
-
baseHeaders['User-Agent'] = eventData.userAgent;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// 다른 정보들은 HTTP 헤더가 아닌 이벤트 데이터 body에 포함하는 것이 적절
|
|
48
|
-
// (currentUrl, referrer 등은 POST body로 전송)
|
|
49
|
-
|
|
50
|
-
return baseHeaders;
|
|
40
|
+
static createForEvents(apiKey: string, _eventData?: Record<string, any>): Record<string, string> {
|
|
41
|
+
return ApiHeaders.create(apiKey);
|
|
51
42
|
}
|
|
52
43
|
}
|