@roottale/analytics-runtime 0.3.0 → 0.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/dist/chunk-DIN7LIST.js +95 -0
- package/dist/chunk-DIN7LIST.js.map +1 -0
- package/dist/{chunk-FPKZZSGL.js → chunk-G7OLBHT5.js} +62 -52
- package/dist/chunk-G7OLBHT5.js.map +1 -0
- package/dist/chunk-GBIGQHVN.js +123 -0
- package/dist/chunk-GBIGQHVN.js.map +1 -0
- package/dist/chunk-GW523RS4.js +200 -0
- package/dist/chunk-GW523RS4.js.map +1 -0
- package/dist/events.d.ts +147 -0
- package/dist/events.js +11 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +117 -3
- package/dist/index.js +33 -5
- package/dist/index.js.map +1 -1
- package/dist/next.d.ts +7 -0
- package/dist/next.js +33 -0
- package/dist/next.js.map +1 -0
- package/dist/react.d.ts +16 -1
- package/dist/react.js +32 -4
- package/dist/react.js.map +1 -1
- package/package.json +16 -1
- package/dist/chunk-FPKZZSGL.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/observers.ts"],"sourcesContent":["// 설계(measurement-design-draft.md) §1(2단계) — 브라우저 감지기: section_view·\n// scroll_depth·read_complete·form_start·form_abandon.\n//\n// 인라인 RUNTIME 문자열이 아닌 일반 ESM — react/next 어댑터가 import 해\n// 호출한다. IntersectionObserver 인스턴스·WeakMap 상태 등 모듈 스코프 가변\n// 상태가 필요해 beacon/loader 처럼 문자열 템플릿으로 표현하기 부적합하다\n// (send() 하나만 훅하면 되는 ga4-bridge/journey 와는 성격이 다르다).\n//\n// data 속성 계약(starter 등 소비 측이 emit):\n// data-track-view=\"<section_id>\" section_view, 뷰포트 도달 1회/페이지\n// article[data-track-read=\"<pid>\"] read_complete, 75%+15초 체류\n// form[data-track-form=\"<form_id>\"] form_start(첫 focusin)·form_abandon\n//\n// SPA 재바인드 계약: pathname 변화 시 next.tsx 어댑터가\n// teardownObservers() → initObservers() 를 호출해 새 DOM 에 옵저버를\n// 재바인드한다. 모든 발화는 `trackCustomEvent()`(beacon.ts) 를 거치므로\n// journey/GA4 미러 훅에도 동일하게 흘러간다(단, 본 파일 이벤트는 전부\n// events.ts 상 journey:false·ga4:false — 고빈도/참여 신호라 훅은 그냥\n// no-op한다).\n\nimport { trackCustomEvent } from \"./beacon\";\n\nconst SCROLL_MILESTONES = [25, 50, 75, 100] as const;\nconst READ_COMPLETE_THRESHOLD = 0.75;\nconst READ_COMPLETE_DWELL_MS = 15_000;\n\ninterface FormTrackState {\n started: boolean;\n dirty: boolean;\n submitted: boolean;\n lastField?: string;\n}\n\nlet sectionObserver: IntersectionObserver | null = null;\nlet readObserver: IntersectionObserver | null = null;\nlet readTimer: ReturnType<typeof setTimeout> | null = null;\nlet readFired = false;\nlet scrollFired = new Set<number>();\nlet scrollHandler: (() => void) | null = null;\nlet formFocusHandler: ((e: Event) => void) | null = null;\nlet formInputHandler: ((e: Event) => void) | null = null;\nlet formSubmitHandler: ((e: Event) => void) | null = null;\nlet pagehideHandler: ((e: PageTransitionEvent) => void) | null = null;\n\nconst formState = new WeakMap<Element, FormTrackState>();\n\nfunction initSectionView(): void {\n const els = document.querySelectorAll<HTMLElement>(\"[data-track-view]\");\n if (els.length === 0) return;\n const fired = new Set<Element>();\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (!entry.isIntersecting || fired.has(entry.target)) continue;\n fired.add(entry.target);\n const sectionId = entry.target.getAttribute(\"data-track-view\");\n trackCustomEvent(\n \"section_view\",\n sectionId ? { section_id: sectionId } : undefined,\n );\n observer.unobserve(entry.target);\n }\n },\n { threshold: 0.5 },\n );\n els.forEach((el) => observer.observe(el));\n sectionObserver = observer;\n}\n\nfunction initScrollDepth(): void {\n scrollFired = new Set<number>();\n const onScroll = (): void => {\n const doc = document.documentElement;\n const scrollable = doc.scrollHeight - doc.clientHeight;\n // 스크롤 가능한 영역이 없는(뷰포트보다 짧은) 페이지는 전부 보인 것으로\n // 간주 — 100% 로 간주해 4개 이정표를 즉시 만족시킨다(의도된 동작).\n const pct =\n scrollable <= 0\n ? 100\n : Math.round((window.scrollY / scrollable) * 100);\n for (const milestone of SCROLL_MILESTONES) {\n if (pct >= milestone && !scrollFired.has(milestone)) {\n scrollFired.add(milestone);\n trackCustomEvent(\"scroll_depth\", { pct: milestone });\n }\n }\n };\n scrollHandler = onScroll;\n window.addEventListener(\"scroll\", onScroll, { passive: true });\n onScroll();\n}\n\nfunction initReadComplete(): void {\n const article = document.querySelector<HTMLElement>(\"[data-track-read]\");\n if (!article) return;\n readFired = false;\n const observer = new IntersectionObserver(\n (entries) => {\n const entry = entries[0];\n if (!entry) return;\n const visibleEnough =\n entry.isIntersecting &&\n entry.intersectionRatio >= READ_COMPLETE_THRESHOLD;\n if (visibleEnough) {\n if (readTimer || readFired) return;\n // heuristic: 75%+ 가시 상태가 15초 연속 유지될 때만 발화. 중간에\n // 임계 아래로 떨어지면 타이머를 취소(재진입 시 처음부터 다시 잰다).\n readTimer = setTimeout(() => {\n readTimer = null;\n if (readFired) return;\n readFired = true;\n const pid = article.getAttribute(\"data-track-read\");\n trackCustomEvent(\"read_complete\", pid ? { pid } : undefined);\n }, READ_COMPLETE_DWELL_MS);\n } else if (readTimer) {\n clearTimeout(readTimer);\n readTimer = null;\n }\n },\n { threshold: READ_COMPLETE_THRESHOLD },\n );\n observer.observe(article);\n readObserver = observer;\n}\n\nfunction trackedForm(target: EventTarget | null): Element | null {\n const el = target as Element | null;\n return el?.closest?.(\"form[data-track-form]\") ?? null;\n}\n\nfunction initFormTracking(): void {\n const onFocusIn = (e: Event): void => {\n const form = trackedForm(e.target);\n if (!form) return;\n const state = formState.get(form) ?? {\n started: false,\n dirty: false,\n submitted: false,\n };\n if (!state.started) {\n state.started = true;\n const formId = form.getAttribute(\"data-track-form\");\n trackCustomEvent(\"form_start\", formId ? { form_id: formId } : undefined);\n }\n formState.set(form, state);\n };\n const onInput = (e: Event): void => {\n const form = trackedForm(e.target);\n if (!form) return;\n const state = formState.get(form) ?? {\n started: false,\n dirty: false,\n submitted: false,\n };\n state.dirty = true;\n const target = e.target as { name?: string; id?: string } | null;\n const fieldName = target?.name || target?.id;\n if (fieldName) state.lastField = fieldName;\n formState.set(form, state);\n };\n const onSubmit = (e: Event): void => {\n const form = e.target as Element | null;\n if (!form?.matches?.(\"form[data-track-form]\")) return;\n const state = formState.get(form);\n if (state) {\n state.submitted = true;\n formState.set(form, state);\n }\n };\n document.addEventListener(\"focusin\", onFocusIn, true);\n document.addEventListener(\"input\", onInput, true);\n document.addEventListener(\"change\", onInput, true);\n document.addEventListener(\"submit\", onSubmit, true);\n formFocusHandler = onFocusIn;\n formInputHandler = onInput;\n formSubmitHandler = onSubmit;\n}\n\nfunction initFormAbandon(): void {\n const onPageHide = (e: PageTransitionEvent): void => {\n // heuristic(설계 §1 P1-5, KPI 산정 제외): dirty && !event.persisted &&\n // 미제출. bfcache 로 보존되는(persisted=true) 페이지 은닉은 실제 이탈이\n // 아니므로 제외 — 근사 신호일 뿐이라 journey:false·ga4:false.\n if (e.persisted) return;\n document\n .querySelectorAll<HTMLFormElement>(\"form[data-track-form]\")\n .forEach((form) => {\n const state = formState.get(form);\n if (!state || !state.dirty || state.submitted) return;\n const formId = form.getAttribute(\"data-track-form\");\n trackCustomEvent(\"form_abandon\", {\n ...(formId ? { form_id: formId } : {}),\n ...(state.lastField ? { last_field: state.lastField } : {}),\n });\n });\n };\n pagehideHandler = onPageHide;\n window.addEventListener(\"pagehide\", onPageHide);\n}\n\n/**\n * 감지기 초기화 — section_view·scroll_depth·read_complete·form_start·\n * form_abandon. SSR/비브라우저 환경에서는 no-op. teardownObservers() 없이\n * 재호출하면 중복 바인딩될 수 있다 — 어댑터는 pathname 변화 시 teardown →\n * init 순서를 지킨다(SPA 재바인드 계약).\n */\nexport function initObservers(): void {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return;\n initSectionView();\n initScrollDepth();\n initReadComplete();\n initFormTracking();\n initFormAbandon();\n}\n\n/** 모든 감지기 해제 — SPA pathname 변화 시 재바인드 전에 호출한다. */\nexport function teardownObservers(): void {\n sectionObserver?.disconnect();\n sectionObserver = null;\n readObserver?.disconnect();\n readObserver = null;\n if (readTimer) {\n clearTimeout(readTimer);\n readTimer = null;\n }\n readFired = false;\n if (scrollHandler) {\n window.removeEventListener(\"scroll\", scrollHandler);\n scrollHandler = null;\n }\n scrollFired = new Set<number>();\n if (formFocusHandler) {\n document.removeEventListener(\"focusin\", formFocusHandler, true);\n formFocusHandler = null;\n }\n if (formInputHandler) {\n document.removeEventListener(\"input\", formInputHandler, true);\n document.removeEventListener(\"change\", formInputHandler, true);\n formInputHandler = null;\n }\n if (formSubmitHandler) {\n document.removeEventListener(\"submit\", formSubmitHandler, true);\n formSubmitHandler = null;\n }\n if (pagehideHandler) {\n window.removeEventListener(\"pagehide\", pagehideHandler);\n pagehideHandler = null;\n }\n}\n\ndeclare global {\n interface Window {\n /**\n * react.tsx(RSC, hookless) 가 명시적으로 `enableObservers={false}` 일 때만\n * `false` 를 인라인 스크립트로 심는다 — next.tsx 어댑터가 initObservers()\n * 호출 전 이 값을 확인한다(미설정 시 기본 true, \"기본 true\" 계약 구현).\n */\n __rtObserversEnabled?: boolean;\n }\n}\n"],"mappings":";;;;;AAsBA,IAAM,oBAAoB,CAAC,IAAI,IAAI,IAAI,GAAG;AAC1C,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAS/B,IAAI,kBAA+C;AACnD,IAAI,eAA4C;AAChD,IAAI,YAAkD;AACtD,IAAI,YAAY;AAChB,IAAI,cAAc,oBAAI,IAAY;AAClC,IAAI,gBAAqC;AACzC,IAAI,mBAAgD;AACpD,IAAI,mBAAgD;AACpD,IAAI,oBAAiD;AACrD,IAAI,kBAA6D;AAEjE,IAAM,YAAY,oBAAI,QAAiC;AAEvD,SAAS,kBAAwB;AAC/B,QAAM,MAAM,SAAS,iBAA8B,mBAAmB;AACtE,MAAI,IAAI,WAAW,EAAG;AACtB,QAAM,QAAQ,oBAAI,IAAa;AAC/B,QAAM,WAAW,IAAI;AAAA,IACnB,CAAC,YAAY;AACX,iBAAW,SAAS,SAAS;AAC3B,YAAI,CAAC,MAAM,kBAAkB,MAAM,IAAI,MAAM,MAAM,EAAG;AACtD,cAAM,IAAI,MAAM,MAAM;AACtB,cAAM,YAAY,MAAM,OAAO,aAAa,iBAAiB;AAC7D;AAAA,UACE;AAAA,UACA,YAAY,EAAE,YAAY,UAAU,IAAI;AAAA,QAC1C;AACA,iBAAS,UAAU,MAAM,MAAM;AAAA,MACjC;AAAA,IACF;AAAA,IACA,EAAE,WAAW,IAAI;AAAA,EACnB;AACA,MAAI,QAAQ,CAAC,OAAO,SAAS,QAAQ,EAAE,CAAC;AACxC,oBAAkB;AACpB;AAEA,SAAS,kBAAwB;AAC/B,gBAAc,oBAAI,IAAY;AAC9B,QAAM,WAAW,MAAY;AAC3B,UAAM,MAAM,SAAS;AACrB,UAAM,aAAa,IAAI,eAAe,IAAI;AAG1C,UAAM,MACJ,cAAc,IACV,MACA,KAAK,MAAO,OAAO,UAAU,aAAc,GAAG;AACpD,eAAW,aAAa,mBAAmB;AACzC,UAAI,OAAO,aAAa,CAAC,YAAY,IAAI,SAAS,GAAG;AACnD,oBAAY,IAAI,SAAS;AACzB,yBAAiB,gBAAgB,EAAE,KAAK,UAAU,CAAC;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACA,kBAAgB;AAChB,SAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAC7D,WAAS;AACX;AAEA,SAAS,mBAAyB;AAChC,QAAM,UAAU,SAAS,cAA2B,mBAAmB;AACvE,MAAI,CAAC,QAAS;AACd,cAAY;AACZ,QAAM,WAAW,IAAI;AAAA,IACnB,CAAC,YAAY;AACX,YAAM,QAAQ,QAAQ,CAAC;AACvB,UAAI,CAAC,MAAO;AACZ,YAAM,gBACJ,MAAM,kBACN,MAAM,qBAAqB;AAC7B,UAAI,eAAe;AACjB,YAAI,aAAa,UAAW;AAG5B,oBAAY,WAAW,MAAM;AAC3B,sBAAY;AACZ,cAAI,UAAW;AACf,sBAAY;AACZ,gBAAM,MAAM,QAAQ,aAAa,iBAAiB;AAClD,2BAAiB,iBAAiB,MAAM,EAAE,IAAI,IAAI,MAAS;AAAA,QAC7D,GAAG,sBAAsB;AAAA,MAC3B,WAAW,WAAW;AACpB,qBAAa,SAAS;AACtB,oBAAY;AAAA,MACd;AAAA,IACF;AAAA,IACA,EAAE,WAAW,wBAAwB;AAAA,EACvC;AACA,WAAS,QAAQ,OAAO;AACxB,iBAAe;AACjB;AAEA,SAAS,YAAY,QAA4C;AAC/D,QAAM,KAAK;AACX,SAAO,IAAI,UAAU,uBAAuB,KAAK;AACnD;AAEA,SAAS,mBAAyB;AAChC,QAAM,YAAY,CAAC,MAAmB;AACpC,UAAM,OAAO,YAAY,EAAE,MAAM;AACjC,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,UAAU,IAAI,IAAI,KAAK;AAAA,MACnC,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AACA,QAAI,CAAC,MAAM,SAAS;AAClB,YAAM,UAAU;AAChB,YAAM,SAAS,KAAK,aAAa,iBAAiB;AAClD,uBAAiB,cAAc,SAAS,EAAE,SAAS,OAAO,IAAI,MAAS;AAAA,IACzE;AACA,cAAU,IAAI,MAAM,KAAK;AAAA,EAC3B;AACA,QAAM,UAAU,CAAC,MAAmB;AAClC,UAAM,OAAO,YAAY,EAAE,MAAM;AACjC,QAAI,CAAC,KAAM;AACX,UAAM,QAAQ,UAAU,IAAI,IAAI,KAAK;AAAA,MACnC,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AACA,UAAM,QAAQ;AACd,UAAM,SAAS,EAAE;AACjB,UAAM,YAAY,QAAQ,QAAQ,QAAQ;AAC1C,QAAI,UAAW,OAAM,YAAY;AACjC,cAAU,IAAI,MAAM,KAAK;AAAA,EAC3B;AACA,QAAM,WAAW,CAAC,MAAmB;AACnC,UAAM,OAAO,EAAE;AACf,QAAI,CAAC,MAAM,UAAU,uBAAuB,EAAG;AAC/C,UAAM,QAAQ,UAAU,IAAI,IAAI;AAChC,QAAI,OAAO;AACT,YAAM,YAAY;AAClB,gBAAU,IAAI,MAAM,KAAK;AAAA,IAC3B;AAAA,EACF;AACA,WAAS,iBAAiB,WAAW,WAAW,IAAI;AACpD,WAAS,iBAAiB,SAAS,SAAS,IAAI;AAChD,WAAS,iBAAiB,UAAU,SAAS,IAAI;AACjD,WAAS,iBAAiB,UAAU,UAAU,IAAI;AAClD,qBAAmB;AACnB,qBAAmB;AACnB,sBAAoB;AACtB;AAEA,SAAS,kBAAwB;AAC/B,QAAM,aAAa,CAAC,MAAiC;AAInD,QAAI,EAAE,UAAW;AACjB,aACG,iBAAkC,uBAAuB,EACzD,QAAQ,CAAC,SAAS;AACjB,YAAM,QAAQ,UAAU,IAAI,IAAI;AAChC,UAAI,CAAC,SAAS,CAAC,MAAM,SAAS,MAAM,UAAW;AAC/C,YAAM,SAAS,KAAK,aAAa,iBAAiB;AAClD,uBAAiB,gBAAgB;AAAA,QAC/B,GAAI,SAAS,EAAE,SAAS,OAAO,IAAI,CAAC;AAAA,QACpC,GAAI,MAAM,YAAY,EAAE,YAAY,MAAM,UAAU,IAAI,CAAC;AAAA,MAC3D,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AACA,oBAAkB;AAClB,SAAO,iBAAiB,YAAY,UAAU;AAChD;AAQO,SAAS,gBAAsB;AACpC,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,YAAa;AACtE,kBAAgB;AAChB,kBAAgB;AAChB,mBAAiB;AACjB,mBAAiB;AACjB,kBAAgB;AAClB;AAGO,SAAS,oBAA0B;AACxC,mBAAiB,WAAW;AAC5B,oBAAkB;AAClB,gBAAc,WAAW;AACzB,iBAAe;AACf,MAAI,WAAW;AACb,iBAAa,SAAS;AACtB,gBAAY;AAAA,EACd;AACA,cAAY;AACZ,MAAI,eAAe;AACjB,WAAO,oBAAoB,UAAU,aAAa;AAClD,oBAAgB;AAAA,EAClB;AACA,gBAAc,oBAAI,IAAY;AAC9B,MAAI,kBAAkB;AACpB,aAAS,oBAAoB,WAAW,kBAAkB,IAAI;AAC9D,uBAAmB;AAAA,EACrB;AACA,MAAI,kBAAkB;AACpB,aAAS,oBAAoB,SAAS,kBAAkB,IAAI;AAC5D,aAAS,oBAAoB,UAAU,kBAAkB,IAAI;AAC7D,uBAAmB;AAAA,EACrB;AACA,MAAI,mBAAmB;AACrB,aAAS,oBAAoB,UAAU,mBAAmB,IAAI;AAC9D,wBAAoB;AAAA,EACtB;AACA,MAAI,iBAAiB;AACnB,WAAO,oBAAoB,YAAY,eAAe;AACtD,sBAAkB;AAAA,EACpB;AACF;","names":[]}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
interface TrackEventDef {
|
|
2
|
+
/** GA4 로 미러 발화하는 이벤트인지(설계 §0 게이트 계약). */
|
|
3
|
+
readonly ga4: boolean;
|
|
4
|
+
/** GA4 미러 시 사용할 gtag 이벤트명. `ga4=true` 일 때만 존재. */
|
|
5
|
+
readonly ga4Name?: string;
|
|
6
|
+
/** sessionStorage 여정 링버퍼(journey.ts)에 기록할지. */
|
|
7
|
+
readonly journey: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* 허용 차원 키(의미상 이름) — 순서대로 blob d1/d2/d3 슬롯에 매핑(설계 §4).
|
|
10
|
+
* 어드민 표시·문서용 semantic name. 실제 payload 파싱은 {@link wireDims}.
|
|
11
|
+
*/
|
|
12
|
+
readonly dims: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* `dims` 와 순서 동일하되 **실제 /collect JSON payload 상의 키 이름**
|
|
15
|
+
* (beacon.ts RUNTIME 이 두 경로로 값을 보내기 때문에 `dims` 와 다를 수 있다):
|
|
16
|
+
* - 클릭 위임(`[data-track]` delegated click): `dataset` 의 `data-track-*`
|
|
17
|
+
* camelCase 키를 그대로 전송(`data-track-placement`→`trackPlacement`,
|
|
18
|
+
* `data-track-cta`→`trackCta`, `data-track-q`→`trackQ`,
|
|
19
|
+
* `data-track-provider`→`trackProvider`).
|
|
20
|
+
* - JS 직접 호출(`trackCustomEvent`/`window.__rtTrack` — observers.ts·
|
|
21
|
+
* lead-form-meta.client.tsx·not-found 404 비콘 등): 호출부가 이미
|
|
22
|
+
* `dims` 와 같은 snake_case 키(`section_id`·`pct`·`pid`·`form_id`·
|
|
23
|
+
* `last_field`)로 extra 객체를 만들어 보낸다.
|
|
24
|
+
* api-core `/collect` 가 이 키로 body 에서 값을 읽어 blob d1/d2/d3 에 채운다.
|
|
25
|
+
*/
|
|
26
|
+
readonly wireDims?: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
declare const TRACK_EVENTS: {
|
|
29
|
+
readonly pageview: {
|
|
30
|
+
readonly ga4: false;
|
|
31
|
+
readonly journey: true;
|
|
32
|
+
readonly dims: readonly ["pid"];
|
|
33
|
+
readonly wireDims: readonly ["pid"];
|
|
34
|
+
};
|
|
35
|
+
readonly lead_submit: {
|
|
36
|
+
readonly ga4: true;
|
|
37
|
+
readonly ga4Name: "generate_lead";
|
|
38
|
+
readonly journey: true;
|
|
39
|
+
readonly dims: readonly ["form_id"];
|
|
40
|
+
readonly wireDims: readonly ["form_id"];
|
|
41
|
+
};
|
|
42
|
+
readonly form_submit_attempt: {
|
|
43
|
+
readonly ga4: false;
|
|
44
|
+
readonly journey: true;
|
|
45
|
+
readonly dims: readonly ["form_id"];
|
|
46
|
+
readonly wireDims: readonly ["form_id"];
|
|
47
|
+
};
|
|
48
|
+
readonly phone_click: {
|
|
49
|
+
readonly ga4: true;
|
|
50
|
+
readonly ga4Name: "phone_call_click";
|
|
51
|
+
readonly journey: true;
|
|
52
|
+
readonly dims: readonly ["placement"];
|
|
53
|
+
readonly wireDims: readonly ["trackPlacement"];
|
|
54
|
+
};
|
|
55
|
+
readonly email_click: {
|
|
56
|
+
readonly ga4: true;
|
|
57
|
+
readonly ga4Name: "email_click";
|
|
58
|
+
readonly journey: true;
|
|
59
|
+
readonly dims: readonly ["placement"];
|
|
60
|
+
readonly wireDims: readonly ["trackPlacement"];
|
|
61
|
+
};
|
|
62
|
+
readonly chat_click: {
|
|
63
|
+
readonly ga4: true;
|
|
64
|
+
readonly ga4Name: "chat_click";
|
|
65
|
+
readonly journey: true;
|
|
66
|
+
readonly dims: readonly ["provider", "placement"];
|
|
67
|
+
readonly wireDims: readonly ["trackProvider", "trackPlacement"];
|
|
68
|
+
};
|
|
69
|
+
readonly booking_click: {
|
|
70
|
+
readonly ga4: true;
|
|
71
|
+
readonly ga4Name: "booking_outbound";
|
|
72
|
+
readonly journey: true;
|
|
73
|
+
readonly dims: readonly ["provider", "placement"];
|
|
74
|
+
readonly wireDims: readonly ["trackProvider", "trackPlacement"];
|
|
75
|
+
};
|
|
76
|
+
readonly cta_click: {
|
|
77
|
+
readonly ga4: false;
|
|
78
|
+
readonly journey: false;
|
|
79
|
+
readonly dims: readonly ["cta_id", "placement"];
|
|
80
|
+
readonly wireDims: readonly ["trackCta", "trackPlacement"];
|
|
81
|
+
};
|
|
82
|
+
readonly pricing_view: {
|
|
83
|
+
readonly ga4: false;
|
|
84
|
+
readonly journey: false;
|
|
85
|
+
readonly dims: readonly ["item"];
|
|
86
|
+
readonly wireDims: readonly ["trackItem"];
|
|
87
|
+
};
|
|
88
|
+
readonly pricing_expand: {
|
|
89
|
+
readonly ga4: false;
|
|
90
|
+
readonly journey: false;
|
|
91
|
+
readonly dims: readonly ["item"];
|
|
92
|
+
readonly wireDims: readonly ["trackItem"];
|
|
93
|
+
};
|
|
94
|
+
readonly faq_open: {
|
|
95
|
+
readonly ga4: false;
|
|
96
|
+
readonly journey: false;
|
|
97
|
+
readonly dims: readonly ["q_index"];
|
|
98
|
+
readonly wireDims: readonly ["trackQ"];
|
|
99
|
+
};
|
|
100
|
+
readonly gallery_interact: {
|
|
101
|
+
readonly ga4: false;
|
|
102
|
+
readonly journey: false;
|
|
103
|
+
readonly dims: readonly ["spot"];
|
|
104
|
+
readonly wireDims: readonly ["trackSpot"];
|
|
105
|
+
};
|
|
106
|
+
readonly form_start: {
|
|
107
|
+
readonly ga4: false;
|
|
108
|
+
readonly journey: false;
|
|
109
|
+
readonly dims: readonly ["form_id"];
|
|
110
|
+
readonly wireDims: readonly ["form_id"];
|
|
111
|
+
};
|
|
112
|
+
readonly form_abandon: {
|
|
113
|
+
readonly ga4: false;
|
|
114
|
+
readonly journey: false;
|
|
115
|
+
readonly dims: readonly ["form_id", "last_field"];
|
|
116
|
+
readonly wireDims: readonly ["form_id", "last_field"];
|
|
117
|
+
};
|
|
118
|
+
readonly section_view: {
|
|
119
|
+
readonly ga4: false;
|
|
120
|
+
readonly journey: false;
|
|
121
|
+
readonly dims: readonly ["section_id"];
|
|
122
|
+
readonly wireDims: readonly ["section_id"];
|
|
123
|
+
};
|
|
124
|
+
readonly scroll_depth: {
|
|
125
|
+
readonly ga4: false;
|
|
126
|
+
readonly journey: false;
|
|
127
|
+
readonly dims: readonly ["pct"];
|
|
128
|
+
readonly wireDims: readonly ["pct"];
|
|
129
|
+
};
|
|
130
|
+
readonly read_complete: {
|
|
131
|
+
readonly ga4: false;
|
|
132
|
+
readonly journey: false;
|
|
133
|
+
readonly dims: readonly ["pid"];
|
|
134
|
+
readonly wireDims: readonly ["pid"];
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
type TrackEventName = keyof typeof TRACK_EVENTS;
|
|
138
|
+
/** `name` 이 사전에 등록된 이벤트인지 — api-core `/collect` allowlist 가드로 재사용. */
|
|
139
|
+
declare function isTrackEvent(name: string): name is TrackEventName;
|
|
140
|
+
/**
|
|
141
|
+
* GA4 미러 대상 이벤트만 뽑은 파생 상수 — `{eventName: ga4EventName}`.
|
|
142
|
+
* `ga4-bridge.ts` 가 이 맵으로 미러 여부·gtag 이벤트명을 조회한다. 사전이
|
|
143
|
+
* SSoT 이므로 이 상수는 `TRACK_EVENTS` 에서 계산될 뿐 별도로 유지하지 않는다.
|
|
144
|
+
*/
|
|
145
|
+
declare const GA4_MIRROR: Readonly<Partial<Record<TrackEventName, string>>>;
|
|
146
|
+
|
|
147
|
+
export { GA4_MIRROR, TRACK_EVENTS, type TrackEventDef, type TrackEventName, isTrackEvent };
|
package/dist/events.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { C as ConsentCategory, a as ConsentState, T as TagProvider, A as AnalyticsSiteConfig, b as AnalyticsManifest } from './consent-banner-WVGsxcY2.js';
|
|
2
2
|
export { c as ConsentBannerOptions, d as ConsentValue, R as ResolvedTag, e as TagConfig, r as renderConsentBannerMarkup, f as renderConsentBannerScript } from './consent-banner-WVGsxcY2.js';
|
|
3
|
+
export { GA4_MIRROR, TRACK_EVENTS, TrackEventDef, TrackEventName, isTrackEvent } from './events.js';
|
|
3
4
|
|
|
4
5
|
declare const ALL_CONSENT_CATEGORIES: readonly ConsentCategory[];
|
|
5
6
|
/** fail-closed 기본값 — 전부 denied. */
|
|
@@ -55,10 +56,14 @@ declare const ATTRIBUTION_TTL_MS: number;
|
|
|
55
56
|
/**
|
|
56
57
|
* localStorage["_rt_attr"] 에 저장되는 first-touch 형태 (식별자 없음).
|
|
57
58
|
* 키를 짧게 유지 — 비콘 인라인 런타임과 폼 hidden field 양쪽에서 쓰인다.
|
|
59
|
+
*
|
|
60
|
+
* v1 데이터 하위호환: 이미 저장된 v1 envelope(ut/un/gcl/lt 없음)도 유효하게
|
|
61
|
+
* 읽는다 — TTL 이 남아있으면 구조를 마이그레이션하지 않고 그대로 유지하며,
|
|
62
|
+
* 신호가 있는 로드에서 `lt` 만 추가로 얹는다. 새로 캡처되는 envelope 만 `v:2`.
|
|
58
63
|
*/
|
|
59
64
|
interface FirstTouchAttribution {
|
|
60
|
-
/** 포맷 버전. */
|
|
61
|
-
v: 1;
|
|
65
|
+
/** 포맷 버전. 저장 시점의 v1 데이터도 유효하게 읽는다(하위호환). */
|
|
66
|
+
v: 1 | 2;
|
|
62
67
|
/** landing pathname (query 제외). */
|
|
63
68
|
lp: string;
|
|
64
69
|
/** 캡처 시각 (epoch ms). */
|
|
@@ -71,8 +76,37 @@ interface FirstTouchAttribution {
|
|
|
71
76
|
um?: string;
|
|
72
77
|
/** utm_campaign (있을 때만). */
|
|
73
78
|
uc?: string;
|
|
79
|
+
/** utm_term (있을 때만, v2). */
|
|
80
|
+
ut?: string;
|
|
81
|
+
/** utm_content (있을 때만, v2). */
|
|
82
|
+
un?: string;
|
|
83
|
+
/** 광고 클릭 유입 존재 플래그(v2) — gclid/fbclid **원값은 절대 저장하지 않음**. */
|
|
84
|
+
gcl?: 1;
|
|
74
85
|
/** 외부 referrer hostname — raw URL 미저장, 동일 host 면 생략. */
|
|
75
86
|
rf?: string;
|
|
87
|
+
/** last non-direct touch (v2) — 신호 있는 로드에서만 통째 교체. */
|
|
88
|
+
lt?: LastTouchAttribution;
|
|
89
|
+
}
|
|
90
|
+
/** `lt` 슬롯을 교체시킨 채널 — 우선순위 rs > utm > gcl > rf. */
|
|
91
|
+
type LastTouchChannel = "rs" | "utm" | "gcl" | "rf";
|
|
92
|
+
/**
|
|
93
|
+
* last non-direct touch — first-touch 와 동형이나 `ch`(판정 채널)를 더 갖는다.
|
|
94
|
+
* first-touch 와 동일한 30일 봉투 안에서 관리(별도 TTL 없음).
|
|
95
|
+
*/
|
|
96
|
+
interface LastTouchAttribution {
|
|
97
|
+
/** 갱신 시각 (epoch ms). */
|
|
98
|
+
ts: number;
|
|
99
|
+
/** 이 갱신을 유발한 채널(우선순위 rs > utm > gcl > rf). */
|
|
100
|
+
ch: LastTouchChannel;
|
|
101
|
+
rs?: string;
|
|
102
|
+
us?: string;
|
|
103
|
+
um?: string;
|
|
104
|
+
uc?: string;
|
|
105
|
+
ut?: string;
|
|
106
|
+
un?: string;
|
|
107
|
+
/** 광고 클릭 유입 존재 플래그 — 원값 미저장(first-touch 와 동일 정책). */
|
|
108
|
+
gcl?: 1;
|
|
109
|
+
rf?: string;
|
|
76
110
|
}
|
|
77
111
|
/**
|
|
78
112
|
* 비콘 dispatcher JS 본문. <script is:inline> 로 감싸 주입.
|
|
@@ -85,5 +119,85 @@ interface FirstTouchAttribution {
|
|
|
85
119
|
* 값 형태는 `/v1/collect` 와의 공유 계약(변경 시 양쪽 동시 갱신).
|
|
86
120
|
*/
|
|
87
121
|
declare function renderBeaconScript(options: BeaconOptions): string;
|
|
122
|
+
declare global {
|
|
123
|
+
interface Window {
|
|
124
|
+
/** RUNTIME 이 노출하는 send() — data-track 클릭 delegation 과 동일 함수. */
|
|
125
|
+
__rtTrack?: (ev: string, extra?: Record<string, unknown>) => void;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* `window.__rtTrack` 안전 호출 래퍼(W4-5 PR 2) — 클릭이 아닌 시점(마운트 등)
|
|
130
|
+
* 에 커스텀 이벤트를 보내야 하는 클라이언트 컴포넌트(예: starter
|
|
131
|
+
* `app/not-found.tsx` 의 404 비콘)의 표준 진입점. `RootTaleAnalytics` 가 비콘
|
|
132
|
+
* 없이 렌더된 사이트(collectUrl/siteId 미설정)에서는 전역 자체가 없으므로
|
|
133
|
+
* 자동으로 no-op — 별도 게이트 없이 안전.
|
|
134
|
+
*/
|
|
135
|
+
declare function trackCustomEvent(ev: string, extra?: Record<string, unknown>): void;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* GA4 미러 브리지 스크립트. `<RootTaleAnalytics>` 가 beacon 스크립트와 함께
|
|
139
|
+
* (순서 무관) 주입한다. `enableGa4Bridge={false}` 로 아예 생략 가능(react.tsx).
|
|
140
|
+
*/
|
|
141
|
+
declare function renderGa4BridgeScript(): string;
|
|
142
|
+
declare global {
|
|
143
|
+
interface Window {
|
|
144
|
+
/** beacon send() 가 매 이벤트마다 호출하는 GA4 미러 훅(설치돼 있을 때만). */
|
|
145
|
+
__rtGa4Mirror?: (ev: string, extra?: Record<string, unknown>) => void;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** sessionStorage 키 — cms-client/renderer 와의 공유 계약(변경 시 양쪽 동시 갱신). */
|
|
150
|
+
declare const JOURNEY_STORAGE_KEY = "_rt_journey";
|
|
151
|
+
/** 링버퍼 최대 항목 수(변경 시 RUNTIME 내 하드코딩 리터럴도 동시 갱신). */
|
|
152
|
+
declare const JOURNEY_MAX_ITEMS = 30;
|
|
153
|
+
/** `_rt_journey` 항목 형태. */
|
|
154
|
+
interface JourneyEntry {
|
|
155
|
+
/** 캡처 시각 (epoch ms). */
|
|
156
|
+
t: number;
|
|
157
|
+
/** 이벤트 이름 (events.ts 사전). */
|
|
158
|
+
ev: string;
|
|
159
|
+
/** 발생 경로 (pathname). vertical 게이트 대상은 서버에서 제거될 수 있음. */
|
|
160
|
+
path?: string;
|
|
161
|
+
/** 소형 차원 데이터 (data-track-* 로 캡처된 extra). */
|
|
162
|
+
d?: Record<string, unknown>;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* journey 기록 브리지 스크립트. `<RootTaleAnalytics>` 가 beacon 스크립트와
|
|
166
|
+
* 함께(순서 무관) 항상 주입한다 — 1차 캡처 인프라라 opt-out 프롭 없음(ga4
|
|
167
|
+
* 미러와 달리).
|
|
168
|
+
*/
|
|
169
|
+
declare function renderJourneyBridgeScript(): string;
|
|
170
|
+
/**
|
|
171
|
+
* sessionStorage 의 journey 링버퍼를 읽는다(브라우저 전용). 파싱 실패·미존재
|
|
172
|
+
* 시 빈 배열. cms-client/renderer 가 문의 제출 시 이 값을 재직렬화해 동봉한다
|
|
173
|
+
* (submit capture/`formdata` 시점 — 설계 §3 P1-3, hidden 스냅샷 아님).
|
|
174
|
+
*/
|
|
175
|
+
declare function readJourney(): JourneyEntry[];
|
|
176
|
+
declare global {
|
|
177
|
+
interface Window {
|
|
178
|
+
/** beacon send() 가 매 이벤트마다 호출하는 journey 기록 훅(설치돼 있을 때만). */
|
|
179
|
+
__rtJourneyRecord?: (ev: string, extra?: Record<string, unknown>) => void;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 감지기 초기화 — section_view·scroll_depth·read_complete·form_start·
|
|
185
|
+
* form_abandon. SSR/비브라우저 환경에서는 no-op. teardownObservers() 없이
|
|
186
|
+
* 재호출하면 중복 바인딩될 수 있다 — 어댑터는 pathname 변화 시 teardown →
|
|
187
|
+
* init 순서를 지킨다(SPA 재바인드 계약).
|
|
188
|
+
*/
|
|
189
|
+
declare function initObservers(): void;
|
|
190
|
+
/** 모든 감지기 해제 — SPA pathname 변화 시 재바인드 전에 호출한다. */
|
|
191
|
+
declare function teardownObservers(): void;
|
|
192
|
+
declare global {
|
|
193
|
+
interface Window {
|
|
194
|
+
/**
|
|
195
|
+
* react.tsx(RSC, hookless) 가 명시적으로 `enableObservers={false}` 일 때만
|
|
196
|
+
* `false` 를 인라인 스크립트로 심는다 — next.tsx 어댑터가 initObservers()
|
|
197
|
+
* 호출 전 이 값을 확인한다(미설정 시 기본 true, "기본 true" 계약 구현).
|
|
198
|
+
*/
|
|
199
|
+
__rtObserversEnabled?: boolean;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
88
202
|
|
|
89
|
-
export { ALL_CONSENT_CATEGORIES, ATTRIBUTION_STORAGE_KEY, ATTRIBUTION_TTL_MS, AnalyticsManifest, AnalyticsSiteConfig, type BeaconOptions, ConsentCategory, ConsentState, DENIED_CONSENT, type FirstTouchAttribution, PROVIDER_CONSENT, TagProvider, buildManifest, isTagAllowed, renderBeaconScript, renderConsentBootstrap, renderLoaderScript };
|
|
203
|
+
export { ALL_CONSENT_CATEGORIES, ATTRIBUTION_STORAGE_KEY, ATTRIBUTION_TTL_MS, AnalyticsManifest, AnalyticsSiteConfig, type BeaconOptions, ConsentCategory, ConsentState, DENIED_CONSENT, type FirstTouchAttribution, JOURNEY_MAX_ITEMS, JOURNEY_STORAGE_KEY, type JourneyEntry, type LastTouchAttribution, type LastTouchChannel, PROVIDER_CONSENT, TagProvider, buildManifest, initObservers, isTagAllowed, readJourney, renderBeaconScript, renderConsentBootstrap, renderGa4BridgeScript, renderJourneyBridgeScript, renderLoaderScript, teardownObservers, trackCustomEvent };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
JOURNEY_MAX_ITEMS,
|
|
3
|
+
JOURNEY_STORAGE_KEY,
|
|
4
|
+
readJourney,
|
|
5
5
|
renderConsentBannerMarkup,
|
|
6
6
|
renderConsentBannerScript,
|
|
7
7
|
renderConsentBootstrap,
|
|
8
|
+
renderGa4BridgeScript,
|
|
9
|
+
renderJourneyBridgeScript,
|
|
8
10
|
renderLoaderScript
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-G7OLBHT5.js";
|
|
12
|
+
import {
|
|
13
|
+
initObservers,
|
|
14
|
+
teardownObservers
|
|
15
|
+
} from "./chunk-GW523RS4.js";
|
|
16
|
+
import {
|
|
17
|
+
ATTRIBUTION_STORAGE_KEY,
|
|
18
|
+
ATTRIBUTION_TTL_MS,
|
|
19
|
+
renderBeaconScript,
|
|
20
|
+
trackCustomEvent
|
|
21
|
+
} from "./chunk-DIN7LIST.js";
|
|
22
|
+
import {
|
|
23
|
+
GA4_MIRROR,
|
|
24
|
+
TRACK_EVENTS,
|
|
25
|
+
isTrackEvent
|
|
26
|
+
} from "./chunk-GBIGQHVN.js";
|
|
10
27
|
|
|
11
28
|
// src/consent.ts
|
|
12
29
|
var ALL_CONSENT_CATEGORIES = [
|
|
@@ -56,13 +73,24 @@ export {
|
|
|
56
73
|
ATTRIBUTION_STORAGE_KEY,
|
|
57
74
|
ATTRIBUTION_TTL_MS,
|
|
58
75
|
DENIED_CONSENT,
|
|
76
|
+
GA4_MIRROR,
|
|
77
|
+
JOURNEY_MAX_ITEMS,
|
|
78
|
+
JOURNEY_STORAGE_KEY,
|
|
59
79
|
PROVIDER_CONSENT,
|
|
80
|
+
TRACK_EVENTS,
|
|
60
81
|
buildManifest,
|
|
82
|
+
initObservers,
|
|
61
83
|
isTagAllowed,
|
|
84
|
+
isTrackEvent,
|
|
85
|
+
readJourney,
|
|
62
86
|
renderBeaconScript,
|
|
63
87
|
renderConsentBannerMarkup,
|
|
64
88
|
renderConsentBannerScript,
|
|
65
89
|
renderConsentBootstrap,
|
|
66
|
-
|
|
90
|
+
renderGa4BridgeScript,
|
|
91
|
+
renderJourneyBridgeScript,
|
|
92
|
+
renderLoaderScript,
|
|
93
|
+
teardownObservers,
|
|
94
|
+
trackCustomEvent
|
|
67
95
|
};
|
|
68
96
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/consent.ts","../src/manifest.ts"],"sourcesContent":["// ADR-0038 §2 — 동의 fail-closed + provider↔consent 매핑.\n\nimport type { ConsentCategory, ConsentState, TagProvider } from \"./types\";\n\nexport const ALL_CONSENT_CATEGORIES: readonly ConsentCategory[] = [\n \"analytics_storage\",\n \"ad_storage\",\n \"ad_user_data\",\n \"ad_personalization\",\n] as const;\n\n/** fail-closed 기본값 — 전부 denied. */\nexport const DENIED_CONSENT: ConsentState = {\n analytics_storage: \"denied\",\n ad_storage: \"denied\",\n ad_user_data: \"denied\",\n ad_personalization: \"denied\",\n};\n\n/**\n * provider 가 발화하기 전 granted 여야 하는 동의 카테고리.\n * - analytics 도구(GA4·Clarity·Naver) → `analytics_storage`\n * - 광고 도구(Meta Pixel) → `ad_storage` + `ad_user_data` + `ad_personalization`\n * (국외이전·광고목적이라 동의 요건 가장 강함)\n */\nexport const PROVIDER_CONSENT: Record<TagProvider, ConsentCategory[]> = {\n ga4: [\"analytics_storage\"],\n clarity: [\"analytics_storage\"],\n naver: [\"analytics_storage\"],\n meta_pixel: [\"ad_storage\", \"ad_user_data\", \"ad_personalization\"],\n};\n\n/** required 카테고리가 *모두* granted 일 때만 true. */\nexport function isTagAllowed(\n required: readonly ConsentCategory[],\n consent: ConsentState,\n): boolean {\n return required.every((c) => consent[c] === \"granted\");\n}\n","// ADR-0038 §1 — site 태그 구성 → manifest (순수 함수).\n\nimport { DENIED_CONSENT, PROVIDER_CONSENT } from \"./consent\";\nimport type { AnalyticsManifest, AnalyticsSiteConfig } from \"./types\";\n\n/**\n * site 태그 구성을 manifest 로 해석.\n * - `enabled=false` 또는 빈 id 는 제외.\n * - provider 당 1개 (중복은 첫 항목만).\n * - 각 태그에 필요한 동의 카테고리(`PROVIDER_CONSENT`)를 부착.\n * - 동의 기본값은 항상 전부 denied (fail-closed).\n */\nexport function buildManifest(config: AnalyticsSiteConfig): AnalyticsManifest {\n const seen = new Set<string>();\n const tags = config.tags\n .filter(\n (t) => t.enabled && typeof t.id === \"string\" && t.id.trim() !== \"\",\n )\n .filter((t) => {\n if (seen.has(t.provider)) return false;\n seen.add(t.provider);\n return true;\n })\n .map((t) => ({\n provider: t.provider,\n id: t.id.trim(),\n requiredConsent: PROVIDER_CONSENT[t.provider],\n }));\n\n return {\n siteId: config.siteId,\n tags,\n consentDefault: { ...DENIED_CONSENT },\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/consent.ts","../src/manifest.ts"],"sourcesContent":["// ADR-0038 §2 — 동의 fail-closed + provider↔consent 매핑.\n\nimport type { ConsentCategory, ConsentState, TagProvider } from \"./types\";\n\nexport const ALL_CONSENT_CATEGORIES: readonly ConsentCategory[] = [\n \"analytics_storage\",\n \"ad_storage\",\n \"ad_user_data\",\n \"ad_personalization\",\n] as const;\n\n/** fail-closed 기본값 — 전부 denied. */\nexport const DENIED_CONSENT: ConsentState = {\n analytics_storage: \"denied\",\n ad_storage: \"denied\",\n ad_user_data: \"denied\",\n ad_personalization: \"denied\",\n};\n\n/**\n * provider 가 발화하기 전 granted 여야 하는 동의 카테고리.\n * - analytics 도구(GA4·Clarity·Naver) → `analytics_storage`\n * - 광고 도구(Meta Pixel) → `ad_storage` + `ad_user_data` + `ad_personalization`\n * (국외이전·광고목적이라 동의 요건 가장 강함)\n */\nexport const PROVIDER_CONSENT: Record<TagProvider, ConsentCategory[]> = {\n ga4: [\"analytics_storage\"],\n clarity: [\"analytics_storage\"],\n naver: [\"analytics_storage\"],\n meta_pixel: [\"ad_storage\", \"ad_user_data\", \"ad_personalization\"],\n};\n\n/** required 카테고리가 *모두* granted 일 때만 true. */\nexport function isTagAllowed(\n required: readonly ConsentCategory[],\n consent: ConsentState,\n): boolean {\n return required.every((c) => consent[c] === \"granted\");\n}\n","// ADR-0038 §1 — site 태그 구성 → manifest (순수 함수).\n\nimport { DENIED_CONSENT, PROVIDER_CONSENT } from \"./consent\";\nimport type { AnalyticsManifest, AnalyticsSiteConfig } from \"./types\";\n\n/**\n * site 태그 구성을 manifest 로 해석.\n * - `enabled=false` 또는 빈 id 는 제외.\n * - provider 당 1개 (중복은 첫 항목만).\n * - 각 태그에 필요한 동의 카테고리(`PROVIDER_CONSENT`)를 부착.\n * - 동의 기본값은 항상 전부 denied (fail-closed).\n */\nexport function buildManifest(config: AnalyticsSiteConfig): AnalyticsManifest {\n const seen = new Set<string>();\n const tags = config.tags\n .filter(\n (t) => t.enabled && typeof t.id === \"string\" && t.id.trim() !== \"\",\n )\n .filter((t) => {\n if (seen.has(t.provider)) return false;\n seen.add(t.provider);\n return true;\n })\n .map((t) => ({\n provider: t.provider,\n id: t.id.trim(),\n requiredConsent: PROVIDER_CONSENT[t.provider],\n }));\n\n return {\n siteId: config.siteId,\n tags,\n consentDefault: { ...DENIED_CONSENT },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,IAAM,yBAAqD;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,iBAA+B;AAAA,EAC1C,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,oBAAoB;AACtB;AAQO,IAAM,mBAA2D;AAAA,EACtE,KAAK,CAAC,mBAAmB;AAAA,EACzB,SAAS,CAAC,mBAAmB;AAAA,EAC7B,OAAO,CAAC,mBAAmB;AAAA,EAC3B,YAAY,CAAC,cAAc,gBAAgB,oBAAoB;AACjE;AAGO,SAAS,aACd,UACA,SACS;AACT,SAAO,SAAS,MAAM,CAAC,MAAM,QAAQ,CAAC,MAAM,SAAS;AACvD;;;AC1BO,SAAS,cAAc,QAAgD;AAC5E,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,OAAO,OAAO,KACjB;AAAA,IACC,CAAC,MAAM,EAAE,WAAW,OAAO,EAAE,OAAO,YAAY,EAAE,GAAG,KAAK,MAAM;AAAA,EAClE,EACC,OAAO,CAAC,MAAM;AACb,QAAI,KAAK,IAAI,EAAE,QAAQ,EAAG,QAAO;AACjC,SAAK,IAAI,EAAE,QAAQ;AACnB,WAAO;AAAA,EACT,CAAC,EACA,IAAI,CAAC,OAAO;AAAA,IACX,UAAU,EAAE;AAAA,IACZ,IAAI,EAAE,GAAG,KAAK;AAAA,IACd,iBAAiB,iBAAiB,EAAE,QAAQ;AAAA,EAC9C,EAAE;AAEJ,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf;AAAA,IACA,gBAAgB,EAAE,GAAG,eAAe;AAAA,EACtC;AACF;","names":[]}
|
package/dist/next.d.ts
ADDED
package/dist/next.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
initObservers,
|
|
4
|
+
teardownObservers
|
|
5
|
+
} from "./chunk-GW523RS4.js";
|
|
6
|
+
import {
|
|
7
|
+
trackCustomEvent
|
|
8
|
+
} from "./chunk-DIN7LIST.js";
|
|
9
|
+
|
|
10
|
+
// src/next.tsx
|
|
11
|
+
import { useEffect, useRef } from "react";
|
|
12
|
+
import { usePathname } from "next/navigation";
|
|
13
|
+
function observersEnabled() {
|
|
14
|
+
if (typeof window === "undefined") return false;
|
|
15
|
+
return window.__rtObserversEnabled !== false;
|
|
16
|
+
}
|
|
17
|
+
function RootTaleAnalyticsNextAdapter() {
|
|
18
|
+
const pathname = usePathname();
|
|
19
|
+
const mounted = useRef(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (mounted.current) {
|
|
22
|
+
trackCustomEvent("pageview");
|
|
23
|
+
}
|
|
24
|
+
mounted.current = true;
|
|
25
|
+
if (observersEnabled()) initObservers();
|
|
26
|
+
return () => teardownObservers();
|
|
27
|
+
}, [pathname]);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
RootTaleAnalyticsNextAdapter
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/next.tsx"],"sourcesContent":["\"use client\";\n\n// 설계(measurement-design-draft.md) §5 — Next App Router SPA 어댑터.\n//\n// App Router 클라이언트 네비게이션은 풀 페이지 로드가 아니므로 beacon\n// RUNTIME 의 최초 1회 pageview(스크립트 파싱 시점)만으로는 이후 라우트\n// 전환을 놓친다. 본 어댑터가 usePathname() 변화를 감지해 pageview 재발화 +\n// observer 재바인드(teardownObservers → initObservers)를 담당한다.\n//\n// 초기 마운트에서는 pageview 를 재발화하지 않는다 — beacon RUNTIME 이 이미\n// 최초 1회 담당(중복 방지, useRef 로 첫 렌더 skip). 초기 마운트에서도\n// observer 초기화는 수행한다(첫 페이지에도 section_view 등 감지가 필요).\n//\n// `RootTaleAnalytics`(react.tsx, RSC 호환·hookless)와 함께 root layout 에\n// 나란히 배치한다:\n// <body>\n// {children}\n// <RootTaleAnalytics manifest={manifest} collectUrl={url} siteId={id} />\n// <RootTaleAnalyticsNextAdapter />\n// </body>\n\nimport { useEffect, useRef } from \"react\";\nimport { usePathname } from \"next/navigation\";\n\nimport { trackCustomEvent } from \"./beacon\";\nimport { initObservers, teardownObservers } from \"./observers\";\n\n/** `window.__rtObserversEnabled` 미설정 시 기본 활성(react.tsx 의 기본값과 동일). */\nfunction observersEnabled(): boolean {\n if (typeof window === \"undefined\") return false;\n return window.__rtObserversEnabled !== false;\n}\n\n/**\n * pathname 변화를 감지해 pageview 재발화 + observer 재바인드를 수행하는\n * 드롭인 컴포넌트. 렌더 출력 없음(`null`) — 부수효과 전용.\n */\nexport function RootTaleAnalyticsNextAdapter(): null {\n const pathname = usePathname();\n const mounted = useRef(false);\n\n useEffect(() => {\n // pathname 변화(재실행)일 때만 pageview 재발화 — 최초 마운트는 beacon\n // RUNTIME 이 이미 담당(중복 방지). teardown 은 React 가 이전 실행의\n // cleanup(아래 return)으로 자동 수행하므로 여기선 init 만 하면 SPA\n // 재바인드 계약(teardown → pageview 재발화 → init)이 자연히 성립한다.\n if (mounted.current) {\n trackCustomEvent(\"pageview\");\n }\n mounted.current = true;\n if (observersEnabled()) initObservers();\n return () => teardownObservers();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [pathname]);\n\n return null;\n}\n"],"mappings":";;;;;;;;;;AAqBA,SAAS,WAAW,cAAc;AAClC,SAAS,mBAAmB;AAM5B,SAAS,mBAA4B;AACnC,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,OAAO,yBAAyB;AACzC;AAMO,SAAS,+BAAqC;AACnD,QAAM,WAAW,YAAY;AAC7B,QAAM,UAAU,OAAO,KAAK;AAE5B,YAAU,MAAM;AAKd,QAAI,QAAQ,SAAS;AACnB,uBAAiB,UAAU;AAAA,IAC7B;AACA,YAAQ,UAAU;AAClB,QAAI,iBAAiB,EAAG,eAAc;AACtC,WAAO,MAAM,kBAAkB;AAAA,EAEjC,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO;AACT;","names":[]}
|
package/dist/react.d.ts
CHANGED
|
@@ -14,11 +14,26 @@ interface RootTaleAnalyticsProps {
|
|
|
14
14
|
trackPageview?: boolean;
|
|
15
15
|
/** 동의 배너 카피 override. */
|
|
16
16
|
bannerOptions?: ConsentBannerOptions;
|
|
17
|
+
/**
|
|
18
|
+
* GA4 미러 브리지 스크립트 주입 여부(설계 §0, 기본 true). beacon 이 없으면
|
|
19
|
+
* (collectUrl/siteId 미설정) 어차피 미주입 — 이벤트를 흘려보낼 send() 자체가
|
|
20
|
+
* 없기 때문.
|
|
21
|
+
*/
|
|
22
|
+
enableGa4Bridge?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* 방문 여정(observers.ts) 감지기 활성 여부(설계 §1 2단계, 기본 true). 본
|
|
25
|
+
* 컴포넌트는 RSC 호환(hook 없음)이라 IntersectionObserver 등을 직접
|
|
26
|
+
* 구동하지 못한다 — `false` 로 지정하면 `window.__rtObserversEnabled=false`
|
|
27
|
+
* 를 심어 `RootTaleAnalyticsNextAdapter`(next.tsx)가 initObservers() 호출
|
|
28
|
+
* 전 이 값을 읽고 건너뛰게 한다. `true`(기본)는 아무 것도 심지 않음(next.tsx
|
|
29
|
+
* 의 기본값이 이미 활성이므로 무동작이 곧 활성).
|
|
30
|
+
*/
|
|
31
|
+
enableObservers?: boolean;
|
|
17
32
|
}
|
|
18
33
|
/**
|
|
19
34
|
* 단일 드롭인 컴포넌트. RSC 호환(hook/상태 없음) — Server Component layout 에
|
|
20
35
|
* 바로 배치 가능.
|
|
21
36
|
*/
|
|
22
|
-
declare function RootTaleAnalytics({ manifest, collectUrl, siteId, privacyUrl, trackPageview, bannerOptions, }: RootTaleAnalyticsProps): react_jsx_runtime.JSX.Element;
|
|
37
|
+
declare function RootTaleAnalytics({ manifest, collectUrl, siteId, privacyUrl, trackPageview, bannerOptions, enableGa4Bridge, enableObservers, }: RootTaleAnalyticsProps): react_jsx_runtime.JSX.Element;
|
|
23
38
|
|
|
24
39
|
export { RootTaleAnalytics, type RootTaleAnalyticsProps };
|
package/dist/react.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
renderBeaconScript,
|
|
3
2
|
renderConsentBannerMarkup,
|
|
4
3
|
renderConsentBannerScript,
|
|
5
4
|
renderConsentBootstrap,
|
|
5
|
+
renderGa4BridgeScript,
|
|
6
|
+
renderJourneyBridgeScript,
|
|
6
7
|
renderLoaderScript
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-G7OLBHT5.js";
|
|
9
|
+
import {
|
|
10
|
+
renderBeaconScript
|
|
11
|
+
} from "./chunk-DIN7LIST.js";
|
|
12
|
+
import "./chunk-GBIGQHVN.js";
|
|
8
13
|
|
|
9
14
|
// src/react.tsx
|
|
10
15
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -14,7 +19,9 @@ function RootTaleAnalytics({
|
|
|
14
19
|
siteId,
|
|
15
20
|
privacyUrl,
|
|
16
21
|
trackPageview,
|
|
17
|
-
bannerOptions
|
|
22
|
+
bannerOptions,
|
|
23
|
+
enableGa4Bridge = true,
|
|
24
|
+
enableObservers = true
|
|
18
25
|
}) {
|
|
19
26
|
const beacon = collectUrl && siteId ? renderBeaconScript({ collectUrl, siteId, trackPageview }) : null;
|
|
20
27
|
const bannerOpts = {
|
|
@@ -37,6 +44,7 @@ function RootTaleAnalytics({
|
|
|
37
44
|
/* @__PURE__ */ jsx(
|
|
38
45
|
"div",
|
|
39
46
|
{
|
|
47
|
+
suppressHydrationWarning: true,
|
|
40
48
|
dangerouslySetInnerHTML: {
|
|
41
49
|
__html: renderConsentBannerMarkup(bannerOpts)
|
|
42
50
|
}
|
|
@@ -48,7 +56,27 @@ function RootTaleAnalytics({
|
|
|
48
56
|
dangerouslySetInnerHTML: { __html: renderConsentBannerScript() }
|
|
49
57
|
}
|
|
50
58
|
),
|
|
51
|
-
beacon ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: beacon } }) : null
|
|
59
|
+
beacon ? /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: beacon } }) : null,
|
|
60
|
+
beacon ? /* @__PURE__ */ jsx(
|
|
61
|
+
"script",
|
|
62
|
+
{
|
|
63
|
+
dangerouslySetInnerHTML: { __html: renderJourneyBridgeScript() }
|
|
64
|
+
}
|
|
65
|
+
) : null,
|
|
66
|
+
beacon && enableGa4Bridge ? /* @__PURE__ */ jsx(
|
|
67
|
+
"script",
|
|
68
|
+
{
|
|
69
|
+
dangerouslySetInnerHTML: { __html: renderGa4BridgeScript() }
|
|
70
|
+
}
|
|
71
|
+
) : null,
|
|
72
|
+
enableObservers === false ? /* @__PURE__ */ jsx(
|
|
73
|
+
"script",
|
|
74
|
+
{
|
|
75
|
+
dangerouslySetInnerHTML: {
|
|
76
|
+
__html: "window.__rtObserversEnabled=false;"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
) : null
|
|
52
80
|
] });
|
|
53
81
|
}
|
|
54
82
|
export {
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.tsx"],"sourcesContent":["// ADR-0038 — Next.js/React 드롭인 어댑터.\n//\n// `@roottale/analytics-runtime/react` 로 import. core 는 React-free 유지하고\n// 본 어댑터만 react(peerDependency)에 의존한다.\n//\n// 사용 (Next App Router root layout):\n// import { RootTaleAnalytics } from \"@roottale/analytics-runtime/react\";\n// ...\n// <body>\n// {children}\n// <RootTaleAnalytics manifest={manifest} collectUrl={url} siteId={id} />\n// </body>\n//\n// 모든 <script> 는 dangerouslySetInnerHTML 인라인 — Next 가 실제 실행되는\n// <script> 로 렌더(set:html innerHTML 함정 회피). 순서: consent bootstrap(전부\n// denied) → loader(fail-closed) → 배너 → 비콘.\n\nimport type { AnalyticsManifest } from \"./types\";\nimport { renderConsentBootstrap, renderLoaderScript } from \"./loader\";\nimport {\n renderConsentBannerMarkup,\n renderConsentBannerScript,\n type ConsentBannerOptions,\n} from \"./consent-banner\";\nimport { renderBeaconScript } from \"./beacon\";\n\nexport interface RootTaleAnalyticsProps {\n /** buildManifest() 결과. plane ① 태그 + fail-closed 동의 기본값. */\n manifest: AnalyticsManifest;\n /** plane ② 비콘 수집 엔드포인트. 없으면 비콘 미주입. */\n collectUrl?: string;\n /** 비콘용 site 식별자. collectUrl 과 함께 제공. */\n siteId?: string;\n /** 동의 배너 개인정보처리방침 링크. */\n privacyUrl?: string;\n /** 비콘 pageview 자동 전송 (기본 true). */\n trackPageview?: boolean;\n /** 동의 배너 카피 override. */\n bannerOptions?: ConsentBannerOptions;\n}\n\n/**\n * 단일 드롭인 컴포넌트. RSC 호환(hook/상태 없음) — Server Component layout 에\n * 바로 배치 가능.\n */\nexport function RootTaleAnalytics({\n manifest,\n collectUrl,\n siteId,\n privacyUrl,\n trackPageview,\n bannerOptions,\n}: RootTaleAnalyticsProps) {\n const beacon =\n collectUrl && siteId\n ? renderBeaconScript({ collectUrl, siteId, trackPageview })\n : null;\n const bannerOpts: ConsentBannerOptions = {\n privacyUrl,\n ...bannerOptions,\n };\n\n return (\n <>\n <script\n dangerouslySetInnerHTML={{ __html: renderConsentBootstrap() }}\n />\n <script\n dangerouslySetInnerHTML={{ __html: renderLoaderScript(manifest) }}\n />\n <div\n dangerouslySetInnerHTML={{\n __html: renderConsentBannerMarkup(bannerOpts),\n }}\n />\n <script\n dangerouslySetInnerHTML={{ __html: renderConsentBannerScript() }}\n />\n {beacon ? (\n <script dangerouslySetInnerHTML={{ __html: beacon }} />\n ) : null}\n </>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx"],"sourcesContent":["// ADR-0038 — Next.js/React 드롭인 어댑터.\n//\n// `@roottale/analytics-runtime/react` 로 import. core 는 React-free 유지하고\n// 본 어댑터만 react(peerDependency)에 의존한다.\n//\n// 사용 (Next App Router root layout):\n// import { RootTaleAnalytics } from \"@roottale/analytics-runtime/react\";\n// ...\n// <body>\n// {children}\n// <RootTaleAnalytics manifest={manifest} collectUrl={url} siteId={id} />\n// </body>\n//\n// 모든 <script> 는 dangerouslySetInnerHTML 인라인 — Next 가 실제 실행되는\n// <script> 로 렌더(set:html innerHTML 함정 회피). 순서: consent bootstrap(전부\n// denied) → loader(fail-closed) → 배너 → 비콘.\n\nimport type { AnalyticsManifest } from \"./types\";\nimport { renderConsentBootstrap, renderLoaderScript } from \"./loader\";\nimport {\n renderConsentBannerMarkup,\n renderConsentBannerScript,\n type ConsentBannerOptions,\n} from \"./consent-banner\";\nimport { renderBeaconScript } from \"./beacon\";\nimport { renderJourneyBridgeScript } from \"./journey\";\nimport { renderGa4BridgeScript } from \"./ga4-bridge\";\n\nexport interface RootTaleAnalyticsProps {\n /** buildManifest() 결과. plane ① 태그 + fail-closed 동의 기본값. */\n manifest: AnalyticsManifest;\n /** plane ② 비콘 수집 엔드포인트. 없으면 비콘 미주입. */\n collectUrl?: string;\n /** 비콘용 site 식별자. collectUrl 과 함께 제공. */\n siteId?: string;\n /** 동의 배너 개인정보처리방침 링크. */\n privacyUrl?: string;\n /** 비콘 pageview 자동 전송 (기본 true). */\n trackPageview?: boolean;\n /** 동의 배너 카피 override. */\n bannerOptions?: ConsentBannerOptions;\n /**\n * GA4 미러 브리지 스크립트 주입 여부(설계 §0, 기본 true). beacon 이 없으면\n * (collectUrl/siteId 미설정) 어차피 미주입 — 이벤트를 흘려보낼 send() 자체가\n * 없기 때문.\n */\n enableGa4Bridge?: boolean;\n /**\n * 방문 여정(observers.ts) 감지기 활성 여부(설계 §1 2단계, 기본 true). 본\n * 컴포넌트는 RSC 호환(hook 없음)이라 IntersectionObserver 등을 직접\n * 구동하지 못한다 — `false` 로 지정하면 `window.__rtObserversEnabled=false`\n * 를 심어 `RootTaleAnalyticsNextAdapter`(next.tsx)가 initObservers() 호출\n * 전 이 값을 읽고 건너뛰게 한다. `true`(기본)는 아무 것도 심지 않음(next.tsx\n * 의 기본값이 이미 활성이므로 무동작이 곧 활성).\n */\n enableObservers?: boolean;\n}\n\n/**\n * 단일 드롭인 컴포넌트. RSC 호환(hook/상태 없음) — Server Component layout 에\n * 바로 배치 가능.\n */\nexport function RootTaleAnalytics({\n manifest,\n collectUrl,\n siteId,\n privacyUrl,\n trackPageview,\n bannerOptions,\n enableGa4Bridge = true,\n enableObservers = true,\n}: RootTaleAnalyticsProps) {\n const beacon =\n collectUrl && siteId\n ? renderBeaconScript({ collectUrl, siteId, trackPageview })\n : null;\n const bannerOpts: ConsentBannerOptions = {\n privacyUrl,\n ...bannerOptions,\n };\n\n return (\n <>\n <script\n dangerouslySetInnerHTML={{ __html: renderConsentBootstrap() }}\n />\n <script\n dangerouslySetInnerHTML={{ __html: renderLoaderScript(manifest) }}\n />\n {/*\n suppressHydrationWarning: `renderConsentBannerScript()` 는 일반\n `<script>` 로 삽입되어 브라우저가 이 div 뒤에서 그 스크립트를 만나는\n 즉시(React 하이드레이션이 시작되기 전, 최초 HTML 파싱 도중) 동의 기록이\n 없으면 `el.hidden = false` 를 실행해 `hidden` 속성을 지운다 — 배너를\n 깜빡임 없이 보여주기 위해 의도된 동작이다. 그 결과 React 가 하이드레이션\n 시점에 비교하는 실제 DOM 은 이 컴포넌트가 SSR 로 낸 `__html`(hidden 포함)\n 과 달라져 \"tree hydrated but attributes didn't match\" 경고가 난다 —\n React 문서가 안내하는 FOUC-방지 스크립트의 표준 처방(next-themes 류가\n `<html>` 에 쓰는 것과 동일 패턴)대로 이 노드에서만 경고를 억제한다.\n */}\n <div\n suppressHydrationWarning\n dangerouslySetInnerHTML={{\n __html: renderConsentBannerMarkup(bannerOpts),\n }}\n />\n <script\n dangerouslySetInnerHTML={{ __html: renderConsentBannerScript() }}\n />\n {beacon ? (\n <script dangerouslySetInnerHTML={{ __html: beacon }} />\n ) : null}\n {/*\n journey 브리지는 beacon 이 있을 때만 의미가 있다(훅 대상인\n window.__rtTrack/send() 자체가 beacon 스크립트가 심는다) — opt-out\n 프롭 없음(1차 캡처 인프라, 설계 §3).\n */}\n {beacon ? (\n <script\n dangerouslySetInnerHTML={{ __html: renderJourneyBridgeScript() }}\n />\n ) : null}\n {beacon && enableGa4Bridge ? (\n <script\n dangerouslySetInnerHTML={{ __html: renderGa4BridgeScript() }}\n />\n ) : null}\n {enableObservers === false ? (\n <script\n dangerouslySetInnerHTML={{\n __html: \"window.__rtObserversEnabled=false;\",\n }}\n />\n ) : null}\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkFI,mBACE,KADF;AApBG,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,kBAAkB;AACpB,GAA2B;AACzB,QAAM,SACJ,cAAc,SACV,mBAAmB,EAAE,YAAY,QAAQ,cAAc,CAAC,IACxD;AACN,QAAM,aAAmC;AAAA,IACvC;AAAA,IACA,GAAG;AAAA,EACL;AAEA,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB,EAAE,QAAQ,uBAAuB,EAAE;AAAA;AAAA,IAC9D;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB,EAAE,QAAQ,mBAAmB,QAAQ,EAAE;AAAA;AAAA,IAClE;AAAA,IAYA;AAAA,MAAC;AAAA;AAAA,QACC,0BAAwB;AAAA,QACxB,yBAAyB;AAAA,UACvB,QAAQ,0BAA0B,UAAU;AAAA,QAC9C;AAAA;AAAA,IACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB,EAAE,QAAQ,0BAA0B,EAAE;AAAA;AAAA,IACjE;AAAA,IACC,SACC,oBAAC,YAAO,yBAAyB,EAAE,QAAQ,OAAO,GAAG,IACnD;AAAA,IAMH,SACC;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB,EAAE,QAAQ,0BAA0B,EAAE;AAAA;AAAA,IACjE,IACE;AAAA,IACH,UAAU,kBACT;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB,EAAE,QAAQ,sBAAsB,EAAE;AAAA;AAAA,IAC7D,IACE;AAAA,IACH,oBAAoB,QACnB;AAAA,MAAC;AAAA;AAAA,QACC,yBAAyB;AAAA,UACvB,QAAQ;AAAA,QACV;AAAA;AAAA,IACF,IACE;AAAA,KACN;AAEJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roottale/analytics-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "RootTale analytics runtime (ADR-0038) — Consent Mode v2 fail-closed tag loader, consent banner, and first-party data-track beacon. Framework-agnostic strings + optional React drop-in (<RootTaleAnalytics>).",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"import": "./dist/react.js",
|
|
17
17
|
"default": "./dist/react.js"
|
|
18
18
|
},
|
|
19
|
+
"./next": {
|
|
20
|
+
"types": "./dist/next.d.ts",
|
|
21
|
+
"import": "./dist/next.js",
|
|
22
|
+
"default": "./dist/next.js"
|
|
23
|
+
},
|
|
24
|
+
"./events": {
|
|
25
|
+
"types": "./dist/events.d.ts",
|
|
26
|
+
"import": "./dist/events.js",
|
|
27
|
+
"default": "./dist/events.js"
|
|
28
|
+
},
|
|
19
29
|
"./package.json": "./package.json"
|
|
20
30
|
},
|
|
21
31
|
"files": [
|
|
@@ -23,15 +33,20 @@
|
|
|
23
33
|
"README.md"
|
|
24
34
|
],
|
|
25
35
|
"peerDependencies": {
|
|
36
|
+
"next": "^16.0.0",
|
|
26
37
|
"react": "^19.0.0"
|
|
27
38
|
},
|
|
28
39
|
"peerDependenciesMeta": {
|
|
40
|
+
"next": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
29
43
|
"react": {
|
|
30
44
|
"optional": true
|
|
31
45
|
}
|
|
32
46
|
},
|
|
33
47
|
"devDependencies": {
|
|
34
48
|
"@types/react": "^19.0.0",
|
|
49
|
+
"next": "^16.0.0",
|
|
35
50
|
"react": "^19.0.0",
|
|
36
51
|
"tsup": "^8.0.0",
|
|
37
52
|
"typescript": "^5.7.0",
|