@diegotsi/flint-react 1.0.0 → 1.0.2
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 +470 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -5
- package/dist/index.d.ts +18 -5
- package/dist/index.js +462 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
FlintModal: () => FlintModal,
|
|
34
34
|
FlintWidget: () => FlintWidget,
|
|
35
|
-
flint: () =>
|
|
35
|
+
flint: () => flint
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(index_exports);
|
|
38
38
|
|
|
@@ -40,8 +40,437 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
var import_react2 = require("react");
|
|
41
41
|
var import_react_i18next = require("react-i18next");
|
|
42
42
|
|
|
43
|
-
//
|
|
44
|
-
var
|
|
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
|
+
}
|
|
45
474
|
|
|
46
475
|
// src/ScreenAnnotator.tsx
|
|
47
476
|
var import_modern_screenshot = require("modern-screenshot");
|
|
@@ -185,9 +614,6 @@ function ScreenAnnotator({ zIndex, onCapture, onCancel }) {
|
|
|
185
614
|
);
|
|
186
615
|
}
|
|
187
616
|
|
|
188
|
-
// src/theme.ts
|
|
189
|
-
var import_core2 = require("@flint/core");
|
|
190
|
-
|
|
191
617
|
// src/FlintModal.tsx
|
|
192
618
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
193
619
|
var SEVERITIES = ["P1", "P2", "P3", "P4"];
|
|
@@ -254,7 +680,7 @@ function FlintModal({
|
|
|
254
680
|
onError
|
|
255
681
|
}) {
|
|
256
682
|
const { t } = (0, import_react_i18next.useTranslation)();
|
|
257
|
-
const colors =
|
|
683
|
+
const colors = resolveTheme(theme);
|
|
258
684
|
const isDark = theme === "dark";
|
|
259
685
|
const [severity, setSeverity] = (0, import_react2.useState)("P2");
|
|
260
686
|
const [description, setDescription] = (0, import_react2.useState)("");
|
|
@@ -331,13 +757,13 @@ function FlintModal({
|
|
|
331
757
|
}
|
|
332
758
|
}
|
|
333
759
|
try {
|
|
334
|
-
const res = await
|
|
760
|
+
const res = await submitReport(serverUrl, projectKey, payload, !isText ? screenshot ?? void 0 : void 0);
|
|
335
761
|
setResult(res);
|
|
336
762
|
setStatus("success");
|
|
337
763
|
onSuccess?.(res);
|
|
338
764
|
const events = getReplayEvents();
|
|
339
765
|
if (events.length > 0) {
|
|
340
|
-
|
|
766
|
+
submitReplay(serverUrl, projectKey, res.id, events).catch(() => {
|
|
341
767
|
});
|
|
342
768
|
}
|
|
343
769
|
} catch (err) {
|
|
@@ -1180,15 +1606,6 @@ function CheckIcon({ size = 20 }) {
|
|
|
1180
1606
|
var import_react4 = require("react");
|
|
1181
1607
|
var import_react_i18next3 = require("react-i18next");
|
|
1182
1608
|
|
|
1183
|
-
// src/collectors/console.ts
|
|
1184
|
-
var import_core3 = require("@flint/core");
|
|
1185
|
-
|
|
1186
|
-
// src/collectors/environment.ts
|
|
1187
|
-
var import_core4 = require("@flint/core");
|
|
1188
|
-
|
|
1189
|
-
// src/collectors/network.ts
|
|
1190
|
-
var import_core5 = require("@flint/core");
|
|
1191
|
-
|
|
1192
1609
|
// src/i18n/index.ts
|
|
1193
1610
|
var import_i18next = require("i18next");
|
|
1194
1611
|
var import_react_i18next2 = require("react-i18next");
|
|
@@ -1239,11 +1656,9 @@ widgetI18n.use(import_react_i18next2.initReactI18next).init({
|
|
|
1239
1656
|
var i18n_default = widgetI18n;
|
|
1240
1657
|
|
|
1241
1658
|
// src/store.ts
|
|
1242
|
-
var import_core6 = require("@flint/core");
|
|
1243
1659
|
var import_react3 = require("react");
|
|
1244
|
-
var import_core7 = require("@flint/core");
|
|
1245
1660
|
function useFlintStore() {
|
|
1246
|
-
return (0, import_react3.useSyncExternalStore)(
|
|
1661
|
+
return (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot);
|
|
1247
1662
|
}
|
|
1248
1663
|
|
|
1249
1664
|
// src/FlintWidget.tsx
|
|
@@ -1271,6 +1686,8 @@ function WidgetContent({
|
|
|
1271
1686
|
enableScreenshot = true,
|
|
1272
1687
|
enableConsole = true,
|
|
1273
1688
|
enableNetwork = true,
|
|
1689
|
+
enableFrustration = false,
|
|
1690
|
+
autoReportFrustration = false,
|
|
1274
1691
|
onBeforeSubmit,
|
|
1275
1692
|
onSuccess,
|
|
1276
1693
|
onError,
|
|
@@ -1303,7 +1720,7 @@ function WidgetContent({
|
|
|
1303
1720
|
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
1304
1721
|
const [hovered, setHovered] = (0, import_react4.useState)(false);
|
|
1305
1722
|
const pendingSelection = (0, import_react4.useRef)("");
|
|
1306
|
-
const colors =
|
|
1723
|
+
const colors = resolveTheme(theme);
|
|
1307
1724
|
const [selectionTooltip, setSelectionTooltip] = (0, import_react4.useState)(null);
|
|
1308
1725
|
const tooltipRef = (0, import_react4.useRef)(null);
|
|
1309
1726
|
const triggerRef = (0, import_react4.useRef)(null);
|
|
@@ -1351,7 +1768,7 @@ function WidgetContent({
|
|
|
1351
1768
|
const replayEvents = (0, import_react4.useRef)([]);
|
|
1352
1769
|
const stopReplay = (0, import_react4.useRef)(null);
|
|
1353
1770
|
if (enableConsole && !consoleCollector.current) {
|
|
1354
|
-
consoleCollector.current =
|
|
1771
|
+
consoleCollector.current = createConsoleCollector();
|
|
1355
1772
|
consoleCollector.current.start();
|
|
1356
1773
|
}
|
|
1357
1774
|
if (enableNetwork && !networkCollector.current) {
|
|
@@ -1362,9 +1779,14 @@ function WidgetContent({
|
|
|
1362
1779
|
return "";
|
|
1363
1780
|
}
|
|
1364
1781
|
})();
|
|
1365
|
-
networkCollector.current =
|
|
1782
|
+
networkCollector.current = createNetworkCollector(flintHost ? [flintHost] : []);
|
|
1366
1783
|
networkCollector.current.start();
|
|
1367
1784
|
}
|
|
1785
|
+
const frustrationCollector = (0, import_react4.useRef)(null);
|
|
1786
|
+
if (enableFrustration && !frustrationCollector.current) {
|
|
1787
|
+
frustrationCollector.current = createFrustrationCollector();
|
|
1788
|
+
frustrationCollector.current.start();
|
|
1789
|
+
}
|
|
1368
1790
|
(0, import_react4.useEffect)(() => {
|
|
1369
1791
|
let cancelled = false;
|
|
1370
1792
|
if (enableReplay) {
|
|
@@ -1386,9 +1808,32 @@ function WidgetContent({
|
|
|
1386
1808
|
cancelled = true;
|
|
1387
1809
|
consoleCollector.current?.stop();
|
|
1388
1810
|
networkCollector.current?.stop();
|
|
1811
|
+
frustrationCollector.current?.stop();
|
|
1389
1812
|
stopReplay.current?.();
|
|
1390
1813
|
};
|
|
1391
1814
|
}, [enableReplay]);
|
|
1815
|
+
(0, import_react4.useEffect)(() => {
|
|
1816
|
+
if (!enableFrustration || !autoReportFrustration || !frustrationCollector.current) return;
|
|
1817
|
+
const unsubscribe = frustrationCollector.current.onFrustration(async (event) => {
|
|
1818
|
+
const user2 = resolvedUser;
|
|
1819
|
+
await submitReport(serverUrl, projectKey, {
|
|
1820
|
+
reporterId: user2?.id ?? "anonymous",
|
|
1821
|
+
reporterName: user2?.name ?? "Anonymous",
|
|
1822
|
+
reporterEmail: user2?.email,
|
|
1823
|
+
description: `[Auto-detected] ${event.type.replace(/_/g, " ")}: ${event.details}`,
|
|
1824
|
+
severity: event.type === "error_loop" ? "P1" : event.type === "rage_click" ? "P2" : "P3",
|
|
1825
|
+
url: event.url,
|
|
1826
|
+
meta: {
|
|
1827
|
+
environment: collectEnvironment(),
|
|
1828
|
+
consoleLogs: consoleCollector.current?.getEntries() ?? [],
|
|
1829
|
+
networkErrors: networkCollector.current?.getEntries() ?? [],
|
|
1830
|
+
frustrationEvent: event
|
|
1831
|
+
}
|
|
1832
|
+
}).catch(() => {
|
|
1833
|
+
});
|
|
1834
|
+
});
|
|
1835
|
+
return unsubscribe;
|
|
1836
|
+
}, [enableFrustration, autoReportFrustration]);
|
|
1392
1837
|
const label = buttonLabel ?? t("buttonLabel");
|
|
1393
1838
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1394
1839
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -1485,7 +1930,7 @@ function WidgetContent({
|
|
|
1485
1930
|
onClose?.();
|
|
1486
1931
|
pendingSelection.current = "";
|
|
1487
1932
|
},
|
|
1488
|
-
getEnvironment:
|
|
1933
|
+
getEnvironment: collectEnvironment,
|
|
1489
1934
|
getConsoleLogs: () => consoleCollector.current?.getEntries() ?? [],
|
|
1490
1935
|
getNetworkErrors: () => networkCollector.current?.getEntries() ?? [],
|
|
1491
1936
|
getReplayEvents: () => [...replayEvents.current],
|