@eventra_dev/eventra-sdk 1.1.0 → 1.1.2

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
@@ -8,11 +8,11 @@ Production-grade analytics SDK for tracking **feature usage, product behavior, a
8
8
 
9
9
  Eventra helps you:
10
10
 
11
- * Track feature adoption
12
- * Detect unused features
13
- * Understand user behavior
14
- * Monitor backend usage
15
- * Analyze product growth
11
+ - Track feature adoption
12
+ - Detect unused features
13
+ - Understand user behavior
14
+ - Monitor backend usage
15
+ - Analyze product growth
16
16
 
17
17
  ---
18
18
 
@@ -20,12 +20,12 @@ Eventra helps you:
20
20
 
21
21
  Eventra SDK is:
22
22
 
23
- * Lightweight (~minimal overhead)
24
- * Runtime-aware (Browser, Node, Edge, Serverless)
25
- * Resilient (batching + retry + circuit breaker)
26
- * Durable (browser persistence)
27
- * Smart (adaptive behavior per environment)
28
- * TypeScript-first
23
+ - Lightweight (~minimal overhead)
24
+ - Runtime-aware (Browser, Node, Edge, Serverless)
25
+ - Resilient (batching + retry + circuit breaker)
26
+ - Durable (browser persistence)
27
+ - Consistent (same delivery model across all environments)
28
+ - TypeScript-first
29
29
 
30
30
  ---
31
31
 
@@ -64,11 +64,11 @@ That's it.
64
64
 
65
65
  The SDK automatically handles:
66
66
 
67
- * batching (browser)
68
- * retries
69
- * queueing
70
- * flushing
71
- * runtime adaptation
67
+ - batching
68
+ - retries
69
+ - queueing
70
+ - flushing
71
+ - runtime adaptation
72
72
 
73
73
  ---
74
74
 
@@ -76,13 +76,13 @@ The SDK automatically handles:
76
76
 
77
77
  Eventra SDK adapts automatically:
78
78
 
79
- | Environment | Behavior |
80
- | ----------- | ------------------------------ |
81
- | Browser | batching + persistence + retry |
82
- | Node.js | instant send |
83
- | Serverless | sync send (no event loss) |
84
- | Edge | lightweight send |
85
- | Workers | instant send |
79
+ | Environment | Behavior |
80
+ | ----------- | -------------------------------------- |
81
+ | Browser | batching + persistence + retry |
82
+ | Node.js | batching + retry |
83
+ | Serverless | immediate flush + retry |
84
+ | Edge | lightweight batching |
85
+ | Workers | batching |
86
86
 
87
87
  No config needed.
88
88
 
