@senzops/web 1.0.0 → 1.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/dist/index.d.mts CHANGED
@@ -1,21 +1,24 @@
1
- interface Config {
2
- webId: string;
3
- endpoint?: string;
4
- }
5
- declare class SenzorWebAgent {
6
- private config;
7
- private startTime;
8
- private endpoint;
9
- constructor();
10
- init(config: Config): void;
11
- private initSession;
12
- private getIds;
13
- private trackPageView;
14
- private trackPing;
15
- private send;
16
- private fallbackSend;
17
- private setupListeners;
18
- }
19
- declare const Senzor: SenzorWebAgent;
20
-
21
- export { Senzor };
1
+ interface Config {
2
+ webId: string;
3
+ endpoint?: string;
4
+ }
5
+ declare class SenzorWebAgent {
6
+ private config;
7
+ private startTime;
8
+ private endpoint;
9
+ private initialized;
10
+ constructor();
11
+ init(config: Config): void;
12
+ private normalizeUrl;
13
+ private manageSession;
14
+ private determineReferrer;
15
+ private getIds;
16
+ private trackPageView;
17
+ private trackPing;
18
+ private send;
19
+ private fallbackSend;
20
+ private setupListeners;
21
+ }
22
+ declare const Senzor: SenzorWebAgent;
23
+
24
+ export { Senzor };
package/dist/index.d.ts CHANGED
@@ -1,21 +1,24 @@
1
- interface Config {
2
- webId: string;
3
- endpoint?: string;
4
- }
5
- declare class SenzorWebAgent {
6
- private config;
7
- private startTime;
8
- private endpoint;
9
- constructor();
10
- init(config: Config): void;
11
- private initSession;
12
- private getIds;
13
- private trackPageView;
14
- private trackPing;
15
- private send;
16
- private fallbackSend;
17
- private setupListeners;
18
- }
19
- declare const Senzor: SenzorWebAgent;
20
-
21
- export { Senzor };
1
+ interface Config {
2
+ webId: string;
3
+ endpoint?: string;
4
+ }
5
+ declare class SenzorWebAgent {
6
+ private config;
7
+ private startTime;
8
+ private endpoint;
9
+ private initialized;
10
+ constructor();
11
+ init(config: Config): void;
12
+ private normalizeUrl;
13
+ private manageSession;
14
+ private determineReferrer;
15
+ private getIds;
16
+ private trackPageView;
17
+ private trackPing;
18
+ private send;
19
+ private fallbackSend;
20
+ private setupListeners;
21
+ }
22
+ declare const Senzor: SenzorWebAgent;
23
+
24
+ export { Senzor };
@@ -1 +1 @@
1
- (()=>{function o(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{let e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16)})}var i=class{config;startTime;endpoint;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint=""}init(e){if(this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required to initialize analytics.");return}this.initSession(),this.trackPageView(),this.setupListeners()}initSession(){let e=localStorage.getItem("senzor_vid");e||(e=o(),localStorage.setItem("senzor_vid",e));let t=sessionStorage.getItem("senzor_sid");t||(t=o(),sessionStorage.setItem("senzor_sid",t))}getIds(){return{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown"}}trackPageView(){this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Failed to send telemetry:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():this.startTime=Date.now()}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},s=new i;typeof window<"u"&&(window.Senzor=s);})();
1
+ (()=>{function d(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let e=Math.random()*16|0;return(a==="x"?e:e&3|8).toString(16)})}var o=class{config;startTime;endpoint;initialized;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint="",this.initialized=!1}init(e){if(this.initialized){console.warn("[Senzor] Agent already initialized.");return}if(this.initialized=!0,this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}normalizeUrl(e){return e?e.replace(/^https?:\/\//,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",d());let i=sessionStorage.getItem("senzor_sid"),n=e-t>r;!i||n?(i=d(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),n=!1;if(t)try{new URL(t).hostname!==r&&(n=!0)}catch{n=!0}if(n){let s=this.normalizeUrl(t);s!==i&&sessionStorage.setItem("senzor_ref",s)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Telemetry Error:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},c=new o;typeof window<"u"&&(window.Senzor=c);})();
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- var s=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var h=(i,e)=>{for(var t in e)s(i,t,{get:e[t],enumerable:!0})},l=(i,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of p(e))!g.call(i,n)&&n!==t&&s(i,n,{get:()=>e[n],enumerable:!(o=c(e,n))||o.enumerable});return i};var w=i=>l(s({},"__esModule",{value:!0}),i);var f={};h(f,{Senzor:()=>d});module.exports=w(f);function a(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,i=>{let e=Math.random()*16|0;return(i==="x"?e:e&3|8).toString(16)})}var r=class{config;startTime;endpoint;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint=""}init(e){if(this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required to initialize analytics.");return}this.initSession(),this.trackPageView(),this.setupListeners()}initSession(){let e=localStorage.getItem("senzor_vid");e||(e=a(),localStorage.setItem("senzor_vid",e));let t=sessionStorage.getItem("senzor_sid");t||(t=a(),sessionStorage.setItem("senzor_sid",t))}getIds(){return{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown"}}trackPageView(){this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Failed to send telemetry:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():this.startTime=Date.now()}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},d=new r;typeof window<"u"&&(window.Senzor=d);0&&(module.exports={Senzor});
1
+ var a=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var f=(n,e)=>{for(var t in e)a(n,t,{get:e[t],enumerable:!0})},w=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of h(e))!p.call(n,i)&&i!==t&&a(n,i,{get:()=>e[i],enumerable:!(r=g(e,i))||r.enumerable});return n};var m=n=>w(a({},"__esModule",{value:!0}),n);var u={};f(u,{Senzor:()=>l});module.exports=m(u);function c(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{let e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16)})}var d=class{config;startTime;endpoint;initialized;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint="",this.initialized=!1}init(e){if(this.initialized){console.warn("[Senzor] Agent already initialized.");return}if(this.initialized=!0,this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}normalizeUrl(e){return e?e.replace(/^https?:\/\//,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",c());let i=sessionStorage.getItem("senzor_sid"),s=e-t>r;!i||s?(i=c(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),s=!1;if(t)try{new URL(t).hostname!==r&&(s=!0)}catch{s=!0}if(s){let o=this.normalizeUrl(t);o!==i&&sessionStorage.setItem("senzor_ref",o)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Telemetry Error:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},l=new d;typeof window<"u"&&(window.Senzor=l);0&&(module.exports={Senzor});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- function o(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,n=>{let e=Math.random()*16|0;return(n==="x"?e:e&3|8).toString(16)})}var i=class{config;startTime;endpoint;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint=""}init(e){if(this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required to initialize analytics.");return}this.initSession(),this.trackPageView(),this.setupListeners()}initSession(){let e=localStorage.getItem("senzor_vid");e||(e=o(),localStorage.setItem("senzor_vid",e));let t=sessionStorage.getItem("senzor_sid");t||(t=o(),sessionStorage.setItem("senzor_sid",t))}getIds(){return{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown"}}trackPageView(){this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,referrer:document.referrer,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Failed to send telemetry:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():this.startTime=Date.now()}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},s=new i;typeof window<"u"&&(window.Senzor=s);export{s as Senzor};
1
+ function d(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,a=>{let e=Math.random()*16|0;return(a==="x"?e:e&3|8).toString(16)})}var o=class{config;startTime;endpoint;initialized;constructor(){this.config={webId:"",endpoint:"https://api.senzor.dev/api/ingest/web"},this.startTime=Date.now(),this.endpoint="",this.initialized=!1}init(e){if(this.initialized){console.warn("[Senzor] Agent already initialized.");return}if(this.initialized=!0,this.config={...this.config,...e},this.endpoint=this.config.endpoint||"https://api.senzor.dev/api/ingest/web",!this.config.webId){console.error("[Senzor] WebId is required.");return}this.manageSession(),this.trackPageView(),this.setupListeners()}normalizeUrl(e){return e?e.replace(/^https?:\/\//,""):""}manageSession(){let e=Date.now(),t=parseInt(localStorage.getItem("senzor_last_activity")||"0",10),r=1800*1e3;localStorage.getItem("senzor_vid")||localStorage.setItem("senzor_vid",d());let i=sessionStorage.getItem("senzor_sid"),n=e-t>r;!i||n?(i=d(),sessionStorage.setItem("senzor_sid",i),this.determineReferrer(!0)):this.determineReferrer(!1),localStorage.setItem("senzor_last_activity",e.toString())}determineReferrer(e){let t=document.referrer,r=window.location.hostname,i=sessionStorage.getItem("senzor_ref"),n=!1;if(t)try{new URL(t).hostname!==r&&(n=!0)}catch{n=!0}if(n){let s=this.normalizeUrl(t);s!==i&&sessionStorage.setItem("senzor_ref",s)}else e&&!i&&sessionStorage.setItem("senzor_ref","Direct")}getIds(){return localStorage.setItem("senzor_last_activity",Date.now().toString()),{visitorId:localStorage.getItem("senzor_vid")||"unknown",sessionId:sessionStorage.getItem("senzor_sid")||"unknown",referrer:sessionStorage.getItem("senzor_ref")||"Direct"}}trackPageView(){this.manageSession(),this.startTime=Date.now();let e={type:"pageview",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer};this.send(e)}trackPing(){let e=Math.floor((Date.now()-this.startTime)/1e3);if(e<1)return;let t={type:"ping",webId:this.config.webId,...this.getIds(),url:window.location.href,path:window.location.pathname,title:document.title,width:window.innerWidth,timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,referrer:this.getIds().referrer,duration:e};this.send(t)}send(e){if(navigator.sendBeacon){let t=new Blob([JSON.stringify(e)],{type:"application/json"});navigator.sendBeacon(this.endpoint,t)||this.fallbackSend(e)}else this.fallbackSend(e)}fallbackSend(e){fetch(this.endpoint,{method:"POST",body:JSON.stringify(e),keepalive:!0,headers:{"Content-Type":"application/json"}}).catch(t=>console.error("[Senzor] Telemetry Error:",t))}setupListeners(){let e=history.pushState;history.pushState=(...t)=>{this.trackPing(),e.apply(history,t),this.trackPageView()},window.addEventListener("popstate",()=>{this.trackPing(),this.trackPageView()}),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?this.trackPing():(this.startTime=Date.now(),this.manageSession())}),window.addEventListener("beforeunload",()=>{this.trackPing()})}},c=new o;typeof window<"u"&&(window.Senzor=c);export{c as Senzor};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/web",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Senzor Web Analytics SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,4 +16,4 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  }
19
- }
19
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,3 @@
1
- // Remove the external uuid import causing the crash
2
- // import { v4 as uuidv4 } from 'uuid';
3
-
4
1
  interface Config {
5
2
  webId: string;
6
3
  endpoint?: string;
@@ -13,19 +10,18 @@ interface Payload {
13
10
  sessionId: string;
14
11
  url: string;
15
12
  path: string;
13
+ title: string;
16
14
  referrer: string;
17
15
  width: number;
18
16
  timezone: string;
19
- duration?: number; // Only for pings/unload
17
+ duration?: number;
20
18
  }
21
19
 
22
- // --- Native UUID Helper (Fixes "require('crypto')" error) ---
20
+ // Browser-Native UUID (No Node.js dependencies)
23
21
  function generateUUID(): string {
24
- // Use native crypto API if available (Modern Browsers)
25
22
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
26
23
  return crypto.randomUUID();
27
24
  }
28
- // Fallback for older environments
29
25
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
30
26
  const r = (Math.random() * 16) | 0;
31
27
  const v = c === 'x' ? r : (r & 0x3) | 0x8;
@@ -37,76 +33,130 @@ class SenzorWebAgent {
37
33
  private config: Config;
38
34
  private startTime: number;
39
35
  private endpoint: string;
36
+ private initialized: boolean;
40
37
 
41
38
  constructor() {
42
39
  this.config = { webId: '', endpoint: 'https://api.senzor.dev/api/ingest/web' };
43
40
  this.startTime = Date.now();
44
41
  this.endpoint = '';
42
+ this.initialized = false;
45
43
  }
46
44
 
47
45
  public init(config: Config) {
46
+ if (this.initialized) {
47
+ console.warn('[Senzor] Agent already initialized.');
48
+ return;
49
+ }
50
+ this.initialized = true;
51
+
48
52
  this.config = { ...this.config, ...config };
49
- // Allow overriding endpoint for self-hosting or dev
50
53
  this.endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/web';
51
54
 
52
55
  if (!this.config.webId) {
53
- console.error('[Senzor] WebId is required to initialize analytics.');
56
+ console.error('[Senzor] WebId is required.');
54
57
  return;
55
58
  }
56
59
 
57
- // 1. Initialize Session
58
- this.initSession();
59
-
60
- // 2. Track Initial Page View
60
+ this.manageSession();
61
61
  this.trackPageView();
62
-
63
- // 3. Setup Listeners
64
62
  this.setupListeners();
65
63
  }
66
64
 
67
- private initSession() {
68
- // Persistent Visitor ID (1 year)
69
- let vid = localStorage.getItem('senzor_vid');
70
- if (!vid) {
71
- vid = generateUUID();
72
- localStorage.setItem('senzor_vid', vid!);
65
+ // Helper to normalize referrer (strip protocol)
66
+ private normalizeUrl(url: string): string {
67
+ if (!url) return '';
68
+ return url.replace(/^https?:\/\//, '');
69
+ }
70
+
71
+ private manageSession() {
72
+ const now = Date.now();
73
+ const lastActivity = parseInt(localStorage.getItem('senzor_last_activity') || '0', 10);
74
+ const sessionTimeout = 30 * 60 * 1000; // 30 mins
75
+
76
+ // Visitor ID (Persistent 1 Year)
77
+ if (!localStorage.getItem('senzor_vid')) {
78
+ localStorage.setItem('senzor_vid', generateUUID());
73
79
  }
74
80
 
75
- // Session ID (Expires when browser closes)
76
- let sid = sessionStorage.getItem('senzor_sid');
77
- if (!sid) {
78
- sid = generateUUID();
79
- sessionStorage.setItem('senzor_sid', sid!);
81
+ // Session ID
82
+ let sessionId = sessionStorage.getItem('senzor_sid');
83
+ const isExpired = (now - lastActivity > sessionTimeout);
84
+
85
+ // Session logic
86
+ if (!sessionId || isExpired) {
87
+ sessionId = generateUUID();
88
+ sessionStorage.setItem('senzor_sid', sessionId);
89
+ this.determineReferrer(true);
90
+ } else {
91
+ // Ongoing session: Check if external source changed
92
+ this.determineReferrer(false);
93
+ }
94
+
95
+ localStorage.setItem('senzor_last_activity', now.toString());
96
+ }
97
+
98
+ private determineReferrer(isNewSession: boolean) {
99
+ const rawReferrer = document.referrer;
100
+ const currentHost = window.location.hostname;
101
+ let storedReferrer = sessionStorage.getItem('senzor_ref');
102
+
103
+ let isExternal = false;
104
+ if (rawReferrer) {
105
+ try {
106
+ const refUrl = new URL(rawReferrer);
107
+ // Compare hosts
108
+ if (refUrl.hostname !== currentHost) {
109
+ isExternal = true;
110
+ }
111
+ } catch (e) {
112
+ isExternal = true;
113
+ }
114
+ }
115
+
116
+ if (isExternal) {
117
+ // Always overwrite if it's a new external source
118
+ const cleanRef = this.normalizeUrl(rawReferrer);
119
+ // Only update if different to avoid redundant writes
120
+ if (cleanRef !== storedReferrer) {
121
+ sessionStorage.setItem('senzor_ref', cleanRef);
122
+ }
123
+ } else if (isNewSession && !storedReferrer) {
124
+ // New session with internal/no referrer = Direct
125
+ sessionStorage.setItem('senzor_ref', 'Direct');
80
126
  }
81
127
  }
82
128
 
83
129
  private getIds() {
130
+ localStorage.setItem('senzor_last_activity', Date.now().toString());
84
131
  return {
85
132
  visitorId: localStorage.getItem('senzor_vid') || 'unknown',
86
- sessionId: sessionStorage.getItem('senzor_sid') || 'unknown'
133
+ sessionId: sessionStorage.getItem('senzor_sid') || 'unknown',
134
+ referrer: sessionStorage.getItem('senzor_ref') || 'Direct'
87
135
  };
88
136
  }
89
137
 
90
138
  private trackPageView() {
91
- this.startTime = Date.now(); // Reset timer for new page
139
+ this.manageSession();
140
+ this.startTime = Date.now();
141
+
92
142
  const payload: Payload = {
93
143
  type: 'pageview',
94
144
  webId: this.config.webId,
95
145
  ...this.getIds(),
96
146
  url: window.location.href,
97
147
  path: window.location.pathname,
98
- referrer: document.referrer,
148
+ title: document.title,
99
149
  width: window.innerWidth,
100
150
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
151
+ referrer: this.getIds().referrer
101
152
  };
102
153
 
103
154
  this.send(payload);
104
155
  }
105
156
 
106
- // Captures time spent on page when user leaves or hides tab
107
157
  private trackPing() {
108
158
  const duration = Math.floor((Date.now() - this.startTime) / 1000);
109
- if (duration < 1) return; // Ignore accidental bounces
159
+ if (duration < 1) return;
110
160
 
111
161
  const payload: Payload = {
112
162
  type: 'ping',
@@ -114,9 +164,10 @@ class SenzorWebAgent {
114
164
  ...this.getIds(),
115
165
  url: window.location.href,
116
166
  path: window.location.pathname,
117
- referrer: document.referrer,
167
+ title: document.title,
118
168
  width: window.innerWidth,
119
169
  timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
170
+ referrer: this.getIds().referrer,
120
171
  duration: duration
121
172
  };
122
173
 
@@ -124,10 +175,8 @@ class SenzorWebAgent {
124
175
  }
125
176
 
126
177
  private send(data: Payload) {
127
- // Use sendBeacon for reliability during unload, fallback to fetch
128
178
  if (navigator.sendBeacon) {
129
179
  const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
130
- // sendBeacon returns false if it fails (e.g. payload too large)
131
180
  const success = navigator.sendBeacon(this.endpoint, blob);
132
181
  if (!success) this.fallbackSend(data);
133
182
  } else {
@@ -141,16 +190,16 @@ class SenzorWebAgent {
141
190
  body: JSON.stringify(data),
142
191
  keepalive: true,
143
192
  headers: { 'Content-Type': 'application/json' }
144
- }).catch(err => console.error('[Senzor] Failed to send telemetry:', err));
193
+ }).catch(err => console.error('[Senzor] Telemetry Error:', err));
145
194
  }
146
195
 
147
196
  private setupListeners() {
148
- // 1. History API Support (SPA - React/Next.js/Vue)
197
+ // SPA Support
149
198
  const originalPushState = history.pushState;
150
199
  history.pushState = (...args) => {
151
- this.trackPing(); // Send duration for previous page
200
+ this.trackPing();
152
201
  originalPushState.apply(history, args);
153
- this.trackPageView(); // Track new page
202
+ this.trackPageView();
154
203
  };
155
204
 
156
205
  window.addEventListener('popstate', () => {
@@ -158,27 +207,25 @@ class SenzorWebAgent {
158
207
  this.trackPageView();
159
208
  });
160
209
 
161
- // 2. Visibility Change (Tab switch / Minimize)
210
+ // Visibility & Unload
162
211
  document.addEventListener('visibilitychange', () => {
163
212
  if (document.visibilityState === 'hidden') {
164
213
  this.trackPing();
165
214
  } else {
166
- // User came back, reset timer so we don't count background time
215
+ // User returned, restart timer (don't count background time)
167
216
  this.startTime = Date.now();
217
+ this.manageSession();
168
218
  }
169
219
  });
170
220
 
171
- // 3. Before Unload (Closing tab)
172
221
  window.addEventListener('beforeunload', () => {
173
222
  this.trackPing();
174
223
  });
175
224
  }
176
225
  }
177
226
 
178
- // Export Singleton
179
227
  export const Senzor = new SenzorWebAgent();
180
228
 
181
- // Allow window access for script tag usage
182
229
  if (typeof window !== 'undefined') {
183
230
  (window as any).Senzor = Senzor;
184
231
  }