@eventra_dev/eventra-sdk 1.0.7 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -246
- package/dist/index.cjs +131 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -17
- package/dist/index.d.ts +15 -17
- package/dist/index.mjs +131 -172
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,131 +1,144 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
retryBaseDelay: 300
|
|
10
|
-
};
|
|
11
|
-
function getGlobalFetch() {
|
|
12
|
-
if (typeof fetch !== "undefined") {
|
|
13
|
-
return fetch.bind(globalThis);
|
|
14
|
-
}
|
|
15
|
-
return void 0;
|
|
2
|
+
function detectRuntime() {
|
|
3
|
+
const g = globalThis;
|
|
4
|
+
if (typeof window !== "undefined") return "browser";
|
|
5
|
+
if (g.EdgeRuntime) return "edge";
|
|
6
|
+
if (g.process?.env?.AWS_LAMBDA_FUNCTION_NAME) return "serverless";
|
|
7
|
+
if (g.process?.versions?.node) return "node";
|
|
8
|
+
return "unknown";
|
|
16
9
|
}
|
|
17
|
-
function
|
|
18
|
-
if (typeof crypto !== "undefined" &&
|
|
10
|
+
function uuid() {
|
|
11
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
19
12
|
return crypto.randomUUID();
|
|
20
13
|
}
|
|
21
|
-
|
|
22
|
-
const ts = Date.now().toString(16);
|
|
23
|
-
return `${ts}-${rnd}`;
|
|
24
|
-
}
|
|
25
|
-
function isBrowser() {
|
|
26
|
-
return typeof window !== "undefined";
|
|
14
|
+
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
27
15
|
}
|
|
28
16
|
function sleep(ms) {
|
|
29
17
|
return new Promise((r) => setTimeout(r, ms));
|
|
30
18
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
localStorage
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
19
|
+
function getFetch() {
|
|
20
|
+
if (typeof fetch !== "undefined") return fetch.bind(globalThis);
|
|
21
|
+
throw new Error("fetch not available");
|
|
22
|
+
}
|
|
23
|
+
var Storage = class {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.enabled = false;
|
|
26
|
+
try {
|
|
27
|
+
if (typeof localStorage !== "undefined") {
|
|
28
|
+
localStorage.setItem("__t", "1");
|
|
29
|
+
localStorage.removeItem("__t");
|
|
30
|
+
this.enabled = true;
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
44
33
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
34
|
+
}
|
|
35
|
+
get(key) {
|
|
36
|
+
if (!this.enabled) return null;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(localStorage.getItem(key) || "null");
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
52
41
|
}
|
|
53
|
-
return false;
|
|
54
|
-
} catch {
|
|
55
|
-
return true;
|
|
56
42
|
}
|
|
57
|
-
|
|
43
|
+
set(key, value) {
|
|
44
|
+
if (!this.enabled) return;
|
|
45
|
+
try {
|
|
46
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var Leader = class {
|
|
52
|
+
constructor() {
|
|
53
|
+
this.isLeader = true;
|
|
54
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
55
|
+
this.channel = new BroadcastChannel("eventra");
|
|
56
|
+
this.channel.onmessage = (e) => {
|
|
57
|
+
if (e.data === "leader") {
|
|
58
|
+
this.isLeader = false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
this.channel.postMessage("leader");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
canSend() {
|
|
65
|
+
return this.isLeader;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
58
68
|
var Eventra = class {
|
|
59
69
|
constructor(options) {
|
|
70
|
+
this.fetch = getFetch();
|
|
60
71
|
this.queue = [];
|
|
61
72
|
this.inFlight = false;
|
|
62
73
|
this.destroyed = false;
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
74
|
+
this.maxBatch = 50;
|
|
75
|
+
this.maxQueue = 1e4;
|
|
76
|
+
this.flushInterval = 2e3;
|
|
77
|
+
this.retries = 3;
|
|
78
|
+
this.retryDelay = 300;
|
|
79
|
+
this.storage = new Storage();
|
|
80
|
+
this.leader = new Leader();
|
|
81
|
+
this.failureCount = 0;
|
|
82
|
+
this.circuitOpenUntil = 0;
|
|
83
|
+
if (!options.apiKey) throw new Error("apiKey required");
|
|
67
84
|
this.apiKey = options.apiKey;
|
|
68
|
-
this.endpoint = options.endpoint ??
|
|
69
|
-
if (!this.endpoint)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
74
|
-
this.maxQueueSize = options.maxQueueSize ?? DEFAULTS.maxQueueSize;
|
|
75
|
-
this.maxRetries = options.maxRetries ?? DEFAULTS.maxRetries;
|
|
76
|
-
this.retryBaseDelay = options.retryBaseDelayMs ?? DEFAULTS.retryBaseDelay;
|
|
77
|
-
this.multiTabMode = options.multiTabMode ?? "independent";
|
|
78
|
-
this.fetchImpl = options.fetchImpl ?? getGlobalFetch();
|
|
79
|
-
if (!this.fetchImpl) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
"Eventra: fetch is not available. Provide fetchImpl."
|
|
82
|
-
);
|
|
83
|
-
}
|
|
85
|
+
this.endpoint = options.endpoint ?? "";
|
|
86
|
+
if (!this.endpoint) throw new Error("endpoint required");
|
|
87
|
+
this.runtime = detectRuntime();
|
|
88
|
+
this.maxBatch = options.maxBatchSize ?? this.maxBatch;
|
|
89
|
+
this.maxQueue = options.maxQueueSize ?? this.maxQueue;
|
|
90
|
+
this.flushInterval = options.flushInterval ?? this.flushInterval;
|
|
84
91
|
this.sdkInfo = {
|
|
85
92
|
name: "@eventra_dev/eventra-sdk",
|
|
86
|
-
version:
|
|
87
|
-
runtime: this.
|
|
93
|
+
version: "ultra",
|
|
94
|
+
runtime: this.runtime
|
|
88
95
|
};
|
|
89
|
-
if (
|
|
96
|
+
if (this.runtime === "browser") {
|
|
97
|
+
const saved = this.storage.get("__eventra_q__");
|
|
98
|
+
if (saved) this.queue = saved;
|
|
90
99
|
this.startTimer();
|
|
100
|
+
this.setupBrowserExit();
|
|
91
101
|
}
|
|
92
|
-
if (options.autoFlushOnExit !== false) {
|
|
93
|
-
this.setupExitHandlers();
|
|
94
|
-
}
|
|
95
|
-
this.onEventsDropped = options.onEventsDropped;
|
|
96
102
|
}
|
|
97
|
-
|
|
103
|
+
// PUBLIC
|
|
98
104
|
track(name, options) {
|
|
99
105
|
if (this.destroyed) return;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.onEventsDropped?.(this.droppedEvents);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
this.queue.push({
|
|
106
|
-
idempotencyKey: generateUUIDv4(),
|
|
106
|
+
const event = {
|
|
107
|
+
idempotencyKey: uuid(),
|
|
107
108
|
name,
|
|
108
109
|
userId: options?.userId,
|
|
109
110
|
properties: options?.properties ?? {},
|
|
110
111
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
111
|
-
}
|
|
112
|
-
if (this.
|
|
112
|
+
};
|
|
113
|
+
if (this.runtime !== "browser") {
|
|
114
|
+
void this.send([event]);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (this.queue.length >= this.maxQueue) return;
|
|
118
|
+
this.queue.push(event);
|
|
119
|
+
this.persist();
|
|
120
|
+
if (this.queue.length >= this.maxBatch) {
|
|
113
121
|
void this.flush();
|
|
114
122
|
}
|
|
115
123
|
}
|
|
116
124
|
async flush() {
|
|
117
|
-
if (this.destroyed) return;
|
|
118
|
-
if (this.
|
|
119
|
-
if (this.
|
|
120
|
-
if (
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
125
|
+
if (this.inFlight || this.destroyed) return;
|
|
126
|
+
if (!this.queue.length) return;
|
|
127
|
+
if (!this.leader.canSend()) return;
|
|
128
|
+
if (Date.now() < this.circuitOpenUntil) return;
|
|
123
129
|
this.inFlight = true;
|
|
124
|
-
const batch = this.queue.splice(0, this.
|
|
130
|
+
const batch = this.queue.splice(0, this.maxBatch);
|
|
131
|
+
this.persist();
|
|
125
132
|
try {
|
|
126
133
|
await this.send(batch);
|
|
134
|
+
this.failureCount = 0;
|
|
127
135
|
} catch {
|
|
128
|
-
this.queue
|
|
136
|
+
this.queue = [...batch, ...this.queue];
|
|
137
|
+
this.persist();
|
|
138
|
+
this.failureCount++;
|
|
139
|
+
if (this.failureCount >= 5) {
|
|
140
|
+
this.circuitOpenUntil = Date.now() + 5e3;
|
|
141
|
+
}
|
|
129
142
|
} finally {
|
|
130
143
|
this.inFlight = false;
|
|
131
144
|
}
|
|
@@ -134,112 +147,58 @@ var Eventra = class {
|
|
|
134
147
|
this.destroyed = true;
|
|
135
148
|
if (this.timer) clearInterval(this.timer);
|
|
136
149
|
}
|
|
137
|
-
|
|
138
|
-
trySendBeacon(payload) {
|
|
139
|
-
if (!isBrowser()) return false;
|
|
140
|
-
try {
|
|
141
|
-
if (navigator.sendBeacon) {
|
|
142
|
-
const blob = new Blob([payload], {
|
|
143
|
-
type: "application/json"
|
|
144
|
-
});
|
|
145
|
-
return navigator.sendBeacon(this.endpoint, blob);
|
|
146
|
-
}
|
|
147
|
-
} catch {
|
|
148
|
-
}
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
150
|
+
// SEND (CORE)
|
|
151
151
|
async send(events) {
|
|
152
152
|
const payload = JSON.stringify({
|
|
153
153
|
sentAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
154
154
|
sdk: this.sdkInfo,
|
|
155
155
|
events
|
|
156
156
|
});
|
|
157
|
-
if (
|
|
158
|
-
|
|
157
|
+
if (this.runtime === "browser" && typeof navigator !== "undefined" && navigator.sendBeacon && payload.length < 6e4 && document.visibilityState === "hidden") {
|
|
158
|
+
navigator.sendBeacon(this.endpoint, payload);
|
|
159
|
+
return;
|
|
159
160
|
}
|
|
160
161
|
let attempt = 0;
|
|
161
|
-
while (attempt <= this.
|
|
162
|
+
while (attempt <= this.retries) {
|
|
162
163
|
try {
|
|
163
|
-
const
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
166
|
+
const res = await this.fetch(this.endpoint, {
|
|
164
167
|
method: "POST",
|
|
165
168
|
headers: {
|
|
166
169
|
"Content-Type": "application/json",
|
|
167
170
|
"x-api-key": this.apiKey
|
|
168
171
|
},
|
|
169
|
-
body: payload
|
|
172
|
+
body: payload,
|
|
173
|
+
signal: controller.signal
|
|
170
174
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (!res.ok) {
|
|
178
|
-
throw new Error(`HTTP ${res.status}`);
|
|
179
|
-
}
|
|
175
|
+
clearTimeout(timeout);
|
|
176
|
+
if (res.status >= 400 && res.status < 500) return;
|
|
177
|
+
if (!res.ok) throw new Error();
|
|
180
178
|
return;
|
|
181
|
-
} catch
|
|
179
|
+
} catch {
|
|
182
180
|
attempt++;
|
|
183
|
-
if (attempt > this.
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
const jitter = 0.5 + Math.random();
|
|
187
|
-
const delay = this.retryBaseDelay * 2 ** (attempt - 1) * jitter;
|
|
188
|
-
await sleep(delay);
|
|
181
|
+
if (attempt > this.retries) throw new Error();
|
|
182
|
+
await sleep(this.retryDelay * 2 ** attempt);
|
|
189
183
|
}
|
|
190
184
|
}
|
|
191
185
|
}
|
|
192
|
-
|
|
193
|
-
detectRuntime() {
|
|
194
|
-
const g = globalThis;
|
|
195
|
-
if (typeof g["EdgeRuntime"] !== "undefined") {
|
|
196
|
-
return "edge-vercel";
|
|
197
|
-
}
|
|
198
|
-
if (typeof g["WebSocketPair"] !== "undefined" && typeof g["caches"] !== "undefined") {
|
|
199
|
-
return "edge-cloudflare";
|
|
200
|
-
}
|
|
201
|
-
if (g["Deno"]) return "deno";
|
|
202
|
-
if (g["Bun"]) return "bun";
|
|
203
|
-
if (typeof window !== "undefined") {
|
|
204
|
-
return "browser";
|
|
205
|
-
}
|
|
206
|
-
if (typeof self !== "undefined" && typeof window === "undefined") {
|
|
207
|
-
return "worker";
|
|
208
|
-
}
|
|
209
|
-
const maybeProcess = g["process"];
|
|
210
|
-
if (maybeProcess?.versions?.node) {
|
|
211
|
-
return "node";
|
|
212
|
-
}
|
|
213
|
-
return "unknown";
|
|
214
|
-
}
|
|
215
|
-
/* ---------------- Timer ---------------- */
|
|
186
|
+
// BROWSER
|
|
216
187
|
startTimer() {
|
|
217
188
|
this.timer = setInterval(() => {
|
|
218
189
|
void this.flush();
|
|
219
190
|
}, this.flushInterval);
|
|
220
191
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const maybeProcess = g["process"];
|
|
232
|
-
if (typeof maybeProcess?.once === "function") {
|
|
233
|
-
const shutdown = async () => {
|
|
234
|
-
try {
|
|
235
|
-
await this.flush();
|
|
236
|
-
} catch {
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
maybeProcess.once("SIGINT", shutdown);
|
|
240
|
-
maybeProcess.once("SIGTERM", shutdown);
|
|
241
|
-
maybeProcess.once("beforeExit", shutdown);
|
|
242
|
-
}
|
|
192
|
+
setupBrowserExit() {
|
|
193
|
+
window.addEventListener("visibilitychange", () => {
|
|
194
|
+
if (document.visibilityState === "hidden") {
|
|
195
|
+
void this.flush();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
persist() {
|
|
200
|
+
if (this.runtime !== "browser") return;
|
|
201
|
+
this.storage.set("__eventra_q__", this.queue);
|
|
243
202
|
}
|
|
244
203
|
};
|
|
245
204
|
export {
|
package/dist/index.mjs.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_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 const g = globalThis as Record<string, unknown>;\n\n // Edge (Vercel)\n if (typeof g[\"EdgeRuntime\"] !== \"undefined\") {\n return \"edge-vercel\";\n }\n\n // Cloudflare Workers\n if (\n typeof g[\"WebSocketPair\"] !== \"undefined\" &&\n typeof g[\"caches\"] !== \"undefined\"\n ) {\n return \"edge-cloudflare\";\n }\n\n // Deno\n if (g[\"Deno\"]) return \"deno\";\n\n // Bun\n if (g[\"Bun\"]) return \"bun\";\n\n // Browser (window)\n if (typeof window !== \"undefined\") {\n return \"browser\";\n }\n\n // Web Worker / Service Worker\n if (\n typeof self !== \"undefined\" &&\n typeof window === \"undefined\"\n ) {\n return \"worker\";\n }\n\n // Node\n const maybeProcess = g[\"process\"] as\n | { versions?: { node?: string } }\n | undefined;\n\n if (maybeProcess?.versions?.node) {\n return \"node\";\n }\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 { 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":[]}
|