@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 +77 -464
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -130
- package/dist/index.d.ts +6 -130
- package/dist/index.js +67 -455
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2,437 +2,8 @@
|
|
|
2
2
|
import { useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
import {
|
|
7
|
-
async function fetchWithRetry(url, init, retries = 3, baseDelay = 1e3) {
|
|
8
|
-
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
9
|
-
try {
|
|
10
|
-
const res = await fetch(url, init);
|
|
11
|
-
if (res.ok || attempt === retries) return res;
|
|
12
|
-
if (res.status >= 400 && res.status < 500 && res.status !== 429) return res;
|
|
13
|
-
} catch (err) {
|
|
14
|
-
if (attempt === retries) throw err;
|
|
15
|
-
}
|
|
16
|
-
await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
|
|
17
|
-
}
|
|
18
|
-
throw new Error("Max retries exceeded");
|
|
19
|
-
}
|
|
20
|
-
async function submitReport(serverUrl, projectKey, payload, screenshot) {
|
|
21
|
-
const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports`;
|
|
22
|
-
let body;
|
|
23
|
-
const headers = {
|
|
24
|
-
"x-project-key": projectKey
|
|
25
|
-
};
|
|
26
|
-
if (screenshot) {
|
|
27
|
-
const form = new FormData();
|
|
28
|
-
form.append("reporterId", payload.reporterId);
|
|
29
|
-
form.append("reporterName", payload.reporterName);
|
|
30
|
-
if (payload.reporterEmail) form.append("reporterEmail", payload.reporterEmail);
|
|
31
|
-
form.append("description", payload.description);
|
|
32
|
-
if (payload.expectedBehavior) form.append("expectedBehavior", payload.expectedBehavior);
|
|
33
|
-
if (payload.stepsToReproduce) form.append("stepsToReproduce", JSON.stringify(payload.stepsToReproduce));
|
|
34
|
-
if (payload.externalReplayUrl) form.append("externalReplayUrl", payload.externalReplayUrl);
|
|
35
|
-
if (payload.additionalContext) form.append("additionalContext", payload.additionalContext);
|
|
36
|
-
form.append("severity", payload.severity);
|
|
37
|
-
if (payload.url) form.append("url", payload.url);
|
|
38
|
-
if (payload.meta) form.append("meta", JSON.stringify(payload.meta));
|
|
39
|
-
form.append("screenshot", screenshot);
|
|
40
|
-
body = form;
|
|
41
|
-
} else {
|
|
42
|
-
body = JSON.stringify(payload);
|
|
43
|
-
headers["Content-Type"] = "application/json";
|
|
44
|
-
}
|
|
45
|
-
const res = await fetchWithRetry(url, { method: "POST", headers, body });
|
|
46
|
-
if (!res.ok) {
|
|
47
|
-
const err = await res.json().catch(() => ({ error: "Unknown error" }));
|
|
48
|
-
throw new Error(err.error ?? `HTTP ${res.status}`);
|
|
49
|
-
}
|
|
50
|
-
return res.json();
|
|
51
|
-
}
|
|
52
|
-
async function submitReplay(serverUrl, projectKey, reportId, events) {
|
|
53
|
-
const json = JSON.stringify(events);
|
|
54
|
-
const encoded = new TextEncoder().encode(json);
|
|
55
|
-
const compressed = gzipSync(encoded);
|
|
56
|
-
const url = `${serverUrl.replace(/\/$/, "")}/api/v1/bug-reports/${reportId}/replay`;
|
|
57
|
-
await fetch(url, {
|
|
58
|
-
method: "POST",
|
|
59
|
-
headers: {
|
|
60
|
-
"x-project-key": projectKey,
|
|
61
|
-
"Content-Type": "application/octet-stream"
|
|
62
|
-
},
|
|
63
|
-
body: compressed.buffer
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
var MAX_ENTRIES = 50;
|
|
67
|
-
function createConsoleCollector() {
|
|
68
|
-
const entries = [];
|
|
69
|
-
let active = false;
|
|
70
|
-
const originals = {
|
|
71
|
-
log: console.log.bind(console),
|
|
72
|
-
warn: console.warn.bind(console),
|
|
73
|
-
error: console.error.bind(console)
|
|
74
|
-
};
|
|
75
|
-
let origOnerror = null;
|
|
76
|
-
let origUnhandled = null;
|
|
77
|
-
function push(level, args) {
|
|
78
|
-
let str;
|
|
79
|
-
try {
|
|
80
|
-
str = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
81
|
-
} catch {
|
|
82
|
-
str = String(args[0]);
|
|
83
|
-
}
|
|
84
|
-
if (str.length > 500) str = str.slice(0, 500) + "\u2026";
|
|
85
|
-
entries.push({ level, args: str, timestamp: Date.now() });
|
|
86
|
-
if (entries.length > MAX_ENTRIES) entries.shift();
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
start() {
|
|
90
|
-
if (active) return;
|
|
91
|
-
active = true;
|
|
92
|
-
console.log = (...args) => {
|
|
93
|
-
push("log", args);
|
|
94
|
-
originals.log(...args);
|
|
95
|
-
};
|
|
96
|
-
console.warn = (...args) => {
|
|
97
|
-
push("warn", args);
|
|
98
|
-
originals.warn(...args);
|
|
99
|
-
};
|
|
100
|
-
console.error = (...args) => {
|
|
101
|
-
push("error", args);
|
|
102
|
-
originals.error(...args);
|
|
103
|
-
};
|
|
104
|
-
origOnerror = window.onerror;
|
|
105
|
-
window.onerror = (msg, src, line, col, err) => {
|
|
106
|
-
push("error", [err?.message ?? String(msg), `${src}:${line}:${col}`]);
|
|
107
|
-
if (typeof origOnerror === "function") return origOnerror(msg, src, line, col, err);
|
|
108
|
-
return false;
|
|
109
|
-
};
|
|
110
|
-
origUnhandled = window.onunhandledrejection;
|
|
111
|
-
window.onunhandledrejection = (event) => {
|
|
112
|
-
const reason = event.reason instanceof Error ? event.reason.message : JSON.stringify(event.reason);
|
|
113
|
-
push("error", ["UnhandledRejection:", reason]);
|
|
114
|
-
if (typeof origUnhandled === "function") origUnhandled.call(window, event);
|
|
115
|
-
};
|
|
116
|
-
},
|
|
117
|
-
stop() {
|
|
118
|
-
if (!active) return;
|
|
119
|
-
active = false;
|
|
120
|
-
console.log = originals.log;
|
|
121
|
-
console.warn = originals.warn;
|
|
122
|
-
console.error = originals.error;
|
|
123
|
-
window.onerror = origOnerror;
|
|
124
|
-
window.onunhandledrejection = origUnhandled;
|
|
125
|
-
},
|
|
126
|
-
getEntries() {
|
|
127
|
-
return [...entries];
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
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
|
-
function createFrustrationCollector(opts) {
|
|
183
|
-
const threshold = opts?.rageClickThreshold ?? 3;
|
|
184
|
-
const window2 = opts?.rageClickWindow ?? 500;
|
|
185
|
-
const errorThreshold = opts?.errorLoopThreshold ?? 3;
|
|
186
|
-
const errorWindow = opts?.errorLoopWindow ?? 3e4;
|
|
187
|
-
const deadClicksEnabled = opts?.enableDeadClicks ?? true;
|
|
188
|
-
const events = [];
|
|
189
|
-
const listeners2 = /* @__PURE__ */ new Set();
|
|
190
|
-
const clickHistory = [];
|
|
191
|
-
const errorCounts = /* @__PURE__ */ new Map();
|
|
192
|
-
let clickHandler = null;
|
|
193
|
-
let origConsoleError = null;
|
|
194
|
-
function emit2(event) {
|
|
195
|
-
events.push(event);
|
|
196
|
-
if (events.length > 50) events.shift();
|
|
197
|
-
for (const cb of listeners2) cb(event);
|
|
198
|
-
}
|
|
199
|
-
function getCSSSelector(el) {
|
|
200
|
-
if (el.id) return `#${el.id}`;
|
|
201
|
-
const tag = el.tagName.toLowerCase();
|
|
202
|
-
const cls = [...el.classList].slice(0, 3).join(".");
|
|
203
|
-
if (cls) return `${tag}.${cls}`;
|
|
204
|
-
return tag;
|
|
205
|
-
}
|
|
206
|
-
function isInteractive(el) {
|
|
207
|
-
const tag = el.tagName.toLowerCase();
|
|
208
|
-
if (["a", "button", "input", "select", "textarea", "label", "summary"].includes(tag)) return true;
|
|
209
|
-
if (el.getAttribute("role") === "button" || el.getAttribute("tabindex")) return true;
|
|
210
|
-
if (el.onclick || el.getAttribute("onclick")) return true;
|
|
211
|
-
if (el.closest("a, button, [role=button], [onclick]")) return true;
|
|
212
|
-
try {
|
|
213
|
-
if (getComputedStyle(el).cursor === "pointer") return true;
|
|
214
|
-
} catch {
|
|
215
|
-
}
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
function handleClick(e) {
|
|
219
|
-
const target = e.target;
|
|
220
|
-
if (!target) return;
|
|
221
|
-
const now = Date.now();
|
|
222
|
-
clickHistory.push({ target, time: now });
|
|
223
|
-
while (clickHistory.length > 0 && now - clickHistory[0].time > 1e3) {
|
|
224
|
-
clickHistory.shift();
|
|
225
|
-
}
|
|
226
|
-
const recentOnSame = clickHistory.filter((c) => c.target === target && now - c.time < window2);
|
|
227
|
-
if (recentOnSame.length >= threshold) {
|
|
228
|
-
emit2({
|
|
229
|
-
type: "rage_click",
|
|
230
|
-
timestamp: now,
|
|
231
|
-
target: getCSSSelector(target),
|
|
232
|
-
details: `${recentOnSame.length} clicks in ${now - recentOnSame[0].time}ms`,
|
|
233
|
-
url: globalThis.location?.href ?? ""
|
|
234
|
-
});
|
|
235
|
-
clickHistory.length = 0;
|
|
236
|
-
}
|
|
237
|
-
if (deadClicksEnabled && !isInteractive(target)) {
|
|
238
|
-
emit2({
|
|
239
|
-
type: "dead_click",
|
|
240
|
-
timestamp: now,
|
|
241
|
-
target: getCSSSelector(target),
|
|
242
|
-
details: `Click on non-interactive <${target.tagName.toLowerCase()}>`,
|
|
243
|
-
url: globalThis.location?.href ?? ""
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function patchConsoleError() {
|
|
248
|
-
origConsoleError = console.error;
|
|
249
|
-
console.error = (...args) => {
|
|
250
|
-
origConsoleError?.apply(console, args);
|
|
251
|
-
const key = String(args[0]).slice(0, 100);
|
|
252
|
-
const now = Date.now();
|
|
253
|
-
const existing = errorCounts.get(key);
|
|
254
|
-
if (existing && now - existing.firstSeen < errorWindow) {
|
|
255
|
-
existing.count++;
|
|
256
|
-
if (existing.count >= errorThreshold) {
|
|
257
|
-
emit2({
|
|
258
|
-
type: "error_loop",
|
|
259
|
-
timestamp: now,
|
|
260
|
-
target: "console",
|
|
261
|
-
details: `Same error ${existing.count}x in ${Math.round((now - existing.firstSeen) / 1e3)}s: ${key}`,
|
|
262
|
-
url: globalThis.location?.href ?? ""
|
|
263
|
-
});
|
|
264
|
-
errorCounts.delete(key);
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
errorCounts.set(key, { count: 1, firstSeen: now });
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
return {
|
|
272
|
-
start() {
|
|
273
|
-
clickHandler = handleClick;
|
|
274
|
-
document.addEventListener("click", clickHandler, true);
|
|
275
|
-
patchConsoleError();
|
|
276
|
-
},
|
|
277
|
-
stop() {
|
|
278
|
-
if (clickHandler) document.removeEventListener("click", clickHandler, true);
|
|
279
|
-
if (origConsoleError) console.error = origConsoleError;
|
|
280
|
-
clickHistory.length = 0;
|
|
281
|
-
errorCounts.clear();
|
|
282
|
-
},
|
|
283
|
-
getEvents() {
|
|
284
|
-
return [...events];
|
|
285
|
-
},
|
|
286
|
-
onFrustration(callback) {
|
|
287
|
-
listeners2.add(callback);
|
|
288
|
-
return () => listeners2.delete(callback);
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
var MAX_ENTRIES2 = 50;
|
|
293
|
-
var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
|
|
294
|
-
"browser-intake-datadoghq.com",
|
|
295
|
-
"rum.browser-intake-datadoghq.com",
|
|
296
|
-
"logs.browser-intake-datadoghq.com",
|
|
297
|
-
"session-replay.browser-intake-datadoghq.com"
|
|
298
|
-
]);
|
|
299
|
-
function isBlockedUrl(url, extra) {
|
|
300
|
-
try {
|
|
301
|
-
const host = new URL(url, location.href).hostname;
|
|
302
|
-
const all = [...BLOCKED_HOSTS, ...extra];
|
|
303
|
-
return all.some((b) => host === b || host.endsWith("." + b));
|
|
304
|
-
} catch {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
function truncateUrl(url) {
|
|
309
|
-
try {
|
|
310
|
-
const u = new URL(url, location.href);
|
|
311
|
-
const base = `${u.origin}${u.pathname}`;
|
|
312
|
-
return base.length > 200 ? base.slice(0, 200) + "\u2026" : base;
|
|
313
|
-
} catch {
|
|
314
|
-
return url.length > 200 ? url.slice(0, 200) + "\u2026" : url;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
function createNetworkCollector(extraBlockedHosts = []) {
|
|
318
|
-
const entries = [];
|
|
319
|
-
const blocked = new Set(extraBlockedHosts);
|
|
320
|
-
let origFetch = null;
|
|
321
|
-
let origXHROpen = null;
|
|
322
|
-
let active = false;
|
|
323
|
-
function push(entry) {
|
|
324
|
-
entries.push(entry);
|
|
325
|
-
if (entries.length > MAX_ENTRIES2) entries.shift();
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
start() {
|
|
329
|
-
if (active) return;
|
|
330
|
-
active = true;
|
|
331
|
-
origFetch = window.fetch;
|
|
332
|
-
window.fetch = async (input, init) => {
|
|
333
|
-
const method = (init?.method ?? "GET").toUpperCase();
|
|
334
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
335
|
-
const startTime = Date.now();
|
|
336
|
-
const res = await origFetch.call(window, input, init);
|
|
337
|
-
if (res.status >= 400 && !isBlockedUrl(url, blocked)) {
|
|
338
|
-
push({
|
|
339
|
-
method,
|
|
340
|
-
url: truncateUrl(url),
|
|
341
|
-
status: res.status,
|
|
342
|
-
duration: Date.now() - startTime,
|
|
343
|
-
timestamp: startTime
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
return res;
|
|
347
|
-
};
|
|
348
|
-
origXHROpen = XMLHttpRequest.prototype.open;
|
|
349
|
-
XMLHttpRequest.prototype.open = function(method, url, async, username, password) {
|
|
350
|
-
const startTime = Date.now();
|
|
351
|
-
const urlStr = typeof url === "string" ? url : url.href;
|
|
352
|
-
this.addEventListener("load", () => {
|
|
353
|
-
if (this.status >= 400 && !isBlockedUrl(urlStr, blocked)) {
|
|
354
|
-
push({
|
|
355
|
-
method: method.toUpperCase(),
|
|
356
|
-
url: truncateUrl(urlStr),
|
|
357
|
-
status: this.status,
|
|
358
|
-
duration: Date.now() - startTime,
|
|
359
|
-
timestamp: startTime
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
return origXHROpen.apply(this, [method, url, async ?? true, username, password]);
|
|
364
|
-
};
|
|
365
|
-
},
|
|
366
|
-
stop() {
|
|
367
|
-
if (!active) return;
|
|
368
|
-
active = false;
|
|
369
|
-
if (origFetch) window.fetch = origFetch;
|
|
370
|
-
if (origXHROpen) XMLHttpRequest.prototype.open = origXHROpen;
|
|
371
|
-
},
|
|
372
|
-
getEntries() {
|
|
373
|
-
return [...entries];
|
|
374
|
-
}
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
var state = { user: void 0, sessionReplay: void 0 };
|
|
378
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
379
|
-
function emit() {
|
|
380
|
-
for (const l of listeners) l();
|
|
381
|
-
}
|
|
382
|
-
function subscribe(listener) {
|
|
383
|
-
listeners.add(listener);
|
|
384
|
-
return () => listeners.delete(listener);
|
|
385
|
-
}
|
|
386
|
-
function getSnapshot() {
|
|
387
|
-
return state;
|
|
388
|
-
}
|
|
389
|
-
var flint = {
|
|
390
|
-
setUser(user) {
|
|
391
|
-
state = { ...state, user: user ?? void 0 };
|
|
392
|
-
emit();
|
|
393
|
-
},
|
|
394
|
-
setSessionReplay(url) {
|
|
395
|
-
state = { ...state, sessionReplay: url ?? void 0 };
|
|
396
|
-
emit();
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
var light = {
|
|
400
|
-
background: "rgba(255,255,255,0.90)",
|
|
401
|
-
backgroundSecondary: "rgba(249,250,251,0.75)",
|
|
402
|
-
accent: "#2563eb",
|
|
403
|
-
accentHover: "#1d4ed8",
|
|
404
|
-
text: "#111827",
|
|
405
|
-
textMuted: "#6b7280",
|
|
406
|
-
border: "rgba(255,255,255,0.9)",
|
|
407
|
-
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)",
|
|
408
|
-
buttonText: "#ffffff",
|
|
409
|
-
backdropFilter: "blur(32px) saturate(1.8)"
|
|
410
|
-
};
|
|
411
|
-
var dark = {
|
|
412
|
-
background: "rgba(15,20,35,0.88)",
|
|
413
|
-
backgroundSecondary: "rgba(5,8,18,0.65)",
|
|
414
|
-
accent: "#4d8aff",
|
|
415
|
-
accentHover: "#3b6fdb",
|
|
416
|
-
text: "#dde3ef",
|
|
417
|
-
textMuted: "#6b7a93",
|
|
418
|
-
border: "rgba(255,255,255,0.08)",
|
|
419
|
-
shadow: "0 24px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04)",
|
|
420
|
-
buttonText: "#ffffff",
|
|
421
|
-
backdropFilter: "blur(32px) saturate(1.6)"
|
|
422
|
-
};
|
|
423
|
-
function resolveTheme(theme) {
|
|
424
|
-
if (theme === "dark") return dark;
|
|
425
|
-
if (theme === "light") return light;
|
|
426
|
-
const override = theme;
|
|
427
|
-
return {
|
|
428
|
-
...light,
|
|
429
|
-
background: override.background ?? light.background,
|
|
430
|
-
accent: override.accent ?? light.accent,
|
|
431
|
-
accentHover: override.accent ?? light.accentHover,
|
|
432
|
-
text: override.text ?? light.text,
|
|
433
|
-
border: override.border ?? light.border
|
|
434
|
-
};
|
|
435
|
-
}
|
|
5
|
+
// src/api.ts
|
|
6
|
+
import { submitReplay, submitReport } from "@diegotsi/flint-core";
|
|
436
7
|
|
|
437
8
|
// src/ScreenAnnotator.tsx
|
|
438
9
|
import { domToCanvas } from "modern-screenshot";
|
|
@@ -576,6 +147,9 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
|
|
|
576
147
|
);
|
|
577
148
|
}
|
|
578
149
|
|
|
150
|
+
// src/theme.ts
|
|
151
|
+
import { resolveTheme } from "@diegotsi/flint-core";
|
|
152
|
+
|
|
579
153
|
// src/FlintModal.tsx
|
|
580
154
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
581
155
|
var SEVERITIES = ["P1", "P2", "P3", "P4"];
|
|
@@ -632,6 +206,7 @@ function FlintModal({
|
|
|
632
206
|
getEnvironment,
|
|
633
207
|
getConsoleLogs,
|
|
634
208
|
getNetworkErrors,
|
|
209
|
+
getFormErrors,
|
|
635
210
|
getReplayEvents,
|
|
636
211
|
getExternalReplayUrl,
|
|
637
212
|
initialSelection = "",
|
|
@@ -690,7 +265,8 @@ function FlintModal({
|
|
|
690
265
|
...meta,
|
|
691
266
|
environment: getEnvironment(),
|
|
692
267
|
consoleLogs: getConsoleLogs(),
|
|
693
|
-
networkErrors: getNetworkErrors()
|
|
268
|
+
networkErrors: getNetworkErrors(),
|
|
269
|
+
formErrors: getFormErrors()
|
|
694
270
|
};
|
|
695
271
|
if (isText) {
|
|
696
272
|
collectedMeta.textIssue = {
|
|
@@ -1565,9 +1141,25 @@ function CheckIcon({ size = 20 }) {
|
|
|
1565
1141
|
}
|
|
1566
1142
|
|
|
1567
1143
|
// src/FlintWidget.tsx
|
|
1144
|
+
import { Flint as Flint2 } from "@diegotsi/flint-core";
|
|
1568
1145
|
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
1569
1146
|
import { I18nextProvider, useTranslation as useTranslation2 } from "react-i18next";
|
|
1570
1147
|
|
|
1148
|
+
// src/collectors/console.ts
|
|
1149
|
+
import { createConsoleCollector } from "@diegotsi/flint-core";
|
|
1150
|
+
|
|
1151
|
+
// src/collectors/environment.ts
|
|
1152
|
+
import { collectEnvironment } from "@diegotsi/flint-core";
|
|
1153
|
+
|
|
1154
|
+
// src/collectors/formErrors.ts
|
|
1155
|
+
import { createFormErrorCollector } from "@diegotsi/flint-core";
|
|
1156
|
+
|
|
1157
|
+
// src/collectors/frustration.ts
|
|
1158
|
+
import { createFrustrationCollector } from "@diegotsi/flint-core";
|
|
1159
|
+
|
|
1160
|
+
// src/collectors/network.ts
|
|
1161
|
+
import { createNetworkCollector } from "@diegotsi/flint-core";
|
|
1162
|
+
|
|
1571
1163
|
// src/i18n/index.ts
|
|
1572
1164
|
import { createInstance } from "i18next";
|
|
1573
1165
|
import { initReactI18next } from "react-i18next";
|
|
@@ -1618,7 +1210,9 @@ widgetI18n.use(initReactI18next).init({
|
|
|
1618
1210
|
var i18n_default = widgetI18n;
|
|
1619
1211
|
|
|
1620
1212
|
// src/store.ts
|
|
1213
|
+
import { getSnapshot, subscribe } from "@diegotsi/flint-core";
|
|
1621
1214
|
import { useSyncExternalStore } from "react";
|
|
1215
|
+
import { _setFormErrorCollector, Flint, flint } from "@diegotsi/flint-core";
|
|
1622
1216
|
function useFlintStore() {
|
|
1623
1217
|
return useSyncExternalStore(subscribe, getSnapshot);
|
|
1624
1218
|
}
|
|
@@ -1648,6 +1242,7 @@ function WidgetContent({
|
|
|
1648
1242
|
enableScreenshot = true,
|
|
1649
1243
|
enableConsole = true,
|
|
1650
1244
|
enableNetwork = true,
|
|
1245
|
+
enableFormErrors = true,
|
|
1651
1246
|
enableFrustration = false,
|
|
1652
1247
|
autoReportFrustration = false,
|
|
1653
1248
|
onBeforeSubmit,
|
|
@@ -1725,31 +1320,42 @@ function WidgetContent({
|
|
|
1725
1320
|
setOpen(true);
|
|
1726
1321
|
onOpen?.();
|
|
1727
1322
|
};
|
|
1323
|
+
const globalInit = Flint2.isInitialized();
|
|
1324
|
+
const global = Flint2.getInstance();
|
|
1728
1325
|
const consoleCollector = useRef3(null);
|
|
1729
1326
|
const networkCollector = useRef3(null);
|
|
1730
1327
|
const replayEvents = useRef3([]);
|
|
1731
1328
|
const stopReplay = useRef3(null);
|
|
1732
|
-
|
|
1733
|
-
consoleCollector.current = createConsoleCollector();
|
|
1734
|
-
consoleCollector.current.start();
|
|
1735
|
-
}
|
|
1736
|
-
if (enableNetwork && !networkCollector.current) {
|
|
1737
|
-
const flintHost = (() => {
|
|
1738
|
-
try {
|
|
1739
|
-
return new URL(serverUrl).hostname;
|
|
1740
|
-
} catch {
|
|
1741
|
-
return "";
|
|
1742
|
-
}
|
|
1743
|
-
})();
|
|
1744
|
-
networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
|
|
1745
|
-
networkCollector.current.start();
|
|
1746
|
-
}
|
|
1329
|
+
const formErrorCollector = useRef3(null);
|
|
1747
1330
|
const frustrationCollector = useRef3(null);
|
|
1748
|
-
if (
|
|
1749
|
-
|
|
1750
|
-
|
|
1331
|
+
if (!globalInit) {
|
|
1332
|
+
if (enableConsole && !consoleCollector.current) {
|
|
1333
|
+
consoleCollector.current = createConsoleCollector();
|
|
1334
|
+
consoleCollector.current.start();
|
|
1335
|
+
}
|
|
1336
|
+
if (enableNetwork && !networkCollector.current) {
|
|
1337
|
+
const flintHost = (() => {
|
|
1338
|
+
try {
|
|
1339
|
+
return new URL(serverUrl).hostname;
|
|
1340
|
+
} catch {
|
|
1341
|
+
return "";
|
|
1342
|
+
}
|
|
1343
|
+
})();
|
|
1344
|
+
networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
|
|
1345
|
+
networkCollector.current.start();
|
|
1346
|
+
}
|
|
1347
|
+
if (enableFormErrors && !formErrorCollector.current) {
|
|
1348
|
+
formErrorCollector.current = createFormErrorCollector();
|
|
1349
|
+
formErrorCollector.current.start();
|
|
1350
|
+
_setFormErrorCollector(formErrorCollector.current);
|
|
1351
|
+
}
|
|
1352
|
+
if (enableFrustration && !frustrationCollector.current) {
|
|
1353
|
+
frustrationCollector.current = createFrustrationCollector();
|
|
1354
|
+
frustrationCollector.current.start();
|
|
1355
|
+
}
|
|
1751
1356
|
}
|
|
1752
1357
|
useEffect3(() => {
|
|
1358
|
+
if (globalInit) return;
|
|
1753
1359
|
let cancelled = false;
|
|
1754
1360
|
if (enableReplay) {
|
|
1755
1361
|
import("rrweb").then(({ record }) => {
|
|
@@ -1770,11 +1376,14 @@ function WidgetContent({
|
|
|
1770
1376
|
cancelled = true;
|
|
1771
1377
|
consoleCollector.current?.stop();
|
|
1772
1378
|
networkCollector.current?.stop();
|
|
1379
|
+
formErrorCollector.current?.stop();
|
|
1380
|
+
_setFormErrorCollector(null);
|
|
1773
1381
|
frustrationCollector.current?.stop();
|
|
1774
1382
|
stopReplay.current?.();
|
|
1775
1383
|
};
|
|
1776
|
-
}, [enableReplay]);
|
|
1384
|
+
}, [enableReplay, globalInit]);
|
|
1777
1385
|
useEffect3(() => {
|
|
1386
|
+
if (globalInit) return;
|
|
1778
1387
|
if (!enableFrustration || !autoReportFrustration || !frustrationCollector.current) return;
|
|
1779
1388
|
const unsubscribe = frustrationCollector.current.onFrustration(async (event) => {
|
|
1780
1389
|
const user2 = resolvedUser;
|
|
@@ -1789,13 +1398,14 @@ function WidgetContent({
|
|
|
1789
1398
|
environment: collectEnvironment(),
|
|
1790
1399
|
consoleLogs: consoleCollector.current?.getEntries() ?? [],
|
|
1791
1400
|
networkErrors: networkCollector.current?.getEntries() ?? [],
|
|
1401
|
+
formErrors: formErrorCollector.current?.getEntries() ?? [],
|
|
1792
1402
|
frustrationEvent: event
|
|
1793
1403
|
}
|
|
1794
1404
|
}).catch(() => {
|
|
1795
1405
|
});
|
|
1796
1406
|
});
|
|
1797
1407
|
return unsubscribe;
|
|
1798
|
-
}, [enableFrustration, autoReportFrustration]);
|
|
1408
|
+
}, [enableFrustration, autoReportFrustration, globalInit]);
|
|
1799
1409
|
const label = buttonLabel ?? t("buttonLabel");
|
|
1800
1410
|
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1801
1411
|
/* @__PURE__ */ jsxs3(
|
|
@@ -1893,9 +1503,10 @@ function WidgetContent({
|
|
|
1893
1503
|
pendingSelection.current = "";
|
|
1894
1504
|
},
|
|
1895
1505
|
getEnvironment: collectEnvironment,
|
|
1896
|
-
getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
|
|
1897
|
-
getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
|
|
1898
|
-
|
|
1506
|
+
getConsoleLogs: () => globalInit ? global?.console?.getEntries() ?? [] : consoleCollector.current?.getEntries() ?? [],
|
|
1507
|
+
getNetworkErrors: () => globalInit ? global?.network?.getEntries() ?? [] : networkCollector.current?.getEntries() ?? [],
|
|
1508
|
+
getFormErrors: () => globalInit ? global?.formErrors?.getEntries() ?? [] : formErrorCollector.current?.getEntries() ?? [],
|
|
1509
|
+
getReplayEvents: () => globalInit ? Flint2.getReplayEvents() : [...replayEvents.current],
|
|
1899
1510
|
getExternalReplayUrl,
|
|
1900
1511
|
initialSelection: pendingSelection.current,
|
|
1901
1512
|
enableScreenshot,
|
|
@@ -1947,6 +1558,7 @@ function SparkIcon2() {
|
|
|
1947
1558
|
);
|
|
1948
1559
|
}
|
|
1949
1560
|
export {
|
|
1561
|
+
Flint,
|
|
1950
1562
|
FlintModal,
|
|
1951
1563
|
FlintWidget,
|
|
1952
1564
|
flint
|