@vircle/sdk-web 0.4.0 → 0.6.1

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 CHANGED
@@ -23,7 +23,7 @@ VircleCore를 확장하여 웹 환경에 특화된 기능을 제공하는 웹
23
23
 
24
24
  ### 세션 관리
25
25
  - **세션 타임아웃**: 비활성 상태 감지 후 자동 세션 로테이션 (기본 30분)
26
- - **fetch+keepalive**: 페이지 종료 시 인증 헤더 포함 이벤트 전송
26
+ - **fetch+keepalive**: 페이지 종료 시 `Authorization` 헤더 포함 이벤트 전송
27
27
 
28
28
  ### 보안 및 프라이버시
29
29
  - **페이로드 암호화**: Web Crypto API 기반 하이브리드 암호화 (AES-256-GCM + RSA-OAEP)
@@ -162,7 +162,7 @@ await vircle.identify('user-123', {
162
162
  │ • 자동 추적 (페이지뷰/클릭/폼/에러/SPA) │
163
163
  │ • 웹 컨텍스트 수집 (WebContextCollector) │
164
164
  │ • 세션 타임아웃 / 로테이션 │
165
- │ • sendBeacon (페이지 이탈 보호)
165
+ │ • fetch+keepalive (페이지 이탈 보호)
166
166
  │ • StorageFactory (IndexedDB/LocalStorage) │
167
167
  │ • 하이브리드 암호화 (WebExtendedCryptoAdapter)│
168
168
  │ • 에러 클래스 (VircleWebError 계열) │
@@ -244,6 +244,22 @@ User-Agent 기반 디바이스/브라우저/OS 감지 및 화면 정보 수집
244
244
  | `version` | `string \| undefined` | `<meta name="version">` | 앱 버전 |
245
245
  | `environment` | `'development' \| 'production'` | `window.location.hostname` | localhost/staging → development, 그 외 → production |
246
246
 
247
+ #### campaign (CampaignContext)
248
+
249
+ URL의 UTM 파라미터 및 광고 클릭 ID를 수집합니다. `sessionStorage`에 저장하여 MPA 페이지 전환 간에도 유지됩니다.
250
+
251
+ | 필드 | 타입 | 조건 | 설명 |
252
+ |------|------|------|------|
253
+ | `source` | `string` | `utm_source` 존재 시 | 트래픽 소스 (예: `google`) |
254
+ | `medium` | `string` | `utm_medium` 존재 시 | 트래픽 매체 (예: `cpc`) |
255
+ | `campaign` | `string` | `utm_campaign` 존재 시 | 캠페인 이름 |
256
+ | `term` | `string` | `utm_term` 존재 시 | 검색 키워드 |
257
+ | `content` | `string` | `utm_content` 존재 시 | 광고 콘텐츠 구분 |
258
+ | `gclid` | `string` | `gclid` 존재 시 | Google Ads 클릭 ID |
259
+ | `fbclid` | `string` | `fbclid` 존재 시 | Facebook 클릭 ID |
260
+ | `msclkid` | `string` | `msclkid` 존재 시 | Microsoft Ads 클릭 ID |
261
+ | `ttclid` | `string` | `ttclid` 존재 시 | TikTok 클릭 ID |
262
+
247
263
  #### custom (Record<string, unknown>)
248
264
 
249
265
  추가 브라우저 환경 정보 (가용한 경우에만 수집)
@@ -251,7 +267,6 @@ User-Agent 기반 디바이스/브라우저/OS 감지 및 화면 정보 수집
251
267
  | 필드 | 타입 | 조건 | 설명 |
252
268
  |------|------|------|------|
253
269
  | `referrer` | `string` | `document.referrer` 존재 시 | 이전 페이지 URL |
254
- | `campaign` | `{ source, medium, campaign, term, content }` | URL에 `utm_*` 파라미터 존재 시 | UTM 캠페인 파라미터 (`utm_` 접두사 제거) |
255
270
  | `cookieEnabled` | `boolean` | 항상 | 쿠키 활성화 여부 |
256
271
  | `doNotTrack` | `boolean` | `navigator.doNotTrack` 존재 시 | DNT 설정 여부 |
257
272
  | `connection` | `{ effectiveType, downlink, rtt, saveData }` | Network Information API 지원 시 | 네트워크 연결 정보 |
@@ -284,13 +299,13 @@ User-Agent 기반 디바이스/브라우저/OS 감지 및 화면 정보 수집
284
299
  version: '2.1.0',
285
300
  environment: 'production'
286
301
  },
302
+ campaign: {
303
+ source: 'google',
304
+ medium: 'cpc',
305
+ campaign: 'summer_sale'
306
+ },
287
307
  custom: {
288
308
  referrer: 'https://google.com',
289
- campaign: {
290
- source: 'google',
291
- medium: 'cpc',
292
- campaign: 'summer_sale'
293
- },
294
309
  cookieEnabled: true,
295
310
  doNotTrack: false,
296
311
  connection: {
@@ -398,14 +413,14 @@ const vircle = new VircleWeb({
398
413
  });
399
414
  ```
400
415
 
401
- ### sendBeacon (페이지 이탈 보호)
416
+ ### fetch+keepalive (페이지 이탈 보호)
402
417
 
403
- 페이지 종료 시 `navigator.sendBeacon`을 사용하여 대기 중인 이벤트를 안전하게 전송합니다:
418
+ 페이지 종료 시 `fetch()` + `keepalive: true` 옵션을 사용하여 대기 중인 이벤트를 안전하게 전송합니다:
404
419
 
405
- - **beforeunload**: 페이지 닫기/새로고침 시 큐의 이벤트를 beacon으로 전송
406
- - **visibilitychange → hidden**: 탭 전환/최소화 시 beacon으로 전송
407
- - **제약사항**: sendBeacon은 커스텀 헤더를 설정할없으므로 API 키를 요청 body에 포함하며, 암호화는 스킵됩니다
408
- - **폴백**: `navigator.sendBeacon` 미지원 환경에서는 자동으로 무시됩니다
420
+ - **beforeunload**: 페이지 닫기/새로고침 시 큐의 이벤트를 keepalive fetch로 전송
421
+ - **visibilitychange → hidden**: 탭 전환/최소화 시 keepalive fetch로 전송
422
+ - **장점**: `Authorization` 헤더를 포함할있어 인증된 요청으로 전송 가능
423
+ - **폴백**: `keepalive` 미지원 환경에서는 자동으로 무시됩니다
409
424
 
410
425
  ```typescript
411
426
  const vircle = new VircleWeb(
@@ -445,7 +460,7 @@ try {
445
460
  - **컨텍스트 캐싱**: ContextCache를 통한 브라우저 정보 캐싱 (기본 5분), 중복 수집 방지를 위한 Promise 재사용
446
461
  - **이벤트 배칭**: TransportService의 큐를 통한 네트워크 요청 최소화
447
462
  - **메모리 관리**: cleanup 시 모든 이벤트 리스너 제거 및 History API 원복으로 누수 방지
448
- - **페이지 이탈 대응**: `navigator.sendBeacon`을 사용한 페이지 종료 시 안정적 이벤트 전송
463
+ - **페이지 이탈 대응**: `fetch()+keepalive`를 사용한 페이지 종료 시 안정적 이벤트 전송
449
464
  - **큐 크기 제한**: TaskScheduler의 `maxQueueSize`(기본 1000)로 메모리 오버플로우 방지
450
465
 
451
466
  ### 추적 제외
@@ -502,7 +517,7 @@ interface VircleWebConfig extends VircleConfig {
502
517
 
503
518
  ```typescript
504
519
  interface VircleWebOptions extends Partial<VircleCoreOptions> {
505
- flushOnUnload?: boolean; // 페이지 언로드 시 sendBeacon 전송 (기본: true)
520
+ flushOnUnload?: boolean; // 페이지 언로드 시 fetch+keepalive 전송 (기본: true)
506
521
  contextCacheTime?: number; // 컨텍스트 캐시 시간(ms) (기본: 300000 = 5분)
507
522
  }
508
523
  ```
@@ -618,6 +633,10 @@ import type { VircleWebConfig, VircleWebOptions } from '@vircle/sdk-web';
618
633
  // 컨텍스트 수집기
619
634
  import { WebContextCollector } from '@vircle/sdk-web';
620
635
 
636
+ // F-4b Link Decoration — outbound link에 ?_vuid= 자동 부착 (프로그래밍 API)
637
+ import { LinkDecorator } from '@vircle/sdk-web';
638
+ import type { LinkDecoratorOptions } from '@vircle/sdk-web';
639
+
621
640
  // 스토리지
622
641
  import { LocalStorageAdapter, IndexedDBAdapter, StorageFactory } from '@vircle/sdk-web';
623
642
  import type { StorageType } from '@vircle/sdk-web'; // 'auto' | 'localStorage' | 'indexedDB'
@@ -678,7 +697,7 @@ import type {
678
697
 
679
698
  4. **암호화**: `enableEncryption: true` 설정 시 서버 공개키로 이벤트 페이로드를 암호화합니다. 공개키는 CDN(`RemoteConfigService`)에서 자동 fetch됩니다.
680
699
 
681
- 5. **sendBeacon 전송**: 페이지 종료 시 `sendBeacon`으로 전송되는 이벤트는 커스텀 헤더 설정이 불가하여 암호화/HMAC 서명 없이 전송됩니다. API 키는 요청 body에 포함됩니다. 정상 동작 중에는 `fetch()` 기반의 암호화된 전송이 사용됩니다.
700
+ 5. **페이지 이탈 전송**: 페이지 종료 시 `fetch()+keepalive`로 전송되며, `Authorization` 헤더를 포함한 인증된 요청으로 전송됩니다. 정상 동작과 동일한 `fetch()` 기반 전송을 사용합니다.
682
701
 
683
702
  ## 성능 영향
684
703
 
@@ -762,7 +781,7 @@ const vircle = new VircleWeb(
762
781
  },
763
782
  {
764
783
  contextCacheTime: 600000, // 10분 캐싱 (기본 5분)
765
- flushOnUnload: true, // sendBeacon으로 페이지 이탈 시 자동 전송 (기본값)
784
+ flushOnUnload: true, // fetch+keepalive로 페이지 이탈 시 자동 전송 (기본값)
766
785
  },
767
786
  );
768
787
 
@@ -938,6 +957,37 @@ await vircle.identify<CustomUserTraits>('user-123', {
938
957
  });
939
958
  ```
940
959
 
960
+ ## Identity Graph 강화
961
+
962
+ Meta CAPI EMQ 7+ 도달을 위한 호스트 통합(PII 해시, consent, 크로스 도메인 link decoration, Meta Pixel 활성화 등):
963
+
964
+ - [호스트 통합 가이드](./docs/host-integration-guide.md) — `vircle.identify()` PII 전달, `setConsent()`, `linkDecoration` 설정
965
+ - [Cafe24 PII 수집 가이드](../plugin-cafe24/docs/cafe24-pii-collection.md) — Cafe24 자사몰 환경 한정
966
+
967
+ 핵심 설정 예시:
968
+
969
+ ```typescript
970
+ new VircleWeb({
971
+ apiKey: 'vk_prod_...',
972
+ storageType: 'localStorage',
973
+ accountSwitchDetectionEnabled: true,
974
+ defaultConsent: { analytics: true, marketing: false, functional: true },
975
+ linkDecoration: {
976
+ enabled: true,
977
+ allowedDomains: ['.vircle.co.kr', 'shop.example.com'],
978
+ },
979
+ });
980
+
981
+ // 호스트 동의관리 응답 후
982
+ vircle.setConsent({ analytics: true, marketing: true, functional: true });
983
+
984
+ // PII는 SDK가 정규화 + SHA-256 해시 → em/ph array로 전송 (평문 키는 strip)
985
+ await vircle.identify(userId, {
986
+ email: 'user@example.com',
987
+ phone: '+82 10-1234-5678',
988
+ });
989
+ ```
990
+
941
991
  ## 라이선스
942
992
 
943
993
  MIT
package/dist/index.d.ts CHANGED
@@ -448,6 +448,29 @@ interface VircleWebConfig extends VircleConfig {
448
448
  * 지정된 시간 동안 비활성 상태이면 새 세션을 시작합니다.
449
449
  */
450
450
  sessionTimeout?: number;
451
+ /**
452
+ * F-4b 크로스 도메인 Link Decoration — outbound link에 `?_vuid=`를 click 시점에 자동 부착.
453
+ * 같은 partner의 여러 도메인 간(예: 라운지 ↔ Cafe24 자사몰) vuid 전파 자동화.
454
+ *
455
+ * 동작:
456
+ * - 페이지 로드 시 LinkDecorator가 capture-phase event delegation 등록 (mousedown/click/auxclick)
457
+ * - 사용자가 a[href] 클릭 시 link href가 `allowedDomains` 매치되면 `?_vuid=<anonymousId>` 부착
458
+ * - 정적 URL을 미리 변경하지 않음 — PR 미리보기·OG 태그 등에 vuid 노출 회피
459
+ * - 컨텍스트 메뉴("Copy link", "Open in new tab")도 mousedown 시점 갱신으로 커버
460
+ * - `allowedDomains` 매치되지 않은 외부 사이트에는 부착 안 함 (vuid 누출 방지)
461
+ */
462
+ linkDecoration?: {
463
+ /** 기본 false. true로 명시해야 활성화 */
464
+ enabled?: boolean;
465
+ /**
466
+ * vuid를 부착할 도메인 화이트리스트. hostname suffix 매칭.
467
+ * 예: ['shop.vircle.co.kr', 'lounge.vircle.co.kr'] → 정확 매치
468
+ * ['.vircle.co.kr'] → 서브도메인 포함 매치
469
+ */
470
+ allowedDomains?: string[];
471
+ /** URL 파라미터 이름. 기본 `_vuid` */
472
+ paramName?: string;
473
+ };
451
474
  }
452
475
  interface VircleWebOptions extends Partial<VircleCoreOptions> {
453
476
  /**
@@ -489,6 +512,7 @@ declare class VircleWeb extends VircleCore {
489
512
  private originalReplaceState?;
490
513
  private lastActivityTime;
491
514
  private sessionTimeout;
515
+ private linkDecorator?;
492
516
  constructor(config: VircleWebConfig, options?: VircleWebOptions);
493
517
  /**
494
518
  * SDK 초기화 및 자동 추적 설정
@@ -577,6 +601,11 @@ declare class VircleWeb extends VircleCore {
577
601
  * @private
578
602
  */
579
603
  private setupAutoTracking;
604
+ /**
605
+ * F-4b Link Decoration 활성화 — outbound link click 시 ?_vuid= 자동 부착.
606
+ * @private
607
+ */
608
+ private setupLinkDecoration;
580
609
  /**
581
610
  * 에러 추적 설정
582
611
  *
@@ -727,7 +756,17 @@ declare class WebContextCollector {
727
756
  */
728
757
  private static readonly CAMPAIGN_PARAMS_MAP;
729
758
  private static readonly STORAGE_PREFIX;
759
+ /** localStorage 영구 백업 키 (PG 리다이렉트 등으로 sessionStorage 유실 시 복원용) */
760
+ private static readonly CAMPAIGN_PERSIST_KEY;
761
+ /** localStorage 캠페인 데이터 만료 기간: 7일 (광고 어트리뷰션 윈도우) */
762
+ private static readonly CAMPAIGN_PERSIST_TTL_MS;
730
763
  collect(): Promise<EventContext>;
764
+ /**
765
+ * Meta `_fbp`/`_fbc` 쿠키 read-only 수집. 호스트가 Meta Pixel을 설치했거나
766
+ * plugin-cafe24의 MetaPixelLoader가 쿠키를 발급해 둔 경우 read한다.
767
+ * 둘 다 부재 시 undefined 반환 (EventContext.meta omit).
768
+ */
769
+ private collectMetaContext;
731
770
  /**
732
771
  * 디바이스 정보 수집
733
772
  */
@@ -765,10 +804,19 @@ declare class WebContextCollector {
765
804
  */
766
805
  private getBrowserVersion;
767
806
  /**
768
- * 캠페인 파라미터 수집 (UTM 5 + 광고 클릭 ID 4)
807
+ * 캠페인 파라미터 수집 (UTM 5 + 광고 클릭 ID 5 + 네이버 검색광고 8)
769
808
  * sessionStorage를 사용하여 MPA 페이지 전환 간에도 유지
770
809
  */
771
810
  private collectCampaignContext;
811
+ /**
812
+ * 캠페인 파라미터를 localStorage에 타임스탬프와 함께 백업.
813
+ * PG 리다이렉트로 새 탭이 열리거나 sessionStorage가 초기화되는 경우 복원용.
814
+ */
815
+ private persistCampaignToLocalStorage;
816
+ /**
817
+ * localStorage에서 캠페인 파라미터를 복원. 7일 초과 시 만료 처리.
818
+ */
819
+ private restoreCampaignFromLocalStorage;
772
820
  /**
773
821
  * 앱 이름 가져오기 (meta 태그에서)
774
822
  */
@@ -783,6 +831,68 @@ declare class WebContextCollector {
783
831
  private getEnvironment;
784
832
  }
785
833
 
834
+ /**
835
+ * @module LinkDecorator
836
+ *
837
+ * F-4b 크로스 도메인 Link Decoration — outbound link에 `?_vuid=` click 시점 자동 부착.
838
+ *
839
+ * 같은 partner의 여러 도메인 간(예: 라운지 ↔ Cafe24 자사몰) vuid 전파 자동화.
840
+ * GA4 `linker` plugin과 동일 패턴.
841
+ *
842
+ * 설계 결정:
843
+ * - **정적 URL 미변경**: 페이지 로드 시 `<a href>` 속성을 직접 수정하지 않는다. PR 미리보기·OG 태그 등
844
+ * 에서 vuid 노출 회피.
845
+ * - **click 시점 동적 부착**: capturing-phase click 리스너가 `a[href]` 매치 시 즉시 href 갱신
846
+ * (delegate 방식 — 동적으로 추가된 link도 자동 커버).
847
+ * - **MutationObserver 불필요**: capture-phase delegate가 동적 콘텐츠도 처리하므로 별도 observer 불요
848
+ * (성능·복잡도 절감).
849
+ * - **allowedDomains 화이트리스트**: 매치 안 된 외부 사이트에는 부착 안 함 (vuid 누출 방지).
850
+ *
851
+ * Limitations:
852
+ * - JavaScript 비활성 환경에선 동작 안 함 (정적 URL 미변경 트레이드오프)
853
+ * - middle-click/wheel-click도 처리하기 위해 'auxclick' 이벤트도 리슨
854
+ * - `target="_blank"` 새 창 link도 동일 처리
855
+ */
856
+ interface LinkDecoratorOptions {
857
+ /** vuid를 부착할 도메인 화이트리스트. hostname suffix 매칭 */
858
+ allowedDomains: string[];
859
+ /** URL 파라미터 이름 (기본 `_vuid`) */
860
+ paramName?: string;
861
+ /** 현재 anonymousId를 반환하는 콜백 — click 시점에 호출 */
862
+ getAnonymousId: () => string | undefined;
863
+ }
864
+ declare class LinkDecorator {
865
+ private allowedDomains;
866
+ private paramName;
867
+ private getAnonymousId;
868
+ private clickHandler?;
869
+ constructor(options: LinkDecoratorOptions);
870
+ /**
871
+ * mousedown/click/auxclick 이벤트 capture-phase delegate 등록. 동적 추가 link도 자동 커버.
872
+ * SSR/non-DOM 환경(typeof document === 'undefined')에서는 no-op.
873
+ *
874
+ * 이벤트별 역할:
875
+ * - `mousedown`: 우클릭 후 "Copy link address" / "Open in new tab" 컨텍스트 메뉴 케이스 커버
876
+ * (이 흐름에서는 click이 발생하지 않으므로 mousedown 시점에 href 갱신)
877
+ * - `click`: 키보드 네비게이션(focus + Enter) 등 mousedown 미발생 케이스 백업
878
+ * - `auxclick`: middle-click (휠 클릭으로 새 탭 열기)
879
+ */
880
+ start(): void;
881
+ stop(): void;
882
+ /**
883
+ * 외부 호출용 — 임의 URL에 vuid 파라미터 부착. 화이트리스트 매치되지 않으면 원본 URL 반환.
884
+ * 호스트가 동적으로 만든 URL 등에 사용 가능.
885
+ */
886
+ decorate(href: string): string;
887
+ private handleClick;
888
+ /**
889
+ * hostname 화이트리스트 매칭.
890
+ * - `.example.com` → suffix match (서브도메인 포함)
891
+ * - `example.com` → exact match
892
+ */
893
+ private isAllowedDomain;
894
+ }
895
+
786
896
  /**
787
897
  * Web Crypto API 기반 암호화 어댑터
788
898
  *
@@ -819,6 +929,11 @@ declare class WebExtendedCryptoAdapter implements ExtendedCryptoAdapter {
819
929
  * 2. RSA-OAEP로 AES 키 암호화
820
930
  */
821
931
  encryptPayload(data: Record<string, any>, publicKeyBase64: string): Promise<EncryptedPayload>;
932
+ /**
933
+ * SHA-256 해시 — Meta CAPI em/ph 등 PII 매칭 키 생성용.
934
+ * Web Crypto API의 crypto.subtle.digest 사용.
935
+ */
936
+ sha256(input: string): Promise<string>;
822
937
  /**
823
938
  * Web Crypto API 사용 가능 여부
824
939
  */
@@ -941,5 +1056,5 @@ declare class VircleBrowserCompatibilityError extends VircleWebError {
941
1056
  constructor(message: string, details?: Record<string, any>);
942
1057
  }
943
1058
 
944
- export { IndexedDBAdapter, LocalStorageAdapter, StorageFactory, VircleBrowserCompatibilityError, VircleConfigError, VircleInitializationError, VircleStorageError, VircleWeb, VircleWebError, WebContextCollector, WebExtendedCryptoAdapter, VircleWeb as default };
945
- export type { StorageType, VircleWebConfig, VircleWebOptions };
1059
+ export { IndexedDBAdapter, LinkDecorator, LocalStorageAdapter, StorageFactory, VircleBrowserCompatibilityError, VircleConfigError, VircleInitializationError, VircleStorageError, VircleWeb, VircleWebError, WebContextCollector, WebExtendedCryptoAdapter, VircleWeb as default };
1060
+ export type { LinkDecoratorOptions, StorageType, VircleWebConfig, VircleWebOptions };