trackhome 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -22
- package/dist/index.js.map +1 -1
- package/dist/t.umd.js +1 -1
- package/dist/t.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -45,13 +45,13 @@ export interface TrackerConfig {
|
|
|
45
45
|
*
|
|
46
46
|
* Idempotent — calling init() twice is a no-op.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
48
|
+
* Works in BOTH browsers and Node.js backends. In a browser, autoPageView
|
|
49
|
+
* hooks SPA navigation. In Node, autoPageView is ignored (no window/history).
|
|
50
|
+
*
|
|
51
|
+
* With `autoPageView` (default: true, browser-only), the SDK:
|
|
49
52
|
* - fires a `page_view` event immediately for the current URL
|
|
50
53
|
* - hooks history.pushState / replaceState + popstate + hashchange
|
|
51
54
|
* - fires a `page_view` tagged `path:<current-path>` on every navigation
|
|
52
|
-
*
|
|
53
|
-
* That means many sites don't need to call track() at all for basic traffic
|
|
54
|
-
* analytics — init() alone is enough.
|
|
55
55
|
*/
|
|
56
56
|
export declare function init(userCfgOrUrl: string | TrackerConfig): void;
|
|
57
57
|
/** Replace the persistent tag set. */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAsBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CA6C/D;AAED,sCAAsC;AACtC,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAG5C;AAED,uCAAuC;AACvC,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAGxC;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAG7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,IAAI,CAG7D;AAED,qDAAqD;AACrD,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAO,GAAG,IAAI,CAElE;AAED,gEAAgE;AAChE,wBAAsB,KAAK,CAAC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB7E;AAmID,8CAA8C;AAC9C,eAAO,MAAM,OAAO;;;;;;;;CAA0D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -36,17 +36,15 @@ const isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefin
|
|
|
36
36
|
*
|
|
37
37
|
* Idempotent — calling init() twice is a no-op.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* Works in BOTH browsers and Node.js backends. In a browser, autoPageView
|
|
40
|
+
* hooks SPA navigation. In Node, autoPageView is ignored (no window/history).
|
|
41
|
+
*
|
|
42
|
+
* With `autoPageView` (default: true, browser-only), the SDK:
|
|
40
43
|
* - fires a `page_view` event immediately for the current URL
|
|
41
44
|
* - hooks history.pushState / replaceState + popstate + hashchange
|
|
42
45
|
* - fires a `page_view` tagged `path:<current-path>` on every navigation
|
|
43
|
-
*
|
|
44
|
-
* That means many sites don't need to call track() at all for basic traffic
|
|
45
|
-
* analytics — init() alone is enough.
|
|
46
46
|
*/
|
|
47
47
|
export function init(userCfgOrUrl) {
|
|
48
|
-
if (!isBrowser)
|
|
49
|
-
return;
|
|
50
48
|
if (cfg)
|
|
51
49
|
return; // idempotent
|
|
52
50
|
const userCfg = typeof userCfgOrUrl === 'string' ? { endpoint: userCfgOrUrl } : userCfgOrUrl;
|
|
@@ -65,25 +63,27 @@ export function init(userCfgOrUrl) {
|
|
|
65
63
|
tags: dedupe(userCfg.tags ?? []),
|
|
66
64
|
};
|
|
67
65
|
cfg = c;
|
|
68
|
-
// Background flush timer
|
|
69
|
-
// reference; we don't need to hold onto it.
|
|
66
|
+
// Background flush timer — works in both browser and Node.
|
|
70
67
|
setInterval(() => {
|
|
71
68
|
void flush().catch(() => { });
|
|
72
69
|
}, c.flushIntervalMs);
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
// Browser-only hooks: visibilitychange, beforeunload, pagehide, autoPageView.
|
|
71
|
+
// These reference `window`, `document`, `history` which don't exist in Node.
|
|
72
|
+
if (isBrowser) {
|
|
73
|
+
window.addEventListener('visibilitychange', () => {
|
|
74
|
+
if (document.visibilityState === 'hidden')
|
|
75
|
+
void flush({ useBeacon: true }).catch(() => { });
|
|
76
|
+
});
|
|
77
|
+
window.addEventListener('beforeunload', () => {
|
|
75
78
|
void flush({ useBeacon: true }).catch(() => { });
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
installAutoPageView();
|
|
85
|
-
// Fire the initial page_view for the entry URL.
|
|
86
|
-
page();
|
|
79
|
+
});
|
|
80
|
+
window.addEventListener('pagehide', () => {
|
|
81
|
+
void flush({ useBeacon: true }).catch(() => { });
|
|
82
|
+
});
|
|
83
|
+
if (c.autoPageView) {
|
|
84
|
+
installAutoPageView();
|
|
85
|
+
page();
|
|
86
|
+
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
/** Replace the persistent tag set. */
|
|
@@ -137,7 +137,7 @@ export async function flush(opts = {}) {
|
|
|
137
137
|
const batch = queue.splice(0, queue.length);
|
|
138
138
|
const payload = encodeBatch(batch);
|
|
139
139
|
try {
|
|
140
|
-
if (opts.useBeacon && navigator.sendBeacon) {
|
|
140
|
+
if (opts.useBeacon && typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
141
141
|
const blob = new Blob([payload], { type: 'application/json' });
|
|
142
142
|
const ok = navigator.sendBeacon(`${cfg.endpoint}/ev`, blob);
|
|
143
143
|
if (ok)
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA2BH,MAAM,QAAQ,GAAG;IACf,SAAS,EAAE,EAAE;IACb,eAAe,EAAE,IAAK;CACd,CAAC;AAEX,IAAI,GAES,CAAC;AACd,IAAI,KAAK,GAAmB,EAAE,CAAC;AAC/B,IAAI,qBAAqB,GAAG,KAAK,CAAC;AAElC,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,OAAO,SAAS,KAAK,WAAW,CAAC;AAEpF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,IAAI,CAAC,YAAoC;IACvD,IAAI,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/t.umd.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(a,
|
|
1
|
+
(function(a,d){typeof exports=="object"&&typeof module<"u"?d(exports):typeof define=="function"&&define.amd?define(["exports"],d):(a=typeof globalThis<"u"?globalThis:a||self,d(a.Tracker={}))})(this,function(a){"use strict";const d={batchSize:20,flushIntervalMs:5e3};let i,r=[],y=!1;const S=typeof window<"u"&&typeof navigator<"u";function v(t){if(i)return;const e=typeof t=="string"?{endpoint:t}:t;if(e.disabled)return;if(!e.endpoint){console.warn("[trackhome] init: endpoint is required");return}const n={endpoint:e.endpoint.replace(/\/$/,""),autoPageView:e.autoPageView??!0,batchSize:e.batchSize??d.batchSize,flushIntervalMs:e.flushIntervalMs??d.flushIntervalMs,tags:p(e.tags??[])};i=n,setInterval(()=>{u().catch(()=>{})},n.flushIntervalMs),S&&(window.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&u({useBeacon:!0}).catch(()=>{})}),window.addEventListener("beforeunload",()=>{u({useBeacon:!0}).catch(()=>{})}),window.addEventListener("pagehide",()=>{u({useBeacon:!0}).catch(()=>{})}),n.autoPageView&&(B(),h()))}function w(t){i&&(i.tags=p(t))}function b(t){i&&(i.tags.includes(t)||(i.tags=[...i.tags,t]))}function l(t,e=[],n){!i||!t||T(t,e,n)}function h(t,e=[]){const n=t??(typeof location<"u"?location.pathname:"");l("page_view",[n?`page:${n}`:"",...e].filter(Boolean))}function m(t,e=[]){l("identify",[`user:${t}`,...e])}async function u(t={}){if(!i||r.length===0)return;const e=r.splice(0,r.length),n=k(e);try{if(t.useBeacon&&typeof navigator<"u"&&navigator.sendBeacon){const o=new Blob([n],{type:"application/json"});if(navigator.sendBeacon(`${i.endpoint}/ev`,o))return}await fetch(`${i.endpoint}/ev`,{method:"POST",headers:{"content-type":"application/json"},credentials:"include",keepalive:!0,body:n})}catch(o){r.unshift(...e),i&&!t.useBeacon&&console.warn("[trackhome] flush failed",o)}}function T(t,e,n){const o={type:t,tags:p([...(i==null?void 0:i.tags)??[],...e])};n!==void 0&&Number.isFinite(n)&&(o.value=n),r.push(o),r.length>=((i==null?void 0:i.batchSize)??d.batchSize)&&u().catch(()=>{})}function p(t){return Array.from(new Set(t.filter(e=>typeof e=="string"&&e.length>0)))}function B(){if(y||typeof history>"u")return;y=!0;let t=typeof location<"u"?location.pathname:"";const e=()=>{if(!(i!=null&&i.autoPageView))return;const n=typeof location<"u"?location.pathname:"";n!==t&&(t=n,h())};for(const n of["pushState","replaceState"]){const o=history[n];history[n]=function(...f){const g=o.apply(this,f);return setTimeout(e,0),g}}window.addEventListener("popstate",e),window.addEventListener("hashchange",e)}function k(t){const e=[],n=new Map,o=[],f=new Map;function g(s){let c=n.get(s);return c===void 0&&(c=e.length,e.push(s),n.set(s,c)),c}function M(s){let c=f.get(s);return c===void 0&&(c=o.length,o.push(s),f.set(s,c)),c}const P=t.map(s=>{const c=g(s.type),z=s.tags.map(M),E=s.value??0;return[c,z,E]});return JSON.stringify({v:2,dict:{types:e,tags:o},rows:P})}const I={init:v,track:l,page:h,identify:m,setTags:w,addTag:b,flush:u};a.Tracker=I,a.addTag=b,a.flush=u,a.identify=m,a.init=v,a.page=h,a.setTags=w,a.track=l,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})});
|
|
2
2
|
//# sourceMappingURL=t.umd.js.map
|
package/dist/t.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"t.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * trackhome — tiny browser SDK for the trackhome analytics server.\n *\n * Simplest setup (auto-collects page views + clicks):\n *\n * import { init } from 'trackhome';\n * init('https://trk.example.com');\n *\n * That's it. The SDK fires a page_view immediately, hooks SPA navigation,\n * and flushes in the background. For custom events:\n *\n * import { track } from 'trackhome';\n * track('signup_click', ['tier:pro']);\n *\n * Tags-only model: every event is {type, tags}. The visitor is identified\n * by a server-set HttpOnly cookie (__tv), minted automatically on the first\n * /ev call. JS never touches the cookie or any visitor id.\n *\n * Persistent tags: set via init({tags}) or setTags()/addTag(); they are\n * auto-merged into every subsequent event. Per-event tags (2nd arg to track)\n * layer on top.\n */\n\nexport interface TrackerConfig {\n /** Base URL of your trackhome instance. Required. */\n endpoint: string;\n /** Tags applied to every event from this init. */\n tags?: string[];\n /**\n * Auto-fire `page_view` events on init + on SPA route changes\n * (popstate / hashchange / pushState / replaceState). Default: true.\n */\n autoPageView?: boolean;\n /** Max events per batch before a forced flush. Default: 20. */\n batchSize?: number;\n /** Max time (ms) between flushes. Default: 5000. */\n flushIntervalMs?: number;\n /** Disable SDK entirely (e.g. in dev). Default: false. */\n disabled?: boolean;\n}\n\ninterface PendingEvent {\n type: string;\n tags: string[];\n ts?: string;\n value?: number;\n}\n\nconst DEFAULTS = {\n batchSize: 20,\n flushIntervalMs: 5_000,\n} as const;\n\nlet cfg:\n | (Omit<TrackerConfig, 'tags' | 'disabled'> & { tags: string[] })\n | undefined;\nlet queue: PendingEvent[] = [];\nlet autoPageViewInstalled = false;\n\nconst isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefined';\n\n/**\n * Initialize the SDK. Two accepted shapes:\n *\n * init('https://trk.example.com') // shorthand: just the URL\n * init({ endpoint: 'https://trk.example.com' }) // full config\n *\n * Idempotent — calling init() twice is a no-op.\n *\n * With `autoPageView` (default: true), the SDK:\n * - fires a `page_view` event immediately for the current URL\n * - hooks history.pushState / replaceState + popstate + hashchange\n * - fires a `page_view` tagged `path:<current-path>` on every navigation\n *\n * That means many sites don't need to call track() at all for basic traffic\n * analytics — init() alone is enough.\n */\nexport function init(userCfgOrUrl: string | TrackerConfig): void {\n if (!isBrowser) return;\n if (cfg) return; // idempotent\n\n const userCfg: TrackerConfig =\n typeof userCfgOrUrl === 'string' ? { endpoint: userCfgOrUrl } : userCfgOrUrl;\n\n if (userCfg.disabled) return;\n if (!userCfg.endpoint) {\n // eslint-disable-next-line no-console\n console.warn('[trackhome] init: endpoint is required');\n return;\n }\n\n const c: Omit<TrackerConfig, 'tags' | 'disabled'> & { tags: string[] } = {\n endpoint: userCfg.endpoint.replace(/\\/$/, ''),\n autoPageView: userCfg.autoPageView ?? true,\n batchSize: userCfg.batchSize ?? DEFAULTS.batchSize,\n flushIntervalMs: userCfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n tags: dedupe(userCfg.tags ?? []),\n };\n cfg = c;\n\n // Background flush timer lives for the page lifetime. setInterval keeps the\n // reference; we don't need to hold onto it.\n setInterval(() => {\n void flush().catch(() => {});\n }, c.flushIntervalMs);\n\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') void flush({ useBeacon: true }).catch(() => {});\n });\n window.addEventListener('beforeunload', () => {\n void flush({ useBeacon: true }).catch(() => {});\n });\n window.addEventListener('pagehide', () => {\n void flush({ useBeacon: true }).catch(() => {});\n });\n\n if (c.autoPageView) {\n installAutoPageView();\n // Fire the initial page_view for the entry URL.\n page();\n }\n}\n\n/** Replace the persistent tag set. */\nexport function setTags(tags: string[]): void {\n if (!cfg) return;\n cfg.tags = dedupe(tags);\n}\n\n/** Add a tag to the persistent set. */\nexport function addTag(tag: string): void {\n if (!cfg) return;\n if (!cfg.tags.includes(tag)) cfg.tags = [...cfg.tags, tag];\n}\n\n/**\n * Track an event. `tags` (optional) are merged with persistent tags.\n * `value` (optional) attaches a numeric value — revenue, duration, count, etc.\n *\n * track('signup_click', ['tier:pro', 'cta:header']);\n * track('purchase', ['plan:annual'], 49.99); // value = $49.99\n * track('page_view'); // tags + value optional\n */\nexport function track(type: string, tags: string[] = [], value?: number): void {\n if (!cfg || !type) return;\n enqueue(type, tags, value);\n}\n\n/**\n * Shortcut: track('page_view', ['page:'+path]).\n *\n * With no args, uses the current path from window.location — useful for\n * manual SPA hookups when autoPageView isn't right for your router.\n *\n * page() // → page_view with page:/current/path\n * page('pricing') // → page_view with page:pricing\n * page('/blog/post-1') // → page_view with page:/blog/post-1\n */\nexport function page(name?: string, tags: string[] = []): void {\n const path = name ?? (typeof location !== 'undefined' ? location.pathname : '');\n track('page_view', [path ? `page:${path}` : '', ...tags].filter(Boolean));\n}\n\n/** Shortcut: track('identify', ['user:'+userId]). */\nexport function identify(userId: string, tags: string[] = []): void {\n track('identify', [`user:${userId}`, ...tags]);\n}\n\n/** Manually flush the pending queue. Mostly useful in tests. */\nexport async function flush(opts: { useBeacon?: boolean } = {}): Promise<void> {\n if (!cfg || queue.length === 0) return;\n const batch = queue.splice(0, queue.length);\n const payload = encodeBatch(batch);\n try {\n if (opts.useBeacon && navigator.sendBeacon) {\n const blob = new Blob([payload], { type: 'application/json' });\n const ok = navigator.sendBeacon(`${cfg.endpoint}/ev`, blob);\n if (ok) return;\n }\n await fetch(`${cfg.endpoint}/ev`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n keepalive: true,\n body: payload,\n });\n } catch (err) {\n queue.unshift(...batch);\n if (cfg && !opts.useBeacon) {\n // eslint-disable-next-line no-console\n console.warn('[trackhome] flush failed', err);\n }\n }\n}\n\n// ============================================================\n// Internal: queue + SPA page_view hook\n// ============================================================\n\nfunction enqueue(type: string, tags: string[], value?: number): void {\n const ev: PendingEvent = { type, tags: dedupe([...(cfg?.tags ?? []), ...tags]) };\n if (value !== undefined && Number.isFinite(value)) ev.value = value;\n queue.push(ev);\n if (queue.length >= (cfg?.batchSize ?? DEFAULTS.batchSize)) {\n void flush().catch(() => {});\n }\n}\n\nfunction dedupe(tags: string[]): string[] {\n return Array.from(new Set(tags.filter((t) => typeof t === 'string' && t.length > 0)));\n}\n\n/**\n * Hook SPA navigation so autoPageView fires on pushState/replaceState too.\n * Most frameworks (React Router, Vue Router, Next.js) use the History API,\n * so this catches their navigations without framework-specific glue.\n */\nfunction installAutoPageView(): void {\n if (autoPageViewInstalled || typeof history === 'undefined') return;\n autoPageViewInstalled = true;\n\n let lastPath = typeof location !== 'undefined' ? location.pathname : '';\n\n const onRouteChange = () => {\n if (!cfg?.autoPageView) return;\n const path = typeof location !== 'undefined' ? location.pathname : '';\n if (path === lastPath) return;\n lastPath = path;\n page();\n };\n\n // Wrap pushState + replaceState so we get a callback after the URL changes.\n for (const method of ['pushState', 'replaceState'] as const) {\n const original = history[method] as (\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) => void;\n history[method] = function (...args: Parameters<typeof original>) {\n const ret = original.apply(this, args);\n // Defer to let the browser update location before we read it.\n setTimeout(onRouteChange, 0);\n return ret;\n };\n }\n window.addEventListener('popstate', onRouteChange);\n window.addEventListener('hashchange', onRouteChange);\n}\n\n// ============================================================\n// Columnar dictionary-encoded wire format (protocol v2)\n// ============================================================\n//\n// Instead of sending [{type:\"page_view\",tags:[\"a\",\"b\"]},...] (repeated\n// strings/keys), the SDK builds string dictionaries once per batch and sends\n// integer indices into those dictionaries. A 500-event batch shrinks from\n// ~40 KB to ~3 KB before gzip.\n//\n// Wire shape (JSON, so it works through any proxy/CDN/sendBeacon):\n//\n// {\n// \"v\": 2,\n// \"dict\": {\n// \"types\": [\"page_view\",\"signup\",\"purchase\"],\n// \"tags\": [\"app:web\",\"variant:a\",\"key:launch\",\"tier:pro\"]\n// },\n// \"rows\": [\n// [0, [0,1,2], 0], // [typeIdx, tagIndices[], value]\n// [1, [0,1], 0],\n// [2, [0,1,3], 49.99]\n// ]\n// }\n//\n// The API auto-detects v2 (object with \"v\":2) vs v1 (array of objects) and\n// handles both transparently. The SDK always sends v2; old payloads from\n// other callers (curl, server-side) still work as v1.\n\ninterface EncodedBatch {\n v: 2;\n dict: { types: string[]; tags: string[] };\n rows: [number, number[], number][];\n}\n\nfunction encodeBatch(batch: PendingEvent[]): string {\n const typeDict: string[] = [];\n const typeMap = new Map<string, number>();\n const tagDict: string[] = [];\n const tagMap = new Map<string, number>();\n\n function typeIdx(t: string): number {\n let i = typeMap.get(t);\n if (i === undefined) {\n i = typeDict.length;\n typeDict.push(t);\n typeMap.set(t, i);\n }\n return i;\n }\n\n function tagIdx(t: string): number {\n let i = tagMap.get(t);\n if (i === undefined) {\n i = tagDict.length;\n tagDict.push(t);\n tagMap.set(t, i);\n }\n return i;\n }\n\n const rows: [number, number[], number][] = batch.map((ev) => {\n const ti = typeIdx(ev.type);\n const tagIndices = ev.tags.map(tagIdx);\n const value = ev.value ?? 0;\n return [ti, tagIndices, value];\n });\n\n const payload: EncodedBatch = {\n v: 2,\n dict: { types: typeDict, tags: tagDict },\n rows,\n };\n return JSON.stringify(payload);\n}\n\n/** Expose a global for UMD <script> users. */\nexport const Tracker = { init, track, page, identify, setTags, addTag, flush };\n"],"names":["DEFAULTS","cfg","queue","autoPageViewInstalled","isBrowser","init","userCfgOrUrl","userCfg","c","dedupe","flush","installAutoPageView","page","setTags","tags","addTag","tag","track","type","value","enqueue","name","path","identify","userId","opts","batch","payload","encodeBatch","blob","err","ev","t","lastPath","onRouteChange","method","original","args","ret","typeDict","typeMap","tagDict","tagMap","typeIdx","i","tagIdx","rows","ti","tagIndices","Tracker"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"t.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * trackhome — tiny browser SDK for the trackhome analytics server.\n *\n * Simplest setup (auto-collects page views + clicks):\n *\n * import { init } from 'trackhome';\n * init('https://trk.example.com');\n *\n * That's it. The SDK fires a page_view immediately, hooks SPA navigation,\n * and flushes in the background. For custom events:\n *\n * import { track } from 'trackhome';\n * track('signup_click', ['tier:pro']);\n *\n * Tags-only model: every event is {type, tags}. The visitor is identified\n * by a server-set HttpOnly cookie (__tv), minted automatically on the first\n * /ev call. JS never touches the cookie or any visitor id.\n *\n * Persistent tags: set via init({tags}) or setTags()/addTag(); they are\n * auto-merged into every subsequent event. Per-event tags (2nd arg to track)\n * layer on top.\n */\n\nexport interface TrackerConfig {\n /** Base URL of your trackhome instance. Required. */\n endpoint: string;\n /** Tags applied to every event from this init. */\n tags?: string[];\n /**\n * Auto-fire `page_view` events on init + on SPA route changes\n * (popstate / hashchange / pushState / replaceState). Default: true.\n */\n autoPageView?: boolean;\n /** Max events per batch before a forced flush. Default: 20. */\n batchSize?: number;\n /** Max time (ms) between flushes. Default: 5000. */\n flushIntervalMs?: number;\n /** Disable SDK entirely (e.g. in dev). Default: false. */\n disabled?: boolean;\n}\n\ninterface PendingEvent {\n type: string;\n tags: string[];\n ts?: string;\n value?: number;\n}\n\nconst DEFAULTS = {\n batchSize: 20,\n flushIntervalMs: 5_000,\n} as const;\n\nlet cfg:\n | (Omit<TrackerConfig, 'tags' | 'disabled'> & { tags: string[] })\n | undefined;\nlet queue: PendingEvent[] = [];\nlet autoPageViewInstalled = false;\n\nconst isBrowser = typeof window !== 'undefined' && typeof navigator !== 'undefined';\n\n/**\n * Initialize the SDK. Two accepted shapes:\n *\n * init('https://trk.example.com') // shorthand: just the URL\n * init({ endpoint: 'https://trk.example.com' }) // full config\n *\n * Idempotent — calling init() twice is a no-op.\n *\n * Works in BOTH browsers and Node.js backends. In a browser, autoPageView\n * hooks SPA navigation. In Node, autoPageView is ignored (no window/history).\n *\n * With `autoPageView` (default: true, browser-only), the SDK:\n * - fires a `page_view` event immediately for the current URL\n * - hooks history.pushState / replaceState + popstate + hashchange\n * - fires a `page_view` tagged `path:<current-path>` on every navigation\n */\nexport function init(userCfgOrUrl: string | TrackerConfig): void {\n if (cfg) return; // idempotent\n\n const userCfg: TrackerConfig =\n typeof userCfgOrUrl === 'string' ? { endpoint: userCfgOrUrl } : userCfgOrUrl;\n\n if (userCfg.disabled) return;\n if (!userCfg.endpoint) {\n // eslint-disable-next-line no-console\n console.warn('[trackhome] init: endpoint is required');\n return;\n }\n\n const c: Omit<TrackerConfig, 'tags' | 'disabled'> & { tags: string[] } = {\n endpoint: userCfg.endpoint.replace(/\\/$/, ''),\n autoPageView: userCfg.autoPageView ?? true,\n batchSize: userCfg.batchSize ?? DEFAULTS.batchSize,\n flushIntervalMs: userCfg.flushIntervalMs ?? DEFAULTS.flushIntervalMs,\n tags: dedupe(userCfg.tags ?? []),\n };\n cfg = c;\n\n // Background flush timer — works in both browser and Node.\n setInterval(() => {\n void flush().catch(() => {});\n }, c.flushIntervalMs);\n\n // Browser-only hooks: visibilitychange, beforeunload, pagehide, autoPageView.\n // These reference `window`, `document`, `history` which don't exist in Node.\n if (isBrowser) {\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') void flush({ useBeacon: true }).catch(() => {});\n });\n window.addEventListener('beforeunload', () => {\n void flush({ useBeacon: true }).catch(() => {});\n });\n window.addEventListener('pagehide', () => {\n void flush({ useBeacon: true }).catch(() => {});\n });\n\n if (c.autoPageView) {\n installAutoPageView();\n page();\n }\n }\n}\n\n/** Replace the persistent tag set. */\nexport function setTags(tags: string[]): void {\n if (!cfg) return;\n cfg.tags = dedupe(tags);\n}\n\n/** Add a tag to the persistent set. */\nexport function addTag(tag: string): void {\n if (!cfg) return;\n if (!cfg.tags.includes(tag)) cfg.tags = [...cfg.tags, tag];\n}\n\n/**\n * Track an event. `tags` (optional) are merged with persistent tags.\n * `value` (optional) attaches a numeric value — revenue, duration, count, etc.\n *\n * track('signup_click', ['tier:pro', 'cta:header']);\n * track('purchase', ['plan:annual'], 49.99); // value = $49.99\n * track('page_view'); // tags + value optional\n */\nexport function track(type: string, tags: string[] = [], value?: number): void {\n if (!cfg || !type) return;\n enqueue(type, tags, value);\n}\n\n/**\n * Shortcut: track('page_view', ['page:'+path]).\n *\n * With no args, uses the current path from window.location — useful for\n * manual SPA hookups when autoPageView isn't right for your router.\n *\n * page() // → page_view with page:/current/path\n * page('pricing') // → page_view with page:pricing\n * page('/blog/post-1') // → page_view with page:/blog/post-1\n */\nexport function page(name?: string, tags: string[] = []): void {\n const path = name ?? (typeof location !== 'undefined' ? location.pathname : '');\n track('page_view', [path ? `page:${path}` : '', ...tags].filter(Boolean));\n}\n\n/** Shortcut: track('identify', ['user:'+userId]). */\nexport function identify(userId: string, tags: string[] = []): void {\n track('identify', [`user:${userId}`, ...tags]);\n}\n\n/** Manually flush the pending queue. Mostly useful in tests. */\nexport async function flush(opts: { useBeacon?: boolean } = {}): Promise<void> {\n if (!cfg || queue.length === 0) return;\n const batch = queue.splice(0, queue.length);\n const payload = encodeBatch(batch);\n try {\n if (opts.useBeacon && typeof navigator !== 'undefined' && navigator.sendBeacon) {\n const blob = new Blob([payload], { type: 'application/json' });\n const ok = navigator.sendBeacon(`${cfg.endpoint}/ev`, blob);\n if (ok) return;\n }\n await fetch(`${cfg.endpoint}/ev`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n keepalive: true,\n body: payload,\n });\n } catch (err) {\n queue.unshift(...batch);\n if (cfg && !opts.useBeacon) {\n // eslint-disable-next-line no-console\n console.warn('[trackhome] flush failed', err);\n }\n }\n}\n\n// ============================================================\n// Internal: queue + SPA page_view hook\n// ============================================================\n\nfunction enqueue(type: string, tags: string[], value?: number): void {\n const ev: PendingEvent = { type, tags: dedupe([...(cfg?.tags ?? []), ...tags]) };\n if (value !== undefined && Number.isFinite(value)) ev.value = value;\n queue.push(ev);\n if (queue.length >= (cfg?.batchSize ?? DEFAULTS.batchSize)) {\n void flush().catch(() => {});\n }\n}\n\nfunction dedupe(tags: string[]): string[] {\n return Array.from(new Set(tags.filter((t) => typeof t === 'string' && t.length > 0)));\n}\n\n/**\n * Hook SPA navigation so autoPageView fires on pushState/replaceState too.\n * Most frameworks (React Router, Vue Router, Next.js) use the History API,\n * so this catches their navigations without framework-specific glue.\n */\nfunction installAutoPageView(): void {\n if (autoPageViewInstalled || typeof history === 'undefined') return;\n autoPageViewInstalled = true;\n\n let lastPath = typeof location !== 'undefined' ? location.pathname : '';\n\n const onRouteChange = () => {\n if (!cfg?.autoPageView) return;\n const path = typeof location !== 'undefined' ? location.pathname : '';\n if (path === lastPath) return;\n lastPath = path;\n page();\n };\n\n // Wrap pushState + replaceState so we get a callback after the URL changes.\n for (const method of ['pushState', 'replaceState'] as const) {\n const original = history[method] as (\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ) => void;\n history[method] = function (...args: Parameters<typeof original>) {\n const ret = original.apply(this, args);\n // Defer to let the browser update location before we read it.\n setTimeout(onRouteChange, 0);\n return ret;\n };\n }\n window.addEventListener('popstate', onRouteChange);\n window.addEventListener('hashchange', onRouteChange);\n}\n\n// ============================================================\n// Columnar dictionary-encoded wire format (protocol v2)\n// ============================================================\n//\n// Instead of sending [{type:\"page_view\",tags:[\"a\",\"b\"]},...] (repeated\n// strings/keys), the SDK builds string dictionaries once per batch and sends\n// integer indices into those dictionaries. A 500-event batch shrinks from\n// ~40 KB to ~3 KB before gzip.\n//\n// Wire shape (JSON, so it works through any proxy/CDN/sendBeacon):\n//\n// {\n// \"v\": 2,\n// \"dict\": {\n// \"types\": [\"page_view\",\"signup\",\"purchase\"],\n// \"tags\": [\"app:web\",\"variant:a\",\"key:launch\",\"tier:pro\"]\n// },\n// \"rows\": [\n// [0, [0,1,2], 0], // [typeIdx, tagIndices[], value]\n// [1, [0,1], 0],\n// [2, [0,1,3], 49.99]\n// ]\n// }\n//\n// The API auto-detects v2 (object with \"v\":2) vs v1 (array of objects) and\n// handles both transparently. The SDK always sends v2; old payloads from\n// other callers (curl, server-side) still work as v1.\n\ninterface EncodedBatch {\n v: 2;\n dict: { types: string[]; tags: string[] };\n rows: [number, number[], number][];\n}\n\nfunction encodeBatch(batch: PendingEvent[]): string {\n const typeDict: string[] = [];\n const typeMap = new Map<string, number>();\n const tagDict: string[] = [];\n const tagMap = new Map<string, number>();\n\n function typeIdx(t: string): number {\n let i = typeMap.get(t);\n if (i === undefined) {\n i = typeDict.length;\n typeDict.push(t);\n typeMap.set(t, i);\n }\n return i;\n }\n\n function tagIdx(t: string): number {\n let i = tagMap.get(t);\n if (i === undefined) {\n i = tagDict.length;\n tagDict.push(t);\n tagMap.set(t, i);\n }\n return i;\n }\n\n const rows: [number, number[], number][] = batch.map((ev) => {\n const ti = typeIdx(ev.type);\n const tagIndices = ev.tags.map(tagIdx);\n const value = ev.value ?? 0;\n return [ti, tagIndices, value];\n });\n\n const payload: EncodedBatch = {\n v: 2,\n dict: { types: typeDict, tags: tagDict },\n rows,\n };\n return JSON.stringify(payload);\n}\n\n/** Expose a global for UMD <script> users. */\nexport const Tracker = { init, track, page, identify, setTags, addTag, flush };\n"],"names":["DEFAULTS","cfg","queue","autoPageViewInstalled","isBrowser","init","userCfgOrUrl","userCfg","c","dedupe","flush","installAutoPageView","page","setTags","tags","addTag","tag","track","type","value","enqueue","name","path","identify","userId","opts","batch","payload","encodeBatch","blob","err","ev","t","lastPath","onRouteChange","method","original","args","ret","typeDict","typeMap","tagDict","tagMap","typeIdx","i","tagIdx","rows","ti","tagIndices","Tracker"],"mappings":"+NAgDA,MAAMA,EAAW,CACf,UAAW,GACX,gBAAiB,GACnB,EAEA,IAAIC,EAGAC,EAAwB,CAAA,EACxBC,EAAwB,GAE5B,MAAMC,EAAY,OAAO,OAAW,KAAe,OAAO,UAAc,IAkBjE,SAASC,EAAKC,EAA4C,CAC/D,GAAIL,EAAK,OAET,MAAMM,EACJ,OAAOD,GAAiB,SAAW,CAAE,SAAUA,GAAiBA,EAElE,GAAIC,EAAQ,SAAU,OACtB,GAAI,CAACA,EAAQ,SAAU,CAErB,QAAQ,KAAK,wCAAwC,EACrD,MACF,CAEA,MAAMC,EAAmE,CACvE,SAAUD,EAAQ,SAAS,QAAQ,MAAO,EAAE,EAC5C,aAAcA,EAAQ,cAAgB,GACtC,UAAWA,EAAQ,WAAaP,EAAS,UACzC,gBAAiBO,EAAQ,iBAAmBP,EAAS,gBACrD,KAAMS,EAAOF,EAAQ,MAAQ,CAAA,CAAE,CAAA,EAEjCN,EAAMO,EAGN,YAAY,IAAM,CACXE,EAAA,EAAQ,MAAM,IAAM,CAAC,CAAC,CAC7B,EAAGF,EAAE,eAAe,EAIhBJ,IACF,OAAO,iBAAiB,mBAAoB,IAAM,CAC5C,SAAS,kBAAoB,UAAeM,EAAM,CAAE,UAAW,EAAA,CAAM,EAAE,MAAM,IAAM,CAAC,CAAC,CAC3F,CAAC,EACD,OAAO,iBAAiB,eAAgB,IAAM,CACvCA,EAAM,CAAE,UAAW,GAAM,EAAE,MAAM,IAAM,CAAC,CAAC,CAChD,CAAC,EACD,OAAO,iBAAiB,WAAY,IAAM,CACnCA,EAAM,CAAE,UAAW,GAAM,EAAE,MAAM,IAAM,CAAC,CAAC,CAChD,CAAC,EAEGF,EAAE,eACJG,EAAA,EACAC,EAAA,GAGN,CAGO,SAASC,EAAQC,EAAsB,CACvCb,IACLA,EAAI,KAAOQ,EAAOK,CAAI,EACxB,CAGO,SAASC,EAAOC,EAAmB,CACnCf,IACAA,EAAI,KAAK,SAASe,CAAG,IAAGf,EAAI,KAAO,CAAC,GAAGA,EAAI,KAAMe,CAAG,GAC3D,CAUO,SAASC,EAAMC,EAAcJ,EAAiB,CAAA,EAAIK,EAAsB,CACzE,CAAClB,GAAO,CAACiB,GACbE,EAAQF,EAAMJ,EAAMK,CAAK,CAC3B,CAYO,SAASP,EAAKS,EAAeP,EAAiB,GAAU,CAC7D,MAAMQ,EAAOD,IAAS,OAAO,SAAa,IAAc,SAAS,SAAW,IAC5EJ,EAAM,YAAa,CAACK,EAAO,QAAQA,CAAI,GAAK,GAAI,GAAGR,CAAI,EAAE,OAAO,OAAO,CAAC,CAC1E,CAGO,SAASS,EAASC,EAAgBV,EAAiB,GAAU,CAClEG,EAAM,WAAY,CAAC,QAAQO,CAAM,GAAI,GAAGV,CAAI,CAAC,CAC/C,CAGA,eAAsBJ,EAAMe,EAAgC,GAAmB,CAC7E,GAAI,CAACxB,GAAOC,EAAM,SAAW,EAAG,OAChC,MAAMwB,EAAQxB,EAAM,OAAO,EAAGA,EAAM,MAAM,EACpCyB,EAAUC,EAAYF,CAAK,EACjC,GAAI,CACF,GAAID,EAAK,WAAa,OAAO,UAAc,KAAe,UAAU,WAAY,CAC9E,MAAMI,EAAO,IAAI,KAAK,CAACF,CAAO,EAAG,CAAE,KAAM,mBAAoB,EAE7D,GADW,UAAU,WAAW,GAAG1B,EAAI,QAAQ,MAAO4B,CAAI,EAClD,MACV,CACA,MAAM,MAAM,GAAG5B,EAAI,QAAQ,MAAO,CAChC,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,YAAa,UACb,UAAW,GACX,KAAM0B,CAAA,CACP,CACH,OAASG,EAAK,CACZ5B,EAAM,QAAQ,GAAGwB,CAAK,EAClBzB,GAAO,CAACwB,EAAK,WAEf,QAAQ,KAAK,2BAA4BK,CAAG,CAEhD,CACF,CAMA,SAASV,EAAQF,EAAcJ,EAAgBK,EAAsB,CACnE,MAAMY,EAAmB,CAAE,KAAAb,EAAM,KAAMT,EAAO,CAAC,IAAIR,GAAA,YAAAA,EAAK,OAAQ,CAAA,EAAK,GAAGa,CAAI,CAAC,CAAA,EACzEK,IAAU,QAAa,OAAO,SAASA,CAAK,MAAM,MAAQA,GAC9DjB,EAAM,KAAK6B,CAAE,EACT7B,EAAM,UAAWD,GAAA,YAAAA,EAAK,YAAaD,EAAS,YACzCU,EAAA,EAAQ,MAAM,IAAM,CAAC,CAAC,CAE/B,CAEA,SAASD,EAAOK,EAA0B,CACxC,OAAO,MAAM,KAAK,IAAI,IAAIA,EAAK,OAAQkB,GAAM,OAAOA,GAAM,UAAYA,EAAE,OAAS,CAAC,CAAC,CAAC,CACtF,CAOA,SAASrB,GAA4B,CACnC,GAAIR,GAAyB,OAAO,QAAY,IAAa,OAC7DA,EAAwB,GAExB,IAAI8B,EAAW,OAAO,SAAa,IAAc,SAAS,SAAW,GAErE,MAAMC,EAAgB,IAAM,CAC1B,GAAI,EAACjC,GAAA,MAAAA,EAAK,cAAc,OACxB,MAAMqB,EAAO,OAAO,SAAa,IAAc,SAAS,SAAW,GAC/DA,IAASW,IACbA,EAAWX,EACXV,EAAA,EACF,EAGA,UAAWuB,IAAU,CAAC,YAAa,cAAc,EAAY,CAC3D,MAAMC,EAAW,QAAQD,CAAM,EAK/B,QAAQA,CAAM,EAAI,YAAaE,EAAmC,CAChE,MAAMC,EAAMF,EAAS,MAAM,KAAMC,CAAI,EAErC,kBAAWH,EAAe,CAAC,EACpBI,CACT,CACF,CACA,OAAO,iBAAiB,WAAYJ,CAAa,EACjD,OAAO,iBAAiB,aAAcA,CAAa,CACrD,CAoCA,SAASN,EAAYF,EAA+B,CAClD,MAAMa,EAAqB,CAAA,EACrBC,MAAc,IACdC,EAAoB,CAAA,EACpBC,MAAa,IAEnB,SAASC,EAAQX,EAAmB,CAClC,IAAIY,EAAIJ,EAAQ,IAAIR,CAAC,EACrB,OAAIY,IAAM,SACRA,EAAIL,EAAS,OACbA,EAAS,KAAKP,CAAC,EACfQ,EAAQ,IAAIR,EAAGY,CAAC,GAEXA,CACT,CAEA,SAASC,EAAOb,EAAmB,CACjC,IAAIY,EAAIF,EAAO,IAAIV,CAAC,EACpB,OAAIY,IAAM,SACRA,EAAIH,EAAQ,OACZA,EAAQ,KAAKT,CAAC,EACdU,EAAO,IAAIV,EAAGY,CAAC,GAEVA,CACT,CAEA,MAAME,EAAqCpB,EAAM,IAAKK,GAAO,CAC3D,MAAMgB,EAAKJ,EAAQZ,EAAG,IAAI,EACpBiB,EAAajB,EAAG,KAAK,IAAIc,CAAM,EAC/B1B,EAAQY,EAAG,OAAS,EAC1B,MAAO,CAACgB,EAAIC,EAAY7B,CAAK,CAC/B,CAAC,EAOD,OAAO,KAAK,UALkB,CAC5B,EAAG,EACH,KAAM,CAAE,MAAOoB,EAAU,KAAME,CAAA,EAC/B,KAAAK,CAAA,CAE2B,CAC/B,CAGO,MAAMG,EAAU,CAAE,KAAA5C,EAAM,MAAAY,EAAO,KAAAL,EAAM,SAAAW,EAAU,QAAAV,EAAS,OAAAE,EAAQ,MAAAL,CAAA"}
|