@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 +59 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +2 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +17 -0
- package/dist/react/index.d.ts +17 -0
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +52 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|