@clamp-sh/analytics 0.1.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 +260 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +139 -0
- package/dist/index.mjs +112 -0
- package/dist/react.d.mts +16 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +141 -0
- package/dist/react.mjs +117 -0
- package/dist/server.d.mts +24 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.js +78 -0
- package/dist/server.mjs +52 -0
- package/dist/types-Bc30ZeCx.d.mts +6 -0
- package/dist/types-Bc30ZeCx.d.ts +6 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# @clamp-sh/analytics
|
|
2
|
+
|
|
3
|
+
Analytics SDK for [Clamp](https://clamp.sh), agentic analytics your coding agent can read, query, and act on. Track pageviews, custom events, and server-side actions. No cookies, no personal data collected, no consent banner required.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @clamp-sh/analytics
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Browser
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { init, track, getAnonymousId } from "@clamp-sh/analytics"
|
|
15
|
+
|
|
16
|
+
init("proj_xxx")
|
|
17
|
+
|
|
18
|
+
// Custom events
|
|
19
|
+
track("signup", { plan: "pro" })
|
|
20
|
+
|
|
21
|
+
// Get visitor ID (for linking server-side events)
|
|
22
|
+
const anonId = getAnonymousId()
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
After `init()`, pageviews are tracked automatically, including SPA navigations.
|
|
26
|
+
|
|
27
|
+
## React
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Analytics } from "@clamp-sh/analytics/react"
|
|
31
|
+
|
|
32
|
+
export default function RootLayout({ children }) {
|
|
33
|
+
return (
|
|
34
|
+
<html>
|
|
35
|
+
<body>
|
|
36
|
+
{children}
|
|
37
|
+
<Analytics projectId="proj_xxx" />
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Add to your root layout. Pageviews are tracked automatically. Use `track()` from `@clamp-sh/analytics` anywhere in your app for custom events.
|
|
45
|
+
|
|
46
|
+
## Server
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { init, track } from "@clamp-sh/analytics/server"
|
|
50
|
+
|
|
51
|
+
init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
|
|
52
|
+
|
|
53
|
+
await track("account_created", {
|
|
54
|
+
anonymousId: "anon_abc123",
|
|
55
|
+
properties: { plan: "pro" },
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Server events require an API key (found in your project settings).
|
|
60
|
+
|
|
61
|
+
## Script tag
|
|
62
|
+
|
|
63
|
+
```html
|
|
64
|
+
<script src="https://cdn.clamp.sh/v1.js"></script>
|
|
65
|
+
<script>
|
|
66
|
+
clamp.init("proj_xxx")
|
|
67
|
+
</script>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Custom events
|
|
71
|
+
|
|
72
|
+
Track any action with `track(name, properties)`. Properties are flat string key-value pairs.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { track } from "@clamp-sh/analytics"
|
|
76
|
+
|
|
77
|
+
track("signup", { plan: "pro", source: "pricing_page" })
|
|
78
|
+
track("feature_used", { name: "csv_export" })
|
|
79
|
+
track("invite_sent", { role: "editor", team: "acme" })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
On the server:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { track } from "@clamp-sh/analytics/server"
|
|
86
|
+
|
|
87
|
+
await track("subscription_created", {
|
|
88
|
+
anonymousId: "anon_abc123",
|
|
89
|
+
properties: { plan: "pro", interval: "monthly" },
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Pageviews are tracked automatically. Everything else goes through `track()`.
|
|
94
|
+
|
|
95
|
+
## Typed events
|
|
96
|
+
|
|
97
|
+
Define your event map once and get autocomplete and type checking across your app. Zero runtime cost.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
type Events = {
|
|
101
|
+
signup: { plan: string; source: string }
|
|
102
|
+
purchase: { amount: string; currency: string }
|
|
103
|
+
feature_used: { name: string }
|
|
104
|
+
invite_sent: { role: string }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
init<Events>("proj_xxx")
|
|
108
|
+
|
|
109
|
+
track("signup", { plan: "pro", source: "homepage" }) // autocomplete
|
|
110
|
+
track("signup", { wrong: "field" }) // type error
|
|
111
|
+
track("unknown_event") // type error
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Works the same way with the server SDK:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { init, track } from "@clamp-sh/analytics/server"
|
|
118
|
+
|
|
119
|
+
init<Events>({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
|
|
120
|
+
|
|
121
|
+
await track("purchase", {
|
|
122
|
+
properties: { amount: "49", currency: "usd" },
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Properties limits
|
|
127
|
+
|
|
128
|
+
Event properties are flat `string → string` maps. Nested objects, arrays, and non-string values are rejected.
|
|
129
|
+
|
|
130
|
+
| Constraint | Limit |
|
|
131
|
+
| -------------- | -------- |
|
|
132
|
+
| Max keys | 20 |
|
|
133
|
+
| Key length | 128 chars |
|
|
134
|
+
| Value length | 512 chars |
|
|
135
|
+
| Payload size | 64 KB |
|
|
136
|
+
|
|
137
|
+
## Examples
|
|
138
|
+
|
|
139
|
+
### Track signups with plan info
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
track("signup", { plan: "pro", source: "pricing_page" })
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Track feature usage
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
track("feature_used", { name: "csv_export" })
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Track button clicks
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<button onClick={() => track("cta_clicked", { label: "Get started", page: "/pricing" })}>
|
|
155
|
+
Get started
|
|
156
|
+
</button>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Link browser visitor to server events
|
|
160
|
+
|
|
161
|
+
Pass the anonymous ID from the browser to your API, then include it in server-side events to connect the two.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// Browser: send anonymous ID with your API call
|
|
165
|
+
const anonId = getAnonymousId()
|
|
166
|
+
await fetch("/api/checkout", {
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: JSON.stringify({ plan: "pro", anonId }),
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// Server: include it in the event
|
|
174
|
+
await track("checkout_completed", {
|
|
175
|
+
anonymousId: req.body.anonId,
|
|
176
|
+
properties: { plan: "pro", amount: "49" },
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Track form submissions
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
function ContactForm() {
|
|
184
|
+
const handleSubmit = (e: FormEvent) => {
|
|
185
|
+
e.preventDefault()
|
|
186
|
+
track("form_submitted", { form: "contact", page: location.pathname })
|
|
187
|
+
}
|
|
188
|
+
return <form onSubmit={handleSubmit}>...</form>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Next.js App Router
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// app/layout.tsx
|
|
196
|
+
import { Analytics } from "@clamp-sh/analytics/react"
|
|
197
|
+
|
|
198
|
+
export default function RootLayout({ children }) {
|
|
199
|
+
return (
|
|
200
|
+
<html>
|
|
201
|
+
<body>
|
|
202
|
+
{children}
|
|
203
|
+
<Analytics projectId="proj_xxx" />
|
|
204
|
+
</body>
|
|
205
|
+
</html>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// app/pricing/page.tsx (client component)
|
|
210
|
+
"use client"
|
|
211
|
+
import { track } from "@clamp-sh/analytics"
|
|
212
|
+
|
|
213
|
+
export default function Pricing() {
|
|
214
|
+
return (
|
|
215
|
+
<button onClick={() => track("plan_selected", { plan: "growth" })}>
|
|
216
|
+
Choose Growth
|
|
217
|
+
</button>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Next.js Server Actions
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// app/actions.ts
|
|
226
|
+
"use server"
|
|
227
|
+
import { track } from "@clamp-sh/analytics/server"
|
|
228
|
+
|
|
229
|
+
export async function createTeam(name: string, anonId: string) {
|
|
230
|
+
const team = await db.teams.create({ name })
|
|
231
|
+
await track("team_created", {
|
|
232
|
+
anonymousId: anonId,
|
|
233
|
+
properties: { team_id: team.id },
|
|
234
|
+
})
|
|
235
|
+
return team
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Express / Node.js backend
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import express from "express"
|
|
243
|
+
import { init, track } from "@clamp-sh/analytics/server"
|
|
244
|
+
|
|
245
|
+
init({ projectId: "proj_xxx", apiKey: "sk_proj_..." })
|
|
246
|
+
|
|
247
|
+
const app = express()
|
|
248
|
+
|
|
249
|
+
app.post("/api/subscribe", async (req, res) => {
|
|
250
|
+
await track("subscription_started", {
|
|
251
|
+
anonymousId: req.body.anonId,
|
|
252
|
+
properties: { plan: req.body.plan },
|
|
253
|
+
})
|
|
254
|
+
res.json({ ok: true })
|
|
255
|
+
})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { E as EventMap, A as AnyEvents } from './types-Bc30ZeCx.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the browser SDK. Call once at app startup.
|
|
5
|
+
* Starts auto-pageview tracking, session management, and batching.
|
|
6
|
+
*/
|
|
7
|
+
declare function init<_E extends EventMap = AnyEvents>(projectId: string, opts?: {
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
}): void;
|
|
10
|
+
/**
|
|
11
|
+
* Track a custom event. Fire-and-forget; events are batched and sent every 5s.
|
|
12
|
+
* If called before init(), events are buffered and flushed when init() runs.
|
|
13
|
+
*/
|
|
14
|
+
declare function track<E extends EventMap = AnyEvents, K extends keyof E & string = string>(name: K, properties?: E[K] extends Record<string, string> ? E[K] : Record<string, string>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get the anonymous visitor ID (persisted in localStorage).
|
|
17
|
+
* Pass this to your server so server-side events can be linked to the visitor.
|
|
18
|
+
*/
|
|
19
|
+
declare function getAnonymousId(): string | null;
|
|
20
|
+
|
|
21
|
+
export { AnyEvents, EventMap, getAnonymousId, init, track };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { E as EventMap, A as AnyEvents } from './types-Bc30ZeCx.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the browser SDK. Call once at app startup.
|
|
5
|
+
* Starts auto-pageview tracking, session management, and batching.
|
|
6
|
+
*/
|
|
7
|
+
declare function init<_E extends EventMap = AnyEvents>(projectId: string, opts?: {
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
}): void;
|
|
10
|
+
/**
|
|
11
|
+
* Track a custom event. Fire-and-forget; events are batched and sent every 5s.
|
|
12
|
+
* If called before init(), events are buffered and flushed when init() runs.
|
|
13
|
+
*/
|
|
14
|
+
declare function track<E extends EventMap = AnyEvents, K extends keyof E & string = string>(name: K, properties?: E[K] extends Record<string, string> ? E[K] : Record<string, string>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get the anonymous visitor ID (persisted in localStorage).
|
|
17
|
+
* Pass this to your server so server-side events can be linked to the visitor.
|
|
18
|
+
*/
|
|
19
|
+
declare function getAnonymousId(): string | null;
|
|
20
|
+
|
|
21
|
+
export { AnyEvents, EventMap, getAnonymousId, init, track };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
getAnonymousId: () => getAnonymousId,
|
|
24
|
+
init: () => init,
|
|
25
|
+
track: () => track
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
29
|
+
var BrowserClient = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
this.projectId = null;
|
|
32
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
33
|
+
this.sessionId = null;
|
|
34
|
+
this.anonymousId = null;
|
|
35
|
+
this.queue = [];
|
|
36
|
+
this.preInitQueue = [];
|
|
37
|
+
this.timer = null;
|
|
38
|
+
this.initialized = false;
|
|
39
|
+
}
|
|
40
|
+
init(projectId, opts) {
|
|
41
|
+
if (typeof window === "undefined") return;
|
|
42
|
+
this.projectId = projectId;
|
|
43
|
+
if (opts?.endpoint) this.endpoint = opts.endpoint;
|
|
44
|
+
this.initialized = true;
|
|
45
|
+
this.sessionId = sessionStorage.getItem("clamp_sid") ?? this.newId("ses");
|
|
46
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
47
|
+
this.anonymousId = localStorage.getItem("clamp_aid") ?? this.newId("anon");
|
|
48
|
+
localStorage.setItem("clamp_aid", this.anonymousId);
|
|
49
|
+
for (const e of this.preInitQueue) {
|
|
50
|
+
this.track(e.name, e.props);
|
|
51
|
+
}
|
|
52
|
+
this.preInitQueue = [];
|
|
53
|
+
this.pageview();
|
|
54
|
+
const origPushState = history.pushState.bind(history);
|
|
55
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
56
|
+
history.pushState = (...args) => {
|
|
57
|
+
origPushState(...args);
|
|
58
|
+
this.pageview();
|
|
59
|
+
};
|
|
60
|
+
history.replaceState = (...args) => {
|
|
61
|
+
origReplaceState(...args);
|
|
62
|
+
this.pageview();
|
|
63
|
+
};
|
|
64
|
+
window.addEventListener("popstate", () => this.pageview());
|
|
65
|
+
this.timer = setInterval(() => this.flush(), 5e3);
|
|
66
|
+
const onHide = () => this.flush(true);
|
|
67
|
+
document.addEventListener("visibilitychange", () => {
|
|
68
|
+
if (document.visibilityState === "hidden") onHide();
|
|
69
|
+
});
|
|
70
|
+
window.addEventListener("pagehide", onHide);
|
|
71
|
+
}
|
|
72
|
+
track(name, properties) {
|
|
73
|
+
if (!this.initialized) {
|
|
74
|
+
this.preInitQueue.push({ name, props: properties });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const event = {
|
|
78
|
+
name,
|
|
79
|
+
url: location.href,
|
|
80
|
+
referrer: document.referrer,
|
|
81
|
+
sessionId: this.sessionId,
|
|
82
|
+
anonymousId: this.anonymousId,
|
|
83
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
+
screenWidth: window.innerWidth,
|
|
85
|
+
screenHeight: window.innerHeight,
|
|
86
|
+
language: navigator.language,
|
|
87
|
+
platform: "web",
|
|
88
|
+
properties
|
|
89
|
+
};
|
|
90
|
+
this.queue.push(event);
|
|
91
|
+
}
|
|
92
|
+
getAnonymousId() {
|
|
93
|
+
return this.anonymousId;
|
|
94
|
+
}
|
|
95
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
96
|
+
pageview() {
|
|
97
|
+
this.track("pageview");
|
|
98
|
+
}
|
|
99
|
+
flush(useBeacon = false) {
|
|
100
|
+
if (!this.projectId || this.queue.length === 0) return;
|
|
101
|
+
const events = this.queue.splice(0, 100);
|
|
102
|
+
const payload = { p: this.projectId, events };
|
|
103
|
+
const url = `${this.endpoint}/e/batch`;
|
|
104
|
+
const body = JSON.stringify(payload);
|
|
105
|
+
if (useBeacon && typeof navigator.sendBeacon === "function") {
|
|
106
|
+
navigator.sendBeacon(url, body);
|
|
107
|
+
} else {
|
|
108
|
+
fetch(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: { "content-type": "application/json" },
|
|
111
|
+
body,
|
|
112
|
+
keepalive: true
|
|
113
|
+
}).catch(() => {
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (this.queue.length > 0) this.flush(useBeacon);
|
|
117
|
+
}
|
|
118
|
+
newId(prefix) {
|
|
119
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
120
|
+
const ts = Date.now().toString(36);
|
|
121
|
+
return `${prefix}_${ts}${rand}`;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var client = new BrowserClient();
|
|
125
|
+
function init(projectId, opts) {
|
|
126
|
+
client.init(projectId, opts);
|
|
127
|
+
}
|
|
128
|
+
function track(name, properties) {
|
|
129
|
+
client.track(name, properties);
|
|
130
|
+
}
|
|
131
|
+
function getAnonymousId() {
|
|
132
|
+
return client.getAnonymousId();
|
|
133
|
+
}
|
|
134
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
+
0 && (module.exports = {
|
|
136
|
+
getAnonymousId,
|
|
137
|
+
init,
|
|
138
|
+
track
|
|
139
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
3
|
+
var BrowserClient = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.projectId = null;
|
|
6
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
7
|
+
this.sessionId = null;
|
|
8
|
+
this.anonymousId = null;
|
|
9
|
+
this.queue = [];
|
|
10
|
+
this.preInitQueue = [];
|
|
11
|
+
this.timer = null;
|
|
12
|
+
this.initialized = false;
|
|
13
|
+
}
|
|
14
|
+
init(projectId, opts) {
|
|
15
|
+
if (typeof window === "undefined") return;
|
|
16
|
+
this.projectId = projectId;
|
|
17
|
+
if (opts?.endpoint) this.endpoint = opts.endpoint;
|
|
18
|
+
this.initialized = true;
|
|
19
|
+
this.sessionId = sessionStorage.getItem("clamp_sid") ?? this.newId("ses");
|
|
20
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
21
|
+
this.anonymousId = localStorage.getItem("clamp_aid") ?? this.newId("anon");
|
|
22
|
+
localStorage.setItem("clamp_aid", this.anonymousId);
|
|
23
|
+
for (const e of this.preInitQueue) {
|
|
24
|
+
this.track(e.name, e.props);
|
|
25
|
+
}
|
|
26
|
+
this.preInitQueue = [];
|
|
27
|
+
this.pageview();
|
|
28
|
+
const origPushState = history.pushState.bind(history);
|
|
29
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
30
|
+
history.pushState = (...args) => {
|
|
31
|
+
origPushState(...args);
|
|
32
|
+
this.pageview();
|
|
33
|
+
};
|
|
34
|
+
history.replaceState = (...args) => {
|
|
35
|
+
origReplaceState(...args);
|
|
36
|
+
this.pageview();
|
|
37
|
+
};
|
|
38
|
+
window.addEventListener("popstate", () => this.pageview());
|
|
39
|
+
this.timer = setInterval(() => this.flush(), 5e3);
|
|
40
|
+
const onHide = () => this.flush(true);
|
|
41
|
+
document.addEventListener("visibilitychange", () => {
|
|
42
|
+
if (document.visibilityState === "hidden") onHide();
|
|
43
|
+
});
|
|
44
|
+
window.addEventListener("pagehide", onHide);
|
|
45
|
+
}
|
|
46
|
+
track(name, properties) {
|
|
47
|
+
if (!this.initialized) {
|
|
48
|
+
this.preInitQueue.push({ name, props: properties });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const event = {
|
|
52
|
+
name,
|
|
53
|
+
url: location.href,
|
|
54
|
+
referrer: document.referrer,
|
|
55
|
+
sessionId: this.sessionId,
|
|
56
|
+
anonymousId: this.anonymousId,
|
|
57
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58
|
+
screenWidth: window.innerWidth,
|
|
59
|
+
screenHeight: window.innerHeight,
|
|
60
|
+
language: navigator.language,
|
|
61
|
+
platform: "web",
|
|
62
|
+
properties
|
|
63
|
+
};
|
|
64
|
+
this.queue.push(event);
|
|
65
|
+
}
|
|
66
|
+
getAnonymousId() {
|
|
67
|
+
return this.anonymousId;
|
|
68
|
+
}
|
|
69
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
70
|
+
pageview() {
|
|
71
|
+
this.track("pageview");
|
|
72
|
+
}
|
|
73
|
+
flush(useBeacon = false) {
|
|
74
|
+
if (!this.projectId || this.queue.length === 0) return;
|
|
75
|
+
const events = this.queue.splice(0, 100);
|
|
76
|
+
const payload = { p: this.projectId, events };
|
|
77
|
+
const url = `${this.endpoint}/e/batch`;
|
|
78
|
+
const body = JSON.stringify(payload);
|
|
79
|
+
if (useBeacon && typeof navigator.sendBeacon === "function") {
|
|
80
|
+
navigator.sendBeacon(url, body);
|
|
81
|
+
} else {
|
|
82
|
+
fetch(url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "content-type": "application/json" },
|
|
85
|
+
body,
|
|
86
|
+
keepalive: true
|
|
87
|
+
}).catch(() => {
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (this.queue.length > 0) this.flush(useBeacon);
|
|
91
|
+
}
|
|
92
|
+
newId(prefix) {
|
|
93
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
94
|
+
const ts = Date.now().toString(36);
|
|
95
|
+
return `${prefix}_${ts}${rand}`;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var client = new BrowserClient();
|
|
99
|
+
function init(projectId, opts) {
|
|
100
|
+
client.init(projectId, opts);
|
|
101
|
+
}
|
|
102
|
+
function track(name, properties) {
|
|
103
|
+
client.track(name, properties);
|
|
104
|
+
}
|
|
105
|
+
function getAnonymousId() {
|
|
106
|
+
return client.getAnonymousId();
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
getAnonymousId,
|
|
110
|
+
init,
|
|
111
|
+
track
|
|
112
|
+
};
|
package/dist/react.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface AnalyticsProps {
|
|
2
|
+
projectId: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Drop-in React component. Add to your root layout to start tracking.
|
|
7
|
+
* Calls init() in useEffect, renders nothing.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { Analytics } from "@clamp-sh/analytics/react"
|
|
11
|
+
* <Analytics projectId="proj_xxx" />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
declare function Analytics({ projectId, endpoint }: AnalyticsProps): null;
|
|
15
|
+
|
|
16
|
+
export { Analytics, type AnalyticsProps };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface AnalyticsProps {
|
|
2
|
+
projectId: string;
|
|
3
|
+
endpoint?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Drop-in React component. Add to your root layout to start tracking.
|
|
7
|
+
* Calls init() in useEffect, renders nothing.
|
|
8
|
+
*
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { Analytics } from "@clamp-sh/analytics/react"
|
|
11
|
+
* <Analytics projectId="proj_xxx" />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
declare function Analytics({ projectId, endpoint }: AnalyticsProps): null;
|
|
15
|
+
|
|
16
|
+
export { Analytics, type AnalyticsProps };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/react.tsx
|
|
22
|
+
var react_exports = {};
|
|
23
|
+
__export(react_exports, {
|
|
24
|
+
Analytics: () => Analytics
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(react_exports);
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
31
|
+
var BrowserClient = class {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.projectId = null;
|
|
34
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
35
|
+
this.sessionId = null;
|
|
36
|
+
this.anonymousId = null;
|
|
37
|
+
this.queue = [];
|
|
38
|
+
this.preInitQueue = [];
|
|
39
|
+
this.timer = null;
|
|
40
|
+
this.initialized = false;
|
|
41
|
+
}
|
|
42
|
+
init(projectId, opts) {
|
|
43
|
+
if (typeof window === "undefined") return;
|
|
44
|
+
this.projectId = projectId;
|
|
45
|
+
if (opts?.endpoint) this.endpoint = opts.endpoint;
|
|
46
|
+
this.initialized = true;
|
|
47
|
+
this.sessionId = sessionStorage.getItem("clamp_sid") ?? this.newId("ses");
|
|
48
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
49
|
+
this.anonymousId = localStorage.getItem("clamp_aid") ?? this.newId("anon");
|
|
50
|
+
localStorage.setItem("clamp_aid", this.anonymousId);
|
|
51
|
+
for (const e of this.preInitQueue) {
|
|
52
|
+
this.track(e.name, e.props);
|
|
53
|
+
}
|
|
54
|
+
this.preInitQueue = [];
|
|
55
|
+
this.pageview();
|
|
56
|
+
const origPushState = history.pushState.bind(history);
|
|
57
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
58
|
+
history.pushState = (...args) => {
|
|
59
|
+
origPushState(...args);
|
|
60
|
+
this.pageview();
|
|
61
|
+
};
|
|
62
|
+
history.replaceState = (...args) => {
|
|
63
|
+
origReplaceState(...args);
|
|
64
|
+
this.pageview();
|
|
65
|
+
};
|
|
66
|
+
window.addEventListener("popstate", () => this.pageview());
|
|
67
|
+
this.timer = setInterval(() => this.flush(), 5e3);
|
|
68
|
+
const onHide = () => this.flush(true);
|
|
69
|
+
document.addEventListener("visibilitychange", () => {
|
|
70
|
+
if (document.visibilityState === "hidden") onHide();
|
|
71
|
+
});
|
|
72
|
+
window.addEventListener("pagehide", onHide);
|
|
73
|
+
}
|
|
74
|
+
track(name, properties) {
|
|
75
|
+
if (!this.initialized) {
|
|
76
|
+
this.preInitQueue.push({ name, props: properties });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const event = {
|
|
80
|
+
name,
|
|
81
|
+
url: location.href,
|
|
82
|
+
referrer: document.referrer,
|
|
83
|
+
sessionId: this.sessionId,
|
|
84
|
+
anonymousId: this.anonymousId,
|
|
85
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
86
|
+
screenWidth: window.innerWidth,
|
|
87
|
+
screenHeight: window.innerHeight,
|
|
88
|
+
language: navigator.language,
|
|
89
|
+
platform: "web",
|
|
90
|
+
properties
|
|
91
|
+
};
|
|
92
|
+
this.queue.push(event);
|
|
93
|
+
}
|
|
94
|
+
getAnonymousId() {
|
|
95
|
+
return this.anonymousId;
|
|
96
|
+
}
|
|
97
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
98
|
+
pageview() {
|
|
99
|
+
this.track("pageview");
|
|
100
|
+
}
|
|
101
|
+
flush(useBeacon = false) {
|
|
102
|
+
if (!this.projectId || this.queue.length === 0) return;
|
|
103
|
+
const events = this.queue.splice(0, 100);
|
|
104
|
+
const payload = { p: this.projectId, events };
|
|
105
|
+
const url = `${this.endpoint}/e/batch`;
|
|
106
|
+
const body = JSON.stringify(payload);
|
|
107
|
+
if (useBeacon && typeof navigator.sendBeacon === "function") {
|
|
108
|
+
navigator.sendBeacon(url, body);
|
|
109
|
+
} else {
|
|
110
|
+
fetch(url, {
|
|
111
|
+
method: "POST",
|
|
112
|
+
headers: { "content-type": "application/json" },
|
|
113
|
+
body,
|
|
114
|
+
keepalive: true
|
|
115
|
+
}).catch(() => {
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (this.queue.length > 0) this.flush(useBeacon);
|
|
119
|
+
}
|
|
120
|
+
newId(prefix) {
|
|
121
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
122
|
+
const ts = Date.now().toString(36);
|
|
123
|
+
return `${prefix}_${ts}${rand}`;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var client = new BrowserClient();
|
|
127
|
+
function init(projectId, opts) {
|
|
128
|
+
client.init(projectId, opts);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/react.tsx
|
|
132
|
+
function Analytics({ projectId, endpoint }) {
|
|
133
|
+
(0, import_react.useEffect)(() => {
|
|
134
|
+
init(projectId, { endpoint });
|
|
135
|
+
}, [projectId, endpoint]);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
139
|
+
0 && (module.exports = {
|
|
140
|
+
Analytics
|
|
141
|
+
});
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react.tsx
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
8
|
+
var BrowserClient = class {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.projectId = null;
|
|
11
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
12
|
+
this.sessionId = null;
|
|
13
|
+
this.anonymousId = null;
|
|
14
|
+
this.queue = [];
|
|
15
|
+
this.preInitQueue = [];
|
|
16
|
+
this.timer = null;
|
|
17
|
+
this.initialized = false;
|
|
18
|
+
}
|
|
19
|
+
init(projectId, opts) {
|
|
20
|
+
if (typeof window === "undefined") return;
|
|
21
|
+
this.projectId = projectId;
|
|
22
|
+
if (opts?.endpoint) this.endpoint = opts.endpoint;
|
|
23
|
+
this.initialized = true;
|
|
24
|
+
this.sessionId = sessionStorage.getItem("clamp_sid") ?? this.newId("ses");
|
|
25
|
+
sessionStorage.setItem("clamp_sid", this.sessionId);
|
|
26
|
+
this.anonymousId = localStorage.getItem("clamp_aid") ?? this.newId("anon");
|
|
27
|
+
localStorage.setItem("clamp_aid", this.anonymousId);
|
|
28
|
+
for (const e of this.preInitQueue) {
|
|
29
|
+
this.track(e.name, e.props);
|
|
30
|
+
}
|
|
31
|
+
this.preInitQueue = [];
|
|
32
|
+
this.pageview();
|
|
33
|
+
const origPushState = history.pushState.bind(history);
|
|
34
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
35
|
+
history.pushState = (...args) => {
|
|
36
|
+
origPushState(...args);
|
|
37
|
+
this.pageview();
|
|
38
|
+
};
|
|
39
|
+
history.replaceState = (...args) => {
|
|
40
|
+
origReplaceState(...args);
|
|
41
|
+
this.pageview();
|
|
42
|
+
};
|
|
43
|
+
window.addEventListener("popstate", () => this.pageview());
|
|
44
|
+
this.timer = setInterval(() => this.flush(), 5e3);
|
|
45
|
+
const onHide = () => this.flush(true);
|
|
46
|
+
document.addEventListener("visibilitychange", () => {
|
|
47
|
+
if (document.visibilityState === "hidden") onHide();
|
|
48
|
+
});
|
|
49
|
+
window.addEventListener("pagehide", onHide);
|
|
50
|
+
}
|
|
51
|
+
track(name, properties) {
|
|
52
|
+
if (!this.initialized) {
|
|
53
|
+
this.preInitQueue.push({ name, props: properties });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const event = {
|
|
57
|
+
name,
|
|
58
|
+
url: location.href,
|
|
59
|
+
referrer: document.referrer,
|
|
60
|
+
sessionId: this.sessionId,
|
|
61
|
+
anonymousId: this.anonymousId,
|
|
62
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
63
|
+
screenWidth: window.innerWidth,
|
|
64
|
+
screenHeight: window.innerHeight,
|
|
65
|
+
language: navigator.language,
|
|
66
|
+
platform: "web",
|
|
67
|
+
properties
|
|
68
|
+
};
|
|
69
|
+
this.queue.push(event);
|
|
70
|
+
}
|
|
71
|
+
getAnonymousId() {
|
|
72
|
+
return this.anonymousId;
|
|
73
|
+
}
|
|
74
|
+
// ── Private ─────────────────────────────────────────────────────────
|
|
75
|
+
pageview() {
|
|
76
|
+
this.track("pageview");
|
|
77
|
+
}
|
|
78
|
+
flush(useBeacon = false) {
|
|
79
|
+
if (!this.projectId || this.queue.length === 0) return;
|
|
80
|
+
const events = this.queue.splice(0, 100);
|
|
81
|
+
const payload = { p: this.projectId, events };
|
|
82
|
+
const url = `${this.endpoint}/e/batch`;
|
|
83
|
+
const body = JSON.stringify(payload);
|
|
84
|
+
if (useBeacon && typeof navigator.sendBeacon === "function") {
|
|
85
|
+
navigator.sendBeacon(url, body);
|
|
86
|
+
} else {
|
|
87
|
+
fetch(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "content-type": "application/json" },
|
|
90
|
+
body,
|
|
91
|
+
keepalive: true
|
|
92
|
+
}).catch(() => {
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (this.queue.length > 0) this.flush(useBeacon);
|
|
96
|
+
}
|
|
97
|
+
newId(prefix) {
|
|
98
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
99
|
+
const ts = Date.now().toString(36);
|
|
100
|
+
return `${prefix}_${ts}${rand}`;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var client = new BrowserClient();
|
|
104
|
+
function init(projectId, opts) {
|
|
105
|
+
client.init(projectId, opts);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/react.tsx
|
|
109
|
+
function Analytics({ projectId, endpoint }) {
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
init(projectId, { endpoint });
|
|
112
|
+
}, [projectId, endpoint]);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
Analytics
|
|
117
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { E as EventMap, A as AnyEvents } from './types-Bc30ZeCx.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the server SDK. Call once at server startup.
|
|
5
|
+
* Requires a project API key (found in project settings).
|
|
6
|
+
*/
|
|
7
|
+
declare function init<_E extends EventMap = AnyEvents>(config: {
|
|
8
|
+
projectId: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* Track a server-side event. Returns a promise that resolves when the event is sent.
|
|
14
|
+
* Accepts an optional anonymousId to link to a browser visitor.
|
|
15
|
+
*/
|
|
16
|
+
declare function track<E extends EventMap = AnyEvents, K extends keyof E & string = string>(name: K, opts?: {
|
|
17
|
+
anonymousId?: string;
|
|
18
|
+
properties?: E[K] extends Record<string, string> ? E[K] : Record<string, string>;
|
|
19
|
+
timestamp?: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export { AnyEvents, EventMap, init, track };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { E as EventMap, A as AnyEvents } from './types-Bc30ZeCx.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize the server SDK. Call once at server startup.
|
|
5
|
+
* Requires a project API key (found in project settings).
|
|
6
|
+
*/
|
|
7
|
+
declare function init<_E extends EventMap = AnyEvents>(config: {
|
|
8
|
+
projectId: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
}): void;
|
|
12
|
+
/**
|
|
13
|
+
* Track a server-side event. Returns a promise that resolves when the event is sent.
|
|
14
|
+
* Accepts an optional anonymousId to link to a browser visitor.
|
|
15
|
+
*/
|
|
16
|
+
declare function track<E extends EventMap = AnyEvents, K extends keyof E & string = string>(name: K, opts?: {
|
|
17
|
+
anonymousId?: string;
|
|
18
|
+
properties?: E[K] extends Record<string, string> ? E[K] : Record<string, string>;
|
|
19
|
+
timestamp?: string;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export { AnyEvents, EventMap, init, track };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
init: () => init,
|
|
24
|
+
track: () => track
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(server_exports);
|
|
27
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
28
|
+
var ServerClient = class {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.projectId = null;
|
|
31
|
+
this.apiKey = null;
|
|
32
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
33
|
+
this.initialized = false;
|
|
34
|
+
}
|
|
35
|
+
init(config) {
|
|
36
|
+
this.projectId = config.projectId;
|
|
37
|
+
this.apiKey = config.apiKey;
|
|
38
|
+
if (config.endpoint) this.endpoint = config.endpoint;
|
|
39
|
+
this.initialized = true;
|
|
40
|
+
}
|
|
41
|
+
async track(name, opts) {
|
|
42
|
+
if (!this.initialized || !this.projectId || !this.apiKey) {
|
|
43
|
+
throw new Error("@clamp-sh/analytics/server: call init() before track()");
|
|
44
|
+
}
|
|
45
|
+
const payload = {
|
|
46
|
+
p: this.projectId,
|
|
47
|
+
name,
|
|
48
|
+
anonymousId: opts?.anonymousId,
|
|
49
|
+
properties: opts?.properties,
|
|
50
|
+
timestamp: opts?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
51
|
+
};
|
|
52
|
+
const res = await fetch(`${this.endpoint}/e/s`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
"x-clamp-key": this.apiKey
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(payload)
|
|
59
|
+
});
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
const body = await res.text().catch(() => "");
|
|
62
|
+
throw new Error(`@clamp-sh/analytics/server: ${res.status} ${body}`);
|
|
63
|
+
}
|
|
64
|
+
return { ok: true };
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var client = new ServerClient();
|
|
68
|
+
function init(config) {
|
|
69
|
+
client.init(config);
|
|
70
|
+
}
|
|
71
|
+
async function track(name, opts) {
|
|
72
|
+
return client.track(name, opts);
|
|
73
|
+
}
|
|
74
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
75
|
+
0 && (module.exports = {
|
|
76
|
+
init,
|
|
77
|
+
track
|
|
78
|
+
});
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
var DEFAULT_ENDPOINT = "https://api.clamp.sh";
|
|
3
|
+
var ServerClient = class {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.projectId = null;
|
|
6
|
+
this.apiKey = null;
|
|
7
|
+
this.endpoint = DEFAULT_ENDPOINT;
|
|
8
|
+
this.initialized = false;
|
|
9
|
+
}
|
|
10
|
+
init(config) {
|
|
11
|
+
this.projectId = config.projectId;
|
|
12
|
+
this.apiKey = config.apiKey;
|
|
13
|
+
if (config.endpoint) this.endpoint = config.endpoint;
|
|
14
|
+
this.initialized = true;
|
|
15
|
+
}
|
|
16
|
+
async track(name, opts) {
|
|
17
|
+
if (!this.initialized || !this.projectId || !this.apiKey) {
|
|
18
|
+
throw new Error("@clamp-sh/analytics/server: call init() before track()");
|
|
19
|
+
}
|
|
20
|
+
const payload = {
|
|
21
|
+
p: this.projectId,
|
|
22
|
+
name,
|
|
23
|
+
anonymousId: opts?.anonymousId,
|
|
24
|
+
properties: opts?.properties,
|
|
25
|
+
timestamp: opts?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
26
|
+
};
|
|
27
|
+
const res = await fetch(`${this.endpoint}/e/s`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
"x-clamp-key": this.apiKey
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify(payload)
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const body = await res.text().catch(() => "");
|
|
37
|
+
throw new Error(`@clamp-sh/analytics/server: ${res.status} ${body}`);
|
|
38
|
+
}
|
|
39
|
+
return { ok: true };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var client = new ServerClient();
|
|
43
|
+
function init(config) {
|
|
44
|
+
client.init(config);
|
|
45
|
+
}
|
|
46
|
+
async function track(name, opts) {
|
|
47
|
+
return client.track(name, opts);
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
init,
|
|
51
|
+
track
|
|
52
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Typed event map for generics. Keys = event names, values = property shapes. */
|
|
2
|
+
type EventMap = Record<string, Record<string, string> | undefined>;
|
|
3
|
+
/** Default event map (no type checking on event names). */
|
|
4
|
+
type AnyEvents = Record<string, Record<string, string> | undefined>;
|
|
5
|
+
|
|
6
|
+
export type { AnyEvents as A, EventMap as E };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Typed event map for generics. Keys = event names, values = property shapes. */
|
|
2
|
+
type EventMap = Record<string, Record<string, string> | undefined>;
|
|
3
|
+
/** Default event map (no type checking on event names). */
|
|
4
|
+
type AnyEvents = Record<string, Record<string, string> | undefined>;
|
|
5
|
+
|
|
6
|
+
export type { AnyEvents as A, EventMap as E };
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clamp-sh/analytics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight analytics SDK for Clamp. Auto-pageviews, sessions, batching. Browser, server, and React.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://clamp.sh",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/clamp-sh/analytics"
|
|
10
|
+
},
|
|
11
|
+
"keywords": ["analytics", "tracking", "pageviews", "sessions", "react", "nextjs"],
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"module": "dist/index.mjs",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.mjs",
|
|
19
|
+
"require": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./server": {
|
|
22
|
+
"types": "./dist/server.d.ts",
|
|
23
|
+
"import": "./dist/server.mjs",
|
|
24
|
+
"require": "./dist/server.js"
|
|
25
|
+
},
|
|
26
|
+
"./react": {
|
|
27
|
+
"types": "./dist/react.d.ts",
|
|
28
|
+
"import": "./dist/react.mjs",
|
|
29
|
+
"require": "./dist/react.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": ["dist"],
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"dev": "tsup --watch",
|
|
37
|
+
"prepublishOnly": "npm run build"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=18"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": { "optional": true }
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"react": "^19",
|
|
47
|
+
"@types/react": "^19",
|
|
48
|
+
"tsup": "^8",
|
|
49
|
+
"typescript": "^5"
|
|
50
|
+
}
|
|
51
|
+
}
|