@diegotsi/flint-react 1.0.2 → 1.2.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 CHANGED
@@ -30,9 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Flint: () => import_flint_core9.Flint,
33
34
  FlintModal: () => FlintModal,
34
35
  FlintWidget: () => FlintWidget,
35
- flint: () => flint
36
+ flint: () => import_flint_core9.flint
36
37
  });
37
38
  module.exports = __toCommonJS(index_exports);
38
39
 
@@ -40,437 +41,8 @@ module.exports = __toCommonJS(index_exports);
40
41
  var import_react2 = require("react");
41
42
  var import_react_i18next = require("react-i18next");
42
43
 
43
- // ../core/dist/index.js
44
- var import_fflate = require("fflate");
45
- async function fetchWithRetry(url, init, retries = 3, baseDelay = 1e3) {
46
- for (let attempt = 0; attempt <= retries; attempt++) {
47
- try {
48
- const res = await fetch(url, init);
49
- if (res.ok || attempt === retries) return res;
50
- if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;
51
- } catch (err) {
52
- if (attempt === retries) throw err;
53
- }
54
- await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
55
- }
56
- throw new Error("Max retries exceeded");
57
- }
58
- async function submitReport(serverUrl, projectKey, payload, screenshot) {
59
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
60
- let body;
61
- const headers = {
62
- "x-project-key": projectKey
63
- };
64
- if (screenshot) {
65
- const form = new FormData();
66
- form.append("reporterId", payload.reporterId);
67
- form.append("reporterName", payload.reporterName);
68
- if (payload.reporterEmail) form.append("reporterEmail", payload.reporterEmail);
69
- form.append("description", payload.description);
70
- if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
71
- if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
72
- if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
73
- if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
74
- form.append("severity", payload.severity);
75
- if (payload.url) form.append("url", payload.url);
76
- if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
77
- form.append("screenshot", screenshot);
78
- body = form;
79
- } else {
80
- body = JSON.stringify(payload);
81
- headers["Content-Type"] = "application/json";
82
- }
83
- const res = await fetchWithRetry(url, { method: "POST", headers, body });
84
- if (!res.ok) {
85
- const err = await res.json().catch(() => ({ error: "Unknown error" }));
86
- throw new Error(err.error ?? `HTTP ${res.status}`);
87
- }
88
- return res.json();
89
- }
90
- async function submitReplay(serverUrl, projectKey, reportId, events) {
91
- const json = JSON.stringify(events);
92
- const encoded = new TextEncoder().encode(json);
93
- const compressed = (0, import_fflate.gzipSync)(encoded);
94
- const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
95
- await fetch(url, {
96
- method: "POST",
97
- headers: {
98
- "x-project-key": projectKey,
99
- "Content-Type": "application/octet-stream"
100
- },
101
- body: compressed.buffer
102
- });
103
- }
104
- var MAX_ENTRIES = 50;
105
- function createConsoleCollector() {
106
- const entries = [];
107
- let active = false;
108
- const originals = {
109
- log: console.log.bind(console),
110
- warn: console.warn.bind(console),
111
- error: console.error.bind(console)
112
- };
113
- let origOnerror = null;
114
- let origUnhandled = null;
115
- function push(level, args) {
116
- let str;
117
- try {
118
- str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
119
- } catch {
120
- str = String(args[0]);
121
- }
122
- if (str.length > 500) str = str.slice(0, 500) + "\u2026";
123
- entries.push({ level, args: str, timestamp: Date.now() });
124
- if (entries.length > MAX_ENTRIES) entries.shift();
125
- }
126
- return {
127
- start() {
128
- if (active) return;
129
- active = true;
130
- console.log = (...args) => {
131
- push("log", args);
132
- originals.log(...args);
133
- };
134
- console.warn = (...args) => {
135
- push("warn", args);
136
- originals.warn(...args);
137
- };
138
- console.error = (...args) => {
139
- push("error", args);
140
- originals.error(...args);
141
- };
142
- origOnerror = window.onerror;
143
- window.onerror = (msg, src, line, col, err) => {
144
- push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
145
- if (typeof origOnerror === "function") return origOnerror(msg, src, line, col, err);
146
- return false;
147
- };
148
- origUnhandled = window.onunhandledrejection;
149
- window.onunhandledrejection = (event) => {
150
- const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
151
- push("error", ["UnhandledRejection:", reason]);
152
- if (typeof origUnhandled === "function") origUnhandled.call(window, event);
153
- };
154
- },
155
- stop() {
156
- if (!active) return;
157
- active = false;
158
- console.log = originals.log;
159
- console.warn = originals.warn;
160
- console.error = originals.error;
161
- window.onerror = origOnerror;
162
- window.onunhandledrejection = origUnhandled;
163
- },
164
- getEntries() {
165
- return [...entries];
166
- }
167
- };
168
- }
169
- function collectEnvironment() {
170
- const ua = navigator.userAgent;
171
- let browser = "Unknown";
172
- const chromeM = ua.match(/Chrome\/(\d+)/);
173
- const firefoxM = ua.match(/Firefox\/(\d+)/);
174
- const edgeM = ua.match(/Edg\/(\d+)/);
175
- const safariM = ua.match(/Version\/(\d+)/);
176
- const operaM = ua.match(/OPR\/(\d+)/);
177
- if (operaM) {
178
- browser = `Opera ${operaM[1]}`;
179
- } else if (edgeM) {
180
- browser = `Edge ${edgeM[1]}`;
181
- } else if (chromeM && !/Edg|OPR/.test(ua)) {
182
- browser = `Chrome ${chromeM[1]}`;
183
- } else if (firefoxM) {
184
- browser = `Firefox ${firefoxM[1]}`;
185
- } else if (safariM && /Safari\//.test(ua)) {
186
- browser = `Safari ${safariM[1]}`;
187
- }
188
- let os = "Unknown";
189
- const macM = ua.match(/Mac OS X (\d+[._]\d+)/);
190
- const winM = ua.match(/Windows NT (\d+\.\d+)/);
191
- const androidM = ua.match(/Android (\d+)/);
192
- const iosM = ua.match(/iPhone OS (\d+[._]\d+)/);
193
- if (macM) {
194
- os = `macOS ${macM[1].replace("_", ".")}`;
195
- } else if (winM) {
196
- const winMap = {
197
- "10.0": "10/11",
198
- "6.3": "8.1",
199
- "6.2": "8",
200
- "6.1": "7"
201
- };
202
- os = `Windows ${winMap[winM[1]] ?? winM[1]}`;
203
- } else if (androidM) {
204
- os = `Android ${androidM[1]}`;
205
- } else if (iosM) {
206
- os = `iOS ${iosM[1].replace(/_/g, ".")}`;
207
- } else if (/Linux/.test(ua)) {
208
- os = "Linux";
209
- }
210
- return {
211
- browser,
212
- os,
213
- viewport: `${window.innerWidth}x${window.innerHeight}`,
214
- screen: `${screen.width}x${screen.height}`,
215
- language: navigator.language,
216
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
217
- online: navigator.onLine
218
- };
219
- }
220
- function createFrustrationCollector(opts) {
221
- const threshold = opts?.rageClickThreshold ?? 3;
222
- const window2 = opts?.rageClickWindow ?? 500;
223
- const errorThreshold = opts?.errorLoopThreshold ?? 3;
224
- const errorWindow = opts?.errorLoopWindow ?? 3e4;
225
- const deadClicksEnabled = opts?.enableDeadClicks ?? true;
226
- const events = [];
227
- const listeners2 = /* @__PURE__ */ new Set();
228
- const clickHistory = [];
229
- const errorCounts = /* @__PURE__ */ new Map();
230
- let clickHandler = null;
231
- let origConsoleError = null;
232
- function emit2(event) {
233
- events.push(event);
234
- if (events.length > 50) events.shift();
235
- for (const cb of listeners2) cb(event);
236
- }
237
- function getCSSSelector(el) {
238
- if (el.id) return `#${el.id}`;
239
- const tag = el.tagName.toLowerCase();
240
- const cls = [...el.classList].slice(0, 3).join(".");
241
- if (cls) return `${tag}.${cls}`;
242
- return tag;
243
- }
244
- function isInteractive(el) {
245
- const tag = el.tagName.toLowerCase();
246
- if (["a", "button", "input", "select", "textarea", "label", "summary"].includes(tag)) return true;
247
- if (el.getAttribute("role") === "button" || el.getAttribute("tabindex")) return true;
248
- if (el.onclick || el.getAttribute("onclick")) return true;
249
- if (el.closest("a, button, [role=button], [onclick]")) return true;
250
- try {
251
- if (getComputedStyle(el).cursor === "pointer") return true;
252
- } catch {
253
- }
254
- return false;
255
- }
256
- function handleClick(e) {
257
- const target = e.target;
258
- if (!target) return;
259
- const now = Date.now();
260
- clickHistory.push({ target, time: now });
261
- while (clickHistory.length > 0 && now - clickHistory[0].time > 1e3) {
262
- clickHistory.shift();
263
- }
264
- const recentOnSame = clickHistory.filter((c) => c.target === target && now - c.time < window2);
265
- if (recentOnSame.length >= threshold) {
266
- emit2({
267
- type: "rage_click",
268
- timestamp: now,
269
- target: getCSSSelector(target),
270
- details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,
271
- url: globalThis.location?.href ?? ""
272
- });
273
- clickHistory.length = 0;
274
- }
275
- if (deadClicksEnabled && !isInteractive(target)) {
276
- emit2({
277
- type: "dead_click",
278
- timestamp: now,
279
- target: getCSSSelector(target),
280
- details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,
281
- url: globalThis.location?.href ?? ""
282
- });
283
- }
284
- }
285
- function patchConsoleError() {
286
- origConsoleError = console.error;
287
- console.error = (...args) => {
288
- origConsoleError?.apply(console, args);
289
- const key = String(args[0]).slice(0, 100);
290
- const now = Date.now();
291
- const existing = errorCounts.get(key);
292
- if (existing && now - existing.firstSeen < errorWindow) {
293
- existing.count++;
294
- if (existing.count >= errorThreshold) {
295
- emit2({
296
- type: "error_loop",
297
- timestamp: now,
298
- target: "console",
299
- details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1e3)}s: ${key}`,
300
- url: globalThis.location?.href ?? ""
301
- });
302
- errorCounts.delete(key);
303
- }
304
- } else {
305
- errorCounts.set(key, { count: 1, firstSeen: now });
306
- }
307
- };
308
- }
309
- return {
310
- start() {
311
- clickHandler = handleClick;
312
- document.addEventListener("click", clickHandler, true);
313
- patchConsoleError();
314
- },
315
- stop() {
316
- if (clickHandler) document.removeEventListener("click", clickHandler, true);
317
- if (origConsoleError) console.error = origConsoleError;
318
- clickHistory.length = 0;
319
- errorCounts.clear();
320
- },
321
- getEvents() {
322
- return [...events];
323
- },
324
- onFrustration(callback) {
325
- listeners2.add(callback);
326
- return () => listeners2.delete(callback);
327
- }
328
- };
329
- }
330
- var MAX_ENTRIES2 = 50;
331
- var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
332
- "browser-intake-datadoghq.com",
333
- "rum.browser-intake-datadoghq.com",
334
- "logs.browser-intake-datadoghq.com",
335
- "session-replay.browser-intake-datadoghq.com"
336
- ]);
337
- function isBlockedUrl(url, extra) {
338
- try {
339
- const host = new URL(url, location.href).hostname;
340
- const all = [...BLOCKED_HOSTS, ...extra];
341
- return all.some((b) => host === b || host.endsWith("." + b));
342
- } catch {
343
- return false;
344
- }
345
- }
346
- function truncateUrl(url) {
347
- try {
348
- const u = new URL(url, location.href);
349
- const base = `${u.origin}${u.pathname}`;
350
- return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
351
- } catch {
352
- return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
353
- }
354
- }
355
- function createNetworkCollector(extraBlockedHosts = []) {
356
- const entries = [];
357
- const blocked = new Set(extraBlockedHosts);
358
- let origFetch = null;
359
- let origXHROpen = null;
360
- let active = false;
361
- function push(entry) {
362
- entries.push(entry);
363
- if (entries.length > MAX_ENTRIES2) entries.shift();
364
- }
365
- return {
366
- start() {
367
- if (active) return;
368
- active = true;
369
- origFetch = window.fetch;
370
- window.fetch = async (input, init) => {
371
- const method = (init?.method ?? "GET").toUpperCase();
372
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
373
- const startTime = Date.now();
374
- const res = await origFetch.call(window, input, init);
375
- if (res.status >= 400 && !isBlockedUrl(url, blocked)) {
376
- push({
377
- method,
378
- url: truncateUrl(url),
379
- status: res.status,
380
- duration: Date.now() - startTime,
381
- timestamp: startTime
382
- });
383
- }
384
- return res;
385
- };
386
- origXHROpen = XMLHttpRequest.prototype.open;
387
- XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
388
- const startTime = Date.now();
389
- const urlStr = typeof url === "string" ? url : url.href;
390
- this.addEventListener("load", () => {
391
- if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {
392
- push({
393
- method: method.toUpperCase(),
394
- url: truncateUrl(urlStr),
395
- status: this.status,
396
- duration: Date.now() - startTime,
397
- timestamp: startTime
398
- });
399
- }
400
- });
401
- return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
402
- };
403
- },
404
- stop() {
405
- if (!active) return;
406
- active = false;
407
- if (origFetch) window.fetch = origFetch;
408
- if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
409
- },
410
- getEntries() {
411
- return [...entries];
412
- }
413
- };
414
- }
415
- var state = { user: void 0, sessionReplay: void 0 };
416
- var listeners = /* @__PURE__ */ new Set();
417
- function emit() {
418
- for (const l of listeners) l();
419
- }
420
- function subscribe(listener) {
421
- listeners.add(listener);
422
- return () => listeners.delete(listener);
423
- }
424
- function getSnapshot() {
425
- return state;
426
- }
427
- var flint = {
428
- setUser(user) {
429
- state = { ...state, user: user ?? void 0 };
430
- emit();
431
- },
432
- setSessionReplay(url) {
433
- state = { ...state, sessionReplay: url ?? void 0 };
434
- emit();
435
- }
436
- };
437
- var light = {
438
- background: "rgba(255,255,255,0.90)",
439
- backgroundSecondary: "rgba(249,250,251,0.75)",
440
- accent: "#2563eb",
441
- accentHover: "#1d4ed8",
442
- text: "#111827",
443
- textMuted: "#6b7280",
444
- border: "rgba(255,255,255,0.9)",
445
- 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)",
446
- buttonText: "#ffffff",
447
- backdropFilter: "blur(32px) saturate(1.8)"
448
- };
449
- var dark = {
450
- background: "rgba(15,20,35,0.88)",
451
- backgroundSecondary: "rgba(5,8,18,0.65)",
452
- accent: "#4d8aff",
453
- accentHover: "#3b6fdb",
454
- text: "#dde3ef",
455
- textMuted: "#6b7a93",
456
- border: "rgba(255,255,255,0.08)",
457
- shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
458
- buttonText: "#ffffff",
459
- backdropFilter: "blur(32px) saturate(1.6)"
460
- };
461
- function resolveTheme(theme) {
462
- if (theme === "dark") return dark;
463
- if (theme === "light") return light;
464
- const override = theme;
465
- return {
466
- ...light,
467
- background: override.background ?? light.background,
468
- accent: override.accent ?? light.accent,
469
- accentHover: override.accent ?? light.accentHover,
470
- text: override.text ?? light.text,
471
- border: override.border ?? light.border
472
- };
473
- }
44
+ // src/api.ts
45
+ var import_flint_core = require("@diegotsi/flint-core");
474
46
 
475
47
  // src/ScreenAnnotator.tsx
476
48
  var import_modern_screenshot = require("modern-screenshot");
@@ -614,6 +186,9 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
614
186
  );
615
187
  }
616
188
 
189
+ // src/theme.ts
190
+ var import_flint_core2 = require("@diegotsi/flint-core");
191
+
617
192
  // src/FlintModal.tsx
618
193
  var import_jsx_runtime2 = require("react/jsx-runtime");
619
194
  var SEVERITIES = ["P1", "P2", "P3", "P4"];
@@ -670,6 +245,7 @@ function FlintModal({
670
245
  getEnvironment,
671
246
  getConsoleLogs,
672
247
  getNetworkErrors,
248
+ getFormErrors,
673
249
  getReplayEvents,
674
250
  getExternalReplayUrl,
675
251
  initialSelection = "",
@@ -680,7 +256,7 @@ function FlintModal({
680
256
  onError
681
257
  }) {
682
258
  const { t } = (0, import_react_i18next.useTranslation)();
683
- const colors = resolveTheme(theme);
259
+ const colors = (0, import_flint_core2.resolveTheme)(theme);
684
260
  const isDark = theme === "dark";
685
261
  const [severity, setSeverity] = (0, import_react2.useState)("P2");
686
262
  const [description, setDescription] = (0, import_react2.useState)("");
@@ -728,7 +304,8 @@ function FlintModal({
728
304
  ...meta,
729
305
  environment: getEnvironment(),
730
306
  consoleLogs: getConsoleLogs(),
731
- networkErrors: getNetworkErrors()
307
+ networkErrors: getNetworkErrors(),
308
+ formErrors: getFormErrors()
732
309
  };
733
310
  if (isText) {
734
311
  collectedMeta.textIssue = {
@@ -757,13 +334,13 @@ function FlintModal({
757
334
  }
758
335
  }
759
336
  try {
760
- const res = await submitReport(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
337
+ const res = await (0, import_flint_core.submitReport)(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
761
338
  setResult(res);
762
339
  setStatus("success");
763
340
  onSuccess?.(res);
764
341
  const events = getReplayEvents();
765
342
  if (events.length > 0) {
766
- submitReplay(serverUrl, projectKey, res.id, events).catch(() => {
343
+ (0, import_flint_core.submitReplay)(serverUrl, projectKey, res.id, events).catch(() => {
767
344
  });
768
345
  }
769
346
  } catch (err) {
@@ -1603,9 +1180,25 @@ function CheckIcon({ size = 20 }) {
1603
1180
  }
1604
1181
 
1605
1182
  // src/FlintWidget.tsx
1183
+ var import_flint_core10 = require("@diegotsi/flint-core");
1606
1184
  var import_react4 = require("react");
1607
1185
  var import_react_i18next3 = require("react-i18next");
1608
1186
 
1187
+ // src/collectors/console.ts
1188
+ var import_flint_core3 = require("@diegotsi/flint-core");
1189
+
1190
+ // src/collectors/environment.ts
1191
+ var import_flint_core4 = require("@diegotsi/flint-core");
1192
+
1193
+ // src/collectors/formErrors.ts
1194
+ var import_flint_core5 = require("@diegotsi/flint-core");
1195
+
1196
+ // src/collectors/frustration.ts
1197
+ var import_flint_core6 = require("@diegotsi/flint-core");
1198
+
1199
+ // src/collectors/network.ts
1200
+ var import_flint_core7 = require("@diegotsi/flint-core");
1201
+
1609
1202
  // src/i18n/index.ts
1610
1203
  var import_i18next = require("i18next");
1611
1204
  var import_react_i18next2 = require("react-i18next");
@@ -1656,9 +1249,11 @@ widgetI18n.use(import_react_i18next2.initReactI18next).init({
1656
1249
  var i18n_default = widgetI18n;
1657
1250
 
1658
1251
  // src/store.ts
1252
+ var import_flint_core8 = require("@diegotsi/flint-core");
1659
1253
  var import_react3 = require("react");
1254
+ var import_flint_core9 = require("@diegotsi/flint-core");
1660
1255
  function useFlintStore() {
1661
- return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
1256
+ return (0, import_react3.useSyncExternalStore)(import_flint_core8.subscribe, import_flint_core8.getSnapshot);
1662
1257
  }
1663
1258
 
1664
1259
  // src/FlintWidget.tsx
@@ -1686,6 +1281,7 @@ function WidgetContent({
1686
1281
  enableScreenshot = true,
1687
1282
  enableConsole = true,
1688
1283
  enableNetwork = true,
1284
+ enableFormErrors = true,
1689
1285
  enableFrustration = false,
1690
1286
  autoReportFrustration = false,
1691
1287
  onBeforeSubmit,
@@ -1720,7 +1316,7 @@ function WidgetContent({
1720
1316
  const [open, setOpen] = (0, import_react4.useState)(false);
1721
1317
  const [hovered, setHovered] = (0, import_react4.useState)(false);
1722
1318
  const pendingSelection = (0, import_react4.useRef)("");
1723
- const colors = resolveTheme(theme);
1319
+ const colors = (0, import_flint_core2.resolveTheme)(theme);
1724
1320
  const [selectionTooltip, setSelectionTooltip] = (0, import_react4.useState)(null);
1725
1321
  const tooltipRef = (0, import_react4.useRef)(null);
1726
1322
  const triggerRef = (0, import_react4.useRef)(null);
@@ -1763,31 +1359,42 @@ function WidgetContent({
1763
1359
  setOpen(true);
1764
1360
  onOpen?.();
1765
1361
  };
1362
+ const globalInit = import_flint_core10.Flint.isInitialized();
1363
+ const global = import_flint_core10.Flint.getInstance();
1766
1364
  const consoleCollector = (0, import_react4.useRef)(null);
1767
1365
  const networkCollector = (0, import_react4.useRef)(null);
1768
1366
  const replayEvents = (0, import_react4.useRef)([]);
1769
1367
  const stopReplay = (0, import_react4.useRef)(null);
1770
- if (enableConsole && !consoleCollector.current) {
1771
- consoleCollector.current = createConsoleCollector();
1772
- consoleCollector.current.start();
1773
- }
1774
- if (enableNetwork && !networkCollector.current) {
1775
- const flintHost = (() => {
1776
- try {
1777
- return new URL(serverUrl).hostname;
1778
- } catch {
1779
- return "";
1780
- }
1781
- })();
1782
- networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
1783
- networkCollector.current.start();
1784
- }
1368
+ const formErrorCollector = (0, import_react4.useRef)(null);
1785
1369
  const frustrationCollector = (0, import_react4.useRef)(null);
1786
- if (enableFrustration && !frustrationCollector.current) {
1787
- frustrationCollector.current = createFrustrationCollector();
1788
- frustrationCollector.current.start();
1370
+ if (!globalInit) {
1371
+ if (enableConsole && !consoleCollector.current) {
1372
+ consoleCollector.current = (0, import_flint_core3.createConsoleCollector)();
1373
+ consoleCollector.current.start();
1374
+ }
1375
+ if (enableNetwork && !networkCollector.current) {
1376
+ const flintHost = (() => {
1377
+ try {
1378
+ return new URL(serverUrl).hostname;
1379
+ } catch {
1380
+ return "";
1381
+ }
1382
+ })();
1383
+ networkCollector.current = (0, import_flint_core7.createNetworkCollector)(flintHost ? [flintHost] : []);
1384
+ networkCollector.current.start();
1385
+ }
1386
+ if (enableFormErrors && !formErrorCollector.current) {
1387
+ formErrorCollector.current = (0, import_flint_core5.createFormErrorCollector)();
1388
+ formErrorCollector.current.start();
1389
+ (0, import_flint_core9._setFormErrorCollector)(formErrorCollector.current);
1390
+ }
1391
+ if (enableFrustration && !frustrationCollector.current) {
1392
+ frustrationCollector.current = (0, import_flint_core6.createFrustrationCollector)();
1393
+ frustrationCollector.current.start();
1394
+ }
1789
1395
  }
1790
1396
  (0, import_react4.useEffect)(() => {
1397
+ if (globalInit) return;
1791
1398
  let cancelled = false;
1792
1399
  if (enableReplay) {
1793
1400
  import("rrweb").then(({ record }) => {
@@ -1808,15 +1415,18 @@ function WidgetContent({
1808
1415
  cancelled = true;
1809
1416
  consoleCollector.current?.stop();
1810
1417
  networkCollector.current?.stop();
1418
+ formErrorCollector.current?.stop();
1419
+ (0, import_flint_core9._setFormErrorCollector)(null);
1811
1420
  frustrationCollector.current?.stop();
1812
1421
  stopReplay.current?.();
1813
1422
  };
1814
- }, [enableReplay]);
1423
+ }, [enableReplay, globalInit]);
1815
1424
  (0, import_react4.useEffect)(() => {
1425
+ if (globalInit) return;
1816
1426
  if (!enableFrustration || !autoReportFrustration || !frustrationCollector.current) return;
1817
1427
  const unsubscribe = frustrationCollector.current.onFrustration(async (event) => {
1818
1428
  const user2 = resolvedUser;
1819
- await submitReport(serverUrl, projectKey, {
1429
+ await (0, import_flint_core.submitReport)(serverUrl, projectKey, {
1820
1430
  reporterId: user2?.id ?? "anonymous",
1821
1431
  reporterName: user2?.name ?? "Anonymous",
1822
1432
  reporterEmail: user2?.email,
@@ -1824,16 +1434,17 @@ function WidgetContent({
1824
1434
  severity: event.type === "error_loop" ? "P1" : event.type === "rage_click" ? "P2" : "P3",
1825
1435
  url: event.url,
1826
1436
  meta: {
1827
- environment: collectEnvironment(),
1437
+ environment: (0, import_flint_core4.collectEnvironment)(),
1828
1438
  consoleLogs: consoleCollector.current?.getEntries() ?? [],
1829
1439
  networkErrors: networkCollector.current?.getEntries() ?? [],
1440
+ formErrors: formErrorCollector.current?.getEntries() ?? [],
1830
1441
  frustrationEvent: event
1831
1442
  }
1832
1443
  }).catch(() => {
1833
1444
  });
1834
1445
  });
1835
1446
  return unsubscribe;
1836
- }, [enableFrustration, autoReportFrustration]);
1447
+ }, [enableFrustration, autoReportFrustration, globalInit]);
1837
1448
  const label = buttonLabel ?? t("buttonLabel");
1838
1449
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
1839
1450
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
@@ -1930,10 +1541,11 @@ function WidgetContent({
1930
1541
  onClose?.();
1931
1542
  pendingSelection.current = "";
1932
1543
  },
1933
- getEnvironment: collectEnvironment,
1934
- getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
1935
- getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
1936
- getReplayEvents: () => [...replayEvents.current],
1544
+ getEnvironment: import_flint_core4.collectEnvironment,
1545
+ getConsoleLogs: () => globalInit ? global?.console?.getEntries() ?? [] : consoleCollector.current?.getEntries() ?? [],
1546
+ getNetworkErrors: () => globalInit ? global?.network?.getEntries() ?? [] : networkCollector.current?.getEntries() ?? [],
1547
+ getFormErrors: () => globalInit ? global?.formErrors?.getEntries() ?? [] : formErrorCollector.current?.getEntries() ?? [],
1548
+ getReplayEvents: () => globalInit ? import_flint_core10.Flint.getReplayEvents() : [...replayEvents.current],
1937
1549
  getExternalReplayUrl,
1938
1550
  initialSelection: pendingSelection.current,
1939
1551
  enableScreenshot,
@@ -1986,6 +1598,7 @@ function SparkIcon2() {
1986
1598
  }
1987
1599
  // Annotate the CommonJS export names for ESM import in node:
1988
1600
  0 && (module.exports = {
1601
+ Flint,
1989
1602
  FlintModal,
1990
1603
  FlintWidget,
1991
1604
  flint