@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adstage/web-sdk",
3
- "version": "3.0.13",
3
+ "version": "3.0.15",
4
4
  "description": "AdStage Web SDK - Production-ready marketing platform SDK with React Provider support for seamless integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
@@ -109,8 +109,11 @@ export class AdStage {
109
109
  }
110
110
  }
111
111
 
112
- // 서버에 Attribution 자동 등록 (클릭 ID 있는 경우, 단 URL로 이미 전달된 경우는 생략)
113
- if (!aidFromUrl && trackingParams && TrackingParamsModule.hasClickId()) {
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 발견되면 true 반환
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 || !TrackingParamsModule.hasClickId()) {
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
- // 현재 클릭 ID 추출
233
- const currentClickId =
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 (!currentClickId) {
256
+ if (!currentIdentifier) {
237
257
  return false;
238
258
  }
239
259
 
240
- // 이미 처리된 클릭 ID인지 확인 (중복 방지)
260
+ // 이미 처리된 식별자인지 확인 (중복 방지)
241
261
  const lastClickId = sessionStorage.getItem('adstage_last_click_id');
242
262
 
243
- // 새로운 클릭 ID면 전송 필요
244
- if (currentClickId !== lastClickId) {
245
- sessionStorage.setItem('adstage_last_click_id', currentClickId);
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;
@@ -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
- // User-Agent는 이벤트 추적에서 실제로 사용됨
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
- * User-Agent는 서버에서 실제로 사용됨
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, eventData?: Record<string, any>): Record<string, string> {
40
- const baseHeaders = ApiHeaders.create(apiKey);
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
  }