tracwell 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 +33 -1
- package/dist/npm/config.d.ts +1 -1
- package/dist/npm/index.d.ts +1 -1
- package/dist/npm/index.js +1 -1
- package/dist/npm/types.d.ts +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ window.tracwell?.track("signup_completed", { plan: "starter" });
|
|
|
27
27
|
|
|
28
28
|
The script derives `/v1/events` from its own origin, so the same artifact works
|
|
29
29
|
against the loopback Collector during local development. The production bundle
|
|
30
|
-
reports SDK version `0.
|
|
30
|
+
reports SDK version `0.2.0`, targets ES2022-capable browsers, and has a 4.7 KB
|
|
31
31
|
gzip budget enforced by
|
|
32
32
|
`build:cdn`.
|
|
33
33
|
|
|
@@ -49,12 +49,32 @@ const tracwell = createTracwell({
|
|
|
49
49
|
|
|
50
50
|
tracwell.track("signup_completed", { plan: "starter" });
|
|
51
51
|
tracwell.identify("customer_123");
|
|
52
|
+
|
|
53
|
+
const session = tracwell.getSession();
|
|
52
54
|
```
|
|
53
55
|
|
|
54
56
|
`createTracwell()` starts collection immediately in the browser and captures
|
|
55
57
|
the initial page view by default. It must be called after the document is
|
|
56
58
|
available, not during server rendering.
|
|
57
59
|
|
|
60
|
+
## Framework compatibility
|
|
61
|
+
|
|
62
|
+
The npm SDK is a framework-neutral ESM browser library. It has no React, Vue,
|
|
63
|
+
Svelte, Angular, router, or rendering-framework dependency. Importing it during
|
|
64
|
+
SSR is safe; call `createTracwell()` from that framework's browser mount or
|
|
65
|
+
hydration lifecycle after `document` is available.
|
|
66
|
+
|
|
67
|
+
Multi-page applications capture a page view each time the browser loads the
|
|
68
|
+
application. Single-page applications using the standard History API receive
|
|
69
|
+
automatic page views for `pushState`, `replaceState`, and back/forward
|
|
70
|
+
navigation. This covers the default path-based routers in modern web
|
|
71
|
+
frameworks. Hash fragments remain intentionally excluded from analytics URLs;
|
|
72
|
+
hash-only navigation does not create a page view.
|
|
73
|
+
|
|
74
|
+
Framework adapters should own only lifecycle integration and dependency
|
|
75
|
+
injection. Event validation, identity, attribution, navigation observation,
|
|
76
|
+
batching, retries, consent, and delivery remain in this core package.
|
|
77
|
+
|
|
58
78
|
## Responsibilities
|
|
59
79
|
|
|
60
80
|
- Generate stable event and batch IDs before delivery.
|
|
@@ -90,6 +110,17 @@ Collection mode is explicit:
|
|
|
90
110
|
reported as external acquisition.
|
|
91
111
|
- `identify()` validates the supplied opaque user ID before associating it with
|
|
92
112
|
future events.
|
|
113
|
+
- Identifying a different user on the same installation rotates both anonymous
|
|
114
|
+
identity and session before recording the new identity. Call `reset()` on
|
|
115
|
+
logout so an anonymous post-logout visit also starts cleanly.
|
|
116
|
+
- `getSession()` returns the active Product-mode `anonymousId`, `sessionId`,
|
|
117
|
+
and optional `userId` so a server-created checkout can carry the same
|
|
118
|
+
identifiers into an authenticated revenue event. It returns `undefined` in
|
|
119
|
+
Private mode, without consent, when Do Not Track blocks collection, or after
|
|
120
|
+
shutdown.
|
|
121
|
+
- Treat the returned IDs as attribution context, not authorization. Revenue
|
|
122
|
+
`user_id` must come from the authenticated account or verified payment
|
|
123
|
+
webhook rather than a client-supplied value.
|
|
93
124
|
- Hash-only URL changes do not create duplicate SPA page views.
|
|
94
125
|
- URL fragments are excluded from captured URLs and `path` contains only the
|
|
95
126
|
pathname.
|
|
@@ -148,6 +179,7 @@ src/
|
|
|
148
179
|
runtime.ts Browser API adapter
|
|
149
180
|
types.ts Public configuration, client, delivery, and error types
|
|
150
181
|
client.test.ts Deterministic SDK behavior and failure tests
|
|
182
|
+
index.test.ts SSR import and browser-initialization boundary
|
|
151
183
|
scripts/
|
|
152
184
|
build-cdn.mjs Minified browser build and gzip-size enforcement
|
|
153
185
|
build-npm.mjs Self-contained ESM package build
|
package/dist/npm/config.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const DEFAULT_BATCH_SIZE = 25;
|
|
|
4
4
|
export declare const DEFAULT_FLUSH_INTERVAL_MS = 10000;
|
|
5
5
|
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
6
6
|
export declare const DEFAULT_SESSION_TIMEOUT_MS: number;
|
|
7
|
-
export declare const SDK_VERSION = "0.
|
|
7
|
+
export declare const SDK_VERSION = "0.2.0";
|
|
8
8
|
export declare function normalizeProjectKey(value: string, onError?: (error: SdkError) => void): string;
|
|
9
9
|
export declare function normalizeCollectorEndpoint(value: string, onError?: (error: SdkError) => void): string;
|
|
10
10
|
export declare function normalizeCollectorUrl(endpoint: URL, onError?: (error: SdkError) => void): string;
|
package/dist/npm/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { DEFAULT_BATCH_SIZE, DEFAULT_COLLECTOR_ENDPOINT, DEFAULT_FLUSH_INTERVAL_MS, DEFAULT_MAX_RETRIES, DEFAULT_SESSION_TIMEOUT_MS, SDK_VERSION, } from "./config";
|
|
2
|
-
export type { ContractIssue, ContractIssueCode, ConsentState, DeliveryReport, EventProperties, JsonPrimitive, JsonValue, SdkError, SdkErrorCode, TracwellClient, TracwellConfig, } from "./types";
|
|
2
|
+
export type { ContractIssue, ContractIssueCode, ConsentState, DeliveryReport, EventProperties, JsonPrimitive, JsonValue, SdkError, SdkErrorCode, TracwellClient, TracwellConfig, TracwellSession, } from "./types";
|
|
3
3
|
export declare function createTracwell(config: import("./types").TracwellConfig): import("./types").TracwellClient;
|
package/dist/npm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var D="https://collect.tracwell.app/v1/events",k=25,U=1e4,O=2,H=1800*1e3,b="0.1.0";function m(e,t){throw t?.({code:"CONFIG_INVALID",message:e}),new TypeError(e)}function tt(e,t){let n=e.trim();return n.length<20||n.length>128||!/^[A-Za-z0-9_-]+$/u.test(n)?m("projectKey does not match the public project-key contract.",t):n}function et(e,t){let n;try{n=new URL(e)}catch{return m("endpoint must be an absolute HTTPS URL.",t)}return nt(n,t)}function nt(e,t){let n=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname==="[::1]";return e.protocol!=="https:"&&!n?m("endpoint must use HTTPS unless it uses a loopback host.",t):e.toString()}function E(e,t,n,r,i){return!Number.isSafeInteger(t)||t<n||t>r?m(`${e} must be an integer between ${n} and ${r}.`,i):t}function B(e){let t=tt(e.projectKey,e.onError),n=e.collectionMode??"private",r=et(e.endpoint??D,e.onError);return n==="private"&&e.persistence!==void 0&&e.persistence!=="none"?m('Private collection requires persistence: "none".',e.onError):{autoTrackPageViews:e.autoTrackPageViews??!0,batchSize:E("batchSize",e.batchSize??k,1,50,e.onError),consent:e.consent??"granted",collectionMode:n,endpoint:r,flushIntervalMs:E("flushIntervalMs",e.flushIntervalMs??U,1e3,6e4,e.onError),maxRetries:E("maxRetries",e.maxRetries??O,0,5,e.onError),persistence:n==="private"?"none":e.persistence??"localStorage",projectKey:t,respectDoNotTrack:e.respectDoNotTrack??!0,sessionTimeoutMs:E("sessionTimeoutMs",e.sessionTimeoutMs??H,6e4,1440*60*1e3,e.onError),...e.onDelivery?{onDelivery:e.onDelivery}:{},...e.onError?{onError:e.onError}:{}}}var X=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"];function rt(e){let t=e?.trim();return t||void 0}function it(e){if(e)try{return new URL(e).toString()}catch{return}}function G(e){let t=new URL(e.href),n={},r=it(e.referrer);r&&(n.referrer=r);for(let i of X){let o=rt(t.searchParams.get(i));o&&(n[i]=o)}return n}function A(e){let t=X.map(n=>e[n]??"").join("");return t.replaceAll("","")?t:void 0}var N=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu,_t=/^[A-Za-z][A-Za-z0-9_.:-]*$/u,yt=/[\s\x00-\x1f\x7f]/u,Tt=/[\x00-\x1f\x7f]/u;function L(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}function R(e){return e.length>0&&e.length<=256&&!yt.test(e)}function Y(e,t=2048){if(e.length>t)return!1;try{return new URL(e).origin.length>0}catch{return!1}}function w(e,t,n){return t>4?!1:typeof e=="string"?e.length<=1024:typeof e=="number"?Number.isFinite(e):e===null||typeof e=="boolean"?!0:Array.isArray(e)?e.length<=50&&e.every(r=>w(r,t+1,n)):Object.entries(e).every(([r,i])=>(n.count+=1,n.count<=100&&r.length>0&&r.length<=128&&!Tt.test(r)&&w(i,t+1,n)))}function K(e){let{context:t}=e;try{if(L(e)>16384)return!1}catch{return!1}return N.test(e.event_id)&&R(e.anonymous_id)&&R(e.session_id)&&(e.user_id===void 0||R(e.user_id))&&(e.event_type!=="event"||e.event_name.length<=100&&_t.test(e.event_name)&&e.event_name!=="identify"&&e.event_name!=="page_view")&&Y(t.url)&&t.path.startsWith("/")&&t.path.length<=1024&&(t.title===void 0||t.title.length<=512)&&(t.referrer===void 0||Y(t.referrer))&&[t.utm_source,t.utm_medium,t.utm_campaign,t.utm_term,t.utm_content].every(n=>n===void 0||n.length>0&&n.length<=256)&&(e.properties===void 0||w(e.properties,0,{count:0}))}function z(e){if(!e||typeof e!="object")return;let t=e;if(!(t.receipt_version!==1||typeof t.receipt_id!="string"||!N.test(t.receipt_id)||typeof t.batch_id!="string"||!N.test(t.batch_id)||!Number.isInteger(t.accepted_events)||(t.accepted_events??0)<1||(t.accepted_events??0)>50||typeof t.accepted_at!="string"||Number.isNaN(Date.parse(t.accepted_at))))return t}function St(e,t){let n=new URL(e.href);return n.hash="",{path:n.pathname,url:n.toString(),...e.title?{title:e.title}:{},...t}}function j(e,t,n,r){let i={schema_version:1,event_id:e.randomUUID(),event_name:n.eventName,event_type:n.eventType,timestamp:new Date(e.now()).toISOString(),anonymous_id:t.anonymousId,session_id:t.sessionId,context:St(e.page(),t.attribution),...n.properties?{properties:n.properties}:{},...n.userId?{user_id:n.userId}:{}};if(!K(i)){r?.({code:"EVENT_INVALID",message:"Event was not queued because it failed the v1 contract."});return}return i}var F=1;function gt(e){if(!e||typeof e!="object")return!1;let t=e;return t.version===F&&typeof t.anonymousId=="string"&&typeof t.sessionId=="string"&&typeof t.lastActivityAt=="number"&&typeof t.attribution=="object"&&t.attribution!==null&&(t.userId===void 0||typeof t.userId=="string")}var _=class{#t;#o;#e;#i;#r;#n;constructor(t,n){this.#e=t,this.#o=n.onError,this.#i=n.sessionTimeoutMs,this.#t=`tracwell:${n.projectKey}:identity`,this.#r=n.persistence==="localStorage"?this.#p(t):void 0}identify(t,n){let r=this.#d(n);return r.userId=t,this.#h(r),this.#s(r)}reset(){if(this.#n=void 0,this.#r)try{this.#r.removeItem(this.#t)}catch(t){this.#a(t)}}snapshot(t){return this.#s(this.#d(t))}#c(t){let n=this.#e.now();return{anonymousId:this.#e.randomUUID(),attribution:t,lastActivityAt:n,sessionId:this.#e.randomUUID(),version:F}}#d(t){let n=this.#e.now(),r=this.#u();if(!r){let a=this.#c(t);return this.#h(a),a}let i=A(r.attribution),o=A(t);return(n-r.lastActivityAt>=this.#i||o!==void 0&&o!==i)&&(r.sessionId=this.#e.randomUUID(),r.attribution=t),r.lastActivityAt=n,this.#h(r),r}#u(){if(this.#n)return this.#n;if(this.#r)try{let t=this.#r.getItem(this.#t);if(!t)return;let n=JSON.parse(t);if(!gt(n)){this.#r.removeItem(this.#t);return}return this.#n=n,n}catch(t){this.#a(t);return}}#p(t){try{return t.storage()}catch(n){this.#a(n);return}}#a(t){this.#o?.({cause:t,code:"STORAGE_UNAVAILABLE",message:"Persistent identity storage is unavailable; using in-memory identity."})}#s(t){return{anonymousId:t.anonymousId,attribution:t.attribution,sessionId:t.sessionId,...t.userId?{userId:t.userId}:{}}}#h(t){if(this.#n=t,!!this.#r)try{this.#r.setItem(this.#t,JSON.stringify(t))}catch(n){this.#a(n)}}};function f(e,t,n,r){return{...r===void 0?{}:{cause:r},code:e,eventIds:n.batch.events.map(i=>i.event_id),message:t}}var y=class{#t;#o;#e;#i;#r;#n=[];constructor(t,n){this.#o=t,this.#t=n}enqueue(t){this.#n.push(t),this.#n.length>=this.#t.batchSize&&this.flush()}clear(){this.#i?.(),this.#i=void 0,this.#e=void 0,this.#n=[]}flush(){return this.#r?this.#r:(this.#r=this.#c().finally(()=>{this.#r=void 0}),this.#r)}flushWithBeacon(){for(this.#i?.(),this.#i=void 0;this.#e||this.#n.length>0;){let t=this.#e??this.#d();if(!t)return;let n=JSON.stringify(t.batch);if(!this.#o.sendBeacon(this.#t.endpoint,n)){this.#t.onError?.(f("DELIVERY_FAILED","Browser lifecycle handoff was rejected by sendBeacon.",t));return}this.#e=void 0,this.#u({batchId:t.batch.batch_id,eventIds:t.batch.events.map(r=>r.event_id),status:"handed_off"})}}async#c(){this.#i?.(),this.#i=void 0;let t=this.#e??this.#d();if(!t)return;await this.#a(t)&&this.#n.length>0&&await this.#c()}#d(){if(this.#n.length===0)return;let t=this.#n.slice(0,this.#t.batchSize),n;for(;t.length>0;){let r={batch_version:1,batch_id:this.#o.randomUUID(),project_key:this.#t.projectKey,sent_at:new Date(this.#o.now()).toISOString(),sdk:{name:"tracwell-browser",version:b},events:t};if(L(r)<=49152){n=r;break}t.pop()}if(!n){let r=this.#n.shift();r&&this.#t.onError?.({code:"EVENT_INVALID",eventIds:[r.event_id],message:"Event cannot fit in a valid browser batch."});return}return this.#n.splice(0,n.events.length),this.#e={attempt:0,batch:n},this.#e}#u(t){this.#t.onDelivery?.(t)}#p(t){let n=250*2**(t.attempt-1);this.#i=this.#o.setTimeout(()=>{this.#i=void 0,this.flush()},n)}async#a(t){let n;try{n=await this.#o.fetch(this.#t.endpoint,JSON.stringify(t.batch),"text/plain;charset=UTF-8")}catch(o){return this.#e!==t?!1:this.#s(t,o)}if(n.status!==202)return n.status===408||n.status===429||n.status>=500?this.#s(t,new Error(`Collector returned HTTP ${n.status}.`)):(this.#e===t&&(this.#e=void 0),this.#t.onError?.(f("DELIVERY_REJECTED",`Collector rejected the batch with non-retryable HTTP ${n.status}.`,t)),!1);let r;try{r=await n.json()}catch(o){return this.#t.onError?.(f("RECEIPT_INVALID","Collector returned an unreadable acceptance receipt.",t,o)),this.#s(t,o)}let i=z(r);return!i||i.batch_id!==t.batch.batch_id||i.accepted_events!==t.batch.events.length?(this.#t.onError?.(f("RECEIPT_INVALID","Collector receipt does not match the submitted batch.",t)),this.#s(t,new Error("Acceptance receipt mismatch."))):(this.#e===t&&(this.#e=void 0),this.#u({batchId:t.batch.batch_id,eventIds:t.batch.events.map(o=>o.event_id),receiptId:i.receipt_id,status:"accepted"}),!0)}#s(t,n){return this.#e!==t?!1:(t.attempt+=1,t.attempt<=this.#t.maxRetries?(this.#t.onError?.(f("DELIVERY_FAILED","Event batch delivery failed and is scheduled for bounded retry.",t,n)),this.#p(t),!1):(this.#e=void 0,this.#t.onError?.(f("DELIVERY_EXHAUSTED","Event batch delivery exhausted the configured retry limit.",t,n)),!1))}};function J(e,t){let n=new _(e,t),r=new y(e,t),i=t.consent,o,u=!1,p=!1,a=[];function T(){return i==="granted"&&!(t.respectDoNotTrack&&e.doNotTrackEnabled())}function S(s,c,d,h){if(p||!T())return;let V=G(e.page()),M=n.snapshot(V),l=h??M.userId,W=s==="identify"&&l?{eventName:"identify",eventType:"identify",...d?{properties:d}:{},userId:l}:s==="pageview"?{eventName:"page_view",eventType:"pageview",...d?{properties:d}:{},...l?{userId:l}:{}}:{eventName:c,eventType:"event",...d?{properties:d}:{},...l?{userId:l}:{}},v=j(e,M,W,t.onError);if(v)return s==="identify"&&h&&n.identify(h,V),r.enqueue(v),v.event_id}function g(s){if(!T())return;let c=new URL(e.page().href);c.hash="";let d=c.toString();if(d===o)return;let h=S("pageview","page_view",s);return h&&(o=d),h}function P(){a.forEach(s=>s()),a=[],u=!1}function x(){u||p||!T()||(u=!0,a=[e.addPageHideListener(()=>r.flushWithBeacon()),e.addVisibilityHiddenListener(()=>r.flushWithBeacon()),e.setInterval(()=>{r.flush()},t.flushIntervalMs)],t.autoTrackPageViews&&(a.push(e.subscribeToNavigation(()=>{g()})),g()))}return x(),{flush(){return r.flush()},identify(s,c){if(t.collectionMode==="private"){t.onError?.({code:"IDENTIFY_UNAVAILABLE",message:"identify() is unavailable in Private collection mode."});return}return S("identify","identify",c,s)},page:g,reset(){n.reset(),o=void 0},setConsent(s){if(i=s,s==="denied"){P(),r.clear(),n.reset(),o=void 0;return}x()},async shutdown(){p||(P(),await r.flush(),p=!0)},track(s,c){return S("event",s,c)}}}function $(e,t){return window.addEventListener(e,t),()=>window.removeEventListener(e,t)}function q(){return{addPageHideListener(e){return $("pagehide",e)},addVisibilityHiddenListener(e){let t=()=>{document.visibilityState==="hidden"&&e()};return document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},doNotTrackEnabled(){return navigator.doNotTrack==="1"},async fetch(e,t,n){return fetch(e,{body:t,credentials:"omit",headers:{"content-type":n},keepalive:!0,method:"POST"})},now(){return Date.now()},page(){return{href:window.location.href,referrer:document.referrer,title:document.title}},randomUUID(){return crypto.randomUUID()},sendBeacon(e,t){return navigator.sendBeacon(e,t)},setInterval(e,t){let n=window.setInterval(e,t);return()=>window.clearInterval(n)},setTimeout(e,t){let n=window.setTimeout(e,t);return()=>window.clearTimeout(n)},storage(){return window.localStorage},subscribeToNavigation(e){let t=!0,n=history.pushState,r=history.replaceState,i=()=>{t&&queueMicrotask(e)},o=function(...a){n.apply(history,a),i()},u=function(...a){r.apply(history,a),i()};history.pushState=o,history.replaceState=u;let p=$("popstate",e);return()=>{t=!1,p(),history.pushState===o&&(history.pushState=n),history.replaceState===u&&(history.replaceState=r)}}}}function Ft(e){if(typeof window>"u"||typeof document>"u"||typeof navigator>"u")throw new Error("createTracwell must be called in a browser after the document is available.");return J(q(),B(e))}export{k as DEFAULT_BATCH_SIZE,D as DEFAULT_COLLECTOR_ENDPOINT,U as DEFAULT_FLUSH_INTERVAL_MS,O as DEFAULT_MAX_RETRIES,H as DEFAULT_SESSION_TIMEOUT_MS,b as SDK_VERSION,Ft as createTracwell};
|
|
1
|
+
var k="https://collect.tracwell.app/v1/events",O=25,H=1e4,B=2,X=1800*1e3,b="0.2.0";function E(e,t){throw t?.({code:"CONFIG_INVALID",message:e}),new TypeError(e)}function et(e,t){let n=e.trim();return n.length<20||n.length>128||!/^[A-Za-z0-9_-]+$/u.test(n)?E("projectKey does not match the public project-key contract.",t):n}function nt(e,t){let n;try{n=new URL(e)}catch{return E("endpoint must be an absolute HTTPS URL.",t)}return rt(n,t)}function rt(e,t){let n=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname==="[::1]";return e.protocol!=="https:"&&!n?E("endpoint must use HTTPS unless it uses a loopback host.",t):e.toString()}function y(e,t,n,r,i){return!Number.isSafeInteger(t)||t<n||t>r?E(`${e} must be an integer between ${n} and ${r}.`,i):t}function G(e){let t=et(e.projectKey,e.onError),n=e.collectionMode??"private",r=nt(e.endpoint??k,e.onError);return n==="private"&&e.persistence!==void 0&&e.persistence!=="none"?E('Private collection requires persistence: "none".',e.onError):{autoTrackPageViews:e.autoTrackPageViews??!0,batchSize:y("batchSize",e.batchSize??O,1,50,e.onError),consent:e.consent??"granted",collectionMode:n,endpoint:r,flushIntervalMs:y("flushIntervalMs",e.flushIntervalMs??H,1e3,6e4,e.onError),maxRetries:y("maxRetries",e.maxRetries??B,0,5,e.onError),persistence:n==="private"?"none":e.persistence??"localStorage",projectKey:t,respectDoNotTrack:e.respectDoNotTrack??!0,sessionTimeoutMs:y("sessionTimeoutMs",e.sessionTimeoutMs??X,6e4,1440*60*1e3,e.onError),...e.onDelivery?{onDelivery:e.onDelivery}:{},...e.onError?{onError:e.onError}:{}}}var Y=["utm_source","utm_medium","utm_campaign","utm_term","utm_content"];function it(e){let t=e?.trim();return t||void 0}function ot(e){if(e)try{return new URL(e).toString()}catch{return}}function A(e){let t=new URL(e.href),n={},r=ot(e.referrer);r&&(n.referrer=r);for(let i of Y){let o=it(t.searchParams.get(i));o&&(n[i]=o)}return n}function R(e){let t=Y.map(n=>e[n]??"").join("");return t.replaceAll("","")?t:void 0}var w=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu,yt=/^[A-Za-z][A-Za-z0-9_.:-]*$/u,Tt=/[\s\x00-\x1f\x7f]/u,St=/[\x00-\x1f\x7f]/u;function C(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}function N(e){return e.length>0&&e.length<=256&&!Tt.test(e)}function K(e,t=2048){if(e.length>t)return!1;try{return new URL(e).origin.length>0}catch{return!1}}function L(e,t,n){return t>4?!1:typeof e=="string"?e.length<=1024:typeof e=="number"?Number.isFinite(e):e===null||typeof e=="boolean"?!0:Array.isArray(e)?e.length<=50&&e.every(r=>L(r,t+1,n)):Object.entries(e).every(([r,i])=>(n.count+=1,n.count<=100&&r.length>0&&r.length<=128&&!St.test(r)&&L(i,t+1,n)))}function z(e){let{context:t}=e;try{if(C(e)>16384)return!1}catch{return!1}return w.test(e.event_id)&&N(e.anonymous_id)&&N(e.session_id)&&(e.user_id===void 0||N(e.user_id))&&(e.event_type!=="event"||e.event_name.length<=100&&yt.test(e.event_name)&&e.event_name!=="identify"&&e.event_name!=="page_view")&&K(t.url)&&t.path.startsWith("/")&&t.path.length<=1024&&(t.title===void 0||t.title.length<=512)&&(t.referrer===void 0||K(t.referrer))&&[t.utm_source,t.utm_medium,t.utm_campaign,t.utm_term,t.utm_content].every(n=>n===void 0||n.length>0&&n.length<=256)&&(e.properties===void 0||L(e.properties,0,{count:0}))}function j(e){if(!e||typeof e!="object")return;let t=e;if(!(t.receipt_version!==1||typeof t.receipt_id!="string"||!w.test(t.receipt_id)||typeof t.batch_id!="string"||!w.test(t.batch_id)||!Number.isInteger(t.accepted_events)||(t.accepted_events??0)<1||(t.accepted_events??0)>50||typeof t.accepted_at!="string"||Number.isNaN(Date.parse(t.accepted_at))))return t}function It(e,t){let n=new URL(e.href);return n.hash="",{path:n.pathname,url:n.toString(),...e.title?{title:e.title}:{},...t}}function F(e,t,n,r){let i={schema_version:1,event_id:e.randomUUID(),event_name:n.eventName,event_type:n.eventType,timestamp:new Date(e.now()).toISOString(),anonymous_id:t.anonymousId,session_id:t.sessionId,context:It(e.page(),t.attribution),...n.properties?{properties:n.properties}:{},...n.userId?{user_id:n.userId}:{}};if(!z(i)){r?.({code:"EVENT_INVALID",message:"Event was not queued because it failed the v1 contract."});return}return i}var J=1;function gt(e){if(!e||typeof e!="object")return!1;let t=e;return t.version===J&&typeof t.anonymousId=="string"&&typeof t.sessionId=="string"&&typeof t.lastActivityAt=="number"&&typeof t.attribution=="object"&&t.attribution!==null&&(t.userId===void 0||typeof t.userId=="string")}var T=class{#e;#o;#t;#i;#r;#n;constructor(t,n){this.#t=t,this.#o=n.onError,this.#i=n.sessionTimeoutMs,this.#e=`tracwell:${n.projectKey}:identity`,this.#r=n.persistence==="localStorage"?this.#p(t):void 0}identify(t,n){let r=this.#d(n);return r.userId&&r.userId!==t&&(r.anonymousId=this.#t.randomUUID(),r.sessionId=this.#t.randomUUID()),r.userId=t,this.#h(r),this.#s(r)}reset(){if(this.#n=void 0,this.#r)try{this.#r.removeItem(this.#e)}catch(t){this.#a(t)}}snapshot(t){return this.#s(this.#d(t))}#c(t){let n=this.#t.now();return{anonymousId:this.#t.randomUUID(),attribution:t,lastActivityAt:n,sessionId:this.#t.randomUUID(),version:J}}#d(t){let n=this.#t.now(),r=this.#u();if(!r){let a=this.#c(t);return this.#h(a),a}let i=R(r.attribution),o=R(t);return(n-r.lastActivityAt>=this.#i||o!==void 0&&o!==i)&&(r.sessionId=this.#t.randomUUID(),r.attribution=t),r.lastActivityAt=n,this.#h(r),r}#u(){if(this.#n)return this.#n;if(this.#r)try{let t=this.#r.getItem(this.#e);if(!t)return;let n=JSON.parse(t);if(!gt(n)){this.#r.removeItem(this.#e);return}return this.#n=n,n}catch(t){this.#a(t);return}}#p(t){try{return t.storage()}catch(n){this.#a(n);return}}#a(t){this.#o?.({cause:t,code:"STORAGE_UNAVAILABLE",message:"Persistent identity storage is unavailable; using in-memory identity."})}#s(t){return{anonymousId:t.anonymousId,attribution:t.attribution,sessionId:t.sessionId,...t.userId?{userId:t.userId}:{}}}#h(t){if(this.#n=t,!!this.#r)try{this.#r.setItem(this.#e,JSON.stringify(t))}catch(n){this.#a(n)}}};function f(e,t,n,r){return{...r===void 0?{}:{cause:r},code:e,eventIds:n.batch.events.map(i=>i.event_id),message:t}}var S=class{#e;#o;#t;#i;#r;#n=[];constructor(t,n){this.#o=t,this.#e=n}enqueue(t){this.#n.push(t),this.#n.length>=this.#e.batchSize&&this.flush()}clear(){this.#i?.(),this.#i=void 0,this.#t=void 0,this.#n=[]}flush(){return this.#r?this.#r:(this.#r=this.#c().finally(()=>{this.#r=void 0}),this.#r)}flushWithBeacon(){for(this.#i?.(),this.#i=void 0;this.#t||this.#n.length>0;){let t=this.#t??this.#d();if(!t)return;let n=JSON.stringify(t.batch);if(!this.#o.sendBeacon(this.#e.endpoint,n)){this.#e.onError?.(f("DELIVERY_FAILED","Browser lifecycle handoff was rejected by sendBeacon.",t));return}this.#t=void 0,this.#u({batchId:t.batch.batch_id,eventIds:t.batch.events.map(r=>r.event_id),status:"handed_off"})}}async#c(){this.#i?.(),this.#i=void 0;let t=this.#t??this.#d();if(!t)return;await this.#a(t)&&this.#n.length>0&&await this.#c()}#d(){if(this.#n.length===0)return;let t=this.#n.slice(0,this.#e.batchSize),n;for(;t.length>0;){let r={batch_version:1,batch_id:this.#o.randomUUID(),project_key:this.#e.projectKey,sent_at:new Date(this.#o.now()).toISOString(),sdk:{name:"tracwell-browser",version:b},events:t};if(C(r)<=49152){n=r;break}t.pop()}if(!n){let r=this.#n.shift();r&&this.#e.onError?.({code:"EVENT_INVALID",eventIds:[r.event_id],message:"Event cannot fit in a valid browser batch."});return}return this.#n.splice(0,n.events.length),this.#t={attempt:0,batch:n},this.#t}#u(t){this.#e.onDelivery?.(t)}#p(t){let n=250*2**(t.attempt-1);this.#i=this.#o.setTimeout(()=>{this.#i=void 0,this.flush()},n)}async#a(t){let n;try{n=await this.#o.fetch(this.#e.endpoint,JSON.stringify(t.batch),"text/plain;charset=UTF-8")}catch(o){return this.#t!==t?!1:this.#s(t,o)}if(n.status!==202)return n.status===408||n.status===429||n.status>=500?this.#s(t,new Error(`Collector returned HTTP ${n.status}.`)):(this.#t===t&&(this.#t=void 0),this.#e.onError?.(f("DELIVERY_REJECTED",`Collector rejected the batch with non-retryable HTTP ${n.status}.`,t)),!1);let r;try{r=await n.json()}catch(o){return this.#e.onError?.(f("RECEIPT_INVALID","Collector returned an unreadable acceptance receipt.",t,o)),this.#s(t,o)}let i=j(r);return!i||i.batch_id!==t.batch.batch_id||i.accepted_events!==t.batch.events.length?(this.#e.onError?.(f("RECEIPT_INVALID","Collector receipt does not match the submitted batch.",t)),this.#s(t,new Error("Acceptance receipt mismatch."))):(this.#t===t&&(this.#t=void 0),this.#u({batchId:t.batch.batch_id,eventIds:t.batch.events.map(o=>o.event_id),receiptId:i.receipt_id,status:"accepted"}),!0)}#s(t,n){return this.#t!==t?!1:(t.attempt+=1,t.attempt<=this.#e.maxRetries?(this.#e.onError?.(f("DELIVERY_FAILED","Event batch delivery failed and is scheduled for bounded retry.",t,n)),this.#p(t),!1):(this.#t=void 0,this.#e.onError?.(f("DELIVERY_EXHAUSTED","Event batch delivery exhausted the configured retry limit.",t,n)),!1))}};function $(e,t){let n=new T(e,t),r=new S(e,t),i=t.consent,o,p=!1,c=!1,a=[];function _(){return i==="granted"&&!(t.respectDoNotTrack&&e.doNotTrackEnabled())}function I(s,u,d,h){if(c||!_())return;let V=A(e.page()),D=n.snapshot(V),l=h??D.userId,Q=s==="identify"&&l?{eventName:"identify",eventType:"identify",...d?{properties:d}:{},userId:l}:s==="pageview"?{eventName:"page_view",eventType:"pageview",...d?{properties:d}:{},...l?{userId:l}:{}}:{eventName:u,eventType:"event",...d?{properties:d}:{},...l?{userId:l}:{}},m=F(e,D,Q,t.onError);if(m){if(s==="identify"&&h){let U=n.identify(h,V);m.anonymous_id=U.anonymousId,m.session_id=U.sessionId}return r.enqueue(m),m.event_id}}function g(s){if(!_())return;let u=new URL(e.page().href);u.hash="";let d=u.toString();if(d===o)return;let h=I("pageview","page_view",s);return h&&(o=d),h}function x(){a.forEach(s=>s()),a=[],p=!1}function M(){p||c||!_()||(p=!0,a=[e.addPageHideListener(()=>r.flushWithBeacon()),e.addVisibilityHiddenListener(()=>r.flushWithBeacon()),e.setInterval(()=>{r.flush()},t.flushIntervalMs)],t.autoTrackPageViews&&(a.push(e.subscribeToNavigation(()=>{g()})),g()))}return M(),{flush(){return r.flush()},getSession(){if(c||t.collectionMode!=="product"||!_())return;let s=n.snapshot(A(e.page()));return{anonymousId:s.anonymousId,sessionId:s.sessionId,...s.userId?{userId:s.userId}:{}}},identify(s,u){if(t.collectionMode==="private"){t.onError?.({code:"IDENTIFY_UNAVAILABLE",message:"identify() is unavailable in Private collection mode."});return}return I("identify","identify",u,s)},page:g,reset(){n.reset(),o=void 0},setConsent(s){if(i=s,s==="denied"){x(),r.clear(),n.reset(),o=void 0;return}M()},async shutdown(){c||(x(),await r.flush(),c=!0)},track(s,u){return I("event",s,u)}}}function q(e,t){return window.addEventListener(e,t),()=>window.removeEventListener(e,t)}function W(){return{addPageHideListener(e){return q("pagehide",e)},addVisibilityHiddenListener(e){let t=()=>{document.visibilityState==="hidden"&&e()};return document.addEventListener("visibilitychange",t),()=>document.removeEventListener("visibilitychange",t)},doNotTrackEnabled(){return navigator.doNotTrack==="1"},async fetch(e,t,n){return fetch(e,{body:t,credentials:"omit",headers:{"content-type":n},keepalive:!0,method:"POST"})},now(){return Date.now()},page(){return{href:window.location.href,referrer:document.referrer,title:document.title}},randomUUID(){return crypto.randomUUID()},sendBeacon(e,t){return navigator.sendBeacon(e,t)},setInterval(e,t){let n=window.setInterval(e,t);return()=>window.clearInterval(n)},setTimeout(e,t){let n=window.setTimeout(e,t);return()=>window.clearTimeout(n)},storage(){return window.localStorage},subscribeToNavigation(e){let t=!0,n=history.pushState,r=history.replaceState,i=()=>{t&&queueMicrotask(e)},o=function(...a){n.apply(history,a),i()},p=function(...a){r.apply(history,a),i()};history.pushState=o,history.replaceState=p;let c=q("popstate",e);return()=>{t=!1,c(),history.pushState===o&&(history.pushState=n),history.replaceState===p&&(history.replaceState=r)}}}}function Jt(e){if(typeof window>"u"||typeof document>"u"||typeof navigator>"u")throw new Error("createTracwell must be called in a browser after the document is available.");return $(W(),G(e))}export{O as DEFAULT_BATCH_SIZE,k as DEFAULT_COLLECTOR_ENDPOINT,H as DEFAULT_FLUSH_INTERVAL_MS,B as DEFAULT_MAX_RETRIES,X as DEFAULT_SESSION_TIMEOUT_MS,b as SDK_VERSION,Jt as createTracwell};
|
package/dist/npm/types.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface DeliveryReport {
|
|
|
25
25
|
status: "accepted" | "handed_off";
|
|
26
26
|
receiptId?: string;
|
|
27
27
|
}
|
|
28
|
+
export interface TracwellSession {
|
|
29
|
+
anonymousId: string;
|
|
30
|
+
sessionId: string;
|
|
31
|
+
userId?: string;
|
|
32
|
+
}
|
|
28
33
|
export interface TracwellConfig {
|
|
29
34
|
projectKey: string;
|
|
30
35
|
collectionMode?: CollectionMode;
|
|
@@ -42,6 +47,7 @@ export interface TracwellConfig {
|
|
|
42
47
|
}
|
|
43
48
|
export interface TracwellClient {
|
|
44
49
|
flush(): Promise<void>;
|
|
50
|
+
getSession(): TracwellSession | undefined;
|
|
45
51
|
identify(userId: string, properties?: EventProperties): string | undefined;
|
|
46
52
|
page(properties?: EventProperties): string | undefined;
|
|
47
53
|
reset(): void;
|