@diegotsi/flint-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,494 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ collectEnvironment: () => collectEnvironment,
24
+ createConsoleCollector: () => createConsoleCollector,
25
+ createFrustrationCollector: () => createFrustrationCollector,
26
+ createNetworkCollector: () => createNetworkCollector,
27
+ flint: () => flint,
28
+ getSnapshot: () => getSnapshot,
29
+ resolveTheme: () => resolveTheme,
30
+ submitReplay: () => submitReplay,
31
+ submitReport: () => submitReport,
32
+ subscribe: () => subscribe
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/api.ts
37
+ var import_fflate = require("fflate");
38
+ async function fetchWithRetry(url, init, retries = 3, baseDelay = 1e3) {
39
+ for (let attempt = 0; attempt <= retries; attempt++) {
40
+ try {
41
+ const res = await fetch(url, init);
42
+ if (res.ok || attempt === retries) return res;
43
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;
44
+ } catch (err) {
45
+ if (attempt === retries) throw err;
46
+ }
47
+ await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
48
+ }
49
+ throw new Error("Max retries exceeded");
50
+ }
51
+ async function submitReport(serverUrl, projectKey, payload, screenshot) {
52
+ const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
53
+ let body;
54
+ const headers = {
55
+ "x-project-key": projectKey
56
+ };
57
+ if (screenshot) {
58
+ const form = new FormData();
59
+ form.append("reporterId", payload.reporterId);
60
+ form.append("reporterName", payload.reporterName);
61
+ if (payload.reporterEmail) form.append("reporterEmail", payload.reporterEmail);
62
+ form.append("description", payload.description);
63
+ if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
64
+ if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
65
+ if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
66
+ if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
67
+ form.append("severity", payload.severity);
68
+ if (payload.url) form.append("url", payload.url);
69
+ if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
70
+ form.append("screenshot", screenshot);
71
+ body = form;
72
+ } else {
73
+ body = JSON.stringify(payload);
74
+ headers["Content-Type"] = "application/json";
75
+ }
76
+ const res = await fetchWithRetry(url, { method: "POST", headers, body });
77
+ if (!res.ok) {
78
+ const err = await res.json().catch(() => ({ error: "Unknown error" }));
79
+ throw new Error(err.error ?? `HTTP ${res.status}`);
80
+ }
81
+ return res.json();
82
+ }
83
+ async function submitReplay(serverUrl, projectKey, reportId, events) {
84
+ const json = JSON.stringify(events);
85
+ const encoded = new TextEncoder().encode(json);
86
+ const compressed = (0, import_fflate.gzipSync)(encoded);
87
+ const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
88
+ await fetch(url, {
89
+ method: "POST",
90
+ headers: {
91
+ "x-project-key": projectKey,
92
+ "Content-Type": "application/octet-stream"
93
+ },
94
+ body: compressed.buffer
95
+ });
96
+ }
97
+
98
+ // src/collectors/console.ts
99
+ var MAX_ENTRIES = 50;
100
+ function createConsoleCollector() {
101
+ const entries = [];
102
+ let active = false;
103
+ const originals = {
104
+ log: console.log.bind(console),
105
+ warn: console.warn.bind(console),
106
+ error: console.error.bind(console)
107
+ };
108
+ let origOnerror = null;
109
+ let origUnhandled = null;
110
+ function push(level, args) {
111
+ let str;
112
+ try {
113
+ str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
114
+ } catch {
115
+ str = String(args[0]);
116
+ }
117
+ if (str.length > 500) str = str.slice(0, 500) + "\u2026";
118
+ entries.push({ level, args: str, timestamp: Date.now() });
119
+ if (entries.length > MAX_ENTRIES) entries.shift();
120
+ }
121
+ return {
122
+ start() {
123
+ if (active) return;
124
+ active = true;
125
+ console.log = (...args) => {
126
+ push("log", args);
127
+ originals.log(...args);
128
+ };
129
+ console.warn = (...args) => {
130
+ push("warn", args);
131
+ originals.warn(...args);
132
+ };
133
+ console.error = (...args) => {
134
+ push("error", args);
135
+ originals.error(...args);
136
+ };
137
+ origOnerror = window.onerror;
138
+ window.onerror = (msg, src, line, col, err) => {
139
+ push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
140
+ if (typeof origOnerror === "function") return origOnerror(msg, src, line, col, err);
141
+ return false;
142
+ };
143
+ origUnhandled = window.onunhandledrejection;
144
+ window.onunhandledrejection = (event) => {
145
+ const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
146
+ push("error", ["UnhandledRejection:", reason]);
147
+ if (typeof origUnhandled === "function") origUnhandled.call(window, event);
148
+ };
149
+ },
150
+ stop() {
151
+ if (!active) return;
152
+ active = false;
153
+ console.log = originals.log;
154
+ console.warn = originals.warn;
155
+ console.error = originals.error;
156
+ window.onerror = origOnerror;
157
+ window.onunhandledrejection = origUnhandled;
158
+ },
159
+ getEntries() {
160
+ return [...entries];
161
+ }
162
+ };
163
+ }
164
+
165
+ // src/collectors/environment.ts
166
+ function collectEnvironment() {
167
+ const ua = navigator.userAgent;
168
+ let browser = "Unknown";
169
+ const chromeM = ua.match(/Chrome\/(\d+)/);
170
+ const firefoxM = ua.match(/Firefox\/(\d+)/);
171
+ const edgeM = ua.match(/Edg\/(\d+)/);
172
+ const safariM = ua.match(/Version\/(\d+)/);
173
+ const operaM = ua.match(/OPR\/(\d+)/);
174
+ if (operaM) {
175
+ browser = `Opera ${operaM[1]}`;
176
+ } else if (edgeM) {
177
+ browser = `Edge ${edgeM[1]}`;
178
+ } else if (chromeM && !/Edg|OPR/.test(ua)) {
179
+ browser = `Chrome ${chromeM[1]}`;
180
+ } else if (firefoxM) {
181
+ browser = `Firefox ${firefoxM[1]}`;
182
+ } else if (safariM && /Safari\//.test(ua)) {
183
+ browser = `Safari ${safariM[1]}`;
184
+ }
185
+ let os = "Unknown";
186
+ const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
187
+ const winM = ua.match(/Windows NT (\d+\.\d+)/);
188
+ const androidM = ua.match(/Android (\d+)/);
189
+ const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
190
+ if (macM) {
191
+ os = `macOS ${macM[1].replace("_", ".")}`;
192
+ } else if (winM) {
193
+ const winMap = {
194
+ "10.0": "10/11",
195
+ "6.3": "8.1",
196
+ "6.2": "8",
197
+ "6.1": "7"
198
+ };
199
+ os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
200
+ } else if (androidM) {
201
+ os = `Android ${androidM[1]}`;
202
+ } else if (iosM) {
203
+ os = `iOS ${iosM[1].replace(/_/g, ".")}`;
204
+ } else if (/Linux/.test(ua)) {
205
+ os = "Linux";
206
+ }
207
+ return {
208
+ browser,
209
+ os,
210
+ viewport: `${window.innerWidth}x${window.innerHeight}`,
211
+ screen: `${screen.width}x${screen.height}`,
212
+ language: navigator.language,
213
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
214
+ online: navigator.onLine
215
+ };
216
+ }
217
+
218
+ // src/collectors/network.ts
219
+ var MAX_ENTRIES2 = 50;
220
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
221
+ "browser-intake-datadoghq.com",
222
+ "rum.browser-intake-datadoghq.com",
223
+ "logs.browser-intake-datadoghq.com",
224
+ "session-replay.browser-intake-datadoghq.com"
225
+ ]);
226
+ function isBlockedUrl(url, extra) {
227
+ try {
228
+ const host = new URL(url, location.href).hostname;
229
+ const all = [...BLOCKED_HOSTS, ...extra];
230
+ return all.some((b) => host === b || host.endsWith("." + b));
231
+ } catch {
232
+ return false;
233
+ }
234
+ }
235
+ function truncateUrl(url) {
236
+ try {
237
+ const u = new URL(url, location.href);
238
+ const base = `${u.origin}${u.pathname}`;
239
+ return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
240
+ } catch {
241
+ return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
242
+ }
243
+ }
244
+ function createNetworkCollector(extraBlockedHosts = []) {
245
+ const entries = [];
246
+ const blocked = new Set(extraBlockedHosts);
247
+ let origFetch = null;
248
+ let origXHROpen = null;
249
+ let active = false;
250
+ function push(entry) {
251
+ entries.push(entry);
252
+ if (entries.length > MAX_ENTRIES2) entries.shift();
253
+ }
254
+ return {
255
+ start() {
256
+ if (active) return;
257
+ active = true;
258
+ origFetch = window.fetch;
259
+ window.fetch = async (input, init) => {
260
+ const method = (init?.method ?? "GET").toUpperCase();
261
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
262
+ const startTime = Date.now();
263
+ const res = await origFetch.call(window, input, init);
264
+ if (res.status >= 400 && !isBlockedUrl(url, blocked)) {
265
+ push({
266
+ method,
267
+ url: truncateUrl(url),
268
+ status: res.status,
269
+ duration: Date.now() - startTime,
270
+ timestamp: startTime
271
+ });
272
+ }
273
+ return res;
274
+ };
275
+ origXHROpen = XMLHttpRequest.prototype.open;
276
+ XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
277
+ const startTime = Date.now();
278
+ const urlStr = typeof url === "string" ? url : url.href;
279
+ this.addEventListener("load", () => {
280
+ if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {
281
+ push({
282
+ method: method.toUpperCase(),
283
+ url: truncateUrl(urlStr),
284
+ status: this.status,
285
+ duration: Date.now() - startTime,
286
+ timestamp: startTime
287
+ });
288
+ }
289
+ });
290
+ return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
291
+ };
292
+ },
293
+ stop() {
294
+ if (!active) return;
295
+ active = false;
296
+ if (origFetch) window.fetch = origFetch;
297
+ if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
298
+ },
299
+ getEntries() {
300
+ return [...entries];
301
+ }
302
+ };
303
+ }
304
+
305
+ // src/collectors/frustration.ts
306
+ function createFrustrationCollector(opts) {
307
+ const threshold = opts?.rageClickThreshold ?? 3;
308
+ const window2 = opts?.rageClickWindow ?? 500;
309
+ const errorThreshold = opts?.errorLoopThreshold ?? 3;
310
+ const errorWindow = opts?.errorLoopWindow ?? 3e4;
311
+ const deadClicksEnabled = opts?.enableDeadClicks ?? true;
312
+ const events = [];
313
+ const listeners2 = /* @__PURE__ */ new Set();
314
+ const clickHistory = [];
315
+ const errorCounts = /* @__PURE__ */ new Map();
316
+ let clickHandler = null;
317
+ let origConsoleError = null;
318
+ function emit2(event) {
319
+ events.push(event);
320
+ if (events.length > 50) events.shift();
321
+ for (const cb of listeners2) cb(event);
322
+ }
323
+ function getCSSSelector(el) {
324
+ if (el.id) return `#${el.id}`;
325
+ const tag = el.tagName.toLowerCase();
326
+ const cls = [...el.classList].slice(0, 3).join(".");
327
+ if (cls) return `${tag}.${cls}`;
328
+ return tag;
329
+ }
330
+ function isInteractive(el) {
331
+ const tag = el.tagName.toLowerCase();
332
+ if (["a", "button", "input", "select", "textarea", "label", "summary"].includes(tag)) return true;
333
+ if (el.getAttribute("role") === "button" || el.getAttribute("tabindex")) return true;
334
+ if (el.onclick || el.getAttribute("onclick")) return true;
335
+ if (el.closest("a, button, [role=button], [onclick]")) return true;
336
+ try {
337
+ if (getComputedStyle(el).cursor === "pointer") return true;
338
+ } catch {
339
+ }
340
+ return false;
341
+ }
342
+ function handleClick(e) {
343
+ const target = e.target;
344
+ if (!target) return;
345
+ const now = Date.now();
346
+ clickHistory.push({ target, time: now });
347
+ while (clickHistory.length > 0 && now - clickHistory[0].time > 1e3) {
348
+ clickHistory.shift();
349
+ }
350
+ const recentOnSame = clickHistory.filter(
351
+ (c) => c.target === target && now - c.time < window2
352
+ );
353
+ if (recentOnSame.length >= threshold) {
354
+ emit2({
355
+ type: "rage_click",
356
+ timestamp: now,
357
+ target: getCSSSelector(target),
358
+ details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,
359
+ url: globalThis.location?.href ?? ""
360
+ });
361
+ clickHistory.length = 0;
362
+ }
363
+ if (deadClicksEnabled && !isInteractive(target)) {
364
+ emit2({
365
+ type: "dead_click",
366
+ timestamp: now,
367
+ target: getCSSSelector(target),
368
+ details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,
369
+ url: globalThis.location?.href ?? ""
370
+ });
371
+ }
372
+ }
373
+ function patchConsoleError() {
374
+ origConsoleError = console.error;
375
+ console.error = (...args) => {
376
+ origConsoleError?.apply(console, args);
377
+ const key = String(args[0]).slice(0, 100);
378
+ const now = Date.now();
379
+ const existing = errorCounts.get(key);
380
+ if (existing && now - existing.firstSeen < errorWindow) {
381
+ existing.count++;
382
+ if (existing.count >= errorThreshold) {
383
+ emit2({
384
+ type: "error_loop",
385
+ timestamp: now,
386
+ target: "console",
387
+ details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1e3)}s: ${key}`,
388
+ url: globalThis.location?.href ?? ""
389
+ });
390
+ errorCounts.delete(key);
391
+ }
392
+ } else {
393
+ errorCounts.set(key, { count: 1, firstSeen: now });
394
+ }
395
+ };
396
+ }
397
+ return {
398
+ start() {
399
+ clickHandler = handleClick;
400
+ document.addEventListener("click", clickHandler, true);
401
+ patchConsoleError();
402
+ },
403
+ stop() {
404
+ if (clickHandler) document.removeEventListener("click", clickHandler, true);
405
+ if (origConsoleError) console.error = origConsoleError;
406
+ clickHistory.length = 0;
407
+ errorCounts.clear();
408
+ },
409
+ getEvents() {
410
+ return [...events];
411
+ },
412
+ onFrustration(callback) {
413
+ listeners2.add(callback);
414
+ return () => listeners2.delete(callback);
415
+ }
416
+ };
417
+ }
418
+
419
+ // src/store.ts
420
+ var state = { user: void 0, sessionReplay: void 0 };
421
+ var listeners = /* @__PURE__ */ new Set();
422
+ function emit() {
423
+ for (const l of listeners) l();
424
+ }
425
+ function subscribe(listener) {
426
+ listeners.add(listener);
427
+ return () => listeners.delete(listener);
428
+ }
429
+ function getSnapshot() {
430
+ return state;
431
+ }
432
+ var flint = {
433
+ setUser(user) {
434
+ state = { ...state, user: user ?? void 0 };
435
+ emit();
436
+ },
437
+ setSessionReplay(url) {
438
+ state = { ...state, sessionReplay: url ?? void 0 };
439
+ emit();
440
+ }
441
+ };
442
+
443
+ // src/theme.ts
444
+ var light = {
445
+ background: "rgba(255,255,255,0.90)",
446
+ backgroundSecondary: "rgba(249,250,251,0.75)",
447
+ accent: "#2563eb",
448
+ accentHover: "#1d4ed8",
449
+ text: "#111827",
450
+ textMuted: "#6b7280",
451
+ border: "rgba(255,255,255,0.9)",
452
+ shadow: "0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)",
453
+ buttonText: "#ffffff",
454
+ backdropFilter: "blur(32px) saturate(1.8)"
455
+ };
456
+ var dark = {
457
+ background: "rgba(15,20,35,0.88)",
458
+ backgroundSecondary: "rgba(5,8,18,0.65)",
459
+ accent: "#4d8aff",
460
+ accentHover: "#3b6fdb",
461
+ text: "#dde3ef",
462
+ textMuted: "#6b7a93",
463
+ border: "rgba(255,255,255,0.08)",
464
+ shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
465
+ buttonText: "#ffffff",
466
+ backdropFilter: "blur(32px) saturate(1.6)"
467
+ };
468
+ function resolveTheme(theme) {
469
+ if (theme === "dark") return dark;
470
+ if (theme === "light") return light;
471
+ const override = theme;
472
+ return {
473
+ ...light,
474
+ background: override.background ?? light.background,
475
+ accent: override.accent ?? light.accent,
476
+ accentHover: override.accent ?? light.accentHover,
477
+ text: override.text ?? light.text,
478
+ border: override.border ?? light.border
479
+ };
480
+ }
481
+ // Annotate the CommonJS export names for ESM import in node:
482
+ 0 && (module.exports = {
483
+ collectEnvironment,
484
+ createConsoleCollector,
485
+ createFrustrationCollector,
486
+ createNetworkCollector,
487
+ flint,
488
+ getSnapshot,
489
+ resolveTheme,
490
+ submitReplay,
491
+ submitReport,
492
+ subscribe
493
+ });
494
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/api.ts","../src/collectors/console.ts","../src/collectors/environment.ts","../src/collectors/network.ts","../src/collectors/frustration.ts","../src/store.ts","../src/theme.ts"],"sourcesContent":["// Collectors\n\n// Types (re-export)\nexport type {\n ConsoleEntry,\n EnvironmentInfo,\n FlintUser,\n FlintWidgetProps,\n NetworkEntry,\n ReportPayload,\n ReportResult,\n Severity,\n Theme,\n ThemeOverride,\n} from \"@flint/types\";\n// API\nexport { submitReplay, submitReport } from \"./api.js\";\n// Collector types\nexport type { ConsoleCollector } from \"./collectors/console.js\";\nexport { createConsoleCollector } from \"./collectors/console.js\";\nexport { collectEnvironment } from \"./collectors/environment.js\";\nexport type { NetworkCollector } from \"./collectors/network.js\";\nexport { createNetworkCollector } from \"./collectors/network.js\";\nexport type { FrustrationCollector, FrustrationEvent } from \"./collectors/frustration.js\";\nexport { createFrustrationCollector } from \"./collectors/frustration.js\";\nexport type { FlintState } from \"./store.js\";\n// State\nexport { flint, getSnapshot, subscribe } from \"./store.js\";\nexport type { ResolvedTheme } from \"./theme.js\";\n// Theme\nexport { resolveTheme } from \"./theme.js\";\n","import type { ReportPayload, ReportResult } from \"@flint/types\";\nimport { gzipSync } from \"fflate\";\n\nasync function fetchWithRetry(url: string, init: RequestInit, retries = 3, baseDelay = 1000): Promise<Response> {\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const res = await fetch(url, init);\n if (res.ok || attempt === retries) return res;\n // Don't retry client errors (4xx) except 429\n if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;\n } catch (err) {\n if (attempt === retries) throw err;\n }\n await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));\n }\n throw new Error(\"Max retries exceeded\");\n}\n\nexport async function submitReport(\n serverUrl: string,\n projectKey: string,\n payload: ReportPayload,\n screenshot?: File,\n): Promise<ReportResult> {\n const url = `${serverUrl.replace(/\\/$/, \"\")}/api/v1/bug-reports`;\n\n let body: BodyInit;\n const headers: Record<string, string> = {\n \"x-project-key\": projectKey,\n };\n\n if (screenshot) {\n const form = new FormData();\n form.append(\"reporterId\", payload.reporterId);\n form.append(\"reporterName\", payload.reporterName);\n if (payload.reporterEmail) form.append(\"reporterEmail\", payload.reporterEmail);\n form.append(\"description\", payload.description);\n if (payload.expectedBehavior) form.append(\"expectedBehavior\", payload.expectedBehavior);\n if (payload.stepsToReproduce) form.append(\"stepsToReproduce\", JSON.stringify(payload.stepsToReproduce));\n if (payload.externalReplayUrl) form.append(\"externalReplayUrl\", payload.externalReplayUrl);\n if (payload.additionalContext) form.append(\"additionalContext\", payload.additionalContext);\n form.append(\"severity\", payload.severity);\n if (payload.url) form.append(\"url\", payload.url);\n if (payload.meta) form.append(\"meta\", JSON.stringify(payload.meta));\n form.append(\"screenshot\", screenshot);\n body = form;\n } else {\n body = JSON.stringify(payload);\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const res = await fetchWithRetry(url, { method: \"POST\", headers, body });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: \"Unknown error\" }));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<ReportResult>;\n}\n\nexport async function submitReplay(\n serverUrl: string,\n projectKey: string,\n reportId: string,\n events: unknown[],\n): Promise<void> {\n const json = JSON.stringify(events);\n const encoded = new TextEncoder().encode(json);\n const compressed = gzipSync(encoded);\n\n const url = `${serverUrl.replace(/\\/$/, \"\")}/api/v1/bug-reports/${reportId}/replay`;\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"x-project-key\": projectKey,\n \"Content-Type\": \"application/octet-stream\",\n },\n body: compressed.buffer as ArrayBuffer,\n });\n}\n","import type { ConsoleEntry } from \"@flint/types\";\n\nconst MAX_ENTRIES = 50;\n\nexport interface ConsoleCollector {\n start(): void;\n stop(): void;\n getEntries(): ConsoleEntry[];\n}\n\nexport function createConsoleCollector(): ConsoleCollector {\n const entries: ConsoleEntry[] = [];\n let active = false;\n\n const originals = {\n log: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n };\n\n let origOnerror: typeof window.onerror = null;\n let origUnhandled: typeof window.onunhandledrejection = null;\n\n function push(level: ConsoleEntry[\"level\"], args: unknown[]) {\n let str: string;\n try {\n str = args.map((a) => (typeof a === \"string\" ? a : JSON.stringify(a))).join(\" \");\n } catch {\n str = String(args[0]);\n }\n if (str.length > 500) str = str.slice(0, 500) + \"\\u2026\";\n entries.push({ level, args: str, timestamp: Date.now() });\n if (entries.length > MAX_ENTRIES) entries.shift();\n }\n\n return {\n start() {\n if (active) return;\n active = true;\n\n console.log = (...args: unknown[]) => {\n push(\"log\", args);\n originals.log(...args);\n };\n console.warn = (...args: unknown[]) => {\n push(\"warn\", args);\n originals.warn(...args);\n };\n console.error = (...args: unknown[]) => {\n push(\"error\", args);\n originals.error(...args);\n };\n\n origOnerror = window.onerror;\n window.onerror = (msg, src, line, col, err) => {\n push(\"error\", [err?.message ?? String(msg), `${src}:${line}:${col}`]);\n if (typeof origOnerror === \"function\") return origOnerror(msg, src, line, col, err);\n return false;\n };\n\n origUnhandled = window.onunhandledrejection;\n window.onunhandledrejection = (event) => {\n const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);\n push(\"error\", [\"UnhandledRejection:\", reason]);\n if (typeof origUnhandled === \"function\") origUnhandled.call(window, event);\n };\n },\n\n stop() {\n if (!active) return;\n active = false;\n console.log = originals.log;\n console.warn = originals.warn;\n console.error = originals.error;\n window.onerror = origOnerror;\n window.onunhandledrejection = origUnhandled;\n },\n\n getEntries() {\n return [...entries];\n },\n };\n}\n","import type { EnvironmentInfo } from \"@flint/types\";\n\nexport function collectEnvironment(): EnvironmentInfo {\n const ua = navigator.userAgent;\n\n // -- Browser --\n let browser = \"Unknown\";\n const chromeM = ua.match(/Chrome\\/(\\d+)/);\n const firefoxM = ua.match(/Firefox\\/(\\d+)/);\n const edgeM = ua.match(/Edg\\/(\\d+)/);\n const safariM = ua.match(/Version\\/(\\d+)/);\n const operaM = ua.match(/OPR\\/(\\d+)/);\n\n if (operaM) {\n browser = `Opera ${operaM[1]}`;\n } else if (edgeM) {\n browser = `Edge ${edgeM[1]}`;\n } else if (chromeM && !/Edg|OPR/.test(ua)) {\n browser = `Chrome ${chromeM[1]}`;\n } else if (firefoxM) {\n browser = `Firefox ${firefoxM[1]}`;\n } else if (safariM && /Safari\\//.test(ua)) {\n browser = `Safari ${safariM[1]}`;\n }\n\n // -- OS --\n let os = \"Unknown\";\n const macM = ua.match(/Mac OS X (\\d+[._]\\d+)/);\n const winM = ua.match(/Windows NT (\\d+\\.\\d+)/);\n const androidM = ua.match(/Android (\\d+)/);\n const iosM = ua.match(/iPhone OS (\\d+[._]\\d+)/);\n\n if (macM) {\n os = `macOS ${macM[1].replace(\"_\", \".\")}`;\n } else if (winM) {\n const winMap: Record<string, string> = {\n \"10.0\": \"10/11\",\n \"6.3\": \"8.1\",\n \"6.2\": \"8\",\n \"6.1\": \"7\",\n };\n os = `Windows ${winMap[winM[1]] ?? winM[1]}`;\n } else if (androidM) {\n os = `Android ${androidM[1]}`;\n } else if (iosM) {\n os = `iOS ${iosM[1].replace(/_/g, \".\")}`;\n } else if (/Linux/.test(ua)) {\n os = \"Linux\";\n }\n\n return {\n browser,\n os,\n viewport: `${window.innerWidth}x${window.innerHeight}`,\n screen: `${screen.width}x${screen.height}`,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n online: navigator.onLine,\n };\n}\n","import type { NetworkEntry } from \"@flint/types\";\n\nconst MAX_ENTRIES = 50;\n\nconst BLOCKED_HOSTS = new Set([\n \"browser-intake-datadoghq.com\",\n \"rum.browser-intake-datadoghq.com\",\n \"logs.browser-intake-datadoghq.com\",\n \"session-replay.browser-intake-datadoghq.com\",\n]);\n\nfunction isBlockedUrl(url: string, extra: Set<string>): boolean {\n try {\n const host = new URL(url, location.href).hostname;\n const all = [...BLOCKED_HOSTS, ...extra];\n return all.some((b) => host === b || host.endsWith(\".\" + b));\n } catch {\n return false;\n }\n}\n\nexport interface NetworkCollector {\n start(): void;\n stop(): void;\n getEntries(): NetworkEntry[];\n}\n\nfunction truncateUrl(url: string): string {\n try {\n const u = new URL(url, location.href);\n const base = `${u.origin}${u.pathname}`;\n return base.length > 200 ? base.slice(0, 200) + \"\\u2026\" : base;\n } catch {\n return url.length > 200 ? url.slice(0, 200) + \"\\u2026\" : url;\n }\n}\n\nexport function createNetworkCollector(extraBlockedHosts: string[] = []): NetworkCollector {\n const entries: NetworkEntry[] = [];\n const blocked = new Set(extraBlockedHosts);\n let origFetch: typeof window.fetch | null = null;\n let origXHROpen: typeof XMLHttpRequest.prototype.open | null = null;\n let active = false;\n\n function push(entry: NetworkEntry) {\n entries.push(entry);\n if (entries.length > MAX_ENTRIES) entries.shift();\n }\n\n return {\n start() {\n if (active) return;\n active = true;\n\n // -- Patch fetch --\n origFetch = window.fetch;\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const method = ((init?.method ?? \"GET\") as string).toUpperCase();\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : (input as Request).url;\n const startTime = Date.now();\n const res = await origFetch!.call(window, input, init);\n if (res.status >= 400 && !isBlockedUrl(url, blocked)) {\n push({\n method,\n url: truncateUrl(url),\n status: res.status,\n duration: Date.now() - startTime,\n timestamp: startTime,\n });\n }\n return res;\n };\n\n // -- Patch XHR --\n origXHROpen = XMLHttpRequest.prototype.open;\n XMLHttpRequest.prototype.open = function (\n method: string,\n url: string | URL,\n async?: boolean,\n username?: string | null,\n password?: string | null,\n ) {\n const startTime = Date.now();\n const urlStr = typeof url === \"string\" ? url : (url as URL).href;\n\n this.addEventListener(\"load\", () => {\n if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {\n push({\n method: method.toUpperCase(),\n url: truncateUrl(urlStr),\n status: this.status,\n duration: Date.now() - startTime,\n timestamp: startTime,\n });\n }\n });\n\n return origXHROpen!.apply(this, [method, url, async ?? true, username, password] as Parameters<\n typeof XMLHttpRequest.prototype.open\n >);\n };\n },\n\n stop() {\n if (!active) return;\n active = false;\n if (origFetch) window.fetch = origFetch;\n if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;\n },\n\n getEntries() {\n return [...entries];\n },\n };\n}\n","export interface FrustrationEvent {\n type: \"rage_click\" | \"dead_click\" | \"error_loop\";\n timestamp: number;\n target: string;\n details: string;\n url: string;\n}\n\nexport interface FrustrationCollector {\n start(): void;\n stop(): void;\n getEvents(): FrustrationEvent[];\n onFrustration(callback: (event: FrustrationEvent) => void): () => void;\n}\n\nexport function createFrustrationCollector(opts?: {\n rageClickThreshold?: number;\n rageClickWindow?: number;\n errorLoopThreshold?: number;\n errorLoopWindow?: number;\n enableDeadClicks?: boolean;\n}): FrustrationCollector {\n const threshold = opts?.rageClickThreshold ?? 3;\n const window = opts?.rageClickWindow ?? 500;\n const errorThreshold = opts?.errorLoopThreshold ?? 3;\n const errorWindow = opts?.errorLoopWindow ?? 30_000;\n const deadClicksEnabled = opts?.enableDeadClicks ?? true;\n\n const events: FrustrationEvent[] = [];\n const listeners = new Set<(event: FrustrationEvent) => void>();\n const clickHistory: { target: EventTarget | null; time: number }[] = [];\n const errorCounts = new Map<string, { count: number; firstSeen: number }>();\n let clickHandler: ((e: MouseEvent) => void) | null = null;\n let origConsoleError: typeof console.error | null = null;\n\n function emit(event: FrustrationEvent) {\n events.push(event);\n if (events.length > 50) events.shift();\n for (const cb of listeners) cb(event);\n }\n\n function getCSSSelector(el: Element): string {\n if (el.id) return `#${el.id}`;\n const tag = el.tagName.toLowerCase();\n const cls = [...el.classList].slice(0, 3).join(\".\");\n if (cls) return `${tag}.${cls}`;\n return tag;\n }\n\n function isInteractive(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if ([\"a\", \"button\", \"input\", \"select\", \"textarea\", \"label\", \"summary\"].includes(tag)) return true;\n if (el.getAttribute(\"role\") === \"button\" || el.getAttribute(\"tabindex\")) return true;\n if (el.onclick || el.getAttribute(\"onclick\")) return true;\n if (el.closest(\"a, button, [role=button], [onclick]\")) return true;\n try { if (getComputedStyle(el).cursor === \"pointer\") return true; } catch {}\n return false;\n }\n\n function handleClick(e: MouseEvent) {\n const target = e.target as HTMLElement;\n if (!target) return;\n const now = Date.now();\n\n // --- Rage click detection ---\n clickHistory.push({ target, time: now });\n // Remove old clicks\n while (clickHistory.length > 0 && now - clickHistory[0].time > 1000) {\n clickHistory.shift();\n }\n const recentOnSame = clickHistory.filter(\n (c) => c.target === target && now - c.time < window\n );\n if (recentOnSame.length >= threshold) {\n emit({\n type: \"rage_click\",\n timestamp: now,\n target: getCSSSelector(target),\n details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,\n url: globalThis.location?.href ?? \"\",\n });\n clickHistory.length = 0;\n }\n\n // --- Dead click detection ---\n if (deadClicksEnabled && !isInteractive(target)) {\n emit({\n type: \"dead_click\",\n timestamp: now,\n target: getCSSSelector(target),\n details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,\n url: globalThis.location?.href ?? \"\",\n });\n }\n }\n\n function patchConsoleError() {\n origConsoleError = console.error;\n console.error = (...args: unknown[]) => {\n origConsoleError?.apply(console, args);\n // Check for error loop\n const key = String(args[0]).slice(0, 100);\n const now = Date.now();\n const existing = errorCounts.get(key);\n if (existing && now - existing.firstSeen < errorWindow) {\n existing.count++;\n if (existing.count >= errorThreshold) {\n emit({\n type: \"error_loop\",\n timestamp: now,\n target: \"console\",\n details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1000)}s: ${key}`,\n url: globalThis.location?.href ?? \"\",\n });\n errorCounts.delete(key);\n }\n } else {\n errorCounts.set(key, { count: 1, firstSeen: now });\n }\n };\n }\n\n return {\n start() {\n clickHandler = handleClick;\n document.addEventListener(\"click\", clickHandler, true);\n patchConsoleError();\n },\n stop() {\n if (clickHandler) document.removeEventListener(\"click\", clickHandler, true);\n if (origConsoleError) console.error = origConsoleError;\n clickHistory.length = 0;\n errorCounts.clear();\n },\n getEvents() { return [...events]; },\n onFrustration(callback) {\n listeners.add(callback);\n return () => listeners.delete(callback);\n },\n };\n}\n","import type { FlintUser } from \"@flint/types\";\n\nexport interface FlintState {\n user: FlintUser | undefined;\n sessionReplay: string | (() => string) | undefined;\n}\n\nlet state: FlintState = { user: undefined, sessionReplay: undefined };\nconst listeners = new Set<() => void>();\n\nfunction emit() {\n for (const l of listeners) l();\n}\n\nexport function subscribe(listener: () => void) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getSnapshot(): FlintState {\n return state;\n}\n\nexport const flint = {\n setUser(user: FlintUser | null) {\n state = { ...state, user: user ?? undefined };\n emit();\n },\n setSessionReplay(url: string | (() => string) | null) {\n state = { ...state, sessionReplay: url ?? undefined };\n emit();\n },\n};\n","import type { Theme, ThemeOverride } from \"@flint/types\";\n\nexport interface ResolvedTheme {\n background: string;\n backgroundSecondary: string;\n accent: string;\n accentHover: string;\n text: string;\n textMuted: string;\n border: string;\n shadow: string;\n buttonText: string;\n backdropFilter: string;\n}\n\nconst light: ResolvedTheme = {\n background: \"rgba(255,255,255,0.90)\",\n backgroundSecondary: \"rgba(249,250,251,0.75)\",\n accent: \"#2563eb\",\n accentHover: \"#1d4ed8\",\n text: \"#111827\",\n textMuted: \"#6b7280\",\n border: \"rgba(255,255,255,0.9)\",\n shadow: \"0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)\",\n buttonText: \"#ffffff\",\n backdropFilter: \"blur(32px) saturate(1.8)\",\n};\n\nconst dark: ResolvedTheme = {\n background: \"rgba(15,20,35,0.88)\",\n backgroundSecondary: \"rgba(5,8,18,0.65)\",\n accent: \"#4d8aff\",\n accentHover: \"#3b6fdb\",\n text: \"#dde3ef\",\n textMuted: \"#6b7a93\",\n border: \"rgba(255,255,255,0.08)\",\n shadow: \"0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)\",\n buttonText: \"#ffffff\",\n backdropFilter: \"blur(32px) saturate(1.6)\",\n};\n\nexport function resolveTheme(theme: Theme): ResolvedTheme {\n if (theme === \"dark\") return dark;\n if (theme === \"light\") return light;\n // ThemeOverride — merge over light base\n const override = theme as ThemeOverride;\n return {\n ...light,\n background: override.background ?? light.background,\n accent: override.accent ?? light.accent,\n accentHover: override.accent ?? light.accentHover,\n text: override.text ?? light.text,\n border: override.border ?? light.border,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,oBAAyB;AAEzB,eAAe,eAAe,KAAa,MAAmB,UAAU,GAAG,YAAY,KAAyB;AAC9G,WAAS,UAAU,GAAG,WAAW,SAAS,WAAW;AACnD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK,IAAI;AACjC,UAAI,IAAI,MAAM,YAAY,QAAS,QAAO;AAE1C,UAAI,IAAI,UAAU,OAAO,IAAI,SAAS,OAAO,IAAI,WAAW,IAAK,QAAO;AAAA,IAC1E,SAAS,KAAK;AACZ,UAAI,YAAY,QAAS,OAAM;AAAA,IACjC;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,YAAY,KAAK,OAAO,CAAC;AAAA,EAClE;AACA,QAAM,IAAI,MAAM,sBAAsB;AACxC;AAEA,eAAsB,aACpB,WACA,YACA,SACA,YACuB;AACvB,QAAM,MAAM,GAAG,UAAU,QAAQ,OAAO,EAAE,CAAC;AAE3C,MAAI;AACJ,QAAM,UAAkC;AAAA,IACtC,iBAAiB;AAAA,EACnB;AAEA,MAAI,YAAY;AACd,UAAM,OAAO,IAAI,SAAS;AAC1B,SAAK,OAAO,cAAc,QAAQ,UAAU;AAC5C,SAAK,OAAO,gBAAgB,QAAQ,YAAY;AAChD,QAAI,QAAQ,cAAe,MAAK,OAAO,iBAAiB,QAAQ,aAAa;AAC7E,SAAK,OAAO,eAAe,QAAQ,WAAW;AAC9C,QAAI,QAAQ,iBAAkB,MAAK,OAAO,oBAAoB,QAAQ,gBAAgB;AACtF,QAAI,QAAQ,iBAAkB,MAAK,OAAO,oBAAoB,KAAK,UAAU,QAAQ,gBAAgB,CAAC;AACtG,QAAI,QAAQ,kBAAmB,MAAK,OAAO,qBAAqB,QAAQ,iBAAiB;AACzF,QAAI,QAAQ,kBAAmB,MAAK,OAAO,qBAAqB,QAAQ,iBAAiB;AACzF,SAAK,OAAO,YAAY,QAAQ,QAAQ;AACxC,QAAI,QAAQ,IAAK,MAAK,OAAO,OAAO,QAAQ,GAAG;AAC/C,QAAI,QAAQ,KAAM,MAAK,OAAO,QAAQ,KAAK,UAAU,QAAQ,IAAI,CAAC;AAClE,SAAK,OAAO,cAAc,UAAU;AACpC,WAAO;AAAA,EACT,OAAO;AACL,WAAO,KAAK,UAAU,OAAO;AAC7B,YAAQ,cAAc,IAAI;AAAA,EAC5B;AAEA,QAAM,MAAM,MAAM,eAAe,KAAK,EAAE,QAAQ,QAAQ,SAAS,KAAK,CAAC;AAEvE,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,MAAM,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,EAAE,OAAO,gBAAgB,EAAE;AACrE,UAAM,IAAI,MAAO,IAA2B,SAAS,QAAQ,IAAI,MAAM,EAAE;AAAA,EAC3E;AAEA,SAAO,IAAI,KAAK;AAClB;AAEA,eAAsB,aACpB,WACA,YACA,UACA,QACe;AACf,QAAM,OAAO,KAAK,UAAU,MAAM;AAClC,QAAM,UAAU,IAAI,YAAY,EAAE,OAAO,IAAI;AAC7C,QAAM,iBAAa,wBAAS,OAAO;AAEnC,QAAM,MAAM,GAAG,UAAU,QAAQ,OAAO,EAAE,CAAC,uBAAuB,QAAQ;AAC1E,QAAM,MAAM,KAAK;AAAA,IACf,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,WAAW;AAAA,EACnB,CAAC;AACH;;;AC9EA,IAAM,cAAc;AAQb,SAAS,yBAA2C;AACzD,QAAM,UAA0B,CAAC;AACjC,MAAI,SAAS;AAEb,QAAM,YAAY;AAAA,IAChB,KAAK,QAAQ,IAAI,KAAK,OAAO;AAAA,IAC7B,MAAM,QAAQ,KAAK,KAAK,OAAO;AAAA,IAC/B,OAAO,QAAQ,MAAM,KAAK,OAAO;AAAA,EACnC;AAEA,MAAI,cAAqC;AACzC,MAAI,gBAAoD;AAExD,WAAS,KAAK,OAA8B,MAAiB;AAC3D,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,CAAC,CAAE,EAAE,KAAK,GAAG;AAAA,IACjF,QAAQ;AACN,YAAM,OAAO,KAAK,CAAC,CAAC;AAAA,IACtB;AACA,QAAI,IAAI,SAAS,IAAK,OAAM,IAAI,MAAM,GAAG,GAAG,IAAI;AAChD,YAAQ,KAAK,EAAE,OAAO,MAAM,KAAK,WAAW,KAAK,IAAI,EAAE,CAAC;AACxD,QAAI,QAAQ,SAAS,YAAa,SAAQ,MAAM;AAAA,EAClD;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,OAAQ;AACZ,eAAS;AAET,cAAQ,MAAM,IAAI,SAAoB;AACpC,aAAK,OAAO,IAAI;AAChB,kBAAU,IAAI,GAAG,IAAI;AAAA,MACvB;AACA,cAAQ,OAAO,IAAI,SAAoB;AACrC,aAAK,QAAQ,IAAI;AACjB,kBAAU,KAAK,GAAG,IAAI;AAAA,MACxB;AACA,cAAQ,QAAQ,IAAI,SAAoB;AACtC,aAAK,SAAS,IAAI;AAClB,kBAAU,MAAM,GAAG,IAAI;AAAA,MACzB;AAEA,oBAAc,OAAO;AACrB,aAAO,UAAU,CAAC,KAAK,KAAK,MAAM,KAAK,QAAQ;AAC7C,aAAK,SAAS,CAAC,KAAK,WAAW,OAAO,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;AACpE,YAAI,OAAO,gBAAgB,WAAY,QAAO,YAAY,KAAK,KAAK,MAAM,KAAK,GAAG;AAClF,eAAO;AAAA,MACT;AAEA,sBAAgB,OAAO;AACvB,aAAO,uBAAuB,CAAC,UAAU;AACvC,cAAM,SAAS,MAAM,kBAAkB,QAAQ,MAAM,OAAO,UAAU,KAAK,UAAU,MAAM,MAAM;AACjG,aAAK,SAAS,CAAC,uBAAuB,MAAM,CAAC;AAC7C,YAAI,OAAO,kBAAkB,WAAY,eAAc,KAAK,QAAQ,KAAK;AAAA,MAC3E;AAAA,IACF;AAAA,IAEA,OAAO;AACL,UAAI,CAAC,OAAQ;AACb,eAAS;AACT,cAAQ,MAAM,UAAU;AACxB,cAAQ,OAAO,UAAU;AACzB,cAAQ,QAAQ,UAAU;AAC1B,aAAO,UAAU;AACjB,aAAO,uBAAuB;AAAA,IAChC;AAAA,IAEA,aAAa;AACX,aAAO,CAAC,GAAG,OAAO;AAAA,IACpB;AAAA,EACF;AACF;;;AChFO,SAAS,qBAAsC;AACpD,QAAM,KAAK,UAAU;AAGrB,MAAI,UAAU;AACd,QAAM,UAAU,GAAG,MAAM,eAAe;AACxC,QAAM,WAAW,GAAG,MAAM,gBAAgB;AAC1C,QAAM,QAAQ,GAAG,MAAM,YAAY;AACnC,QAAM,UAAU,GAAG,MAAM,gBAAgB;AACzC,QAAM,SAAS,GAAG,MAAM,YAAY;AAEpC,MAAI,QAAQ;AACV,cAAU,SAAS,OAAO,CAAC,CAAC;AAAA,EAC9B,WAAW,OAAO;AAChB,cAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC5B,WAAW,WAAW,CAAC,UAAU,KAAK,EAAE,GAAG;AACzC,cAAU,UAAU,QAAQ,CAAC,CAAC;AAAA,EAChC,WAAW,UAAU;AACnB,cAAU,WAAW,SAAS,CAAC,CAAC;AAAA,EAClC,WAAW,WAAW,WAAW,KAAK,EAAE,GAAG;AACzC,cAAU,UAAU,QAAQ,CAAC,CAAC;AAAA,EAChC;AAGA,MAAI,KAAK;AACT,QAAM,OAAO,GAAG,MAAM,uBAAuB;AAC7C,QAAM,OAAO,GAAG,MAAM,uBAAuB;AAC7C,QAAM,WAAW,GAAG,MAAM,eAAe;AACzC,QAAM,OAAO,GAAG,MAAM,wBAAwB;AAE9C,MAAI,MAAM;AACR,SAAK,SAAS,KAAK,CAAC,EAAE,QAAQ,KAAK,GAAG,CAAC;AAAA,EACzC,WAAW,MAAM;AACf,UAAM,SAAiC;AAAA,MACrC,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACT;AACA,SAAK,WAAW,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;AAAA,EAC5C,WAAW,UAAU;AACnB,SAAK,WAAW,SAAS,CAAC,CAAC;AAAA,EAC7B,WAAW,MAAM;AACf,SAAK,OAAO,KAAK,CAAC,EAAE,QAAQ,MAAM,GAAG,CAAC;AAAA,EACxC,WAAW,QAAQ,KAAK,EAAE,GAAG;AAC3B,SAAK;AAAA,EACP;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,UAAU,GAAG,OAAO,UAAU,IAAI,OAAO,WAAW;AAAA,IACpD,QAAQ,GAAG,OAAO,KAAK,IAAI,OAAO,MAAM;AAAA,IACxC,UAAU,UAAU;AAAA,IACpB,UAAU,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,IAClD,QAAQ,UAAU;AAAA,EACpB;AACF;;;ACzDA,IAAMA,eAAc;AAEpB,IAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,KAAa,OAA6B;AAC9D,MAAI;AACF,UAAM,OAAO,IAAI,IAAI,KAAK,SAAS,IAAI,EAAE;AACzC,UAAM,MAAM,CAAC,GAAG,eAAe,GAAG,KAAK;AACvC,WAAO,IAAI,KAAK,CAAC,MAAM,SAAS,KAAK,KAAK,SAAS,MAAM,CAAC,CAAC;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAAS,YAAY,KAAqB;AACxC,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI;AACpC,UAAM,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,QAAQ;AACrC,WAAO,KAAK,SAAS,MAAM,KAAK,MAAM,GAAG,GAAG,IAAI,WAAW;AAAA,EAC7D,QAAQ;AACN,WAAO,IAAI,SAAS,MAAM,IAAI,MAAM,GAAG,GAAG,IAAI,WAAW;AAAA,EAC3D;AACF;AAEO,SAAS,uBAAuB,oBAA8B,CAAC,GAAqB;AACzF,QAAM,UAA0B,CAAC;AACjC,QAAM,UAAU,IAAI,IAAI,iBAAiB;AACzC,MAAI,YAAwC;AAC5C,MAAI,cAA2D;AAC/D,MAAI,SAAS;AAEb,WAAS,KAAK,OAAqB;AACjC,YAAQ,KAAK,KAAK;AAClB,QAAI,QAAQ,SAASA,aAAa,SAAQ,MAAM;AAAA,EAClD;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,OAAQ;AACZ,eAAS;AAGT,kBAAY,OAAO;AACnB,aAAO,QAAQ,OAAO,OAA0B,SAA0C;AACxF,cAAM,UAAW,MAAM,UAAU,OAAkB,YAAY;AAC/D,cAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAQ,MAAkB;AACvG,cAAM,YAAY,KAAK,IAAI;AAC3B,cAAM,MAAM,MAAM,UAAW,KAAK,QAAQ,OAAO,IAAI;AACrD,YAAI,IAAI,UAAU,OAAO,CAAC,aAAa,KAAK,OAAO,GAAG;AACpD,eAAK;AAAA,YACH;AAAA,YACA,KAAK,YAAY,GAAG;AAAA,YACpB,QAAQ,IAAI;AAAA,YACZ,UAAU,KAAK,IAAI,IAAI;AAAA,YACvB,WAAW;AAAA,UACb,CAAC;AAAA,QACH;AACA,eAAO;AAAA,MACT;AAGA,oBAAc,eAAe,UAAU;AACvC,qBAAe,UAAU,OAAO,SAC9B,QACA,KACA,OACA,UACA,UACA;AACA,cAAM,YAAY,KAAK,IAAI;AAC3B,cAAM,SAAS,OAAO,QAAQ,WAAW,MAAO,IAAY;AAE5D,aAAK,iBAAiB,QAAQ,MAAM;AAClC,cAAI,KAAK,UAAU,OAAO,CAAC,aAAa,QAAQ,OAAO,GAAG;AACxD,iBAAK;AAAA,cACH,QAAQ,OAAO,YAAY;AAAA,cAC3B,KAAK,YAAY,MAAM;AAAA,cACvB,QAAQ,KAAK;AAAA,cACb,UAAU,KAAK,IAAI,IAAI;AAAA,cACvB,WAAW;AAAA,YACb,CAAC;AAAA,UACH;AAAA,QACF,CAAC;AAED,eAAO,YAAa,MAAM,MAAM,CAAC,QAAQ,KAAK,SAAS,MAAM,UAAU,QAAQ,CAE9E;AAAA,MACH;AAAA,IACF;AAAA,IAEA,OAAO;AACL,UAAI,CAAC,OAAQ;AACb,eAAS;AACT,UAAI,UAAW,QAAO,QAAQ;AAC9B,UAAI,YAAa,gBAAe,UAAU,OAAO;AAAA,IACnD;AAAA,IAEA,aAAa;AACX,aAAO,CAAC,GAAG,OAAO;AAAA,IACpB;AAAA,EACF;AACF;;;ACnGO,SAAS,2BAA2B,MAMlB;AACvB,QAAM,YAAY,MAAM,sBAAsB;AAC9C,QAAMC,UAAS,MAAM,mBAAmB;AACxC,QAAM,iBAAiB,MAAM,sBAAsB;AACnD,QAAM,cAAc,MAAM,mBAAmB;AAC7C,QAAM,oBAAoB,MAAM,oBAAoB;AAEpD,QAAM,SAA6B,CAAC;AACpC,QAAMC,aAAY,oBAAI,IAAuC;AAC7D,QAAM,eAA+D,CAAC;AACtE,QAAM,cAAc,oBAAI,IAAkD;AAC1E,MAAI,eAAiD;AACrD,MAAI,mBAAgD;AAEpD,WAASC,MAAK,OAAyB;AACrC,WAAO,KAAK,KAAK;AACjB,QAAI,OAAO,SAAS,GAAI,QAAO,MAAM;AACrC,eAAW,MAAMD,WAAW,IAAG,KAAK;AAAA,EACtC;AAEA,WAAS,eAAe,IAAqB;AAC3C,QAAI,GAAG,GAAI,QAAO,IAAI,GAAG,EAAE;AAC3B,UAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,UAAM,MAAM,CAAC,GAAG,GAAG,SAAS,EAAE,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG;AAClD,QAAI,IAAK,QAAO,GAAG,GAAG,IAAI,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,IAA0B;AAC/C,UAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,QAAI,CAAC,KAAK,UAAU,SAAS,UAAU,YAAY,SAAS,SAAS,EAAE,SAAS,GAAG,EAAG,QAAO;AAC7F,QAAI,GAAG,aAAa,MAAM,MAAM,YAAY,GAAG,aAAa,UAAU,EAAG,QAAO;AAChF,QAAI,GAAG,WAAW,GAAG,aAAa,SAAS,EAAG,QAAO;AACrD,QAAI,GAAG,QAAQ,qCAAqC,EAAG,QAAO;AAC9D,QAAI;AAAE,UAAI,iBAAiB,EAAE,EAAE,WAAW,UAAW,QAAO;AAAA,IAAM,QAAQ;AAAA,IAAC;AAC3E,WAAO;AAAA,EACT;AAEA,WAAS,YAAY,GAAe;AAClC,UAAM,SAAS,EAAE;AACjB,QAAI,CAAC,OAAQ;AACb,UAAM,MAAM,KAAK,IAAI;AAGrB,iBAAa,KAAK,EAAE,QAAQ,MAAM,IAAI,CAAC;AAEvC,WAAO,aAAa,SAAS,KAAK,MAAM,aAAa,CAAC,EAAE,OAAO,KAAM;AACnE,mBAAa,MAAM;AAAA,IACrB;AACA,UAAM,eAAe,aAAa;AAAA,MAChC,CAAC,MAAM,EAAE,WAAW,UAAU,MAAM,EAAE,OAAOD;AAAA,IAC/C;AACA,QAAI,aAAa,UAAU,WAAW;AACpC,MAAAE,MAAK;AAAA,QACH,MAAM;AAAA,QACN,WAAW;AAAA,QACX,QAAQ,eAAe,MAAM;AAAA,QAC7B,SAAS,GAAG,aAAa,MAAM,cAAc,MAAM,aAAa,CAAC,EAAE,IAAI;AAAA,QACvE,KAAK,WAAW,UAAU,QAAQ;AAAA,MACpC,CAAC;AACD,mBAAa,SAAS;AAAA,IACxB;AAGA,QAAI,qBAAqB,CAAC,cAAc,MAAM,GAAG;AAC/C,MAAAA,MAAK;AAAA,QACH,MAAM;AAAA,QACN,WAAW;AAAA,QACX,QAAQ,eAAe,MAAM;AAAA,QAC7B,SAAS,6BAA6B,OAAO,QAAQ,YAAY,CAAC;AAAA,QAClE,KAAK,WAAW,UAAU,QAAQ;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AAEA,WAAS,oBAAoB;AAC3B,uBAAmB,QAAQ;AAC3B,YAAQ,QAAQ,IAAI,SAAoB;AACtC,wBAAkB,MAAM,SAAS,IAAI;AAErC,YAAM,MAAM,OAAO,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,GAAG;AACxC,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,WAAW,YAAY,IAAI,GAAG;AACpC,UAAI,YAAY,MAAM,SAAS,YAAY,aAAa;AACtD,iBAAS;AACT,YAAI,SAAS,SAAS,gBAAgB;AACpC,UAAAA,MAAK;AAAA,YACH,MAAM;AAAA,YACN,WAAW;AAAA,YACX,QAAQ;AAAA,YACR,SAAS,cAAc,SAAS,KAAK,QAAQ,KAAK,OAAO,MAAM,SAAS,aAAa,GAAI,CAAC,MAAM,GAAG;AAAA,YACnG,KAAK,WAAW,UAAU,QAAQ;AAAA,UACpC,CAAC;AACD,sBAAY,OAAO,GAAG;AAAA,QACxB;AAAA,MACF,OAAO;AACL,oBAAY,IAAI,KAAK,EAAE,OAAO,GAAG,WAAW,IAAI,CAAC;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,qBAAe;AACf,eAAS,iBAAiB,SAAS,cAAc,IAAI;AACrD,wBAAkB;AAAA,IACpB;AAAA,IACA,OAAO;AACL,UAAI,aAAc,UAAS,oBAAoB,SAAS,cAAc,IAAI;AAC1E,UAAI,iBAAkB,SAAQ,QAAQ;AACtC,mBAAa,SAAS;AACtB,kBAAY,MAAM;AAAA,IACpB;AAAA,IACA,YAAY;AAAE,aAAO,CAAC,GAAG,MAAM;AAAA,IAAG;AAAA,IAClC,cAAc,UAAU;AACtB,MAAAD,WAAU,IAAI,QAAQ;AACtB,aAAO,MAAMA,WAAU,OAAO,QAAQ;AAAA,IACxC;AAAA,EACF;AACF;;;ACrIA,IAAI,QAAoB,EAAE,MAAM,QAAW,eAAe,OAAU;AACpE,IAAM,YAAY,oBAAI,IAAgB;AAEtC,SAAS,OAAO;AACd,aAAW,KAAK,UAAW,GAAE;AAC/B;AAEO,SAAS,UAAU,UAAsB;AAC9C,YAAU,IAAI,QAAQ;AACtB,SAAO,MAAM,UAAU,OAAO,QAAQ;AACxC;AAEO,SAAS,cAA0B;AACxC,SAAO;AACT;AAEO,IAAM,QAAQ;AAAA,EACnB,QAAQ,MAAwB;AAC9B,YAAQ,EAAE,GAAG,OAAO,MAAM,QAAQ,OAAU;AAC5C,SAAK;AAAA,EACP;AAAA,EACA,iBAAiB,KAAqC;AACpD,YAAQ,EAAE,GAAG,OAAO,eAAe,OAAO,OAAU;AACpD,SAAK;AAAA,EACP;AACF;;;ACjBA,IAAM,QAAuB;AAAA,EAC3B,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,MAAM;AAAA,EACN,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAClB;AAEA,IAAM,OAAsB;AAAA,EAC1B,YAAY;AAAA,EACZ,qBAAqB;AAAA,EACrB,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,MAAM;AAAA,EACN,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,gBAAgB;AAClB;AAEO,SAAS,aAAa,OAA6B;AACxD,MAAI,UAAU,OAAQ,QAAO;AAC7B,MAAI,UAAU,QAAS,QAAO;AAE9B,QAAM,WAAW;AACjB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY,SAAS,cAAc,MAAM;AAAA,IACzC,QAAQ,SAAS,UAAU,MAAM;AAAA,IACjC,aAAa,SAAS,UAAU,MAAM;AAAA,IACtC,MAAM,SAAS,QAAQ,MAAM;AAAA,IAC7B,QAAQ,SAAS,UAAU,MAAM;AAAA,EACnC;AACF;","names":["MAX_ENTRIES","window","listeners","emit"]}
@@ -0,0 +1,69 @@
1
+ import { ReportPayload, ReportResult, ConsoleEntry, EnvironmentInfo, NetworkEntry, FlintUser, Theme } from '@flint/types';
2
+ export { ConsoleEntry, EnvironmentInfo, FlintUser, FlintWidgetProps, NetworkEntry, ReportPayload, ReportResult, Severity, Theme, ThemeOverride } from '@flint/types';
3
+
4
+ declare function submitReport(serverUrl: string, projectKey: string, payload: ReportPayload, screenshot?: File): Promise<ReportResult>;
5
+ declare function submitReplay(serverUrl: string, projectKey: string, reportId: string, events: unknown[]): Promise<void>;
6
+
7
+ interface ConsoleCollector {
8
+ start(): void;
9
+ stop(): void;
10
+ getEntries(): ConsoleEntry[];
11
+ }
12
+ declare function createConsoleCollector(): ConsoleCollector;
13
+
14
+ declare function collectEnvironment(): EnvironmentInfo;
15
+
16
+ interface NetworkCollector {
17
+ start(): void;
18
+ stop(): void;
19
+ getEntries(): NetworkEntry[];
20
+ }
21
+ declare function createNetworkCollector(extraBlockedHosts?: string[]): NetworkCollector;
22
+
23
+ interface FrustrationEvent {
24
+ type: "rage_click" | "dead_click" | "error_loop";
25
+ timestamp: number;
26
+ target: string;
27
+ details: string;
28
+ url: string;
29
+ }
30
+ interface FrustrationCollector {
31
+ start(): void;
32
+ stop(): void;
33
+ getEvents(): FrustrationEvent[];
34
+ onFrustration(callback: (event: FrustrationEvent) => void): () => void;
35
+ }
36
+ declare function createFrustrationCollector(opts?: {
37
+ rageClickThreshold?: number;
38
+ rageClickWindow?: number;
39
+ errorLoopThreshold?: number;
40
+ errorLoopWindow?: number;
41
+ enableDeadClicks?: boolean;
42
+ }): FrustrationCollector;
43
+
44
+ interface FlintState {
45
+ user: FlintUser | undefined;
46
+ sessionReplay: string | (() => string) | undefined;
47
+ }
48
+ declare function subscribe(listener: () => void): () => boolean;
49
+ declare function getSnapshot(): FlintState;
50
+ declare const flint: {
51
+ setUser(user: FlintUser | null): void;
52
+ setSessionReplay(url: string | (() => string) | null): void;
53
+ };
54
+
55
+ interface ResolvedTheme {
56
+ background: string;
57
+ backgroundSecondary: string;
58
+ accent: string;
59
+ accentHover: string;
60
+ text: string;
61
+ textMuted: string;
62
+ border: string;
63
+ shadow: string;
64
+ buttonText: string;
65
+ backdropFilter: string;
66
+ }
67
+ declare function resolveTheme(theme: Theme): ResolvedTheme;
68
+
69
+ export { type ConsoleCollector, type FlintState, type FrustrationCollector, type FrustrationEvent, type NetworkCollector, type ResolvedTheme, collectEnvironment, createConsoleCollector, createFrustrationCollector, createNetworkCollector, flint, getSnapshot, resolveTheme, submitReplay, submitReport, subscribe };
@@ -0,0 +1,69 @@
1
+ import { ReportPayload, ReportResult, ConsoleEntry, EnvironmentInfo, NetworkEntry, FlintUser, Theme } from '@flint/types';
2
+ export { ConsoleEntry, EnvironmentInfo, FlintUser, FlintWidgetProps, NetworkEntry, ReportPayload, ReportResult, Severity, Theme, ThemeOverride } from '@flint/types';
3
+
4
+ declare function submitReport(serverUrl: string, projectKey: string, payload: ReportPayload, screenshot?: File): Promise<ReportResult>;
5
+ declare function submitReplay(serverUrl: string, projectKey: string, reportId: string, events: unknown[]): Promise<void>;
6
+
7
+ interface ConsoleCollector {
8
+ start(): void;
9
+ stop(): void;
10
+ getEntries(): ConsoleEntry[];
11
+ }
12
+ declare function createConsoleCollector(): ConsoleCollector;
13
+
14
+ declare function collectEnvironment(): EnvironmentInfo;
15
+
16
+ interface NetworkCollector {
17
+ start(): void;
18
+ stop(): void;
19
+ getEntries(): NetworkEntry[];
20
+ }
21
+ declare function createNetworkCollector(extraBlockedHosts?: string[]): NetworkCollector;
22
+
23
+ interface FrustrationEvent {
24
+ type: "rage_click" | "dead_click" | "error_loop";
25
+ timestamp: number;
26
+ target: string;
27
+ details: string;
28
+ url: string;
29
+ }
30
+ interface FrustrationCollector {
31
+ start(): void;
32
+ stop(): void;
33
+ getEvents(): FrustrationEvent[];
34
+ onFrustration(callback: (event: FrustrationEvent) => void): () => void;
35
+ }
36
+ declare function createFrustrationCollector(opts?: {
37
+ rageClickThreshold?: number;
38
+ rageClickWindow?: number;
39
+ errorLoopThreshold?: number;
40
+ errorLoopWindow?: number;
41
+ enableDeadClicks?: boolean;
42
+ }): FrustrationCollector;
43
+
44
+ interface FlintState {
45
+ user: FlintUser | undefined;
46
+ sessionReplay: string | (() => string) | undefined;
47
+ }
48
+ declare function subscribe(listener: () => void): () => boolean;
49
+ declare function getSnapshot(): FlintState;
50
+ declare const flint: {
51
+ setUser(user: FlintUser | null): void;
52
+ setSessionReplay(url: string | (() => string) | null): void;
53
+ };
54
+
55
+ interface ResolvedTheme {
56
+ background: string;
57
+ backgroundSecondary: string;
58
+ accent: string;
59
+ accentHover: string;
60
+ text: string;
61
+ textMuted: string;
62
+ border: string;
63
+ shadow: string;
64
+ buttonText: string;
65
+ backdropFilter: string;
66
+ }
67
+ declare function resolveTheme(theme: Theme): ResolvedTheme;
68
+
69
+ export { type ConsoleCollector, type FlintState, type FrustrationCollector, type FrustrationEvent, type NetworkCollector, type ResolvedTheme, collectEnvironment, createConsoleCollector, createFrustrationCollector, createNetworkCollector, flint, getSnapshot, resolveTheme, submitReplay, submitReport, subscribe };
package/dist/index.js ADDED
@@ -0,0 +1,458 @@
1
+ // src/api.ts
2
+ import { gzipSync } from "fflate";
3
+ async function fetchWithRetry(url, init, retries = 3, baseDelay = 1e3) {
4
+ for (let attempt = 0; attempt <= retries; attempt++) {
5
+ try {
6
+ const res = await fetch(url, init);
7
+ if (res.ok || attempt === retries) return res;
8
+ if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;
9
+ } catch (err) {
10
+ if (attempt === retries) throw err;
11
+ }
12
+ await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
13
+ }
14
+ throw new Error("Max retries exceeded");
15
+ }
16
+ async function submitReport(serverUrl, projectKey, payload, screenshot) {
17
+ const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
18
+ let body;
19
+ const headers = {
20
+ "x-project-key": projectKey
21
+ };
22
+ if (screenshot) {
23
+ const form = new FormData();
24
+ form.append("reporterId", payload.reporterId);
25
+ form.append("reporterName", payload.reporterName);
26
+ if (payload.reporterEmail) form.append("reporterEmail", payload.reporterEmail);
27
+ form.append("description", payload.description);
28
+ if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
29
+ if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
30
+ if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
31
+ if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
32
+ form.append("severity", payload.severity);
33
+ if (payload.url) form.append("url", payload.url);
34
+ if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
35
+ form.append("screenshot", screenshot);
36
+ body = form;
37
+ } else {
38
+ body = JSON.stringify(payload);
39
+ headers["Content-Type"] = "application/json";
40
+ }
41
+ const res = await fetchWithRetry(url, { method: "POST", headers, body });
42
+ if (!res.ok) {
43
+ const err = await res.json().catch(() => ({ error: "Unknown error" }));
44
+ throw new Error(err.error ?? `HTTP ${res.status}`);
45
+ }
46
+ return res.json();
47
+ }
48
+ async function submitReplay(serverUrl, projectKey, reportId, events) {
49
+ const json = JSON.stringify(events);
50
+ const encoded = new TextEncoder().encode(json);
51
+ const compressed = gzipSync(encoded);
52
+ const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
53
+ await fetch(url, {
54
+ method: "POST",
55
+ headers: {
56
+ "x-project-key": projectKey,
57
+ "Content-Type": "application/octet-stream"
58
+ },
59
+ body: compressed.buffer
60
+ });
61
+ }
62
+
63
+ // src/collectors/console.ts
64
+ var MAX_ENTRIES = 50;
65
+ function createConsoleCollector() {
66
+ const entries = [];
67
+ let active = false;
68
+ const originals = {
69
+ log: console.log.bind(console),
70
+ warn: console.warn.bind(console),
71
+ error: console.error.bind(console)
72
+ };
73
+ let origOnerror = null;
74
+ let origUnhandled = null;
75
+ function push(level, args) {
76
+ let str;
77
+ try {
78
+ str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
79
+ } catch {
80
+ str = String(args[0]);
81
+ }
82
+ if (str.length > 500) str = str.slice(0, 500) + "\u2026";
83
+ entries.push({ level, args: str, timestamp: Date.now() });
84
+ if (entries.length > MAX_ENTRIES) entries.shift();
85
+ }
86
+ return {
87
+ start() {
88
+ if (active) return;
89
+ active = true;
90
+ console.log = (...args) => {
91
+ push("log", args);
92
+ originals.log(...args);
93
+ };
94
+ console.warn = (...args) => {
95
+ push("warn", args);
96
+ originals.warn(...args);
97
+ };
98
+ console.error = (...args) => {
99
+ push("error", args);
100
+ originals.error(...args);
101
+ };
102
+ origOnerror = window.onerror;
103
+ window.onerror = (msg, src, line, col, err) => {
104
+ push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
105
+ if (typeof origOnerror === "function") return origOnerror(msg, src, line, col, err);
106
+ return false;
107
+ };
108
+ origUnhandled = window.onunhandledrejection;
109
+ window.onunhandledrejection = (event) => {
110
+ const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
111
+ push("error", ["UnhandledRejection:", reason]);
112
+ if (typeof origUnhandled === "function") origUnhandled.call(window, event);
113
+ };
114
+ },
115
+ stop() {
116
+ if (!active) return;
117
+ active = false;
118
+ console.log = originals.log;
119
+ console.warn = originals.warn;
120
+ console.error = originals.error;
121
+ window.onerror = origOnerror;
122
+ window.onunhandledrejection = origUnhandled;
123
+ },
124
+ getEntries() {
125
+ return [...entries];
126
+ }
127
+ };
128
+ }
129
+
130
+ // src/collectors/environment.ts
131
+ function collectEnvironment() {
132
+ const ua = navigator.userAgent;
133
+ let browser = "Unknown";
134
+ const chromeM = ua.match(/Chrome\/(\d+)/);
135
+ const firefoxM = ua.match(/Firefox\/(\d+)/);
136
+ const edgeM = ua.match(/Edg\/(\d+)/);
137
+ const safariM = ua.match(/Version\/(\d+)/);
138
+ const operaM = ua.match(/OPR\/(\d+)/);
139
+ if (operaM) {
140
+ browser = `Opera ${operaM[1]}`;
141
+ } else if (edgeM) {
142
+ browser = `Edge ${edgeM[1]}`;
143
+ } else if (chromeM && !/Edg|OPR/.test(ua)) {
144
+ browser = `Chrome ${chromeM[1]}`;
145
+ } else if (firefoxM) {
146
+ browser = `Firefox ${firefoxM[1]}`;
147
+ } else if (safariM && /Safari\//.test(ua)) {
148
+ browser = `Safari ${safariM[1]}`;
149
+ }
150
+ let os = "Unknown";
151
+ const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
152
+ const winM = ua.match(/Windows NT (\d+\.\d+)/);
153
+ const androidM = ua.match(/Android (\d+)/);
154
+ const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
155
+ if (macM) {
156
+ os = `macOS ${macM[1].replace("_", ".")}`;
157
+ } else if (winM) {
158
+ const winMap = {
159
+ "10.0": "10/11",
160
+ "6.3": "8.1",
161
+ "6.2": "8",
162
+ "6.1": "7"
163
+ };
164
+ os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
165
+ } else if (androidM) {
166
+ os = `Android ${androidM[1]}`;
167
+ } else if (iosM) {
168
+ os = `iOS ${iosM[1].replace(/_/g, ".")}`;
169
+ } else if (/Linux/.test(ua)) {
170
+ os = "Linux";
171
+ }
172
+ return {
173
+ browser,
174
+ os,
175
+ viewport: `${window.innerWidth}x${window.innerHeight}`,
176
+ screen: `${screen.width}x${screen.height}`,
177
+ language: navigator.language,
178
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
179
+ online: navigator.onLine
180
+ };
181
+ }
182
+
183
+ // src/collectors/network.ts
184
+ var MAX_ENTRIES2 = 50;
185
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
186
+ "browser-intake-datadoghq.com",
187
+ "rum.browser-intake-datadoghq.com",
188
+ "logs.browser-intake-datadoghq.com",
189
+ "session-replay.browser-intake-datadoghq.com"
190
+ ]);
191
+ function isBlockedUrl(url, extra) {
192
+ try {
193
+ const host = new URL(url, location.href).hostname;
194
+ const all = [...BLOCKED_HOSTS, ...extra];
195
+ return all.some((b) => host === b || host.endsWith("." + b));
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+ function truncateUrl(url) {
201
+ try {
202
+ const u = new URL(url, location.href);
203
+ const base = `${u.origin}${u.pathname}`;
204
+ return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
205
+ } catch {
206
+ return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
207
+ }
208
+ }
209
+ function createNetworkCollector(extraBlockedHosts = []) {
210
+ const entries = [];
211
+ const blocked = new Set(extraBlockedHosts);
212
+ let origFetch = null;
213
+ let origXHROpen = null;
214
+ let active = false;
215
+ function push(entry) {
216
+ entries.push(entry);
217
+ if (entries.length > MAX_ENTRIES2) entries.shift();
218
+ }
219
+ return {
220
+ start() {
221
+ if (active) return;
222
+ active = true;
223
+ origFetch = window.fetch;
224
+ window.fetch = async (input, init) => {
225
+ const method = (init?.method ?? "GET").toUpperCase();
226
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
227
+ const startTime = Date.now();
228
+ const res = await origFetch.call(window, input, init);
229
+ if (res.status >= 400 && !isBlockedUrl(url, blocked)) {
230
+ push({
231
+ method,
232
+ url: truncateUrl(url),
233
+ status: res.status,
234
+ duration: Date.now() - startTime,
235
+ timestamp: startTime
236
+ });
237
+ }
238
+ return res;
239
+ };
240
+ origXHROpen = XMLHttpRequest.prototype.open;
241
+ XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
242
+ const startTime = Date.now();
243
+ const urlStr = typeof url === "string" ? url : url.href;
244
+ this.addEventListener("load", () => {
245
+ if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {
246
+ push({
247
+ method: method.toUpperCase(),
248
+ url: truncateUrl(urlStr),
249
+ status: this.status,
250
+ duration: Date.now() - startTime,
251
+ timestamp: startTime
252
+ });
253
+ }
254
+ });
255
+ return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
256
+ };
257
+ },
258
+ stop() {
259
+ if (!active) return;
260
+ active = false;
261
+ if (origFetch) window.fetch = origFetch;
262
+ if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
263
+ },
264
+ getEntries() {
265
+ return [...entries];
266
+ }
267
+ };
268
+ }
269
+
270
+ // src/collectors/frustration.ts
271
+ function createFrustrationCollector(opts) {
272
+ const threshold = opts?.rageClickThreshold ?? 3;
273
+ const window2 = opts?.rageClickWindow ?? 500;
274
+ const errorThreshold = opts?.errorLoopThreshold ?? 3;
275
+ const errorWindow = opts?.errorLoopWindow ?? 3e4;
276
+ const deadClicksEnabled = opts?.enableDeadClicks ?? true;
277
+ const events = [];
278
+ const listeners2 = /* @__PURE__ */ new Set();
279
+ const clickHistory = [];
280
+ const errorCounts = /* @__PURE__ */ new Map();
281
+ let clickHandler = null;
282
+ let origConsoleError = null;
283
+ function emit2(event) {
284
+ events.push(event);
285
+ if (events.length > 50) events.shift();
286
+ for (const cb of listeners2) cb(event);
287
+ }
288
+ function getCSSSelector(el) {
289
+ if (el.id) return `#${el.id}`;
290
+ const tag = el.tagName.toLowerCase();
291
+ const cls = [...el.classList].slice(0, 3).join(".");
292
+ if (cls) return `${tag}.${cls}`;
293
+ return tag;
294
+ }
295
+ function isInteractive(el) {
296
+ const tag = el.tagName.toLowerCase();
297
+ if (["a", "button", "input", "select", "textarea", "label", "summary"].includes(tag)) return true;
298
+ if (el.getAttribute("role") === "button" || el.getAttribute("tabindex")) return true;
299
+ if (el.onclick || el.getAttribute("onclick")) return true;
300
+ if (el.closest("a, button, [role=button], [onclick]")) return true;
301
+ try {
302
+ if (getComputedStyle(el).cursor === "pointer") return true;
303
+ } catch {
304
+ }
305
+ return false;
306
+ }
307
+ function handleClick(e) {
308
+ const target = e.target;
309
+ if (!target) return;
310
+ const now = Date.now();
311
+ clickHistory.push({ target, time: now });
312
+ while (clickHistory.length > 0 && now - clickHistory[0].time > 1e3) {
313
+ clickHistory.shift();
314
+ }
315
+ const recentOnSame = clickHistory.filter(
316
+ (c) => c.target === target && now - c.time < window2
317
+ );
318
+ if (recentOnSame.length >= threshold) {
319
+ emit2({
320
+ type: "rage_click",
321
+ timestamp: now,
322
+ target: getCSSSelector(target),
323
+ details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,
324
+ url: globalThis.location?.href ?? ""
325
+ });
326
+ clickHistory.length = 0;
327
+ }
328
+ if (deadClicksEnabled && !isInteractive(target)) {
329
+ emit2({
330
+ type: "dead_click",
331
+ timestamp: now,
332
+ target: getCSSSelector(target),
333
+ details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,
334
+ url: globalThis.location?.href ?? ""
335
+ });
336
+ }
337
+ }
338
+ function patchConsoleError() {
339
+ origConsoleError = console.error;
340
+ console.error = (...args) => {
341
+ origConsoleError?.apply(console, args);
342
+ const key = String(args[0]).slice(0, 100);
343
+ const now = Date.now();
344
+ const existing = errorCounts.get(key);
345
+ if (existing && now - existing.firstSeen < errorWindow) {
346
+ existing.count++;
347
+ if (existing.count >= errorThreshold) {
348
+ emit2({
349
+ type: "error_loop",
350
+ timestamp: now,
351
+ target: "console",
352
+ details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1e3)}s: ${key}`,
353
+ url: globalThis.location?.href ?? ""
354
+ });
355
+ errorCounts.delete(key);
356
+ }
357
+ } else {
358
+ errorCounts.set(key, { count: 1, firstSeen: now });
359
+ }
360
+ };
361
+ }
362
+ return {
363
+ start() {
364
+ clickHandler = handleClick;
365
+ document.addEventListener("click", clickHandler, true);
366
+ patchConsoleError();
367
+ },
368
+ stop() {
369
+ if (clickHandler) document.removeEventListener("click", clickHandler, true);
370
+ if (origConsoleError) console.error = origConsoleError;
371
+ clickHistory.length = 0;
372
+ errorCounts.clear();
373
+ },
374
+ getEvents() {
375
+ return [...events];
376
+ },
377
+ onFrustration(callback) {
378
+ listeners2.add(callback);
379
+ return () => listeners2.delete(callback);
380
+ }
381
+ };
382
+ }
383
+
384
+ // src/store.ts
385
+ var state = { user: void 0, sessionReplay: void 0 };
386
+ var listeners = /* @__PURE__ */ new Set();
387
+ function emit() {
388
+ for (const l of listeners) l();
389
+ }
390
+ function subscribe(listener) {
391
+ listeners.add(listener);
392
+ return () => listeners.delete(listener);
393
+ }
394
+ function getSnapshot() {
395
+ return state;
396
+ }
397
+ var flint = {
398
+ setUser(user) {
399
+ state = { ...state, user: user ?? void 0 };
400
+ emit();
401
+ },
402
+ setSessionReplay(url) {
403
+ state = { ...state, sessionReplay: url ?? void 0 };
404
+ emit();
405
+ }
406
+ };
407
+
408
+ // src/theme.ts
409
+ var light = {
410
+ background: "rgba(255,255,255,0.90)",
411
+ backgroundSecondary: "rgba(249,250,251,0.75)",
412
+ accent: "#2563eb",
413
+ accentHover: "#1d4ed8",
414
+ text: "#111827",
415
+ textMuted: "#6b7280",
416
+ border: "rgba(255,255,255,0.9)",
417
+ shadow: "0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)",
418
+ buttonText: "#ffffff",
419
+ backdropFilter: "blur(32px) saturate(1.8)"
420
+ };
421
+ var dark = {
422
+ background: "rgba(15,20,35,0.88)",
423
+ backgroundSecondary: "rgba(5,8,18,0.65)",
424
+ accent: "#4d8aff",
425
+ accentHover: "#3b6fdb",
426
+ text: "#dde3ef",
427
+ textMuted: "#6b7a93",
428
+ border: "rgba(255,255,255,0.08)",
429
+ shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
430
+ buttonText: "#ffffff",
431
+ backdropFilter: "blur(32px) saturate(1.6)"
432
+ };
433
+ function resolveTheme(theme) {
434
+ if (theme === "dark") return dark;
435
+ if (theme === "light") return light;
436
+ const override = theme;
437
+ return {
438
+ ...light,
439
+ background: override.background ?? light.background,
440
+ accent: override.accent ?? light.accent,
441
+ accentHover: override.accent ?? light.accentHover,
442
+ text: override.text ?? light.text,
443
+ border: override.border ?? light.border
444
+ };
445
+ }
446
+ export {
447
+ collectEnvironment,
448
+ createConsoleCollector,
449
+ createFrustrationCollector,
450
+ createNetworkCollector,
451
+ flint,
452
+ getSnapshot,
453
+ resolveTheme,
454
+ submitReplay,
455
+ submitReport,
456
+ subscribe
457
+ };
458
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/api.ts","../src/collectors/console.ts","../src/collectors/environment.ts","../src/collectors/network.ts","../src/collectors/frustration.ts","../src/store.ts","../src/theme.ts"],"sourcesContent":["import type { ReportPayload, ReportResult } from \"@flint/types\";\nimport { gzipSync } from \"fflate\";\n\nasync function fetchWithRetry(url: string, init: RequestInit, retries = 3, baseDelay = 1000): Promise<Response> {\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const res = await fetch(url, init);\n if (res.ok || attempt === retries) return res;\n // Don't retry client errors (4xx) except 429\n if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;\n } catch (err) {\n if (attempt === retries) throw err;\n }\n await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));\n }\n throw new Error(\"Max retries exceeded\");\n}\n\nexport async function submitReport(\n serverUrl: string,\n projectKey: string,\n payload: ReportPayload,\n screenshot?: File,\n): Promise<ReportResult> {\n const url = `${serverUrl.replace(/\\/$/, \"\")}/api/v1/bug-reports`;\n\n let body: BodyInit;\n const headers: Record<string, string> = {\n \"x-project-key\": projectKey,\n };\n\n if (screenshot) {\n const form = new FormData();\n form.append(\"reporterId\", payload.reporterId);\n form.append(\"reporterName\", payload.reporterName);\n if (payload.reporterEmail) form.append(\"reporterEmail\", payload.reporterEmail);\n form.append(\"description\", payload.description);\n if (payload.expectedBehavior) form.append(\"expectedBehavior\", payload.expectedBehavior);\n if (payload.stepsToReproduce) form.append(\"stepsToReproduce\", JSON.stringify(payload.stepsToReproduce));\n if (payload.externalReplayUrl) form.append(\"externalReplayUrl\", payload.externalReplayUrl);\n if (payload.additionalContext) form.append(\"additionalContext\", payload.additionalContext);\n form.append(\"severity\", payload.severity);\n if (payload.url) form.append(\"url\", payload.url);\n if (payload.meta) form.append(\"meta\", JSON.stringify(payload.meta));\n form.append(\"screenshot\", screenshot);\n body = form;\n } else {\n body = JSON.stringify(payload);\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const res = await fetchWithRetry(url, { method: \"POST\", headers, body });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({ error: \"Unknown error\" }));\n throw new Error((err as { error?: string }).error ?? `HTTP ${res.status}`);\n }\n\n return res.json() as Promise<ReportResult>;\n}\n\nexport async function submitReplay(\n serverUrl: string,\n projectKey: string,\n reportId: string,\n events: unknown[],\n): Promise<void> {\n const json = JSON.stringify(events);\n const encoded = new TextEncoder().encode(json);\n const compressed = gzipSync(encoded);\n\n const url = `${serverUrl.replace(/\\/$/, \"\")}/api/v1/bug-reports/${reportId}/replay`;\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"x-project-key\": projectKey,\n \"Content-Type\": \"application/octet-stream\",\n },\n body: compressed.buffer as ArrayBuffer,\n });\n}\n","import type { ConsoleEntry } from \"@flint/types\";\n\nconst MAX_ENTRIES = 50;\n\nexport interface ConsoleCollector {\n start(): void;\n stop(): void;\n getEntries(): ConsoleEntry[];\n}\n\nexport function createConsoleCollector(): ConsoleCollector {\n const entries: ConsoleEntry[] = [];\n let active = false;\n\n const originals = {\n log: console.log.bind(console),\n warn: console.warn.bind(console),\n error: console.error.bind(console),\n };\n\n let origOnerror: typeof window.onerror = null;\n let origUnhandled: typeof window.onunhandledrejection = null;\n\n function push(level: ConsoleEntry[\"level\"], args: unknown[]) {\n let str: string;\n try {\n str = args.map((a) => (typeof a === \"string\" ? a : JSON.stringify(a))).join(\" \");\n } catch {\n str = String(args[0]);\n }\n if (str.length > 500) str = str.slice(0, 500) + \"\\u2026\";\n entries.push({ level, args: str, timestamp: Date.now() });\n if (entries.length > MAX_ENTRIES) entries.shift();\n }\n\n return {\n start() {\n if (active) return;\n active = true;\n\n console.log = (...args: unknown[]) => {\n push(\"log\", args);\n originals.log(...args);\n };\n console.warn = (...args: unknown[]) => {\n push(\"warn\", args);\n originals.warn(...args);\n };\n console.error = (...args: unknown[]) => {\n push(\"error\", args);\n originals.error(...args);\n };\n\n origOnerror = window.onerror;\n window.onerror = (msg, src, line, col, err) => {\n push(\"error\", [err?.message ?? String(msg), `${src}:${line}:${col}`]);\n if (typeof origOnerror === \"function\") return origOnerror(msg, src, line, col, err);\n return false;\n };\n\n origUnhandled = window.onunhandledrejection;\n window.onunhandledrejection = (event) => {\n const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);\n push(\"error\", [\"UnhandledRejection:\", reason]);\n if (typeof origUnhandled === \"function\") origUnhandled.call(window, event);\n };\n },\n\n stop() {\n if (!active) return;\n active = false;\n console.log = originals.log;\n console.warn = originals.warn;\n console.error = originals.error;\n window.onerror = origOnerror;\n window.onunhandledrejection = origUnhandled;\n },\n\n getEntries() {\n return [...entries];\n },\n };\n}\n","import type { EnvironmentInfo } from \"@flint/types\";\n\nexport function collectEnvironment(): EnvironmentInfo {\n const ua = navigator.userAgent;\n\n // -- Browser --\n let browser = \"Unknown\";\n const chromeM = ua.match(/Chrome\\/(\\d+)/);\n const firefoxM = ua.match(/Firefox\\/(\\d+)/);\n const edgeM = ua.match(/Edg\\/(\\d+)/);\n const safariM = ua.match(/Version\\/(\\d+)/);\n const operaM = ua.match(/OPR\\/(\\d+)/);\n\n if (operaM) {\n browser = `Opera ${operaM[1]}`;\n } else if (edgeM) {\n browser = `Edge ${edgeM[1]}`;\n } else if (chromeM && !/Edg|OPR/.test(ua)) {\n browser = `Chrome ${chromeM[1]}`;\n } else if (firefoxM) {\n browser = `Firefox ${firefoxM[1]}`;\n } else if (safariM && /Safari\\//.test(ua)) {\n browser = `Safari ${safariM[1]}`;\n }\n\n // -- OS --\n let os = \"Unknown\";\n const macM = ua.match(/Mac OS X (\\d+[._]\\d+)/);\n const winM = ua.match(/Windows NT (\\d+\\.\\d+)/);\n const androidM = ua.match(/Android (\\d+)/);\n const iosM = ua.match(/iPhone OS (\\d+[._]\\d+)/);\n\n if (macM) {\n os = `macOS ${macM[1].replace(\"_\", \".\")}`;\n } else if (winM) {\n const winMap: Record<string, string> = {\n \"10.0\": \"10/11\",\n \"6.3\": \"8.1\",\n \"6.2\": \"8\",\n \"6.1\": \"7\",\n };\n os = `Windows ${winMap[winM[1]] ?? winM[1]}`;\n } else if (androidM) {\n os = `Android ${androidM[1]}`;\n } else if (iosM) {\n os = `iOS ${iosM[1].replace(/_/g, \".\")}`;\n } else if (/Linux/.test(ua)) {\n os = \"Linux\";\n }\n\n return {\n browser,\n os,\n viewport: `${window.innerWidth}x${window.innerHeight}`,\n screen: `${screen.width}x${screen.height}`,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n online: navigator.onLine,\n };\n}\n","import type { NetworkEntry } from \"@flint/types\";\n\nconst MAX_ENTRIES = 50;\n\nconst BLOCKED_HOSTS = new Set([\n \"browser-intake-datadoghq.com\",\n \"rum.browser-intake-datadoghq.com\",\n \"logs.browser-intake-datadoghq.com\",\n \"session-replay.browser-intake-datadoghq.com\",\n]);\n\nfunction isBlockedUrl(url: string, extra: Set<string>): boolean {\n try {\n const host = new URL(url, location.href).hostname;\n const all = [...BLOCKED_HOSTS, ...extra];\n return all.some((b) => host === b || host.endsWith(\".\" + b));\n } catch {\n return false;\n }\n}\n\nexport interface NetworkCollector {\n start(): void;\n stop(): void;\n getEntries(): NetworkEntry[];\n}\n\nfunction truncateUrl(url: string): string {\n try {\n const u = new URL(url, location.href);\n const base = `${u.origin}${u.pathname}`;\n return base.length > 200 ? base.slice(0, 200) + \"\\u2026\" : base;\n } catch {\n return url.length > 200 ? url.slice(0, 200) + \"\\u2026\" : url;\n }\n}\n\nexport function createNetworkCollector(extraBlockedHosts: string[] = []): NetworkCollector {\n const entries: NetworkEntry[] = [];\n const blocked = new Set(extraBlockedHosts);\n let origFetch: typeof window.fetch | null = null;\n let origXHROpen: typeof XMLHttpRequest.prototype.open | null = null;\n let active = false;\n\n function push(entry: NetworkEntry) {\n entries.push(entry);\n if (entries.length > MAX_ENTRIES) entries.shift();\n }\n\n return {\n start() {\n if (active) return;\n active = true;\n\n // -- Patch fetch --\n origFetch = window.fetch;\n window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {\n const method = ((init?.method ?? \"GET\") as string).toUpperCase();\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.href : (input as Request).url;\n const startTime = Date.now();\n const res = await origFetch!.call(window, input, init);\n if (res.status >= 400 && !isBlockedUrl(url, blocked)) {\n push({\n method,\n url: truncateUrl(url),\n status: res.status,\n duration: Date.now() - startTime,\n timestamp: startTime,\n });\n }\n return res;\n };\n\n // -- Patch XHR --\n origXHROpen = XMLHttpRequest.prototype.open;\n XMLHttpRequest.prototype.open = function (\n method: string,\n url: string | URL,\n async?: boolean,\n username?: string | null,\n password?: string | null,\n ) {\n const startTime = Date.now();\n const urlStr = typeof url === \"string\" ? url : (url as URL).href;\n\n this.addEventListener(\"load\", () => {\n if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {\n push({\n method: method.toUpperCase(),\n url: truncateUrl(urlStr),\n status: this.status,\n duration: Date.now() - startTime,\n timestamp: startTime,\n });\n }\n });\n\n return origXHROpen!.apply(this, [method, url, async ?? true, username, password] as Parameters<\n typeof XMLHttpRequest.prototype.open\n >);\n };\n },\n\n stop() {\n if (!active) return;\n active = false;\n if (origFetch) window.fetch = origFetch;\n if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;\n },\n\n getEntries() {\n return [...entries];\n },\n };\n}\n","export interface FrustrationEvent {\n type: \"rage_click\" | \"dead_click\" | \"error_loop\";\n timestamp: number;\n target: string;\n details: string;\n url: string;\n}\n\nexport interface FrustrationCollector {\n start(): void;\n stop(): void;\n getEvents(): FrustrationEvent[];\n onFrustration(callback: (event: FrustrationEvent) => void): () => void;\n}\n\nexport function createFrustrationCollector(opts?: {\n rageClickThreshold?: number;\n rageClickWindow?: number;\n errorLoopThreshold?: number;\n errorLoopWindow?: number;\n enableDeadClicks?: boolean;\n}): FrustrationCollector {\n const threshold = opts?.rageClickThreshold ?? 3;\n const window = opts?.rageClickWindow ?? 500;\n const errorThreshold = opts?.errorLoopThreshold ?? 3;\n const errorWindow = opts?.errorLoopWindow ?? 30_000;\n const deadClicksEnabled = opts?.enableDeadClicks ?? true;\n\n const events: FrustrationEvent[] = [];\n const listeners = new Set<(event: FrustrationEvent) => void>();\n const clickHistory: { target: EventTarget | null; time: number }[] = [];\n const errorCounts = new Map<string, { count: number; firstSeen: number }>();\n let clickHandler: ((e: MouseEvent) => void) | null = null;\n let origConsoleError: typeof console.error | null = null;\n\n function emit(event: FrustrationEvent) {\n events.push(event);\n if (events.length > 50) events.shift();\n for (const cb of listeners) cb(event);\n }\n\n function getCSSSelector(el: Element): string {\n if (el.id) return `#${el.id}`;\n const tag = el.tagName.toLowerCase();\n const cls = [...el.classList].slice(0, 3).join(\".\");\n if (cls) return `${tag}.${cls}`;\n return tag;\n }\n\n function isInteractive(el: HTMLElement): boolean {\n const tag = el.tagName.toLowerCase();\n if ([\"a\", \"button\", \"input\", \"select\", \"textarea\", \"label\", \"summary\"].includes(tag)) return true;\n if (el.getAttribute(\"role\") === \"button\" || el.getAttribute(\"tabindex\")) return true;\n if (el.onclick || el.getAttribute(\"onclick\")) return true;\n if (el.closest(\"a, button, [role=button], [onclick]\")) return true;\n try { if (getComputedStyle(el).cursor === \"pointer\") return true; } catch {}\n return false;\n }\n\n function handleClick(e: MouseEvent) {\n const target = e.target as HTMLElement;\n if (!target) return;\n const now = Date.now();\n\n // --- Rage click detection ---\n clickHistory.push({ target, time: now });\n // Remove old clicks\n while (clickHistory.length > 0 && now - clickHistory[0].time > 1000) {\n clickHistory.shift();\n }\n const recentOnSame = clickHistory.filter(\n (c) => c.target === target && now - c.time < window\n );\n if (recentOnSame.length >= threshold) {\n emit({\n type: \"rage_click\",\n timestamp: now,\n target: getCSSSelector(target),\n details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,\n url: globalThis.location?.href ?? \"\",\n });\n clickHistory.length = 0;\n }\n\n // --- Dead click detection ---\n if (deadClicksEnabled && !isInteractive(target)) {\n emit({\n type: \"dead_click\",\n timestamp: now,\n target: getCSSSelector(target),\n details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,\n url: globalThis.location?.href ?? \"\",\n });\n }\n }\n\n function patchConsoleError() {\n origConsoleError = console.error;\n console.error = (...args: unknown[]) => {\n origConsoleError?.apply(console, args);\n // Check for error loop\n const key = String(args[0]).slice(0, 100);\n const now = Date.now();\n const existing = errorCounts.get(key);\n if (existing && now - existing.firstSeen < errorWindow) {\n existing.count++;\n if (existing.count >= errorThreshold) {\n emit({\n type: \"error_loop\",\n timestamp: now,\n target: \"console\",\n details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1000)}s: ${key}`,\n url: globalThis.location?.href ?? \"\",\n });\n errorCounts.delete(key);\n }\n } else {\n errorCounts.set(key, { count: 1, firstSeen: now });\n }\n };\n }\n\n return {\n start() {\n clickHandler = handleClick;\n document.addEventListener(\"click\", clickHandler, true);\n patchConsoleError();\n },\n stop() {\n if (clickHandler) document.removeEventListener(\"click\", clickHandler, true);\n if (origConsoleError) console.error = origConsoleError;\n clickHistory.length = 0;\n errorCounts.clear();\n },\n getEvents() { return [...events]; },\n onFrustration(callback) {\n listeners.add(callback);\n return () => listeners.delete(callback);\n },\n };\n}\n","import type { FlintUser } from \"@flint/types\";\n\nexport interface FlintState {\n user: FlintUser | undefined;\n sessionReplay: string | (() => string) | undefined;\n}\n\nlet state: FlintState = { user: undefined, sessionReplay: undefined };\nconst listeners = new Set<() => void>();\n\nfunction emit() {\n for (const l of listeners) l();\n}\n\nexport function subscribe(listener: () => void) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function getSnapshot(): FlintState {\n return state;\n}\n\nexport const flint = {\n setUser(user: FlintUser | null) {\n state = { ...state, user: user ?? undefined };\n emit();\n },\n setSessionReplay(url: string | (() => string) | null) {\n state = { ...state, sessionReplay: url ?? undefined };\n emit();\n },\n};\n","import type { Theme, ThemeOverride } from \"@flint/types\";\n\nexport interface ResolvedTheme {\n background: string;\n backgroundSecondary: string;\n accent: string;\n accentHover: string;\n text: string;\n textMuted: string;\n border: string;\n shadow: string;\n buttonText: string;\n backdropFilter: string;\n}\n\nconst light: ResolvedTheme = {\n background: \"rgba(255,255,255,0.90)\",\n backgroundSecondary: \"rgba(249,250,251,0.75)\",\n accent: \"#2563eb\",\n accentHover: \"#1d4ed8\",\n text: \"#111827\",\n textMuted: \"#6b7280\",\n border: \"rgba(255,255,255,0.9)\",\n shadow: \"0 32px 80px rgba(0,0,0,0.18), 0 8px 32px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)\",\n buttonText: \"#ffffff\",\n backdropFilter: \"blur(32px) saturate(1.8)\",\n};\n\nconst dark: ResolvedTheme = {\n background: \"rgba(15,20,35,0.88)\",\n backgroundSecondary: \"rgba(5,8,18,0.65)\",\n accent: \"#4d8aff\",\n accentHover: \"#3b6fdb\",\n text: \"#dde3ef\",\n textMuted: \"#6b7a93\",\n border: \"rgba(255,255,255,0.08)\",\n shadow: \"0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)\",\n buttonText: \"#ffffff\",\n backdropFilter: \"blur(32px) saturate(1.6)\",\n};\n\nexport function resolveTheme(theme: Theme): ResolvedTheme {\n if (theme === \"dark\") return dark;\n if (theme === \"light\") return light;\n // ThemeOverride — merge over light base\n const override = theme as ThemeOverride;\n return {\n ...light,\n background: override.background ?? light.background,\n accent: override.accent ?? light.accent,\n accentHover: override.accent ?? light.accentHover,\n text: override.text ?? light.text,\n border: override.border ?? light.border,\n };\n}\n"],"mappings":"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","names":["MAX_ENTRIES","window","listeners","emit"]}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@diegotsi/flint-core",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "@flint/types": "workspace:*",
22
+ "fflate": "^0.8.2"
23
+ },
24
+ "devDependencies": {
25
+ "tsup": "^8.4.0",
26
+ "typescript": "^5.6.0"
27
+ }
28
+ }