@dodger/sdk-core 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ 'use strict';var g="0.1.0",E={endpoint:"https://sdk.dodger.app/ingest",streamEndpoint:"https://sdk.dodger.app/stream",flushInterval:5e3,maxBatchSize:50,sampleRate:1,streamEnabled:true,debug:false};function u(i,t){return {...E,...t,apiKey:i}}var c="__dodger_session_id",b="__dodger_anon_id",f="__dodger_last_activity";function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{let t=Math.random()*16|0;return (i==="x"?t:t&3|8).toString(16)})}var o=class{constructor(t){this.storage=t??null,this.sessionId=v(),this.anonymousUserId=v(),this.lastActivity=Date.now();}async init(){if(this.storage)try{let t=await this.storage.get(b);t?this.anonymousUserId=t:await this.storage.set(b,this.anonymousUserId);let e=await this.storage.get(c),s=await this.storage.get(f);if(e&&s){let n=parseInt(s,10);Date.now()-n<18e5?(this.sessionId=e,this.lastActivity=n):await this.storage.set(c,this.sessionId);}else await this.storage.set(c,this.sessionId);await this.storage.set(f,this.lastActivity.toString());}catch{}}async touch(){let t=Date.now();if(t-this.lastActivity>18e5&&(this.sessionId=v(),this.storage))try{await this.storage.set(c,this.sessionId);}catch{}if(this.lastActivity=t,this.storage)try{await this.storage.set(f,t.toString());}catch{}}getSessionId(){return this.sessionId}getAnonymousUserId(){return this.anonymousUserId}getMetadata(){return {sessionId:this.sessionId,anonymousUserId:this.anonymousUserId}}},m=class{get(t){try{return localStorage.getItem(t)}catch{return null}}set(t,e){try{localStorage.setItem(t,e);}catch{}}remove(t){try{localStorage.removeItem(t);}catch{}}};var D=/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,x=/(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,R=/\b\d{3}-\d{2}-\d{4}\b/g,C=/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,h="[REDACTED]";function l(i){return i&&i.replace(D,h).replace(x,h).replace(R,h).replace(C,h)}function y(i){let t={};for(let[e,s]of Object.entries(i))typeof s=="string"?t[e]=l(s):s!==null&&typeof s=="object"&&!Array.isArray(s)?t[e]=y(s):Array.isArray(s)?t[e]=s.map(n=>typeof n=="string"?l(n):n!==null&&typeof n=="object"?y(n):n):t[e]=s;return t}function S(i){return {...i,pageUrl:i.pageUrl?l(i.pageUrl):i.pageUrl,data:y(i.data)}}var r=class{constructor(t,e,s){this.buffer=[];this.flushTimer=null;this.active=false;this.config=t,this.sessionMetadata=e,this.onFlush=s;}start(){this.active||(this.active=true,this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}stop(){this.active=false,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null);}add(t){if(!this.active)return;let e=t;if(this.config.beforeSend){try{e=this.config.beforeSend(t);}catch{return}if(!e)return}e=S(e),this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize&&this.flush();}flush(){if(this.buffer.length===0)return;let e={events:this.buffer.splice(0),session:this.sessionMetadata,sdkVersion:g};try{this.onFlush(e);}catch{}}updateSessionMetadata(t){this.sessionMetadata=t;}pending(){return this.buffer.length}};var I=[1e3,2e3,4e3],a=class{constructor(t,e,s=false){this.endpoint=t,this.apiKey=e,this.debug=s;}async send(t){let e=JSON.stringify(t);for(let s=0;s<=3;s++)try{let n=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:e,keepalive:!0});if(n.ok||n.status===202)return;if(n.status>=400&&n.status<500){this.debug&&console.warn(`[Dodger] Event batch rejected (${n.status})`);return}s<3&&await this.delay(I[s]);}catch{s<3?await this.delay(I[s]):this.debug&&console.warn("[Dodger] Failed to send event batch after retries");}}sendBeacon(t){if(typeof navigator>"u"||!navigator.sendBeacon)return false;try{let e=new Blob([JSON.stringify(t)],{type:"application/json"}),s=`${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;return navigator.sendBeacon(s,e)}catch{return false}}delay(t){return new Promise(e=>setTimeout(e,t))}};var d=class{constructor(t,e,s,n=false){this.eventSource=null;this.onConfig=null;this.reconnectAttempts=0;this.maxReconnectAttempts=5;this.reconnectTimer=null;this.active=false;this.endpoint=t,this.apiKey=e,this.sessionId=s,this.debug=n;}connect(t){typeof EventSource>"u"||this.active||(this.active=true,this.onConfig=t??null,this.doConnect());}doConnect(){if(!this.active)return;let t=`${this.endpoint}?key=${encodeURIComponent(this.apiKey)}&session=${encodeURIComponent(this.sessionId)}`;try{this.eventSource=new EventSource(t),this.eventSource.addEventListener("connected",e=>{this.reconnectAttempts=0,this.debug&&console.log("[Dodger] Stream connected",e.data);}),this.eventSource.addEventListener("config",e=>{try{let s=JSON.parse(e.data);this.onConfig?.(s);}catch{}}),this.eventSource.addEventListener("ping",()=>{}),this.eventSource.onerror=()=>{this.eventSource?.close(),this.eventSource=null,this.scheduleReconnect();};}catch{this.scheduleReconnect();}}scheduleReconnect(){if(!this.active)return;if(this.reconnectAttempts>=this.maxReconnectAttempts){this.debug&&console.warn("[Dodger] Stream max reconnect attempts reached");return}let t=Math.min(1e3*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.doConnect();},t);}disconnect(){this.active=false,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null);}};var p=class{constructor(t,e,s){this.stream=null;this.initialized=false;this.userTraits={};this.config=u(t,e),this.session=new o(s),this.transport=new a(this.config.endpoint,this.config.apiKey,this.config.debug),this.sampled=Math.random()<=this.config.sampleRate,this.buffer=new r(this.config,this.session.getMetadata(),n=>{this.transport.send(n).catch(()=>{});});}async init(){if(!this.initialized)try{await this.session.init(),this.buffer.updateSessionMetadata(this.session.getMetadata()),this.sampled&&(this.buffer.start(),this.config.streamEnabled&&(this.stream=new d(this.config.streamEndpoint,this.config.apiKey,this.session.getSessionId(),this.config.debug),this.stream.connect(t=>{this.applyRemoteConfig(t);}))),this.initialized=!0,this.config.debug&&console.log(`[Dodger] Initialized (sampled: ${this.sampled}, session: ${this.session.getSessionId()})`);}catch{}}track(t,e){if(!(!this.initialized||!this.sampled))try{this.session.touch();let s={type:t,timestamp:Date.now(),sessionId:this.session.getSessionId(),data:e??{}};this.buffer.add(s);}catch{}}identify(t){this.initialized&&t&&(this.userTraits={...this.userTraits,...t});}flush(){this.initialized&&this.buffer.flush();}flushBeacon(){!this.initialized||this.buffer.pending()===0||this.buffer.flush();}destroy(){try{this.buffer.flush(),this.buffer.stop(),this.stream?.disconnect(),this.initialized=!1;}catch{}}getSessionId(){return this.session.getSessionId()}getAnonymousUserId(){return this.session.getAnonymousUserId()}getSessionMetadata(){return this.session.getMetadata()}isActive(){return this.initialized&&this.sampled}getTransport(){return this.transport}getConfig(){return this.config}updateSessionMetadata(t){let s={...this.session.getMetadata(),...t};this.buffer.updateSessionMetadata(s);}applyRemoteConfig(t){t.sampleRate!==void 0&&(this.config.sampleRate=t.sampleRate,this.sampled=Math.random()<=t.sampleRate,this.sampled?this.buffer.start():this.buffer.stop());}},T={_client:null,async init(i,t,e){return this._client&&this._client.destroy(),this._client=new p(i,t,e),await this._client.init(),this._client},track(i,t){this._client?.track(i,t);},identify(i){this._client?.identify(i);},flush(){this._client?.flush();},destroy(){this._client?.destroy(),this._client=null;},get client(){return this._client}};
2
+ exports.DEFAULT_CONFIG=E;exports.Dodger=T;exports.DodgerClient=p;exports.EventBuffer=r;exports.FetchTransport=a;exports.LocalStorageAdapter=m;exports.SDK_VERSION=g;exports.SessionManager=o;exports.StreamConnection=d;exports.resolveConfig=u;exports.scrubEvent=S;exports.scrubText=l;//# sourceMappingURL=index.cjs.map
3
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/session.ts","../src/scrub.ts","../src/events.ts","../src/transport.ts","../src/stream.ts","../src/client.ts"],"names":["SDK_VERSION","DEFAULT_CONFIG","resolveConfig","apiKey","options","SESSION_KEY","ANON_ID_KEY","LAST_ACTIVITY_KEY","uuid","c","r","SessionManager","storage","storedAnonId","storedSessionId","storedLastActivity","lastActivityTime","now","LocalStorageAdapter","key","value","EMAIL_REGEX","PHONE_REGEX","SSN_REGEX","CREDIT_CARD_REGEX","REDACTED","scrubText","text","scrubObject","obj","result","item","scrubEvent","event","EventBuffer","config","sessionMetadata","onFlush","processedEvent","payload","metadata","RETRY_DELAYS","FetchTransport","endpoint","debug","body","attempt","response","blob","url","ms","resolve","StreamConnection","sessionId","onConfig","delay","DodgerClient","remoteConfig","eventType","data","traits","partial","updated","Dodger"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { DodgerConfig, DodgerOptions } from \"./types.js\";\n\nexport const SDK_VERSION = \"0.1.0\";\n\nexport const DEFAULT_CONFIG: Omit<DodgerConfig, \"apiKey\"> = {\n endpoint: \"https://sdk.dodger.app/ingest\",\n streamEndpoint: \"https://sdk.dodger.app/stream\",\n flushInterval: 5000,\n maxBatchSize: 50,\n sampleRate: 1.0,\n streamEnabled: true,\n debug: false,\n};\n\nexport function resolveConfig(\n apiKey: string,\n options?: DodgerOptions\n): DodgerConfig {\n return {\n ...DEFAULT_CONFIG,\n ...options,\n apiKey,\n };\n}\n","import type { SessionMetadata, StorageAdapter } from \"./types.js\";\n\nconst SESSION_KEY = \"__dodger_session_id\";\nconst ANON_ID_KEY = \"__dodger_anon_id\";\nconst LAST_ACTIVITY_KEY = \"__dodger_last_activity\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n/** Generate a UUID v4 using native crypto */\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport class SessionManager {\n private sessionId: string;\n private anonymousUserId: string;\n private lastActivity: number;\n private storage: StorageAdapter | null;\n\n constructor(storage?: StorageAdapter) {\n this.storage = storage ?? null;\n this.sessionId = uuid();\n this.anonymousUserId = uuid();\n this.lastActivity = Date.now();\n }\n\n /** Initialize session from storage, restoring or creating IDs */\n async init(): Promise<void> {\n if (!this.storage) return;\n\n try {\n // Restore or create anonymous ID (persists across sessions)\n const storedAnonId = await this.storage.get(ANON_ID_KEY);\n if (storedAnonId) {\n this.anonymousUserId = storedAnonId;\n } else {\n await this.storage.set(ANON_ID_KEY, this.anonymousUserId);\n }\n\n // Check for existing session\n const storedSessionId = await this.storage.get(SESSION_KEY);\n const storedLastActivity = await this.storage.get(LAST_ACTIVITY_KEY);\n\n if (storedSessionId && storedLastActivity) {\n const lastActivityTime = parseInt(storedLastActivity, 10);\n if (Date.now() - lastActivityTime < SESSION_TIMEOUT_MS) {\n // Resume existing session\n this.sessionId = storedSessionId;\n this.lastActivity = lastActivityTime;\n } else {\n // Session expired — start new one\n await this.storage.set(SESSION_KEY, this.sessionId);\n }\n } else {\n await this.storage.set(SESSION_KEY, this.sessionId);\n }\n\n await this.storage.set(\n LAST_ACTIVITY_KEY,\n this.lastActivity.toString()\n );\n } catch {\n // Storage unavailable — continue with in-memory session\n }\n }\n\n /** Record activity to keep session alive */\n async touch(): Promise<void> {\n const now = Date.now();\n\n // Check if session has timed out\n if (now - this.lastActivity > SESSION_TIMEOUT_MS) {\n this.sessionId = uuid();\n if (this.storage) {\n try {\n await this.storage.set(SESSION_KEY, this.sessionId);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n this.lastActivity = now;\n if (this.storage) {\n try {\n await this.storage.set(LAST_ACTIVITY_KEY, now.toString());\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n getAnonymousUserId(): string {\n return this.anonymousUserId;\n }\n\n getMetadata(): SessionMetadata {\n return {\n sessionId: this.sessionId,\n anonymousUserId: this.anonymousUserId,\n };\n }\n}\n\n/** LocalStorage adapter for web browsers */\nexport class LocalStorageAdapter implements StorageAdapter {\n get(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // localStorage unavailable or full\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Ignore\n }\n }\n}\n","import type { DodgerEvent } from \"./types.js\";\n\nconst EMAIL_REGEX =\n /[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}/g;\nconst PHONE_REGEX =\n /(\\+?1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g;\nconst SSN_REGEX = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\nconst CREDIT_CARD_REGEX = /\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/g;\n\nconst REDACTED = \"[REDACTED]\";\n\n/** Strip PII patterns from a string */\nexport function scrubText(text: string): string {\n if (!text) return text;\n return text\n .replace(EMAIL_REGEX, REDACTED)\n .replace(PHONE_REGEX, REDACTED)\n .replace(SSN_REGEX, REDACTED)\n .replace(CREDIT_CARD_REGEX, REDACTED);\n}\n\n/** Deep-scrub an object's string values */\nfunction scrubObject(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = scrubText(value);\n } else if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value)\n ) {\n result[key] = scrubObject(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n result[key] = value.map((item) =>\n typeof item === \"string\"\n ? scrubText(item)\n : item !== null && typeof item === \"object\"\n ? scrubObject(item as Record<string, unknown>)\n : item\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/** Scrub PII from an event's data and metadata */\nexport function scrubEvent(event: DodgerEvent): DodgerEvent {\n return {\n ...event,\n pageUrl: event.pageUrl ? scrubText(event.pageUrl) : event.pageUrl,\n data: scrubObject(event.data),\n };\n}\n","import type { DodgerEvent, DodgerConfig, BatchPayload, SessionMetadata } from \"./types.js\";\nimport { scrubEvent } from \"./scrub.js\";\nimport { SDK_VERSION } from \"./config.js\";\n\nexport type FlushCallback = (payload: BatchPayload) => void;\n\nexport class EventBuffer {\n private buffer: DodgerEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private config: DodgerConfig;\n private sessionMetadata: SessionMetadata;\n private onFlush: FlushCallback;\n private active = false;\n\n constructor(\n config: DodgerConfig,\n sessionMetadata: SessionMetadata,\n onFlush: FlushCallback\n ) {\n this.config = config;\n this.sessionMetadata = sessionMetadata;\n this.onFlush = onFlush;\n }\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.flushTimer = setInterval(() => {\n this.flush();\n }, this.config.flushInterval);\n }\n\n stop(): void {\n this.active = false;\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n add(event: DodgerEvent): void {\n if (!this.active) return;\n\n // Apply beforeSend hook\n let processedEvent: DodgerEvent | null = event;\n if (this.config.beforeSend) {\n try {\n processedEvent = this.config.beforeSend(event);\n } catch {\n // beforeSend threw — skip event\n return;\n }\n if (!processedEvent) return;\n }\n\n // Scrub PII\n processedEvent = scrubEvent(processedEvent);\n\n this.buffer.push(processedEvent);\n\n // Flush if buffer is full\n if (this.buffer.length >= this.config.maxBatchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer.splice(0);\n const payload: BatchPayload = {\n events,\n session: this.sessionMetadata,\n sdkVersion: SDK_VERSION,\n };\n\n try {\n this.onFlush(payload);\n } catch {\n // Silently drop — never crash host app\n }\n }\n\n updateSessionMetadata(metadata: SessionMetadata): void {\n this.sessionMetadata = metadata;\n }\n\n pending(): number {\n return this.buffer.length;\n }\n}\n","import type { BatchPayload } from \"./types.js\";\n\nconst MAX_RETRIES = 3;\nconst RETRY_DELAYS = [1000, 2000, 4000];\n\nexport class FetchTransport {\n private endpoint: string;\n private apiKey: string;\n private debug: boolean;\n\n constructor(endpoint: string, apiKey: string, debug = false) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.debug = debug;\n }\n\n async send(payload: BatchPayload): Promise<void> {\n const body = JSON.stringify(payload);\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body,\n keepalive: true,\n });\n\n if (response.ok || response.status === 202) {\n return;\n }\n\n // 4xx errors — don't retry (client error)\n if (response.status >= 400 && response.status < 500) {\n if (this.debug) {\n console.warn(\n `[Dodger] Event batch rejected (${response.status})`\n );\n }\n return;\n }\n\n // 5xx — retry\n if (attempt < MAX_RETRIES) {\n await this.delay(RETRY_DELAYS[attempt]);\n }\n } catch {\n // Network error — retry\n if (attempt < MAX_RETRIES) {\n await this.delay(RETRY_DELAYS[attempt]);\n } else if (this.debug) {\n console.warn(\"[Dodger] Failed to send event batch after retries\");\n }\n }\n }\n }\n\n /** Send via navigator.sendBeacon (for page unload) */\n sendBeacon(payload: BatchPayload): boolean {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return false;\n }\n\n try {\n const blob = new Blob([JSON.stringify(payload)], {\n type: \"application/json\",\n });\n // sendBeacon doesn't support custom headers, so we encode the key in the URL\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;\n return navigator.sendBeacon(url, blob);\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { RemoteConfig } from \"./types.js\";\n\ntype ConfigCallback = (config: RemoteConfig) => void;\n\nexport class StreamConnection {\n private endpoint: string;\n private apiKey: string;\n private sessionId: string;\n private eventSource: EventSource | null = null;\n private onConfig: ConfigCallback | null = null;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 5;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private active = false;\n private debug: boolean;\n\n constructor(\n endpoint: string,\n apiKey: string,\n sessionId: string,\n debug = false\n ) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.sessionId = sessionId;\n this.debug = debug;\n }\n\n connect(onConfig?: ConfigCallback): void {\n if (typeof EventSource === \"undefined\") return;\n if (this.active) return;\n\n this.active = true;\n this.onConfig = onConfig ?? null;\n this.doConnect();\n }\n\n private doConnect(): void {\n if (!this.active) return;\n\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}&session=${encodeURIComponent(this.sessionId)}`;\n\n try {\n this.eventSource = new EventSource(url);\n\n this.eventSource.addEventListener(\"connected\", (e) => {\n this.reconnectAttempts = 0;\n if (this.debug) {\n console.log(\"[Dodger] Stream connected\", (e as MessageEvent).data);\n }\n });\n\n this.eventSource.addEventListener(\"config\", (e) => {\n try {\n const config: RemoteConfig = JSON.parse((e as MessageEvent).data);\n this.onConfig?.(config);\n } catch {\n // Invalid config data\n }\n });\n\n this.eventSource.addEventListener(\"ping\", () => {\n // Keep-alive acknowledged\n });\n\n this.eventSource.onerror = () => {\n this.eventSource?.close();\n this.eventSource = null;\n this.scheduleReconnect();\n };\n } catch {\n this.scheduleReconnect();\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.active) return;\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.debug) {\n console.warn(\"[Dodger] Stream max reconnect attempts reached\");\n }\n return;\n }\n\n const delay = Math.min(\n 1000 * Math.pow(2, this.reconnectAttempts),\n 30000\n );\n this.reconnectAttempts++;\n\n this.reconnectTimer = setTimeout(() => {\n this.doConnect();\n }, delay);\n }\n\n disconnect(): void {\n this.active = false;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n }\n}\n","import type {\n DodgerConfig,\n DodgerEvent,\n DodgerEventType,\n DodgerOptions,\n RemoteConfig,\n SessionMetadata,\n StorageAdapter,\n} from \"./types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { SessionManager } from \"./session.js\";\nimport { EventBuffer } from \"./events.js\";\nimport { FetchTransport } from \"./transport.js\";\nimport { StreamConnection } from \"./stream.js\";\n\nexport class DodgerClient {\n private config: DodgerConfig;\n private session: SessionManager;\n private buffer: EventBuffer;\n private transport: FetchTransport;\n private stream: StreamConnection | null = null;\n private sampled: boolean;\n private initialized = false;\n private userTraits: Record<string, unknown> = {};\n\n constructor(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter) {\n this.config = resolveConfig(apiKey, options);\n this.session = new SessionManager(storage);\n this.transport = new FetchTransport(\n this.config.endpoint,\n this.config.apiKey,\n this.config.debug\n );\n\n // Determine if this session should be sampled\n this.sampled = Math.random() <= this.config.sampleRate;\n\n this.buffer = new EventBuffer(\n this.config,\n this.session.getMetadata(),\n (payload) => {\n this.transport.send(payload).catch(() => {\n // Silently drop\n });\n }\n );\n }\n\n /** Initialize the client — must be called before tracking */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n await this.session.init();\n this.buffer.updateSessionMetadata(this.session.getMetadata());\n\n if (this.sampled) {\n this.buffer.start();\n\n // Open stream connection\n if (this.config.streamEnabled) {\n this.stream = new StreamConnection(\n this.config.streamEndpoint,\n this.config.apiKey,\n this.session.getSessionId(),\n this.config.debug\n );\n this.stream.connect((remoteConfig) => {\n this.applyRemoteConfig(remoteConfig);\n });\n }\n }\n\n this.initialized = true;\n\n if (this.config.debug) {\n console.log(\n `[Dodger] Initialized (sampled: ${this.sampled}, session: ${this.session.getSessionId()})`\n );\n }\n } catch {\n // Fail silently\n }\n }\n\n /** Track a custom event */\n track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void {\n if (!this.initialized || !this.sampled) return;\n\n try {\n this.session.touch();\n\n const event: DodgerEvent = {\n type: eventType as DodgerEventType,\n timestamp: Date.now(),\n sessionId: this.session.getSessionId(),\n data: data ?? {},\n };\n\n this.buffer.add(event);\n } catch {\n // Never crash host app\n }\n }\n\n /** Identify the user with traits (not PII — use for segmentation) */\n identify(traits?: Record<string, unknown>): void {\n if (!this.initialized) return;\n if (traits) {\n this.userTraits = { ...this.userTraits, ...traits };\n }\n }\n\n /** Manually flush the event buffer */\n flush(): void {\n if (!this.initialized) return;\n this.buffer.flush();\n }\n\n /** Flush using sendBeacon (for page unload) */\n flushBeacon(): void {\n if (!this.initialized || this.buffer.pending() === 0) return;\n this.buffer.flush();\n }\n\n /** Destroy the client and clean up */\n destroy(): void {\n try {\n this.buffer.flush();\n this.buffer.stop();\n this.stream?.disconnect();\n this.initialized = false;\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Get the current session ID */\n getSessionId(): string {\n return this.session.getSessionId();\n }\n\n /** Get the anonymous user ID */\n getAnonymousUserId(): string {\n return this.session.getAnonymousUserId();\n }\n\n /** Get the current session metadata */\n getSessionMetadata(): SessionMetadata {\n return this.session.getMetadata();\n }\n\n /** Check if the client is initialized and sampling */\n isActive(): boolean {\n return this.initialized && this.sampled;\n }\n\n /** Get the transport for direct sendBeacon calls */\n getTransport(): FetchTransport {\n return this.transport;\n }\n\n /** Get the current config */\n getConfig(): DodgerConfig {\n return this.config;\n }\n\n /** Update session metadata (called by platform adapters to add viewport, device info, etc.) */\n updateSessionMetadata(partial: Partial<SessionMetadata>): void {\n const current = this.session.getMetadata();\n const updated: SessionMetadata = { ...current, ...partial };\n this.buffer.updateSessionMetadata(updated);\n }\n\n private applyRemoteConfig(config: RemoteConfig): void {\n if (config.sampleRate !== undefined) {\n this.config.sampleRate = config.sampleRate;\n // Re-evaluate sampling\n this.sampled = Math.random() <= config.sampleRate;\n if (!this.sampled) {\n this.buffer.stop();\n } else {\n this.buffer.start();\n }\n }\n }\n}\n\n/**\n * Singleton Dodger instance for the simple two-line integration:\n *\n * ```\n * import { Dodger } from '@dodger/sdk-core';\n * Dodger.init('pk_...');\n * ```\n */\nexport const Dodger = {\n _client: null as DodgerClient | null,\n\n async init(\n apiKey: string,\n options?: DodgerOptions,\n storage?: StorageAdapter\n ): Promise<DodgerClient> {\n if (this._client) {\n this._client.destroy();\n }\n this._client = new DodgerClient(apiKey, options, storage);\n await this._client.init();\n return this._client;\n },\n\n track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void {\n this._client?.track(eventType, data);\n },\n\n identify(traits?: Record<string, unknown>): void {\n this._client?.identify(traits);\n },\n\n flush(): void {\n this._client?.flush();\n },\n\n destroy(): void {\n this._client?.destroy();\n this._client = null;\n },\n\n get client(): DodgerClient | null {\n return this._client;\n },\n};\n"]}
@@ -0,0 +1,244 @@
1
+ /** Event types captured by the SDK */
2
+ type DodgerEventType = "click" | "scroll" | "navigation" | "rage_click" | "dead_click" | "form_interaction" | "page_view" | "session_start" | "session_end" | "touch" | "rage_tap" | "screen_view" | "custom";
3
+ /** Base event structure */
4
+ interface DodgerEvent {
5
+ type: DodgerEventType;
6
+ timestamp: number;
7
+ sessionId: string;
8
+ pageUrl?: string;
9
+ data: Record<string, unknown>;
10
+ }
11
+ /** Click event data */
12
+ interface ClickEventData {
13
+ x: number;
14
+ y: number;
15
+ selector: string;
16
+ elementTag: string;
17
+ elementText?: string;
18
+ viewportWidth: number;
19
+ viewportHeight: number;
20
+ }
21
+ /** Scroll event data */
22
+ interface ScrollEventData {
23
+ scrollDepth: number;
24
+ scrollTop: number;
25
+ viewportHeight: number;
26
+ documentHeight: number;
27
+ direction: "up" | "down";
28
+ }
29
+ /** Navigation event data */
30
+ interface NavigationEventData {
31
+ fromUrl: string;
32
+ toUrl: string;
33
+ referrer?: string;
34
+ timeOnPreviousPage: number;
35
+ }
36
+ /** Form interaction data */
37
+ interface FormEventData {
38
+ action: "focus" | "blur";
39
+ fieldName: string;
40
+ fieldType: string;
41
+ formId?: string;
42
+ }
43
+ /** Touch event data (React Native) */
44
+ interface TouchEventData {
45
+ x: number;
46
+ y: number;
47
+ componentType?: string;
48
+ testID?: string;
49
+ accessibilityLabel?: string;
50
+ }
51
+ /** Screen view data (React Native) */
52
+ interface ScreenViewData {
53
+ screenName: string;
54
+ previousScreen?: string;
55
+ params?: Record<string, string>;
56
+ }
57
+ /** Session metadata sent with each batch */
58
+ interface SessionMetadata {
59
+ sessionId: string;
60
+ anonymousUserId: string;
61
+ viewport?: {
62
+ width: number;
63
+ height: number;
64
+ };
65
+ deviceType?: string;
66
+ browser?: string;
67
+ os?: string;
68
+ screenResolution?: string;
69
+ language?: string;
70
+ timezone?: string;
71
+ }
72
+ /** Batch payload sent to the ingestion endpoint */
73
+ interface BatchPayload {
74
+ events: DodgerEvent[];
75
+ session: SessionMetadata;
76
+ sdkVersion: string;
77
+ }
78
+ /** SDK configuration */
79
+ interface DodgerConfig {
80
+ apiKey: string;
81
+ endpoint: string;
82
+ streamEndpoint: string;
83
+ flushInterval: number;
84
+ maxBatchSize: number;
85
+ sampleRate: number;
86
+ streamEnabled: boolean;
87
+ debug: boolean;
88
+ beforeSend?: (event: DodgerEvent) => DodgerEvent | null;
89
+ }
90
+ /** User-provided partial config */
91
+ type DodgerOptions = Partial<Omit<DodgerConfig, "apiKey">>;
92
+ /** Storage adapter interface for persistence */
93
+ interface StorageAdapter {
94
+ get(key: string): string | null | Promise<string | null>;
95
+ set(key: string, value: string): void | Promise<void>;
96
+ remove(key: string): void | Promise<void>;
97
+ }
98
+ /** Stream event types received from the server */
99
+ type StreamEventType = "connected" | "config" | "ping";
100
+ /** Stream event data */
101
+ interface StreamEvent {
102
+ type: StreamEventType;
103
+ data: Record<string, unknown>;
104
+ }
105
+ /** Config received from stream */
106
+ interface RemoteConfig {
107
+ sampleRate?: number;
108
+ features?: Record<string, boolean>;
109
+ }
110
+
111
+ declare class FetchTransport {
112
+ private endpoint;
113
+ private apiKey;
114
+ private debug;
115
+ constructor(endpoint: string, apiKey: string, debug?: boolean);
116
+ send(payload: BatchPayload): Promise<void>;
117
+ /** Send via navigator.sendBeacon (for page unload) */
118
+ sendBeacon(payload: BatchPayload): boolean;
119
+ private delay;
120
+ }
121
+
122
+ declare class DodgerClient {
123
+ private config;
124
+ private session;
125
+ private buffer;
126
+ private transport;
127
+ private stream;
128
+ private sampled;
129
+ private initialized;
130
+ private userTraits;
131
+ constructor(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter);
132
+ /** Initialize the client — must be called before tracking */
133
+ init(): Promise<void>;
134
+ /** Track a custom event */
135
+ track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void;
136
+ /** Identify the user with traits (not PII — use for segmentation) */
137
+ identify(traits?: Record<string, unknown>): void;
138
+ /** Manually flush the event buffer */
139
+ flush(): void;
140
+ /** Flush using sendBeacon (for page unload) */
141
+ flushBeacon(): void;
142
+ /** Destroy the client and clean up */
143
+ destroy(): void;
144
+ /** Get the current session ID */
145
+ getSessionId(): string;
146
+ /** Get the anonymous user ID */
147
+ getAnonymousUserId(): string;
148
+ /** Get the current session metadata */
149
+ getSessionMetadata(): SessionMetadata;
150
+ /** Check if the client is initialized and sampling */
151
+ isActive(): boolean;
152
+ /** Get the transport for direct sendBeacon calls */
153
+ getTransport(): FetchTransport;
154
+ /** Get the current config */
155
+ getConfig(): DodgerConfig;
156
+ /** Update session metadata (called by platform adapters to add viewport, device info, etc.) */
157
+ updateSessionMetadata(partial: Partial<SessionMetadata>): void;
158
+ private applyRemoteConfig;
159
+ }
160
+ /**
161
+ * Singleton Dodger instance for the simple two-line integration:
162
+ *
163
+ * ```
164
+ * import { Dodger } from '@dodger/sdk-core';
165
+ * Dodger.init('pk_...');
166
+ * ```
167
+ */
168
+ declare const Dodger: {
169
+ _client: DodgerClient | null;
170
+ init(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter): Promise<DodgerClient>;
171
+ track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void;
172
+ identify(traits?: Record<string, unknown>): void;
173
+ flush(): void;
174
+ destroy(): void;
175
+ readonly client: DodgerClient | null;
176
+ };
177
+
178
+ declare class SessionManager {
179
+ private sessionId;
180
+ private anonymousUserId;
181
+ private lastActivity;
182
+ private storage;
183
+ constructor(storage?: StorageAdapter);
184
+ /** Initialize session from storage, restoring or creating IDs */
185
+ init(): Promise<void>;
186
+ /** Record activity to keep session alive */
187
+ touch(): Promise<void>;
188
+ getSessionId(): string;
189
+ getAnonymousUserId(): string;
190
+ getMetadata(): SessionMetadata;
191
+ }
192
+ /** LocalStorage adapter for web browsers */
193
+ declare class LocalStorageAdapter implements StorageAdapter {
194
+ get(key: string): string | null;
195
+ set(key: string, value: string): void;
196
+ remove(key: string): void;
197
+ }
198
+
199
+ type FlushCallback = (payload: BatchPayload) => void;
200
+ declare class EventBuffer {
201
+ private buffer;
202
+ private flushTimer;
203
+ private config;
204
+ private sessionMetadata;
205
+ private onFlush;
206
+ private active;
207
+ constructor(config: DodgerConfig, sessionMetadata: SessionMetadata, onFlush: FlushCallback);
208
+ start(): void;
209
+ stop(): void;
210
+ add(event: DodgerEvent): void;
211
+ flush(): void;
212
+ updateSessionMetadata(metadata: SessionMetadata): void;
213
+ pending(): number;
214
+ }
215
+
216
+ type ConfigCallback = (config: RemoteConfig) => void;
217
+ declare class StreamConnection {
218
+ private endpoint;
219
+ private apiKey;
220
+ private sessionId;
221
+ private eventSource;
222
+ private onConfig;
223
+ private reconnectAttempts;
224
+ private maxReconnectAttempts;
225
+ private reconnectTimer;
226
+ private active;
227
+ private debug;
228
+ constructor(endpoint: string, apiKey: string, sessionId: string, debug?: boolean);
229
+ connect(onConfig?: ConfigCallback): void;
230
+ private doConnect;
231
+ private scheduleReconnect;
232
+ disconnect(): void;
233
+ }
234
+
235
+ /** Strip PII patterns from a string */
236
+ declare function scrubText(text: string): string;
237
+ /** Scrub PII from an event's data and metadata */
238
+ declare function scrubEvent(event: DodgerEvent): DodgerEvent;
239
+
240
+ declare const SDK_VERSION = "0.1.0";
241
+ declare const DEFAULT_CONFIG: Omit<DodgerConfig, "apiKey">;
242
+ declare function resolveConfig(apiKey: string, options?: DodgerOptions): DodgerConfig;
243
+
244
+ export { type BatchPayload, type ClickEventData, DEFAULT_CONFIG, Dodger, DodgerClient, type DodgerConfig, type DodgerEvent, type DodgerEventType, type DodgerOptions, EventBuffer, FetchTransport, type FormEventData, LocalStorageAdapter, type NavigationEventData, type RemoteConfig, SDK_VERSION, type ScreenViewData, type ScrollEventData, SessionManager, type SessionMetadata, type StorageAdapter, StreamConnection, type StreamEvent, type StreamEventType, type TouchEventData, resolveConfig, scrubEvent, scrubText };
@@ -0,0 +1,244 @@
1
+ /** Event types captured by the SDK */
2
+ type DodgerEventType = "click" | "scroll" | "navigation" | "rage_click" | "dead_click" | "form_interaction" | "page_view" | "session_start" | "session_end" | "touch" | "rage_tap" | "screen_view" | "custom";
3
+ /** Base event structure */
4
+ interface DodgerEvent {
5
+ type: DodgerEventType;
6
+ timestamp: number;
7
+ sessionId: string;
8
+ pageUrl?: string;
9
+ data: Record<string, unknown>;
10
+ }
11
+ /** Click event data */
12
+ interface ClickEventData {
13
+ x: number;
14
+ y: number;
15
+ selector: string;
16
+ elementTag: string;
17
+ elementText?: string;
18
+ viewportWidth: number;
19
+ viewportHeight: number;
20
+ }
21
+ /** Scroll event data */
22
+ interface ScrollEventData {
23
+ scrollDepth: number;
24
+ scrollTop: number;
25
+ viewportHeight: number;
26
+ documentHeight: number;
27
+ direction: "up" | "down";
28
+ }
29
+ /** Navigation event data */
30
+ interface NavigationEventData {
31
+ fromUrl: string;
32
+ toUrl: string;
33
+ referrer?: string;
34
+ timeOnPreviousPage: number;
35
+ }
36
+ /** Form interaction data */
37
+ interface FormEventData {
38
+ action: "focus" | "blur";
39
+ fieldName: string;
40
+ fieldType: string;
41
+ formId?: string;
42
+ }
43
+ /** Touch event data (React Native) */
44
+ interface TouchEventData {
45
+ x: number;
46
+ y: number;
47
+ componentType?: string;
48
+ testID?: string;
49
+ accessibilityLabel?: string;
50
+ }
51
+ /** Screen view data (React Native) */
52
+ interface ScreenViewData {
53
+ screenName: string;
54
+ previousScreen?: string;
55
+ params?: Record<string, string>;
56
+ }
57
+ /** Session metadata sent with each batch */
58
+ interface SessionMetadata {
59
+ sessionId: string;
60
+ anonymousUserId: string;
61
+ viewport?: {
62
+ width: number;
63
+ height: number;
64
+ };
65
+ deviceType?: string;
66
+ browser?: string;
67
+ os?: string;
68
+ screenResolution?: string;
69
+ language?: string;
70
+ timezone?: string;
71
+ }
72
+ /** Batch payload sent to the ingestion endpoint */
73
+ interface BatchPayload {
74
+ events: DodgerEvent[];
75
+ session: SessionMetadata;
76
+ sdkVersion: string;
77
+ }
78
+ /** SDK configuration */
79
+ interface DodgerConfig {
80
+ apiKey: string;
81
+ endpoint: string;
82
+ streamEndpoint: string;
83
+ flushInterval: number;
84
+ maxBatchSize: number;
85
+ sampleRate: number;
86
+ streamEnabled: boolean;
87
+ debug: boolean;
88
+ beforeSend?: (event: DodgerEvent) => DodgerEvent | null;
89
+ }
90
+ /** User-provided partial config */
91
+ type DodgerOptions = Partial<Omit<DodgerConfig, "apiKey">>;
92
+ /** Storage adapter interface for persistence */
93
+ interface StorageAdapter {
94
+ get(key: string): string | null | Promise<string | null>;
95
+ set(key: string, value: string): void | Promise<void>;
96
+ remove(key: string): void | Promise<void>;
97
+ }
98
+ /** Stream event types received from the server */
99
+ type StreamEventType = "connected" | "config" | "ping";
100
+ /** Stream event data */
101
+ interface StreamEvent {
102
+ type: StreamEventType;
103
+ data: Record<string, unknown>;
104
+ }
105
+ /** Config received from stream */
106
+ interface RemoteConfig {
107
+ sampleRate?: number;
108
+ features?: Record<string, boolean>;
109
+ }
110
+
111
+ declare class FetchTransport {
112
+ private endpoint;
113
+ private apiKey;
114
+ private debug;
115
+ constructor(endpoint: string, apiKey: string, debug?: boolean);
116
+ send(payload: BatchPayload): Promise<void>;
117
+ /** Send via navigator.sendBeacon (for page unload) */
118
+ sendBeacon(payload: BatchPayload): boolean;
119
+ private delay;
120
+ }
121
+
122
+ declare class DodgerClient {
123
+ private config;
124
+ private session;
125
+ private buffer;
126
+ private transport;
127
+ private stream;
128
+ private sampled;
129
+ private initialized;
130
+ private userTraits;
131
+ constructor(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter);
132
+ /** Initialize the client — must be called before tracking */
133
+ init(): Promise<void>;
134
+ /** Track a custom event */
135
+ track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void;
136
+ /** Identify the user with traits (not PII — use for segmentation) */
137
+ identify(traits?: Record<string, unknown>): void;
138
+ /** Manually flush the event buffer */
139
+ flush(): void;
140
+ /** Flush using sendBeacon (for page unload) */
141
+ flushBeacon(): void;
142
+ /** Destroy the client and clean up */
143
+ destroy(): void;
144
+ /** Get the current session ID */
145
+ getSessionId(): string;
146
+ /** Get the anonymous user ID */
147
+ getAnonymousUserId(): string;
148
+ /** Get the current session metadata */
149
+ getSessionMetadata(): SessionMetadata;
150
+ /** Check if the client is initialized and sampling */
151
+ isActive(): boolean;
152
+ /** Get the transport for direct sendBeacon calls */
153
+ getTransport(): FetchTransport;
154
+ /** Get the current config */
155
+ getConfig(): DodgerConfig;
156
+ /** Update session metadata (called by platform adapters to add viewport, device info, etc.) */
157
+ updateSessionMetadata(partial: Partial<SessionMetadata>): void;
158
+ private applyRemoteConfig;
159
+ }
160
+ /**
161
+ * Singleton Dodger instance for the simple two-line integration:
162
+ *
163
+ * ```
164
+ * import { Dodger } from '@dodger/sdk-core';
165
+ * Dodger.init('pk_...');
166
+ * ```
167
+ */
168
+ declare const Dodger: {
169
+ _client: DodgerClient | null;
170
+ init(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter): Promise<DodgerClient>;
171
+ track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void;
172
+ identify(traits?: Record<string, unknown>): void;
173
+ flush(): void;
174
+ destroy(): void;
175
+ readonly client: DodgerClient | null;
176
+ };
177
+
178
+ declare class SessionManager {
179
+ private sessionId;
180
+ private anonymousUserId;
181
+ private lastActivity;
182
+ private storage;
183
+ constructor(storage?: StorageAdapter);
184
+ /** Initialize session from storage, restoring or creating IDs */
185
+ init(): Promise<void>;
186
+ /** Record activity to keep session alive */
187
+ touch(): Promise<void>;
188
+ getSessionId(): string;
189
+ getAnonymousUserId(): string;
190
+ getMetadata(): SessionMetadata;
191
+ }
192
+ /** LocalStorage adapter for web browsers */
193
+ declare class LocalStorageAdapter implements StorageAdapter {
194
+ get(key: string): string | null;
195
+ set(key: string, value: string): void;
196
+ remove(key: string): void;
197
+ }
198
+
199
+ type FlushCallback = (payload: BatchPayload) => void;
200
+ declare class EventBuffer {
201
+ private buffer;
202
+ private flushTimer;
203
+ private config;
204
+ private sessionMetadata;
205
+ private onFlush;
206
+ private active;
207
+ constructor(config: DodgerConfig, sessionMetadata: SessionMetadata, onFlush: FlushCallback);
208
+ start(): void;
209
+ stop(): void;
210
+ add(event: DodgerEvent): void;
211
+ flush(): void;
212
+ updateSessionMetadata(metadata: SessionMetadata): void;
213
+ pending(): number;
214
+ }
215
+
216
+ type ConfigCallback = (config: RemoteConfig) => void;
217
+ declare class StreamConnection {
218
+ private endpoint;
219
+ private apiKey;
220
+ private sessionId;
221
+ private eventSource;
222
+ private onConfig;
223
+ private reconnectAttempts;
224
+ private maxReconnectAttempts;
225
+ private reconnectTimer;
226
+ private active;
227
+ private debug;
228
+ constructor(endpoint: string, apiKey: string, sessionId: string, debug?: boolean);
229
+ connect(onConfig?: ConfigCallback): void;
230
+ private doConnect;
231
+ private scheduleReconnect;
232
+ disconnect(): void;
233
+ }
234
+
235
+ /** Strip PII patterns from a string */
236
+ declare function scrubText(text: string): string;
237
+ /** Scrub PII from an event's data and metadata */
238
+ declare function scrubEvent(event: DodgerEvent): DodgerEvent;
239
+
240
+ declare const SDK_VERSION = "0.1.0";
241
+ declare const DEFAULT_CONFIG: Omit<DodgerConfig, "apiKey">;
242
+ declare function resolveConfig(apiKey: string, options?: DodgerOptions): DodgerConfig;
243
+
244
+ export { type BatchPayload, type ClickEventData, DEFAULT_CONFIG, Dodger, DodgerClient, type DodgerConfig, type DodgerEvent, type DodgerEventType, type DodgerOptions, EventBuffer, FetchTransport, type FormEventData, LocalStorageAdapter, type NavigationEventData, type RemoteConfig, SDK_VERSION, type ScreenViewData, type ScrollEventData, SessionManager, type SessionMetadata, type StorageAdapter, StreamConnection, type StreamEvent, type StreamEventType, type TouchEventData, resolveConfig, scrubEvent, scrubText };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ var g="0.1.0",E={endpoint:"https://sdk.dodger.app/ingest",streamEndpoint:"https://sdk.dodger.app/stream",flushInterval:5e3,maxBatchSize:50,sampleRate:1,streamEnabled:true,debug:false};function u(i,t){return {...E,...t,apiKey:i}}var c="__dodger_session_id",b="__dodger_anon_id",f="__dodger_last_activity";function v(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{let t=Math.random()*16|0;return (i==="x"?t:t&3|8).toString(16)})}var o=class{constructor(t){this.storage=t??null,this.sessionId=v(),this.anonymousUserId=v(),this.lastActivity=Date.now();}async init(){if(this.storage)try{let t=await this.storage.get(b);t?this.anonymousUserId=t:await this.storage.set(b,this.anonymousUserId);let e=await this.storage.get(c),s=await this.storage.get(f);if(e&&s){let n=parseInt(s,10);Date.now()-n<18e5?(this.sessionId=e,this.lastActivity=n):await this.storage.set(c,this.sessionId);}else await this.storage.set(c,this.sessionId);await this.storage.set(f,this.lastActivity.toString());}catch{}}async touch(){let t=Date.now();if(t-this.lastActivity>18e5&&(this.sessionId=v(),this.storage))try{await this.storage.set(c,this.sessionId);}catch{}if(this.lastActivity=t,this.storage)try{await this.storage.set(f,t.toString());}catch{}}getSessionId(){return this.sessionId}getAnonymousUserId(){return this.anonymousUserId}getMetadata(){return {sessionId:this.sessionId,anonymousUserId:this.anonymousUserId}}},m=class{get(t){try{return localStorage.getItem(t)}catch{return null}}set(t,e){try{localStorage.setItem(t,e);}catch{}}remove(t){try{localStorage.removeItem(t);}catch{}}};var D=/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g,x=/(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,R=/\b\d{3}-\d{2}-\d{4}\b/g,C=/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,h="[REDACTED]";function l(i){return i&&i.replace(D,h).replace(x,h).replace(R,h).replace(C,h)}function y(i){let t={};for(let[e,s]of Object.entries(i))typeof s=="string"?t[e]=l(s):s!==null&&typeof s=="object"&&!Array.isArray(s)?t[e]=y(s):Array.isArray(s)?t[e]=s.map(n=>typeof n=="string"?l(n):n!==null&&typeof n=="object"?y(n):n):t[e]=s;return t}function S(i){return {...i,pageUrl:i.pageUrl?l(i.pageUrl):i.pageUrl,data:y(i.data)}}var r=class{constructor(t,e,s){this.buffer=[];this.flushTimer=null;this.active=false;this.config=t,this.sessionMetadata=e,this.onFlush=s;}start(){this.active||(this.active=true,this.flushTimer=setInterval(()=>{this.flush();},this.config.flushInterval));}stop(){this.active=false,this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=null);}add(t){if(!this.active)return;let e=t;if(this.config.beforeSend){try{e=this.config.beforeSend(t);}catch{return}if(!e)return}e=S(e),this.buffer.push(e),this.buffer.length>=this.config.maxBatchSize&&this.flush();}flush(){if(this.buffer.length===0)return;let e={events:this.buffer.splice(0),session:this.sessionMetadata,sdkVersion:g};try{this.onFlush(e);}catch{}}updateSessionMetadata(t){this.sessionMetadata=t;}pending(){return this.buffer.length}};var I=[1e3,2e3,4e3],a=class{constructor(t,e,s=false){this.endpoint=t,this.apiKey=e,this.debug=s;}async send(t){let e=JSON.stringify(t);for(let s=0;s<=3;s++)try{let n=await fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:e,keepalive:!0});if(n.ok||n.status===202)return;if(n.status>=400&&n.status<500){this.debug&&console.warn(`[Dodger] Event batch rejected (${n.status})`);return}s<3&&await this.delay(I[s]);}catch{s<3?await this.delay(I[s]):this.debug&&console.warn("[Dodger] Failed to send event batch after retries");}}sendBeacon(t){if(typeof navigator>"u"||!navigator.sendBeacon)return false;try{let e=new Blob([JSON.stringify(t)],{type:"application/json"}),s=`${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;return navigator.sendBeacon(s,e)}catch{return false}}delay(t){return new Promise(e=>setTimeout(e,t))}};var d=class{constructor(t,e,s,n=false){this.eventSource=null;this.onConfig=null;this.reconnectAttempts=0;this.maxReconnectAttempts=5;this.reconnectTimer=null;this.active=false;this.endpoint=t,this.apiKey=e,this.sessionId=s,this.debug=n;}connect(t){typeof EventSource>"u"||this.active||(this.active=true,this.onConfig=t??null,this.doConnect());}doConnect(){if(!this.active)return;let t=`${this.endpoint}?key=${encodeURIComponent(this.apiKey)}&session=${encodeURIComponent(this.sessionId)}`;try{this.eventSource=new EventSource(t),this.eventSource.addEventListener("connected",e=>{this.reconnectAttempts=0,this.debug&&console.log("[Dodger] Stream connected",e.data);}),this.eventSource.addEventListener("config",e=>{try{let s=JSON.parse(e.data);this.onConfig?.(s);}catch{}}),this.eventSource.addEventListener("ping",()=>{}),this.eventSource.onerror=()=>{this.eventSource?.close(),this.eventSource=null,this.scheduleReconnect();};}catch{this.scheduleReconnect();}}scheduleReconnect(){if(!this.active)return;if(this.reconnectAttempts>=this.maxReconnectAttempts){this.debug&&console.warn("[Dodger] Stream max reconnect attempts reached");return}let t=Math.min(1e3*Math.pow(2,this.reconnectAttempts),3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.doConnect();},t);}disconnect(){this.active=false,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null);}};var p=class{constructor(t,e,s){this.stream=null;this.initialized=false;this.userTraits={};this.config=u(t,e),this.session=new o(s),this.transport=new a(this.config.endpoint,this.config.apiKey,this.config.debug),this.sampled=Math.random()<=this.config.sampleRate,this.buffer=new r(this.config,this.session.getMetadata(),n=>{this.transport.send(n).catch(()=>{});});}async init(){if(!this.initialized)try{await this.session.init(),this.buffer.updateSessionMetadata(this.session.getMetadata()),this.sampled&&(this.buffer.start(),this.config.streamEnabled&&(this.stream=new d(this.config.streamEndpoint,this.config.apiKey,this.session.getSessionId(),this.config.debug),this.stream.connect(t=>{this.applyRemoteConfig(t);}))),this.initialized=!0,this.config.debug&&console.log(`[Dodger] Initialized (sampled: ${this.sampled}, session: ${this.session.getSessionId()})`);}catch{}}track(t,e){if(!(!this.initialized||!this.sampled))try{this.session.touch();let s={type:t,timestamp:Date.now(),sessionId:this.session.getSessionId(),data:e??{}};this.buffer.add(s);}catch{}}identify(t){this.initialized&&t&&(this.userTraits={...this.userTraits,...t});}flush(){this.initialized&&this.buffer.flush();}flushBeacon(){!this.initialized||this.buffer.pending()===0||this.buffer.flush();}destroy(){try{this.buffer.flush(),this.buffer.stop(),this.stream?.disconnect(),this.initialized=!1;}catch{}}getSessionId(){return this.session.getSessionId()}getAnonymousUserId(){return this.session.getAnonymousUserId()}getSessionMetadata(){return this.session.getMetadata()}isActive(){return this.initialized&&this.sampled}getTransport(){return this.transport}getConfig(){return this.config}updateSessionMetadata(t){let s={...this.session.getMetadata(),...t};this.buffer.updateSessionMetadata(s);}applyRemoteConfig(t){t.sampleRate!==void 0&&(this.config.sampleRate=t.sampleRate,this.sampled=Math.random()<=t.sampleRate,this.sampled?this.buffer.start():this.buffer.stop());}},T={_client:null,async init(i,t,e){return this._client&&this._client.destroy(),this._client=new p(i,t,e),await this._client.init(),this._client},track(i,t){this._client?.track(i,t);},identify(i){this._client?.identify(i);},flush(){this._client?.flush();},destroy(){this._client?.destroy(),this._client=null;},get client(){return this._client}};
2
+ export{E as DEFAULT_CONFIG,T as Dodger,p as DodgerClient,r as EventBuffer,a as FetchTransport,m as LocalStorageAdapter,g as SDK_VERSION,o as SessionManager,d as StreamConnection,u as resolveConfig,S as scrubEvent,l as scrubText};//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/session.ts","../src/scrub.ts","../src/events.ts","../src/transport.ts","../src/stream.ts","../src/client.ts"],"names":["SDK_VERSION","DEFAULT_CONFIG","resolveConfig","apiKey","options","SESSION_KEY","ANON_ID_KEY","LAST_ACTIVITY_KEY","uuid","c","r","SessionManager","storage","storedAnonId","storedSessionId","storedLastActivity","lastActivityTime","now","LocalStorageAdapter","key","value","EMAIL_REGEX","PHONE_REGEX","SSN_REGEX","CREDIT_CARD_REGEX","REDACTED","scrubText","text","scrubObject","obj","result","item","scrubEvent","event","EventBuffer","config","sessionMetadata","onFlush","processedEvent","payload","metadata","RETRY_DELAYS","FetchTransport","endpoint","debug","body","attempt","response","blob","url","ms","resolve","StreamConnection","sessionId","onConfig","delay","DodgerClient","remoteConfig","eventType","data","traits","partial","updated","Dodger"],"mappings":"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","file":"index.js","sourcesContent":["import type { DodgerConfig, DodgerOptions } from \"./types.js\";\n\nexport const SDK_VERSION = \"0.1.0\";\n\nexport const DEFAULT_CONFIG: Omit<DodgerConfig, \"apiKey\"> = {\n endpoint: \"https://sdk.dodger.app/ingest\",\n streamEndpoint: \"https://sdk.dodger.app/stream\",\n flushInterval: 5000,\n maxBatchSize: 50,\n sampleRate: 1.0,\n streamEnabled: true,\n debug: false,\n};\n\nexport function resolveConfig(\n apiKey: string,\n options?: DodgerOptions\n): DodgerConfig {\n return {\n ...DEFAULT_CONFIG,\n ...options,\n apiKey,\n };\n}\n","import type { SessionMetadata, StorageAdapter } from \"./types.js\";\n\nconst SESSION_KEY = \"__dodger_session_id\";\nconst ANON_ID_KEY = \"__dodger_anon_id\";\nconst LAST_ACTIVITY_KEY = \"__dodger_last_activity\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n/** Generate a UUID v4 using native crypto */\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older environments\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nexport class SessionManager {\n private sessionId: string;\n private anonymousUserId: string;\n private lastActivity: number;\n private storage: StorageAdapter | null;\n\n constructor(storage?: StorageAdapter) {\n this.storage = storage ?? null;\n this.sessionId = uuid();\n this.anonymousUserId = uuid();\n this.lastActivity = Date.now();\n }\n\n /** Initialize session from storage, restoring or creating IDs */\n async init(): Promise<void> {\n if (!this.storage) return;\n\n try {\n // Restore or create anonymous ID (persists across sessions)\n const storedAnonId = await this.storage.get(ANON_ID_KEY);\n if (storedAnonId) {\n this.anonymousUserId = storedAnonId;\n } else {\n await this.storage.set(ANON_ID_KEY, this.anonymousUserId);\n }\n\n // Check for existing session\n const storedSessionId = await this.storage.get(SESSION_KEY);\n const storedLastActivity = await this.storage.get(LAST_ACTIVITY_KEY);\n\n if (storedSessionId && storedLastActivity) {\n const lastActivityTime = parseInt(storedLastActivity, 10);\n if (Date.now() - lastActivityTime < SESSION_TIMEOUT_MS) {\n // Resume existing session\n this.sessionId = storedSessionId;\n this.lastActivity = lastActivityTime;\n } else {\n // Session expired — start new one\n await this.storage.set(SESSION_KEY, this.sessionId);\n }\n } else {\n await this.storage.set(SESSION_KEY, this.sessionId);\n }\n\n await this.storage.set(\n LAST_ACTIVITY_KEY,\n this.lastActivity.toString()\n );\n } catch {\n // Storage unavailable — continue with in-memory session\n }\n }\n\n /** Record activity to keep session alive */\n async touch(): Promise<void> {\n const now = Date.now();\n\n // Check if session has timed out\n if (now - this.lastActivity > SESSION_TIMEOUT_MS) {\n this.sessionId = uuid();\n if (this.storage) {\n try {\n await this.storage.set(SESSION_KEY, this.sessionId);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n this.lastActivity = now;\n if (this.storage) {\n try {\n await this.storage.set(LAST_ACTIVITY_KEY, now.toString());\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n getSessionId(): string {\n return this.sessionId;\n }\n\n getAnonymousUserId(): string {\n return this.anonymousUserId;\n }\n\n getMetadata(): SessionMetadata {\n return {\n sessionId: this.sessionId,\n anonymousUserId: this.anonymousUserId,\n };\n }\n}\n\n/** LocalStorage adapter for web browsers */\nexport class LocalStorageAdapter implements StorageAdapter {\n get(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n }\n\n set(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // localStorage unavailable or full\n }\n }\n\n remove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // Ignore\n }\n }\n}\n","import type { DodgerEvent } from \"./types.js\";\n\nconst EMAIL_REGEX =\n /[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}/g;\nconst PHONE_REGEX =\n /(\\+?1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g;\nconst SSN_REGEX = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;\nconst CREDIT_CARD_REGEX = /\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b/g;\n\nconst REDACTED = \"[REDACTED]\";\n\n/** Strip PII patterns from a string */\nexport function scrubText(text: string): string {\n if (!text) return text;\n return text\n .replace(EMAIL_REGEX, REDACTED)\n .replace(PHONE_REGEX, REDACTED)\n .replace(SSN_REGEX, REDACTED)\n .replace(CREDIT_CARD_REGEX, REDACTED);\n}\n\n/** Deep-scrub an object's string values */\nfunction scrubObject(obj: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = scrubText(value);\n } else if (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value)\n ) {\n result[key] = scrubObject(value as Record<string, unknown>);\n } else if (Array.isArray(value)) {\n result[key] = value.map((item) =>\n typeof item === \"string\"\n ? scrubText(item)\n : item !== null && typeof item === \"object\"\n ? scrubObject(item as Record<string, unknown>)\n : item\n );\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/** Scrub PII from an event's data and metadata */\nexport function scrubEvent(event: DodgerEvent): DodgerEvent {\n return {\n ...event,\n pageUrl: event.pageUrl ? scrubText(event.pageUrl) : event.pageUrl,\n data: scrubObject(event.data),\n };\n}\n","import type { DodgerEvent, DodgerConfig, BatchPayload, SessionMetadata } from \"./types.js\";\nimport { scrubEvent } from \"./scrub.js\";\nimport { SDK_VERSION } from \"./config.js\";\n\nexport type FlushCallback = (payload: BatchPayload) => void;\n\nexport class EventBuffer {\n private buffer: DodgerEvent[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private config: DodgerConfig;\n private sessionMetadata: SessionMetadata;\n private onFlush: FlushCallback;\n private active = false;\n\n constructor(\n config: DodgerConfig,\n sessionMetadata: SessionMetadata,\n onFlush: FlushCallback\n ) {\n this.config = config;\n this.sessionMetadata = sessionMetadata;\n this.onFlush = onFlush;\n }\n\n start(): void {\n if (this.active) return;\n this.active = true;\n this.flushTimer = setInterval(() => {\n this.flush();\n }, this.config.flushInterval);\n }\n\n stop(): void {\n this.active = false;\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n add(event: DodgerEvent): void {\n if (!this.active) return;\n\n // Apply beforeSend hook\n let processedEvent: DodgerEvent | null = event;\n if (this.config.beforeSend) {\n try {\n processedEvent = this.config.beforeSend(event);\n } catch {\n // beforeSend threw — skip event\n return;\n }\n if (!processedEvent) return;\n }\n\n // Scrub PII\n processedEvent = scrubEvent(processedEvent);\n\n this.buffer.push(processedEvent);\n\n // Flush if buffer is full\n if (this.buffer.length >= this.config.maxBatchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.buffer.length === 0) return;\n\n const events = this.buffer.splice(0);\n const payload: BatchPayload = {\n events,\n session: this.sessionMetadata,\n sdkVersion: SDK_VERSION,\n };\n\n try {\n this.onFlush(payload);\n } catch {\n // Silently drop — never crash host app\n }\n }\n\n updateSessionMetadata(metadata: SessionMetadata): void {\n this.sessionMetadata = metadata;\n }\n\n pending(): number {\n return this.buffer.length;\n }\n}\n","import type { BatchPayload } from \"./types.js\";\n\nconst MAX_RETRIES = 3;\nconst RETRY_DELAYS = [1000, 2000, 4000];\n\nexport class FetchTransport {\n private endpoint: string;\n private apiKey: string;\n private debug: boolean;\n\n constructor(endpoint: string, apiKey: string, debug = false) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.debug = debug;\n }\n\n async send(payload: BatchPayload): Promise<void> {\n const body = JSON.stringify(payload);\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n },\n body,\n keepalive: true,\n });\n\n if (response.ok || response.status === 202) {\n return;\n }\n\n // 4xx errors — don't retry (client error)\n if (response.status >= 400 && response.status < 500) {\n if (this.debug) {\n console.warn(\n `[Dodger] Event batch rejected (${response.status})`\n );\n }\n return;\n }\n\n // 5xx — retry\n if (attempt < MAX_RETRIES) {\n await this.delay(RETRY_DELAYS[attempt]);\n }\n } catch {\n // Network error — retry\n if (attempt < MAX_RETRIES) {\n await this.delay(RETRY_DELAYS[attempt]);\n } else if (this.debug) {\n console.warn(\"[Dodger] Failed to send event batch after retries\");\n }\n }\n }\n }\n\n /** Send via navigator.sendBeacon (for page unload) */\n sendBeacon(payload: BatchPayload): boolean {\n if (typeof navigator === \"undefined\" || !navigator.sendBeacon) {\n return false;\n }\n\n try {\n const blob = new Blob([JSON.stringify(payload)], {\n type: \"application/json\",\n });\n // sendBeacon doesn't support custom headers, so we encode the key in the URL\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}`;\n return navigator.sendBeacon(url, blob);\n } catch {\n return false;\n }\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { RemoteConfig } from \"./types.js\";\n\ntype ConfigCallback = (config: RemoteConfig) => void;\n\nexport class StreamConnection {\n private endpoint: string;\n private apiKey: string;\n private sessionId: string;\n private eventSource: EventSource | null = null;\n private onConfig: ConfigCallback | null = null;\n private reconnectAttempts = 0;\n private maxReconnectAttempts = 5;\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n private active = false;\n private debug: boolean;\n\n constructor(\n endpoint: string,\n apiKey: string,\n sessionId: string,\n debug = false\n ) {\n this.endpoint = endpoint;\n this.apiKey = apiKey;\n this.sessionId = sessionId;\n this.debug = debug;\n }\n\n connect(onConfig?: ConfigCallback): void {\n if (typeof EventSource === \"undefined\") return;\n if (this.active) return;\n\n this.active = true;\n this.onConfig = onConfig ?? null;\n this.doConnect();\n }\n\n private doConnect(): void {\n if (!this.active) return;\n\n const url = `${this.endpoint}?key=${encodeURIComponent(this.apiKey)}&session=${encodeURIComponent(this.sessionId)}`;\n\n try {\n this.eventSource = new EventSource(url);\n\n this.eventSource.addEventListener(\"connected\", (e) => {\n this.reconnectAttempts = 0;\n if (this.debug) {\n console.log(\"[Dodger] Stream connected\", (e as MessageEvent).data);\n }\n });\n\n this.eventSource.addEventListener(\"config\", (e) => {\n try {\n const config: RemoteConfig = JSON.parse((e as MessageEvent).data);\n this.onConfig?.(config);\n } catch {\n // Invalid config data\n }\n });\n\n this.eventSource.addEventListener(\"ping\", () => {\n // Keep-alive acknowledged\n });\n\n this.eventSource.onerror = () => {\n this.eventSource?.close();\n this.eventSource = null;\n this.scheduleReconnect();\n };\n } catch {\n this.scheduleReconnect();\n }\n }\n\n private scheduleReconnect(): void {\n if (!this.active) return;\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n if (this.debug) {\n console.warn(\"[Dodger] Stream max reconnect attempts reached\");\n }\n return;\n }\n\n const delay = Math.min(\n 1000 * Math.pow(2, this.reconnectAttempts),\n 30000\n );\n this.reconnectAttempts++;\n\n this.reconnectTimer = setTimeout(() => {\n this.doConnect();\n }, delay);\n }\n\n disconnect(): void {\n this.active = false;\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = null;\n }\n if (this.eventSource) {\n this.eventSource.close();\n this.eventSource = null;\n }\n }\n}\n","import type {\n DodgerConfig,\n DodgerEvent,\n DodgerEventType,\n DodgerOptions,\n RemoteConfig,\n SessionMetadata,\n StorageAdapter,\n} from \"./types.js\";\nimport { resolveConfig } from \"./config.js\";\nimport { SessionManager } from \"./session.js\";\nimport { EventBuffer } from \"./events.js\";\nimport { FetchTransport } from \"./transport.js\";\nimport { StreamConnection } from \"./stream.js\";\n\nexport class DodgerClient {\n private config: DodgerConfig;\n private session: SessionManager;\n private buffer: EventBuffer;\n private transport: FetchTransport;\n private stream: StreamConnection | null = null;\n private sampled: boolean;\n private initialized = false;\n private userTraits: Record<string, unknown> = {};\n\n constructor(apiKey: string, options?: DodgerOptions, storage?: StorageAdapter) {\n this.config = resolveConfig(apiKey, options);\n this.session = new SessionManager(storage);\n this.transport = new FetchTransport(\n this.config.endpoint,\n this.config.apiKey,\n this.config.debug\n );\n\n // Determine if this session should be sampled\n this.sampled = Math.random() <= this.config.sampleRate;\n\n this.buffer = new EventBuffer(\n this.config,\n this.session.getMetadata(),\n (payload) => {\n this.transport.send(payload).catch(() => {\n // Silently drop\n });\n }\n );\n }\n\n /** Initialize the client — must be called before tracking */\n async init(): Promise<void> {\n if (this.initialized) return;\n\n try {\n await this.session.init();\n this.buffer.updateSessionMetadata(this.session.getMetadata());\n\n if (this.sampled) {\n this.buffer.start();\n\n // Open stream connection\n if (this.config.streamEnabled) {\n this.stream = new StreamConnection(\n this.config.streamEndpoint,\n this.config.apiKey,\n this.session.getSessionId(),\n this.config.debug\n );\n this.stream.connect((remoteConfig) => {\n this.applyRemoteConfig(remoteConfig);\n });\n }\n }\n\n this.initialized = true;\n\n if (this.config.debug) {\n console.log(\n `[Dodger] Initialized (sampled: ${this.sampled}, session: ${this.session.getSessionId()})`\n );\n }\n } catch {\n // Fail silently\n }\n }\n\n /** Track a custom event */\n track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void {\n if (!this.initialized || !this.sampled) return;\n\n try {\n this.session.touch();\n\n const event: DodgerEvent = {\n type: eventType as DodgerEventType,\n timestamp: Date.now(),\n sessionId: this.session.getSessionId(),\n data: data ?? {},\n };\n\n this.buffer.add(event);\n } catch {\n // Never crash host app\n }\n }\n\n /** Identify the user with traits (not PII — use for segmentation) */\n identify(traits?: Record<string, unknown>): void {\n if (!this.initialized) return;\n if (traits) {\n this.userTraits = { ...this.userTraits, ...traits };\n }\n }\n\n /** Manually flush the event buffer */\n flush(): void {\n if (!this.initialized) return;\n this.buffer.flush();\n }\n\n /** Flush using sendBeacon (for page unload) */\n flushBeacon(): void {\n if (!this.initialized || this.buffer.pending() === 0) return;\n this.buffer.flush();\n }\n\n /** Destroy the client and clean up */\n destroy(): void {\n try {\n this.buffer.flush();\n this.buffer.stop();\n this.stream?.disconnect();\n this.initialized = false;\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Get the current session ID */\n getSessionId(): string {\n return this.session.getSessionId();\n }\n\n /** Get the anonymous user ID */\n getAnonymousUserId(): string {\n return this.session.getAnonymousUserId();\n }\n\n /** Get the current session metadata */\n getSessionMetadata(): SessionMetadata {\n return this.session.getMetadata();\n }\n\n /** Check if the client is initialized and sampling */\n isActive(): boolean {\n return this.initialized && this.sampled;\n }\n\n /** Get the transport for direct sendBeacon calls */\n getTransport(): FetchTransport {\n return this.transport;\n }\n\n /** Get the current config */\n getConfig(): DodgerConfig {\n return this.config;\n }\n\n /** Update session metadata (called by platform adapters to add viewport, device info, etc.) */\n updateSessionMetadata(partial: Partial<SessionMetadata>): void {\n const current = this.session.getMetadata();\n const updated: SessionMetadata = { ...current, ...partial };\n this.buffer.updateSessionMetadata(updated);\n }\n\n private applyRemoteConfig(config: RemoteConfig): void {\n if (config.sampleRate !== undefined) {\n this.config.sampleRate = config.sampleRate;\n // Re-evaluate sampling\n this.sampled = Math.random() <= config.sampleRate;\n if (!this.sampled) {\n this.buffer.stop();\n } else {\n this.buffer.start();\n }\n }\n }\n}\n\n/**\n * Singleton Dodger instance for the simple two-line integration:\n *\n * ```\n * import { Dodger } from '@dodger/sdk-core';\n * Dodger.init('pk_...');\n * ```\n */\nexport const Dodger = {\n _client: null as DodgerClient | null,\n\n async init(\n apiKey: string,\n options?: DodgerOptions,\n storage?: StorageAdapter\n ): Promise<DodgerClient> {\n if (this._client) {\n this._client.destroy();\n }\n this._client = new DodgerClient(apiKey, options, storage);\n await this._client.init();\n return this._client;\n },\n\n track(eventType: DodgerEventType | string, data?: Record<string, unknown>): void {\n this._client?.track(eventType, data);\n },\n\n identify(traits?: Record<string, unknown>): void {\n this._client?.identify(traits);\n },\n\n flush(): void {\n this._client?.flush();\n },\n\n destroy(): void {\n this._client?.destroy();\n this._client = null;\n },\n\n get client(): DodgerClient | null {\n return this._client;\n },\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@dodger/sdk-core",
3
+ "version": "0.1.0",
4
+ "description": "Core event tracking, batching, and transport for Dodger SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": ["dist"],
22
+ "sideEffects": false,
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "dev": "tsup --watch",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "devDependencies": {
29
+ "tsup": "^8.4.0",
30
+ "typescript": "^5.8.0"
31
+ },
32
+ "license": "MIT"
33
+ }