landom-sdk 0.1.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.
@@ -0,0 +1,7 @@
1
+ import type { SDKConfig } from '../types';
2
+ import type { Logger } from '../utils/logger';
3
+ import type { EventQueue } from './event-queue';
4
+ export declare function setContext(q: EventQueue, c: SDKConfig, l: Logger): void;
5
+ export declare function getQueue(): EventQueue;
6
+ export declare function getConfig(): SDKConfig;
7
+ export declare function getLogger(): Logger;
@@ -0,0 +1,30 @@
1
+ import type { SDKEvent } from '../types';
2
+ import type { Transport } from '../transport/transport';
3
+ import type { Logger } from '../utils/logger';
4
+ /** EventQueue 생성에 필요한 설정 */
5
+ export interface EventQueueConfig {
6
+ flushInterval: number;
7
+ flushQueueSize: number;
8
+ maxQueueSize: number;
9
+ beforeSend?: (event: SDKEvent) => SDKEvent | null;
10
+ transport: Transport;
11
+ logger: Logger;
12
+ }
13
+ /**
14
+ * 이벤트 큐 모듈
15
+ *
16
+ * 이벤트를 배열에 버퍼링하고, 조건 충족 시 배치 전송한다.
17
+ * - flushInterval마다 자동 flush
18
+ * - flushQueueSize 도달 시 즉시 flush
19
+ * - maxQueueSize 초과 시 오래된 이벤트 드롭
20
+ * - beforeSend 훅으로 이벤트 가공/필터링
21
+ */
22
+ export declare function createEventQueue(config: EventQueueConfig): {
23
+ push: (event: SDKEvent) => void;
24
+ flush: () => Promise<void>;
25
+ flushSync: () => void;
26
+ start: () => void;
27
+ stop: () => void;
28
+ };
29
+ /** createEventQueue 반환 타입 */
30
+ export type EventQueue = ReturnType<typeof createEventQueue>;
@@ -0,0 +1,27 @@
1
+ import type { SDKOptions, EventType, EventPayload } from '../types';
2
+ /** 이벤트 수집기 인터페이스 (setup/teardown) */
3
+ export interface Collector {
4
+ setup(): void;
5
+ teardown(): void;
6
+ }
7
+ /**
8
+ * SDK 초기화
9
+ * 싱글턴으로 동작하며, 중복 호출 시 경고만 출력한다.
10
+ * 이벤트 수집기는 registerCollectors()로 별도 등록한다.
11
+ */
12
+ export declare function init(options: SDKOptions): void;
13
+ /**
14
+ * 이벤트 수집기 일괄 등록 및 시작
15
+ * init() 이후에 호출해야 한다.
16
+ */
17
+ export declare function registerCollectors(items: Collector[]): void;
18
+ /**
19
+ * 이벤트를 큐에 추가
20
+ * 외부에서 커스텀 이벤트를 보낼 때 사용
21
+ */
22
+ export declare function capture(type: EventType, payload?: EventPayload): void;
23
+ /**
24
+ * SDK 종료
25
+ * 모든 수집기 해제 + 잔여 이벤트 flush + 상태 초기화
26
+ */
27
+ export declare function destroy(): void;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 클릭(click) 이벤트 수집
4
+ * capture: true로 문서 최상단에서 모든 클릭을 감지
5
+ */
6
+ export declare function createClickCollector(): Collector;
@@ -0,0 +1,7 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 이탈(exit) 이벤트 수집기
4
+ * beforeunload + visibilitychange로 이탈을 감지
5
+ * 이탈 직전 보이는 요소와 최대 스크롤 깊이를 기록하고 flushSync로 즉시 전송
6
+ */
7
+ export declare function createExitCollector(): Collector;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 입력(input) 이벤트 수집기
4
+ * focus 이벤트로 입력 필드 진입만 1회 기록( 값은 수집하지 않음)
5
+ */
6
+ export declare function createInputCollector(): Collector;
@@ -0,0 +1,7 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 체류 섹션(ping) 이벤트 수집기
4
+ * IntersectionObserver로 현재 뷰포트에 보이는 섹션을 감지하고
5
+ * 5초 간격으로 현재 체류 섹션을 보고
6
+ */
7
+ export declare function createPingCollector(): Collector;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * rrweb 기반 세션 리플레이 수집기.
4
+ * 개인정보 보호를 위해 input 값은 기본 마스킹하고, rr-block/rr-mask 클래스를 지원한다.
5
+ */
6
+ export declare function createReplayCollector(): Collector;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 스크롤(scroll) 이벤트 수집기
4
+ * 500ms trailing-edge 쓰로틀로 스크롤 위치와 비율을 기록
5
+ */
6
+ export declare function createScrollCollector(): Collector;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 페이지 진입(start) 이벤트 수집
4
+ * init 시 즉시 start 이벤트를 발행
5
+ */
6
+ export declare function createStartCollector(): Collector;
@@ -0,0 +1,6 @@
1
+ import type { Collector } from '../core/sdk';
2
+ /**
3
+ * 탭 전환/최소화(visibility) 이벤트 수집기
4
+ * visibilitychange 이벤트로 탭 활성/비활성 상태를 추적
5
+ */
6
+ export declare function createVisibilityCollector(): Collector;
@@ -0,0 +1,2 @@
1
+ export { init, capture, destroy } from './core/sdk';
2
+ export type { EventType, EventPayload, StartPayload, VisibilityPayload, ScrollPayload, ClickPayload, InputPayload, ReplayPayload, PingPayload, ExitPayload, CustomPayload, SDKEvent, TransportPayload, SDKConfig, SDKOptions, } from './types';
@@ -0,0 +1,23 @@
1
+ import type { TransportPayload } from '../types';
2
+ import type { Logger } from '../utils/logger';
3
+ /** Transport 생성에 필요한 설정 */
4
+ export interface TransportConfig {
5
+ endpoint: string;
6
+ apiKey: string;
7
+ maxRetries: number;
8
+ logger: Logger;
9
+ }
10
+ /**
11
+ * 이벤트 전송 모듈
12
+ *
13
+ * 두 가지 전송 방식
14
+ * - send(): fetch(keepalive)로 전송. 커스텀 헤더(X-Project-Key) 사용 가능.
15
+ * - sendSync(): navigator.sendBeacon으로 전송. 페이지 이탈 시 사용.
16
+ * sendBeacon은 커스텀 헤더를 설정할 수 없으므로 body에 apiKey를 포함
17
+ */
18
+ export declare function createTransport(config: TransportConfig): {
19
+ send: (payload: TransportPayload) => Promise<boolean>;
20
+ sendSync: (payload: TransportPayload) => void;
21
+ };
22
+ /** createTransport 반환 타입 */
23
+ export type Transport = ReturnType<typeof createTransport>;
@@ -0,0 +1,75 @@
1
+ export type EventType = 'start' | 'visibility' | 'scroll' | 'click' | 'input' | 'replay' | 'ping' | 'exit' | 'custom';
2
+ export interface StartPayload {
3
+ }
4
+ export interface VisibilityPayload {
5
+ isVisible: boolean;
6
+ }
7
+ export interface ScrollPayload {
8
+ yOffset: number;
9
+ percentage: number;
10
+ }
11
+ export interface ClickPayload {
12
+ targetId: string;
13
+ }
14
+ export interface InputPayload {
15
+ fieldId: string;
16
+ }
17
+ export interface ReplayPayload {
18
+ event: unknown;
19
+ isCheckout: boolean;
20
+ version: 'rrweb';
21
+ }
22
+ export interface CompressedReplayPayload {
23
+ compressed: true;
24
+ compression: 'gzip';
25
+ encoding: 'base64';
26
+ data: string;
27
+ version: 'rrweb';
28
+ }
29
+ export interface PingPayload {
30
+ sectionId: string;
31
+ }
32
+ export interface ExitPayload {
33
+ lastElementId: string;
34
+ maxDepth: number;
35
+ }
36
+ export interface CustomPayload {
37
+ [key: string]: unknown;
38
+ }
39
+ export type EventPayload = StartPayload | VisibilityPayload | ScrollPayload | ClickPayload | InputPayload | ReplayPayload | CompressedReplayPayload | PingPayload | ExitPayload | CustomPayload;
40
+ export interface SDKEvent {
41
+ type: EventType;
42
+ timestamp: number;
43
+ cssSelector: string | null;
44
+ payload: EventPayload;
45
+ }
46
+ export interface TransportPayload {
47
+ sessionId: string;
48
+ userAgent: string;
49
+ url: string;
50
+ events: SDKEvent[];
51
+ }
52
+ export interface SDKConfig {
53
+ apiKey: string;
54
+ endpoint: string;
55
+ flushInterval: number;
56
+ flushQueueSize: number;
57
+ maxQueueSize: number;
58
+ maxRetries: number;
59
+ enableReplay: boolean;
60
+ replayMaskAllInputs: boolean;
61
+ replayBlockClass: string;
62
+ replayBlockSelector?: string;
63
+ replayMaskTextClass: string;
64
+ replayMaskTextSelector?: string;
65
+ replayInlineStylesheet: boolean;
66
+ replayCheckoutEveryNms: number;
67
+ replayMousemoveSampling: number | false;
68
+ replayMousemoveCallbackSampling: number;
69
+ replayScrollSampling: number;
70
+ replayInputSampling: 'all' | 'last';
71
+ beforeSend?: (event: SDKEvent) => SDKEvent | null;
72
+ debug: boolean;
73
+ }
74
+ export type SDKOptions = Partial<SDKConfig> & Pick<SDKConfig, 'apiKey'>;
75
+ export declare const DEFAULT_CONFIG: Omit<SDKConfig, 'apiKey'>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * DOM 요소에서 식별자를 추출한다
3
+ * 우선순위: #id > tag.firstClass > tag
4
+ * 클릭, 입력, 이탈 이벤트에서 타겟 요소를 식별하는 데 사용
5
+ */
6
+ export declare function getElementId(el: Element): string;
@@ -0,0 +1,10 @@
1
+ /** SDK 내부 로거 인터페이스(디버깅용으로 넣음) */
2
+ export interface Logger {
3
+ log(...args: unknown[]): void;
4
+ warn(...args: unknown[]): void;
5
+ }
6
+ /**
7
+ * 조건부 로거 생성
8
+ * debug가 true일 때만 콘솔에 [LandOm] 접두사와 함께 출력
9
+ */
10
+ export declare function createLogger(debug: boolean): Logger;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 요소의 CSS selector를 생성한다.
3
+ * LandOm-LLM의 html_tools/selector_lookup.py:build_css_selector와 동일한 규칙을 따른다.
4
+ * 규칙이 어긋나면 백엔드의 selector→funnel 매핑이 깨지므로 변경 시 LLM 쪽과 동기화 필수.
5
+ *
6
+ * 규칙:
7
+ * 1) id 속성이 있으면 `tag[id="..."]`를 쓰고 더 이상 위로 올라가지 않는다
8
+ * 2) 그 외에는 `tag:nth-of-type(N)` (같은 태그명 형제 중 1-based 위치)
9
+ * 3) 결합자는 ` > ` (직계 자식)
10
+ * 4) root까지만 올라간다 (기본값 document.body)
11
+ */
12
+ export declare function buildCssSelector(node: Element, root?: Element): string;
@@ -0,0 +1,18 @@
1
+ /** sessionStorage에 저장되는 세션 데이터 */
2
+ interface SessionData {
3
+ sessionId: string;
4
+ startedAt: number;
5
+ lastActivityAt: number;
6
+ }
7
+ /**
8
+ * UUIDv7 생성
9
+ * 상위 48비트에 밀리초 타임스탬프를 담아 시간순 정렬
10
+ */
11
+ export declare function generateUUIDv7(): string;
12
+ /** 기존 세션 반환, 없거나 만료되었으면 새로 생성 */
13
+ export declare function getOrCreateSession(): SessionData;
14
+ /** 마지막 활동 시간 갱신 */
15
+ export declare function touchSession(): void;
16
+ /** 현재 세션 ID 반환 (활동 시간도 갱신) */
17
+ export declare function getSessionId(): string;
18
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Trailing-edge throttle
3
+ * 마지막 호출 기준으로 ms 간격마다 한 번씩 실행한다
4
+ * 스크롤 등 고빈도 이벤트의 호출 횟수를 제한할 때 사용
5
+ *
6
+ * 예시 (ms = 500):
7
+ * 0ms → 스크롤 100px 들어옴 → lastArgs = 100, 타이머 시작
8
+ * 50ms → 스크롤 200px 들어옴 → lastArgs = 200, 타이머 있으니 무시
9
+ * 100ms → 스크롤 350px 들어옴 → lastArgs = 350, 타이머 있으니 무시
10
+ * 500ms → 타이머 만료 → fn(350) 실행 (마지막 값만 사용됨)
11
+ */
12
+ export declare function throttle<T extends (...args: any[]) => void>(fn: T, ms: number): T;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "landom-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight SDK that auto-captures landing page user behavior (clicks, scrolls, inputs, exits, rrweb session replay) and ships it to your analytics backend.",
5
+ "main": "dist/landom-sdk.cjs.js",
6
+ "module": "dist/landom-sdk.esm.js",
7
+ "browser": "dist/landom-sdk.umd.js",
8
+ "types": "dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/types/index.d.ts",
12
+ "import": "./dist/landom-sdk.esm.js",
13
+ "require": "./dist/landom-sdk.cjs.js",
14
+ "default": "./dist/landom-sdk.umd.js"
15
+ }
16
+ },
17
+ "sideEffects": false,
18
+ "files": [
19
+ "dist",
20
+ "LICENSE",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "rollup -c && tsc -p tsconfig.json",
25
+ "dev": "rollup -c -w",
26
+ "test": "jest --passWithNoTests",
27
+ "lint": "eslint src/",
28
+ "format": "prettier --write src/",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/DontYouKnowFunnel/LandOm-SDK.git"
37
+ },
38
+ "keywords": [
39
+ "analytics",
40
+ "landing-page",
41
+ "user-behavior",
42
+ "sdk",
43
+ "event-tracking"
44
+ ],
45
+ "author": "Kim Seongmin",
46
+ "license": "MIT",
47
+ "bugs": {
48
+ "url": "https://github.com/DontYouKnowFunnel/LandOm-SDK/issues"
49
+ },
50
+ "homepage": "https://github.com/DontYouKnowFunnel/LandOm-SDK#readme",
51
+ "devDependencies": {
52
+ "@rollup/plugin-node-resolve": "^16.0.3",
53
+ "rollup": "^4.60.0",
54
+ "rollup-plugin-esbuild": "^6.2.1",
55
+ "typescript": "^6.0.2"
56
+ },
57
+ "dependencies": {
58
+ "rrweb": "^2.0.0-alpha.4"
59
+ }
60
+ }