@indiebob/tracker 0.2.3 → 0.3.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/CHANGELOG.md +80 -0
- package/README.md +125 -6
- package/dist/index.cjs +880 -2
- package/dist/index.d.mts +69 -11
- package/dist/index.d.ts +69 -11
- package/dist/index.global.js +3 -3
- package/dist/index.js +878 -2
- package/dist/server.d.mts +8 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +5 -7
- package/dist/server.mjs +5 -7
- package/dist/vue.cjs +904 -2
- package/dist/vue.global.js +3 -3
- package/dist/vue.js +900 -2
- package/package.json +11 -9
package/dist/index.js
CHANGED
|
@@ -1,2 +1,878 @@
|
|
|
1
|
-
var dt=Object.defineProperty,ft=Object.defineProperties;var pt=Object.getOwnPropertyDescriptors;var D=Object.getOwnPropertySymbols;var gt=Object.prototype.hasOwnProperty,mt=Object.prototype.propertyIsEnumerable;var F=(e,t,n)=>t in e?dt(e,t,{enumerable:true,configurable:true,writable:true,value:n}):e[t]=n,p=(e,t)=>{for(var n in t||(t={}))gt.call(t,n)&&F(e,n,t[n]);if(D)for(var n of D(t))mt.call(t,n)&&F(e,n,t[n]);return e},N=(e,t)=>ft(e,pt(t));var l=(e,t,n)=>new Promise((o,i)=>{var r=a=>{try{s(n.next(a));}catch(u){i(u);}},c=a=>{try{s(n.throw(a));}catch(u){i(u);}},s=a=>a.done?o(a.value):Promise.resolve(a.value).then(r,c);s((n=n.apply(e,t)).next());});var ht="https://www.indiebob.com/api/collect";function U(e){var t,n,o,i,r;if(!e.projectId||!e.projectId.startsWith("proj_"))throw new Error(`[IndieBob] Invalid projectId "${e.projectId}". Must start with "proj_".`);return {projectId:e.projectId,endpoint:(t=e.endpoint)!=null?t:ht,debug:(n=e.debug)!=null?n:false,respectDnt:(o=e.respectDnt)!=null?o:true,respectGpc:(i=e.respectGpc)!=null?i:true,autoDetect:(r=e.autoDetect)!=null?r:true}}var d="_ib_anon";function y(){let e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-",t="";if(typeof crypto!="undefined"&&crypto.getRandomValues){let n=new Uint8Array(21);crypto.getRandomValues(n);for(let o of n)t+=e[o%e.length];}else for(let n=0;n<21;n++)t+=e[Math.floor(Math.random()*e.length)];return t}function b(){try{let e=localStorage.getItem(d);return e||(e=y(),localStorage.setItem(d,e)),e}catch(e){try{let t=sessionStorage.getItem(d);return t||(t=y(),sessionStorage.setItem(d,t)),t}catch(t){return y()}}}function L(){try{localStorage.removeItem(d);}catch(e){}try{sessionStorage.removeItem(d);}catch(e){}}var I="_ib_session",S="_ib_session_last";function M(){try{return crypto.randomUUID()}catch(e){return Math.random().toString(36).slice(2)+Math.random().toString(36).slice(2)}}function E(){var e;try{let t=Date.now(),n=parseInt((e=sessionStorage.getItem(S))!=null?e:"0",10),o=sessionStorage.getItem(I),i=n>0&&t-n>18e5;return (!o||i)&&(o=M(),sessionStorage.setItem(I,o)),sessionStorage.setItem(S,String(t)),o}catch(t){return M()}}function q(){try{sessionStorage.removeItem(I);}catch(e){}try{sessionStorage.removeItem(S);}catch(e){}}var g="_ib_first_utm",j="_ib_last_utm",m="_ib_referrer";function B(e){let t=new URLSearchParams(e),n={},o=false;return t.has("utm_source")?(n.source=t.get("utm_source"),o=true):t.get("ref")&&(n.source=t.get("ref"),o=true),t.has("utm_medium")&&(n.medium=t.get("utm_medium"),o=true),t.has("utm_campaign")&&(n.campaign=t.get("utm_campaign"),o=true),t.has("utm_term")&&(n.term=t.get("utm_term"),o=true),t.has("utm_content")&&(n.content=t.get("utm_content"),o=true),o?n:null}function Y(e,t){let n=B(e);try{n&&!localStorage.getItem(g)&&localStorage.setItem(g,JSON.stringify(n)),n&&localStorage.setItem(j,JSON.stringify(n)),t&&!localStorage.getItem(m)&&localStorage.setItem(m,t);}catch(o){}}function H(){try{let e=localStorage.getItem(g);return e?JSON.parse(e):void 0}catch(e){return}}function K(){var e;try{return (e=localStorage.getItem(m))!=null?e:void 0}catch(t){return}}function V(e){var t;return (t=B(e))!=null?t:void 0}function Q(){try{localStorage.removeItem(g);}catch(e){}try{localStorage.removeItem(j);}catch(e){}try{localStorage.removeItem(m);}catch(e){}}var h="_ib_optout",vt="_ib_queue";function f(e){try{if(localStorage.getItem(h))return !1}catch(t){}return !(e.respectDnt&&typeof navigator!="undefined"&&(navigator.doNotTrack==="1"||navigator.msDoNotTrack==="1")||e.respectGpc&&typeof navigator!="undefined"&&navigator.globalPrivacyControl===true)}function z(){try{localStorage.setItem(h,"1");}catch(e){}L(),q(),Q();try{localStorage.removeItem(vt);}catch(e){}}function X(){try{localStorage.removeItem(h);}catch(e){}}function J(){try{return !!localStorage.getItem(h)}catch(e){return false}}var yt=[1e3,2e3,4e3];function bt(e){return l(this,null,function*(){return new Promise(t=>setTimeout(t,e))})}function _(e,t,n=false){return l(this,null,function*(){let o=JSON.stringify(e);if(n&&typeof navigator!="undefined"&&navigator.sendBeacon){let i=new Blob([o],{type:"text/plain;charset=UTF-8"}),r=navigator.sendBeacon(t.endpoint,i);return t.debug&&console.log("[IndieBob] sendBeacon:",r,e.batch.length,"events"),r}for(let i=0;i<3;i++){try{let r=yield fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json"},body:o,keepalive:!0});if(r.ok)return t.debug&&console.log("[IndieBob] sent:",e.batch.length,"events"),!0;if(r.status>=400&&r.status<500)return t.debug&&console.warn("[IndieBob] 4xx error, not retrying:",r.status),!1}catch(r){t.debug&&console.warn("[IndieBob] fetch error (attempt",i+1,"):",r);}i<2&&(yield bt(yt[i]));}return t.debug&&console.warn("[IndieBob] failed to send after",3,"attempts"),false})}var w="_ib_queue",It=10,St=5e3,Et=50,v=class{constructor(t){this.queue=[];this.timer=null;this.flushing=false;this.config=t,this.startTimer(),this.setupVisibilityFlush(),this.setupOnlineFlush();}push(t){this.queue.push(t),this.queue.length>=It&&this.flush();}flush(t=false){return l(this,null,function*(){if(this.flushing&&!t||this.queue.length===0)return;this.flushing=true;let n=this.queue.splice(0);(yield _({batch:n},this.config,t))||this.pushToOfflineQueue(n),this.flushing=false;})}flushOfflineQueue(){return l(this,null,function*(){let t=this.getOfflineQueue();if(t.length===0)return;this.clearOfflineQueue(),yield _({batch:t},this.config);})}destroy(){this.timer&&clearInterval(this.timer);}startTimer(){this.timer=setInterval(()=>{this.queue.length>0&&this.flush();},St);}setupVisibilityFlush(){typeof document!="undefined"&&(document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush(true);},{passive:true}),window.addEventListener("pagehide",()=>{this.flush(true);},{passive:true}));}setupOnlineFlush(){typeof window!="undefined"&&(window.addEventListener("online",()=>{this.flushOfflineQueue();}),navigator.onLine&&this.flushOfflineQueue());}pushToOfflineQueue(t){try{let i=[...this.getOfflineQueue(),...t].slice(-Et);localStorage.setItem(w,JSON.stringify(i));}catch(n){}}getOfflineQueue(){try{let t=localStorage.getItem(w);return t?JSON.parse(t):[]}catch(t){return []}}clearOfflineQueue(){try{localStorage.removeItem(w);}catch(t){}}};function $(){if(typeof window=="undefined")return "desktop";let e=window.innerWidth;return e<=768?"mobile":e<=1024?"tablet":"desktop"}function G(){return typeof window=="undefined"?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight}}var _t=/^https?:\/\/[^/]*(stripe\.com|paypal\.com|paddle\.com|lemonsqueezy\.com|gumroad\.com)(\/|$)/i;function T(e){var R,O,x,A,C,P;let t=typeof window=="undefined",n=(R=e==null?void 0:e.path)!=null?R:t?"":window.location.pathname+window.location.search,o=t?"":window.location.href,i=(O=e==null?void 0:e.title)!=null?O:t?"":document.title,r=(x=e==null?void 0:e.referrer)!=null?x:t?"":document.referrer,c=!t&&(r===""||_t.test(r))&&(A=K())!=null?A:r,s=G(),a=$(),u=t?void 0:(C=V(window.location.search))!=null?C:H();return {page:{path:n,url:o,title:i,referrer:c},device:N(p({},s),{type:a}),utm:u,locale:t?"en":navigator.language,timezone:t?"UTC":(P=Intl.DateTimeFormat().resolvedOptions().timeZone)!=null?P:"UTC",sdkVersion:"0.2.3"}}function W(e){if(typeof window=="undefined")return ()=>{};let t=history.pushState.bind(history),n=history.replaceState.bind(history);return history.pushState=function(...o){t(...o),e();},history.replaceState=function(...o){n(...o);},window.addEventListener("popstate",e),()=>{history.pushState=t,history.replaceState=n,window.removeEventListener("popstate",e);}}var wt={signup:[/\/(sign-?up|register|join|create-?account|get-?started|onboard|registro|inscription|s-?inscrire|anmeldung|registrieren|cadastro|registrazione|aanmelden|inschrijven)/i],login:[/\/(log-?in|sign-?in|auth(\/login)?|iniciar-?sesion|connexion|se-?connecter|anmelden|einloggen|entrar|accesso|accedi|inloggen)/i],pricing:[/\/(pricing|plans|upgrade|subscribe|precios|tarifs|preise|precos|prezzi|prijzen)/i],checkout:[/\/(checkout|cart|basket|pay(ment)?|order|comprar|pagar|panier|caisse|warenkorb|kasse|carrinho|carrello|winkelwagen)/i],success:[/\/(success|thank-?you|thanks|confirmation|confirmed|complete[d]?|gracias|merci|danke|obrigado|grazie|bedankt)/i],onboarding:[/\/(onboarding|welcome|getting-?started|setup|bienvenido|bienvenue|willkommen|benvenuto|welkom)/i],contact:[/\/(contact|contacto|kontakt|contato|contatto|nous-?contacter)/i]},Z={signup:"signup_start",login:"login_view",pricing:"pricing_view",checkout:"checkout_view",success:"success_view",onboarding:"onboarding_view",contact:"contact_view"};function Tt(e,t){return e instanceof RegExp?e.test(t):t.includes(e)}function tt(e,t){var n;for(let o of Object.keys(Z))if(((n=t==null?void 0:t[o])!=null?n:wt[o]).some(r=>Tt(r,e)))return Z[o];return null}function et(e){return e==="signup_start"?"signup_submit":e==="login_view"?"login_submit":e==="checkout_view"?"checkout_submit":null}function nt(e,t){if(typeof document=="undefined")return ()=>{};let n=o=>{o.target instanceof HTMLFormElement&&t(e);};return document.addEventListener("submit",n,true),()=>document.removeEventListener("submit",n,true)}var k="ib_identified";function ot(){return typeof localStorage=="undefined"?true:!localStorage.getItem(k)}function it(){typeof localStorage!="undefined"&&localStorage.setItem(k,"1");}function rt(){typeof localStorage!="undefined"&&localStorage.removeItem(k);}var st="data-ib-event",at="data-ib-";function ct(e){if(typeof document=="undefined")return ()=>{};let t=n=>{let o=n.target;for(;o&&o!==document.body;){let i=o.getAttribute(st);if(i){let r={auto:true};for(let c of o.attributes)if(c.name.startsWith(at)&&c.name!==st){let s=c.name.slice(at.length);r[s]=c.value;}e(i,r);return}o=o.parentElement;}};return document.addEventListener("click",t,true),()=>document.removeEventListener("click",t,true)}function ut(e){if(typeof document=="undefined")return ()=>{};let t=n=>{let o=n.target;for(;o&&o!==document.body;){if(o.tagName==="A"){let i=o.href;if(!i||!/^https?:\/\//.test(i))return;try{let r=new URL(i);r.hostname!==window.location.hostname&&e("outbound_click",{auto:!0,url:i,hostname:r.hostname});}catch(r){}return}o=o.parentElement;}};return document.addEventListener("click",t,true),()=>document.removeEventListener("click",t,true)}var lt=class e{constructor(t){this.cleanupHistory=null;this.cleanupAttributes=null;this.cleanupOutbound=null;this.cleanupFormSubmit=null;if(this.config=t,this.queue=new v(t),typeof window!="undefined"&&(Y(window.location.search,document.referrer),t.autoDetect!==false)){let n=typeof t.autoDetect=="object"?t.autoDetect:{};n.dataAttributes!==false&&(this.cleanupAttributes=ct((o,i)=>this.track(o,i))),n.outboundClicks!==false&&(this.cleanupOutbound=ut((o,i)=>this.track(o,i)));}}static init(t){let n=U(t);return new e(n)}page(t){var o;if(!f(this.config))return;let n=T(t);if(this.queue.push(this.buildEvent("page",{context:n})),this.config.autoDetect!==false){let i=n.page.path,r=typeof this.config.autoDetect=="object"?this.config.autoDetect:{},c={signup:r.signupPaths,login:r.loginPaths,pricing:r.pricingPaths,checkout:r.checkoutPaths,success:r.successPaths,onboarding:r.onboardingPaths,contact:r.contactPaths},s=tt(i,c);if(s&&(this.queue.push(this.buildEvent("track",{event:s,properties:{auto:true},context:n})),this.config.debug&&console.log(`[IndieBob] auto-detected: ${s} on ${i}`),r.formSubmit!==false)){(o=this.cleanupFormSubmit)==null||o.call(this);let a=et(s);a&&(this.cleanupFormSubmit=nt(a,u=>{this.queue.push(this.buildEvent("track",{event:u,properties:{auto:true}})),this.config.debug&&console.log(`[IndieBob] auto-detected: ${u}`);}));}}}track(t,n){f(this.config)&&this.queue.push(this.buildEvent("track",{event:t,properties:n}));}identify(t,n){if(f(this.config)&&(this.userId=t,this.queue.push(this.buildEvent("identify",{traits:n,userId:t})),this.config.autoDetect!==false)){let o=ot(),i=o?"signup":"login";this.queue.push(this.buildEvent("track",{event:i,properties:{auto:true}})),o&&it(),this.config.debug&&console.log(`[IndieBob] auto-detected: ${i} for ${t}`);}}revenue(t){var n;f(this.config)&&this.queue.push(this.buildEvent("revenue",{revenue:{amount:t.amount,currency:(n=t.currency)!=null?n:"USD",plan:t.plan,type:t.type}}));}optOut(){var t,n,o;this.queue.flush(),z(),rt(),this.userId=void 0,(t=this.cleanupAttributes)==null||t.call(this),(n=this.cleanupOutbound)==null||n.call(this),(o=this.cleanupFormSubmit)==null||o.call(this),this.cleanupAttributes=null,this.cleanupOutbound=null,this.cleanupFormSubmit=null,this.config.debug&&console.log("[IndieBob] opted out");}optIn(){X(),this.config.debug&&console.log("[IndieBob] opted in");}isOptedOut(){return J()}flush(){return l(this,null,function*(){yield this.queue.flush();})}getAnonymousId(){return b()}getSessionId(){return E()}getConfig(){return Object.freeze(p({},this.config))}enableAutoPage(){var t;return (t=this.cleanupHistory)==null||t.call(this),this.cleanupHistory=W(()=>this.page()),()=>{var n;(n=this.cleanupHistory)==null||n.call(this),this.cleanupHistory=null;}}buildEvent(t,n){var o,i;return {v:1,projectId:this.config.projectId,type:t,anonymousId:b(),userId:(o=this.userId)!=null?o:n.userId,sessionId:E(),timestamp:new Date().toISOString(),context:(i=n.context)!=null?i:T(),event:n.event,properties:n.properties,traits:n.traits,revenue:n.revenue}}};
|
|
2
|
-
|
|
1
|
+
// src/core/config.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://www.indiebob.com/api/collect";
|
|
3
|
+
function resolveConfig(input) {
|
|
4
|
+
var _a, _b, _c, _d, _e, _f;
|
|
5
|
+
if (!input.projectId || !input.projectId.startsWith("proj_")) {
|
|
6
|
+
throw new Error(
|
|
7
|
+
`[IndieBob] Invalid projectId "${input.projectId}". Must start with "proj_".`
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
projectId: input.projectId,
|
|
12
|
+
endpoint: (_a = input.endpoint) != null ? _a : DEFAULT_ENDPOINT,
|
|
13
|
+
debug: (_b = input.debug) != null ? _b : false,
|
|
14
|
+
respectDnt: (_c = input.respectDnt) != null ? _c : true,
|
|
15
|
+
respectGpc: (_d = input.respectGpc) != null ? _d : true,
|
|
16
|
+
autoDetect: (_e = input.autoDetect) != null ? _e : true,
|
|
17
|
+
hashMode: (_f = input.hashMode) != null ? _f : false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/core/identity.ts
|
|
22
|
+
var ANON_KEY = "_ib_anon";
|
|
23
|
+
function generateId() {
|
|
24
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
25
|
+
const bytes = new Uint8Array(21);
|
|
26
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) crypto.getRandomValues(bytes);
|
|
27
|
+
else for (let i = 0; i < 21; i++) bytes[i] = Math.random() * 64;
|
|
28
|
+
let id = "";
|
|
29
|
+
for (const b of bytes) id += chars[b & 63];
|
|
30
|
+
return id;
|
|
31
|
+
}
|
|
32
|
+
function getAnonymousId() {
|
|
33
|
+
try {
|
|
34
|
+
let id = localStorage.getItem(ANON_KEY);
|
|
35
|
+
if (!id) {
|
|
36
|
+
id = generateId();
|
|
37
|
+
localStorage.setItem(ANON_KEY, id);
|
|
38
|
+
}
|
|
39
|
+
return id;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
try {
|
|
42
|
+
let id = sessionStorage.getItem(ANON_KEY);
|
|
43
|
+
if (!id) {
|
|
44
|
+
id = generateId();
|
|
45
|
+
sessionStorage.setItem(ANON_KEY, id);
|
|
46
|
+
}
|
|
47
|
+
return id;
|
|
48
|
+
} catch (e2) {
|
|
49
|
+
return generateId();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function clearAnonymousId() {
|
|
54
|
+
try {
|
|
55
|
+
localStorage.removeItem(ANON_KEY);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
sessionStorage.removeItem(ANON_KEY);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/core/session.ts
|
|
65
|
+
var SESSION_KEY = "_ib_session";
|
|
66
|
+
var SESSION_LAST_KEY = "_ib_session_last";
|
|
67
|
+
var INACTIVITY_MS = 30 * 60 * 1e3;
|
|
68
|
+
function generateSessionId() {
|
|
69
|
+
try {
|
|
70
|
+
return crypto.randomUUID();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return generateId();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function getSessionId() {
|
|
76
|
+
var _a;
|
|
77
|
+
try {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const lastActivity = parseInt((_a = sessionStorage.getItem(SESSION_LAST_KEY)) != null ? _a : "0", 10);
|
|
80
|
+
let sessionId = sessionStorage.getItem(SESSION_KEY);
|
|
81
|
+
const expired = lastActivity > 0 && now - lastActivity > INACTIVITY_MS;
|
|
82
|
+
if (!sessionId || expired) {
|
|
83
|
+
sessionId = generateSessionId();
|
|
84
|
+
sessionStorage.setItem(SESSION_KEY, sessionId);
|
|
85
|
+
}
|
|
86
|
+
sessionStorage.setItem(SESSION_LAST_KEY, String(now));
|
|
87
|
+
return sessionId;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return generateSessionId();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function clearSession() {
|
|
93
|
+
try {
|
|
94
|
+
sessionStorage.removeItem(SESSION_KEY);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
sessionStorage.removeItem(SESSION_LAST_KEY);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/core/attribution.ts
|
|
104
|
+
var FIRST_UTM_KEY = "_ib_first_utm";
|
|
105
|
+
var LAST_UTM_KEY = "_ib_last_utm";
|
|
106
|
+
var REFERRER_KEY = "_ib_referrer";
|
|
107
|
+
function parseUtm(search) {
|
|
108
|
+
const params = new URLSearchParams(search);
|
|
109
|
+
const utm = {};
|
|
110
|
+
let hasAny = false;
|
|
111
|
+
if (!params.has("utm_source") && params.get("ref")) {
|
|
112
|
+
utm.source = params.get("ref");
|
|
113
|
+
hasAny = true;
|
|
114
|
+
}
|
|
115
|
+
for (const key of ["source", "medium", "campaign", "term", "content"]) {
|
|
116
|
+
if (params.has("utm_" + key)) {
|
|
117
|
+
utm[key] = params.get("utm_" + key);
|
|
118
|
+
hasAny = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return hasAny ? utm : null;
|
|
122
|
+
}
|
|
123
|
+
function initAttribution(search, referrer) {
|
|
124
|
+
const utm = parseUtm(search);
|
|
125
|
+
try {
|
|
126
|
+
if (utm && !localStorage.getItem(FIRST_UTM_KEY)) {
|
|
127
|
+
localStorage.setItem(FIRST_UTM_KEY, JSON.stringify(utm));
|
|
128
|
+
}
|
|
129
|
+
if (utm) {
|
|
130
|
+
localStorage.setItem(LAST_UTM_KEY, JSON.stringify(utm));
|
|
131
|
+
}
|
|
132
|
+
if (referrer && !localStorage.getItem(REFERRER_KEY)) {
|
|
133
|
+
localStorage.setItem(REFERRER_KEY, referrer);
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function getStoredFirstUtm() {
|
|
139
|
+
try {
|
|
140
|
+
const raw = localStorage.getItem(FIRST_UTM_KEY);
|
|
141
|
+
return raw ? JSON.parse(raw) : void 0;
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function getStoredReferrer() {
|
|
147
|
+
var _a;
|
|
148
|
+
try {
|
|
149
|
+
return (_a = localStorage.getItem(REFERRER_KEY)) != null ? _a : void 0;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function clearAttribution() {
|
|
155
|
+
try {
|
|
156
|
+
localStorage.removeItem(FIRST_UTM_KEY);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
localStorage.removeItem(LAST_UTM_KEY);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
localStorage.removeItem(REFERRER_KEY);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/core/exclude.ts
|
|
170
|
+
var IGNORE_KEY = "ib_ignore";
|
|
171
|
+
function applyIgnore(search, store) {
|
|
172
|
+
const p = new URLSearchParams(search).get(IGNORE_KEY);
|
|
173
|
+
if (p === "1") store.setItem(IGNORE_KEY, "1");
|
|
174
|
+
else if (p === "0") store.removeItem(IGNORE_KEY);
|
|
175
|
+
return store.getItem(IGNORE_KEY) === "1";
|
|
176
|
+
}
|
|
177
|
+
function isIgnored() {
|
|
178
|
+
try {
|
|
179
|
+
return localStorage.getItem(IGNORE_KEY) === "1";
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function isExcluded() {
|
|
185
|
+
return typeof navigator !== "undefined" && navigator.webdriver === true || isIgnored();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/core/consent.ts
|
|
189
|
+
var OPT_OUT_KEY = "_ib_optout";
|
|
190
|
+
var OFFLINE_QUEUE_KEY = "_ib_queue";
|
|
191
|
+
function isTrackingAllowed(config) {
|
|
192
|
+
if (isExcluded()) return false;
|
|
193
|
+
try {
|
|
194
|
+
if (localStorage.getItem(OPT_OUT_KEY)) return false;
|
|
195
|
+
} catch (e) {
|
|
196
|
+
}
|
|
197
|
+
if (config.respectDnt && typeof navigator !== "undefined") {
|
|
198
|
+
if (navigator.doNotTrack === "1" || navigator.msDoNotTrack === "1") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (config.respectGpc && typeof navigator !== "undefined") {
|
|
203
|
+
if (navigator.globalPrivacyControl === true) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
function optOut() {
|
|
210
|
+
try {
|
|
211
|
+
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
212
|
+
} catch (e) {
|
|
213
|
+
}
|
|
214
|
+
clearAnonymousId();
|
|
215
|
+
clearSession();
|
|
216
|
+
clearAttribution();
|
|
217
|
+
try {
|
|
218
|
+
localStorage.removeItem(OFFLINE_QUEUE_KEY);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function optIn() {
|
|
223
|
+
try {
|
|
224
|
+
localStorage.removeItem(OPT_OUT_KEY);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function isOptedOut() {
|
|
229
|
+
try {
|
|
230
|
+
return !!localStorage.getItem(OPT_OUT_KEY);
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/core/transport.ts
|
|
237
|
+
var MAX_RETRIES = 3;
|
|
238
|
+
function log(config, ...args) {
|
|
239
|
+
if (config.debug) console.log("[IndieBob]", ...args);
|
|
240
|
+
}
|
|
241
|
+
async function sendBatch(payload, config, isUnloading = false) {
|
|
242
|
+
const body = JSON.stringify(payload);
|
|
243
|
+
if (isUnloading && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
244
|
+
const blob = new Blob([body], { type: "text/plain;charset=UTF-8" });
|
|
245
|
+
const ok = navigator.sendBeacon(config.endpoint, blob);
|
|
246
|
+
log(config, "sendBeacon:", ok, payload.batch.length, "events");
|
|
247
|
+
return ok;
|
|
248
|
+
}
|
|
249
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
250
|
+
try {
|
|
251
|
+
const res = await fetch(config.endpoint, {
|
|
252
|
+
method: "POST",
|
|
253
|
+
headers: { "Content-Type": "application/json" },
|
|
254
|
+
body,
|
|
255
|
+
keepalive: true
|
|
256
|
+
});
|
|
257
|
+
if (res.ok) {
|
|
258
|
+
log(config, "sent:", payload.batch.length, "events");
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (res.status >= 400 && res.status < 500) {
|
|
262
|
+
log(config, "4xx error, not retrying:", res.status);
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
} catch (err) {
|
|
266
|
+
log(config, "fetch error (attempt", attempt + 1, "):", err);
|
|
267
|
+
}
|
|
268
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
269
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 << attempt));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
log(config, "failed to send after", MAX_RETRIES, "attempts");
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/core/queue.ts
|
|
277
|
+
var OFFLINE_KEY = "_ib_queue";
|
|
278
|
+
var MAX_BATCH = 10;
|
|
279
|
+
var FLUSH_INTERVAL_MS = 5e3;
|
|
280
|
+
var MAX_OFFLINE = 50;
|
|
281
|
+
var EventQueue = class {
|
|
282
|
+
/**
|
|
283
|
+
* @param beforeHide runs right before the hide flush, so an event that
|
|
284
|
+
* describes the page being left (engagement) rides the same beacon.
|
|
285
|
+
*/
|
|
286
|
+
constructor(config, beforeHide) {
|
|
287
|
+
this.b = [];
|
|
288
|
+
this.i = false;
|
|
289
|
+
this.a = config;
|
|
290
|
+
setInterval(() => {
|
|
291
|
+
if (this.b.length > 0) void this.flush();
|
|
292
|
+
}, FLUSH_INTERVAL_MS);
|
|
293
|
+
if (typeof window === "undefined") return;
|
|
294
|
+
const hide = () => {
|
|
295
|
+
beforeHide == null ? void 0 : beforeHide();
|
|
296
|
+
void this.flush(true);
|
|
297
|
+
};
|
|
298
|
+
document.addEventListener("visibilitychange", () => {
|
|
299
|
+
if (document.visibilityState === "hidden") hide();
|
|
300
|
+
});
|
|
301
|
+
window.addEventListener("pagehide", hide);
|
|
302
|
+
window.addEventListener("online", () => {
|
|
303
|
+
void this.m();
|
|
304
|
+
});
|
|
305
|
+
if (navigator.onLine) {
|
|
306
|
+
void this.m();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
push(event) {
|
|
310
|
+
this.b.push(event);
|
|
311
|
+
if (this.b.length >= MAX_BATCH) {
|
|
312
|
+
void this.flush();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async flush(isUnloading = false) {
|
|
316
|
+
if (this.i && !isUnloading) return;
|
|
317
|
+
if (this.b.length === 0) return;
|
|
318
|
+
this.i = true;
|
|
319
|
+
const batch = this.b.splice(0);
|
|
320
|
+
const payload = { batch };
|
|
321
|
+
const ok = await sendBatch(payload, this.a, isUnloading);
|
|
322
|
+
if (!ok) {
|
|
323
|
+
try {
|
|
324
|
+
const trimmed = [...this.n(), ...batch].slice(-MAX_OFFLINE);
|
|
325
|
+
localStorage.setItem(OFFLINE_KEY, JSON.stringify(trimmed));
|
|
326
|
+
} catch (e) {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.i = false;
|
|
330
|
+
}
|
|
331
|
+
async m() {
|
|
332
|
+
const stored = this.n();
|
|
333
|
+
if (stored.length === 0) return;
|
|
334
|
+
try {
|
|
335
|
+
localStorage.removeItem(OFFLINE_KEY);
|
|
336
|
+
} catch (e) {
|
|
337
|
+
}
|
|
338
|
+
const payload = { batch: stored };
|
|
339
|
+
await sendBatch(payload, this.a);
|
|
340
|
+
}
|
|
341
|
+
n() {
|
|
342
|
+
try {
|
|
343
|
+
const raw = localStorage.getItem(OFFLINE_KEY);
|
|
344
|
+
if (!raw) return [];
|
|
345
|
+
return JSON.parse(raw);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
return [];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// src/collectors/page.ts
|
|
353
|
+
function pagePath(loc, hashMode) {
|
|
354
|
+
return loc.pathname + loc.search + (hashMode ? loc.hash : "");
|
|
355
|
+
}
|
|
356
|
+
function patchHistory(onNavigate, hashMode) {
|
|
357
|
+
if (typeof window === "undefined") return () => {
|
|
358
|
+
};
|
|
359
|
+
const originalPushState = history.pushState.bind(history);
|
|
360
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
361
|
+
history.pushState = function(...args) {
|
|
362
|
+
originalPushState(...args);
|
|
363
|
+
onNavigate();
|
|
364
|
+
};
|
|
365
|
+
history.replaceState = function(...args) {
|
|
366
|
+
originalReplaceState(...args);
|
|
367
|
+
};
|
|
368
|
+
window.addEventListener("popstate", onNavigate);
|
|
369
|
+
if (hashMode) window.addEventListener("hashchange", onNavigate);
|
|
370
|
+
return () => {
|
|
371
|
+
history.pushState = originalPushState;
|
|
372
|
+
history.replaceState = originalReplaceState;
|
|
373
|
+
window.removeEventListener("popstate", onNavigate);
|
|
374
|
+
window.removeEventListener("hashchange", onNavigate);
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/collectors/context.ts
|
|
379
|
+
var PAYMENT_HOST = /^https?:\/\/[^/]*(stripe\.com|paypal\.com|paddle\.com|lemonsqueezy\.com|gumroad\.com)(\/|$)/i;
|
|
380
|
+
function buildContext(overrides, hashMode) {
|
|
381
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
382
|
+
const sdkVersion = "0.3.0" ;
|
|
383
|
+
if (typeof window === "undefined") {
|
|
384
|
+
return {
|
|
385
|
+
page: { path: (_a = overrides == null ? void 0 : overrides.path) != null ? _a : "", url: "", title: (_b = overrides == null ? void 0 : overrides.title) != null ? _b : "", referrer: (_c = overrides == null ? void 0 : overrides.referrer) != null ? _c : "" },
|
|
386
|
+
device: { width: 0, height: 0, type: "desktop" },
|
|
387
|
+
locale: "en",
|
|
388
|
+
timezone: "UTC",
|
|
389
|
+
sdkVersion
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
const liveReferrer = (_d = overrides == null ? void 0 : overrides.referrer) != null ? _d : document.referrer;
|
|
393
|
+
const width = innerWidth;
|
|
394
|
+
return {
|
|
395
|
+
page: {
|
|
396
|
+
path: (_e = overrides == null ? void 0 : overrides.path) != null ? _e : pagePath(location, hashMode),
|
|
397
|
+
url: location.href,
|
|
398
|
+
title: (_f = overrides == null ? void 0 : overrides.title) != null ? _f : document.title,
|
|
399
|
+
// First touch survives the round trip: a /thanks page reached from Stripe
|
|
400
|
+
// reports the referrer and UTM the visit STARTED with, not checkout.stripe.com
|
|
401
|
+
// — otherwise every sale files itself under the payment host.
|
|
402
|
+
referrer: liveReferrer === "" || PAYMENT_HOST.test(liveReferrer) ? (_g = getStoredReferrer()) != null ? _g : liveReferrer : liveReferrer
|
|
403
|
+
},
|
|
404
|
+
// Device class from the viewport width; still required by /api/collect
|
|
405
|
+
device: { width, height: innerHeight, type: width <= 768 ? "mobile" : width <= 1024 ? "tablet" : "desktop" },
|
|
406
|
+
utm: (_h = parseUtm(location.search)) != null ? _h : getStoredFirstUtm(),
|
|
407
|
+
locale: navigator.language,
|
|
408
|
+
timezone: (_i = Intl.DateTimeFormat().resolvedOptions().timeZone) != null ? _i : "UTC",
|
|
409
|
+
sdkVersion
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// src/collectors/intent.ts
|
|
414
|
+
var INTENTS = [
|
|
415
|
+
[
|
|
416
|
+
"signup",
|
|
417
|
+
"signup_start",
|
|
418
|
+
/\/(sign-?up|register|join|create-?account|get-?started|onboard|registro|inscription|s-?inscrire|anmeldung|registrieren|cadastro|registrazione|aanmelden|inschrijven)/i
|
|
419
|
+
],
|
|
420
|
+
[
|
|
421
|
+
"login",
|
|
422
|
+
"login_view",
|
|
423
|
+
/\/(log-?in|sign-?in|auth(\/login)?|iniciar-?sesion|connexion|se-?connecter|anmelden|einloggen|entrar|accesso|accedi|inloggen)/i
|
|
424
|
+
],
|
|
425
|
+
[
|
|
426
|
+
"pricing",
|
|
427
|
+
"pricing_view",
|
|
428
|
+
/\/(pricing|plans|upgrade|subscribe|precios|tarifs|preise|precos|prezzi|prijzen)/i
|
|
429
|
+
],
|
|
430
|
+
[
|
|
431
|
+
"checkout",
|
|
432
|
+
"checkout_view",
|
|
433
|
+
/\/(checkout|cart|basket|pay(ment)?|order|comprar|pagar|panier|caisse|warenkorb|kasse|carrinho|carrello|winkelwagen)/i
|
|
434
|
+
],
|
|
435
|
+
[
|
|
436
|
+
"success",
|
|
437
|
+
"success_view",
|
|
438
|
+
/\/(success|thank-?you|thanks|confirmation|confirmed|complete[d]?|gracias|merci|danke|obrigado|grazie|bedankt)/i
|
|
439
|
+
],
|
|
440
|
+
[
|
|
441
|
+
"onboarding",
|
|
442
|
+
"onboarding_view",
|
|
443
|
+
/\/(onboarding|welcome|getting-?started|setup|bienvenido|bienvenue|willkommen|benvenuto|welkom)/i
|
|
444
|
+
],
|
|
445
|
+
[
|
|
446
|
+
"contact",
|
|
447
|
+
"contact_view",
|
|
448
|
+
/\/(contact|contacto|kontakt|contato|contatto|nous-?contacter)/i
|
|
449
|
+
]
|
|
450
|
+
];
|
|
451
|
+
function matchPattern(pattern, path) {
|
|
452
|
+
if (pattern instanceof RegExp) return pattern.test(path);
|
|
453
|
+
return path.includes(pattern);
|
|
454
|
+
}
|
|
455
|
+
function detectIntent(path, custom) {
|
|
456
|
+
var _a;
|
|
457
|
+
for (const [category, event, pattern] of INTENTS) {
|
|
458
|
+
const patterns = (_a = custom == null ? void 0 : custom[`${category}Paths`]) != null ? _a : [pattern];
|
|
459
|
+
if (patterns.some((p) => matchPattern(p, path))) return event;
|
|
460
|
+
}
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
function intentToSubmitEvent(intent) {
|
|
464
|
+
const m = /^(signup|login|checkout)_/.exec(intent);
|
|
465
|
+
return m ? m[1] + "_submit" : null;
|
|
466
|
+
}
|
|
467
|
+
function observeFormSubmits(submitEventName, onSubmit) {
|
|
468
|
+
if (typeof document === "undefined") return () => {
|
|
469
|
+
};
|
|
470
|
+
const handler = (e) => {
|
|
471
|
+
if (e.target instanceof HTMLFormElement) {
|
|
472
|
+
onSubmit(submitEventName);
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
document.addEventListener("submit", handler, true);
|
|
476
|
+
return () => document.removeEventListener("submit", handler, true);
|
|
477
|
+
}
|
|
478
|
+
var IDENTIFIED_KEY = "ib_identified";
|
|
479
|
+
function isFirstIdentify() {
|
|
480
|
+
if (typeof localStorage === "undefined") return true;
|
|
481
|
+
return !localStorage.getItem(IDENTIFIED_KEY);
|
|
482
|
+
}
|
|
483
|
+
function markIdentified() {
|
|
484
|
+
if (typeof localStorage === "undefined") return;
|
|
485
|
+
localStorage.setItem(IDENTIFIED_KEY, "1");
|
|
486
|
+
}
|
|
487
|
+
function clearIdentified() {
|
|
488
|
+
if (typeof localStorage === "undefined") return;
|
|
489
|
+
localStorage.removeItem(IDENTIFIED_KEY);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/collectors/attributes.ts
|
|
493
|
+
var ATTR_EVENT = "data-ib-event";
|
|
494
|
+
var ATTR_PREFIX = "data-ib-";
|
|
495
|
+
function attributeEvent(el) {
|
|
496
|
+
while (el && el !== document.body) {
|
|
497
|
+
const eventName = el.getAttribute(ATTR_EVENT);
|
|
498
|
+
if (eventName) {
|
|
499
|
+
const props = { auto: true };
|
|
500
|
+
for (const attr of el.attributes) {
|
|
501
|
+
if (attr.name.startsWith(ATTR_PREFIX) && attr.name !== ATTR_EVENT) {
|
|
502
|
+
props[attr.name.slice(ATTR_PREFIX.length)] = attr.value;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return [eventName, props];
|
|
506
|
+
}
|
|
507
|
+
el = el.parentElement;
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/collectors/links.ts
|
|
513
|
+
var DOWNLOAD_EXT = /\.(pdf|zip|dmg|exe|msi|pkg|apk|ipa|deb|rpm|appimage|t?gz|bz2|xz|tar|7z|rar|csv|xlsx?|docx?|pptx?|txt|epub|mp3|wav|mp4|mov|iso)$/i;
|
|
514
|
+
function downloadExt(pathname) {
|
|
515
|
+
const m = DOWNLOAD_EXT.exec(pathname);
|
|
516
|
+
return m ? m[1].toLowerCase() : null;
|
|
517
|
+
}
|
|
518
|
+
function classifyLink(href, host, downloads, outbound) {
|
|
519
|
+
if (!/^https?:\/\//i.test(href)) return null;
|
|
520
|
+
try {
|
|
521
|
+
const u = new URL(href);
|
|
522
|
+
const ext = downloads && downloadExt(u.pathname);
|
|
523
|
+
if (ext) return ["file_download", { auto: true, url: u.origin + u.pathname, path: u.pathname, ext }];
|
|
524
|
+
if (outbound && u.hostname !== host) return ["outbound_click", { auto: true, url: href, hostname: u.hostname }];
|
|
525
|
+
} catch (e) {
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
function initClickTracking(onTrack, attrs, downloads, outbound) {
|
|
530
|
+
if (typeof document === "undefined") return () => {
|
|
531
|
+
};
|
|
532
|
+
const handler = (e) => {
|
|
533
|
+
var _a;
|
|
534
|
+
const el = e.target;
|
|
535
|
+
const a = (_a = el == null ? void 0 : el.closest) == null ? void 0 : _a.call(el, "a");
|
|
536
|
+
for (const hit of [
|
|
537
|
+
attrs && attributeEvent(el),
|
|
538
|
+
a && classifyLink(a.href, location.hostname, downloads, outbound)
|
|
539
|
+
]) {
|
|
540
|
+
if (hit) onTrack(hit[0], hit[1]);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
document.addEventListener("click", handler, true);
|
|
544
|
+
return () => document.removeEventListener("click", handler, true);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/collectors/engagement.ts
|
|
548
|
+
var ENGAGEMENT_MIN_MS = 3e3;
|
|
549
|
+
function shouldSendEngagement(ms, scroll, lastSentScroll) {
|
|
550
|
+
return scroll > lastSentScroll || ms >= ENGAGEMENT_MIN_MS;
|
|
551
|
+
}
|
|
552
|
+
function scrollPercent(top, viewport, docHeight) {
|
|
553
|
+
return docHeight > 0 ? Math.min(100, Math.round((top + viewport) / docHeight * 100)) : 0;
|
|
554
|
+
}
|
|
555
|
+
function trackEngagement() {
|
|
556
|
+
let ms = 0, since = 0, scroll = 0, sent = 0;
|
|
557
|
+
const tick = () => {
|
|
558
|
+
const now = Date.now();
|
|
559
|
+
if (since) ms += now - since;
|
|
560
|
+
since = document.visibilityState === "visible" && document.hasFocus() ? now : 0;
|
|
561
|
+
};
|
|
562
|
+
const sample = () => {
|
|
563
|
+
var _a, _b;
|
|
564
|
+
scroll = Math.max(scroll, scrollPercent(
|
|
565
|
+
scrollY,
|
|
566
|
+
innerHeight,
|
|
567
|
+
Math.max(document.documentElement.scrollHeight, (_b = (_a = document.body) == null ? void 0 : _a.scrollHeight) != null ? _b : 0)
|
|
568
|
+
));
|
|
569
|
+
};
|
|
570
|
+
const reset = () => {
|
|
571
|
+
ms = scroll = sent = since = 0;
|
|
572
|
+
tick();
|
|
573
|
+
};
|
|
574
|
+
for (const t of ["focus", "blur"]) window.addEventListener(t, tick);
|
|
575
|
+
document.addEventListener("visibilitychange", tick);
|
|
576
|
+
window.addEventListener("scroll", sample);
|
|
577
|
+
reset();
|
|
578
|
+
return {
|
|
579
|
+
r: sample,
|
|
580
|
+
s: reset,
|
|
581
|
+
t(measure) {
|
|
582
|
+
if (measure) sample();
|
|
583
|
+
tick();
|
|
584
|
+
if (!shouldSendEngagement(ms, scroll, sent)) return null;
|
|
585
|
+
const r = { ms: Math.min(ms, 864e5), scroll };
|
|
586
|
+
ms = 0;
|
|
587
|
+
sent = scroll;
|
|
588
|
+
return r;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/index.ts
|
|
594
|
+
var instances = {};
|
|
595
|
+
function unseen() {
|
|
596
|
+
return typeof document !== "undefined" && (document.visibilityState === "hidden" || document.prerendering === true);
|
|
597
|
+
}
|
|
598
|
+
var IndieBob = class _IndieBob {
|
|
599
|
+
constructor(config) {
|
|
600
|
+
this.f = null;
|
|
601
|
+
this.k = null;
|
|
602
|
+
this.g = null;
|
|
603
|
+
/** Engagement meter for the current page (browser only). */
|
|
604
|
+
this.d = null;
|
|
605
|
+
/** Context of the page last counted — what an engagement event describes. */
|
|
606
|
+
this.l = null;
|
|
607
|
+
/** A first pageview waiting for the tab to become visible. */
|
|
608
|
+
this.h = false;
|
|
609
|
+
this.a = config;
|
|
610
|
+
this.b = new EventQueue(config, () => this.q(true));
|
|
611
|
+
if (typeof window !== "undefined") {
|
|
612
|
+
try {
|
|
613
|
+
if (applyIgnore(window.location.search, localStorage)) {
|
|
614
|
+
console.info("IndieBob: this browser is excluded from analytics (ib_ignore)");
|
|
615
|
+
}
|
|
616
|
+
} catch (e) {
|
|
617
|
+
}
|
|
618
|
+
initAttribution(window.location.search, document.referrer);
|
|
619
|
+
this.d = trackEngagement();
|
|
620
|
+
window.addEventListener("pageshow", (e) => {
|
|
621
|
+
if (e.persisted && this.e !== void 0) {
|
|
622
|
+
this.e = void 0;
|
|
623
|
+
this.page(this.o);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
if (config.autoDetect !== false) {
|
|
627
|
+
const opts = typeof config.autoDetect === "object" ? config.autoDetect : {};
|
|
628
|
+
this.k = initClickTracking(
|
|
629
|
+
(event, properties) => this.track(event, properties),
|
|
630
|
+
opts.dataAttributes !== false,
|
|
631
|
+
opts.fileDownloads !== false,
|
|
632
|
+
opts.outboundClicks !== false
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Create a tracker instance. Call once at app startup.
|
|
639
|
+
*
|
|
640
|
+
* In the browser, calling it again for the same projectId returns the
|
|
641
|
+
* instance already running (its first config wins) — so a `data-project`
|
|
642
|
+
* script tag plus a manual init() never doubles the events.
|
|
643
|
+
*
|
|
644
|
+
* @example
|
|
645
|
+
* const bob = IndieBob.init({ projectId: 'proj_your16charId' })
|
|
646
|
+
*/
|
|
647
|
+
static init(config) {
|
|
648
|
+
var _a, _b;
|
|
649
|
+
const resolved = resolveConfig(config);
|
|
650
|
+
return typeof window === "undefined" ? new _IndieBob(resolved) : (_b = instances[_a = resolved.projectId]) != null ? _b : instances[_a] = new _IndieBob(resolved);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Track a page view. Call on every page load and SPA navigation.
|
|
654
|
+
* Auto-detects signup/login/pricing pages and fires semantic events.
|
|
655
|
+
*
|
|
656
|
+
* @example
|
|
657
|
+
* bob.page()
|
|
658
|
+
* bob.page({ path: '/pricing' })
|
|
659
|
+
*/
|
|
660
|
+
page(overrides) {
|
|
661
|
+
var _a;
|
|
662
|
+
if (!isTrackingAllowed(this.a)) return;
|
|
663
|
+
if (this.e === void 0 && unseen()) {
|
|
664
|
+
this.p = overrides;
|
|
665
|
+
if (!this.h) {
|
|
666
|
+
this.h = true;
|
|
667
|
+
const go = () => {
|
|
668
|
+
if (this.h && !unseen()) {
|
|
669
|
+
this.h = false;
|
|
670
|
+
this.page(this.p);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
document.addEventListener("visibilitychange", go);
|
|
674
|
+
document.addEventListener("prerenderingchange", go);
|
|
675
|
+
}
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const ctx = buildContext(overrides, this.a.hashMode);
|
|
679
|
+
if (ctx.page.path === this.e) return;
|
|
680
|
+
this.q(false);
|
|
681
|
+
this.e = ctx.page.path;
|
|
682
|
+
this.o = overrides;
|
|
683
|
+
this.l = ctx;
|
|
684
|
+
if (this.d) {
|
|
685
|
+
this.d.s();
|
|
686
|
+
setTimeout(this.d.r, 1e3);
|
|
687
|
+
}
|
|
688
|
+
this.b.push(this.c("page", { context: ctx }));
|
|
689
|
+
if (this.a.autoDetect !== false) {
|
|
690
|
+
const path = ctx.page.path;
|
|
691
|
+
const opts = typeof this.a.autoDetect === "object" ? this.a.autoDetect : {};
|
|
692
|
+
const intent = detectIntent(path, opts);
|
|
693
|
+
if (intent) {
|
|
694
|
+
this.b.push(this.c("track", {
|
|
695
|
+
event: intent,
|
|
696
|
+
properties: { auto: true },
|
|
697
|
+
context: ctx
|
|
698
|
+
}));
|
|
699
|
+
log(this.a, `auto-detected: ${intent} on ${path}`);
|
|
700
|
+
if (opts.formSubmit !== false) {
|
|
701
|
+
(_a = this.g) == null ? void 0 : _a.call(this);
|
|
702
|
+
const submitEvent = intentToSubmitEvent(intent);
|
|
703
|
+
if (submitEvent) {
|
|
704
|
+
this.g = observeFormSubmits(submitEvent, (eventName) => {
|
|
705
|
+
this.b.push(this.c("track", {
|
|
706
|
+
event: eventName,
|
|
707
|
+
properties: { auto: true }
|
|
708
|
+
}));
|
|
709
|
+
log(this.a, `auto-detected: ${eventName}`);
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Track a custom event.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* bob.track('signup_started')
|
|
721
|
+
* bob.track('feature_used', { feature: 'csv_export' })
|
|
722
|
+
*/
|
|
723
|
+
track(event, properties) {
|
|
724
|
+
if (!isTrackingAllowed(this.a)) return;
|
|
725
|
+
this.b.push(this.c("track", { event, properties }));
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Associate the current anonymous visitor with a known user ID.
|
|
729
|
+
* Call after login or signup.
|
|
730
|
+
*
|
|
731
|
+
* When autoDetect is enabled (default), this also fires:
|
|
732
|
+
* - 'signup' if this is the first time this visitor is identified (new user)
|
|
733
|
+
* - 'login' if this visitor was previously identified (returning user)
|
|
734
|
+
*
|
|
735
|
+
* PASS `isNew` WHEN YOUR SERVER KNOWS THE ANSWER. Without it the only
|
|
736
|
+
* evidence available is a localStorage flag this SDK wrote itself, and that
|
|
737
|
+
* flag cannot exist in a browser that has never run identify() before — so
|
|
738
|
+
* the FIRST identify in any browser looks like a signup. On a product that
|
|
739
|
+
* already has users, the day you install identify() every returning user
|
|
740
|
+
* files a phantom `signup` and the funnel's top step is wrong from then on.
|
|
741
|
+
* The server knows whether it just INSERTed a user row; tell us and the
|
|
742
|
+
* guess is skipped entirely.
|
|
743
|
+
*
|
|
744
|
+
* @example
|
|
745
|
+
* bob.identify('user_123', { plan: 'pro' }) // guess
|
|
746
|
+
* bob.identify('user_123', { plan: 'pro' }, { isNew: true }) // server truth
|
|
747
|
+
*/
|
|
748
|
+
identify(userId, traits, options) {
|
|
749
|
+
if (!isTrackingAllowed(this.a)) return;
|
|
750
|
+
this.j = userId;
|
|
751
|
+
this.b.push(this.c("identify", { traits, userId }));
|
|
752
|
+
if (this.a.autoDetect !== false) {
|
|
753
|
+
const first = typeof (options == null ? void 0 : options.isNew) === "boolean" ? options.isNew : isFirstIdentify();
|
|
754
|
+
const eventName = first ? "signup" : "login";
|
|
755
|
+
this.b.push(this.c("track", {
|
|
756
|
+
event: eventName,
|
|
757
|
+
// `auto` says where the verdict came from, so a funnel that looks
|
|
758
|
+
// wrong later can be traced to a guess rather than to the server.
|
|
759
|
+
properties: { auto: typeof (options == null ? void 0 : options.isNew) !== "boolean" }
|
|
760
|
+
}));
|
|
761
|
+
markIdentified();
|
|
762
|
+
log(this.a, `${eventName} for ${userId}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Track a revenue event.
|
|
767
|
+
*
|
|
768
|
+
* @example
|
|
769
|
+
* bob.revenue({ amount: 29.00, currency: 'USD', plan: 'pro-monthly', type: 'new' })
|
|
770
|
+
*/
|
|
771
|
+
revenue(data) {
|
|
772
|
+
var _a;
|
|
773
|
+
if (!isTrackingAllowed(this.a)) return;
|
|
774
|
+
this.b.push(this.c("revenue", {
|
|
775
|
+
revenue: {
|
|
776
|
+
amount: data.amount,
|
|
777
|
+
currency: (_a = data.currency) != null ? _a : "USD",
|
|
778
|
+
plan: data.plan,
|
|
779
|
+
type: data.type
|
|
780
|
+
}
|
|
781
|
+
}));
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Stop all tracking and clear all stored identifiers.
|
|
785
|
+
* Respects user's privacy preferences.
|
|
786
|
+
*/
|
|
787
|
+
optOut() {
|
|
788
|
+
var _a, _b;
|
|
789
|
+
void this.b.flush();
|
|
790
|
+
optOut();
|
|
791
|
+
clearIdentified();
|
|
792
|
+
this.j = void 0;
|
|
793
|
+
(_a = this.k) == null ? void 0 : _a.call(this);
|
|
794
|
+
(_b = this.g) == null ? void 0 : _b.call(this);
|
|
795
|
+
this.k = null;
|
|
796
|
+
this.g = null;
|
|
797
|
+
log(this.a, "opted out");
|
|
798
|
+
}
|
|
799
|
+
/** Re-enable tracking after optOut(). */
|
|
800
|
+
optIn() {
|
|
801
|
+
optIn();
|
|
802
|
+
log(this.a, "opted in");
|
|
803
|
+
}
|
|
804
|
+
/** Returns true if the user has explicitly opted out. */
|
|
805
|
+
isOptedOut() {
|
|
806
|
+
return isOptedOut();
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Returns true if this browser excluded itself with `?ib_ignore=1`
|
|
810
|
+
* (the owner's own visits). `?ib_ignore=0` undoes it.
|
|
811
|
+
*/
|
|
812
|
+
isIgnored() {
|
|
813
|
+
return isIgnored();
|
|
814
|
+
}
|
|
815
|
+
/** Force-send all queued events immediately. Useful in tests. */
|
|
816
|
+
async flush() {
|
|
817
|
+
await this.b.flush();
|
|
818
|
+
}
|
|
819
|
+
/** Get the current anonymous ID. */
|
|
820
|
+
getAnonymousId() {
|
|
821
|
+
return getAnonymousId();
|
|
822
|
+
}
|
|
823
|
+
/** Get the current session ID. */
|
|
824
|
+
getSessionId() {
|
|
825
|
+
return getSessionId();
|
|
826
|
+
}
|
|
827
|
+
/** Get the resolved configuration (read-only). */
|
|
828
|
+
getConfig() {
|
|
829
|
+
return Object.freeze({ ...this.a });
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Enable auto-tracking of SPA navigation.
|
|
833
|
+
* Returns a cleanup function to remove the listeners.
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* const cleanup = bob.enableAutoPage()
|
|
837
|
+
* // Later: cleanup()
|
|
838
|
+
*/
|
|
839
|
+
enableAutoPage() {
|
|
840
|
+
var _a;
|
|
841
|
+
(_a = this.f) == null ? void 0 : _a.call(this);
|
|
842
|
+
this.f = patchHistory(() => this.page(), this.a.hashMode);
|
|
843
|
+
return () => {
|
|
844
|
+
var _a2;
|
|
845
|
+
(_a2 = this.f) == null ? void 0 : _a2.call(this);
|
|
846
|
+
this.f = null;
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Queue an engagement event for the page last counted, if its reading is
|
|
851
|
+
* worth sending. `measure`: sample the scroll first (false on an SPA
|
|
852
|
+
* change, when the DOM already shows the next page).
|
|
853
|
+
*/
|
|
854
|
+
q(measure) {
|
|
855
|
+
var _a;
|
|
856
|
+
const reading = this.l && ((_a = this.d) == null ? void 0 : _a.t(measure));
|
|
857
|
+
if (reading && isTrackingAllowed(this.a)) {
|
|
858
|
+
this.b.push(this.c("engagement", { context: this.l, engagement: reading }));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
c(type, extras) {
|
|
862
|
+
var _a, _b;
|
|
863
|
+
return {
|
|
864
|
+
...extras,
|
|
865
|
+
v: 1,
|
|
866
|
+
id: generateId(),
|
|
867
|
+
projectId: this.a.projectId,
|
|
868
|
+
type,
|
|
869
|
+
anonymousId: getAnonymousId(),
|
|
870
|
+
userId: (_a = this.j) != null ? _a : extras.userId,
|
|
871
|
+
sessionId: getSessionId(),
|
|
872
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
873
|
+
context: (_b = extras.context) != null ? _b : buildContext(void 0, this.a.hashMode)
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
export { IndieBob };
|