@clamp-sh/analytics 0.2.0 → 0.3.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/cdn.global.js +1 -1
- package/dist/index.js +85 -5
- package/dist/index.mjs +85 -5
- package/dist/react.js +85 -5
- package/dist/react.mjs +85 -5
- package/package.json +1 -1
package/dist/cdn.global.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var clamp=(()=>{var
|
|
1
|
+
"use strict";var clamp=(()=>{var r=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var l=(i,e)=>{for(var t in e)r(i,t,{get:e[t],enumerable:!0})},c=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of h(e))!p.call(i,a)&&a!==t&&r(i,a,{get:()=>e[a],enumerable:!(n=g(e,a))||n.enumerable});return i};var u=i=>c(r({},"__esModule",{value:!0}),i);var w={};l(w,{getAnonymousId:()=>f,init:()=>m,track:()=>y});var v="https://api.clamp.sh";var o=class{constructor(){this.projectId=null;this.endpoint=v;this.sessionId=null;this.anonymousId=null;this.queue=[];this.preInitQueue=[];this.initialized=!1;this.lastPageviewPath=null;this.lastPageviewTime=0;this.engagementSeconds=0;this.engagementTimer=null;this.pageviewEndSent=!1;this.hiddenAt=null}init(e,t){if(typeof window>"u")return;this.projectId=e,t?.endpoint&&(this.endpoint=t.endpoint),this.initialized=!0,this.sessionId=sessionStorage.getItem("clamp_sid")??this.newId("ses"),sessionStorage.setItem("clamp_sid",this.sessionId),this.anonymousId=localStorage.getItem("clamp_aid")??this.newId("anon"),localStorage.setItem("clamp_aid",this.anonymousId);for(let s of this.preInitQueue)this.track(s.name,s.props);this.preInitQueue=[],this.pageview();let n=history.pushState.bind(history),a=history.replaceState.bind(history);history.pushState=(...s)=>{n(...s),this.pageview()},history.replaceState=(...s)=>{a(...s),this.pageview()},window.addEventListener("popstate",()=>this.pageview()),window.addEventListener("pageshow",s=>{s.persisted&&(this.pageviewEndSent=!1,this.pageview())}),setInterval(()=>this.flush(),5e3),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"?(this.hiddenAt=Date.now(),this.stopEngagement(),this.sendPageviewEnd(),this.flush(!0)):(this.hiddenAt!==null&&Date.now()-this.hiddenAt>=18e5&&(this.sessionId=this.newId("ses"),sessionStorage.setItem("clamp_sid",this.sessionId)),this.hiddenAt=null,this.startEngagement())}),window.addEventListener("pagehide",()=>{this.stopEngagement(),this.sendPageviewEnd(),this.flush(!0)}),this.startEngagement()}track(e,t){if(!this.initialized){this.preInitQueue.push({name:e,props:t});return}let n={name:e,url:location.href,referrer:document.referrer,sessionId:this.sessionId,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),screenWidth:window.innerWidth,screenHeight:window.innerHeight,language:navigator.language,platform:"web",properties:t};this.queue.push(n)}getAnonymousId(){return this.anonymousId}pageview(){let e=location.pathname+location.search,t=Date.now();e===this.lastPageviewPath&&t-this.lastPageviewTime<500||(this.lastPageviewPath!==null&&this.sendPageviewEnd(),this.lastPageviewPath=e,this.lastPageviewTime=t,this.engagementSeconds=0,this.pageviewEndSent=!1,this.track("pageview"))}startEngagement(){this.engagementTimer||(this.engagementTimer=setInterval(()=>{document.visibilityState==="visible"&&(this.engagementSeconds+=1)},1e3))}stopEngagement(){this.engagementTimer&&(clearInterval(this.engagementTimer),this.engagementTimer=null)}sendPageviewEnd(){if(this.pageviewEndSent||this.engagementSeconds<1||!this.lastPageviewPath)return;this.pageviewEndSent=!0;let e={p:this.projectId,events:[{name:"pageview_end",url:location.origin+this.lastPageviewPath,referrer:"",sessionId:this.sessionId,anonymousId:this.anonymousId,timestamp:new Date().toISOString(),platform:"web",properties:{engagement_seconds:String(this.engagementSeconds)}}]},t=`${this.endpoint}/e/batch`,n=JSON.stringify(e);typeof navigator.sendBeacon=="function"?navigator.sendBeacon(t,n):fetch(t,{method:"POST",headers:{"content-type":"application/json"},body:n,keepalive:!0}).catch(()=>{})}flush(e=!1){if(!this.projectId||this.queue.length===0)return;let t=this.queue.splice(0,100),n={p:this.projectId,events:t},a=`${this.endpoint}/e/batch`,s=JSON.stringify(n);e&&typeof navigator.sendBeacon=="function"?navigator.sendBeacon(a,s):fetch(a,{method:"POST",headers:{"content-type":"application/json"},body:s,keepalive:!0}).catch(()=>{}),this.queue.length>0&&this.flush(e)}newId(e){let t=Math.random().toString(36).slice(2,10),n=Date.now().toString(36);return`${e}_${n}${t}`}},d=new o;function m(i,e){d.init(i,e)}function y(i,e){d.track(i,e)}function f(){return d.getAnonymousId()}return u(w);})();
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(src_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(src_exports);
|
|
28
28
|
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
29
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
29
30
|
var BrowserClient = class {
|
|
30
31
|
constructor() {
|
|
31
32
|
this.projectId = null;
|
|
@@ -34,8 +35,16 @@ var BrowserClient = class {
|
|
|
34
35
|
this.anonymousId = null;
|
|
35
36
|
this.queue = [];
|
|
36
37
|
this.preInitQueue = [];
|
|
37
|
-
this.timer = null;
|
|
38
38
|
this.initialized = false;
|
|
39
|
+
// Dedup state
|
|
40
|
+
this.lastPageviewPath = null;
|
|
41
|
+
this.lastPageviewTime = 0;
|
|
42
|
+
// Engagement tracking
|
|
43
|
+
this.engagementSeconds = 0;
|
|
44
|
+
this.engagementTimer = null;
|
|
45
|
+
this.pageviewEndSent = false;
|
|
46
|
+
// Session idle tracking
|
|
47
|
+
this.hiddenAt = null;
|
|
39
48
|
}
|
|
40
49
|
init(projectId, opts) {
|
|
41
50
|
if (typeof window === "undefined") return;
|
|
@@ -62,12 +71,34 @@ var BrowserClient = class {
|
|
|
62
71
|
this.pageview();
|
|
63
72
|
};
|
|
64
73
|
window.addEventListener("popstate", () => this.pageview());
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
window.addEventListener("pageshow", (e) => {
|
|
75
|
+
if (e.persisted) {
|
|
76
|
+
this.pageviewEndSent = false;
|
|
77
|
+
this.pageview();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
setInterval(() => this.flush(), 5e3);
|
|
67
81
|
document.addEventListener("visibilitychange", () => {
|
|
68
|
-
if (document.visibilityState === "hidden")
|
|
82
|
+
if (document.visibilityState === "hidden") {
|
|
83
|
+
this.hiddenAt = Date.now();
|
|
84
|
+
this.stopEngagement();
|
|
85
|
+
this.sendPageviewEnd();
|
|
86
|
+
this.flush(true);
|
|
87
|
+
} else {
|
|
88
|
+
if (this.hiddenAt !== null && Date.now() - this.hiddenAt >= SESSION_IDLE_MS) {
|
|
89
|
+
this.sessionId = this.newId("ses");
|
|
90
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
91
|
+
}
|
|
92
|
+
this.hiddenAt = null;
|
|
93
|
+
this.startEngagement();
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
window.addEventListener("pagehide", () => {
|
|
97
|
+
this.stopEngagement();
|
|
98
|
+
this.sendPageviewEnd();
|
|
99
|
+
this.flush(true);
|
|
69
100
|
});
|
|
70
|
-
|
|
101
|
+
this.startEngagement();
|
|
71
102
|
}
|
|
72
103
|
track(name, properties) {
|
|
73
104
|
if (!this.initialized) {
|
|
@@ -94,8 +125,57 @@ var BrowserClient = class {
|
|
|
94
125
|
}
|
|
95
126
|
// ── Private ─────────────────────────────────────────────────────────
|
|
96
127
|
pageview() {
|
|
128
|
+
const path = location.pathname + location.search;
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
if (path === this.lastPageviewPath && now - this.lastPageviewTime < 500) return;
|
|
131
|
+
if (this.lastPageviewPath !== null) {
|
|
132
|
+
this.sendPageviewEnd();
|
|
133
|
+
}
|
|
134
|
+
this.lastPageviewPath = path;
|
|
135
|
+
this.lastPageviewTime = now;
|
|
136
|
+
this.engagementSeconds = 0;
|
|
137
|
+
this.pageviewEndSent = false;
|
|
97
138
|
this.track("pageview");
|
|
98
139
|
}
|
|
140
|
+
startEngagement() {
|
|
141
|
+
if (this.engagementTimer) return;
|
|
142
|
+
this.engagementTimer = setInterval(() => {
|
|
143
|
+
if (document.visibilityState === "visible") {
|
|
144
|
+
this.engagementSeconds += 1;
|
|
145
|
+
}
|
|
146
|
+
}, 1e3);
|
|
147
|
+
}
|
|
148
|
+
stopEngagement() {
|
|
149
|
+
if (this.engagementTimer) {
|
|
150
|
+
clearInterval(this.engagementTimer);
|
|
151
|
+
this.engagementTimer = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
sendPageviewEnd() {
|
|
155
|
+
if (this.pageviewEndSent || this.engagementSeconds < 1 || !this.lastPageviewPath) return;
|
|
156
|
+
this.pageviewEndSent = true;
|
|
157
|
+
const payload = {
|
|
158
|
+
p: this.projectId,
|
|
159
|
+
events: [{
|
|
160
|
+
name: "pageview_end",
|
|
161
|
+
url: location.origin + this.lastPageviewPath,
|
|
162
|
+
referrer: "",
|
|
163
|
+
sessionId: this.sessionId,
|
|
164
|
+
anonymousId: this.anonymousId,
|
|
165
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
166
|
+
platform: "web",
|
|
167
|
+
properties: { engagement_seconds: String(this.engagementSeconds) }
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
const url = `${this.endpoint}/e/batch`;
|
|
171
|
+
const body = JSON.stringify(payload);
|
|
172
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
173
|
+
navigator.sendBeacon(url, body);
|
|
174
|
+
} else {
|
|
175
|
+
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
99
179
|
flush(useBeacon = false) {
|
|
100
180
|
if (!this.projectId || this.queue.length === 0) return;
|
|
101
181
|
const events = this.queue.splice(0, 100);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
3
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
3
4
|
var BrowserClient = class {
|
|
4
5
|
constructor() {
|
|
5
6
|
this.projectId = null;
|
|
@@ -8,8 +9,16 @@ var BrowserClient = class {
|
|
|
8
9
|
this.anonymousId = null;
|
|
9
10
|
this.queue = [];
|
|
10
11
|
this.preInitQueue = [];
|
|
11
|
-
this.timer = null;
|
|
12
12
|
this.initialized = false;
|
|
13
|
+
// Dedup state
|
|
14
|
+
this.lastPageviewPath = null;
|
|
15
|
+
this.lastPageviewTime = 0;
|
|
16
|
+
// Engagement tracking
|
|
17
|
+
this.engagementSeconds = 0;
|
|
18
|
+
this.engagementTimer = null;
|
|
19
|
+
this.pageviewEndSent = false;
|
|
20
|
+
// Session idle tracking
|
|
21
|
+
this.hiddenAt = null;
|
|
13
22
|
}
|
|
14
23
|
init(projectId, opts) {
|
|
15
24
|
if (typeof window === "undefined") return;
|
|
@@ -36,12 +45,34 @@ var BrowserClient = class {
|
|
|
36
45
|
this.pageview();
|
|
37
46
|
};
|
|
38
47
|
window.addEventListener("popstate", () => this.pageview());
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
window.addEventListener("pageshow", (e) => {
|
|
49
|
+
if (e.persisted) {
|
|
50
|
+
this.pageviewEndSent = false;
|
|
51
|
+
this.pageview();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
setInterval(() => this.flush(), 5e3);
|
|
41
55
|
document.addEventListener("visibilitychange", () => {
|
|
42
|
-
if (document.visibilityState === "hidden")
|
|
56
|
+
if (document.visibilityState === "hidden") {
|
|
57
|
+
this.hiddenAt = Date.now();
|
|
58
|
+
this.stopEngagement();
|
|
59
|
+
this.sendPageviewEnd();
|
|
60
|
+
this.flush(true);
|
|
61
|
+
} else {
|
|
62
|
+
if (this.hiddenAt !== null && Date.now() - this.hiddenAt >= SESSION_IDLE_MS) {
|
|
63
|
+
this.sessionId = this.newId("ses");
|
|
64
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
65
|
+
}
|
|
66
|
+
this.hiddenAt = null;
|
|
67
|
+
this.startEngagement();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
window.addEventListener("pagehide", () => {
|
|
71
|
+
this.stopEngagement();
|
|
72
|
+
this.sendPageviewEnd();
|
|
73
|
+
this.flush(true);
|
|
43
74
|
});
|
|
44
|
-
|
|
75
|
+
this.startEngagement();
|
|
45
76
|
}
|
|
46
77
|
track(name, properties) {
|
|
47
78
|
if (!this.initialized) {
|
|
@@ -68,8 +99,57 @@ var BrowserClient = class {
|
|
|
68
99
|
}
|
|
69
100
|
// ── Private ─────────────────────────────────────────────────────────
|
|
70
101
|
pageview() {
|
|
102
|
+
const path = location.pathname + location.search;
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
if (path === this.lastPageviewPath && now - this.lastPageviewTime < 500) return;
|
|
105
|
+
if (this.lastPageviewPath !== null) {
|
|
106
|
+
this.sendPageviewEnd();
|
|
107
|
+
}
|
|
108
|
+
this.lastPageviewPath = path;
|
|
109
|
+
this.lastPageviewTime = now;
|
|
110
|
+
this.engagementSeconds = 0;
|
|
111
|
+
this.pageviewEndSent = false;
|
|
71
112
|
this.track("pageview");
|
|
72
113
|
}
|
|
114
|
+
startEngagement() {
|
|
115
|
+
if (this.engagementTimer) return;
|
|
116
|
+
this.engagementTimer = setInterval(() => {
|
|
117
|
+
if (document.visibilityState === "visible") {
|
|
118
|
+
this.engagementSeconds += 1;
|
|
119
|
+
}
|
|
120
|
+
}, 1e3);
|
|
121
|
+
}
|
|
122
|
+
stopEngagement() {
|
|
123
|
+
if (this.engagementTimer) {
|
|
124
|
+
clearInterval(this.engagementTimer);
|
|
125
|
+
this.engagementTimer = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
sendPageviewEnd() {
|
|
129
|
+
if (this.pageviewEndSent || this.engagementSeconds < 1 || !this.lastPageviewPath) return;
|
|
130
|
+
this.pageviewEndSent = true;
|
|
131
|
+
const payload = {
|
|
132
|
+
p: this.projectId,
|
|
133
|
+
events: [{
|
|
134
|
+
name: "pageview_end",
|
|
135
|
+
url: location.origin + this.lastPageviewPath,
|
|
136
|
+
referrer: "",
|
|
137
|
+
sessionId: this.sessionId,
|
|
138
|
+
anonymousId: this.anonymousId,
|
|
139
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
140
|
+
platform: "web",
|
|
141
|
+
properties: { engagement_seconds: String(this.engagementSeconds) }
|
|
142
|
+
}]
|
|
143
|
+
};
|
|
144
|
+
const url = `${this.endpoint}/e/batch`;
|
|
145
|
+
const body = JSON.stringify(payload);
|
|
146
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
147
|
+
navigator.sendBeacon(url, body);
|
|
148
|
+
} else {
|
|
149
|
+
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
73
153
|
flush(useBeacon = false) {
|
|
74
154
|
if (!this.projectId || this.queue.length === 0) return;
|
|
75
155
|
const events = this.queue.splice(0, 100);
|
package/dist/react.js
CHANGED
|
@@ -28,6 +28,7 @@ var import_react = require("react");
|
|
|
28
28
|
|
|
29
29
|
// src/index.ts
|
|
30
30
|
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
31
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
31
32
|
var BrowserClient = class {
|
|
32
33
|
constructor() {
|
|
33
34
|
this.projectId = null;
|
|
@@ -36,8 +37,16 @@ var BrowserClient = class {
|
|
|
36
37
|
this.anonymousId = null;
|
|
37
38
|
this.queue = [];
|
|
38
39
|
this.preInitQueue = [];
|
|
39
|
-
this.timer = null;
|
|
40
40
|
this.initialized = false;
|
|
41
|
+
// Dedup state
|
|
42
|
+
this.lastPageviewPath = null;
|
|
43
|
+
this.lastPageviewTime = 0;
|
|
44
|
+
// Engagement tracking
|
|
45
|
+
this.engagementSeconds = 0;
|
|
46
|
+
this.engagementTimer = null;
|
|
47
|
+
this.pageviewEndSent = false;
|
|
48
|
+
// Session idle tracking
|
|
49
|
+
this.hiddenAt = null;
|
|
41
50
|
}
|
|
42
51
|
init(projectId, opts) {
|
|
43
52
|
if (typeof window === "undefined") return;
|
|
@@ -64,12 +73,34 @@ var BrowserClient = class {
|
|
|
64
73
|
this.pageview();
|
|
65
74
|
};
|
|
66
75
|
window.addEventListener("popstate", () => this.pageview());
|
|
67
|
-
|
|
68
|
-
|
|
76
|
+
window.addEventListener("pageshow", (e) => {
|
|
77
|
+
if (e.persisted) {
|
|
78
|
+
this.pageviewEndSent = false;
|
|
79
|
+
this.pageview();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
setInterval(() => this.flush(), 5e3);
|
|
69
83
|
document.addEventListener("visibilitychange", () => {
|
|
70
|
-
if (document.visibilityState === "hidden")
|
|
84
|
+
if (document.visibilityState === "hidden") {
|
|
85
|
+
this.hiddenAt = Date.now();
|
|
86
|
+
this.stopEngagement();
|
|
87
|
+
this.sendPageviewEnd();
|
|
88
|
+
this.flush(true);
|
|
89
|
+
} else {
|
|
90
|
+
if (this.hiddenAt !== null && Date.now() - this.hiddenAt >= SESSION_IDLE_MS) {
|
|
91
|
+
this.sessionId = this.newId("ses");
|
|
92
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
93
|
+
}
|
|
94
|
+
this.hiddenAt = null;
|
|
95
|
+
this.startEngagement();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
window.addEventListener("pagehide", () => {
|
|
99
|
+
this.stopEngagement();
|
|
100
|
+
this.sendPageviewEnd();
|
|
101
|
+
this.flush(true);
|
|
71
102
|
});
|
|
72
|
-
|
|
103
|
+
this.startEngagement();
|
|
73
104
|
}
|
|
74
105
|
track(name, properties) {
|
|
75
106
|
if (!this.initialized) {
|
|
@@ -96,8 +127,57 @@ var BrowserClient = class {
|
|
|
96
127
|
}
|
|
97
128
|
// ── Private ─────────────────────────────────────────────────────────
|
|
98
129
|
pageview() {
|
|
130
|
+
const path = location.pathname + location.search;
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
if (path === this.lastPageviewPath && now - this.lastPageviewTime < 500) return;
|
|
133
|
+
if (this.lastPageviewPath !== null) {
|
|
134
|
+
this.sendPageviewEnd();
|
|
135
|
+
}
|
|
136
|
+
this.lastPageviewPath = path;
|
|
137
|
+
this.lastPageviewTime = now;
|
|
138
|
+
this.engagementSeconds = 0;
|
|
139
|
+
this.pageviewEndSent = false;
|
|
99
140
|
this.track("pageview");
|
|
100
141
|
}
|
|
142
|
+
startEngagement() {
|
|
143
|
+
if (this.engagementTimer) return;
|
|
144
|
+
this.engagementTimer = setInterval(() => {
|
|
145
|
+
if (document.visibilityState === "visible") {
|
|
146
|
+
this.engagementSeconds += 1;
|
|
147
|
+
}
|
|
148
|
+
}, 1e3);
|
|
149
|
+
}
|
|
150
|
+
stopEngagement() {
|
|
151
|
+
if (this.engagementTimer) {
|
|
152
|
+
clearInterval(this.engagementTimer);
|
|
153
|
+
this.engagementTimer = null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
sendPageviewEnd() {
|
|
157
|
+
if (this.pageviewEndSent || this.engagementSeconds < 1 || !this.lastPageviewPath) return;
|
|
158
|
+
this.pageviewEndSent = true;
|
|
159
|
+
const payload = {
|
|
160
|
+
p: this.projectId,
|
|
161
|
+
events: [{
|
|
162
|
+
name: "pageview_end",
|
|
163
|
+
url: location.origin + this.lastPageviewPath,
|
|
164
|
+
referrer: "",
|
|
165
|
+
sessionId: this.sessionId,
|
|
166
|
+
anonymousId: this.anonymousId,
|
|
167
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
168
|
+
platform: "web",
|
|
169
|
+
properties: { engagement_seconds: String(this.engagementSeconds) }
|
|
170
|
+
}]
|
|
171
|
+
};
|
|
172
|
+
const url = `${this.endpoint}/e/batch`;
|
|
173
|
+
const body = JSON.stringify(payload);
|
|
174
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
175
|
+
navigator.sendBeacon(url, body);
|
|
176
|
+
} else {
|
|
177
|
+
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
101
181
|
flush(useBeacon = false) {
|
|
102
182
|
if (!this.projectId || this.queue.length === 0) return;
|
|
103
183
|
const events = this.queue.splice(0, 100);
|
package/dist/react.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { useEffect } from "react";
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
8
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
8
9
|
var BrowserClient = class {
|
|
9
10
|
constructor() {
|
|
10
11
|
this.projectId = null;
|
|
@@ -13,8 +14,16 @@ var BrowserClient = class {
|
|
|
13
14
|
this.anonymousId = null;
|
|
14
15
|
this.queue = [];
|
|
15
16
|
this.preInitQueue = [];
|
|
16
|
-
this.timer = null;
|
|
17
17
|
this.initialized = false;
|
|
18
|
+
// Dedup state
|
|
19
|
+
this.lastPageviewPath = null;
|
|
20
|
+
this.lastPageviewTime = 0;
|
|
21
|
+
// Engagement tracking
|
|
22
|
+
this.engagementSeconds = 0;
|
|
23
|
+
this.engagementTimer = null;
|
|
24
|
+
this.pageviewEndSent = false;
|
|
25
|
+
// Session idle tracking
|
|
26
|
+
this.hiddenAt = null;
|
|
18
27
|
}
|
|
19
28
|
init(projectId, opts) {
|
|
20
29
|
if (typeof window === "undefined") return;
|
|
@@ -41,12 +50,34 @@ var BrowserClient = class {
|
|
|
41
50
|
this.pageview();
|
|
42
51
|
};
|
|
43
52
|
window.addEventListener("popstate", () => this.pageview());
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
window.addEventListener("pageshow", (e) => {
|
|
54
|
+
if (e.persisted) {
|
|
55
|
+
this.pageviewEndSent = false;
|
|
56
|
+
this.pageview();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
setInterval(() => this.flush(), 5e3);
|
|
46
60
|
document.addEventListener("visibilitychange", () => {
|
|
47
|
-
if (document.visibilityState === "hidden")
|
|
61
|
+
if (document.visibilityState === "hidden") {
|
|
62
|
+
this.hiddenAt = Date.now();
|
|
63
|
+
this.stopEngagement();
|
|
64
|
+
this.sendPageviewEnd();
|
|
65
|
+
this.flush(true);
|
|
66
|
+
} else {
|
|
67
|
+
if (this.hiddenAt !== null && Date.now() - this.hiddenAt >= SESSION_IDLE_MS) {
|
|
68
|
+
this.sessionId = this.newId("ses");
|
|
69
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
70
|
+
}
|
|
71
|
+
this.hiddenAt = null;
|
|
72
|
+
this.startEngagement();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
window.addEventListener("pagehide", () => {
|
|
76
|
+
this.stopEngagement();
|
|
77
|
+
this.sendPageviewEnd();
|
|
78
|
+
this.flush(true);
|
|
48
79
|
});
|
|
49
|
-
|
|
80
|
+
this.startEngagement();
|
|
50
81
|
}
|
|
51
82
|
track(name, properties) {
|
|
52
83
|
if (!this.initialized) {
|
|
@@ -73,8 +104,57 @@ var BrowserClient = class {
|
|
|
73
104
|
}
|
|
74
105
|
// ── Private ─────────────────────────────────────────────────────────
|
|
75
106
|
pageview() {
|
|
107
|
+
const path = location.pathname + location.search;
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
if (path === this.lastPageviewPath && now - this.lastPageviewTime < 500) return;
|
|
110
|
+
if (this.lastPageviewPath !== null) {
|
|
111
|
+
this.sendPageviewEnd();
|
|
112
|
+
}
|
|
113
|
+
this.lastPageviewPath = path;
|
|
114
|
+
this.lastPageviewTime = now;
|
|
115
|
+
this.engagementSeconds = 0;
|
|
116
|
+
this.pageviewEndSent = false;
|
|
76
117
|
this.track("pageview");
|
|
77
118
|
}
|
|
119
|
+
startEngagement() {
|
|
120
|
+
if (this.engagementTimer) return;
|
|
121
|
+
this.engagementTimer = setInterval(() => {
|
|
122
|
+
if (document.visibilityState === "visible") {
|
|
123
|
+
this.engagementSeconds += 1;
|
|
124
|
+
}
|
|
125
|
+
}, 1e3);
|
|
126
|
+
}
|
|
127
|
+
stopEngagement() {
|
|
128
|
+
if (this.engagementTimer) {
|
|
129
|
+
clearInterval(this.engagementTimer);
|
|
130
|
+
this.engagementTimer = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
sendPageviewEnd() {
|
|
134
|
+
if (this.pageviewEndSent || this.engagementSeconds < 1 || !this.lastPageviewPath) return;
|
|
135
|
+
this.pageviewEndSent = true;
|
|
136
|
+
const payload = {
|
|
137
|
+
p: this.projectId,
|
|
138
|
+
events: [{
|
|
139
|
+
name: "pageview_end",
|
|
140
|
+
url: location.origin + this.lastPageviewPath,
|
|
141
|
+
referrer: "",
|
|
142
|
+
sessionId: this.sessionId,
|
|
143
|
+
anonymousId: this.anonymousId,
|
|
144
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
145
|
+
platform: "web",
|
|
146
|
+
properties: { engagement_seconds: String(this.engagementSeconds) }
|
|
147
|
+
}]
|
|
148
|
+
};
|
|
149
|
+
const url = `${this.endpoint}/e/batch`;
|
|
150
|
+
const body = JSON.stringify(payload);
|
|
151
|
+
if (typeof navigator.sendBeacon === "function") {
|
|
152
|
+
navigator.sendBeacon(url, body);
|
|
153
|
+
} else {
|
|
154
|
+
fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body, keepalive: true }).catch(() => {
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
78
158
|
flush(useBeacon = false) {
|
|
79
159
|
if (!this.projectId || this.queue.length === 0) return;
|
|
80
160
|
const events = this.queue.splice(0, 100);
|
package/package.json
CHANGED