@storewatch/browser 0.1.0 → 0.2.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 +15 -0
- package/dist/client.d.ts +2 -0
- package/dist/identity.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/types.d.ts +13 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,5 +12,20 @@ sw.track("product_viewed", { productId: "prod_123", price: money(1249, "SEK") })
|
|
|
12
12
|
sw.setConsent("granted"); // "pending" (default, cookieless) | "granted" | "denied"
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
**Payment redirects.** Before consent the session lives only in memory, so a
|
|
16
|
+
shopper who leaves for Klarna, PayPal or 3-D Secure would come back as a new
|
|
17
|
+
session. Pass the return URL through `linkUrl` and the session continues; the
|
|
18
|
+
store page that receives it removes the parameter from the address bar:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
stripe.confirmPayment({ elements, confirmParams: { return_url: sw.linkUrl(returnUrl) } });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If the return URL is a server route that redirects onwards, forward the `_sws`
|
|
25
|
+
query parameter to the page it redirects to.
|
|
26
|
+
|
|
27
|
+
A visible tab counts as online only while someone uses it: after 5 minutes
|
|
28
|
+
without a click, scroll, tap or key press it stops (`presenceIdleMs`).
|
|
29
|
+
|
|
15
30
|
Use your **public** key here. Secret keys belong on your server
|
|
16
31
|
(`@storewatch/node`). On Next.js, use `@storewatch/nextjs`.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Storewatch, StorewatchOptions } from "./types.js";
|
|
2
|
+
/** The session in a `linkUrl` link, if it's well-formed and recent enough to still be alive. */
|
|
3
|
+
export declare function readSessionLink(value: string | null, now?: number): string | null;
|
|
2
4
|
export declare function createStorewatch(options: StorewatchOptions): Storewatch;
|
|
3
5
|
/**
|
|
4
6
|
* Idempotent page-level singleton: calling `init` twice (React StrictMode,
|
package/dist/identity.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare function createIdentity(initial: Consent): {
|
|
|
24
24
|
* session, or "online", alive forever.
|
|
25
25
|
*/
|
|
26
26
|
touch(now?: number, activity?: boolean): Session | null;
|
|
27
|
+
/**
|
|
28
|
+
* Continue a session carried over a redirect (see `linkUrl`). It keeps
|
|
29
|
+
* the id but not the landing attribution: the payment page the shopper
|
|
30
|
+
* returns from is not where they came from.
|
|
31
|
+
*/
|
|
32
|
+
adopt(id: string, now?: number): void;
|
|
27
33
|
current: () => Session | null;
|
|
28
34
|
visitorId: () => string | null;
|
|
29
35
|
};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function x(e=Date.now()){let t=crypto.getRandomValues(new Uint8Array(16)),n=e;for(let a=5;a>=0;a--)t[a]=n&255,n=Math.floor(n/256);t[6]=t[6]&15|112,t[8]=t[8]&63|128;let r=Array.from(t,a=>a.toString(16).padStart(2,"0")).join("");return`${r.slice(0,8)}-${r.slice(8,12)}-${r.slice(12,16)}-${r.slice(16,20)}-${r.slice(20)}`}function M(){return x().replace(/-/g,"").slice(-20)}var w="sw_s",k="sw_v",_=30*6e4,U=e=>{try{return localStorage.getItem(e)}catch{return null}},S=(e,t)=>{try{t===null?localStorage.removeItem(e):localStorage.setItem(e,t)}catch{}};function Q(){let e={};try{let t=new URLSearchParams(location.search),n={};for(let r of["source","medium","campaign","term","content"]){let a=t.get(`utm_${r}`);a&&(n[r]=a.slice(0,200))}Object.keys(n).length>0&&(e.utm=n),document.referrer&&(e.referrer=document.referrer)}catch{}return e}function V(e){let t=e,n=null,r=null;function a(){if(t==="granted"){try{let s=JSON.parse(U(w)??"null");s&&typeof s.id=="string"&&Date.now()-s.last<_&&(n??(n=s))}catch{}r=U(k),r||(r=M(),S(k,r))}}return a(),{consent:()=>t,setConsent(s){t=s,s==="denied"?(n=null,r=null,S(w,null),S(k,null)):s==="granted"&&(a(),n&&S(w,JSON.stringify(n)))},touch(s=Date.now(),u=!0){if(t==="denied")return null;let h=!n||s-n.last>=_;return u?(h&&(n={id:M(),last:s,...Q()}),n?(n.last=s,t==="granted"&&S(w,JSON.stringify(n)),n):null):h?null:n},adopt(s,u=Date.now()){t!=="denied"&&(n={id:s,last:u},t==="granted"&&S(w,JSON.stringify(n)))},current:()=>n,visitorId:()=>t==="granted"?r:null}}var q=["page_viewed","product_viewed","collection_viewed","search_performed","add_to_cart","remove_from_cart","cart_updated","checkout_started","checkout_step_viewed","customer_information_entered","shipping_selected","payment_started","payment_failed","payment_authorized","order_completed","order_cancelled","refund_created","session_heartbeat"],ue=new Set(q);var T={batchMaxEvents:100,batchMaxBytes:256*1024,propertiesMaxBytes:4*1024,propertiesMaxDepth:4,stringMaxLength:1024,urlMaxLength:2048,idMaxLength:128,sessionIdMaxLength:64,eventNameMaxLength:64,searchQueryMaxLength:100,orderMaxItems:100};function $(e){return new Intl.NumberFormat("en",{style:"currency",currency:e}).resolvedOptions().maximumFractionDigits??2}function W(e,t){let n=t.toUpperCase(),r=10**$(n);return{amount:Math.round(e*r),currency:n}}function X(e){return e.amount/10**$(e.currency)}var Y=5,Z=6e4,G=60*1024,j=(e,...t)=>{e&&console.warn("[storewatch]",...t)};function F(e){let t=[],n,r=!1,a=0,s=0,u=i=>JSON.stringify({key:e.publicKey,sentAt:new Date().toISOString(),batch:i});function h(i=e.flushIntervalMs){n===void 0&&(n=setTimeout(()=>(n=void 0,void g()),i))}async function g(){if(r||t.length===0)return;r=!0;let i=t.splice(0,T.batchMaxEvents),f=!1;try{let c=await fetch(e.endpoint,{method:"POST",body:u(i),headers:{"content-type":"text/plain;charset=UTF-8"},keepalive:!0,credentials:"omit"});f=c.status===429||c.status>=500,!f&&c.status>=400&&j(e.debug,`ingest rejected batch (${c.status})`)}catch(c){f=!0,j(e.debug,"ingest unreachable",c)}if(r=!1,f){s++,++a<Y?t=i.concat(t).slice(-e.maxQueue):a=0,h(Math.min(Z,1e3*2**s));return}s=0,a=0,t.length>0&&h(t.length>=e.flushAt?0:e.flushIntervalMs)}return{enqueue(i){t.push(i),t.length>e.maxQueue&&t.shift(),t.length>=e.flushAt&&s===0?g():h()},flush:g,beacon(){for(n!==void 0&&clearTimeout(n),n=void 0;t.length>0;){let i=t.splice(0,T.batchMaxEvents);for(;i.length>1&&u(i).length>G;)t.unshift(...i.splice(Math.ceil(i.length/2)));let f=u(i),c=!1;try{c=navigator.sendBeacon(e.endpoint,new Blob([f],{type:"text/plain;charset=UTF-8"}))}catch{c=!1}c||fetch(e.endpoint,{method:"POST",body:f,keepalive:!0,credentials:"omit",headers:{"content-type":"text/plain;charset=UTF-8"}}).catch(()=>{})}},clear(){t=[],n!==void 0&&clearTimeout(n),n=void 0},size:()=>t.length}}var ee="0.2.0",I="_sws",te=5*6e4;function ne(e,t=Date.now()){let n=e?.match(/^([0-9a-f]{20})\.([0-9a-z]{1,11})$/);if(!n)return null;let r=t-parseInt(n[2],36);return r>=-6e4&&r<_?n[1]:null}var O={track(){},page(){},identify(){},setConsent(){},ids:()=>({sessionId:"",anonymousId:null}),linkUrl:e=>e,flush(){},shutdown(){}};function d(e,t,n){return(...r)=>{try{return t(...r)}catch(a){return e&&console.warn("[storewatch]",a),n}}}function H(e){if(typeof window>"u"||typeof document>"u")return O;if(!/^sw_pk_/.test(e.publicKey))return console.error("[storewatch] publicKey must be a public key (sw_pk_\u2026). Secret keys must never reach the browser."),O;let t=e.debug??!1,n=V(e.consent??"pending"),r=F({endpoint:e.endpoint??"/_sw/v1/batch",publicKey:e.publicKey,flushAt:e.flushAt??20,flushIntervalMs:e.flushIntervalMs??2e3,maxQueue:500,debug:t}),a=null,s="",u=Date.now();d(t,()=>{let o=new URL(location.href);if(!o.searchParams.has(I))return;let l=ne(o.searchParams.get(I));l&&!n.touch(Date.now(),!1)&&n.adopt(l),o.searchParams.delete(I),history.replaceState(history.state,"",o.pathname+o.search+o.hash)},void 0)();function h(o,l){let m=o!=="session_heartbeat";m&&(u=Date.now());let y=n.touch(Date.now(),m);if(!y)return null;let K=n.visitorId(),J=n.consent()==="granted";return{eventId:x(),name:o,timestamp:new Date().toISOString(),sessionId:y.id,...K?{anonymousId:K}:{},...J&&a?{customerId:a}:{},properties:l,context:{page:{url:location.href,path:location.pathname,title:document.title.slice(0,200),...y.referrer?{referrer:y.referrer}:{}},...y.utm?{utm:y.utm}:{},locale:navigator.language,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,screen:{width:innerWidth,height:innerHeight},...e.currency?{currency:e.currency}:{},library:{name:"@storewatch/browser",version:ee},consent:n.consent()}}}let g=d(t,(o,l={})=>{let m=h(o,l);m&&r.enqueue(m)},void 0),i=d(t,(o={})=>{s=location.pathname;let l=e.pageType?.(location.pathname);g("page_viewed",{...l?{pageType:l}:{},...o})},void 0),f=!0,c=()=>{f&&location.pathname!==s&&i()},A=history.pushState,L=history.replaceState,N=function(...o){A.apply(this,o),c()},R=function(...o){L.apply(this,o),c()};(e.autoPageViews??!0)&&(history.pushState=N,history.replaceState=R,addEventListener("popstate",c));let z=e.presenceIdleMs??te,E=()=>{u=Date.now()},C=["pointerdown","pointermove","keydown","scroll","wheel","touchstart"];for(let o of C)addEventListener(o,E,{capture:!0,passive:!0});let B=setInterval(d(t,()=>{document.visibilityState==="visible"&&Date.now()-u<z&&n.current()&&g("session_heartbeat")},void 0),e.heartbeatIntervalMs??2e4),P=d(t,()=>{document.visibilityState==="hidden"?r.beacon():E()},void 0),D=d(t,()=>r.beacon(),void 0);return document.addEventListener("visibilitychange",P),addEventListener("pagehide",D),(e.autoPageViews??!0)&&i(),{track:g,page:i,identify:d(t,o=>{a=o},void 0),setConsent:d(t,o=>{n.setConsent(o),o==="denied"&&r.clear()},void 0),ids:d(t,()=>({sessionId:n.touch(Date.now(),!1)?.id??"",anonymousId:n.visitorId()}),{sessionId:"",anonymousId:null}),linkUrl:o=>{try{let l=n.touch(Date.now(),!1);if(!l)return o;let m=new URL(o,location.href);return m.searchParams.set(I,`${l.id}.${Date.now().toString(36)}`),m.toString()}catch(l){return t&&console.warn("[storewatch]",l),o}},flush:d(t,()=>{r.flush()},void 0),shutdown:d(t,()=>{f=!1,clearInterval(B);for(let o of C)removeEventListener(o,E,{capture:!0});r.beacon(),document.removeEventListener("visibilitychange",P),removeEventListener("pagehide",D),removeEventListener("popstate",c),history.pushState===N&&(history.pushState=A),history.replaceState===R&&(history.replaceState=L)},void 0)}}var p=null,re=100,b=[];function oe(e){if(p?.key===e.publicKey)return p.client;p?.client.shutdown(),p={key:e.publicKey,client:H(e)};let t=b;b=[];for(let[n,r]of t)p.client[n](...r);return p.client}var v=e=>(...t)=>{if(p)return p.client[e](...t);b.length<re&&b.push([e,t])},se={track:v("track"),page:v("page"),identify:v("identify"),setConsent:v("setConsent"),ids:()=>p?.client.ids()??O.ids(),linkUrl:e=>p?.client.linkUrl(e)??e,flush:v("flush"),shutdown:()=>p?.client.shutdown()};function ae(){return se}export{H as createStorewatch,ae as getStorewatch,oe as init,W as money,X as toMajor};
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ export type StorewatchOptions = {
|
|
|
30
30
|
/** Default ISO-4217 currency added to event context. */
|
|
31
31
|
currency?: string;
|
|
32
32
|
heartbeatIntervalMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* A visible tab only counts as "online" if the shopper moved, scrolled,
|
|
35
|
+
* tapped or typed within this long. Default 5 minutes.
|
|
36
|
+
*/
|
|
37
|
+
presenceIdleMs?: number;
|
|
33
38
|
flushAt?: number;
|
|
34
39
|
flushIntervalMs?: number;
|
|
35
40
|
/** Log delivery problems to the console. Default false. */
|
|
@@ -46,6 +51,14 @@ export type Storewatch = {
|
|
|
46
51
|
sessionId: string;
|
|
47
52
|
anonymousId: string | null;
|
|
48
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* `url` with the current session attached, for a page that leaves the
|
|
56
|
+
* store and comes back (a payment provider's return URL). Without stored
|
|
57
|
+
* consent the session lives only in memory, so the full page load on
|
|
58
|
+
* return would otherwise start a new one. The receiving page picks the
|
|
59
|
+
* parameter up and removes it from the address bar.
|
|
60
|
+
*/
|
|
61
|
+
linkUrl(url: string): string;
|
|
49
62
|
flush(): void;
|
|
50
63
|
shutdown(): void;
|
|
51
64
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storewatch/browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Storewatch browser SDK: lightweight, consent-aware commerce tracking.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@storewatch/schema": "0.
|
|
22
|
+
"@storewatch/schema": "0.2.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"happy-dom": "^21.0.1",
|