@@ -167,14 +167,14 @@ tracker.track("error.occurred", {
167
167
 
168
168
  ## Where You Can Use It
169
169
 
170
- * Browser apps
171
- * React / Next.js
172
- * Node.js backends
173
- * NestJS services
174
- * Express APIs
175
- * Edge runtimes
176
- * Serverless (AWS / Vercel)
177
- * Bun / Deno
170
+ - Browser apps
171
+ - React / Next.js
172
+ - Node.js backends
173
+ - NestJS services
174
+ - Express APIs
175
+ - Edge runtimes
176
+ - Serverless (AWS / Vercel)
177
+ - Bun / Deno
178
178
 
179
179
  ---
180
180
 
@@ -190,10 +190,10 @@ const tracker = new Eventra({
190
190
  tracker.track("page.viewed");
191
191
  ```
192
192
 
193
- batching
194
- retry
195
- persistence (localStorage)
196
- flush on tab close
193
+ - batching
194
+ - retry
195
+ - persistence (localStorage)
196
+ - flush on tab close
197
197
 
198
198
  ---
199
199
 
@@ -207,8 +207,10 @@ const tracker = new Eventra({
207
207
  tracker.track("invoice.created");
208
208
  ```
209
209
 
210
- immediate send
211
- no buffering
210
+ - batching
211
+ - retry
212
+ - auto flush (interval)
213
+ - graceful shutdown support
212
214
 
213
215
  ---
214
216
 
@@ -220,14 +222,15 @@ export default async function handler(req, res) {
220
222
  apiKey: "...",
221
223
  });
222
224
 
223
- await tracker.track("function.called");
225
+ tracker.track("function.called");
224
226
 
225
227
  res.status(200).end();
226
228
  }
227
229
  ```
228
230
 
229
- no event loss
230
- sync delivery
231
+ - immediate flush
232
+ - retry
233
+ - optimized for short-lived environments
231
234
 
232
235
  ---
233
236
 
@@ -252,7 +255,7 @@ const tracker = new Eventra({
252
255
  | ------------- | ----------------------------- |
253
256
  | apiKey | Project API key |
254
257
  | endpoint | Event ingestion endpoint |
255
- | flushInterval | Batch interval (browser only) |
258
+ | flushInterval | Flush interval (ms) |
256
259
  | maxBatchSize | Events per batch |
257
260
  | maxQueueSize | Max buffer size |
258
261
  | maxRetries | Retry attempts |
@@ -273,7 +276,7 @@ await tracker.flush();
273
276
  tracker.destroy();
274
277
  ```
275
278
 
276
- Stops timers and clears internal state.
279
+ Stops timers, removes listeners, and clears internal state.
277
280
 
278
281
  ---
279
282
 
@@ -281,12 +284,12 @@ Stops timers and clears internal state.
281
284
 
282
285
  Eventra SDK includes:
283
286
 
284
- * Idempotency (no duplicates)
285
- * Retry with exponential backoff
286
- * Circuit breaker (prevents overload)
287
- * Queue persistence (browser)
288
- * Safe fallback for all runtimes
289
- * sendBeacon optimization (browser exit)
287
+ - Idempotency (stable event IDs during retries)
288
+ - Retry with exponential backoff
289
+ - Circuit breaker (prevents overload)
290
+ - Queue-based delivery (all runtimes)
291
+ - Queue persistence (browser)
292
+ - sendBeacon optimization (browser exit)
290
293
 
291
294
  ---
292
295
 
@@ -297,11 +300,12 @@ Eventra SDK includes:
297
300
  "sentAt": "2026-03-12T10:00:00Z",
298
301
  "sdk": {
299
302
  "name": "@eventra_dev/eventra-sdk",
300
- "version": "ultra",
303
+ "version": "1.0.0",
301
304
  "runtime": "browser"
302
305
  },
303
306
  "events": [
304
307
  {
308
+ "idempotencyKey": "uuid",
305
309
  "name": "user_signup",
306
310
  "userId": "user_123",
307
311
  "timestamp": "2026-03-12T10:00:00Z",
package/dist/index.cjs CHANGED
@@ -42,10 +42,6 @@ function uuid() {
42
42
  function sleep(ms) {
43
43
  return new Promise((r) => setTimeout(r, ms));
44
44
  }
45
- function getFetch() {
46
- if (typeof fetch !== "undefined") return fetch.bind(globalThis);
47
- throw new Error("fetch not available");
48
- }
49
45
  var Storage = class {
50
46
  constructor() {
51
47
  this.enabled = false;
@@ -75,14 +71,13 @@ var Storage = class {
75
71
  }
76
72
  };
77
73
  var Leader = class {
78
- constructor() {
74
+ constructor(enabled) {
79
75
  this.isLeader = true;
76
+ if (!enabled) return;
80
77
  if (typeof BroadcastChannel !== "undefined") {
81
78
  this.channel = new BroadcastChannel("eventra");
82
79
  this.channel.onmessage = (e) => {
83
- if (e.data === "leader") {
84
- this.isLeader = false;
85
- }
80
+ if (e.data === "leader") this.isLeader = false;
86
81
  };
87
82
  this.channel.postMessage("leader");
88
83
  }
@@ -90,45 +85,64 @@ var Leader = class {
90
85
  canSend() {
91
86
  return this.isLeader;
92
87
  }
88
+ destroy() {
89
+ this.channel?.close();
90
+ }
93
91
  };
94
92
  var Eventra = class {
95
93
  constructor(options) {
96
- this.fetch = getFetch();
97
94
  this.queue = [];
98
95
  this.inFlight = false;
99
96
  this.destroyed = false;
100
- this.maxBatch = 50;
101
- this.maxQueue = 1e4;
102
- this.flushInterval = 2e3;
103
- this.retries = 3;
104
- this.retryDelay = 300;
105
97
  this.storage = new Storage();
106
- this.leader = new Leader();
98
+ this.leader = null;
107
99
  this.failureCount = 0;
108
100
  this.circuitOpenUntil = 0;
101
+ this.exitHandlers = [];
109
102
  if (!options.apiKey) throw new Error("apiKey required");
103
+ if (!options.endpoint) throw new Error("endpoint required");
104
+ this.options = options;
110
105
  this.apiKey = options.apiKey;
111
- this.endpoint = options.endpoint ?? "";
112
- if (!this.endpoint) throw new Error("endpoint required");
106
+ this.endpoint = options.endpoint;
113
107
  this.runtime = detectRuntime();
114
- this.maxBatch = options.maxBatchSize ?? this.maxBatch;
115
- this.maxQueue = options.maxQueueSize ?? this.maxQueue;
116
- this.flushInterval = options.flushInterval ?? this.flushInterval;
108
+ this.fetch = options.fetchImpl ?? globalThis.fetch;
109
+ if (!this.fetch) {
110
+ throw new Error("fetch not available \u2014 provide fetchImpl");
111
+ }
112
+ this.maxBatch = options.maxBatchSize ?? 50;
113
+ this.maxQueue = options.maxQueueSize ?? 1e4;
114
+ this.flushInterval = options.flushInterval ?? 2e3;
115
+ this.retries = options.maxRetries ?? 3;
116
+ this.retryDelay = options.retryBaseDelayMs ?? 300;
117
117
  this.sdkInfo = {
118
118
  name: "@eventra_dev/eventra-sdk",
119
- version: "ultra",
119
+ version: "1.0.0",
120
120
  runtime: this.runtime
121
121
  };
122
122
  if (this.runtime === "browser") {
123
123
  const saved = this.storage.get("__eventra_q__");
124
- if (saved) this.queue = saved;
124
+ if (Array.isArray(saved)) this.queue = saved;
125
+ }
126
+ if (this.runtime === "browser" && options.multiTabMode === "leader") {
127
+ this.leader = new Leader(true);
128
+ }
129
+ if (!options.disableTimer) {
125
130
  this.startTimer();
131
+ }
132
+ if (this.runtime === "browser") {
126
133
  this.setupBrowserExit();
127
134
  }
135
+ if (options.autoFlushOnExit !== false && this.runtime !== "browser") {
136
+ this.setupProcessExit();
137
+ }
128
138
  }
129
- // PUBLIC
139
+ // ================= PUBLIC =================
130
140
  track(name, options) {
131
141
  if (this.destroyed) return;
142
+ if (this.queue.length >= this.maxQueue) {
143
+ this.options.onEventsDropped?.(1);
144
+ return;
145
+ }
132
146
  const event = {
133
147
  idempotencyKey: uuid(),
134
148
  name,
@@ -136,31 +150,35 @@ var Eventra = class {
136
150
  properties: options?.properties ?? {},
137
151
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
138
152
  };
139
- if (this.runtime !== "browser") {
140
- void this.send([event]);
141
- return;
142
- }
143
- if (this.queue.length >= this.maxQueue) return;
144
153
  this.queue.push(event);
145
- this.persist();
154
+ if (this.runtime === "browser") {
155
+ this.persist();
156
+ }
146
157
  if (this.queue.length >= this.maxBatch) {
147
158
  void this.flush();
148
159
  }
160
+ if (this.runtime === "serverless") {
161
+ void this.flush();
162
+ }
149
163
  }
150
164
  async flush() {
151
165
  if (this.inFlight || this.destroyed) return;
152
166
  if (!this.queue.length) return;
153
- if (!this.leader.canSend()) return;
167
+ if (this.leader && !this.leader.canSend()) return;
154
168
  if (Date.now() < this.circuitOpenUntil) return;
155
169
  this.inFlight = true;
156
170
  const batch = this.queue.splice(0, this.maxBatch);
157
- this.persist();
171
+ if (this.runtime === "browser") {
172
+ this.persist();
173
+ }
158
174
  try {
159
175
  await this.send(batch);
160
176
  this.failureCount = 0;
161
177
  } catch {
162
178
  this.queue = [...batch, ...this.queue];
163
- this.persist();
179
+ if (this.runtime === "browser") {
180
+ this.persist();
181
+ }
164
182
  this.failureCount++;
165
183
  if (this.failureCount >= 5) {
166
184
  this.circuitOpenUntil = Date.now() + 5e3;
@@ -172,8 +190,12 @@ var Eventra = class {
172
190
  destroy() {
173
191
  this.destroyed = true;
174
192
  if (this.timer) clearInterval(this.timer);
193
+ this.leader?.destroy();
194
+ for (const off of this.exitHandlers) {
195
+ off();
196
+ }
175
197
  }
176
- // SEND (CORE)
198
+ // ================= SEND =================
177
199
  async send(events) {
178
200
  const payload = JSON.stringify({
179
201
  sentAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -209,17 +231,43 @@ var Eventra = class {
209
231
  }
210
232
  }
211
233
  }
212
- // BROWSER
234
+ // ================= INTERNAL =================
213
235
  startTimer() {
214
236
  this.timer = setInterval(() => {
215
237
  void this.flush();
216
238
  }, this.flushInterval);
217
239
  }
218
240
  setupBrowserExit() {
219
- window.addEventListener("visibilitychange", () => {
241
+ const handler = () => {
220
242
  if (document.visibilityState === "hidden") {
221
243
  void this.flush();
222
244
  }
245
+ };
246
+ window.addEventListener("visibilitychange", handler);
247
+ this.exitHandlers.push(() => {
248
+ window.removeEventListener("visibilitychange", handler);
249
+ });
250
+ }
251
+ setupProcessExit() {
252
+ const g = globalThis;
253
+ const flushSafe = async () => {
254
+ try {
255
+ await this.flush();
256
+ } catch {
257
+ }
258
+ };
259
+ const beforeExit = () => {
260
+ void flushSafe();
261
+ };
262
+ const sigint = async () => {
263
+ await flushSafe();
264
+ g.exit();
265
+ };
266
+ g?.on?.("beforeExit", beforeExit);
267
+ g?.on?.("SIGINT", sigint);
268
+ this.exitHandlers.push(() => {
269
+ g?.off?.("beforeExit", beforeExit);
270
+ g?.off?.("SIGINT", sigint);
223
271
  });
224
272
  }
225
273
  persist() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export * from \"./client\";\nexport * from \"./types\";\n","import { TrackerOptions, TrackEvent, SdkInfo } from \"./types\";\n\n// RUNTIME DETECTION\ntype Runtime = \"browser\" | \"node\" | \"edge\" | \"serverless\" | \"unknown\";\n\nfunction detectRuntime(): Runtime {\n const g = globalThis as any;\n\n if (typeof window !== \"undefined\") return \"browser\";\n if (g.EdgeRuntime) return \"edge\";\n if (g.process?.env?.AWS_LAMBDA_FUNCTION_NAME) return \"serverless\";\n if (g.process?.versions?.node) return \"node\";\n\n return \"unknown\";\n}\n\n// HELPERS\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction getFetch(): typeof fetch {\n if (typeof fetch !== \"undefined\") return fetch.bind(globalThis);\n throw new Error(\"fetch not available\");\n}\n\n// SAFE STORAGE (browser only)\nclass Storage {\n private enabled = false;\n\n constructor() {\n try {\n if (typeof localStorage !== \"undefined\") {\n localStorage.setItem(\"__t\", \"1\");\n localStorage.removeItem(\"__t\");\n this.enabled = true;\n }\n } catch {}\n }\n\n get(key: string): any {\n if (!this.enabled) return null;\n try {\n return JSON.parse(localStorage.getItem(key) || \"null\");\n } catch {\n return null;\n }\n }\n\n set(key: string, value: any) {\n if (!this.enabled) return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {}\n }\n}\n\n// LEADER ELECTION (browser)\nclass Leader {\n private channel?: BroadcastChannel;\n private isLeader = true;\n\n constructor() {\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(\"eventra\");\n\n this.channel.onmessage = (e) => {\n if (e.data === \"leader\") {\n this.isLeader = false;\n }\n };\n\n this.channel.postMessage(\"leader\");\n }\n }\n\n canSend() {\n return this.isLeader;\n }\n}\n\n// SDK\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private runtime: Runtime;\n private fetch = getFetch();\n\n private queue: TrackEvent[] = [];\n private inFlight = false;\n private destroyed = false;\n\n private maxBatch = 50;\n private maxQueue = 10000;\n private flushInterval = 2000;\n private retries = 3;\n private retryDelay = 300;\n\n private timer?: any;\n\n private storage = new Storage();\n private leader = new Leader();\n\n private failureCount = 0;\n private circuitOpenUntil = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) throw new Error(\"apiKey required\");\n\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint ?? \"\";\n\n if (!this.endpoint) throw new Error(\"endpoint required\");\n\n this.runtime = detectRuntime();\n\n this.maxBatch = options.maxBatchSize ?? this.maxBatch;\n this.maxQueue = options.maxQueueSize ?? this.maxQueue;\n this.flushInterval = options.flushInterval ?? this.flushInterval;\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: \"ultra\",\n runtime: this.runtime,\n };\n\n if (this.runtime === \"browser\") {\n const saved = this.storage.get(\"__eventra_q__\");\n if (saved) this.queue = saved;\n\n this.startTimer();\n this.setupBrowserExit();\n }\n }\n\n // PUBLIC\n track(name: string, options?: any) {\n if (this.destroyed) return;\n\n const event: TrackEvent = {\n idempotencyKey: uuid(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n };\n\n // server environments → instant send\n if (this.runtime !== \"browser\") {\n void this.send([event]);\n return;\n }\n\n // browser → queue\n if (this.queue.length >= this.maxQueue) return;\n\n this.queue.push(event);\n this.persist();\n\n if (this.queue.length >= this.maxBatch) {\n void this.flush();\n }\n }\n\n async flush() {\n if (this.inFlight || this.destroyed) return;\n if (!this.queue.length) return;\n\n if (!this.leader.canSend()) return;\n\n if (Date.now() < this.circuitOpenUntil) return;\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.maxBatch);\n this.persist();\n\n try {\n await this.send(batch);\n\n this.failureCount = 0;\n } catch {\n this.queue = [...batch, ...this.queue];\n this.persist();\n\n this.failureCount++;\n\n if (this.failureCount >= 5) {\n this.circuitOpenUntil = Date.now() + 5000;\n }\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 // SEND (CORE)\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 // sendBeacon fast path\n if (\n this.runtime === \"browser\" &&\n typeof navigator !== \"undefined\" &&\n navigator.sendBeacon &&\n payload.length < 60000 &&\n document.visibilityState === \"hidden\"\n ) {\n navigator.sendBeacon(this.endpoint, payload);\n return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.retries) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const res = await this.fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n if (res.status >= 400 && res.status < 500) return;\n\n if (!res.ok) throw new Error();\n\n return;\n } catch {\n attempt++;\n\n if (attempt > this.retries) throw new Error();\n\n await sleep(this.retryDelay * 2 ** attempt);\n }\n }\n }\n\n // BROWSER\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n private setupBrowserExit() {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n private persist() {\n if (this.runtime !== \"browser\") return;\n this.storage.set(\"__eventra_q__\", this.queue);\n }\n}\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 { TrackerOptions, TrackEvent, SdkInfo } from \"./types\";\n\n// ================= RUNTIME =================\ntype Runtime = \"browser\" | \"node\" | \"edge\" | \"serverless\" | \"unknown\";\n\nfunction detectRuntime(): Runtime {\n const g = globalThis as any;\n\n if (typeof window !== \"undefined\") return \"browser\";\n if (g.EdgeRuntime) return \"edge\";\n if (g.process?.env?.AWS_LAMBDA_FUNCTION_NAME) return \"serverless\";\n if (g.process?.versions?.node) return \"node\";\n\n return \"unknown\";\n}\n\n// ================= HELPERS =================\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n// ================= STORAGE =================\nclass Storage {\n private enabled = false;\n\n constructor() {\n try {\n if (typeof localStorage !== \"undefined\") {\n localStorage.setItem(\"__t\", \"1\");\n localStorage.removeItem(\"__t\");\n this.enabled = true;\n }\n } catch {}\n }\n\n get(key: string): any {\n if (!this.enabled) return null;\n try {\n return JSON.parse(localStorage.getItem(key) || \"null\");\n } catch {\n return null;\n }\n }\n\n set(key: string, value: any) {\n if (!this.enabled) return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {}\n }\n}\n\n// ================= LEADER =================\nclass Leader {\n private channel?: BroadcastChannel;\n private isLeader = true;\n\n constructor(enabled: boolean) {\n if (!enabled) return;\n\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(\"eventra\");\n\n this.channel.onmessage = (e) => {\n if (e.data === \"leader\") this.isLeader = false;\n };\n\n this.channel.postMessage(\"leader\");\n }\n }\n\n canSend() {\n return this.isLeader;\n }\n\n destroy() {\n this.channel?.close();\n }\n}\n\n// ================= SDK =================\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private runtime: Runtime;\n private fetch: typeof fetch;\n\n private queue: TrackEvent[] = [];\n private inFlight = false;\n private destroyed = false;\n\n private maxBatch: number;\n private maxQueue: number;\n private flushInterval: number;\n private retries: number;\n private retryDelay: number;\n\n private timer?: any;\n\n private storage = new Storage();\n private leader: Leader | null = null;\n\n private failureCount = 0;\n private circuitOpenUntil = 0;\n\n private sdkInfo: SdkInfo;\n private options: TrackerOptions;\n\n private exitHandlers: Array<() => void> = [];\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) throw new Error(\"apiKey required\");\n if (!options.endpoint) throw new Error(\"endpoint required\");\n\n this.options = options;\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint;\n this.runtime = detectRuntime();\n\n this.fetch = options.fetchImpl ?? globalThis.fetch;\n if (!this.fetch) {\n throw new Error(\"fetch not available — provide fetchImpl\");\n }\n\n this.maxBatch = options.maxBatchSize ?? 50;\n this.maxQueue = options.maxQueueSize ?? 10000;\n this.flushInterval = options.flushInterval ?? 2000;\n this.retries = options.maxRetries ?? 3;\n this.retryDelay = options.retryBaseDelayMs ?? 300;\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: \"1.0.0\",\n runtime: this.runtime,\n };\n\n // restore browser queue\n if (this.runtime === \"browser\") {\n const saved = this.storage.get(\"__eventra_q__\");\n if (Array.isArray(saved)) this.queue = saved;\n }\n\n // leader (browser only)\n if (this.runtime === \"browser\" && options.multiTabMode === \"leader\") {\n this.leader = new Leader(true);\n }\n\n // timer\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n // browser exit\n if (this.runtime === \"browser\") {\n this.setupBrowserExit();\n }\n\n // node / serverless exit\n if (options.autoFlushOnExit !== false && this.runtime !== \"browser\") {\n this.setupProcessExit();\n }\n }\n\n // ================= PUBLIC =================\n track(name: string, options?: any) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueue) {\n this.options.onEventsDropped?.(1);\n return;\n }\n\n const event: TrackEvent = {\n idempotencyKey: uuid(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n };\n\n this.queue.push(event);\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n if (this.queue.length >= this.maxBatch) {\n void this.flush();\n }\n\n if (this.runtime === \"serverless\") {\n void this.flush();\n }\n }\n\n async flush() {\n if (this.inFlight || this.destroyed) return;\n if (!this.queue.length) return;\n\n if (this.leader && !this.leader.canSend()) return;\n if (Date.now() < this.circuitOpenUntil) return;\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.maxBatch);\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n try {\n await this.send(batch);\n this.failureCount = 0;\n } catch {\n // возвращаем батч назад (без dedupe — это задача backend)\n this.queue = [...batch, ...this.queue];\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n this.failureCount++;\n\n if (this.failureCount >= 5) {\n this.circuitOpenUntil = Date.now() + 5000;\n }\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n\n if (this.timer) clearInterval(this.timer);\n\n this.leader?.destroy();\n\n // remove process listeners\n for (const off of this.exitHandlers) {\n off();\n }\n }\n\n // ================= SEND =================\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 if (\n this.runtime === \"browser\" &&\n typeof navigator !== \"undefined\" &&\n navigator.sendBeacon &&\n payload.length < 60000 &&\n document.visibilityState === \"hidden\"\n ) {\n navigator.sendBeacon(this.endpoint, payload);\n return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.retries) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const res = await this.fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n if (res.status >= 400 && res.status < 500) return;\n if (!res.ok) throw new Error();\n\n return;\n } catch {\n attempt++;\n if (attempt > this.retries) throw new Error();\n await sleep(this.retryDelay * 2 ** attempt);\n }\n }\n }\n\n // ================= INTERNAL =================\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n private setupBrowserExit() {\n const handler = () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n };\n\n window.addEventListener(\"visibilitychange\", handler);\n\n this.exitHandlers.push(() => {\n window.removeEventListener(\"visibilitychange\", handler);\n });\n }\n\n private setupProcessExit() {\n\n const g = globalThis as any;\n\n const flushSafe = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n const beforeExit = () => {\n void flushSafe();\n };\n\n const sigint = async () => {\n await flushSafe();\n g.exit();\n };\n\n g?.on?.(\"beforeExit\", beforeExit);\n g?.on?.(\"SIGINT\", sigint);\n\n this.exitHandlers.push(() => {\n g?.off?.(\"beforeExit\", beforeExit);\n g?.off?.(\"SIGINT\", sigint);\n });\n }\n\n private persist() {\n if (this.runtime !== \"browser\") return;\n this.storage.set(\"__eventra_q__\", this.queue);\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -46,6 +46,8 @@ declare class Eventra {
46
46
  private failureCount;
47
47
  private circuitOpenUntil;
48
48
  private sdkInfo;
49
+ private options;
50
+ private exitHandlers;
49
51
  constructor(options: TrackerOptions);
50
52
  track(name: string, options?: any): void;
51
53
  flush(): Promise<void>;
@@ -53,6 +55,7 @@ declare class Eventra {
53
55
  private send;
54
56
  private startTimer;
55
57
  private setupBrowserExit;
58
+ private setupProcessExit;
56
59
  private persist;
57
60
  }
58
61
 
package/dist/index.d.ts CHANGED
@@ -46,6 +46,8 @@ declare class Eventra {
46
46
  private failureCount;
47
47
  private circuitOpenUntil;
48
48
  private sdkInfo;
49
+ private options;
50
+ private exitHandlers;
49
51
  constructor(options: TrackerOptions);
50
52
  track(name: string, options?: any): void;
51
53
  flush(): Promise<void>;
@@ -53,6 +55,7 @@ declare class Eventra {
53
55
  private send;
54
56
  private startTimer;
55
57
  private setupBrowserExit;
58
+ private setupProcessExit;
56
59
  private persist;
57
60
  }
58
61
 
package/dist/index.mjs CHANGED
@@ -16,10 +16,6 @@ function uuid() {
16
16
  function sleep(ms) {
17
17
  return new Promise((r) => setTimeout(r, ms));
18
18
  }
19
- function getFetch() {
20
- if (typeof fetch !== "undefined") return fetch.bind(globalThis);
21
- throw new Error("fetch not available");
22
- }
23
19
  var Storage = class {
24
20
  constructor() {
25
21
  this.enabled = false;
@@ -49,14 +45,13 @@ var Storage = class {
49
45
  }
50
46
  };
51
47
  var Leader = class {
52
- constructor() {
48
+ constructor(enabled) {
53
49
  this.isLeader = true;
50
+ if (!enabled) return;
54
51
  if (typeof BroadcastChannel !== "undefined") {
55
52
  this.channel = new BroadcastChannel("eventra");
56
53
  this.channel.onmessage = (e) => {
57
- if (e.data === "leader") {
58
- this.isLeader = false;
59
- }
54
+ if (e.data === "leader") this.isLeader = false;
60
55
  };
61
56
  this.channel.postMessage("leader");
62
57
  }
@@ -64,45 +59,64 @@ var Leader = class {
64
59
  canSend() {
65
60
  return this.isLeader;
66
61
  }
62
+ destroy() {
63
+ this.channel?.close();
64
+ }
67
65
  };
68
66
  var Eventra = class {
69
67
  constructor(options) {
70
- this.fetch = getFetch();
71
68
  this.queue = [];
72
69
  this.inFlight = false;
73
70
  this.destroyed = false;
74
- this.maxBatch = 50;
75
- this.maxQueue = 1e4;
76
- this.flushInterval = 2e3;
77
- this.retries = 3;
78
- this.retryDelay = 300;
79
71
  this.storage = new Storage();
80
- this.leader = new Leader();
72
+ this.leader = null;
81
73
  this.failureCount = 0;
82
74
  this.circuitOpenUntil = 0;
75
+ this.exitHandlers = [];
83
76
  if (!options.apiKey) throw new Error("apiKey required");
77
+ if (!options.endpoint) throw new Error("endpoint required");
78
+ this.options = options;
84
79
  this.apiKey = options.apiKey;
85
- this.endpoint = options.endpoint ?? "";
86
- if (!this.endpoint) throw new Error("endpoint required");
80
+ this.endpoint = options.endpoint;
87
81
  this.runtime = detectRuntime();
88
- this.maxBatch = options.maxBatchSize ?? this.maxBatch;
89
- this.maxQueue = options.maxQueueSize ?? this.maxQueue;
90
- this.flushInterval = options.flushInterval ?? this.flushInterval;
82
+ this.fetch = options.fetchImpl ?? globalThis.fetch;
83
+ if (!this.fetch) {
84
+ throw new Error("fetch not available \u2014 provide fetchImpl");
85
+ }
86
+ this.maxBatch = options.maxBatchSize ?? 50;
87
+ this.maxQueue = options.maxQueueSize ?? 1e4;
88
+ this.flushInterval = options.flushInterval ?? 2e3;
89
+ this.retries = options.maxRetries ?? 3;
90
+ this.retryDelay = options.retryBaseDelayMs ?? 300;
91
91
  this.sdkInfo = {
92
92
  name: "@eventra_dev/eventra-sdk",
93
- version: "ultra",
93
+ version: "1.0.0",
94
94
  runtime: this.runtime
95
95
  };
96
96
  if (this.runtime === "browser") {
97
97
  const saved = this.storage.get("__eventra_q__");
98
- if (saved) this.queue = saved;
98
+ if (Array.isArray(saved)) this.queue = saved;
99
+ }
100
+ if (this.runtime === "browser" && options.multiTabMode === "leader") {
101
+ this.leader = new Leader(true);
102
+ }
103
+ if (!options.disableTimer) {
99
104
  this.startTimer();
105
+ }
106
+ if (this.runtime === "browser") {
100
107
  this.setupBrowserExit();
101
108
  }
109
+ if (options.autoFlushOnExit !== false && this.runtime !== "browser") {
110
+ this.setupProcessExit();
111
+ }
102
112
  }
103
- // PUBLIC
113
+ // ================= PUBLIC =================
104
114
  track(name, options) {
105
115
  if (this.destroyed) return;
116
+ if (this.queue.length >= this.maxQueue) {
117
+ this.options.onEventsDropped?.(1);
118
+ return;
119
+ }
106
120
  const event = {
107
121
  idempotencyKey: uuid(),
108
122
  name,
@@ -110,31 +124,35 @@ var Eventra = class {
110
124
  properties: options?.properties ?? {},
111
125
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
112
126
  };
113
- if (this.runtime !== "browser") {
114
- void this.send([event]);
115
- return;
116
- }
117
- if (this.queue.length >= this.maxQueue) return;
118
127
  this.queue.push(event);
119
- this.persist();
128
+ if (this.runtime === "browser") {
129
+ this.persist();
130
+ }
120
131
  if (this.queue.length >= this.maxBatch) {
121
132
  void this.flush();
122
133
  }
134
+ if (this.runtime === "serverless") {
135
+ void this.flush();
136
+ }
123
137
  }
124
138
  async flush() {
125
139
  if (this.inFlight || this.destroyed) return;
126
140
  if (!this.queue.length) return;
127
- if (!this.leader.canSend()) return;
141
+ if (this.leader && !this.leader.canSend()) return;
128
142
  if (Date.now() < this.circuitOpenUntil) return;
129
143
  this.inFlight = true;
130
144
  const batch = this.queue.splice(0, this.maxBatch);
131
- this.persist();
145
+ if (this.runtime === "browser") {
146
+ this.persist();
147
+ }
132
148
  try {
133
149
  await this.send(batch);
134
150
  this.failureCount = 0;
135
151
  } catch {
136
152
  this.queue = [...batch, ...this.queue];
137
- this.persist();
153
+ if (this.runtime === "browser") {
154
+ this.persist();
155
+ }
138
156
  this.failureCount++;
139
157
  if (this.failureCount >= 5) {
140
158
  this.circuitOpenUntil = Date.now() + 5e3;
@@ -146,8 +164,12 @@ var Eventra = class {
146
164
  destroy() {
147
165
  this.destroyed = true;
148
166
  if (this.timer) clearInterval(this.timer);
167
+ this.leader?.destroy();
168
+ for (const off of this.exitHandlers) {
169
+ off();
170
+ }
149
171
  }
150
- // SEND (CORE)
172
+ // ================= SEND =================
151
173
  async send(events) {
152
174
  const payload = JSON.stringify({
153
175
  sentAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -183,17 +205,43 @@ var Eventra = class {
183
205
  }
184
206
  }
185
207
  }
186
- // BROWSER
208
+ // ================= INTERNAL =================
187
209
  startTimer() {
188
210
  this.timer = setInterval(() => {
189
211
  void this.flush();
190
212
  }, this.flushInterval);
191
213
  }
192
214
  setupBrowserExit() {
193
- window.addEventListener("visibilitychange", () => {
215
+ const handler = () => {
194
216
  if (document.visibilityState === "hidden") {
195
217
  void this.flush();
196
218
  }
219
+ };
220
+ window.addEventListener("visibilitychange", handler);
221
+ this.exitHandlers.push(() => {
222
+ window.removeEventListener("visibilitychange", handler);
223
+ });
224
+ }
225
+ setupProcessExit() {
226
+ const g = globalThis;
227
+ const flushSafe = async () => {
228
+ try {
229
+ await this.flush();
230
+ } catch {
231
+ }
232
+ };
233
+ const beforeExit = () => {
234
+ void flushSafe();
235
+ };
236
+ const sigint = async () => {
237
+ await flushSafe();
238
+ g.exit();
239
+ };
240
+ g?.on?.("beforeExit", beforeExit);
241
+ g?.on?.("SIGINT", sigint);
242
+ this.exitHandlers.push(() => {
243
+ g?.off?.("beforeExit", beforeExit);
244
+ g?.off?.("SIGINT", sigint);
197
245
  });
198
246
  }
199
247
  persist() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { TrackerOptions, TrackEvent, SdkInfo } from \"./types\";\n\n// RUNTIME DETECTION\ntype Runtime = \"browser\" | \"node\" | \"edge\" | \"serverless\" | \"unknown\";\n\nfunction detectRuntime(): Runtime {\n const g = globalThis as any;\n\n if (typeof window !== \"undefined\") return \"browser\";\n if (g.EdgeRuntime) return \"edge\";\n if (g.process?.env?.AWS_LAMBDA_FUNCTION_NAME) return \"serverless\";\n if (g.process?.versions?.node) return \"node\";\n\n return \"unknown\";\n}\n\n// HELPERS\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n\nfunction getFetch(): typeof fetch {\n if (typeof fetch !== \"undefined\") return fetch.bind(globalThis);\n throw new Error(\"fetch not available\");\n}\n\n// SAFE STORAGE (browser only)\nclass Storage {\n private enabled = false;\n\n constructor() {\n try {\n if (typeof localStorage !== \"undefined\") {\n localStorage.setItem(\"__t\", \"1\");\n localStorage.removeItem(\"__t\");\n this.enabled = true;\n }\n } catch {}\n }\n\n get(key: string): any {\n if (!this.enabled) return null;\n try {\n return JSON.parse(localStorage.getItem(key) || \"null\");\n } catch {\n return null;\n }\n }\n\n set(key: string, value: any) {\n if (!this.enabled) return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {}\n }\n}\n\n// LEADER ELECTION (browser)\nclass Leader {\n private channel?: BroadcastChannel;\n private isLeader = true;\n\n constructor() {\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(\"eventra\");\n\n this.channel.onmessage = (e) => {\n if (e.data === \"leader\") {\n this.isLeader = false;\n }\n };\n\n this.channel.postMessage(\"leader\");\n }\n }\n\n canSend() {\n return this.isLeader;\n }\n}\n\n// SDK\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private runtime: Runtime;\n private fetch = getFetch();\n\n private queue: TrackEvent[] = [];\n private inFlight = false;\n private destroyed = false;\n\n private maxBatch = 50;\n private maxQueue = 10000;\n private flushInterval = 2000;\n private retries = 3;\n private retryDelay = 300;\n\n private timer?: any;\n\n private storage = new Storage();\n private leader = new Leader();\n\n private failureCount = 0;\n private circuitOpenUntil = 0;\n\n private sdkInfo: SdkInfo;\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) throw new Error(\"apiKey required\");\n\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint ?? \"\";\n\n if (!this.endpoint) throw new Error(\"endpoint required\");\n\n this.runtime = detectRuntime();\n\n this.maxBatch = options.maxBatchSize ?? this.maxBatch;\n this.maxQueue = options.maxQueueSize ?? this.maxQueue;\n this.flushInterval = options.flushInterval ?? this.flushInterval;\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: \"ultra\",\n runtime: this.runtime,\n };\n\n if (this.runtime === \"browser\") {\n const saved = this.storage.get(\"__eventra_q__\");\n if (saved) this.queue = saved;\n\n this.startTimer();\n this.setupBrowserExit();\n }\n }\n\n // PUBLIC\n track(name: string, options?: any) {\n if (this.destroyed) return;\n\n const event: TrackEvent = {\n idempotencyKey: uuid(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n };\n\n // server environments → instant send\n if (this.runtime !== \"browser\") {\n void this.send([event]);\n return;\n }\n\n // browser → queue\n if (this.queue.length >= this.maxQueue) return;\n\n this.queue.push(event);\n this.persist();\n\n if (this.queue.length >= this.maxBatch) {\n void this.flush();\n }\n }\n\n async flush() {\n if (this.inFlight || this.destroyed) return;\n if (!this.queue.length) return;\n\n if (!this.leader.canSend()) return;\n\n if (Date.now() < this.circuitOpenUntil) return;\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.maxBatch);\n this.persist();\n\n try {\n await this.send(batch);\n\n this.failureCount = 0;\n } catch {\n this.queue = [...batch, ...this.queue];\n this.persist();\n\n this.failureCount++;\n\n if (this.failureCount >= 5) {\n this.circuitOpenUntil = Date.now() + 5000;\n }\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 // SEND (CORE)\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 // sendBeacon fast path\n if (\n this.runtime === \"browser\" &&\n typeof navigator !== \"undefined\" &&\n navigator.sendBeacon &&\n payload.length < 60000 &&\n document.visibilityState === \"hidden\"\n ) {\n navigator.sendBeacon(this.endpoint, payload);\n return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.retries) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const res = await this.fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n if (res.status >= 400 && res.status < 500) return;\n\n if (!res.ok) throw new Error();\n\n return;\n } catch {\n attempt++;\n\n if (attempt > this.retries) throw new Error();\n\n await sleep(this.retryDelay * 2 ** attempt);\n }\n }\n }\n\n // BROWSER\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n private setupBrowserExit() {\n window.addEventListener(\"visibilitychange\", () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n });\n }\n\n private persist() {\n if (this.runtime !== \"browser\") return;\n this.storage.set(\"__eventra_q__\", this.queue);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts"],"sourcesContent":["import { TrackerOptions, TrackEvent, SdkInfo } from \"./types\";\n\n// ================= RUNTIME =================\ntype Runtime = \"browser\" | \"node\" | \"edge\" | \"serverless\" | \"unknown\";\n\nfunction detectRuntime(): Runtime {\n const g = globalThis as any;\n\n if (typeof window !== \"undefined\") return \"browser\";\n if (g.EdgeRuntime) return \"edge\";\n if (g.process?.env?.AWS_LAMBDA_FUNCTION_NAME) return \"serverless\";\n if (g.process?.versions?.node) return \"node\";\n\n return \"unknown\";\n}\n\n// ================= HELPERS =================\nfunction uuid(): string {\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction sleep(ms: number) {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n// ================= STORAGE =================\nclass Storage {\n private enabled = false;\n\n constructor() {\n try {\n if (typeof localStorage !== \"undefined\") {\n localStorage.setItem(\"__t\", \"1\");\n localStorage.removeItem(\"__t\");\n this.enabled = true;\n }\n } catch {}\n }\n\n get(key: string): any {\n if (!this.enabled) return null;\n try {\n return JSON.parse(localStorage.getItem(key) || \"null\");\n } catch {\n return null;\n }\n }\n\n set(key: string, value: any) {\n if (!this.enabled) return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {}\n }\n}\n\n// ================= LEADER =================\nclass Leader {\n private channel?: BroadcastChannel;\n private isLeader = true;\n\n constructor(enabled: boolean) {\n if (!enabled) return;\n\n if (typeof BroadcastChannel !== \"undefined\") {\n this.channel = new BroadcastChannel(\"eventra\");\n\n this.channel.onmessage = (e) => {\n if (e.data === \"leader\") this.isLeader = false;\n };\n\n this.channel.postMessage(\"leader\");\n }\n }\n\n canSend() {\n return this.isLeader;\n }\n\n destroy() {\n this.channel?.close();\n }\n}\n\n// ================= SDK =================\nexport class Eventra {\n private apiKey: string;\n private endpoint: string;\n private runtime: Runtime;\n private fetch: typeof fetch;\n\n private queue: TrackEvent[] = [];\n private inFlight = false;\n private destroyed = false;\n\n private maxBatch: number;\n private maxQueue: number;\n private flushInterval: number;\n private retries: number;\n private retryDelay: number;\n\n private timer?: any;\n\n private storage = new Storage();\n private leader: Leader | null = null;\n\n private failureCount = 0;\n private circuitOpenUntil = 0;\n\n private sdkInfo: SdkInfo;\n private options: TrackerOptions;\n\n private exitHandlers: Array<() => void> = [];\n\n constructor(options: TrackerOptions) {\n if (!options.apiKey) throw new Error(\"apiKey required\");\n if (!options.endpoint) throw new Error(\"endpoint required\");\n\n this.options = options;\n this.apiKey = options.apiKey;\n this.endpoint = options.endpoint;\n this.runtime = detectRuntime();\n\n this.fetch = options.fetchImpl ?? globalThis.fetch;\n if (!this.fetch) {\n throw new Error(\"fetch not available — provide fetchImpl\");\n }\n\n this.maxBatch = options.maxBatchSize ?? 50;\n this.maxQueue = options.maxQueueSize ?? 10000;\n this.flushInterval = options.flushInterval ?? 2000;\n this.retries = options.maxRetries ?? 3;\n this.retryDelay = options.retryBaseDelayMs ?? 300;\n\n this.sdkInfo = {\n name: \"@eventra_dev/eventra-sdk\",\n version: \"1.0.0\",\n runtime: this.runtime,\n };\n\n // restore browser queue\n if (this.runtime === \"browser\") {\n const saved = this.storage.get(\"__eventra_q__\");\n if (Array.isArray(saved)) this.queue = saved;\n }\n\n // leader (browser only)\n if (this.runtime === \"browser\" && options.multiTabMode === \"leader\") {\n this.leader = new Leader(true);\n }\n\n // timer\n if (!options.disableTimer) {\n this.startTimer();\n }\n\n // browser exit\n if (this.runtime === \"browser\") {\n this.setupBrowserExit();\n }\n\n // node / serverless exit\n if (options.autoFlushOnExit !== false && this.runtime !== \"browser\") {\n this.setupProcessExit();\n }\n }\n\n // ================= PUBLIC =================\n track(name: string, options?: any) {\n if (this.destroyed) return;\n\n if (this.queue.length >= this.maxQueue) {\n this.options.onEventsDropped?.(1);\n return;\n }\n\n const event: TrackEvent = {\n idempotencyKey: uuid(),\n name,\n userId: options?.userId,\n properties: options?.properties ?? {},\n timestamp: new Date().toISOString(),\n };\n\n this.queue.push(event);\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n if (this.queue.length >= this.maxBatch) {\n void this.flush();\n }\n\n if (this.runtime === \"serverless\") {\n void this.flush();\n }\n }\n\n async flush() {\n if (this.inFlight || this.destroyed) return;\n if (!this.queue.length) return;\n\n if (this.leader && !this.leader.canSend()) return;\n if (Date.now() < this.circuitOpenUntil) return;\n\n this.inFlight = true;\n\n const batch = this.queue.splice(0, this.maxBatch);\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n try {\n await this.send(batch);\n this.failureCount = 0;\n } catch {\n // возвращаем батч назад (без dedupe — это задача backend)\n this.queue = [...batch, ...this.queue];\n\n if (this.runtime === \"browser\") {\n this.persist();\n }\n\n this.failureCount++;\n\n if (this.failureCount >= 5) {\n this.circuitOpenUntil = Date.now() + 5000;\n }\n } finally {\n this.inFlight = false;\n }\n }\n\n destroy() {\n this.destroyed = true;\n\n if (this.timer) clearInterval(this.timer);\n\n this.leader?.destroy();\n\n // remove process listeners\n for (const off of this.exitHandlers) {\n off();\n }\n }\n\n // ================= SEND =================\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 if (\n this.runtime === \"browser\" &&\n typeof navigator !== \"undefined\" &&\n navigator.sendBeacon &&\n payload.length < 60000 &&\n document.visibilityState === \"hidden\"\n ) {\n navigator.sendBeacon(this.endpoint, payload);\n return;\n }\n\n let attempt = 0;\n\n while (attempt <= this.retries) {\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n\n const res = await this.fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": this.apiKey,\n },\n body: payload,\n signal: controller.signal,\n });\n\n clearTimeout(timeout);\n\n if (res.status >= 400 && res.status < 500) return;\n if (!res.ok) throw new Error();\n\n return;\n } catch {\n attempt++;\n if (attempt > this.retries) throw new Error();\n await sleep(this.retryDelay * 2 ** attempt);\n }\n }\n }\n\n // ================= INTERNAL =================\n private startTimer() {\n this.timer = setInterval(() => {\n void this.flush();\n }, this.flushInterval);\n }\n\n private setupBrowserExit() {\n const handler = () => {\n if (document.visibilityState === \"hidden\") {\n void this.flush();\n }\n };\n\n window.addEventListener(\"visibilitychange\", handler);\n\n this.exitHandlers.push(() => {\n window.removeEventListener(\"visibilitychange\", handler);\n });\n }\n\n private setupProcessExit() {\n\n const g = globalThis as any;\n\n const flushSafe = async () => {\n try {\n await this.flush();\n } catch {}\n };\n\n const beforeExit = () => {\n void flushSafe();\n };\n\n const sigint = async () => {\n await flushSafe();\n g.exit();\n };\n\n g?.on?.(\"beforeExit\", beforeExit);\n g?.on?.(\"SIGINT\", sigint);\n\n this.exitHandlers.push(() => {\n g?.off?.(\"beforeExit\", beforeExit);\n g?.off?.(\"SIGINT\", sigint);\n });\n }\n\n private persist() {\n if (this.runtime !== \"browser\") return;\n this.storage.set(\"__eventra_q__\", this.queue);\n }\n}\n"],"mappings":";AAKA,SAAS,gBAAyB;AAChC,QAAM,IAAI;AAEV,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,EAAE,YAAa,QAAO;AAC1B,MAAI,EAAE,SAAS,KAAK,yBAA0B,QAAO;AACrD,MAAI,EAAE,SAAS,UAAU,KAAM,QAAO;AAEtC,SAAO;AACT;AAGA,SAAS,OAAe;AACtB,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAC7D;AAEA,SAAS,MAAM,IAAY;AACzB,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;AAGA,IAAM,UAAN,MAAc;AAAA,EAGZ,cAAc;AAFd,SAAQ,UAAU;AAGhB,QAAI;AACF,UAAI,OAAO,iBAAiB,aAAa;AACvC,qBAAa,QAAQ,OAAO,GAAG;AAC/B,qBAAa,WAAW,KAAK;AAC7B,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,QAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEA,IAAI,KAAkB;AACpB,QAAI,CAAC,KAAK,QAAS,QAAO;AAC1B,QAAI;AACF,aAAO,KAAK,MAAM,aAAa,QAAQ,GAAG,KAAK,MAAM;AAAA,IACvD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,IAAI,KAAa,OAAY;AAC3B,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI;AACF,mBAAa,QAAQ,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,IACjD,QAAQ;AAAA,IAAC;AAAA,EACX;AACF;AAGA,IAAM,SAAN,MAAa;AAAA,EAIX,YAAY,SAAkB;AAF9B,SAAQ,WAAW;AAGjB,QAAI,CAAC,QAAS;AAEd,QAAI,OAAO,qBAAqB,aAAa;AAC3C,WAAK,UAAU,IAAI,iBAAiB,SAAS;AAE7C,WAAK,QAAQ,YAAY,CAAC,MAAM;AAC9B,YAAI,EAAE,SAAS,SAAU,MAAK,WAAW;AAAA,MAC3C;AAEA,WAAK,QAAQ,YAAY,QAAQ;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,UAAU;AACR,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU;AACR,SAAK,SAAS,MAAM;AAAA,EACtB;AACF;AAGO,IAAM,UAAN,MAAc;AAAA,EA6BnB,YAAY,SAAyB;AAvBrC,SAAQ,QAAsB,CAAC;AAC/B,SAAQ,WAAW;AACnB,SAAQ,YAAY;AAUpB,SAAQ,UAAU,IAAI,QAAQ;AAC9B,SAAQ,SAAwB;AAEhC,SAAQ,eAAe;AACvB,SAAQ,mBAAmB;AAK3B,SAAQ,eAAkC,CAAC;AAGzC,QAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,MAAM,iBAAiB;AACtD,QAAI,CAAC,QAAQ,SAAU,OAAM,IAAI,MAAM,mBAAmB;AAE1D,SAAK,UAAU;AACf,SAAK,SAAS,QAAQ;AACtB,SAAK,WAAW,QAAQ;AACxB,SAAK,UAAU,cAAc;AAE7B,SAAK,QAAQ,QAAQ,aAAa,WAAW;AAC7C,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,8CAAyC;AAAA,IAC3D;AAEA,SAAK,WAAW,QAAQ,gBAAgB;AACxC,SAAK,WAAW,QAAQ,gBAAgB;AACxC,SAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,SAAK,UAAU,QAAQ,cAAc;AACrC,SAAK,aAAa,QAAQ,oBAAoB;AAE9C,SAAK,UAAU;AAAA,MACb,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS,KAAK;AAAA,IAChB;AAGA,QAAI,KAAK,YAAY,WAAW;AAC9B,YAAM,QAAQ,KAAK,QAAQ,IAAI,eAAe;AAC9C,UAAI,MAAM,QAAQ,KAAK,EAAG,MAAK,QAAQ;AAAA,IACzC;AAGA,QAAI,KAAK,YAAY,aAAa,QAAQ,iBAAiB,UAAU;AACnE,WAAK,SAAS,IAAI,OAAO,IAAI;AAAA,IAC/B;AAGA,QAAI,CAAC,QAAQ,cAAc;AACzB,WAAK,WAAW;AAAA,IAClB;AAGA,QAAI,KAAK,YAAY,WAAW;AAC9B,WAAK,iBAAiB;AAAA,IACxB;AAGA,QAAI,QAAQ,oBAAoB,SAAS,KAAK,YAAY,WAAW;AACnE,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,MAAc,SAAe;AACjC,QAAI,KAAK,UAAW;AAEpB,QAAI,KAAK,MAAM,UAAU,KAAK,UAAU;AACtC,WAAK,QAAQ,kBAAkB,CAAC;AAChC;AAAA,IACF;AAEA,UAAM,QAAoB;AAAA,MACxB,gBAAgB,KAAK;AAAA,MACrB;AAAA,MACA,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS,cAAc,CAAC;AAAA,MACpC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAEA,SAAK,MAAM,KAAK,KAAK;AAErB,QAAI,KAAK,YAAY,WAAW;AAC9B,WAAK,QAAQ;AAAA,IACf;AAEA,QAAI,KAAK,MAAM,UAAU,KAAK,UAAU;AACtC,WAAK,KAAK,MAAM;AAAA,IAClB;AAEA,QAAI,KAAK,YAAY,cAAc;AACjC,WAAK,KAAK,MAAM;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ;AACZ,QAAI,KAAK,YAAY,KAAK,UAAW;AACrC,QAAI,CAAC,KAAK,MAAM,OAAQ;AAExB,QAAI,KAAK,UAAU,CAAC,KAAK,OAAO,QAAQ,EAAG;AAC3C,QAAI,KAAK,IAAI,IAAI,KAAK,iBAAkB;AAExC,SAAK,WAAW;AAEhB,UAAM,QAAQ,KAAK,MAAM,OAAO,GAAG,KAAK,QAAQ;AAEhD,QAAI,KAAK,YAAY,WAAW;AAC9B,WAAK,QAAQ;AAAA,IACf;AAEA,QAAI;AACF,YAAM,KAAK,KAAK,KAAK;AACrB,WAAK,eAAe;AAAA,IACtB,QAAQ;AAEN,WAAK,QAAQ,CAAC,GAAG,OAAO,GAAG,KAAK,KAAK;AAErC,UAAI,KAAK,YAAY,WAAW;AAC9B,aAAK,QAAQ;AAAA,MACf;AAEA,WAAK;AAEL,UAAI,KAAK,gBAAgB,GAAG;AAC1B,aAAK,mBAAmB,KAAK,IAAI,IAAI;AAAA,MACvC;AAAA,IACF,UAAE;AACA,WAAK,WAAW;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,UAAU;AACR,SAAK,YAAY;AAEjB,QAAI,KAAK,MAAO,eAAc,KAAK,KAAK;AAExC,SAAK,QAAQ,QAAQ;AAGrB,eAAW,OAAO,KAAK,cAAc;AACnC,UAAI;AAAA,IACN;AAAA,EACF;AAAA;AAAA,EAGA,MAAc,KAAK,QAAsB;AACvC,UAAM,UAAU,KAAK,UAAU;AAAA,MAC7B,SAAQ,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC/B,KAAK,KAAK;AAAA,MACV;AAAA,IACF,CAAC;AAED,QACE,KAAK,YAAY,aACjB,OAAO,cAAc,eACrB,UAAU,cACV,QAAQ,SAAS,OACjB,SAAS,oBAAoB,UAC7B;AACA,gBAAU,WAAW,KAAK,UAAU,OAAO;AAC3C;AAAA,IACF;AAEA,QAAI,UAAU;AAEd,WAAO,WAAW,KAAK,SAAS;AAC9B,UAAI;AACF,cAAM,aAAa,IAAI,gBAAgB;AACvC,cAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,GAAI;AAEzD,cAAM,MAAM,MAAM,KAAK,MAAM,KAAK,UAAU;AAAA,UAC1C,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,gBAAgB;AAAA,YAChB,aAAa,KAAK;AAAA,UACpB;AAAA,UACA,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,qBAAa,OAAO;AAEpB,YAAI,IAAI,UAAU,OAAO,IAAI,SAAS,IAAK;AAC3C,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM;AAE7B;AAAA,MACF,QAAQ;AACN;AACA,YAAI,UAAU,KAAK,QAAS,OAAM,IAAI,MAAM;AAC5C,cAAM,MAAM,KAAK,aAAa,KAAK,OAAO;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,aAAa;AACnB,SAAK,QAAQ,YAAY,MAAM;AAC7B,WAAK,KAAK,MAAM;AAAA,IAClB,GAAG,KAAK,aAAa;AAAA,EACvB;AAAA,EAEQ,mBAAmB;AACzB,UAAM,UAAU,MAAM;AACpB,UAAI,SAAS,oBAAoB,UAAU;AACzC,aAAK,KAAK,MAAM;AAAA,MAClB;AAAA,IACF;AAEA,WAAO,iBAAiB,oBAAoB,OAAO;AAEnD,SAAK,aAAa,KAAK,MAAM;AAC3B,aAAO,oBAAoB,oBAAoB,OAAO;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EAEQ,mBAAmB;AAEzB,UAAM,IAAI;AAEV,UAAM,YAAY,YAAY;AAC5B,UAAI;AACF,cAAM,KAAK,MAAM;AAAA,MACnB,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,UAAM,aAAa,MAAM;AACvB,WAAK,UAAU;AAAA,IACjB;AAEA,UAAM,SAAS,YAAY;AACzB,YAAM,UAAU;AAChB,QAAE,KAAK;AAAA,IACT;AAEA,OAAG,KAAK,cAAc,UAAU;AAChC,OAAG,KAAK,UAAU,MAAM;AAExB,SAAK,aAAa,KAAK,MAAM;AAC3B,SAAG,MAAM,cAAc,UAAU;AACjC,SAAG,MAAM,UAAU,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EAEQ,UAAU;AAChB,QAAI,KAAK,YAAY,UAAW;AAChC,SAAK,QAAQ,IAAI,iBAAiB,KAAK,KAAK;AAAA,EAC9C;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"