@obelism/numbers-sdk 0.3.0 → 0.4.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 +3 -2
- package/dist/index.js +21 -3
- package/dist/index.mjs +21 -3
- package/dist/script.global.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,8 +35,9 @@ query string or hash (`?tab=2`, `#pricing`) isn't a second page view.
|
|
|
35
35
|
events belong to; without it they go to the organization's first space.
|
|
36
36
|
`data-endpoint="https://…"` sends somewhere else, e.g. a proxy on your own
|
|
37
37
|
domain; without it events go to numbers' origin, even when a CDN serves the
|
|
38
|
-
file. `data-debug` turns on the
|
|
39
|
-
|
|
38
|
+
file. `data-debug` turns on the development warnings (snake_case names, an 8 KB
|
|
39
|
+
body, a `data-organization` or `data-space` it can't use, an unknown
|
|
40
|
+
`data-environment` or `data-storage`), and
|
|
40
41
|
`data-storage="session"` (or `"none"`) counts without a cookie. npm CDNs
|
|
41
42
|
serve the same file from `@obelism/numbers-sdk/dist/script.global.js`.
|
|
42
43
|
|
package/dist/index.js
CHANGED
|
@@ -178,6 +178,9 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
178
178
|
// Epoch ms until which sending is paused after a 429.
|
|
179
179
|
let pausedUntil = 0;
|
|
180
180
|
let firstPageView = true;
|
|
181
|
+
// The path of the last page view, shared by every autoTrack: a second
|
|
182
|
+
// call replaces the listeners but must not count the page again.
|
|
183
|
+
let lastPath = null;
|
|
181
184
|
let stopAutoTrack = null;
|
|
182
185
|
const sentKeys = new Set();
|
|
183
186
|
const exposed = new Set();
|
|
@@ -296,7 +299,6 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
296
299
|
if (pageViews) {
|
|
297
300
|
// One page view per path: replaceState for a query or hash change
|
|
298
301
|
// isn't a new page.
|
|
299
|
-
let lastPath = null;
|
|
300
302
|
const onNavigate = ()=>{
|
|
301
303
|
if (location.pathname === lastPath) return;
|
|
302
304
|
lastPath = location.pathname;
|
|
@@ -342,9 +344,25 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
342
344
|
} catch {
|
|
343
345
|
return;
|
|
344
346
|
}
|
|
347
|
+
// Only http(s): a data: URL's path is its whole payload, and
|
|
348
|
+
// mailto:/tel: aren't destinations.
|
|
349
|
+
if (!/^https?:$/.test(url.protocol)) return;
|
|
345
350
|
const id = link.closest('[data-numbers-id]')?.getAttribute('data-numbers-id');
|
|
346
|
-
|
|
347
|
-
|
|
351
|
+
const fileName = url.pathname.split('/').pop() || url.pathname;
|
|
352
|
+
const extension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
|
|
353
|
+
if (downloads && isDownload(url, link)) track('file_download', id ?? fileName, id ? undefined : {
|
|
354
|
+
params: {
|
|
355
|
+
file_name: extension ? fileName.slice(0, -(extension.length + 1)) : fileName,
|
|
356
|
+
...extension && {
|
|
357
|
+
file_extension: extension
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
else if (outbound && url.origin !== location.origin) track('outbound_click', id ?? `${url.origin}${url.pathname}`, id ? undefined : {
|
|
362
|
+
params: {
|
|
363
|
+
link_domain: url.hostname
|
|
364
|
+
}
|
|
365
|
+
});
|
|
348
366
|
};
|
|
349
367
|
document.addEventListener('click', onLinkClick, true);
|
|
350
368
|
stops.push(()=>document.removeEventListener('click', onLinkClick, true));
|
package/dist/index.mjs
CHANGED
|
@@ -176,6 +176,9 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
176
176
|
// Epoch ms until which sending is paused after a 429.
|
|
177
177
|
let pausedUntil = 0;
|
|
178
178
|
let firstPageView = true;
|
|
179
|
+
// The path of the last page view, shared by every autoTrack: a second
|
|
180
|
+
// call replaces the listeners but must not count the page again.
|
|
181
|
+
let lastPath = null;
|
|
179
182
|
let stopAutoTrack = null;
|
|
180
183
|
const sentKeys = new Set();
|
|
181
184
|
const exposed = new Set();
|
|
@@ -294,7 +297,6 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
294
297
|
if (pageViews) {
|
|
295
298
|
// One page view per path: replaceState for a query or hash change
|
|
296
299
|
// isn't a new page.
|
|
297
|
-
let lastPath = null;
|
|
298
300
|
const onNavigate = ()=>{
|
|
299
301
|
if (location.pathname === lastPath) return;
|
|
300
302
|
lastPath = location.pathname;
|
|
@@ -340,9 +342,25 @@ const createNumbers = ({ organizationId, spaceId, environment = 'production', en
|
|
|
340
342
|
} catch {
|
|
341
343
|
return;
|
|
342
344
|
}
|
|
345
|
+
// Only http(s): a data: URL's path is its whole payload, and
|
|
346
|
+
// mailto:/tel: aren't destinations.
|
|
347
|
+
if (!/^https?:$/.test(url.protocol)) return;
|
|
343
348
|
const id = link.closest('[data-numbers-id]')?.getAttribute('data-numbers-id');
|
|
344
|
-
|
|
345
|
-
|
|
349
|
+
const fileName = url.pathname.split('/').pop() || url.pathname;
|
|
350
|
+
const extension = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
|
|
351
|
+
if (downloads && isDownload(url, link)) track('file_download', id ?? fileName, id ? undefined : {
|
|
352
|
+
params: {
|
|
353
|
+
file_name: extension ? fileName.slice(0, -(extension.length + 1)) : fileName,
|
|
354
|
+
...extension && {
|
|
355
|
+
file_extension: extension
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
else if (outbound && url.origin !== location.origin) track('outbound_click', id ?? `${url.origin}${url.pathname}`, id ? undefined : {
|
|
360
|
+
params: {
|
|
361
|
+
link_domain: url.hostname
|
|
362
|
+
}
|
|
363
|
+
});
|
|
346
364
|
};
|
|
347
365
|
document.addEventListener('click', onLinkClick, true);
|
|
348
366
|
stops.push(()=>document.removeEventListener('click', onLinkClick, true));
|
package/dist/script.global.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!/*! Obelism numbers (@obelism/numbers-sdk), MIT. https://numbers.obelism.studio */function(){"use strict";let e="https://numbers.obelism.studio/api/log",t="obelism_visitor_id",n="visitorId",
|
|
1
|
+
!/*! Obelism numbers (@obelism/numbers-sdk), MIT. https://numbers.obelism.studio */function(){"use strict";let e="https://numbers.obelism.studio/api/log",t="obelism_visitor_id",n="visitorId",i="visi",r="obelism_visitor_id",o=(e,t)=>e.get(t)?.slice(0,256)||void 0,s="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",a=e=>{let t=document.cookie.split("; ").find(t=>t.split("=")[0]===e);return t?t.split("=")[1]??"":""},l=(e,t)=>{let n=new Date(Date.now()+2592e6);document.cookie=`${e}=${t};path=/;expires=${n.toUTCString()};SameSite=Lax`+("https:"===location.protocol?";Secure":"")},u=()=>{let e=new Uint8Array(26);crypto.getRandomValues(e);let t="";for(let n of e)t+=s[n%s.length];return`${i}_${t}`},d=["7z","avi","csv","doc","docx","gz","mov","mp3","mp4","pdf","ppt","pptx","rar","tar","txt","wav","xls","xlsx","xml","zip"],c=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,m=()=>"u">typeof window&&"u">typeof document,p=["develop","staging","production"];((s,h)=>{if(!h||s.numbers)return;let f=void 0!==h.dataset.debug,g=e=>{f&&"u">typeof console&&console.warn(`[numbers] ${e}`)},b=h.dataset.organization;if(!b)return g("data-organization is missing; nothing is tracked.");b.startsWith("org_")||g(`data-organization "${b}" is not an org_… id.`);let v=h.dataset.space,w=v&&v.startsWith("space_")?v:void 0;v&&!w&&g(`data-space "${v}" is not a space_… id and will be ignored.`);let y=h.dataset.storage;y&&"session"!==y&&"none"!==y&&g(`data-storage "${y}" is not "session" or "none"; using a cookie.`);let $=h.dataset.environment;$&&!p.includes($)&&g(`data-environment "${$}" is not one of ${p.join(", ")}; using production.`);let _=(({organizationId:s,spaceId:p,environment:h="production",endpoint:f=e,device:g,disableWarnings:b=!1,storage:v="cookie"})=>{let w=null,y=g,$=f,_=0,k=!0,x=null,L=null,S=new Set,E=new Set,A=new Set,z=(e,t)=>{!(b||A.has(e))&&(A.add(e),"u">typeof console&&console.warn(`[numbers] ${t}`))},T=()=>m()?(w??(w=((e="cookie")=>{let o;if("none"===e)return{id:u(),recurring:!1};let s=(e=>{if("session"===e)try{return sessionStorage.getItem(r)??""}catch{return""}return a(t)||a(n)})(e),d=2===(o=s.split("_")).length&&o[0]===i&&26===o[1].length,c=d?s:u();return((e,i)=>{if("session"===e){try{sessionStorage.setItem(r,i)}catch{}return}l(t,i),l(n,i)})(e,c),{id:c,recurring:d}})(v)),w):null,D=(e,t)=>{let n,i,r;return y??(n=navigator.userAgent.toLowerCase(),i=/ipad|tablet|android(?!.*mobile)/.test(n)?"tablet":/mobi|iphone|ipod|android/.test(n)?"mobile":"desktop",y={pointer:"desktop"===i?"fine":"coarse",device:i,browser:/edg\//.test(n)?"edge":/samsungbrowser/.test(n)?"samsung internet":/instagram|fban|fbav|tiktok|snapchat|wechat|line\//.test(n)?"social":/firefox|fxios/.test(n)?"firefox":/chrome|crios|chromium/.test(n)?"chrome":/safari/.test(n)?"safari":"other",os:/iphone|ipad|ipod/.test(n)?"ios":/android/.test(n)?"android":/windows/.test(n)?"windows":/mac os/.test(n)?"mac os":"unix"}),{type:e,organizationId:s,...p&&{spaceId:p},environment:h,visitorId:t.id,pointer:y.pointer,device:y.device,screen:(r=window.innerWidth)<=768?"small":r<=1024?"medium":r<=1200?"large":"huge",browser:y.browser,os:y.os,visitor:t.recurring?"recurring":"new"}},U=(e,t)=>{let n=JSON.stringify(e);return"u">typeof TextEncoder&&new TextEncoder().encode(n).length>8192&&z(`size:${t}`,`"${t}" is over 8192 bytes and will be refused: trim its \`params\`.`),fetch($,{method:"POST",headers:{"Content-Type":"text/plain;charset=UTF-8"},body:n,keepalive:!0}).then(e=>(429===e.status&&(_=Date.now()+((e=>{if(!e)return;let t=Number(e);if(Number.isFinite(t))return Math.max(0,1e3*t);let n=Date.parse(e);return Number.isNaN(n)?void 0:Math.max(0,n-Date.now())})(e.headers.get("Retry-After"))??6e4)),e),()=>void 0)},N=(e,t,n,i)=>{let r,s,a,l,u,d=T();if(!d||Date.now()<_)return null;let{value:c,currency:m,params:p,dedupeKey:h}=n;if(void 0!==h){let t=`${e}:${h}`;if(S.has(t))return null;S.add(t)}let f={...D("event",d),...(r=new URLSearchParams(location.search),s={path:location.pathname.slice(0,1024)},a=o(r,"utm_source"),l=o(r,"utm_medium"),u=o(r,"utm_campaign"),a&&(s.utmSource=a),l&&(s.utmMedium=l),u&&(s.utmCampaign=u),s),event:e.slice(0,256),scope:t.slice(0,256)};return i&&(f.referrer=i),"number"==typeof c&&Number.isFinite(c)&&(f.value=c),m&&(f.currency=m.slice(0,8)),p&&"object"==typeof p&&!Array.isArray(p)&&(f.params=p),U(f,e)},R=(e,t,n={})=>m()?(c.test(e)||z(`name:${e}`,`"${e}" isn't snake_case. Prefer GA4-style names like "add_to_cart" or "sign_up"; \`disableWarnings: true\` silences this.`),N(e,t||location.pathname,n)):null,C=()=>{if(!m())return null;let e=k?(()=>{if(document.referrer)try{let e=new URL(document.referrer);if(e.host===location.host)return;return`${e.origin}${e.pathname}`.slice(0,1024)}catch{return}})():void 0;return k=!1,N("page_view",location.pathname,{},e)},I=(e,t)=>R("form_submit",t||(e=>{let t=e.getAttribute("data-numbers-id")||e.id;if(t)return t;try{let t=new URL(e.action,location.href);return t.origin===location.origin?t.pathname:`${t.origin}${t.pathname}`}catch{return"form"}})(e));return{organizationId:s,spaceId:p,environment:h,visitor:T,setDevice:e=>{y=e},setEndpoint:e=>{$=e},track:R,pageView:C,formSubmit:I,expose:e=>{let t=T();if(!t||Date.now()<_)return null;let n=`${e.subjectKind}:${e.subjectId}`;return E.has(n)?null:(E.add(n),U({...D("exposure",t),...e},n))},autoTrack:({pageViews:e=!0,clicks:t=!0,outbound:n=!1,downloads:i=!1,forms:r=!1}={})=>{if(!m())return()=>{};L?.();let o=[];if(e){let e=()=>{location.pathname!==x&&(x=location.pathname,C())},t=t=>{let n=history[t],i=function(...t){let i=n.apply(this,t);return e(),i};history[t]=i,o.push(()=>{history[t]===i&&(history[t]=n)})};t("pushState"),t("replaceState"),window.addEventListener("popstate",e),o.push(()=>window.removeEventListener("popstate",e)),e()}if(t){let e=e=>{let t=e.target,n=t?.closest?.("[data-numbers-id]")?.getAttribute("data-numbers-id");n&&R("click",n)};document.addEventListener("click",e,!0),o.push(()=>document.removeEventListener("click",e,!0))}if(n||i){let e=e=>{let t,r,o=e.target,s=o?.closest?.("a[href]");if(!s)return;try{t=new URL(s.href,location.href)}catch{return}if(!/^https?:$/.test(t.protocol))return;let a=s.closest("[data-numbers-id]")?.getAttribute("data-numbers-id"),l=t.pathname.split("/").pop()||t.pathname,u=l.includes(".")?l.split(".").pop().toLowerCase():"";i&&(r=t,s.hasAttribute("download")||d.includes(r.pathname.split(".").pop()?.toLowerCase()??""))?R("file_download",a??l,a?void 0:{params:{file_name:u?l.slice(0,-(u.length+1)):l,...u&&{file_extension:u}}}):n&&t.origin!==location.origin&&R("outbound_click",a??`${t.origin}${t.pathname}`,a?void 0:{params:{link_domain:t.hostname}})};document.addEventListener("click",e,!0),o.push(()=>document.removeEventListener("click",e,!0))}if(r){let e=e=>{let t=e.target;t instanceof HTMLFormElement&&I(t)};document.addEventListener("submit",e,!0),o.push(()=>document.removeEventListener("submit",e,!0))}let s=()=>{for(let e of o)e();L===s&&(L=null)};return L=s,s}}})({organizationId:b,spaceId:w,environment:$||"production",endpoint:h.dataset.endpoint||(t=>{let n;try{n=new URL(t)}catch{return e}let i="localhost"===n.hostname||"127.0.0.1"===n.hostname;return"numbers.obelism.studio"===n.hostname||i?`${n.origin}/api/log`:e})(h.src),disableWarnings:!f,storage:"session"===y||"none"===y?y:"cookie"});return s.numbers=_,_.autoTrack()})(window,document.currentScript)}();
|
package/package.json
CHANGED