@eventra_dev/eventra-sdk 1.1.0 → 1.1.1
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/dist/index.cjs +83 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +83 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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 =
|
|
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.
|
|
115
|
-
this.
|
|
116
|
-
|
|
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: "
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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() {
|
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 { 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKA,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/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 =
|
|
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.
|
|
89
|
-
this.
|
|
90
|
-
|
|
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: "
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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() {
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":[]}
|