@clamp-sh/analytics 0.1.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/README.md CHANGED
@@ -61,7 +61,7 @@ Server events require an API key (found in your project settings).
61
61
  ## Script tag
62
62
 
63
63
  ```html
64
- <script src="https://cdn.clamp.sh/v1.js"></script>
64
+ <script src="https://cdn.jsdelivr.net/npm/@clamp-sh/analytics@0.2.0/dist/cdn.global.js"></script>
65
65
  <script>
66
66
  clamp.init("proj_xxx")
67
67
  </script>
@@ -0,0 +1 @@
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
- this.timer = setInterval(() => this.flush(), 5e3);
66
- const onHide = () => this.flush(true);
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") onHide();
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
- window.addEventListener("pagehide", onHide);
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
- this.timer = setInterval(() => this.flush(), 5e3);
40
- const onHide = () => this.flush(true);
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") onHide();
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
- window.addEventListener("pagehide", onHide);
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
- this.timer = setInterval(() => this.flush(), 5e3);
68
- const onHide = () => this.flush(true);
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") onHide();
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
- window.addEventListener("pagehide", onHide);
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
- this.timer = setInterval(() => this.flush(), 5e3);
45
- const onHide = () => this.flush(true);
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") onHide();
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
- window.addEventListener("pagehide", onHide);
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
@@ -1,14 +1,22 @@
1
1
  {
2
2
  "name": "@clamp-sh/analytics",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lightweight analytics SDK for Clamp. Auto-pageviews, sessions, batching. Browser, server, and React.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://clamp.sh",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/clamp-sh/analytics"
9
+ "url": "git+https://github.com/clamp-sh/clamp.git",
10
+ "directory": "packages/sdk"
10
11
  },
11
- "keywords": ["analytics", "tracking", "pageviews", "sessions", "react", "nextjs"],
12
+ "keywords": [
13
+ "analytics",
14
+ "tracking",
15
+ "pageviews",
16
+ "sessions",
17
+ "react",
18
+ "nextjs"
19
+ ],
12
20
  "main": "dist/index.js",
13
21
  "module": "dist/index.mjs",
14
22
  "types": "dist/index.d.ts",
@@ -29,7 +37,9 @@
29
37
  "require": "./dist/react.js"
30
38
  }
31
39
  },
32
- "files": ["dist"],
40
+ "files": [
41
+ "dist"
42
+ ],
33
43
  "sideEffects": false,
34
44
  "scripts": {
35
45
  "build": "tsup",
@@ -40,7 +50,9 @@
40
50
  "react": ">=18"
41
51
  },
42
52
  "peerDependenciesMeta": {
43
- "react": { "optional": true }
53
+ "react": {
54
+ "optional": true
55
+ }
44
56
  },
45
57
  "devDependencies": {
46
58
  "react": "^19",