glitchgrab 1.15.0 → 1.17.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.d.mts CHANGED
@@ -288,4 +288,12 @@ declare function captureContext(visitedPages: string[]): CapturedContext;
288
288
  */
289
289
  declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<ReportResult | null>;
290
290
 
291
- export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
291
+ declare function computeSignature(params: {
292
+ errorMessage: string | undefined;
293
+ pageUrl: string | undefined;
294
+ errorStack?: string | undefined;
295
+ }): string;
296
+ declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
297
+ declare function clearDedupCache(): void;
298
+
299
+ export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
package/dist/index.d.ts CHANGED
@@ -288,4 +288,12 @@ declare function captureContext(visitedPages: string[]): CapturedContext;
288
288
  */
289
289
  declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<ReportResult | null>;
290
290
 
291
- export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
291
+ declare function computeSignature(params: {
292
+ errorMessage: string | undefined;
293
+ pageUrl: string | undefined;
294
+ errorStack?: string | undefined;
295
+ }): string;
296
+ declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
297
+ declare function clearDedupCache(): void;
298
+
299
+ export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, fetchGlitchgrabReports, getBreadcrumbs, initBreadcrumbs, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
package/dist/index.js CHANGED
@@ -38,11 +38,14 @@ __export(index_exports, {
38
38
  captureContext: () => captureContext,
39
39
  captureDeviceInfo: () => captureDeviceInfo,
40
40
  clearBreadcrumbs: () => clearBreadcrumbs,
41
+ clearDedupCache: () => clearDedupCache,
42
+ computeSignature: () => computeSignature,
41
43
  fetchGlitchgrabReports: () => fetchGlitchgrabReports,
42
44
  getBreadcrumbs: () => getBreadcrumbs,
43
45
  initBreadcrumbs: () => initBreadcrumbs,
44
46
  sanitizeUrl: () => sanitizeUrl,
45
47
  sendReport: () => sendReport,
48
+ shouldSkipDuplicate: () => shouldSkipDuplicate,
46
49
  useGlitchgrab: () => useGlitchgrab,
47
50
  useGlitchgrabActions: () => useGlitchgrabActions,
48
51
  useGlitchgrabReports: () => useGlitchgrabReports
@@ -313,6 +316,73 @@ async function sendReport(payload, baseUrl) {
313
316
  }
314
317
  }
315
318
 
319
+ // src/dedup.ts
320
+ var DEFAULT_WINDOW_MS = 5 * 60 * 1e3;
321
+ var MAX_ENTRIES = 100;
322
+ var seen = /* @__PURE__ */ new Map();
323
+ function hashString(input) {
324
+ let h1 = 3735928559 ^ 0;
325
+ let h2 = 1103547991 ^ 0;
326
+ for (let i = 0; i < input.length; i++) {
327
+ const ch = input.charCodeAt(i);
328
+ h1 = Math.imul(h1 ^ ch, 2654435761);
329
+ h2 = Math.imul(h2 ^ ch, 1597334677);
330
+ }
331
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
332
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
333
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
334
+ }
335
+ function topStackFrame(stack) {
336
+ if (!stack) return "";
337
+ const lines = stack.split("\n");
338
+ for (const line of lines) {
339
+ const t = line.trim();
340
+ if (t.startsWith("at ") || t.includes("@")) {
341
+ return t;
342
+ }
343
+ }
344
+ return "";
345
+ }
346
+ function stripUrlQuery(url) {
347
+ if (!url) return "";
348
+ try {
349
+ const u = new URL(url);
350
+ return `${u.origin}${u.pathname}`;
351
+ } catch (e) {
352
+ const q = url.indexOf("?");
353
+ return q === -1 ? url : url.slice(0, q);
354
+ }
355
+ }
356
+ function computeSignature(params) {
357
+ var _a, _b;
358
+ const msg = (_b = (_a = params.errorMessage) == null ? void 0 : _a.trim()) != null ? _b : "";
359
+ const page = stripUrlQuery(params.pageUrl);
360
+ const frame = topStackFrame(params.errorStack);
361
+ return hashString(`${msg}|${page}|${frame}`);
362
+ }
363
+ function shouldSkipDuplicate(signature, windowMs = DEFAULT_WINDOW_MS, now = Date.now()) {
364
+ const last = seen.get(signature);
365
+ if (last !== void 0 && now - last < windowMs) {
366
+ return true;
367
+ }
368
+ seen.set(signature, now);
369
+ if (seen.size > MAX_ENTRIES) {
370
+ const cutoff = now - windowMs;
371
+ for (const [sig, ts] of seen) {
372
+ if (ts < cutoff) seen.delete(sig);
373
+ }
374
+ if (seen.size > MAX_ENTRIES) {
375
+ const excess = seen.size - MAX_ENTRIES;
376
+ const keys = Array.from(seen.keys()).slice(0, excess);
377
+ for (const k of keys) seen.delete(k);
378
+ }
379
+ }
380
+ return false;
381
+ }
382
+ function clearDedupCache() {
383
+ seen.clear();
384
+ }
385
+
316
386
  // src/error-boundary.tsx
317
387
  var GlitchgrabErrorBoundary = class extends React.Component {
318
388
  constructor(props) {
@@ -326,6 +396,15 @@ var GlitchgrabErrorBoundary = class extends React.Component {
326
396
  var _a, _b;
327
397
  try {
328
398
  const context = captureContext(this.props.visitedPages);
399
+ const sig = computeSignature({
400
+ errorMessage: error.message,
401
+ pageUrl: context.url,
402
+ errorStack: error.stack
403
+ });
404
+ if (shouldSkipDuplicate(sig)) {
405
+ if (this.props.onError) this.props.onError(error);
406
+ return;
407
+ }
329
408
  const payload = {
330
409
  token: this.props.token,
331
410
  source: "SDK_AUTO",
@@ -593,6 +672,30 @@ function ReportDialog({
593
672
  document.addEventListener("keydown", handleEsc);
594
673
  return () => document.removeEventListener("keydown", handleEsc);
595
674
  }, [isOpen, previewOpen]);
675
+ (0, import_react.useEffect)(() => {
676
+ if (!isOpen) return;
677
+ const handlePaste = (e) => {
678
+ var _a;
679
+ try {
680
+ const items = (_a = e.clipboardData) == null ? void 0 : _a.items;
681
+ if (!items) return;
682
+ for (let i = 0; i < items.length; i++) {
683
+ if (items[i].type.startsWith("image/")) {
684
+ const file = items[i].getAsFile();
685
+ if (!file) continue;
686
+ const reader = new FileReader();
687
+ reader.onload = () => setScreenshot(reader.result);
688
+ reader.readAsDataURL(file);
689
+ e.preventDefault();
690
+ break;
691
+ }
692
+ }
693
+ } catch (e2) {
694
+ }
695
+ };
696
+ window.addEventListener("paste", handlePaste);
697
+ return () => window.removeEventListener("paste", handlePaste);
698
+ }, [isOpen]);
596
699
  const handleClose = () => {
597
700
  setIsOpen(false);
598
701
  setStep(1);
@@ -1002,7 +1105,7 @@ function ReportDialog({
1002
1105
  background: "none",
1003
1106
  cursor: "pointer"
1004
1107
  },
1005
- children: "Upload screenshot"
1108
+ children: "Upload or paste (\u2318V)"
1006
1109
  }
1007
1110
  ),
1008
1111
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -1257,25 +1360,31 @@ function GlitchgrabProviderInner({
1257
1360
  if (typeof window === "undefined") return;
1258
1361
  if (process.env.NODE_ENV === "development") return;
1259
1362
  const handleError = (event) => {
1260
- var _a, _b, _c, _d, _e;
1363
+ var _a, _b, _c, _d, _e, _f;
1261
1364
  try {
1262
1365
  const context = captureContext(visitedPagesRef.current);
1366
+ const sig = computeSignature({
1367
+ errorMessage: event.message,
1368
+ pageUrl: context.url,
1369
+ errorStack: (_a = event.error) == null ? void 0 : _a.stack
1370
+ });
1371
+ if (shouldSkipDuplicate(sig)) return;
1263
1372
  const payload = {
1264
1373
  token,
1265
1374
  source: "SDK_AUTO",
1266
1375
  type: "BUG",
1267
1376
  errorMessage: event.message,
1268
- errorStack: (_a = event.error) == null ? void 0 : _a.stack,
1377
+ errorStack: (_b = event.error) == null ? void 0 : _b.stack,
1269
1378
  pageUrl: context.url,
1270
1379
  userAgent: context.userAgent,
1271
1380
  breadcrumbs: context.breadcrumbs,
1272
- deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
1381
+ deviceInfo: (_c = context.deviceInfo) != null ? _c : void 0,
1273
1382
  metadata: {
1274
1383
  timestamp: context.timestamp,
1275
1384
  visitedPages: JSON.stringify(context.visitedPages),
1276
- filename: (_c = event.filename) != null ? _c : "",
1277
- lineno: String((_d = event.lineno) != null ? _d : ""),
1278
- colno: String((_e = event.colno) != null ? _e : ""),
1385
+ filename: (_d = event.filename) != null ? _d : "",
1386
+ lineno: String((_e = event.lineno) != null ? _e : ""),
1387
+ colno: String((_f = event.colno) != null ? _f : ""),
1279
1388
  ...(session == null ? void 0 : session.userId) ? { sessionUserId: session.userId } : {},
1280
1389
  ...(session == null ? void 0 : session.name) ? { sessionUserName: String(session.name) } : {},
1281
1390
  ...(session == null ? void 0 : session.email) ? { sessionUserEmail: String(session.email) } : {}
@@ -1293,12 +1402,20 @@ function GlitchgrabProviderInner({
1293
1402
  try {
1294
1403
  const context = captureContext(visitedPagesRef.current);
1295
1404
  const reason = event.reason;
1405
+ const errMsg = reason instanceof Error ? reason.message : String(reason);
1406
+ const errStack = reason instanceof Error ? reason.stack : void 0;
1407
+ const sig = computeSignature({
1408
+ errorMessage: errMsg,
1409
+ pageUrl: context.url,
1410
+ errorStack: errStack
1411
+ });
1412
+ if (shouldSkipDuplicate(sig)) return;
1296
1413
  const payload = {
1297
1414
  token,
1298
1415
  source: "SDK_AUTO",
1299
1416
  type: "BUG",
1300
- errorMessage: reason instanceof Error ? reason.message : String(reason),
1301
- errorStack: reason instanceof Error ? reason.stack : void 0,
1417
+ errorMessage: errMsg,
1418
+ errorStack: errStack,
1302
1419
  pageUrl: context.url,
1303
1420
  userAgent: context.userAgent,
1304
1421
  breadcrumbs: context.breadcrumbs,
@@ -1376,6 +1493,20 @@ function GlitchgrabProviderInner({
1376
1493
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
1377
1494
  }
1378
1495
  }, []);
1496
+ (0, import_react2.useEffect)(() => {
1497
+ try {
1498
+ if (typeof window === "undefined") return;
1499
+ const handleKeyDown = (e) => {
1500
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g") {
1501
+ e.preventDefault();
1502
+ openReportDialog();
1503
+ }
1504
+ };
1505
+ window.addEventListener("keydown", handleKeyDown);
1506
+ return () => window.removeEventListener("keydown", handleKeyDown);
1507
+ } catch (e) {
1508
+ }
1509
+ }, [openReportDialog]);
1379
1510
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1380
1511
  GlitchgrabContext.Provider,
1381
1512
  {
@@ -1623,11 +1754,14 @@ function useGlitchgrabActions({
1623
1754
  captureContext,
1624
1755
  captureDeviceInfo,
1625
1756
  clearBreadcrumbs,
1757
+ clearDedupCache,
1758
+ computeSignature,
1626
1759
  fetchGlitchgrabReports,
1627
1760
  getBreadcrumbs,
1628
1761
  initBreadcrumbs,
1629
1762
  sanitizeUrl,
1630
1763
  sendReport,
1764
+ shouldSkipDuplicate,
1631
1765
  useGlitchgrab,
1632
1766
  useGlitchgrabActions,
1633
1767
  useGlitchgrabReports