@siteflow/analytics 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @siteflow/analytics
2
+
3
+ Client SDK för Siteflow Stats. Skickar pageviews, custom events och identify till `stat.siteflow.se`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @siteflow/analytics
9
+ ```
10
+
11
+ ## Quick start (vanilla)
12
+
13
+ ```ts
14
+ import { init, track, identify, pageview } from '@siteflow/analytics'
15
+
16
+ init({ siteKey: 'sft_abc...' })
17
+ pageview()
18
+ track('cta_click', { cta: 'hero' })
19
+ identify('user-123', { email: 'a@b.se' })
20
+ ```
21
+
22
+ ## Quick start (React / Next.js)
23
+
24
+ ```tsx
25
+ import { SiteflowProvider, useTrack } from '@siteflow/analytics/react'
26
+
27
+ // app/layout.tsx
28
+ <SiteflowProvider siteKey={process.env.NEXT_PUBLIC_SITEFLOW_KEY!}>
29
+ {children}
30
+ </SiteflowProvider>
31
+
32
+ // component
33
+ const track = useTrack()
34
+ <button onClick={() => track('cta_click', { cta: 'hero' })}>Boka</button>
35
+ ```
36
+
37
+ ## Auto-tracking via attribut
38
+
39
+ Element med `data-sf-event` triggar `track()` automatiskt:
40
+
41
+ ```html
42
+ <button data-sf-event="cta_click" data-sf-cta="hero" data-sf-plan="pro">Boka</button>
43
+ ```
44
+
45
+ ## API
46
+
47
+ - `init({ siteKey, host?, debug? })` — initialisera SDK:n
48
+ - `track(name, props?)` — skicka custom event
49
+ - `identify(distinctId, traits?)` — koppla anonym besökare till känd användare
50
+ - `pageview(url?)` — manuell pageview
51
+ - `reset()` — logout, rensa cookie
52
+
53
+ ## GDPR
54
+
55
+ Du ansvarar själv för att inhämta samtycke från besökaren innan du anropar `identify()` med personuppgifter.
56
+
57
+ ## License
58
+
59
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var l=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var O=Object.prototype.hasOwnProperty;var A=(e,t)=>{for(var n in t)l(e,n,{get:t[n],enumerable:!0})},U=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of S(t))!O.call(e,r)&&r!==n&&l(e,r,{get:()=>t[r],enumerable:!(i=T(t,r))||i.enumerable});return e};var C=e=>U(l({},"__esModule",{value:!0}),e);var q={};A(q,{identify:()=>R,init:()=>b,pageview:()=>k,reset:()=>_,track:()=>h});module.exports=C(q);var f="__sf_did";function K(){return crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function $(){let e=new RegExp(`(?:^|; )${f}=([^;]*)`,"g"),t=null,n;for(;(n=e.exec(document.cookie))!==null;){let i=decodeURIComponent(n[1]);i&&(t=i)}return t}function v(e){document.cookie=`${f}=${encodeURIComponent(e)}; Max-Age=31536000; Path=/; SameSite=Lax${location.protocol==="https:"?"; Secure":""}`}function x(){let e=$();if(e)return e;let t=K();return v(t),t}function m(e){v(e)}function w(){document.cookie=`${f}=; Max-Age=0; Path=/`}var u="__sf_queue";var d=class{constructor(t){this.cfg=t;this.buffer=[];this.pendingIdentify=null;this.timer=null;this.getDistinctId=()=>"";this.flushQueued(),typeof window<"u"&&window.addEventListener("pagehide",()=>this.flushBeacon())}bindDistinctId(t){this.getDistinctId=t}enqueue(t){this.buffer.push({...t,ts:t.ts??Date.now()}),this.buffer.length>=10?this.flush().catch(()=>{}):this.schedule()}setIdentify(t){this.pendingIdentify=t,this.schedule()}schedule(){this.timer||(this.timer=setTimeout(()=>this.flush().catch(()=>{}),1e3))}async flush(){if(this.timer&&(clearTimeout(this.timer),this.timer=null),this.buffer.length===0&&!this.pendingIdentify)return;let t={site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer,identify:this.pendingIdentify},n=this.buffer;this.buffer=[],this.pendingIdentify=null;try{await this.sendWithRetry(t)}catch{this.queueLocal(n)}}async sendWithRetry(t,n=1){try{let i=await fetch(`${this.cfg.host}/api/collect/event`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t),keepalive:!0});if(!i.ok&&i.status>=500&&n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(t,n+1)}catch(i){if(n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(t,n+1);throw i}}flushBeacon(){if(this.buffer.length===0)return;let t=JSON.stringify({site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer});navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`,t),this.buffer=[]}queueLocal(t){try{let i=[...JSON.parse(localStorage.getItem(u)||"[]"),...t].slice(-50);localStorage.setItem(u,JSON.stringify(i))}catch{}}flushQueued(){try{let t=JSON.parse(localStorage.getItem(u)||"[]");if(t.length===0)return;localStorage.removeItem(u),t.forEach(n=>this.buffer.push(n)),this.schedule()}catch{}}};var s=null;function E(e){s||(s=t=>{let n=t.target;if(!n)return;let i=n.closest("[data-sf-event]");if(!i)return;let r=i.dataset.sfEvent,p={};for(let[a,P]of Object.entries(i.dataset)){if(a==="sfEvent"||!a.startsWith("sf"))continue;let y=a.slice(2),D=y.charAt(0).toLowerCase()+y.slice(1);p[D]=P}e(r,p)},document.addEventListener("click",s,{capture:!0}))}function I(){s&&(document.removeEventListener("click",s,{capture:!0}),s=null)}var o=null,c=null;function b(e){c={siteKey:e.siteKey,host:e.host??"https://stat.siteflow.se",debug:e.debug??!1},o=new d({host:c.host,siteKey:c.siteKey}),o.bindDistinctId(()=>x()),E((t,n)=>h(t,n))}function h(e,t={}){if(!o){g("track called before init");return}o.enqueue({name:e,props:t,url:typeof location<"u"?location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0})}function k(e){if(!o){g("pageview called before init");return}o.enqueue({name:"$pageview",props:{},url:e??(typeof location<"u"?location.href:void 0),referrer:typeof document<"u"?document.referrer:void 0})}function R(e,t={}){if(!o){g("identify called before init");return}m(e),o.setIdentify({distinct_id:e,traits:t})}function _(){w(),I(),o=null,c=null}function g(e){c?.debug&&console.warn(`[siteflow] ${e}`)}0&&(module.exports={identify,init,pageview,reset,track});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/cookie.ts","../src/transport.ts","../src/auto.ts","../src/core.ts"],"sourcesContent":["export { init, track, identify, pageview, reset } from './core'\n","const COOKIE = '__sf_did'\nconst ONE_YEAR = 60 * 60 * 24 * 365\n\nfunction uuid(): string {\n if (crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction read(): string | null {\n // Find the last (most recently set) non-empty value among all cookies with this name\n const re = new RegExp(`(?:^|; )${COOKIE}=([^;]*)`, 'g')\n let last: string | null = null\n let m: RegExpExecArray | null\n while ((m = re.exec(document.cookie)) !== null) {\n const val = decodeURIComponent(m[1])\n if (val) last = val\n }\n return last\n}\n\nfunction write(value: string): void {\n document.cookie = `${COOKIE}=${encodeURIComponent(value)}; Max-Age=${ONE_YEAR}; Path=/; SameSite=Lax${location.protocol === 'https:' ? '; Secure' : ''}`\n}\n\nexport function getOrCreateDistinctId(): string {\n const existing = read()\n if (existing) return existing\n const fresh = uuid()\n write(fresh)\n return fresh\n}\n\nexport function setDistinctId(id: string): void { write(id) }\nexport function clearDistinctId(): void { document.cookie = `${COOKIE}=; Max-Age=0; Path=/` }\n","type EventPayload = {\n name: string\n props: Record<string, unknown>\n url?: string\n referrer?: string\n ts?: number\n}\n\ntype IdentifyPayload = {\n distinct_id: string\n traits: Record<string, unknown>\n}\n\ntype Config = { host: string; siteKey: string }\n\nconst QUEUE_KEY = '__sf_queue'\nconst MAX_QUEUE = 50\n\nexport class Transport {\n private buffer: EventPayload[] = []\n private pendingIdentify: IdentifyPayload | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private getDistinctId: () => string = () => ''\n\n constructor(private cfg: Config) {\n this.flushQueued()\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => this.flushBeacon())\n }\n }\n\n bindDistinctId(fn: () => string): void {\n this.getDistinctId = fn\n }\n\n enqueue(event: EventPayload): void {\n this.buffer.push({ ...event, ts: event.ts ?? Date.now() })\n if (this.buffer.length >= 10) {\n this.flush().catch(() => {})\n } else {\n this.schedule()\n }\n }\n\n setIdentify(p: IdentifyPayload): void {\n this.pendingIdentify = p\n this.schedule()\n }\n\n private schedule(): void {\n if (this.timer) return\n this.timer = setTimeout(() => this.flush().catch(() => {}), 1000)\n }\n\n async flush(): Promise<void> {\n if (this.timer) { clearTimeout(this.timer); this.timer = null }\n if (this.buffer.length === 0 && !this.pendingIdentify) return\n\n const body = {\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer,\n identify: this.pendingIdentify\n }\n const events = this.buffer\n this.buffer = []\n this.pendingIdentify = null\n\n try {\n await this.sendWithRetry(body)\n } catch {\n this.queueLocal(events)\n }\n }\n\n private async sendWithRetry(body: unknown, attempt = 1): Promise<void> {\n try {\n const res = await fetch(`${this.cfg.host}/api/collect/event`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n keepalive: true\n })\n if (!res.ok && res.status >= 500 && attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n } catch (err) {\n if (attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n throw err\n }\n }\n\n private flushBeacon(): void {\n if (this.buffer.length === 0) return\n const body = JSON.stringify({\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer\n })\n navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`, body)\n this.buffer = []\n }\n\n private queueLocal(events: EventPayload[]): void {\n try {\n const cur: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n const merged = [...cur, ...events].slice(-MAX_QUEUE)\n localStorage.setItem(QUEUE_KEY, JSON.stringify(merged))\n } catch {}\n }\n\n private flushQueued(): void {\n try {\n const queued: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n if (queued.length === 0) return\n localStorage.removeItem(QUEUE_KEY)\n queued.forEach(e => this.buffer.push(e))\n this.schedule()\n } catch {}\n }\n}\n","type TrackFn = (name: string, props: Record<string, unknown>) => void\n\nlet listener: ((e: Event) => void) | null = null\n\nexport function installAutoTracking(track: TrackFn): void {\n if (listener) return\n listener = (e: Event) => {\n const target = e.target as Element | null\n if (!target) return\n const el = target.closest('[data-sf-event]') as HTMLElement | null\n if (!el) return\n const name = el.dataset.sfEvent!\n const props: Record<string, unknown> = {}\n // Note: el.dataset already gives camelCased keys for kebab-cased data-* attrs.\n // data-sf-cta -> sfCta; data-sf-plan-id -> sfPlanId.\n // We strip the \"sf\" prefix and lowercase the first letter.\n for (const [k, v] of Object.entries(el.dataset)) {\n if (k === 'sfEvent' || !k.startsWith('sf')) continue\n const stripped = k.slice(2)\n const camel = stripped.charAt(0).toLowerCase() + stripped.slice(1)\n props[camel] = v\n }\n track(name, props)\n }\n document.addEventListener('click', listener, { capture: true })\n}\n\nexport function uninstallAutoTracking(): void {\n if (!listener) return\n document.removeEventListener('click', listener, { capture: true })\n listener = null\n}\n","import { getOrCreateDistinctId, setDistinctId, clearDistinctId } from './cookie'\nimport { Transport } from './transport'\nimport { installAutoTracking, uninstallAutoTracking } from './auto'\n\ntype Config = { siteKey: string; host?: string; debug?: boolean }\n\nlet transport: Transport | null = null\nlet cfg: Required<Config> | null = null\n\nexport function init(c: Config): void {\n cfg = { siteKey: c.siteKey, host: c.host ?? 'https://stat.siteflow.se', debug: c.debug ?? false }\n transport = new Transport({ host: cfg.host, siteKey: cfg.siteKey })\n transport.bindDistinctId(() => getOrCreateDistinctId())\n installAutoTracking((name, props) => track(name, props))\n}\n\nexport function track(name: string, props: Record<string, unknown> = {}): void {\n if (!transport) { warn('track called before init'); return }\n transport.enqueue({\n name, props,\n url: typeof location !== 'undefined' ? location.href : undefined,\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function pageview(url?: string): void {\n if (!transport) { warn('pageview called before init'); return }\n transport.enqueue({\n name: '$pageview', props: {},\n url: url ?? (typeof location !== 'undefined' ? location.href : undefined),\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function identify(distinctId: string, traits: Record<string, unknown> = {}): void {\n if (!transport) { warn('identify called before init'); return }\n setDistinctId(distinctId)\n transport.setIdentify({ distinct_id: distinctId, traits })\n}\n\nexport function reset(): void {\n clearDistinctId()\n uninstallAutoTracking()\n transport = null\n cfg = null\n}\n\nfunction warn(msg: string): void {\n if (cfg?.debug) console.warn(`[siteflow] ${msg}`)\n}\n\nexport async function __test_flush(): Promise<void> {\n await transport?.flush()\n}\n"],"mappings":"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","names":["src_exports","__export","identify","init","pageview","reset","track","__toCommonJS","COOKIE","uuid","c","r","read","re","COOKIE","last","m","val","write","value","getOrCreateDistinctId","existing","fresh","setDistinctId","id","clearDistinctId","QUEUE_KEY","Transport","cfg","fn","event","p","body","events","attempt","res","err","merged","QUEUE_KEY","queued","e","listener","installAutoTracking","track","e","target","el","name","props","k","v","stripped","camel","uninstallAutoTracking","transport","cfg","init","c","Transport","getOrCreateDistinctId","installAutoTracking","name","props","track","warn","pageview","url","identify","distinctId","traits","setDistinctId","reset","clearDistinctId","uninstallAutoTracking","msg"]}
@@ -0,0 +1,12 @@
1
+ type Config = {
2
+ siteKey: string;
3
+ host?: string;
4
+ debug?: boolean;
5
+ };
6
+ declare function init(c: Config): void;
7
+ declare function track(name: string, props?: Record<string, unknown>): void;
8
+ declare function pageview(url?: string): void;
9
+ declare function identify(distinctId: string, traits?: Record<string, unknown>): void;
10
+ declare function reset(): void;
11
+
12
+ export { identify, init, pageview, reset, track };
@@ -0,0 +1,12 @@
1
+ type Config = {
2
+ siteKey: string;
3
+ host?: string;
4
+ debug?: boolean;
5
+ };
6
+ declare function init(c: Config): void;
7
+ declare function track(name: string, props?: Record<string, unknown>): void;
8
+ declare function pageview(url?: string): void;
9
+ declare function identify(distinctId: string, traits?: Record<string, unknown>): void;
10
+ declare function reset(): void;
11
+
12
+ export { identify, init, pageview, reset, track };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var l="__sf_did";function k(){return crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{let t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}function R(){let e=new RegExp(`(?:^|; )${l}=([^;]*)`,"g"),t=null,n;for(;(n=e.exec(document.cookie))!==null;){let i=decodeURIComponent(n[1]);i&&(t=i)}return t}function p(e){document.cookie=`${l}=${encodeURIComponent(e)}; Max-Age=31536000; Path=/; SameSite=Lax${location.protocol==="https:"?"; Secure":""}`}function y(){let e=R();if(e)return e;let t=k();return p(t),t}function v(e){p(e)}function x(){document.cookie=`${l}=; Max-Age=0; Path=/`}var u="__sf_queue";var d=class{constructor(t){this.cfg=t;this.buffer=[];this.pendingIdentify=null;this.timer=null;this.getDistinctId=()=>"";this.flushQueued(),typeof window<"u"&&window.addEventListener("pagehide",()=>this.flushBeacon())}bindDistinctId(t){this.getDistinctId=t}enqueue(t){this.buffer.push({...t,ts:t.ts??Date.now()}),this.buffer.length>=10?this.flush().catch(()=>{}):this.schedule()}setIdentify(t){this.pendingIdentify=t,this.schedule()}schedule(){this.timer||(this.timer=setTimeout(()=>this.flush().catch(()=>{}),1e3))}async flush(){if(this.timer&&(clearTimeout(this.timer),this.timer=null),this.buffer.length===0&&!this.pendingIdentify)return;let t={site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer,identify:this.pendingIdentify},n=this.buffer;this.buffer=[],this.pendingIdentify=null;try{await this.sendWithRetry(t)}catch{this.queueLocal(n)}}async sendWithRetry(t,n=1){try{let i=await fetch(`${this.cfg.host}/api/collect/event`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(t),keepalive:!0});if(!i.ok&&i.status>=500&&n<3)return await new Promise(s=>setTimeout(s,n*n*100)),this.sendWithRetry(t,n+1)}catch(i){if(n<3)return await new Promise(s=>setTimeout(s,n*n*100)),this.sendWithRetry(t,n+1);throw i}}flushBeacon(){if(this.buffer.length===0)return;let t=JSON.stringify({site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer});navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`,t),this.buffer=[]}queueLocal(t){try{let i=[...JSON.parse(localStorage.getItem(u)||"[]"),...t].slice(-50);localStorage.setItem(u,JSON.stringify(i))}catch{}}flushQueued(){try{let t=JSON.parse(localStorage.getItem(u)||"[]");if(t.length===0)return;localStorage.removeItem(u),t.forEach(n=>this.buffer.push(n)),this.schedule()}catch{}}};var o=null;function m(e){o||(o=t=>{let n=t.target;if(!n)return;let i=n.closest("[data-sf-event]");if(!i)return;let s=i.dataset.sfEvent,h={};for(let[a,I]of Object.entries(i.dataset)){if(a==="sfEvent"||!a.startsWith("sf"))continue;let g=a.slice(2),b=g.charAt(0).toLowerCase()+g.slice(1);h[b]=I}e(s,h)},document.addEventListener("click",o,{capture:!0}))}function w(){o&&(document.removeEventListener("click",o,{capture:!0}),o=null)}var r=null,c=null;function _(e){c={siteKey:e.siteKey,host:e.host??"https://stat.siteflow.se",debug:e.debug??!1},r=new d({host:c.host,siteKey:c.siteKey}),r.bindDistinctId(()=>y()),m((t,n)=>E(t,n))}function E(e,t={}){if(!r){f("track called before init");return}r.enqueue({name:e,props:t,url:typeof location<"u"?location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0})}function P(e){if(!r){f("pageview called before init");return}r.enqueue({name:"$pageview",props:{},url:e??(typeof location<"u"?location.href:void 0),referrer:typeof document<"u"?document.referrer:void 0})}function D(e,t={}){if(!r){f("identify called before init");return}v(e),r.setIdentify({distinct_id:e,traits:t})}function T(){x(),w(),r=null,c=null}function f(e){c?.debug&&console.warn(`[siteflow] ${e}`)}export{D as identify,_ as init,P as pageview,T as reset,E as track};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cookie.ts","../src/transport.ts","../src/auto.ts","../src/core.ts"],"sourcesContent":["const COOKIE = '__sf_did'\nconst ONE_YEAR = 60 * 60 * 24 * 365\n\nfunction uuid(): string {\n if (crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction read(): string | null {\n // Find the last (most recently set) non-empty value among all cookies with this name\n const re = new RegExp(`(?:^|; )${COOKIE}=([^;]*)`, 'g')\n let last: string | null = null\n let m: RegExpExecArray | null\n while ((m = re.exec(document.cookie)) !== null) {\n const val = decodeURIComponent(m[1])\n if (val) last = val\n }\n return last\n}\n\nfunction write(value: string): void {\n document.cookie = `${COOKIE}=${encodeURIComponent(value)}; Max-Age=${ONE_YEAR}; Path=/; SameSite=Lax${location.protocol === 'https:' ? '; Secure' : ''}`\n}\n\nexport function getOrCreateDistinctId(): string {\n const existing = read()\n if (existing) return existing\n const fresh = uuid()\n write(fresh)\n return fresh\n}\n\nexport function setDistinctId(id: string): void { write(id) }\nexport function clearDistinctId(): void { document.cookie = `${COOKIE}=; Max-Age=0; Path=/` }\n","type EventPayload = {\n name: string\n props: Record<string, unknown>\n url?: string\n referrer?: string\n ts?: number\n}\n\ntype IdentifyPayload = {\n distinct_id: string\n traits: Record<string, unknown>\n}\n\ntype Config = { host: string; siteKey: string }\n\nconst QUEUE_KEY = '__sf_queue'\nconst MAX_QUEUE = 50\n\nexport class Transport {\n private buffer: EventPayload[] = []\n private pendingIdentify: IdentifyPayload | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private getDistinctId: () => string = () => ''\n\n constructor(private cfg: Config) {\n this.flushQueued()\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => this.flushBeacon())\n }\n }\n\n bindDistinctId(fn: () => string): void {\n this.getDistinctId = fn\n }\n\n enqueue(event: EventPayload): void {\n this.buffer.push({ ...event, ts: event.ts ?? Date.now() })\n if (this.buffer.length >= 10) {\n this.flush().catch(() => {})\n } else {\n this.schedule()\n }\n }\n\n setIdentify(p: IdentifyPayload): void {\n this.pendingIdentify = p\n this.schedule()\n }\n\n private schedule(): void {\n if (this.timer) return\n this.timer = setTimeout(() => this.flush().catch(() => {}), 1000)\n }\n\n async flush(): Promise<void> {\n if (this.timer) { clearTimeout(this.timer); this.timer = null }\n if (this.buffer.length === 0 && !this.pendingIdentify) return\n\n const body = {\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer,\n identify: this.pendingIdentify\n }\n const events = this.buffer\n this.buffer = []\n this.pendingIdentify = null\n\n try {\n await this.sendWithRetry(body)\n } catch {\n this.queueLocal(events)\n }\n }\n\n private async sendWithRetry(body: unknown, attempt = 1): Promise<void> {\n try {\n const res = await fetch(`${this.cfg.host}/api/collect/event`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n keepalive: true\n })\n if (!res.ok && res.status >= 500 && attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n } catch (err) {\n if (attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n throw err\n }\n }\n\n private flushBeacon(): void {\n if (this.buffer.length === 0) return\n const body = JSON.stringify({\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer\n })\n navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`, body)\n this.buffer = []\n }\n\n private queueLocal(events: EventPayload[]): void {\n try {\n const cur: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n const merged = [...cur, ...events].slice(-MAX_QUEUE)\n localStorage.setItem(QUEUE_KEY, JSON.stringify(merged))\n } catch {}\n }\n\n private flushQueued(): void {\n try {\n const queued: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n if (queued.length === 0) return\n localStorage.removeItem(QUEUE_KEY)\n queued.forEach(e => this.buffer.push(e))\n this.schedule()\n } catch {}\n }\n}\n","type TrackFn = (name: string, props: Record<string, unknown>) => void\n\nlet listener: ((e: Event) => void) | null = null\n\nexport function installAutoTracking(track: TrackFn): void {\n if (listener) return\n listener = (e: Event) => {\n const target = e.target as Element | null\n if (!target) return\n const el = target.closest('[data-sf-event]') as HTMLElement | null\n if (!el) return\n const name = el.dataset.sfEvent!\n const props: Record<string, unknown> = {}\n // Note: el.dataset already gives camelCased keys for kebab-cased data-* attrs.\n // data-sf-cta -> sfCta; data-sf-plan-id -> sfPlanId.\n // We strip the \"sf\" prefix and lowercase the first letter.\n for (const [k, v] of Object.entries(el.dataset)) {\n if (k === 'sfEvent' || !k.startsWith('sf')) continue\n const stripped = k.slice(2)\n const camel = stripped.charAt(0).toLowerCase() + stripped.slice(1)\n props[camel] = v\n }\n track(name, props)\n }\n document.addEventListener('click', listener, { capture: true })\n}\n\nexport function uninstallAutoTracking(): void {\n if (!listener) return\n document.removeEventListener('click', listener, { capture: true })\n listener = null\n}\n","import { getOrCreateDistinctId, setDistinctId, clearDistinctId } from './cookie'\nimport { Transport } from './transport'\nimport { installAutoTracking, uninstallAutoTracking } from './auto'\n\ntype Config = { siteKey: string; host?: string; debug?: boolean }\n\nlet transport: Transport | null = null\nlet cfg: Required<Config> | null = null\n\nexport function init(c: Config): void {\n cfg = { siteKey: c.siteKey, host: c.host ?? 'https://stat.siteflow.se', debug: c.debug ?? false }\n transport = new Transport({ host: cfg.host, siteKey: cfg.siteKey })\n transport.bindDistinctId(() => getOrCreateDistinctId())\n installAutoTracking((name, props) => track(name, props))\n}\n\nexport function track(name: string, props: Record<string, unknown> = {}): void {\n if (!transport) { warn('track called before init'); return }\n transport.enqueue({\n name, props,\n url: typeof location !== 'undefined' ? location.href : undefined,\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function pageview(url?: string): void {\n if (!transport) { warn('pageview called before init'); return }\n transport.enqueue({\n name: '$pageview', props: {},\n url: url ?? (typeof location !== 'undefined' ? location.href : undefined),\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function identify(distinctId: string, traits: Record<string, unknown> = {}): void {\n if (!transport) { warn('identify called before init'); return }\n setDistinctId(distinctId)\n transport.setIdentify({ distinct_id: distinctId, traits })\n}\n\nexport function reset(): void {\n clearDistinctId()\n uninstallAutoTracking()\n transport = null\n cfg = null\n}\n\nfunction warn(msg: string): void {\n if (cfg?.debug) console.warn(`[siteflow] ${msg}`)\n}\n\nexport async function __test_flush(): Promise<void> {\n await transport?.flush()\n}\n"],"mappings":"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","names":["COOKIE","uuid","c","r","read","re","COOKIE","last","m","val","write","value","getOrCreateDistinctId","existing","fresh","setDistinctId","id","clearDistinctId","QUEUE_KEY","Transport","cfg","fn","event","p","body","events","attempt","res","r","err","merged","QUEUE_KEY","queued","e","listener","installAutoTracking","track","e","target","el","name","props","k","v","stripped","camel","uninstallAutoTracking","transport","cfg","init","c","Transport","getOrCreateDistinctId","installAutoTracking","name","props","track","warn","pageview","url","identify","distinctId","traits","setDistinctId","reset","clearDistinctId","uninstallAutoTracking","msg"]}
@@ -0,0 +1,2 @@
1
+ "use strict";var f=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var O=(t,e)=>{for(var n in e)f(t,n,{get:e[n],enumerable:!0})},A=(t,e,n,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of S(e))!D.call(t,r)&&r!==n&&f(t,r,{get:()=>e[r],enumerable:!(i=C(e,r))||i.enumerable});return t};var U=t=>A(f({},"__esModule",{value:!0}),t);var Q={};O(Q,{SiteflowProvider:()=>q,useIdentify:()=>N,usePageview:()=>M,useSiteflowContext:()=>J,useTrack:()=>L});module.exports=U(Q);var s=require("react");var v="__sf_did";function K(){return crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}function $(){let t=new RegExp(`(?:^|; )${v}=([^;]*)`,"g"),e=null,n;for(;(n=t.exec(document.cookie))!==null;){let i=decodeURIComponent(n[1]);i&&(e=i)}return e}function x(t){document.cookie=`${v}=${encodeURIComponent(t)}; Max-Age=31536000; Path=/; SameSite=Lax${location.protocol==="https:"?"; Secure":""}`}function m(){let t=$();if(t)return t;let e=K();return x(e),e}function w(t){x(t)}var u="__sf_queue";var c=class{constructor(e){this.cfg=e;this.buffer=[];this.pendingIdentify=null;this.timer=null;this.getDistinctId=()=>"";this.flushQueued(),typeof window<"u"&&window.addEventListener("pagehide",()=>this.flushBeacon())}bindDistinctId(e){this.getDistinctId=e}enqueue(e){this.buffer.push({...e,ts:e.ts??Date.now()}),this.buffer.length>=10?this.flush().catch(()=>{}):this.schedule()}setIdentify(e){this.pendingIdentify=e,this.schedule()}schedule(){this.timer||(this.timer=setTimeout(()=>this.flush().catch(()=>{}),1e3))}async flush(){if(this.timer&&(clearTimeout(this.timer),this.timer=null),this.buffer.length===0&&!this.pendingIdentify)return;let e={site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer,identify:this.pendingIdentify},n=this.buffer;this.buffer=[],this.pendingIdentify=null;try{await this.sendWithRetry(e)}catch{this.queueLocal(n)}}async sendWithRetry(e,n=1){try{let i=await fetch(`${this.cfg.host}/api/collect/event`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e),keepalive:!0});if(!i.ok&&i.status>=500&&n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(e,n+1)}catch(i){if(n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(e,n+1);throw i}}flushBeacon(){if(this.buffer.length===0)return;let e=JSON.stringify({site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer});navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`,e),this.buffer=[]}queueLocal(e){try{let i=[...JSON.parse(localStorage.getItem(u)||"[]"),...e].slice(-50);localStorage.setItem(u,JSON.stringify(i))}catch{}}flushQueued(){try{let e=JSON.parse(localStorage.getItem(u)||"[]");if(e.length===0)return;localStorage.removeItem(u),e.forEach(n=>this.buffer.push(n)),this.schedule()}catch{}}};var l=null;function E(t){l||(l=e=>{let n=e.target;if(!n)return;let i=n.closest("[data-sf-event]");if(!i)return;let r=i.dataset.sfEvent,p={};for(let[a,_]of Object.entries(i.dataset)){if(a==="sfEvent"||!a.startsWith("sf"))continue;let y=a.slice(2),T=y.charAt(0).toLowerCase()+y.slice(1);p[T]=_}t(r,p)},document.addEventListener("click",l,{capture:!0}))}var o=null,d=null;function I(t){d={siteKey:t.siteKey,host:t.host??"https://stat.siteflow.se",debug:t.debug??!1},o=new c({host:d.host,siteKey:d.siteKey}),o.bindDistinctId(()=>m()),E((e,n)=>g(e,n))}function g(t,e={}){if(!o){h("track called before init");return}o.enqueue({name:t,props:e,url:typeof location<"u"?location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0})}function k(t){if(!o){h("pageview called before init");return}o.enqueue({name:"$pageview",props:{},url:t??(typeof location<"u"?location.href:void 0),referrer:typeof document<"u"?document.referrer:void 0})}function b(t,e={}){if(!o){h("identify called before init");return}w(t),o.setIdentify({distinct_id:t,traits:e})}function h(t){d?.debug&&console.warn(`[siteflow] ${t}`)}var R=require("react/jsx-runtime"),P=(0,s.createContext)({ready:!1});function q({siteKey:t,host:e,debug:n,children:i}){let r=(0,s.useRef)(!1);return typeof window<"u"&&!r.current&&(I({siteKey:t,host:e,debug:n}),r.current=!0),(0,R.jsx)(P.Provider,{value:{ready:r.current},children:i})}function L(){return(t,e)=>g(t,e)}function N(){return(t,e)=>b(t,e)}function M(){(0,s.useEffect)(()=>{k()},[])}function J(){return(0,s.useContext)(P)}0&&(module.exports={SiteflowProvider,useIdentify,usePageview,useSiteflowContext,useTrack});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.tsx","../../src/cookie.ts","../../src/transport.ts","../../src/auto.ts","../../src/core.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useRef } from 'react'\nimport { init, track as coreTrack, identify as coreIdentify, pageview as corePageview } from '../core'\n\ntype ProviderProps = {\n siteKey: string\n host?: string\n debug?: boolean\n children: React.ReactNode\n}\n\nconst Ctx = createContext<{ ready: boolean }>({ ready: false })\n\nexport function SiteflowProvider({ siteKey, host, debug, children }: ProviderProps) {\n const initRef = useRef(false)\n if (typeof window !== 'undefined' && !initRef.current) {\n init({ siteKey, host, debug })\n initRef.current = true\n }\n return <Ctx.Provider value={{ ready: initRef.current }}>{children}</Ctx.Provider>\n}\n\nexport function useTrack() {\n return (name: string, props?: Record<string, unknown>) => coreTrack(name, props)\n}\n\nexport function useIdentify() {\n return (id: string, traits?: Record<string, unknown>) => coreIdentify(id, traits)\n}\n\nexport function usePageview() {\n useEffect(() => { corePageview() }, [])\n}\n\nexport function useSiteflowContext() {\n return useContext(Ctx)\n}\n","const COOKIE = '__sf_did'\nconst ONE_YEAR = 60 * 60 * 24 * 365\n\nfunction uuid(): string {\n if (crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction read(): string | null {\n // Find the last (most recently set) non-empty value among all cookies with this name\n const re = new RegExp(`(?:^|; )${COOKIE}=([^;]*)`, 'g')\n let last: string | null = null\n let m: RegExpExecArray | null\n while ((m = re.exec(document.cookie)) !== null) {\n const val = decodeURIComponent(m[1])\n if (val) last = val\n }\n return last\n}\n\nfunction write(value: string): void {\n document.cookie = `${COOKIE}=${encodeURIComponent(value)}; Max-Age=${ONE_YEAR}; Path=/; SameSite=Lax${location.protocol === 'https:' ? '; Secure' : ''}`\n}\n\nexport function getOrCreateDistinctId(): string {\n const existing = read()\n if (existing) return existing\n const fresh = uuid()\n write(fresh)\n return fresh\n}\n\nexport function setDistinctId(id: string): void { write(id) }\nexport function clearDistinctId(): void { document.cookie = `${COOKIE}=; Max-Age=0; Path=/` }\n","type EventPayload = {\n name: string\n props: Record<string, unknown>\n url?: string\n referrer?: string\n ts?: number\n}\n\ntype IdentifyPayload = {\n distinct_id: string\n traits: Record<string, unknown>\n}\n\ntype Config = { host: string; siteKey: string }\n\nconst QUEUE_KEY = '__sf_queue'\nconst MAX_QUEUE = 50\n\nexport class Transport {\n private buffer: EventPayload[] = []\n private pendingIdentify: IdentifyPayload | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private getDistinctId: () => string = () => ''\n\n constructor(private cfg: Config) {\n this.flushQueued()\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => this.flushBeacon())\n }\n }\n\n bindDistinctId(fn: () => string): void {\n this.getDistinctId = fn\n }\n\n enqueue(event: EventPayload): void {\n this.buffer.push({ ...event, ts: event.ts ?? Date.now() })\n if (this.buffer.length >= 10) {\n this.flush().catch(() => {})\n } else {\n this.schedule()\n }\n }\n\n setIdentify(p: IdentifyPayload): void {\n this.pendingIdentify = p\n this.schedule()\n }\n\n private schedule(): void {\n if (this.timer) return\n this.timer = setTimeout(() => this.flush().catch(() => {}), 1000)\n }\n\n async flush(): Promise<void> {\n if (this.timer) { clearTimeout(this.timer); this.timer = null }\n if (this.buffer.length === 0 && !this.pendingIdentify) return\n\n const body = {\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer,\n identify: this.pendingIdentify\n }\n const events = this.buffer\n this.buffer = []\n this.pendingIdentify = null\n\n try {\n await this.sendWithRetry(body)\n } catch {\n this.queueLocal(events)\n }\n }\n\n private async sendWithRetry(body: unknown, attempt = 1): Promise<void> {\n try {\n const res = await fetch(`${this.cfg.host}/api/collect/event`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n keepalive: true\n })\n if (!res.ok && res.status >= 500 && attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n } catch (err) {\n if (attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n throw err\n }\n }\n\n private flushBeacon(): void {\n if (this.buffer.length === 0) return\n const body = JSON.stringify({\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer\n })\n navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`, body)\n this.buffer = []\n }\n\n private queueLocal(events: EventPayload[]): void {\n try {\n const cur: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n const merged = [...cur, ...events].slice(-MAX_QUEUE)\n localStorage.setItem(QUEUE_KEY, JSON.stringify(merged))\n } catch {}\n }\n\n private flushQueued(): void {\n try {\n const queued: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n if (queued.length === 0) return\n localStorage.removeItem(QUEUE_KEY)\n queued.forEach(e => this.buffer.push(e))\n this.schedule()\n } catch {}\n }\n}\n","type TrackFn = (name: string, props: Record<string, unknown>) => void\n\nlet listener: ((e: Event) => void) | null = null\n\nexport function installAutoTracking(track: TrackFn): void {\n if (listener) return\n listener = (e: Event) => {\n const target = e.target as Element | null\n if (!target) return\n const el = target.closest('[data-sf-event]') as HTMLElement | null\n if (!el) return\n const name = el.dataset.sfEvent!\n const props: Record<string, unknown> = {}\n // Note: el.dataset already gives camelCased keys for kebab-cased data-* attrs.\n // data-sf-cta -> sfCta; data-sf-plan-id -> sfPlanId.\n // We strip the \"sf\" prefix and lowercase the first letter.\n for (const [k, v] of Object.entries(el.dataset)) {\n if (k === 'sfEvent' || !k.startsWith('sf')) continue\n const stripped = k.slice(2)\n const camel = stripped.charAt(0).toLowerCase() + stripped.slice(1)\n props[camel] = v\n }\n track(name, props)\n }\n document.addEventListener('click', listener, { capture: true })\n}\n\nexport function uninstallAutoTracking(): void {\n if (!listener) return\n document.removeEventListener('click', listener, { capture: true })\n listener = null\n}\n","import { getOrCreateDistinctId, setDistinctId, clearDistinctId } from './cookie'\nimport { Transport } from './transport'\nimport { installAutoTracking, uninstallAutoTracking } from './auto'\n\ntype Config = { siteKey: string; host?: string; debug?: boolean }\n\nlet transport: Transport | null = null\nlet cfg: Required<Config> | null = null\n\nexport function init(c: Config): void {\n cfg = { siteKey: c.siteKey, host: c.host ?? 'https://stat.siteflow.se', debug: c.debug ?? false }\n transport = new Transport({ host: cfg.host, siteKey: cfg.siteKey })\n transport.bindDistinctId(() => getOrCreateDistinctId())\n installAutoTracking((name, props) => track(name, props))\n}\n\nexport function track(name: string, props: Record<string, unknown> = {}): void {\n if (!transport) { warn('track called before init'); return }\n transport.enqueue({\n name, props,\n url: typeof location !== 'undefined' ? location.href : undefined,\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function pageview(url?: string): void {\n if (!transport) { warn('pageview called before init'); return }\n transport.enqueue({\n name: '$pageview', props: {},\n url: url ?? (typeof location !== 'undefined' ? location.href : undefined),\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function identify(distinctId: string, traits: Record<string, unknown> = {}): void {\n if (!transport) { warn('identify called before init'); return }\n setDistinctId(distinctId)\n transport.setIdentify({ distinct_id: distinctId, traits })\n}\n\nexport function reset(): void {\n clearDistinctId()\n uninstallAutoTracking()\n transport = null\n cfg = null\n}\n\nfunction warn(msg: string): void {\n if (cfg?.debug) console.warn(`[siteflow] ${msg}`)\n}\n\nexport async function __test_flush(): Promise<void> {\n await transport?.flush()\n}\n"],"mappings":"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","names":["react_exports","__export","SiteflowProvider","useIdentify","usePageview","useSiteflowContext","useTrack","__toCommonJS","import_react","COOKIE","uuid","c","r","read","re","COOKIE","last","m","val","write","value","getOrCreateDistinctId","existing","fresh","setDistinctId","id","QUEUE_KEY","Transport","cfg","fn","event","p","body","events","attempt","res","err","merged","QUEUE_KEY","queued","e","listener","installAutoTracking","track","target","el","name","props","k","v","stripped","camel","transport","cfg","init","c","Transport","getOrCreateDistinctId","installAutoTracking","name","props","track","warn","pageview","url","identify","distinctId","traits","setDistinctId","warn","msg","cfg","import_jsx_runtime","Ctx","SiteflowProvider","siteKey","host","debug","children","initRef","init","useTrack","name","props","track","useIdentify","id","traits","identify","usePageview","pageview","useSiteflowContext"]}
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type ProviderProps = {
4
+ siteKey: string;
5
+ host?: string;
6
+ debug?: boolean;
7
+ children: React.ReactNode;
8
+ };
9
+ declare function SiteflowProvider({ siteKey, host, debug, children }: ProviderProps): react_jsx_runtime.JSX.Element;
10
+ declare function useTrack(): (name: string, props?: Record<string, unknown>) => void;
11
+ declare function useIdentify(): (id: string, traits?: Record<string, unknown>) => void;
12
+ declare function usePageview(): void;
13
+ declare function useSiteflowContext(): {
14
+ ready: boolean;
15
+ };
16
+
17
+ export { SiteflowProvider, useIdentify, usePageview, useSiteflowContext, useTrack };
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type ProviderProps = {
4
+ siteKey: string;
5
+ host?: string;
6
+ debug?: boolean;
7
+ children: React.ReactNode;
8
+ };
9
+ declare function SiteflowProvider({ siteKey, host, debug, children }: ProviderProps): react_jsx_runtime.JSX.Element;
10
+ declare function useTrack(): (name: string, props?: Record<string, unknown>) => void;
11
+ declare function useIdentify(): (id: string, traits?: Record<string, unknown>) => void;
12
+ declare function usePageview(): void;
13
+ declare function useSiteflowContext(): {
14
+ ready: boolean;
15
+ };
16
+
17
+ export { SiteflowProvider, useIdentify, usePageview, useSiteflowContext, useTrack };
@@ -0,0 +1,2 @@
1
+ import{createContext as T,useContext as C,useEffect as S,useRef as D}from"react";var p="__sf_did";function R(){return crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=Math.random()*16|0;return(t==="x"?e:e&3|8).toString(16)})}function _(){let t=new RegExp(`(?:^|; )${p}=([^;]*)`,"g"),e=null,n;for(;(n=t.exec(document.cookie))!==null;){let i=decodeURIComponent(n[1]);i&&(e=i)}return e}function y(t){document.cookie=`${p}=${encodeURIComponent(t)}; Max-Age=31536000; Path=/; SameSite=Lax${location.protocol==="https:"?"; Secure":""}`}function v(){let t=_();if(t)return t;let e=R();return y(e),e}function x(t){y(t)}var s="__sf_queue";var u=class{constructor(e){this.cfg=e;this.buffer=[];this.pendingIdentify=null;this.timer=null;this.getDistinctId=()=>"";this.flushQueued(),typeof window<"u"&&window.addEventListener("pagehide",()=>this.flushBeacon())}bindDistinctId(e){this.getDistinctId=e}enqueue(e){this.buffer.push({...e,ts:e.ts??Date.now()}),this.buffer.length>=10?this.flush().catch(()=>{}):this.schedule()}setIdentify(e){this.pendingIdentify=e,this.schedule()}schedule(){this.timer||(this.timer=setTimeout(()=>this.flush().catch(()=>{}),1e3))}async flush(){if(this.timer&&(clearTimeout(this.timer),this.timer=null),this.buffer.length===0&&!this.pendingIdentify)return;let e={site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer,identify:this.pendingIdentify},n=this.buffer;this.buffer=[],this.pendingIdentify=null;try{await this.sendWithRetry(e)}catch{this.queueLocal(n)}}async sendWithRetry(e,n=1){try{let i=await fetch(`${this.cfg.host}/api/collect/event`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e),keepalive:!0});if(!i.ok&&i.status>=500&&n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(e,n+1)}catch(i){if(n<3)return await new Promise(r=>setTimeout(r,n*n*100)),this.sendWithRetry(e,n+1);throw i}}flushBeacon(){if(this.buffer.length===0)return;let e=JSON.stringify({site:this.cfg.siteKey,distinct_id:this.getDistinctId(),events:this.buffer});navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`,e),this.buffer=[]}queueLocal(e){try{let i=[...JSON.parse(localStorage.getItem(s)||"[]"),...e].slice(-50);localStorage.setItem(s,JSON.stringify(i))}catch{}}flushQueued(){try{let e=JSON.parse(localStorage.getItem(s)||"[]");if(e.length===0)return;localStorage.removeItem(s),e.forEach(n=>this.buffer.push(n)),this.schedule()}catch{}}};var a=null;function m(t){a||(a=e=>{let n=e.target;if(!n)return;let i=n.closest("[data-sf-event]");if(!i)return;let r=i.dataset.sfEvent,g={};for(let[d,b]of Object.entries(i.dataset)){if(d==="sfEvent"||!d.startsWith("sf"))continue;let h=d.slice(2),P=h.charAt(0).toLowerCase()+h.slice(1);g[P]=b}t(r,g)},document.addEventListener("click",a,{capture:!0}))}var o=null,c=null;function w(t){c={siteKey:t.siteKey,host:t.host??"https://stat.siteflow.se",debug:t.debug??!1},o=new u({host:c.host,siteKey:c.siteKey}),o.bindDistinctId(()=>v()),m((e,n)=>f(e,n))}function f(t,e={}){if(!o){l("track called before init");return}o.enqueue({name:t,props:e,url:typeof location<"u"?location.href:void 0,referrer:typeof document<"u"?document.referrer:void 0})}function E(t){if(!o){l("pageview called before init");return}o.enqueue({name:"$pageview",props:{},url:t??(typeof location<"u"?location.href:void 0),referrer:typeof document<"u"?document.referrer:void 0})}function I(t,e={}){if(!o){l("identify called before init");return}x(t),o.setIdentify({distinct_id:t,traits:e})}function l(t){c?.debug&&console.warn(`[siteflow] ${t}`)}import{jsx as O}from"react/jsx-runtime";var k=T({ready:!1});function B({siteKey:t,host:e,debug:n,children:i}){let r=D(!1);return typeof window<"u"&&!r.current&&(w({siteKey:t,host:e,debug:n}),r.current=!0),O(k.Provider,{value:{ready:r.current},children:i})}function Y(){return(t,e)=>f(t,e)}function F(){return(t,e)=>I(t,e)}function X(){S(()=>{E()},[])}function j(){return C(k)}export{B as SiteflowProvider,F as useIdentify,X as usePageview,j as useSiteflowContext,Y as useTrack};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/index.tsx","../../src/cookie.ts","../../src/transport.ts","../../src/auto.ts","../../src/core.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useRef } from 'react'\nimport { init, track as coreTrack, identify as coreIdentify, pageview as corePageview } from '../core'\n\ntype ProviderProps = {\n siteKey: string\n host?: string\n debug?: boolean\n children: React.ReactNode\n}\n\nconst Ctx = createContext<{ ready: boolean }>({ ready: false })\n\nexport function SiteflowProvider({ siteKey, host, debug, children }: ProviderProps) {\n const initRef = useRef(false)\n if (typeof window !== 'undefined' && !initRef.current) {\n init({ siteKey, host, debug })\n initRef.current = true\n }\n return <Ctx.Provider value={{ ready: initRef.current }}>{children}</Ctx.Provider>\n}\n\nexport function useTrack() {\n return (name: string, props?: Record<string, unknown>) => coreTrack(name, props)\n}\n\nexport function useIdentify() {\n return (id: string, traits?: Record<string, unknown>) => coreIdentify(id, traits)\n}\n\nexport function usePageview() {\n useEffect(() => { corePageview() }, [])\n}\n\nexport function useSiteflowContext() {\n return useContext(Ctx)\n}\n","const COOKIE = '__sf_did'\nconst ONE_YEAR = 60 * 60 * 24 * 365\n\nfunction uuid(): string {\n if (crypto.randomUUID) return crypto.randomUUID()\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {\n const r = (Math.random() * 16) | 0\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)\n })\n}\n\nfunction read(): string | null {\n // Find the last (most recently set) non-empty value among all cookies with this name\n const re = new RegExp(`(?:^|; )${COOKIE}=([^;]*)`, 'g')\n let last: string | null = null\n let m: RegExpExecArray | null\n while ((m = re.exec(document.cookie)) !== null) {\n const val = decodeURIComponent(m[1])\n if (val) last = val\n }\n return last\n}\n\nfunction write(value: string): void {\n document.cookie = `${COOKIE}=${encodeURIComponent(value)}; Max-Age=${ONE_YEAR}; Path=/; SameSite=Lax${location.protocol === 'https:' ? '; Secure' : ''}`\n}\n\nexport function getOrCreateDistinctId(): string {\n const existing = read()\n if (existing) return existing\n const fresh = uuid()\n write(fresh)\n return fresh\n}\n\nexport function setDistinctId(id: string): void { write(id) }\nexport function clearDistinctId(): void { document.cookie = `${COOKIE}=; Max-Age=0; Path=/` }\n","type EventPayload = {\n name: string\n props: Record<string, unknown>\n url?: string\n referrer?: string\n ts?: number\n}\n\ntype IdentifyPayload = {\n distinct_id: string\n traits: Record<string, unknown>\n}\n\ntype Config = { host: string; siteKey: string }\n\nconst QUEUE_KEY = '__sf_queue'\nconst MAX_QUEUE = 50\n\nexport class Transport {\n private buffer: EventPayload[] = []\n private pendingIdentify: IdentifyPayload | null = null\n private timer: ReturnType<typeof setTimeout> | null = null\n private getDistinctId: () => string = () => ''\n\n constructor(private cfg: Config) {\n this.flushQueued()\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', () => this.flushBeacon())\n }\n }\n\n bindDistinctId(fn: () => string): void {\n this.getDistinctId = fn\n }\n\n enqueue(event: EventPayload): void {\n this.buffer.push({ ...event, ts: event.ts ?? Date.now() })\n if (this.buffer.length >= 10) {\n this.flush().catch(() => {})\n } else {\n this.schedule()\n }\n }\n\n setIdentify(p: IdentifyPayload): void {\n this.pendingIdentify = p\n this.schedule()\n }\n\n private schedule(): void {\n if (this.timer) return\n this.timer = setTimeout(() => this.flush().catch(() => {}), 1000)\n }\n\n async flush(): Promise<void> {\n if (this.timer) { clearTimeout(this.timer); this.timer = null }\n if (this.buffer.length === 0 && !this.pendingIdentify) return\n\n const body = {\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer,\n identify: this.pendingIdentify\n }\n const events = this.buffer\n this.buffer = []\n this.pendingIdentify = null\n\n try {\n await this.sendWithRetry(body)\n } catch {\n this.queueLocal(events)\n }\n }\n\n private async sendWithRetry(body: unknown, attempt = 1): Promise<void> {\n try {\n const res = await fetch(`${this.cfg.host}/api/collect/event`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n keepalive: true\n })\n if (!res.ok && res.status >= 500 && attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n } catch (err) {\n if (attempt < 3) {\n await new Promise(r => setTimeout(r, attempt * attempt * 100))\n return this.sendWithRetry(body, attempt + 1)\n }\n throw err\n }\n }\n\n private flushBeacon(): void {\n if (this.buffer.length === 0) return\n const body = JSON.stringify({\n site: this.cfg.siteKey,\n distinct_id: this.getDistinctId(),\n events: this.buffer\n })\n navigator.sendBeacon?.(`${this.cfg.host}/api/collect/event`, body)\n this.buffer = []\n }\n\n private queueLocal(events: EventPayload[]): void {\n try {\n const cur: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n const merged = [...cur, ...events].slice(-MAX_QUEUE)\n localStorage.setItem(QUEUE_KEY, JSON.stringify(merged))\n } catch {}\n }\n\n private flushQueued(): void {\n try {\n const queued: EventPayload[] = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]')\n if (queued.length === 0) return\n localStorage.removeItem(QUEUE_KEY)\n queued.forEach(e => this.buffer.push(e))\n this.schedule()\n } catch {}\n }\n}\n","type TrackFn = (name: string, props: Record<string, unknown>) => void\n\nlet listener: ((e: Event) => void) | null = null\n\nexport function installAutoTracking(track: TrackFn): void {\n if (listener) return\n listener = (e: Event) => {\n const target = e.target as Element | null\n if (!target) return\n const el = target.closest('[data-sf-event]') as HTMLElement | null\n if (!el) return\n const name = el.dataset.sfEvent!\n const props: Record<string, unknown> = {}\n // Note: el.dataset already gives camelCased keys for kebab-cased data-* attrs.\n // data-sf-cta -> sfCta; data-sf-plan-id -> sfPlanId.\n // We strip the \"sf\" prefix and lowercase the first letter.\n for (const [k, v] of Object.entries(el.dataset)) {\n if (k === 'sfEvent' || !k.startsWith('sf')) continue\n const stripped = k.slice(2)\n const camel = stripped.charAt(0).toLowerCase() + stripped.slice(1)\n props[camel] = v\n }\n track(name, props)\n }\n document.addEventListener('click', listener, { capture: true })\n}\n\nexport function uninstallAutoTracking(): void {\n if (!listener) return\n document.removeEventListener('click', listener, { capture: true })\n listener = null\n}\n","import { getOrCreateDistinctId, setDistinctId, clearDistinctId } from './cookie'\nimport { Transport } from './transport'\nimport { installAutoTracking, uninstallAutoTracking } from './auto'\n\ntype Config = { siteKey: string; host?: string; debug?: boolean }\n\nlet transport: Transport | null = null\nlet cfg: Required<Config> | null = null\n\nexport function init(c: Config): void {\n cfg = { siteKey: c.siteKey, host: c.host ?? 'https://stat.siteflow.se', debug: c.debug ?? false }\n transport = new Transport({ host: cfg.host, siteKey: cfg.siteKey })\n transport.bindDistinctId(() => getOrCreateDistinctId())\n installAutoTracking((name, props) => track(name, props))\n}\n\nexport function track(name: string, props: Record<string, unknown> = {}): void {\n if (!transport) { warn('track called before init'); return }\n transport.enqueue({\n name, props,\n url: typeof location !== 'undefined' ? location.href : undefined,\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function pageview(url?: string): void {\n if (!transport) { warn('pageview called before init'); return }\n transport.enqueue({\n name: '$pageview', props: {},\n url: url ?? (typeof location !== 'undefined' ? location.href : undefined),\n referrer: typeof document !== 'undefined' ? document.referrer : undefined\n })\n}\n\nexport function identify(distinctId: string, traits: Record<string, unknown> = {}): void {\n if (!transport) { warn('identify called before init'); return }\n setDistinctId(distinctId)\n transport.setIdentify({ distinct_id: distinctId, traits })\n}\n\nexport function reset(): void {\n clearDistinctId()\n uninstallAutoTracking()\n transport = null\n cfg = null\n}\n\nfunction warn(msg: string): void {\n if (cfg?.debug) console.warn(`[siteflow] ${msg}`)\n}\n\nexport async function __test_flush(): Promise<void> {\n await transport?.flush()\n}\n"],"mappings":"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","names":["createContext","useContext","useEffect","useRef","COOKIE","uuid","c","r","read","re","COOKIE","last","m","val","write","value","getOrCreateDistinctId","existing","fresh","setDistinctId","id","QUEUE_KEY","Transport","cfg","fn","event","p","body","events","attempt","res","err","merged","QUEUE_KEY","queued","e","listener","installAutoTracking","track","target","el","name","props","k","v","stripped","camel","transport","cfg","init","c","Transport","getOrCreateDistinctId","installAutoTracking","name","props","track","warn","pageview","url","identify","distinctId","traits","setDistinctId","warn","msg","cfg","jsx","Ctx","createContext","SiteflowProvider","siteKey","host","debug","children","initRef","useRef","init","useTrack","name","props","track","useIdentify","id","traits","identify","usePageview","useEffect","pageview","useSiteflowContext","useContext"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@siteflow/analytics",
3
+ "version": "0.1.0",
4
+ "description": "Siteflow Stats client SDK — pageviews, custom events, identify",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./react": {
17
+ "import": "./dist/react/index.js",
18
+ "require": "./dist/react/index.cjs",
19
+ "types": "./dist/react/index.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "typecheck": "tsc --noEmit"
32
+ },
33
+ "peerDependencies": {
34
+ "react": ">=18"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "react": {
38
+ "optional": true
39
+ }
40
+ },
41
+ "devDependencies": {
42
+ "@testing-library/dom": "^10.4.1",
43
+ "@testing-library/react": "^16.3.2",
44
+ "@types/react": "^19",
45
+ "@vitest/coverage-v8": "^2.1.9",
46
+ "happy-dom": "^15",
47
+ "react": "^19",
48
+ "tsup": "^8",
49
+ "typescript": "^5",
50
+ "vitest": "^2"
51
+ }
52
+ }