@eventra_dev/eventra-sdk 0.1.3 → 0.1.5
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 +170 -68
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ Eventra SDK allows you to send **feature usage and product analytics events** to
|
|
|
9
9
|
It is designed to be:
|
|
10
10
|
|
|
11
11
|
* lightweight
|
|
12
|
-
* runtime-agnostic
|
|
12
|
+
* runtime-agnostic
|
|
13
13
|
* resilient (batching + retry)
|
|
14
14
|
* production-safe
|
|
15
|
+
* TypeScript-first
|
|
15
16
|
|
|
16
17
|
---
|
|
17
18
|
|
|
@@ -44,128 +45,229 @@ yarn add @eventra_dev/eventra-sdk
|
|
|
44
45
|
```ts
|
|
45
46
|
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
46
47
|
|
|
47
|
-
const
|
|
48
|
-
apiKey: "
|
|
49
|
-
endpoint: "https://api.eventra.dev/api/v1/events"
|
|
48
|
+
const tracker = new Eventra({
|
|
49
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
tracker.track("checkout.completed", {
|
|
53
53
|
userId: "user_123",
|
|
54
|
-
properties: {
|
|
55
|
-
plan: "pro"
|
|
56
|
-
}
|
|
57
54
|
});
|
|
58
55
|
```
|
|
59
56
|
|
|
60
57
|
---
|
|
61
58
|
|
|
62
|
-
#
|
|
59
|
+
# Where You Can Use Eventra SDK
|
|
60
|
+
|
|
61
|
+
Eventra SDK works in many environments:
|
|
62
|
+
|
|
63
|
+
* Browser applications
|
|
64
|
+
* React apps
|
|
65
|
+
* Next.js apps
|
|
66
|
+
* Node.js backends
|
|
67
|
+
* NestJS services
|
|
68
|
+
* Express APIs
|
|
69
|
+
* Vanilla JavaScript
|
|
70
|
+
* Edge runtimes
|
|
71
|
+
* Bun
|
|
72
|
+
* Deno
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
# Browser Usage
|
|
63
77
|
|
|
64
78
|
```ts
|
|
65
79
|
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
66
80
|
|
|
67
|
-
const
|
|
68
|
-
apiKey: "
|
|
69
|
-
endpoint: "https://api.eventra.dev/api/v1/events"
|
|
81
|
+
const tracker = new Eventra({
|
|
82
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
70
83
|
});
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
userId: "user_1",
|
|
85
|
+
tracker.track("page.viewed", {
|
|
74
86
|
properties: {
|
|
75
|
-
|
|
87
|
+
path: window.location.pathname
|
|
76
88
|
}
|
|
77
89
|
});
|
|
78
90
|
```
|
|
79
91
|
|
|
80
|
-
|
|
92
|
+
The SDK automatically:
|
|
81
93
|
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
94
|
+
* batches events
|
|
95
|
+
* retries failed requests
|
|
96
|
+
* flushes on page exit
|
|
85
97
|
|
|
86
98
|
---
|
|
87
99
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
You can configure the SDK behaviour:
|
|
100
|
+
# React Usage
|
|
91
101
|
|
|
92
102
|
```ts
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
endpoint: "https://api.eventra.dev/api/v1/events",
|
|
103
|
+
import { useEffect } from "react";
|
|
104
|
+
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
maxQueueSize: 10000,
|
|
101
|
-
maxRetries: 3
|
|
106
|
+
const tracker = new Eventra({
|
|
107
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
102
108
|
});
|
|
109
|
+
|
|
110
|
+
export function App() {
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
tracker.track("app.loaded");
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
return <div>Hello</div>;
|
|
117
|
+
}
|
|
103
118
|
```
|
|
104
119
|
|
|
105
|
-
|
|
120
|
+
---
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
# Next.js Usage
|
|
123
|
+
|
|
124
|
+
Client component example:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
"use client";
|
|
128
|
+
|
|
129
|
+
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
130
|
+
|
|
131
|
+
const tracker = new Eventra({
|
|
132
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export function CheckoutButton() {
|
|
136
|
+
return (
|
|
137
|
+
<button
|
|
138
|
+
onClick={() => tracker.track("checkout.started")}
|
|
139
|
+
>
|
|
140
|
+
Checkout
|
|
141
|
+
</button>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
```
|
|
115
145
|
|
|
116
146
|
---
|
|
117
147
|
|
|
118
|
-
#
|
|
148
|
+
# Node.js Usage
|
|
119
149
|
|
|
120
150
|
```ts
|
|
121
151
|
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
122
152
|
|
|
123
|
-
const
|
|
124
|
-
apiKey: "
|
|
125
|
-
endpoint: "https://api.eventra.dev/api/v1/events"
|
|
153
|
+
const tracker = new Eventra({
|
|
154
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
126
155
|
});
|
|
127
156
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
path: window.location.pathname
|
|
131
|
-
}
|
|
157
|
+
tracker.track("invoice.created", {
|
|
158
|
+
userId: "user_123",
|
|
132
159
|
});
|
|
133
160
|
```
|
|
134
161
|
|
|
135
|
-
|
|
162
|
+
---
|
|
136
163
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
164
|
+
# NestJS Usage
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { Injectable } from "@nestjs/common";
|
|
168
|
+
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
169
|
+
|
|
170
|
+
@Injectable()
|
|
171
|
+
export class BillingService {
|
|
172
|
+
|
|
173
|
+
private tracker = new Eventra({
|
|
174
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
charge(userId: string) {
|
|
178
|
+
this.tracker.track("invoice.created", {
|
|
179
|
+
userId
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
140
184
|
|
|
141
185
|
---
|
|
142
186
|
|
|
143
|
-
#
|
|
187
|
+
# Express Usage
|
|
144
188
|
|
|
145
189
|
```ts
|
|
190
|
+
import express from "express";
|
|
146
191
|
import { Eventra } from "@eventra_dev/eventra-sdk";
|
|
147
192
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
193
|
+
const app = express();
|
|
194
|
+
|
|
195
|
+
const tracker = new Eventra({
|
|
196
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
151
197
|
});
|
|
152
198
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
199
|
+
app.post("/checkout", (req, res) => {
|
|
200
|
+
|
|
201
|
+
tracker.track("checkout.completed");
|
|
202
|
+
|
|
203
|
+
res.sendStatus(200);
|
|
157
204
|
});
|
|
158
205
|
```
|
|
159
206
|
|
|
160
207
|
---
|
|
161
208
|
|
|
162
|
-
#
|
|
209
|
+
# Vanilla JavaScript (CDN)
|
|
210
|
+
|
|
211
|
+
You can use Eventra without a bundler.
|
|
212
|
+
|
|
213
|
+
```html
|
|
214
|
+
<script type="module">
|
|
163
215
|
|
|
164
|
-
|
|
216
|
+
import { Eventra } from "https://esm.sh/@eventra_dev/eventra-sdk";
|
|
217
|
+
|
|
218
|
+
const tracker = new Eventra({
|
|
219
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
tracker.track("page.viewed");
|
|
223
|
+
|
|
224
|
+
</script>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
# Configuration
|
|
230
|
+
|
|
231
|
+
You can configure the SDK behaviour.
|
|
165
232
|
|
|
166
233
|
```ts
|
|
167
234
|
const eventra = new Eventra({
|
|
168
|
-
|
|
235
|
+
|
|
236
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
237
|
+
|
|
238
|
+
endpoint: "https://api.eventra.dev/api/v1/events",
|
|
239
|
+
|
|
240
|
+
flushInterval: 2000,
|
|
241
|
+
maxBatchSize: 50,
|
|
242
|
+
maxQueueSize: 10000,
|
|
243
|
+
maxRetries: 3
|
|
244
|
+
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
# Options
|
|
251
|
+
|
|
252
|
+
| option | description |
|
|
253
|
+
| ------------- | ---------------------------------- |
|
|
254
|
+
| apiKey | Project API key |
|
|
255
|
+
| endpoint | Event ingestion endpoint |
|
|
256
|
+
| flushInterval | Batch flush interval (ms) |
|
|
257
|
+
| maxBatchSize | Maximum events per batch |
|
|
258
|
+
| maxQueueSize | Maximum buffered events |
|
|
259
|
+
| maxRetries | Retry attempts for failed requests |
|
|
260
|
+
| multiTabMode | Browser tab coordination mode |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
# Multi-Tab Mode (Browser)
|
|
265
|
+
|
|
266
|
+
To avoid duplicate event sending across multiple tabs:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
const tracker = new Eventra({
|
|
270
|
+
apiKey: "YOUR_PROJECT_API_KEY",
|
|
169
271
|
multiTabMode: "leader"
|
|
170
272
|
});
|
|
171
273
|
```
|
|
@@ -176,10 +278,8 @@ Only one tab will send events.
|
|
|
176
278
|
|
|
177
279
|
# Manual Flush
|
|
178
280
|
|
|
179
|
-
You can flush the queue manually:
|
|
180
|
-
|
|
181
281
|
```ts
|
|
182
|
-
await
|
|
282
|
+
await tracker.flush();
|
|
183
283
|
```
|
|
184
284
|
|
|
185
285
|
---
|
|
@@ -187,7 +287,7 @@ await eventra.flush();
|
|
|
187
287
|
# Shutdown / Cleanup
|
|
188
288
|
|
|
189
289
|
```ts
|
|
190
|
-
|
|
290
|
+
tracker.destroy();
|
|
191
291
|
```
|
|
192
292
|
|
|
193
293
|
Stops timers and prevents further event sending.
|
|
@@ -196,13 +296,14 @@ Stops timers and prevents further event sending.
|
|
|
196
296
|
|
|
197
297
|
# Runtime Support
|
|
198
298
|
|
|
199
|
-
|
|
299
|
+
Eventra SDK works in:
|
|
200
300
|
|
|
201
301
|
* Node.js
|
|
202
302
|
* Browser
|
|
203
303
|
* Bun
|
|
204
304
|
* Deno
|
|
205
305
|
* Edge runtimes
|
|
306
|
+
* Serverless environments
|
|
206
307
|
|
|
207
308
|
---
|
|
208
309
|
|
|
@@ -234,7 +335,8 @@ Events are sent in batches:
|
|
|
234
335
|
# Error Handling
|
|
235
336
|
|
|
236
337
|
Client errors (4xx) are **not retried**.
|
|
237
|
-
|
|
338
|
+
|
|
339
|
+
Server errors (5xx) are retried using **exponential backoff**.
|
|
238
340
|
|
|
239
341
|
---
|
|
240
342
|
|
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
27
|
// src/client.ts
|
|
28
|
-
var SDK_VERSION = "0.1.
|
|
28
|
+
var SDK_VERSION = "0.1.5";
|
|
29
29
|
var DEFAULTS = {
|
|
30
30
|
endpoint: "http://api.eventra.dev/api/v1/ingest/batch",
|
|
31
31
|
flushInterval: 2e3,
|
|
@@ -108,7 +108,7 @@ var Eventra = class {
|
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
this.sdkInfo = {
|
|
111
|
-
name: "@eventra
|
|
111
|
+
name: "@eventra_dev/eventra-sdk",
|
|
112
112
|
version: SDK_VERSION,
|
|
113
113
|
runtime: this.detectRuntime()
|
|
114
114
|
};
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from \"./client\";\nexport * from \"./types\";\n","import {\n TrackerOptions,\n TrackEvent,\n SdkInfo,\n} from \"./types\";\n\n/* ---------------- Constants ---------------- */\n\n\ndeclare const __SDK_VERSION__: string;\ndeclare const __EVENTRA_ENDPOINT__: string | undefined;\n\nconst SDK_VERSION = __SDK_VERSION__;\n\nconst DEFAULTS = {\n endpoint: __EVENTRA_ENDPOINT__ ?? \"\",\n flushInterval: 2000,\n maxBatchSize: 50,\n maxQueueSize: 10_000,\n maxRetries: 3,\n retryBaseDelay: 300,\n};\n\n/* ---------------- Helpers ---------------- */\n\nfunction getGlobalFetch(): typeof fetch | undefined {\n if (typeof fetch !== \"undefined\") {\n return fetch.bind(globalThis);\n }\n return undefined;\n}\n\nfunction generateUUIDv4(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n\n // ultra-rare fallback (не должен происходить в нормальной среде)\n const rnd = Math.random().toString(16).slice(2);\n const ts = Date.now().toString(16);\n return `${ts}-${rnd}`;\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/* ---------------- Leader Election (minimal) ---------------- */\n\nconst LEADER_KEY = \"__eventra_sdk_leader__\";\nconst LEADER_TTL = 4000;\n\nfunction tryBecomeLeader(): boolean {\n if (!isBrowser()) return true;\n\n try {\n const now = Date.now();\n const raw = localStorage.getItem(LEADER_KEY);\n\n if (!raw) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n const parsed = JSON.parse(raw) as { ts: number };\n\n if (now - parsed.ts > LEADER_TTL) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n return false;\n } catch {\n return true; // fail-open\n }\n}\n\n/* ============================================================ */\n\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private flushInterval: number;\n private maxBatchSize: number;\n private maxQueueSize: number;\n private maxRetries: number;\n private retryBaseDelay: number;\n private multiTabMode: \"independent\" | \"leader\";\n\n private fetchImpl?: typeof fetch;\n private queue: TrackEvent[] = [];\n private timer?: ReturnType<typeof setInterval>;\n private inFlight = false;\n private destroyed = false;\n private droppedEvents = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) {\n throw new Error(\"Eventra: apiKey is required\");\n }\n\n this.apiKey = options.apiKey;\n\n this.endpoint = options.endpoint ?? DEFAULTS.endpoint ?? \"\";\n if (!this.endpoint) {\n throw new Error(\"Eventra: endpoint is not configured\");\n }\n\n this.flushInterval =\n options.flushInterval ?? DEFAULTS.flushInterval;\n\n this.maxBatchSize =\n options.maxBatchSize ?? DEFAULTS.maxBatchSize;\n\n this.maxQueueSize =\n options.maxQueueSize ?? DEFAULTS.maxQueueSize;\n\n this.maxRetries =\n options.maxRetries ?? DEFAULTS.maxRetries;\n\n this.retryBaseDelay =\n options.retryBaseDelayMs ?? DEFAULTS.retryBaseDelay;\n\n this.multiTabMode =\n options.multiTabMode ?? \"independent\";\n\n this.fetchImpl =\n options.fetchImpl ?? getGlobalFetch();\n\n if (!this.fetchImpl) {\n throw new Error(\n \"Eventra: fetch is not available. Provide fetchImpl.\"\n );\n }\n\n this.sdkInfo = {\n name: \"@eventra/sdk\",\n version: SDK_VERSION,\n runtime: this.detectRuntime(),\n };\n\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n if (options.autoFlushOnExit !== false) {\n this.setupExitHandlers();\n }\n\n this.onEventsDropped = options.onEventsDropped;\n }\n\n private onEventsDropped?: (count: number) => void;\n\n /* ---------------- Public API ---------------- */\n\n track(\n name: string,\n options?: {\n userId?: string;\n properties?: Record<string, unknown>;\n }\n ) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueueSize) {\n this.droppedEvents++;\n this.onEventsDropped?.(this.droppedEvents);\n return;\n }\n\n this.queue.push({\n idempotencyKey: generateUUIDv4(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n });\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.destroyed) return;\n if (this.inFlight) return;\n if (this.queue.length === 0) return;\n\n if (\n this.multiTabMode === \"leader\" &&\n !tryBecomeLeader()\n ) {\n return;\n }\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n try {\n await this.send(batch);\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n if (this.timer) clearInterval(this.timer);\n }\n\n /* ---------------- Transport ---------------- */\n\n private trySendBeacon(payload: string): boolean {\n if (!isBrowser()) return false;\n\n try {\n if (navigator.sendBeacon) {\n const blob = new Blob([payload], {\n type: \"application/json\",\n });\n return navigator.sendBeacon(this.endpoint, blob);\n }\n } catch {\n // ignore\n }\n\n return false;\n }\n\n private async send(events: TrackEvent[]) {\n const payload = JSON.stringify({\n sentAt: new Date().toISOString(),\n sdk: this.sdkInfo,\n events,\n });\n\n // beacon fast-path (browser only)\n if (\n isBrowser() &&\n payload.length < 60_000 &&\n document.visibilityState === \"hidden\"\n ) {\n if (this.trySendBeacon(payload)) return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.maxRetries) {\n try {\n const res = await this.fetchImpl!(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n });\n\n // ❗ DO NOT retry client errors\n if (res.status >= 400 && res.status < 500) {\n if (res.status === 401 || res.status === 403) {\n console.error(\"Eventra: Invalid API key\");\n }\n return;\n }\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n\n return;\n } catch (err) {\n attempt++;\n\n if (attempt > this.maxRetries) {\n throw err;\n }\n\n const jitter = 0.5 + Math.random(); // 0.5–1.5\n const delay =\n this.retryBaseDelay *\n 2 ** (attempt - 1) *\n jitter;\n\n await sleep(delay);\n }\n }\n }\n\n /* ---------------- Runtime ---------------- */\n\n private detectRuntime(): string {\n if (typeof window !== \"undefined\") return \"browser\";\n\n const g = globalThis as Record<string, unknown>;\n\n if (g[\"Deno\"]) return \"deno\";\n if (g[\"Bun\"]) return \"bun\";\n\n const maybeProcess = g[\"process\"] as\n | { versions?: { node?: string } }\n | undefined;\n\n if (maybeProcess?.versions?.node) return \"node\";\n\n return \"unknown\";\n }\n\n /* ---------------- Timer ---------------- */\n\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n /* ---------------- Exit handling ---------------- */\n\n private setupExitHandlers() {\n // browser\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n // node\n const g = globalThis as Record<string, unknown>;\n\n const maybeProcess = g[\"process\"] as\n | { once?: (event: string, cb: () => void) => void }\n | undefined;\n\n if (typeof maybeProcess?.once === \"function\") {\n const shutdown = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n maybeProcess.once(\"SIGINT\", shutdown);\n maybeProcess.once(\"SIGTERM\", shutdown);\n maybeProcess.once(\"beforeExit\", shutdown);\n }\n }\n}\n\nexport * from \"./types\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from \"./client\";\nexport * from \"./types\";\n","import {\n TrackerOptions,\n TrackEvent,\n SdkInfo,\n} from \"./types\";\n\n/* ---------------- Constants ---------------- */\n\n\ndeclare const __SDK_VERSION__: string;\ndeclare const __EVENTRA_ENDPOINT__: string | undefined;\n\nconst SDK_VERSION = __SDK_VERSION__;\n\nconst DEFAULTS = {\n endpoint: __EVENTRA_ENDPOINT__ ?? \"\",\n flushInterval: 2000,\n maxBatchSize: 50,\n maxQueueSize: 10_000,\n maxRetries: 3,\n retryBaseDelay: 300,\n};\n\n/* ---------------- Helpers ---------------- */\n\nfunction getGlobalFetch(): typeof fetch | undefined {\n if (typeof fetch !== \"undefined\") {\n return fetch.bind(globalThis);\n }\n return undefined;\n}\n\nfunction generateUUIDv4(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n\n // ultra-rare fallback\n const rnd = Math.random().toString(16).slice(2);\n const ts = Date.now().toString(16);\n return `${ts}-${rnd}`;\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/* ---------------- Leader Election (minimal) ---------------- */\n\nconst LEADER_KEY = \"__eventra_sdk_leader__\";\nconst LEADER_TTL = 4000;\n\nfunction tryBecomeLeader(): boolean {\n if (!isBrowser()) return true;\n\n try {\n const now = Date.now();\n const raw = localStorage.getItem(LEADER_KEY);\n\n if (!raw) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n const parsed = JSON.parse(raw) as { ts: number };\n\n if (now - parsed.ts > LEADER_TTL) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n return false;\n } catch {\n return true; // fail-open\n }\n}\n\n/* ============================================================ */\n\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private flushInterval: number;\n private maxBatchSize: number;\n private maxQueueSize: number;\n private maxRetries: number;\n private retryBaseDelay: number;\n private multiTabMode: \"independent\" | \"leader\";\n\n private fetchImpl?: typeof fetch;\n private queue: TrackEvent[] = [];\n private timer?: ReturnType<typeof setInterval>;\n private inFlight = false;\n private destroyed = false;\n private droppedEvents = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) {\n throw new Error(\"Eventra: apiKey is required\");\n }\n\n this.apiKey = options.apiKey;\n\n this.endpoint = options.endpoint ?? DEFAULTS.endpoint ?? \"\";\n if (!this.endpoint) {\n throw new Error(\"Eventra: endpoint is not configured\");\n }\n\n this.flushInterval =\n options.flushInterval ?? DEFAULTS.flushInterval;\n\n this.maxBatchSize =\n options.maxBatchSize ?? DEFAULTS.maxBatchSize;\n\n this.maxQueueSize =\n options.maxQueueSize ?? DEFAULTS.maxQueueSize;\n\n this.maxRetries =\n options.maxRetries ?? DEFAULTS.maxRetries;\n\n this.retryBaseDelay =\n options.retryBaseDelayMs ?? DEFAULTS.retryBaseDelay;\n\n this.multiTabMode =\n options.multiTabMode ?? \"independent\";\n\n this.fetchImpl =\n options.fetchImpl ?? getGlobalFetch();\n\n if (!this.fetchImpl) {\n throw new Error(\n \"Eventra: fetch is not available. Provide fetchImpl.\"\n );\n }\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: SDK_VERSION,\n runtime: this.detectRuntime(),\n };\n\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n if (options.autoFlushOnExit !== false) {\n this.setupExitHandlers();\n }\n\n this.onEventsDropped = options.onEventsDropped;\n }\n\n private onEventsDropped?: (count: number) => void;\n\n /* ---------------- Public API ---------------- */\n\n track(\n name: string,\n options?: {\n userId?: string;\n properties?: Record<string, unknown>;\n }\n ) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueueSize) {\n this.droppedEvents++;\n this.onEventsDropped?.(this.droppedEvents);\n return;\n }\n\n this.queue.push({\n idempotencyKey: generateUUIDv4(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n });\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.destroyed) return;\n if (this.inFlight) return;\n if (this.queue.length === 0) return;\n\n if (\n this.multiTabMode === \"leader\" &&\n !tryBecomeLeader()\n ) {\n return;\n }\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n try {\n await this.send(batch);\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n if (this.timer) clearInterval(this.timer);\n }\n\n /* ---------------- Transport ---------------- */\n\n private trySendBeacon(payload: string): boolean {\n if (!isBrowser()) return false;\n\n try {\n if (navigator.sendBeacon) {\n const blob = new Blob([payload], {\n type: \"application/json\",\n });\n return navigator.sendBeacon(this.endpoint, blob);\n }\n } catch {\n // ignore\n }\n\n return false;\n }\n\n private async send(events: TrackEvent[]) {\n const payload = JSON.stringify({\n sentAt: new Date().toISOString(),\n sdk: this.sdkInfo,\n events,\n });\n\n // beacon fast-path (browser only)\n if (\n isBrowser() &&\n payload.length < 60_000 &&\n document.visibilityState === \"hidden\"\n ) {\n if (this.trySendBeacon(payload)) return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.maxRetries) {\n try {\n const res = await this.fetchImpl!(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n });\n\n // ❗ DO NOT retry client errors\n if (res.status >= 400 && res.status < 500) {\n if (res.status === 401 || res.status === 403) {\n console.error(\"Eventra: Invalid API key\");\n }\n return;\n }\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n\n return;\n } catch (err) {\n attempt++;\n\n if (attempt > this.maxRetries) {\n throw err;\n }\n\n const jitter = 0.5 + Math.random(); // 0.5–1.5\n const delay =\n this.retryBaseDelay *\n 2 ** (attempt - 1) *\n jitter;\n\n await sleep(delay);\n }\n }\n }\n\n /* ---------------- Runtime ---------------- */\n\n private detectRuntime(): string {\n if (typeof window !== \"undefined\") return \"browser\";\n\n const g = globalThis as Record<string, unknown>;\n\n if (g[\"Deno\"]) return \"deno\";\n if (g[\"Bun\"]) return \"bun\";\n\n const maybeProcess = g[\"process\"] as\n | { versions?: { node?: string } }\n | undefined;\n\n if (maybeProcess?.versions?.node) return \"node\";\n\n return \"unknown\";\n }\n\n /* ---------------- Timer ---------------- */\n\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n /* ---------------- Exit handling ---------------- */\n\n private setupExitHandlers() {\n // browser\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n // node\n const g = globalThis as Record<string, unknown>;\n\n const maybeProcess = g[\"process\"] as\n | { once?: (event: string, cb: () => void) => void }\n | undefined;\n\n if (typeof maybeProcess?.once === \"function\") {\n const shutdown = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n maybeProcess.once(\"SIGINT\", shutdown);\n maybeProcess.once(\"SIGTERM\", shutdown);\n maybeProcess.once(\"beforeExit\", shutdown);\n }\n }\n}\n\nexport * from \"./types\";\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
var SDK_VERSION = "0.1.
|
|
2
|
+
var SDK_VERSION = "0.1.5";
|
|
3
3
|
var DEFAULTS = {
|
|
4
4
|
endpoint: "http://api.eventra.dev/api/v1/ingest/batch",
|
|
5
5
|
flushInterval: 2e3,
|
|
@@ -82,7 +82,7 @@ var Eventra = class {
|
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
this.sdkInfo = {
|
|
85
|
-
name: "@eventra
|
|
85
|
+
name: "@eventra_dev/eventra-sdk",
|
|
86
86
|
version: SDK_VERSION,
|
|
87
87
|
runtime: this.detectRuntime()
|
|
88
88
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import {\n TrackerOptions,\n TrackEvent,\n SdkInfo,\n} from \"./types\";\n\n/* ---------------- Constants ---------------- */\n\n\ndeclare const __SDK_VERSION__: string;\ndeclare const __EVENTRA_ENDPOINT__: string | undefined;\n\nconst SDK_VERSION = __SDK_VERSION__;\n\nconst DEFAULTS = {\n endpoint: __EVENTRA_ENDPOINT__ ?? \"\",\n flushInterval: 2000,\n maxBatchSize: 50,\n maxQueueSize: 10_000,\n maxRetries: 3,\n retryBaseDelay: 300,\n};\n\n/* ---------------- Helpers ---------------- */\n\nfunction getGlobalFetch(): typeof fetch | undefined {\n if (typeof fetch !== \"undefined\") {\n return fetch.bind(globalThis);\n }\n return undefined;\n}\n\nfunction generateUUIDv4(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n\n // ultra-rare fallback (не должен происходить в нормальной среде)\n const rnd = Math.random().toString(16).slice(2);\n const ts = Date.now().toString(16);\n return `${ts}-${rnd}`;\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/* ---------------- Leader Election (minimal) ---------------- */\n\nconst LEADER_KEY = \"__eventra_sdk_leader__\";\nconst LEADER_TTL = 4000;\n\nfunction tryBecomeLeader(): boolean {\n if (!isBrowser()) return true;\n\n try {\n const now = Date.now();\n const raw = localStorage.getItem(LEADER_KEY);\n\n if (!raw) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n const parsed = JSON.parse(raw) as { ts: number };\n\n if (now - parsed.ts > LEADER_TTL) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n return false;\n } catch {\n return true; // fail-open\n }\n}\n\n/* ============================================================ */\n\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private flushInterval: number;\n private maxBatchSize: number;\n private maxQueueSize: number;\n private maxRetries: number;\n private retryBaseDelay: number;\n private multiTabMode: \"independent\" | \"leader\";\n\n private fetchImpl?: typeof fetch;\n private queue: TrackEvent[] = [];\n private timer?: ReturnType<typeof setInterval>;\n private inFlight = false;\n private destroyed = false;\n private droppedEvents = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) {\n throw new Error(\"Eventra: apiKey is required\");\n }\n\n this.apiKey = options.apiKey;\n\n this.endpoint = options.endpoint ?? DEFAULTS.endpoint ?? \"\";\n if (!this.endpoint) {\n throw new Error(\"Eventra: endpoint is not configured\");\n }\n\n this.flushInterval =\n options.flushInterval ?? DEFAULTS.flushInterval;\n\n this.maxBatchSize =\n options.maxBatchSize ?? DEFAULTS.maxBatchSize;\n\n this.maxQueueSize =\n options.maxQueueSize ?? DEFAULTS.maxQueueSize;\n\n this.maxRetries =\n options.maxRetries ?? DEFAULTS.maxRetries;\n\n this.retryBaseDelay =\n options.retryBaseDelayMs ?? DEFAULTS.retryBaseDelay;\n\n this.multiTabMode =\n options.multiTabMode ?? \"independent\";\n\n this.fetchImpl =\n options.fetchImpl ?? getGlobalFetch();\n\n if (!this.fetchImpl) {\n throw new Error(\n \"Eventra: fetch is not available. Provide fetchImpl.\"\n );\n }\n\n this.sdkInfo = {\n name: \"@eventra/sdk\",\n version: SDK_VERSION,\n runtime: this.detectRuntime(),\n };\n\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n if (options.autoFlushOnExit !== false) {\n this.setupExitHandlers();\n }\n\n this.onEventsDropped = options.onEventsDropped;\n }\n\n private onEventsDropped?: (count: number) => void;\n\n /* ---------------- Public API ---------------- */\n\n track(\n name: string,\n options?: {\n userId?: string;\n properties?: Record<string, unknown>;\n }\n ) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueueSize) {\n this.droppedEvents++;\n this.onEventsDropped?.(this.droppedEvents);\n return;\n }\n\n this.queue.push({\n idempotencyKey: generateUUIDv4(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n });\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.destroyed) return;\n if (this.inFlight) return;\n if (this.queue.length === 0) return;\n\n if (\n this.multiTabMode === \"leader\" &&\n !tryBecomeLeader()\n ) {\n return;\n }\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n try {\n await this.send(batch);\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n if (this.timer) clearInterval(this.timer);\n }\n\n /* ---------------- Transport ---------------- */\n\n private trySendBeacon(payload: string): boolean {\n if (!isBrowser()) return false;\n\n try {\n if (navigator.sendBeacon) {\n const blob = new Blob([payload], {\n type: \"application/json\",\n });\n return navigator.sendBeacon(this.endpoint, blob);\n }\n } catch {\n // ignore\n }\n\n return false;\n }\n\n private async send(events: TrackEvent[]) {\n const payload = JSON.stringify({\n sentAt: new Date().toISOString(),\n sdk: this.sdkInfo,\n events,\n });\n\n // beacon fast-path (browser only)\n if (\n isBrowser() &&\n payload.length < 60_000 &&\n document.visibilityState === \"hidden\"\n ) {\n if (this.trySendBeacon(payload)) return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.maxRetries) {\n try {\n const res = await this.fetchImpl!(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n });\n\n // ❗ DO NOT retry client errors\n if (res.status >= 400 && res.status < 500) {\n if (res.status === 401 || res.status === 403) {\n console.error(\"Eventra: Invalid API key\");\n }\n return;\n }\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n\n return;\n } catch (err) {\n attempt++;\n\n if (attempt > this.maxRetries) {\n throw err;\n }\n\n const jitter = 0.5 + Math.random(); // 0.5–1.5\n const delay =\n this.retryBaseDelay *\n 2 ** (attempt - 1) *\n jitter;\n\n await sleep(delay);\n }\n }\n }\n\n /* ---------------- Runtime ---------------- */\n\n private detectRuntime(): string {\n if (typeof window !== \"undefined\") return \"browser\";\n\n const g = globalThis as Record<string, unknown>;\n\n if (g[\"Deno\"]) return \"deno\";\n if (g[\"Bun\"]) return \"bun\";\n\n const maybeProcess = g[\"process\"] as\n | { versions?: { node?: string } }\n | undefined;\n\n if (maybeProcess?.versions?.node) return \"node\";\n\n return \"unknown\";\n }\n\n /* ---------------- Timer ---------------- */\n\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n /* ---------------- Exit handling ---------------- */\n\n private setupExitHandlers() {\n // browser\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n // node\n const g = globalThis as Record<string, unknown>;\n\n const maybeProcess = g[\"process\"] as\n | { once?: (event: string, cb: () => void) => void }\n | undefined;\n\n if (typeof maybeProcess?.once === \"function\") {\n const shutdown = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n maybeProcess.once(\"SIGINT\", shutdown);\n maybeProcess.once(\"SIGTERM\", shutdown);\n maybeProcess.once(\"beforeExit\", shutdown);\n }\n }\n}\n\nexport * from \"./types\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import {\n TrackerOptions,\n TrackEvent,\n SdkInfo,\n} from \"./types\";\n\n/* ---------------- Constants ---------------- */\n\n\ndeclare const __SDK_VERSION__: string;\ndeclare const __EVENTRA_ENDPOINT__: string | undefined;\n\nconst SDK_VERSION = __SDK_VERSION__;\n\nconst DEFAULTS = {\n endpoint: __EVENTRA_ENDPOINT__ ?? \"\",\n flushInterval: 2000,\n maxBatchSize: 50,\n maxQueueSize: 10_000,\n maxRetries: 3,\n retryBaseDelay: 300,\n};\n\n/* ---------------- Helpers ---------------- */\n\nfunction getGlobalFetch(): typeof fetch | undefined {\n if (typeof fetch !== \"undefined\") {\n return fetch.bind(globalThis);\n }\n return undefined;\n}\n\nfunction generateUUIDv4(): string {\n if (\n typeof crypto !== \"undefined\" &&\n typeof crypto.randomUUID === \"function\"\n ) {\n return crypto.randomUUID();\n }\n\n // ultra-rare fallback\n const rnd = Math.random().toString(16).slice(2);\n const ts = Date.now().toString(16);\n return `${ts}-${rnd}`;\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/* ---------------- Leader Election (minimal) ---------------- */\n\nconst LEADER_KEY = \"__eventra_sdk_leader__\";\nconst LEADER_TTL = 4000;\n\nfunction tryBecomeLeader(): boolean {\n if (!isBrowser()) return true;\n\n try {\n const now = Date.now();\n const raw = localStorage.getItem(LEADER_KEY);\n\n if (!raw) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n const parsed = JSON.parse(raw) as { ts: number };\n\n if (now - parsed.ts > LEADER_TTL) {\n localStorage.setItem(\n LEADER_KEY,\n JSON.stringify({ ts: now })\n );\n return true;\n }\n\n return false;\n } catch {\n return true; // fail-open\n }\n}\n\n/* ============================================================ */\n\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private flushInterval: number;\n private maxBatchSize: number;\n private maxQueueSize: number;\n private maxRetries: number;\n private retryBaseDelay: number;\n private multiTabMode: \"independent\" | \"leader\";\n\n private fetchImpl?: typeof fetch;\n private queue: TrackEvent[] = [];\n private timer?: ReturnType<typeof setInterval>;\n private inFlight = false;\n private destroyed = false;\n private droppedEvents = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) {\n throw new Error(\"Eventra: apiKey is required\");\n }\n\n this.apiKey = options.apiKey;\n\n this.endpoint = options.endpoint ?? DEFAULTS.endpoint ?? \"\";\n if (!this.endpoint) {\n throw new Error(\"Eventra: endpoint is not configured\");\n }\n\n this.flushInterval =\n options.flushInterval ?? DEFAULTS.flushInterval;\n\n this.maxBatchSize =\n options.maxBatchSize ?? DEFAULTS.maxBatchSize;\n\n this.maxQueueSize =\n options.maxQueueSize ?? DEFAULTS.maxQueueSize;\n\n this.maxRetries =\n options.maxRetries ?? DEFAULTS.maxRetries;\n\n this.retryBaseDelay =\n options.retryBaseDelayMs ?? DEFAULTS.retryBaseDelay;\n\n this.multiTabMode =\n options.multiTabMode ?? \"independent\";\n\n this.fetchImpl =\n options.fetchImpl ?? getGlobalFetch();\n\n if (!this.fetchImpl) {\n throw new Error(\n \"Eventra: fetch is not available. Provide fetchImpl.\"\n );\n }\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: SDK_VERSION,\n runtime: this.detectRuntime(),\n };\n\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n if (options.autoFlushOnExit !== false) {\n this.setupExitHandlers();\n }\n\n this.onEventsDropped = options.onEventsDropped;\n }\n\n private onEventsDropped?: (count: number) => void;\n\n /* ---------------- Public API ---------------- */\n\n track(\n name: string,\n options?: {\n userId?: string;\n properties?: Record<string, unknown>;\n }\n ) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueueSize) {\n this.droppedEvents++;\n this.onEventsDropped?.(this.droppedEvents);\n return;\n }\n\n this.queue.push({\n idempotencyKey: generateUUIDv4(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n });\n\n if (this.queue.length >= this.maxBatchSize) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.destroyed) return;\n if (this.inFlight) return;\n if (this.queue.length === 0) return;\n\n if (\n this.multiTabMode === \"leader\" &&\n !tryBecomeLeader()\n ) {\n return;\n }\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.queue.length);\n\n try {\n await this.send(batch);\n } catch {\n this.queue.unshift(...batch);\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n if (this.timer) clearInterval(this.timer);\n }\n\n /* ---------------- Transport ---------------- */\n\n private trySendBeacon(payload: string): boolean {\n if (!isBrowser()) return false;\n\n try {\n if (navigator.sendBeacon) {\n const blob = new Blob([payload], {\n type: \"application/json\",\n });\n return navigator.sendBeacon(this.endpoint, blob);\n }\n } catch {\n // ignore\n }\n\n return false;\n }\n\n private async send(events: TrackEvent[]) {\n const payload = JSON.stringify({\n sentAt: new Date().toISOString(),\n sdk: this.sdkInfo,\n events,\n });\n\n // beacon fast-path (browser only)\n if (\n isBrowser() &&\n payload.length < 60_000 &&\n document.visibilityState === \"hidden\"\n ) {\n if (this.trySendBeacon(payload)) return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.maxRetries) {\n try {\n const res = await this.fetchImpl!(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n });\n\n // ❗ DO NOT retry client errors\n if (res.status >= 400 && res.status < 500) {\n if (res.status === 401 || res.status === 403) {\n console.error(\"Eventra: Invalid API key\");\n }\n return;\n }\n\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n\n return;\n } catch (err) {\n attempt++;\n\n if (attempt > this.maxRetries) {\n throw err;\n }\n\n const jitter = 0.5 + Math.random(); // 0.5–1.5\n const delay =\n this.retryBaseDelay *\n 2 ** (attempt - 1) *\n jitter;\n\n await sleep(delay);\n }\n }\n }\n\n /* ---------------- Runtime ---------------- */\n\n private detectRuntime(): string {\n if (typeof window !== \"undefined\") return \"browser\";\n\n const g = globalThis as Record<string, unknown>;\n\n if (g[\"Deno\"]) return \"deno\";\n if (g[\"Bun\"]) return \"bun\";\n\n const maybeProcess = g[\"process\"] as\n | { versions?: { node?: string } }\n | undefined;\n\n if (maybeProcess?.versions?.node) return \"node\";\n\n return \"unknown\";\n }\n\n /* ---------------- Timer ---------------- */\n\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n /* ---------------- Exit handling ---------------- */\n\n private setupExitHandlers() {\n // browser\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n // node\n const g = globalThis as Record<string, unknown>;\n\n const maybeProcess = g[\"process\"] as\n | { once?: (event: string, cb: () => void) => void }\n | undefined;\n\n if (typeof maybeProcess?.once === \"function\") {\n const shutdown = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n maybeProcess.once(\"SIGINT\", shutdown);\n maybeProcess.once(\"SIGTERM\", shutdown);\n maybeProcess.once(\"beforeExit\", shutdown);\n }\n }\n}\n\nexport * from \"./types\";\n"],"mappings":"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","names":[]}
|