@litemetrics/tracker 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,15 @@
1
+ import { TrackerConfig } from '@litemetrics/core';
2
+ export { TrackerConfig } from '@litemetrics/core';
3
+
4
+ interface LitemetricsInstance {
5
+ track(name: string, properties?: Record<string, unknown>): void;
6
+ identify(userId: string, traits?: Record<string, unknown>): void;
7
+ page(url?: string, title?: string): void;
8
+ reset(): void;
9
+ opt_out(): void;
10
+ opt_in(): void;
11
+ destroy(): void;
12
+ }
13
+ declare function createTracker(config: TrackerConfig): LitemetricsInstance;
14
+
15
+ export { type LitemetricsInstance, createTracker };
@@ -0,0 +1,15 @@
1
+ import { TrackerConfig } from '@litemetrics/core';
2
+ export { TrackerConfig } from '@litemetrics/core';
3
+
4
+ interface LitemetricsInstance {
5
+ track(name: string, properties?: Record<string, unknown>): void;
6
+ identify(userId: string, traits?: Record<string, unknown>): void;
7
+ page(url?: string, title?: string): void;
8
+ reset(): void;
9
+ opt_out(): void;
10
+ opt_in(): void;
11
+ destroy(): void;
12
+ }
13
+ declare function createTracker(config: TrackerConfig): LitemetricsInstance;
14
+
15
+ export { type LitemetricsInstance, createTracker };
@@ -0,0 +1,511 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createTracker: () => createTracker
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/tracker.ts
28
+ var import_core3 = require("@litemetrics/core");
29
+
30
+ // src/session.ts
31
+ var import_core = require("@litemetrics/core");
32
+
33
+ // src/utils.ts
34
+ function generateId() {
35
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
36
+ return crypto.randomUUID();
37
+ }
38
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
39
+ const r = Math.random() * 16 | 0;
40
+ const v = c === "x" ? r : r & 3 | 8;
41
+ return v.toString(16);
42
+ });
43
+ }
44
+ async function hashString(input) {
45
+ if (typeof crypto !== "undefined" && crypto.subtle) {
46
+ const encoder = new TextEncoder();
47
+ const data = encoder.encode(input);
48
+ const hash = await crypto.subtle.digest("SHA-256", data);
49
+ const array = Array.from(new Uint8Array(hash));
50
+ return array.map((b) => b.toString(16).padStart(2, "0")).join("");
51
+ }
52
+ let h = 0;
53
+ for (let i = 0; i < input.length; i++) {
54
+ h = (h << 5) - h + input.charCodeAt(i) | 0;
55
+ }
56
+ return Math.abs(h).toString(16).padStart(8, "0");
57
+ }
58
+ function parseUTM() {
59
+ if (typeof location === "undefined") return void 0;
60
+ const params = new URLSearchParams(location.search);
61
+ const utm = {};
62
+ let hasUtm = false;
63
+ for (const [key, field] of [
64
+ ["utm_source", "source"],
65
+ ["utm_medium", "medium"],
66
+ ["utm_campaign", "campaign"],
67
+ ["utm_term", "term"],
68
+ ["utm_content", "content"]
69
+ ]) {
70
+ const val = params.get(key);
71
+ if (val) {
72
+ utm[field] = val;
73
+ hasUtm = true;
74
+ }
75
+ }
76
+ return hasUtm ? utm : void 0;
77
+ }
78
+ function getDayString() {
79
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
80
+ }
81
+ function now() {
82
+ return Date.now();
83
+ }
84
+
85
+ // src/session.ts
86
+ function storageGet(key) {
87
+ try {
88
+ return localStorage.getItem(key);
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+ function storageSet(key, value) {
94
+ try {
95
+ localStorage.setItem(key, value);
96
+ } catch {
97
+ }
98
+ }
99
+ function storageRemove(key) {
100
+ try {
101
+ localStorage.removeItem(key);
102
+ } catch {
103
+ }
104
+ }
105
+ var SessionManager = class {
106
+ _sessionId;
107
+ _visitorId = null;
108
+ _userId = null;
109
+ _hostname;
110
+ constructor(hostname) {
111
+ this._hostname = hostname || (typeof location !== "undefined" ? location.hostname : "unknown");
112
+ this._sessionId = this._getOrCreateSession();
113
+ this._userId = storageGet(import_core.STORAGE_KEY_USER);
114
+ }
115
+ get sessionId() {
116
+ return this._sessionId;
117
+ }
118
+ get userId() {
119
+ return this._userId;
120
+ }
121
+ async getVisitorId() {
122
+ if (this._visitorId) return this._visitorId;
123
+ const cached = storageGet(import_core.STORAGE_KEY_VISITOR);
124
+ const today = getDayString();
125
+ if (cached) {
126
+ const [id2, date] = cached.split("|");
127
+ if (date === today) {
128
+ this._visitorId = id2;
129
+ return id2;
130
+ }
131
+ }
132
+ const id = await this._generateVisitorId();
133
+ this._visitorId = id;
134
+ storageSet(import_core.STORAGE_KEY_VISITOR, `${id}|${today}`);
135
+ return id;
136
+ }
137
+ touch() {
138
+ storageSet(import_core.STORAGE_KEY_LAST_ACTIVE, now().toString());
139
+ }
140
+ identify(userId) {
141
+ this._userId = userId;
142
+ storageSet(import_core.STORAGE_KEY_USER, userId);
143
+ }
144
+ reset() {
145
+ this._sessionId = generateId();
146
+ this._visitorId = null;
147
+ this._userId = null;
148
+ storageRemove(import_core.STORAGE_KEY_SESSION);
149
+ storageRemove(import_core.STORAGE_KEY_VISITOR);
150
+ storageRemove(import_core.STORAGE_KEY_USER);
151
+ storageRemove(import_core.STORAGE_KEY_LAST_ACTIVE);
152
+ }
153
+ _getOrCreateSession() {
154
+ const stored = storageGet(import_core.STORAGE_KEY_SESSION);
155
+ const lastActive = storageGet(import_core.STORAGE_KEY_LAST_ACTIVE);
156
+ if (stored && lastActive) {
157
+ const elapsed = now() - parseInt(lastActive, 10);
158
+ if (elapsed < import_core.SESSION_TIMEOUT) {
159
+ this.touch();
160
+ return stored;
161
+ }
162
+ }
163
+ const id = generateId();
164
+ storageSet(import_core.STORAGE_KEY_SESSION, id);
165
+ this.touch();
166
+ return id;
167
+ }
168
+ async _generateVisitorId() {
169
+ const components = [
170
+ this._hostname,
171
+ getDayString(),
172
+ typeof navigator !== "undefined" ? navigator.userAgent : "",
173
+ typeof navigator !== "undefined" ? navigator.language : "",
174
+ typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "",
175
+ typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : ""
176
+ ];
177
+ const raw = components.join("|");
178
+ const hash = await hashString(raw);
179
+ return hash.slice(0, 16);
180
+ }
181
+ };
182
+
183
+ // src/transport.ts
184
+ var import_core2 = require("@litemetrics/core");
185
+ var Transport = class {
186
+ queue = [];
187
+ timer = null;
188
+ endpoint;
189
+ batchSize;
190
+ flushInterval;
191
+ debug;
192
+ constructor(options) {
193
+ this.endpoint = options.endpoint;
194
+ this.batchSize = options.batchSize ?? import_core2.DEFAULT_BATCH_SIZE;
195
+ this.flushInterval = options.flushInterval ?? import_core2.DEFAULT_FLUSH_INTERVAL;
196
+ this.debug = options.debug ?? false;
197
+ this._startTimer();
198
+ this._setupUnload();
199
+ }
200
+ send(event) {
201
+ this.queue.push(event);
202
+ if (this.queue.length >= this.batchSize) {
203
+ this.flush();
204
+ }
205
+ }
206
+ flush() {
207
+ if (this.queue.length === 0) return;
208
+ const events = this.queue.splice(0);
209
+ this._dispatch(events);
210
+ }
211
+ destroy() {
212
+ if (this.timer) {
213
+ clearInterval(this.timer);
214
+ this.timer = null;
215
+ }
216
+ this.flush();
217
+ }
218
+ _dispatch(events) {
219
+ const payload = { events };
220
+ const body = JSON.stringify(payload);
221
+ if (this.debug) {
222
+ console.log("[litemetrics] sending", events.length, "events", events);
223
+ }
224
+ if (typeof fetch !== "undefined") {
225
+ fetch(this.endpoint, {
226
+ method: "POST",
227
+ headers: { "Content-Type": "application/json" },
228
+ body,
229
+ keepalive: true
230
+ }).catch(() => {
231
+ this._beacon(body);
232
+ });
233
+ } else {
234
+ this._beacon(body);
235
+ }
236
+ }
237
+ _beacon(body) {
238
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
239
+ const blob = new Blob([body], { type: "application/json" });
240
+ navigator.sendBeacon(this.endpoint, blob);
241
+ }
242
+ }
243
+ _startTimer() {
244
+ if (typeof setInterval !== "undefined") {
245
+ this.timer = setInterval(() => this.flush(), this.flushInterval);
246
+ }
247
+ }
248
+ _setupUnload() {
249
+ if (typeof document === "undefined") return;
250
+ const onUnload = () => {
251
+ if (this.queue.length === 0) return;
252
+ const payload = { events: this.queue.splice(0) };
253
+ const body = JSON.stringify(payload);
254
+ this._beacon(body);
255
+ };
256
+ document.addEventListener("visibilitychange", () => {
257
+ if (document.visibilityState === "hidden") {
258
+ onUnload();
259
+ }
260
+ });
261
+ if (typeof addEventListener !== "undefined") {
262
+ addEventListener("pagehide", onUnload);
263
+ }
264
+ }
265
+ };
266
+
267
+ // src/auto.ts
268
+ var AutoTracker = class {
269
+ onPage;
270
+ lastUrl = "";
271
+ originalPushState = null;
272
+ originalReplaceState = null;
273
+ constructor(onPage) {
274
+ this.onPage = onPage;
275
+ }
276
+ startSPA() {
277
+ if (typeof history === "undefined" || typeof addEventListener === "undefined") return;
278
+ this.lastUrl = location.href;
279
+ this.originalPushState = history.pushState.bind(history);
280
+ this.originalReplaceState = history.replaceState.bind(history);
281
+ history.pushState = (...args) => {
282
+ this.originalPushState(...args);
283
+ this._onNavigation();
284
+ };
285
+ history.replaceState = (...args) => {
286
+ this.originalReplaceState(...args);
287
+ this._onNavigation();
288
+ };
289
+ addEventListener("popstate", () => this._onNavigation());
290
+ }
291
+ stop() {
292
+ if (this.originalPushState) {
293
+ history.pushState = this.originalPushState;
294
+ }
295
+ if (this.originalReplaceState) {
296
+ history.replaceState = this.originalReplaceState;
297
+ }
298
+ }
299
+ _onNavigation() {
300
+ setTimeout(() => {
301
+ const current = location.href;
302
+ if (current !== this.lastUrl) {
303
+ const referrer = this.lastUrl;
304
+ this.lastUrl = current;
305
+ this.onPage(current, referrer);
306
+ }
307
+ }, 0);
308
+ }
309
+ };
310
+
311
+ // src/attributes.ts
312
+ var ATTR_EVENT = "data-litemetrics-event";
313
+ var ATTR_PREFIX = "data-litemetrics-event-";
314
+ function initAttributeTracking(instance) {
315
+ function handleClick(e) {
316
+ const target = e.target;
317
+ if (!target) return;
318
+ let el = target;
319
+ while (el) {
320
+ const eventName = el.getAttribute(ATTR_EVENT);
321
+ if (eventName) {
322
+ const properties = {};
323
+ const attrs = el.attributes;
324
+ for (let i = 0; i < attrs.length; i++) {
325
+ const attr = attrs[i];
326
+ if (attr.name.startsWith(ATTR_PREFIX)) {
327
+ const key = attr.name.slice(ATTR_PREFIX.length);
328
+ properties[key] = attr.value;
329
+ }
330
+ }
331
+ instance.track(
332
+ eventName,
333
+ Object.keys(properties).length > 0 ? properties : void 0
334
+ );
335
+ return;
336
+ }
337
+ el = el.parentElement;
338
+ }
339
+ }
340
+ document.addEventListener("click", handleClick, true);
341
+ return () => {
342
+ document.removeEventListener("click", handleClick, true);
343
+ };
344
+ }
345
+
346
+ // src/tracker.ts
347
+ function createTracker(config) {
348
+ const {
349
+ siteId,
350
+ endpoint,
351
+ autoTrack = true,
352
+ autoSpa = true,
353
+ debug = false,
354
+ respectDnt = true
355
+ } = config;
356
+ if (respectDnt && typeof navigator !== "undefined" && navigator.doNotTrack === "1") {
357
+ return createNoopTracker();
358
+ }
359
+ try {
360
+ if (localStorage.getItem(import_core3.STORAGE_KEY_OPTOUT) === "1") {
361
+ return createNoopTracker();
362
+ }
363
+ } catch {
364
+ }
365
+ const session = new SessionManager();
366
+ const transport = new Transport({
367
+ endpoint,
368
+ batchSize: config.batchSize,
369
+ flushInterval: config.flushInterval,
370
+ debug
371
+ });
372
+ let autoTracker = null;
373
+ let cleanupAttributes = null;
374
+ let optedOut = false;
375
+ function getContext() {
376
+ const ctx = {};
377
+ if (typeof screen !== "undefined") {
378
+ ctx.screen = { width: screen.width, height: screen.height };
379
+ }
380
+ if (typeof navigator !== "undefined") {
381
+ ctx.language = navigator.language;
382
+ const conn = navigator.connection;
383
+ if (conn) {
384
+ ctx.connection = {
385
+ type: conn.type,
386
+ downlink: conn.downlink,
387
+ effectiveType: conn.effectiveType,
388
+ rtt: conn.rtt
389
+ };
390
+ }
391
+ }
392
+ if (typeof Intl !== "undefined") {
393
+ ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
394
+ }
395
+ const utm = parseUTM();
396
+ if (utm) ctx.utm = utm;
397
+ return ctx;
398
+ }
399
+ async function sendEvent(event) {
400
+ if (optedOut) return;
401
+ session.touch();
402
+ transport.send(event);
403
+ }
404
+ async function trackPage(url, title, referrer) {
405
+ const visitorId = await session.getVisitorId();
406
+ const event = {
407
+ type: "pageview",
408
+ siteId,
409
+ timestamp: now(),
410
+ sessionId: session.sessionId,
411
+ visitorId,
412
+ url: url || (typeof location !== "undefined" ? location.href : ""),
413
+ referrer: referrer || (typeof document !== "undefined" ? document.referrer : void 0),
414
+ title: title || (typeof document !== "undefined" ? document.title : void 0),
415
+ ...getContext()
416
+ };
417
+ sendEvent(event);
418
+ }
419
+ if (autoTrack) {
420
+ trackPage();
421
+ }
422
+ if (autoSpa) {
423
+ autoTracker = new AutoTracker((url, ref) => trackPage(url, void 0, ref));
424
+ autoTracker.startSPA();
425
+ }
426
+ const instance = {
427
+ track(name, properties) {
428
+ session.getVisitorId().then((visitorId) => {
429
+ const event = {
430
+ type: "event",
431
+ siteId,
432
+ timestamp: now(),
433
+ sessionId: session.sessionId,
434
+ visitorId,
435
+ name,
436
+ properties,
437
+ ...getContext()
438
+ };
439
+ sendEvent(event);
440
+ });
441
+ },
442
+ identify(userId, traits) {
443
+ session.identify(userId);
444
+ session.getVisitorId().then((visitorId) => {
445
+ const event = {
446
+ type: "identify",
447
+ siteId,
448
+ timestamp: now(),
449
+ sessionId: session.sessionId,
450
+ visitorId,
451
+ userId,
452
+ traits,
453
+ ...getContext()
454
+ };
455
+ sendEvent(event);
456
+ });
457
+ },
458
+ page(url, title) {
459
+ trackPage(url, title);
460
+ },
461
+ reset() {
462
+ session.reset();
463
+ },
464
+ opt_out() {
465
+ optedOut = true;
466
+ try {
467
+ localStorage.setItem(import_core3.STORAGE_KEY_OPTOUT, "1");
468
+ } catch {
469
+ }
470
+ },
471
+ opt_in() {
472
+ optedOut = false;
473
+ try {
474
+ localStorage.removeItem(import_core3.STORAGE_KEY_OPTOUT);
475
+ } catch {
476
+ }
477
+ },
478
+ destroy() {
479
+ cleanupAttributes?.();
480
+ autoTracker?.stop();
481
+ transport.destroy();
482
+ }
483
+ };
484
+ if (autoTrack && typeof document !== "undefined") {
485
+ cleanupAttributes = initAttributeTracking(instance);
486
+ }
487
+ return instance;
488
+ }
489
+ function createNoopTracker() {
490
+ return {
491
+ track() {
492
+ },
493
+ identify() {
494
+ },
495
+ page() {
496
+ },
497
+ reset() {
498
+ },
499
+ opt_out() {
500
+ },
501
+ opt_in() {
502
+ },
503
+ destroy() {
504
+ }
505
+ };
506
+ }
507
+ // Annotate the CommonJS export names for ESM import in node:
508
+ 0 && (module.exports = {
509
+ createTracker
510
+ });
511
+ //# sourceMappingURL=litemetrics.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/tracker.ts","../src/session.ts","../src/utils.ts","../src/transport.ts","../src/auto.ts","../src/attributes.ts"],"sourcesContent":["export { createTracker } from './tracker';\nexport type { LitemetricsInstance } from './tracker';\nexport type { TrackerConfig } from '@litemetrics/core';\n","import type {\n TrackerConfig,\n ClientEvent,\n PageviewEvent,\n CustomEvent,\n IdentifyEvent,\n ClientContext,\n} from '@litemetrics/core';\nimport { STORAGE_KEY_OPTOUT } from '@litemetrics/core';\nimport { SessionManager } from './session';\nimport { Transport } from './transport';\nimport { AutoTracker } from './auto';\nimport { parseUTM, now } from './utils';\nimport { initAttributeTracking } from './attributes';\n\nexport interface LitemetricsInstance {\n track(name: string, properties?: Record<string, unknown>): void;\n identify(userId: string, traits?: Record<string, unknown>): void;\n page(url?: string, title?: string): void;\n reset(): void;\n opt_out(): void;\n opt_in(): void;\n destroy(): void;\n}\n\nexport function createTracker(config: TrackerConfig): LitemetricsInstance {\n const {\n siteId,\n endpoint,\n autoTrack = true,\n autoSpa = true,\n debug = false,\n respectDnt = true,\n } = config;\n\n // Check Do Not Track\n if (respectDnt && typeof navigator !== 'undefined' && navigator.doNotTrack === '1') {\n return createNoopTracker();\n }\n\n // Check opt-out\n try {\n if (localStorage.getItem(STORAGE_KEY_OPTOUT) === '1') {\n return createNoopTracker();\n }\n } catch {\n // ignore\n }\n\n const session = new SessionManager();\n const transport = new Transport({\n endpoint,\n batchSize: config.batchSize,\n flushInterval: config.flushInterval,\n debug,\n });\n let autoTracker: AutoTracker | null = null;\n let cleanupAttributes: (() => void) | null = null;\n let optedOut = false;\n\n function getContext(): ClientContext {\n const ctx: ClientContext = {};\n if (typeof screen !== 'undefined') {\n ctx.screen = { width: screen.width, height: screen.height };\n }\n if (typeof navigator !== 'undefined') {\n ctx.language = navigator.language;\n // Network Information API\n const conn = (navigator as any).connection;\n if (conn) {\n ctx.connection = {\n type: conn.type,\n downlink: conn.downlink,\n effectiveType: conn.effectiveType,\n rtt: conn.rtt,\n };\n }\n }\n if (typeof Intl !== 'undefined') {\n ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n }\n const utm = parseUTM();\n if (utm) ctx.utm = utm;\n return ctx;\n }\n\n async function sendEvent(event: ClientEvent): Promise<void> {\n if (optedOut) return;\n session.touch();\n transport.send(event);\n }\n\n async function trackPage(url?: string, title?: string, referrer?: string): Promise<void> {\n const visitorId = await session.getVisitorId();\n const event: PageviewEvent & ClientContext = {\n type: 'pageview',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n url: url || (typeof location !== 'undefined' ? location.href : ''),\n referrer: referrer || (typeof document !== 'undefined' ? document.referrer : undefined),\n title: title || (typeof document !== 'undefined' ? document.title : undefined),\n ...getContext(),\n };\n sendEvent(event);\n }\n\n // Auto-track initial page view\n if (autoTrack) {\n trackPage();\n }\n\n // Auto-track SPA navigation\n if (autoSpa) {\n autoTracker = new AutoTracker((url, ref) => trackPage(url, undefined, ref));\n autoTracker.startSPA();\n }\n\n // We need a reference to the instance for attribute tracking\n // so we create it first then init attributes\n const instance: LitemetricsInstance = {\n track(name: string, properties?: Record<string, unknown>): void {\n session.getVisitorId().then((visitorId) => {\n const event: CustomEvent & ClientContext = {\n type: 'event',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n name,\n properties,\n ...getContext(),\n };\n sendEvent(event);\n });\n },\n\n identify(userId: string, traits?: Record<string, unknown>): void {\n session.identify(userId);\n session.getVisitorId().then((visitorId) => {\n const event: IdentifyEvent & ClientContext = {\n type: 'identify',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n userId,\n traits,\n ...getContext(),\n };\n sendEvent(event);\n });\n },\n\n page(url?: string, title?: string): void {\n trackPage(url, title);\n },\n\n reset(): void {\n session.reset();\n },\n\n opt_out(): void {\n optedOut = true;\n try {\n localStorage.setItem(STORAGE_KEY_OPTOUT, '1');\n } catch {\n // ignore\n }\n },\n\n opt_in(): void {\n optedOut = false;\n try {\n localStorage.removeItem(STORAGE_KEY_OPTOUT);\n } catch {\n // ignore\n }\n },\n\n destroy(): void {\n cleanupAttributes?.();\n autoTracker?.stop();\n transport.destroy();\n },\n };\n\n // Initialize data-attribute event tracking\n if (autoTrack && typeof document !== 'undefined') {\n cleanupAttributes = initAttributeTracking(instance);\n }\n\n return instance;\n}\n\nfunction createNoopTracker(): LitemetricsInstance {\n return {\n track() {},\n identify() {},\n page() {},\n reset() {},\n opt_out() {},\n opt_in() {},\n destroy() {},\n };\n}\n","import {\n SESSION_TIMEOUT,\n STORAGE_KEY_SESSION,\n STORAGE_KEY_VISITOR,\n STORAGE_KEY_LAST_ACTIVE,\n STORAGE_KEY_USER,\n} from '@litemetrics/core';\nimport { generateId, hashString, getDayString, now } from './utils';\n\nfunction storageGet(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction storageSet(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // localStorage unavailable (private browsing, etc.)\n }\n}\n\nfunction storageRemove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // noop\n }\n}\n\nexport class SessionManager {\n private _sessionId: string;\n private _visitorId: string | null = null;\n private _userId: string | null = null;\n private _hostname: string;\n\n constructor(hostname?: string) {\n this._hostname = hostname || (typeof location !== 'undefined' ? location.hostname : 'unknown');\n this._sessionId = this._getOrCreateSession();\n this._userId = storageGet(STORAGE_KEY_USER);\n }\n\n get sessionId(): string {\n return this._sessionId;\n }\n\n get userId(): string | null {\n return this._userId;\n }\n\n async getVisitorId(): Promise<string> {\n if (this._visitorId) return this._visitorId;\n\n const cached = storageGet(STORAGE_KEY_VISITOR);\n const today = getDayString();\n\n // Visitor ID rotates daily for privacy\n if (cached) {\n const [id, date] = cached.split('|');\n if (date === today) {\n this._visitorId = id;\n return id;\n }\n }\n\n const id = await this._generateVisitorId();\n this._visitorId = id;\n storageSet(STORAGE_KEY_VISITOR, `${id}|${today}`);\n return id;\n }\n\n touch(): void {\n storageSet(STORAGE_KEY_LAST_ACTIVE, now().toString());\n }\n\n identify(userId: string): void {\n this._userId = userId;\n storageSet(STORAGE_KEY_USER, userId);\n }\n\n reset(): void {\n this._sessionId = generateId();\n this._visitorId = null;\n this._userId = null;\n storageRemove(STORAGE_KEY_SESSION);\n storageRemove(STORAGE_KEY_VISITOR);\n storageRemove(STORAGE_KEY_USER);\n storageRemove(STORAGE_KEY_LAST_ACTIVE);\n }\n\n private _getOrCreateSession(): string {\n const stored = storageGet(STORAGE_KEY_SESSION);\n const lastActive = storageGet(STORAGE_KEY_LAST_ACTIVE);\n\n if (stored && lastActive) {\n const elapsed = now() - parseInt(lastActive, 10);\n if (elapsed < SESSION_TIMEOUT) {\n this.touch();\n return stored;\n }\n }\n\n const id = generateId();\n storageSet(STORAGE_KEY_SESSION, id);\n this.touch();\n return id;\n }\n\n private async _generateVisitorId(): Promise<string> {\n const components = [\n this._hostname,\n getDayString(),\n typeof navigator !== 'undefined' ? navigator.userAgent : '',\n typeof navigator !== 'undefined' ? navigator.language : '',\n typeof Intl !== 'undefined'\n ? Intl.DateTimeFormat().resolvedOptions().timeZone\n : '',\n typeof screen !== 'undefined' ? `${screen.width}x${screen.height}` : '',\n ];\n\n const raw = components.join('|');\n const hash = await hashString(raw);\n return hash.slice(0, 16);\n }\n}\n","import type { UTMParams } from '@litemetrics/core';\n\nexport function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\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 async function hashString(input: string): Promise<string> {\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hash = await crypto.subtle.digest('SHA-256', data);\n const array = Array.from(new Uint8Array(hash));\n return array.map((b) => b.toString(16).padStart(2, '0')).join('');\n }\n // Simple fallback hash\n let h = 0;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) - h + input.charCodeAt(i)) | 0;\n }\n return Math.abs(h).toString(16).padStart(8, '0');\n}\n\nexport function parseUTM(): UTMParams | undefined {\n if (typeof location === 'undefined') return undefined;\n const params = new URLSearchParams(location.search);\n const utm: UTMParams = {};\n let hasUtm = false;\n\n for (const [key, field] of [\n ['utm_source', 'source'],\n ['utm_medium', 'medium'],\n ['utm_campaign', 'campaign'],\n ['utm_term', 'term'],\n ['utm_content', 'content'],\n ] as const) {\n const val = params.get(key);\n if (val) {\n (utm as Record<string, string>)[field] = val;\n hasUtm = true;\n }\n }\n\n return hasUtm ? utm : undefined;\n}\n\nexport function getDayString(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nexport function now(): number {\n return Date.now();\n}\n","import type { ClientEvent, CollectPayload } from '@litemetrics/core';\nimport { DEFAULT_BATCH_SIZE, DEFAULT_FLUSH_INTERVAL } from '@litemetrics/core';\n\nexport interface TransportOptions {\n endpoint: string;\n batchSize?: number;\n flushInterval?: number;\n debug?: boolean;\n}\n\nexport class Transport {\n private queue: ClientEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private endpoint: string;\n private batchSize: number;\n private flushInterval: number;\n private debug: boolean;\n\n constructor(options: TransportOptions) {\n this.endpoint = options.endpoint;\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.debug = options.debug ?? false;\n\n this._startTimer();\n this._setupUnload();\n }\n\n send(event: ClientEvent): void {\n this.queue.push(event);\n if (this.queue.length >= this.batchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.queue.length === 0) return;\n const events = this.queue.splice(0);\n this._dispatch(events);\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush();\n }\n\n private _dispatch(events: ClientEvent[]): void {\n const payload: CollectPayload = { events };\n const body = JSON.stringify(payload);\n\n if (this.debug) {\n console.log('[litemetrics] sending', events.length, 'events', events);\n }\n\n // Try fetch first, fall back to sendBeacon\n if (typeof fetch !== 'undefined') {\n fetch(this.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n // Retry once with sendBeacon\n this._beacon(body);\n });\n } else {\n this._beacon(body);\n }\n }\n\n private _beacon(body: string): void {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.endpoint, blob);\n }\n }\n\n private _startTimer(): void {\n if (typeof setInterval !== 'undefined') {\n this.timer = setInterval(() => this.flush(), this.flushInterval);\n }\n }\n\n private _setupUnload(): void {\n if (typeof document === 'undefined') return;\n\n const onUnload = () => {\n if (this.queue.length === 0) return;\n const payload: CollectPayload = { events: this.queue.splice(0) };\n const body = JSON.stringify(payload);\n // sendBeacon is more reliable during page unload\n this._beacon(body);\n };\n\n // visibilitychange + pagehide is the most reliable combo\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n onUnload();\n }\n });\n\n if (typeof addEventListener !== 'undefined') {\n addEventListener('pagehide', onUnload);\n }\n }\n}\n","export type PageCallback = (url: string, referrer?: string) => void;\n\nexport class AutoTracker {\n private onPage: PageCallback;\n private lastUrl: string = '';\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n\n constructor(onPage: PageCallback) {\n this.onPage = onPage;\n }\n\n startSPA(): void {\n if (typeof history === 'undefined' || typeof addEventListener === 'undefined') return;\n\n this.lastUrl = location.href;\n\n // Monkey-patch pushState and replaceState\n this.originalPushState = history.pushState.bind(history);\n this.originalReplaceState = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n this.originalPushState!(...args);\n this._onNavigation();\n };\n\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n this.originalReplaceState!(...args);\n this._onNavigation();\n };\n\n addEventListener('popstate', () => this._onNavigation());\n }\n\n stop(): void {\n if (this.originalPushState) {\n history.pushState = this.originalPushState;\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState;\n }\n }\n\n private _onNavigation(): void {\n // Small delay to let the URL update\n setTimeout(() => {\n const current = location.href;\n if (current !== this.lastUrl) {\n const referrer = this.lastUrl;\n this.lastUrl = current;\n this.onPage(current, referrer);\n }\n }, 0);\n }\n}\n","import type { LitemetricsInstance } from './tracker';\n\nconst ATTR_EVENT = 'data-litemetrics-event';\nconst ATTR_PREFIX = 'data-litemetrics-event-';\n\n/**\n * Initialize data-attribute event tracking.\n * Clicks on elements with `data-litemetrics-event=\"EventName\"` will be auto-tracked.\n * Additional properties via `data-litemetrics-event-*` attributes.\n *\n * Example:\n * <button data-litemetrics-event=\"Signup\" data-litemetrics-event-plan=\"pro\">\n * → tracks event \"Signup\" with { plan: \"pro\" }\n */\nexport function initAttributeTracking(instance: LitemetricsInstance): () => void {\n function handleClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n\n // Walk up the DOM to find an element with data-litemetrics-event\n let el: HTMLElement | null = target;\n while (el) {\n const eventName = el.getAttribute(ATTR_EVENT);\n if (eventName) {\n // Collect data-litemetrics-event-* properties\n const properties: Record<string, string> = {};\n const attrs = el.attributes;\n for (let i = 0; i < attrs.length; i++) {\n const attr = attrs[i];\n if (attr.name.startsWith(ATTR_PREFIX)) {\n const key = attr.name.slice(ATTR_PREFIX.length);\n properties[key] = attr.value;\n }\n }\n\n instance.track(\n eventName,\n Object.keys(properties).length > 0 ? properties : undefined\n );\n return;\n }\n el = el.parentElement;\n }\n }\n\n document.addEventListener('click', handleClick, true);\n\n return () => {\n document.removeEventListener('click', handleClick, true);\n };\n}\n"],"mappings":"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","names":["import_core","id","import_core"]}
@@ -0,0 +1 @@
1
+ "use strict";var Litemetrics=(()=>{var R=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var B=Object.prototype.hasOwnProperty;var Z=(n,t)=>{for(var e in t)R(n,e,{get:t[e],enumerable:!0})},$=(n,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of j(t))!B.call(n,r)&&r!==e&&R(n,r,{get:()=>t[r],enumerable:!(i=H(t,r))||i.enumerable});return n};var J=n=>$(R({},"__esModule",{value:!0}),n);var W={};Z(W,{createTracker:()=>q});var N=10,D=5e3,V=1800*1e3,v="__litemetrics_sid",m="__litemetrics_vid";var y="__litemetrics_optout",_="__litemetrics_uid",S="__litemetrics_la";function U(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{let t=Math.random()*16|0;return(n==="x"?t:t&3|8).toString(16)})}async function G(n){if(typeof crypto<"u"&&crypto.subtle){let i=new TextEncoder().encode(n),r=await crypto.subtle.digest("SHA-256",i);return Array.from(new Uint8Array(r)).map(l=>l.toString(16).padStart(2,"0")).join("")}let t=0;for(let e=0;e<n.length;e++)t=(t<<5)-t+n.charCodeAt(e)|0;return Math.abs(t).toString(16).padStart(8,"0")}function K(){if(typeof location>"u")return;let n=new URLSearchParams(location.search),t={},e=!1;for(let[i,r]of[["utm_source","source"],["utm_medium","medium"],["utm_campaign","campaign"],["utm_term","term"],["utm_content","content"]]){let d=n.get(i);d&&(t[r]=d,e=!0)}return e?t:void 0}function w(){return new Date().toISOString().slice(0,10)}function p(){return Date.now()}function I(n){try{return localStorage.getItem(n)}catch{return null}}function T(n,t){try{localStorage.setItem(n,t)}catch{}}function E(n){try{localStorage.removeItem(n)}catch{}}var x=class{_sessionId;_visitorId=null;_userId=null;_hostname;constructor(t){this._hostname=t||(typeof location<"u"?location.hostname:"unknown"),this._sessionId=this._getOrCreateSession(),this._userId=I(_)}get sessionId(){return this._sessionId}get userId(){return this._userId}async getVisitorId(){if(this._visitorId)return this._visitorId;let t=I(m),e=w();if(t){let[r,d]=t.split("|");if(d===e)return this._visitorId=r,r}let i=await this._generateVisitorId();return this._visitorId=i,T(m,`${i}|${e}`),i}touch(){T(S,p().toString())}identify(t){this._userId=t,T(_,t)}reset(){this._sessionId=U(),this._visitorId=null,this._userId=null,E(v),E(m),E(_),E(S)}_getOrCreateSession(){let t=I(v),e=I(S);if(t&&e&&p()-parseInt(e,10)<V)return this.touch(),t;let i=U();return T(v,i),this.touch(),i}async _generateVisitorId(){let e=[this._hostname,w(),typeof navigator<"u"?navigator.userAgent:"",typeof navigator<"u"?navigator.language:"",typeof Intl<"u"?Intl.DateTimeFormat().resolvedOptions().timeZone:"",typeof screen<"u"?`${screen.width}x${screen.height}`:""].join("|");return(await G(e)).slice(0,16)}};var b=class{queue=[];timer=null;endpoint;batchSize;flushInterval;debug;constructor(t){this.endpoint=t.endpoint,this.batchSize=t.batchSize??N,this.flushInterval=t.flushInterval??D,this.debug=t.debug??!1,this._startTimer(),this._setupUnload()}send(t){this.queue.push(t),this.queue.length>=this.batchSize&&this.flush()}flush(){if(this.queue.length===0)return;let t=this.queue.splice(0);this._dispatch(t)}destroy(){this.timer&&(clearInterval(this.timer),this.timer=null),this.flush()}_dispatch(t){let i=JSON.stringify({events:t});this.debug&&console.log("[litemetrics] sending",t.length,"events",t),typeof fetch<"u"?fetch(this.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:i,keepalive:!0}).catch(()=>{this._beacon(i)}):this._beacon(i)}_beacon(t){if(typeof navigator<"u"&&navigator.sendBeacon){let e=new Blob([t],{type:"application/json"});navigator.sendBeacon(this.endpoint,e)}}_startTimer(){typeof setInterval<"u"&&(this.timer=setInterval(()=>this.flush(),this.flushInterval))}_setupUnload(){if(typeof document>"u")return;let t=()=>{if(this.queue.length===0)return;let e={events:this.queue.splice(0)},i=JSON.stringify(e);this._beacon(i)};document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&t()}),typeof addEventListener<"u"&&addEventListener("pagehide",t)}};var C=class{onPage;lastUrl="";originalPushState=null;originalReplaceState=null;constructor(t){this.onPage=t}startSPA(){typeof history>"u"||typeof addEventListener>"u"||(this.lastUrl=location.href,this.originalPushState=history.pushState.bind(history),this.originalReplaceState=history.replaceState.bind(history),history.pushState=(...t)=>{this.originalPushState(...t),this._onNavigation()},history.replaceState=(...t)=>{this.originalReplaceState(...t),this._onNavigation()},addEventListener("popstate",()=>this._onNavigation()))}stop(){this.originalPushState&&(history.pushState=this.originalPushState),this.originalReplaceState&&(history.replaceState=this.originalReplaceState)}_onNavigation(){setTimeout(()=>{let t=location.href;if(t!==this.lastUrl){let e=this.lastUrl;this.lastUrl=t,this.onPage(t,e)}},0)}};var Q="data-litemetrics-event",M="data-litemetrics-event-";function Y(n){function t(e){let i=e.target;if(!i)return;let r=i;for(;r;){let d=r.getAttribute(Q);if(d){let l={},s=r.attributes;for(let f=0;f<s.length;f++){let u=s[f];if(u.name.startsWith(M)){let g=u.name.slice(M.length);l[g]=u.value}}n.track(d,Object.keys(l).length>0?l:void 0);return}r=r.parentElement}}return document.addEventListener("click",t,!0),()=>{document.removeEventListener("click",t,!0)}}function q(n){let{siteId:t,endpoint:e,autoTrack:i=!0,autoSpa:r=!0,debug:d=!1,respectDnt:l=!0}=n;if(l&&typeof navigator<"u"&&navigator.doNotTrack==="1")return F();try{if(localStorage.getItem(y)==="1")return F()}catch{}let s=new x,f=new b({endpoint:e,batchSize:n.batchSize,flushInterval:n.flushInterval,debug:d}),u=null,g=null,A=!1;function k(){let o={};if(typeof screen<"u"&&(o.screen={width:screen.width,height:screen.height}),typeof navigator<"u"){o.language=navigator.language;let c=navigator.connection;c&&(o.connection={type:c.type,downlink:c.downlink,effectiveType:c.effectiveType,rtt:c.rtt})}typeof Intl<"u"&&(o.timezone=Intl.DateTimeFormat().resolvedOptions().timeZone);let a=K();return a&&(o.utm=a),o}async function O(o){A||(s.touch(),f.send(o))}async function P(o,a,c){let h=await s.getVisitorId(),z={type:"pageview",siteId:t,timestamp:p(),sessionId:s.sessionId,visitorId:h,url:o||(typeof location<"u"?location.href:""),referrer:c||(typeof document<"u"?document.referrer:void 0),title:a||(typeof document<"u"?document.title:void 0),...k()};O(z)}i&&P(),r&&(u=new C((o,a)=>P(o,void 0,a)),u.startSPA());let L={track(o,a){s.getVisitorId().then(c=>{let h={type:"event",siteId:t,timestamp:p(),sessionId:s.sessionId,visitorId:c,name:o,properties:a,...k()};O(h)})},identify(o,a){s.identify(o),s.getVisitorId().then(c=>{let h={type:"identify",siteId:t,timestamp:p(),sessionId:s.sessionId,visitorId:c,userId:o,traits:a,...k()};O(h)})},page(o,a){P(o,a)},reset(){s.reset()},opt_out(){A=!0;try{localStorage.setItem(y,"1")}catch{}},opt_in(){A=!1;try{localStorage.removeItem(y)}catch{}},destroy(){g?.(),u?.stop(),f.destroy()}};return i&&typeof document<"u"&&(g=Y(L)),L}function F(){return{track(){},identify(){},page(){},reset(){},opt_out(){},opt_in(){},destroy(){}}}return J(W);})();
@@ -0,0 +1,490 @@
1
+ // src/tracker.ts
2
+ import { STORAGE_KEY_OPTOUT } from "@litemetrics/core";
3
+
4
+ // src/session.ts
5
+ import {
6
+ SESSION_TIMEOUT,
7
+ STORAGE_KEY_SESSION,
8
+ STORAGE_KEY_VISITOR,
9
+ STORAGE_KEY_LAST_ACTIVE,
10
+ STORAGE_KEY_USER
11
+ } from "@litemetrics/core";
12
+
13
+ // src/utils.ts
14
+ function generateId() {
15
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
16
+ return crypto.randomUUID();
17
+ }
18
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
19
+ const r = Math.random() * 16 | 0;
20
+ const v = c === "x" ? r : r & 3 | 8;
21
+ return v.toString(16);
22
+ });
23
+ }
24
+ async function hashString(input) {
25
+ if (typeof crypto !== "undefined" && crypto.subtle) {
26
+ const encoder = new TextEncoder();
27
+ const data = encoder.encode(input);
28
+ const hash = await crypto.subtle.digest("SHA-256", data);
29
+ const array = Array.from(new Uint8Array(hash));
30
+ return array.map((b) => b.toString(16).padStart(2, "0")).join("");
31
+ }
32
+ let h = 0;
33
+ for (let i = 0; i < input.length; i++) {
34
+ h = (h << 5) - h + input.charCodeAt(i) | 0;
35
+ }
36
+ return Math.abs(h).toString(16).padStart(8, "0");
37
+ }
38
+ function parseUTM() {
39
+ if (typeof location === "undefined") return void 0;
40
+ const params = new URLSearchParams(location.search);
41
+ const utm = {};
42
+ let hasUtm = false;
43
+ for (const [key, field] of [
44
+ ["utm_source", "source"],
45
+ ["utm_medium", "medium"],
46
+ ["utm_campaign", "campaign"],
47
+ ["utm_term", "term"],
48
+ ["utm_content", "content"]
49
+ ]) {
50
+ const val = params.get(key);
51
+ if (val) {
52
+ utm[field] = val;
53
+ hasUtm = true;
54
+ }
55
+ }
56
+ return hasUtm ? utm : void 0;
57
+ }
58
+ function getDayString() {
59
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
60
+ }
61
+ function now() {
62
+ return Date.now();
63
+ }
64
+
65
+ // src/session.ts
66
+ function storageGet(key) {
67
+ try {
68
+ return localStorage.getItem(key);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ function storageSet(key, value) {
74
+ try {
75
+ localStorage.setItem(key, value);
76
+ } catch {
77
+ }
78
+ }
79
+ function storageRemove(key) {
80
+ try {
81
+ localStorage.removeItem(key);
82
+ } catch {
83
+ }
84
+ }
85
+ var SessionManager = class {
86
+ _sessionId;
87
+ _visitorId = null;
88
+ _userId = null;
89
+ _hostname;
90
+ constructor(hostname) {
91
+ this._hostname = hostname || (typeof location !== "undefined" ? location.hostname : "unknown");
92
+ this._sessionId = this._getOrCreateSession();
93
+ this._userId = storageGet(STORAGE_KEY_USER);
94
+ }
95
+ get sessionId() {
96
+ return this._sessionId;
97
+ }
98
+ get userId() {
99
+ return this._userId;
100
+ }
101
+ async getVisitorId() {
102
+ if (this._visitorId) return this._visitorId;
103
+ const cached = storageGet(STORAGE_KEY_VISITOR);
104
+ const today = getDayString();
105
+ if (cached) {
106
+ const [id2, date] = cached.split("|");
107
+ if (date === today) {
108
+ this._visitorId = id2;
109
+ return id2;
110
+ }
111
+ }
112
+ const id = await this._generateVisitorId();
113
+ this._visitorId = id;
114
+ storageSet(STORAGE_KEY_VISITOR, `${id}|${today}`);
115
+ return id;
116
+ }
117
+ touch() {
118
+ storageSet(STORAGE_KEY_LAST_ACTIVE, now().toString());
119
+ }
120
+ identify(userId) {
121
+ this._userId = userId;
122
+ storageSet(STORAGE_KEY_USER, userId);
123
+ }
124
+ reset() {
125
+ this._sessionId = generateId();
126
+ this._visitorId = null;
127
+ this._userId = null;
128
+ storageRemove(STORAGE_KEY_SESSION);
129
+ storageRemove(STORAGE_KEY_VISITOR);
130
+ storageRemove(STORAGE_KEY_USER);
131
+ storageRemove(STORAGE_KEY_LAST_ACTIVE);
132
+ }
133
+ _getOrCreateSession() {
134
+ const stored = storageGet(STORAGE_KEY_SESSION);
135
+ const lastActive = storageGet(STORAGE_KEY_LAST_ACTIVE);
136
+ if (stored && lastActive) {
137
+ const elapsed = now() - parseInt(lastActive, 10);
138
+ if (elapsed < SESSION_TIMEOUT) {
139
+ this.touch();
140
+ return stored;
141
+ }
142
+ }
143
+ const id = generateId();
144
+ storageSet(STORAGE_KEY_SESSION, id);
145
+ this.touch();
146
+ return id;
147
+ }
148
+ async _generateVisitorId() {
149
+ const components = [
150
+ this._hostname,
151
+ getDayString(),
152
+ typeof navigator !== "undefined" ? navigator.userAgent : "",
153
+ typeof navigator !== "undefined" ? navigator.language : "",
154
+ typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "",
155
+ typeof screen !== "undefined" ? `${screen.width}x${screen.height}` : ""
156
+ ];
157
+ const raw = components.join("|");
158
+ const hash = await hashString(raw);
159
+ return hash.slice(0, 16);
160
+ }
161
+ };
162
+
163
+ // src/transport.ts
164
+ import { DEFAULT_BATCH_SIZE, DEFAULT_FLUSH_INTERVAL } from "@litemetrics/core";
165
+ var Transport = class {
166
+ queue = [];
167
+ timer = null;
168
+ endpoint;
169
+ batchSize;
170
+ flushInterval;
171
+ debug;
172
+ constructor(options) {
173
+ this.endpoint = options.endpoint;
174
+ this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
175
+ this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;
176
+ this.debug = options.debug ?? false;
177
+ this._startTimer();
178
+ this._setupUnload();
179
+ }
180
+ send(event) {
181
+ this.queue.push(event);
182
+ if (this.queue.length >= this.batchSize) {
183
+ this.flush();
184
+ }
185
+ }
186
+ flush() {
187
+ if (this.queue.length === 0) return;
188
+ const events = this.queue.splice(0);
189
+ this._dispatch(events);
190
+ }
191
+ destroy() {
192
+ if (this.timer) {
193
+ clearInterval(this.timer);
194
+ this.timer = null;
195
+ }
196
+ this.flush();
197
+ }
198
+ _dispatch(events) {
199
+ const payload = { events };
200
+ const body = JSON.stringify(payload);
201
+ if (this.debug) {
202
+ console.log("[litemetrics] sending", events.length, "events", events);
203
+ }
204
+ if (typeof fetch !== "undefined") {
205
+ fetch(this.endpoint, {
206
+ method: "POST",
207
+ headers: { "Content-Type": "application/json" },
208
+ body,
209
+ keepalive: true
210
+ }).catch(() => {
211
+ this._beacon(body);
212
+ });
213
+ } else {
214
+ this._beacon(body);
215
+ }
216
+ }
217
+ _beacon(body) {
218
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
219
+ const blob = new Blob([body], { type: "application/json" });
220
+ navigator.sendBeacon(this.endpoint, blob);
221
+ }
222
+ }
223
+ _startTimer() {
224
+ if (typeof setInterval !== "undefined") {
225
+ this.timer = setInterval(() => this.flush(), this.flushInterval);
226
+ }
227
+ }
228
+ _setupUnload() {
229
+ if (typeof document === "undefined") return;
230
+ const onUnload = () => {
231
+ if (this.queue.length === 0) return;
232
+ const payload = { events: this.queue.splice(0) };
233
+ const body = JSON.stringify(payload);
234
+ this._beacon(body);
235
+ };
236
+ document.addEventListener("visibilitychange", () => {
237
+ if (document.visibilityState === "hidden") {
238
+ onUnload();
239
+ }
240
+ });
241
+ if (typeof addEventListener !== "undefined") {
242
+ addEventListener("pagehide", onUnload);
243
+ }
244
+ }
245
+ };
246
+
247
+ // src/auto.ts
248
+ var AutoTracker = class {
249
+ onPage;
250
+ lastUrl = "";
251
+ originalPushState = null;
252
+ originalReplaceState = null;
253
+ constructor(onPage) {
254
+ this.onPage = onPage;
255
+ }
256
+ startSPA() {
257
+ if (typeof history === "undefined" || typeof addEventListener === "undefined") return;
258
+ this.lastUrl = location.href;
259
+ this.originalPushState = history.pushState.bind(history);
260
+ this.originalReplaceState = history.replaceState.bind(history);
261
+ history.pushState = (...args) => {
262
+ this.originalPushState(...args);
263
+ this._onNavigation();
264
+ };
265
+ history.replaceState = (...args) => {
266
+ this.originalReplaceState(...args);
267
+ this._onNavigation();
268
+ };
269
+ addEventListener("popstate", () => this._onNavigation());
270
+ }
271
+ stop() {
272
+ if (this.originalPushState) {
273
+ history.pushState = this.originalPushState;
274
+ }
275
+ if (this.originalReplaceState) {
276
+ history.replaceState = this.originalReplaceState;
277
+ }
278
+ }
279
+ _onNavigation() {
280
+ setTimeout(() => {
281
+ const current = location.href;
282
+ if (current !== this.lastUrl) {
283
+ const referrer = this.lastUrl;
284
+ this.lastUrl = current;
285
+ this.onPage(current, referrer);
286
+ }
287
+ }, 0);
288
+ }
289
+ };
290
+
291
+ // src/attributes.ts
292
+ var ATTR_EVENT = "data-litemetrics-event";
293
+ var ATTR_PREFIX = "data-litemetrics-event-";
294
+ function initAttributeTracking(instance) {
295
+ function handleClick(e) {
296
+ const target = e.target;
297
+ if (!target) return;
298
+ let el = target;
299
+ while (el) {
300
+ const eventName = el.getAttribute(ATTR_EVENT);
301
+ if (eventName) {
302
+ const properties = {};
303
+ const attrs = el.attributes;
304
+ for (let i = 0; i < attrs.length; i++) {
305
+ const attr = attrs[i];
306
+ if (attr.name.startsWith(ATTR_PREFIX)) {
307
+ const key = attr.name.slice(ATTR_PREFIX.length);
308
+ properties[key] = attr.value;
309
+ }
310
+ }
311
+ instance.track(
312
+ eventName,
313
+ Object.keys(properties).length > 0 ? properties : void 0
314
+ );
315
+ return;
316
+ }
317
+ el = el.parentElement;
318
+ }
319
+ }
320
+ document.addEventListener("click", handleClick, true);
321
+ return () => {
322
+ document.removeEventListener("click", handleClick, true);
323
+ };
324
+ }
325
+
326
+ // src/tracker.ts
327
+ function createTracker(config) {
328
+ const {
329
+ siteId,
330
+ endpoint,
331
+ autoTrack = true,
332
+ autoSpa = true,
333
+ debug = false,
334
+ respectDnt = true
335
+ } = config;
336
+ if (respectDnt && typeof navigator !== "undefined" && navigator.doNotTrack === "1") {
337
+ return createNoopTracker();
338
+ }
339
+ try {
340
+ if (localStorage.getItem(STORAGE_KEY_OPTOUT) === "1") {
341
+ return createNoopTracker();
342
+ }
343
+ } catch {
344
+ }
345
+ const session = new SessionManager();
346
+ const transport = new Transport({
347
+ endpoint,
348
+ batchSize: config.batchSize,
349
+ flushInterval: config.flushInterval,
350
+ debug
351
+ });
352
+ let autoTracker = null;
353
+ let cleanupAttributes = null;
354
+ let optedOut = false;
355
+ function getContext() {
356
+ const ctx = {};
357
+ if (typeof screen !== "undefined") {
358
+ ctx.screen = { width: screen.width, height: screen.height };
359
+ }
360
+ if (typeof navigator !== "undefined") {
361
+ ctx.language = navigator.language;
362
+ const conn = navigator.connection;
363
+ if (conn) {
364
+ ctx.connection = {
365
+ type: conn.type,
366
+ downlink: conn.downlink,
367
+ effectiveType: conn.effectiveType,
368
+ rtt: conn.rtt
369
+ };
370
+ }
371
+ }
372
+ if (typeof Intl !== "undefined") {
373
+ ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
374
+ }
375
+ const utm = parseUTM();
376
+ if (utm) ctx.utm = utm;
377
+ return ctx;
378
+ }
379
+ async function sendEvent(event) {
380
+ if (optedOut) return;
381
+ session.touch();
382
+ transport.send(event);
383
+ }
384
+ async function trackPage(url, title, referrer) {
385
+ const visitorId = await session.getVisitorId();
386
+ const event = {
387
+ type: "pageview",
388
+ siteId,
389
+ timestamp: now(),
390
+ sessionId: session.sessionId,
391
+ visitorId,
392
+ url: url || (typeof location !== "undefined" ? location.href : ""),
393
+ referrer: referrer || (typeof document !== "undefined" ? document.referrer : void 0),
394
+ title: title || (typeof document !== "undefined" ? document.title : void 0),
395
+ ...getContext()
396
+ };
397
+ sendEvent(event);
398
+ }
399
+ if (autoTrack) {
400
+ trackPage();
401
+ }
402
+ if (autoSpa) {
403
+ autoTracker = new AutoTracker((url, ref) => trackPage(url, void 0, ref));
404
+ autoTracker.startSPA();
405
+ }
406
+ const instance = {
407
+ track(name, properties) {
408
+ session.getVisitorId().then((visitorId) => {
409
+ const event = {
410
+ type: "event",
411
+ siteId,
412
+ timestamp: now(),
413
+ sessionId: session.sessionId,
414
+ visitorId,
415
+ name,
416
+ properties,
417
+ ...getContext()
418
+ };
419
+ sendEvent(event);
420
+ });
421
+ },
422
+ identify(userId, traits) {
423
+ session.identify(userId);
424
+ session.getVisitorId().then((visitorId) => {
425
+ const event = {
426
+ type: "identify",
427
+ siteId,
428
+ timestamp: now(),
429
+ sessionId: session.sessionId,
430
+ visitorId,
431
+ userId,
432
+ traits,
433
+ ...getContext()
434
+ };
435
+ sendEvent(event);
436
+ });
437
+ },
438
+ page(url, title) {
439
+ trackPage(url, title);
440
+ },
441
+ reset() {
442
+ session.reset();
443
+ },
444
+ opt_out() {
445
+ optedOut = true;
446
+ try {
447
+ localStorage.setItem(STORAGE_KEY_OPTOUT, "1");
448
+ } catch {
449
+ }
450
+ },
451
+ opt_in() {
452
+ optedOut = false;
453
+ try {
454
+ localStorage.removeItem(STORAGE_KEY_OPTOUT);
455
+ } catch {
456
+ }
457
+ },
458
+ destroy() {
459
+ cleanupAttributes?.();
460
+ autoTracker?.stop();
461
+ transport.destroy();
462
+ }
463
+ };
464
+ if (autoTrack && typeof document !== "undefined") {
465
+ cleanupAttributes = initAttributeTracking(instance);
466
+ }
467
+ return instance;
468
+ }
469
+ function createNoopTracker() {
470
+ return {
471
+ track() {
472
+ },
473
+ identify() {
474
+ },
475
+ page() {
476
+ },
477
+ reset() {
478
+ },
479
+ opt_out() {
480
+ },
481
+ opt_in() {
482
+ },
483
+ destroy() {
484
+ }
485
+ };
486
+ }
487
+ export {
488
+ createTracker
489
+ };
490
+ //# sourceMappingURL=litemetrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tracker.ts","../src/session.ts","../src/utils.ts","../src/transport.ts","../src/auto.ts","../src/attributes.ts"],"sourcesContent":["import type {\n TrackerConfig,\n ClientEvent,\n PageviewEvent,\n CustomEvent,\n IdentifyEvent,\n ClientContext,\n} from '@litemetrics/core';\nimport { STORAGE_KEY_OPTOUT } from '@litemetrics/core';\nimport { SessionManager } from './session';\nimport { Transport } from './transport';\nimport { AutoTracker } from './auto';\nimport { parseUTM, now } from './utils';\nimport { initAttributeTracking } from './attributes';\n\nexport interface LitemetricsInstance {\n track(name: string, properties?: Record<string, unknown>): void;\n identify(userId: string, traits?: Record<string, unknown>): void;\n page(url?: string, title?: string): void;\n reset(): void;\n opt_out(): void;\n opt_in(): void;\n destroy(): void;\n}\n\nexport function createTracker(config: TrackerConfig): LitemetricsInstance {\n const {\n siteId,\n endpoint,\n autoTrack = true,\n autoSpa = true,\n debug = false,\n respectDnt = true,\n } = config;\n\n // Check Do Not Track\n if (respectDnt && typeof navigator !== 'undefined' && navigator.doNotTrack === '1') {\n return createNoopTracker();\n }\n\n // Check opt-out\n try {\n if (localStorage.getItem(STORAGE_KEY_OPTOUT) === '1') {\n return createNoopTracker();\n }\n } catch {\n // ignore\n }\n\n const session = new SessionManager();\n const transport = new Transport({\n endpoint,\n batchSize: config.batchSize,\n flushInterval: config.flushInterval,\n debug,\n });\n let autoTracker: AutoTracker | null = null;\n let cleanupAttributes: (() => void) | null = null;\n let optedOut = false;\n\n function getContext(): ClientContext {\n const ctx: ClientContext = {};\n if (typeof screen !== 'undefined') {\n ctx.screen = { width: screen.width, height: screen.height };\n }\n if (typeof navigator !== 'undefined') {\n ctx.language = navigator.language;\n // Network Information API\n const conn = (navigator as any).connection;\n if (conn) {\n ctx.connection = {\n type: conn.type,\n downlink: conn.downlink,\n effectiveType: conn.effectiveType,\n rtt: conn.rtt,\n };\n }\n }\n if (typeof Intl !== 'undefined') {\n ctx.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n }\n const utm = parseUTM();\n if (utm) ctx.utm = utm;\n return ctx;\n }\n\n async function sendEvent(event: ClientEvent): Promise<void> {\n if (optedOut) return;\n session.touch();\n transport.send(event);\n }\n\n async function trackPage(url?: string, title?: string, referrer?: string): Promise<void> {\n const visitorId = await session.getVisitorId();\n const event: PageviewEvent & ClientContext = {\n type: 'pageview',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n url: url || (typeof location !== 'undefined' ? location.href : ''),\n referrer: referrer || (typeof document !== 'undefined' ? document.referrer : undefined),\n title: title || (typeof document !== 'undefined' ? document.title : undefined),\n ...getContext(),\n };\n sendEvent(event);\n }\n\n // Auto-track initial page view\n if (autoTrack) {\n trackPage();\n }\n\n // Auto-track SPA navigation\n if (autoSpa) {\n autoTracker = new AutoTracker((url, ref) => trackPage(url, undefined, ref));\n autoTracker.startSPA();\n }\n\n // We need a reference to the instance for attribute tracking\n // so we create it first then init attributes\n const instance: LitemetricsInstance = {\n track(name: string, properties?: Record<string, unknown>): void {\n session.getVisitorId().then((visitorId) => {\n const event: CustomEvent & ClientContext = {\n type: 'event',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n name,\n properties,\n ...getContext(),\n };\n sendEvent(event);\n });\n },\n\n identify(userId: string, traits?: Record<string, unknown>): void {\n session.identify(userId);\n session.getVisitorId().then((visitorId) => {\n const event: IdentifyEvent & ClientContext = {\n type: 'identify',\n siteId,\n timestamp: now(),\n sessionId: session.sessionId,\n visitorId,\n userId,\n traits,\n ...getContext(),\n };\n sendEvent(event);\n });\n },\n\n page(url?: string, title?: string): void {\n trackPage(url, title);\n },\n\n reset(): void {\n session.reset();\n },\n\n opt_out(): void {\n optedOut = true;\n try {\n localStorage.setItem(STORAGE_KEY_OPTOUT, '1');\n } catch {\n // ignore\n }\n },\n\n opt_in(): void {\n optedOut = false;\n try {\n localStorage.removeItem(STORAGE_KEY_OPTOUT);\n } catch {\n // ignore\n }\n },\n\n destroy(): void {\n cleanupAttributes?.();\n autoTracker?.stop();\n transport.destroy();\n },\n };\n\n // Initialize data-attribute event tracking\n if (autoTrack && typeof document !== 'undefined') {\n cleanupAttributes = initAttributeTracking(instance);\n }\n\n return instance;\n}\n\nfunction createNoopTracker(): LitemetricsInstance {\n return {\n track() {},\n identify() {},\n page() {},\n reset() {},\n opt_out() {},\n opt_in() {},\n destroy() {},\n };\n}\n","import {\n SESSION_TIMEOUT,\n STORAGE_KEY_SESSION,\n STORAGE_KEY_VISITOR,\n STORAGE_KEY_LAST_ACTIVE,\n STORAGE_KEY_USER,\n} from '@litemetrics/core';\nimport { generateId, hashString, getDayString, now } from './utils';\n\nfunction storageGet(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction storageSet(key: string, value: string): void {\n try {\n localStorage.setItem(key, value);\n } catch {\n // localStorage unavailable (private browsing, etc.)\n }\n}\n\nfunction storageRemove(key: string): void {\n try {\n localStorage.removeItem(key);\n } catch {\n // noop\n }\n}\n\nexport class SessionManager {\n private _sessionId: string;\n private _visitorId: string | null = null;\n private _userId: string | null = null;\n private _hostname: string;\n\n constructor(hostname?: string) {\n this._hostname = hostname || (typeof location !== 'undefined' ? location.hostname : 'unknown');\n this._sessionId = this._getOrCreateSession();\n this._userId = storageGet(STORAGE_KEY_USER);\n }\n\n get sessionId(): string {\n return this._sessionId;\n }\n\n get userId(): string | null {\n return this._userId;\n }\n\n async getVisitorId(): Promise<string> {\n if (this._visitorId) return this._visitorId;\n\n const cached = storageGet(STORAGE_KEY_VISITOR);\n const today = getDayString();\n\n // Visitor ID rotates daily for privacy\n if (cached) {\n const [id, date] = cached.split('|');\n if (date === today) {\n this._visitorId = id;\n return id;\n }\n }\n\n const id = await this._generateVisitorId();\n this._visitorId = id;\n storageSet(STORAGE_KEY_VISITOR, `${id}|${today}`);\n return id;\n }\n\n touch(): void {\n storageSet(STORAGE_KEY_LAST_ACTIVE, now().toString());\n }\n\n identify(userId: string): void {\n this._userId = userId;\n storageSet(STORAGE_KEY_USER, userId);\n }\n\n reset(): void {\n this._sessionId = generateId();\n this._visitorId = null;\n this._userId = null;\n storageRemove(STORAGE_KEY_SESSION);\n storageRemove(STORAGE_KEY_VISITOR);\n storageRemove(STORAGE_KEY_USER);\n storageRemove(STORAGE_KEY_LAST_ACTIVE);\n }\n\n private _getOrCreateSession(): string {\n const stored = storageGet(STORAGE_KEY_SESSION);\n const lastActive = storageGet(STORAGE_KEY_LAST_ACTIVE);\n\n if (stored && lastActive) {\n const elapsed = now() - parseInt(lastActive, 10);\n if (elapsed < SESSION_TIMEOUT) {\n this.touch();\n return stored;\n }\n }\n\n const id = generateId();\n storageSet(STORAGE_KEY_SESSION, id);\n this.touch();\n return id;\n }\n\n private async _generateVisitorId(): Promise<string> {\n const components = [\n this._hostname,\n getDayString(),\n typeof navigator !== 'undefined' ? navigator.userAgent : '',\n typeof navigator !== 'undefined' ? navigator.language : '',\n typeof Intl !== 'undefined'\n ? Intl.DateTimeFormat().resolvedOptions().timeZone\n : '',\n typeof screen !== 'undefined' ? `${screen.width}x${screen.height}` : '',\n ];\n\n const raw = components.join('|');\n const hash = await hashString(raw);\n return hash.slice(0, 16);\n }\n}\n","import type { UTMParams } from '@litemetrics/core';\n\nexport function generateId(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // Fallback for older browsers\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 async function hashString(input: string): Promise<string> {\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const encoder = new TextEncoder();\n const data = encoder.encode(input);\n const hash = await crypto.subtle.digest('SHA-256', data);\n const array = Array.from(new Uint8Array(hash));\n return array.map((b) => b.toString(16).padStart(2, '0')).join('');\n }\n // Simple fallback hash\n let h = 0;\n for (let i = 0; i < input.length; i++) {\n h = ((h << 5) - h + input.charCodeAt(i)) | 0;\n }\n return Math.abs(h).toString(16).padStart(8, '0');\n}\n\nexport function parseUTM(): UTMParams | undefined {\n if (typeof location === 'undefined') return undefined;\n const params = new URLSearchParams(location.search);\n const utm: UTMParams = {};\n let hasUtm = false;\n\n for (const [key, field] of [\n ['utm_source', 'source'],\n ['utm_medium', 'medium'],\n ['utm_campaign', 'campaign'],\n ['utm_term', 'term'],\n ['utm_content', 'content'],\n ] as const) {\n const val = params.get(key);\n if (val) {\n (utm as Record<string, string>)[field] = val;\n hasUtm = true;\n }\n }\n\n return hasUtm ? utm : undefined;\n}\n\nexport function getDayString(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nexport function now(): number {\n return Date.now();\n}\n","import type { ClientEvent, CollectPayload } from '@litemetrics/core';\nimport { DEFAULT_BATCH_SIZE, DEFAULT_FLUSH_INTERVAL } from '@litemetrics/core';\n\nexport interface TransportOptions {\n endpoint: string;\n batchSize?: number;\n flushInterval?: number;\n debug?: boolean;\n}\n\nexport class Transport {\n private queue: ClientEvent[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n private endpoint: string;\n private batchSize: number;\n private flushInterval: number;\n private debug: boolean;\n\n constructor(options: TransportOptions) {\n this.endpoint = options.endpoint;\n this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;\n this.flushInterval = options.flushInterval ?? DEFAULT_FLUSH_INTERVAL;\n this.debug = options.debug ?? false;\n\n this._startTimer();\n this._setupUnload();\n }\n\n send(event: ClientEvent): void {\n this.queue.push(event);\n if (this.queue.length >= this.batchSize) {\n this.flush();\n }\n }\n\n flush(): void {\n if (this.queue.length === 0) return;\n const events = this.queue.splice(0);\n this._dispatch(events);\n }\n\n destroy(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.flush();\n }\n\n private _dispatch(events: ClientEvent[]): void {\n const payload: CollectPayload = { events };\n const body = JSON.stringify(payload);\n\n if (this.debug) {\n console.log('[litemetrics] sending', events.length, 'events', events);\n }\n\n // Try fetch first, fall back to sendBeacon\n if (typeof fetch !== 'undefined') {\n fetch(this.endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n keepalive: true,\n }).catch(() => {\n // Retry once with sendBeacon\n this._beacon(body);\n });\n } else {\n this._beacon(body);\n }\n }\n\n private _beacon(body: string): void {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n const blob = new Blob([body], { type: 'application/json' });\n navigator.sendBeacon(this.endpoint, blob);\n }\n }\n\n private _startTimer(): void {\n if (typeof setInterval !== 'undefined') {\n this.timer = setInterval(() => this.flush(), this.flushInterval);\n }\n }\n\n private _setupUnload(): void {\n if (typeof document === 'undefined') return;\n\n const onUnload = () => {\n if (this.queue.length === 0) return;\n const payload: CollectPayload = { events: this.queue.splice(0) };\n const body = JSON.stringify(payload);\n // sendBeacon is more reliable during page unload\n this._beacon(body);\n };\n\n // visibilitychange + pagehide is the most reliable combo\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n onUnload();\n }\n });\n\n if (typeof addEventListener !== 'undefined') {\n addEventListener('pagehide', onUnload);\n }\n }\n}\n","export type PageCallback = (url: string, referrer?: string) => void;\n\nexport class AutoTracker {\n private onPage: PageCallback;\n private lastUrl: string = '';\n private originalPushState: typeof history.pushState | null = null;\n private originalReplaceState: typeof history.replaceState | null = null;\n\n constructor(onPage: PageCallback) {\n this.onPage = onPage;\n }\n\n startSPA(): void {\n if (typeof history === 'undefined' || typeof addEventListener === 'undefined') return;\n\n this.lastUrl = location.href;\n\n // Monkey-patch pushState and replaceState\n this.originalPushState = history.pushState.bind(history);\n this.originalReplaceState = history.replaceState.bind(history);\n\n history.pushState = (...args: Parameters<typeof history.pushState>) => {\n this.originalPushState!(...args);\n this._onNavigation();\n };\n\n history.replaceState = (...args: Parameters<typeof history.replaceState>) => {\n this.originalReplaceState!(...args);\n this._onNavigation();\n };\n\n addEventListener('popstate', () => this._onNavigation());\n }\n\n stop(): void {\n if (this.originalPushState) {\n history.pushState = this.originalPushState;\n }\n if (this.originalReplaceState) {\n history.replaceState = this.originalReplaceState;\n }\n }\n\n private _onNavigation(): void {\n // Small delay to let the URL update\n setTimeout(() => {\n const current = location.href;\n if (current !== this.lastUrl) {\n const referrer = this.lastUrl;\n this.lastUrl = current;\n this.onPage(current, referrer);\n }\n }, 0);\n }\n}\n","import type { LitemetricsInstance } from './tracker';\n\nconst ATTR_EVENT = 'data-litemetrics-event';\nconst ATTR_PREFIX = 'data-litemetrics-event-';\n\n/**\n * Initialize data-attribute event tracking.\n * Clicks on elements with `data-litemetrics-event=\"EventName\"` will be auto-tracked.\n * Additional properties via `data-litemetrics-event-*` attributes.\n *\n * Example:\n * <button data-litemetrics-event=\"Signup\" data-litemetrics-event-plan=\"pro\">\n * → tracks event \"Signup\" with { plan: \"pro\" }\n */\nexport function initAttributeTracking(instance: LitemetricsInstance): () => void {\n function handleClick(e: Event) {\n const target = e.target as HTMLElement | null;\n if (!target) return;\n\n // Walk up the DOM to find an element with data-litemetrics-event\n let el: HTMLElement | null = target;\n while (el) {\n const eventName = el.getAttribute(ATTR_EVENT);\n if (eventName) {\n // Collect data-litemetrics-event-* properties\n const properties: Record<string, string> = {};\n const attrs = el.attributes;\n for (let i = 0; i < attrs.length; i++) {\n const attr = attrs[i];\n if (attr.name.startsWith(ATTR_PREFIX)) {\n const key = attr.name.slice(ATTR_PREFIX.length);\n properties[key] = attr.value;\n }\n }\n\n instance.track(\n eventName,\n Object.keys(properties).length > 0 ? properties : undefined\n );\n return;\n }\n el = el.parentElement;\n }\n }\n\n document.addEventListener('click', handleClick, true);\n\n return () => {\n document.removeEventListener('click', handleClick, true);\n };\n}\n"],"mappings":"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","names":["id"]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@litemetrics/tracker",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight browser analytics tracker (<3KB gzipped)",
5
+ "license": "MIT",
6
+ "author": "Metehan Kurucu",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/metehankurucu/litemetrics",
10
+ "directory": "packages/tracker"
11
+ },
12
+ "keywords": ["analytics", "tracking", "litemetrics", "browser", "pageview"],
13
+ "type": "module",
14
+ "main": "./dist/litemetrics.cjs",
15
+ "module": "./dist/litemetrics.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/litemetrics.js",
21
+ "require": "./dist/litemetrics.cjs"
22
+ },
23
+ "./script": "./dist/litemetrics.global.js"
24
+ },
25
+ "files": ["dist"],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "dev": "tsup --watch",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "dependencies": {
36
+ "@litemetrics/core": "0.1.0"
37
+ },
38
+ "devDependencies": {
39
+ "tsup": "^8",
40
+ "typescript": "^5.7"
41
+ }
42
+ }