@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 +24 -21
- package/dist/index.d.ts +24 -21
- package/dist/index.global.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
- package/src/index.ts +90 -43
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
private
|
|
14
|
-
private
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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.global.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{function
|
|
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
|
|
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
|
|
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
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;
|
|
17
|
+
duration?: number;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
//
|
|
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
|
|
56
|
+
console.error('[Senzor] WebId is required.');
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
76
|
-
let
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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]
|
|
193
|
+
}).catch(err => console.error('[Senzor] Telemetry Error:', err));
|
|
145
194
|
}
|
|
146
195
|
|
|
147
196
|
private setupListeners() {
|
|
148
|
-
//
|
|
197
|
+
// SPA Support
|
|
149
198
|
const originalPushState = history.pushState;
|
|
150
199
|
history.pushState = (...args) => {
|
|
151
|
-
this.trackPing();
|
|
200
|
+
this.trackPing();
|
|
152
201
|
originalPushState.apply(history, args);
|
|
153
|
-
this.trackPageView();
|
|
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
|
-
//
|
|
210
|
+
// Visibility & Unload
|
|
162
211
|
document.addEventListener('visibilitychange', () => {
|
|
163
212
|
if (document.visibilityState === 'hidden') {
|
|
164
213
|
this.trackPing();
|
|
165
214
|
} else {
|
|
166
|
-
// User
|
|
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
|
}
|