glitchgrab 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/index.d.mts +149 -0
- package/dist/index.d.ts +149 -0
- package/dist/index.js +828 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +787 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
GlitchgrabErrorBoundary: () => GlitchgrabErrorBoundary,
|
|
35
|
+
GlitchgrabProvider: () => GlitchgrabProvider,
|
|
36
|
+
ReportButton: () => ReportButton,
|
|
37
|
+
addBreadcrumb: () => addBreadcrumb,
|
|
38
|
+
captureContext: () => captureContext,
|
|
39
|
+
captureDeviceInfo: () => captureDeviceInfo,
|
|
40
|
+
clearBreadcrumbs: () => clearBreadcrumbs,
|
|
41
|
+
getBreadcrumbs: () => getBreadcrumbs,
|
|
42
|
+
initBreadcrumbs: () => initBreadcrumbs,
|
|
43
|
+
sanitizeUrl: () => sanitizeUrl,
|
|
44
|
+
sendReport: () => sendReport,
|
|
45
|
+
useGlitchgrab: () => useGlitchgrab
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/provider.tsx
|
|
50
|
+
var import_react2 = require("react");
|
|
51
|
+
|
|
52
|
+
// src/error-boundary.tsx
|
|
53
|
+
var import_react = __toESM(require("react"));
|
|
54
|
+
|
|
55
|
+
// src/breadcrumbs.ts
|
|
56
|
+
var MAX_DEFAULT = 50;
|
|
57
|
+
var breadcrumbs = [];
|
|
58
|
+
var maxBreadcrumbs = MAX_DEFAULT;
|
|
59
|
+
var initialized = false;
|
|
60
|
+
function initBreadcrumbs(max) {
|
|
61
|
+
if (initialized) return;
|
|
62
|
+
maxBreadcrumbs = max != null ? max : MAX_DEFAULT;
|
|
63
|
+
try {
|
|
64
|
+
if (typeof window === "undefined") return;
|
|
65
|
+
interceptConsole();
|
|
66
|
+
interceptFetch();
|
|
67
|
+
interceptNavigation();
|
|
68
|
+
interceptClicks();
|
|
69
|
+
initialized = true;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function addBreadcrumb(type, message, data) {
|
|
74
|
+
try {
|
|
75
|
+
breadcrumbs.push({
|
|
76
|
+
type,
|
|
77
|
+
message: message.slice(0, 200),
|
|
78
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
79
|
+
data
|
|
80
|
+
});
|
|
81
|
+
if (breadcrumbs.length > maxBreadcrumbs) {
|
|
82
|
+
breadcrumbs = breadcrumbs.slice(-maxBreadcrumbs);
|
|
83
|
+
}
|
|
84
|
+
} catch (e) {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getBreadcrumbs() {
|
|
88
|
+
return [...breadcrumbs];
|
|
89
|
+
}
|
|
90
|
+
function clearBreadcrumbs() {
|
|
91
|
+
breadcrumbs = [];
|
|
92
|
+
}
|
|
93
|
+
function interceptConsole() {
|
|
94
|
+
const origLog = console.log;
|
|
95
|
+
const origWarn = console.warn;
|
|
96
|
+
const origError = console.error;
|
|
97
|
+
console.log = function(...args) {
|
|
98
|
+
addBreadcrumb("console", `[log] ${argsToString(args)}`);
|
|
99
|
+
origLog.apply(console, args);
|
|
100
|
+
};
|
|
101
|
+
console.warn = function(...args) {
|
|
102
|
+
addBreadcrumb("console", `[warn] ${argsToString(args)}`);
|
|
103
|
+
origWarn.apply(console, args);
|
|
104
|
+
};
|
|
105
|
+
console.error = function(...args) {
|
|
106
|
+
addBreadcrumb("console", `[error] ${argsToString(args)}`);
|
|
107
|
+
origError.apply(console, args);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function argsToString(args) {
|
|
111
|
+
try {
|
|
112
|
+
return args.map((a) => {
|
|
113
|
+
if (typeof a === "string") return a;
|
|
114
|
+
if (a instanceof Error) return a.message;
|
|
115
|
+
try {
|
|
116
|
+
return JSON.stringify(a);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
return String(a);
|
|
119
|
+
}
|
|
120
|
+
}).join(" ").slice(0, 200);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return "[unknown]";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function interceptFetch() {
|
|
126
|
+
const origFetch = window.fetch;
|
|
127
|
+
window.fetch = async function(input, init) {
|
|
128
|
+
var _a;
|
|
129
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input instanceof Request ? input.url : String(input);
|
|
130
|
+
const method = (_a = init == null ? void 0 : init.method) != null ? _a : "GET";
|
|
131
|
+
const start = Date.now();
|
|
132
|
+
try {
|
|
133
|
+
const response = await origFetch.apply(window, [input, init]);
|
|
134
|
+
addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 ${response.status}`, {
|
|
135
|
+
method,
|
|
136
|
+
status: String(response.status),
|
|
137
|
+
duration: `${Date.now() - start}ms`
|
|
138
|
+
});
|
|
139
|
+
return response;
|
|
140
|
+
} catch (err) {
|
|
141
|
+
addBreadcrumb("api", `${method} ${url.slice(0, 100)} \u2192 FAILED`, {
|
|
142
|
+
method,
|
|
143
|
+
error: err instanceof Error ? err.message : "unknown",
|
|
144
|
+
duration: `${Date.now() - start}ms`
|
|
145
|
+
});
|
|
146
|
+
throw err;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function interceptNavigation() {
|
|
151
|
+
const origPush = history.pushState.bind(history);
|
|
152
|
+
const origReplace = history.replaceState.bind(history);
|
|
153
|
+
history.pushState = function(...args) {
|
|
154
|
+
origPush(...args);
|
|
155
|
+
addBreadcrumb("navigation", `Navigate to ${window.location.pathname}`);
|
|
156
|
+
};
|
|
157
|
+
history.replaceState = function(...args) {
|
|
158
|
+
origReplace(...args);
|
|
159
|
+
addBreadcrumb("navigation", `Replace to ${window.location.pathname}`);
|
|
160
|
+
};
|
|
161
|
+
window.addEventListener("popstate", () => {
|
|
162
|
+
addBreadcrumb("navigation", `Back/Forward to ${window.location.pathname}`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function interceptClicks() {
|
|
166
|
+
document.addEventListener(
|
|
167
|
+
"click",
|
|
168
|
+
(e) => {
|
|
169
|
+
var _a, _b, _c;
|
|
170
|
+
try {
|
|
171
|
+
const target = e.target;
|
|
172
|
+
const tag = (_a = target.tagName) == null ? void 0 : _a.toLowerCase();
|
|
173
|
+
const text = (_c = (_b = target.textContent) == null ? void 0 : _b.trim().slice(0, 50)) != null ? _c : "";
|
|
174
|
+
const id = target.id ? `#${target.id}` : "";
|
|
175
|
+
const cls = target.className && typeof target.className === "string" ? `.${target.className.split(" ")[0]}` : "";
|
|
176
|
+
addBreadcrumb("click", `Click ${tag}${id}${cls} "${text}"`);
|
|
177
|
+
} catch (e2) {
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{ capture: true }
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// src/utils.ts
|
|
185
|
+
var SENSITIVE_PARAMS = [
|
|
186
|
+
"token",
|
|
187
|
+
"key",
|
|
188
|
+
"secret",
|
|
189
|
+
"password",
|
|
190
|
+
"passwd",
|
|
191
|
+
"auth",
|
|
192
|
+
"authorization",
|
|
193
|
+
"session",
|
|
194
|
+
"sessionid",
|
|
195
|
+
"session_id",
|
|
196
|
+
"api_key",
|
|
197
|
+
"apikey",
|
|
198
|
+
"access_token",
|
|
199
|
+
"refresh_token",
|
|
200
|
+
"client_secret",
|
|
201
|
+
"code",
|
|
202
|
+
"state",
|
|
203
|
+
"nonce",
|
|
204
|
+
"credential",
|
|
205
|
+
"private"
|
|
206
|
+
];
|
|
207
|
+
function sanitizeUrl(url) {
|
|
208
|
+
var _a;
|
|
209
|
+
try {
|
|
210
|
+
const parsed = new URL(url);
|
|
211
|
+
const params = new URLSearchParams(parsed.search);
|
|
212
|
+
let modified = false;
|
|
213
|
+
for (const key of Array.from(params.keys())) {
|
|
214
|
+
if (SENSITIVE_PARAMS.some((s) => key.toLowerCase().includes(s))) {
|
|
215
|
+
params.set(key, "[REDACTED]");
|
|
216
|
+
modified = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (modified) parsed.search = params.toString();
|
|
220
|
+
return parsed.toString();
|
|
221
|
+
} catch (e) {
|
|
222
|
+
return (_a = url.split("?")[0]) != null ? _a : url;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function captureDeviceInfo() {
|
|
226
|
+
var _a, _b, _c;
|
|
227
|
+
try {
|
|
228
|
+
if (typeof window === "undefined") return null;
|
|
229
|
+
return {
|
|
230
|
+
screenWidth: screen.width,
|
|
231
|
+
screenHeight: screen.height,
|
|
232
|
+
viewportWidth: window.innerWidth,
|
|
233
|
+
viewportHeight: window.innerHeight,
|
|
234
|
+
platform: (_a = navigator.platform) != null ? _a : "unknown",
|
|
235
|
+
language: (_b = navigator.language) != null ? _b : "unknown",
|
|
236
|
+
online: navigator.onLine,
|
|
237
|
+
colorScheme: window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
|
|
238
|
+
devicePixelRatio: (_c = window.devicePixelRatio) != null ? _c : 1
|
|
239
|
+
};
|
|
240
|
+
} catch (e) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function captureContext(visitedPages) {
|
|
245
|
+
try {
|
|
246
|
+
return {
|
|
247
|
+
url: sanitizeUrl(typeof window !== "undefined" ? window.location.href : ""),
|
|
248
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
249
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
250
|
+
visitedPages: visitedPages.map(sanitizeUrl),
|
|
251
|
+
breadcrumbs: getBreadcrumbs(),
|
|
252
|
+
deviceInfo: captureDeviceInfo()
|
|
253
|
+
};
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return {
|
|
256
|
+
url: "",
|
|
257
|
+
userAgent: "",
|
|
258
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259
|
+
visitedPages: [],
|
|
260
|
+
breadcrumbs: [],
|
|
261
|
+
deviceInfo: null
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
var DEFAULT_BASE_URL = "https://glitchgrab.dev";
|
|
266
|
+
var MAX_RETRIES = 3;
|
|
267
|
+
var RETRY_BASE_MS = 500;
|
|
268
|
+
async function sendReport(payload, baseUrl) {
|
|
269
|
+
var _a;
|
|
270
|
+
try {
|
|
271
|
+
const url = `${baseUrl != null ? baseUrl : DEFAULT_BASE_URL}/api/v1/sdk/report`;
|
|
272
|
+
const body = JSON.stringify(payload);
|
|
273
|
+
const headers = {
|
|
274
|
+
"Content-Type": "application/json",
|
|
275
|
+
Authorization: `Bearer ${payload.token}`
|
|
276
|
+
};
|
|
277
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch(url, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers,
|
|
282
|
+
body,
|
|
283
|
+
keepalive: true
|
|
284
|
+
});
|
|
285
|
+
if (response.ok) {
|
|
286
|
+
const data = await response.json();
|
|
287
|
+
return (_a = data.data) != null ? _a : { success: data.success };
|
|
288
|
+
}
|
|
289
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
}
|
|
294
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
295
|
+
await new Promise(
|
|
296
|
+
(resolve) => setTimeout(resolve, RETRY_BASE_MS * Math.pow(2, attempt))
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
301
|
+
try {
|
|
302
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
303
|
+
} catch (e) {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
} catch (e) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/error-boundary.tsx
|
|
313
|
+
var GlitchgrabErrorBoundary = class extends import_react.default.Component {
|
|
314
|
+
constructor(props) {
|
|
315
|
+
super(props);
|
|
316
|
+
this.state = { hasError: false };
|
|
317
|
+
}
|
|
318
|
+
static getDerivedStateFromError() {
|
|
319
|
+
return { hasError: true };
|
|
320
|
+
}
|
|
321
|
+
componentDidCatch(error, errorInfo) {
|
|
322
|
+
var _a, _b;
|
|
323
|
+
try {
|
|
324
|
+
const context = captureContext(this.props.visitedPages);
|
|
325
|
+
const payload = {
|
|
326
|
+
token: this.props.token,
|
|
327
|
+
source: "SDK_AUTO",
|
|
328
|
+
type: "BUG",
|
|
329
|
+
errorMessage: error.message,
|
|
330
|
+
errorStack: error.stack,
|
|
331
|
+
componentStack: (_a = errorInfo.componentStack) != null ? _a : void 0,
|
|
332
|
+
pageUrl: context.url,
|
|
333
|
+
userAgent: context.userAgent,
|
|
334
|
+
breadcrumbs: context.breadcrumbs,
|
|
335
|
+
deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
|
|
336
|
+
metadata: {
|
|
337
|
+
timestamp: context.timestamp,
|
|
338
|
+
visitedPages: JSON.stringify(context.visitedPages)
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
sendReport(payload, this.props.baseUrl);
|
|
342
|
+
if (this.props.onError) {
|
|
343
|
+
this.props.onError(error);
|
|
344
|
+
}
|
|
345
|
+
} catch (e) {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
render() {
|
|
349
|
+
if (this.state.hasError) {
|
|
350
|
+
if (this.props.fallback) {
|
|
351
|
+
return this.props.fallback;
|
|
352
|
+
}
|
|
353
|
+
return this.props.children;
|
|
354
|
+
}
|
|
355
|
+
return this.props.children;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// src/provider.tsx
|
|
360
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
361
|
+
var DEFAULT_BASE_URL2 = "https://glitchgrab.dev";
|
|
362
|
+
var GlitchgrabContext = (0, import_react2.createContext)(null);
|
|
363
|
+
function useGlitchgrab() {
|
|
364
|
+
const ctx = (0, import_react2.useContext)(GlitchgrabContext);
|
|
365
|
+
if (!ctx) {
|
|
366
|
+
throw new Error("useGlitchgrab must be used within a GlitchgrabProvider");
|
|
367
|
+
}
|
|
368
|
+
return ctx;
|
|
369
|
+
}
|
|
370
|
+
function GlitchgrabProviderInner({
|
|
371
|
+
token,
|
|
372
|
+
baseUrl,
|
|
373
|
+
onError,
|
|
374
|
+
onReportSent,
|
|
375
|
+
breadcrumbs: enableBreadcrumbs = true,
|
|
376
|
+
maxBreadcrumbs: maxBreadcrumbs2 = 50,
|
|
377
|
+
children,
|
|
378
|
+
fallback
|
|
379
|
+
}) {
|
|
380
|
+
const visitedPagesRef = (0, import_react2.useRef)([]);
|
|
381
|
+
(0, import_react2.useEffect)(() => {
|
|
382
|
+
if (enableBreadcrumbs) {
|
|
383
|
+
initBreadcrumbs(maxBreadcrumbs2);
|
|
384
|
+
}
|
|
385
|
+
}, [enableBreadcrumbs, maxBreadcrumbs2]);
|
|
386
|
+
(0, import_react2.useEffect)(() => {
|
|
387
|
+
try {
|
|
388
|
+
if (typeof window === "undefined") return;
|
|
389
|
+
const trackPage = () => {
|
|
390
|
+
try {
|
|
391
|
+
const sanitized = sanitizeUrl(window.location.href);
|
|
392
|
+
const pages = visitedPagesRef.current;
|
|
393
|
+
if (pages[pages.length - 1] !== sanitized) {
|
|
394
|
+
pages.push(sanitized);
|
|
395
|
+
if (pages.length > 20) {
|
|
396
|
+
pages.splice(0, pages.length - 20);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
} catch (e) {
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
trackPage();
|
|
403
|
+
const handlePopState = () => trackPage();
|
|
404
|
+
window.addEventListener("popstate", handlePopState);
|
|
405
|
+
const origPushState = history.pushState.bind(history);
|
|
406
|
+
const origReplaceState = history.replaceState.bind(history);
|
|
407
|
+
history.pushState = function(...args) {
|
|
408
|
+
origPushState(...args);
|
|
409
|
+
trackPage();
|
|
410
|
+
};
|
|
411
|
+
history.replaceState = function(...args) {
|
|
412
|
+
origReplaceState(...args);
|
|
413
|
+
trackPage();
|
|
414
|
+
};
|
|
415
|
+
return () => {
|
|
416
|
+
window.removeEventListener("popstate", handlePopState);
|
|
417
|
+
history.pushState = origPushState;
|
|
418
|
+
history.replaceState = origReplaceState;
|
|
419
|
+
};
|
|
420
|
+
} catch (e) {
|
|
421
|
+
}
|
|
422
|
+
}, []);
|
|
423
|
+
(0, import_react2.useEffect)(() => {
|
|
424
|
+
try {
|
|
425
|
+
if (typeof window === "undefined") return;
|
|
426
|
+
const handleError = (event) => {
|
|
427
|
+
var _a, _b, _c, _d, _e;
|
|
428
|
+
try {
|
|
429
|
+
const context = captureContext(visitedPagesRef.current);
|
|
430
|
+
const payload = {
|
|
431
|
+
token,
|
|
432
|
+
source: "SDK_AUTO",
|
|
433
|
+
type: "BUG",
|
|
434
|
+
errorMessage: event.message,
|
|
435
|
+
errorStack: (_a = event.error) == null ? void 0 : _a.stack,
|
|
436
|
+
pageUrl: context.url,
|
|
437
|
+
userAgent: context.userAgent,
|
|
438
|
+
breadcrumbs: context.breadcrumbs,
|
|
439
|
+
deviceInfo: (_b = context.deviceInfo) != null ? _b : void 0,
|
|
440
|
+
metadata: {
|
|
441
|
+
timestamp: context.timestamp,
|
|
442
|
+
visitedPages: JSON.stringify(context.visitedPages),
|
|
443
|
+
filename: (_c = event.filename) != null ? _c : "",
|
|
444
|
+
lineno: String((_d = event.lineno) != null ? _d : ""),
|
|
445
|
+
colno: String((_e = event.colno) != null ? _e : "")
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
sendReport(payload, baseUrl).then((result) => {
|
|
449
|
+
if (result && onReportSent) onReportSent(result);
|
|
450
|
+
});
|
|
451
|
+
if (onError && event.error) onError(event.error);
|
|
452
|
+
} catch (e) {
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
const handleRejection = (event) => {
|
|
456
|
+
var _a;
|
|
457
|
+
try {
|
|
458
|
+
const context = captureContext(visitedPagesRef.current);
|
|
459
|
+
const reason = event.reason;
|
|
460
|
+
const payload = {
|
|
461
|
+
token,
|
|
462
|
+
source: "SDK_AUTO",
|
|
463
|
+
type: "BUG",
|
|
464
|
+
errorMessage: reason instanceof Error ? reason.message : String(reason),
|
|
465
|
+
errorStack: reason instanceof Error ? reason.stack : void 0,
|
|
466
|
+
pageUrl: context.url,
|
|
467
|
+
userAgent: context.userAgent,
|
|
468
|
+
breadcrumbs: context.breadcrumbs,
|
|
469
|
+
deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
|
|
470
|
+
metadata: {
|
|
471
|
+
timestamp: context.timestamp,
|
|
472
|
+
visitedPages: JSON.stringify(context.visitedPages),
|
|
473
|
+
type: "unhandledrejection"
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
sendReport(payload, baseUrl).then((result) => {
|
|
477
|
+
if (result && onReportSent) onReportSent(result);
|
|
478
|
+
});
|
|
479
|
+
if (onError && reason instanceof Error) onError(reason);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
window.addEventListener("error", handleError);
|
|
484
|
+
window.addEventListener("unhandledrejection", handleRejection);
|
|
485
|
+
return () => {
|
|
486
|
+
window.removeEventListener("error", handleError);
|
|
487
|
+
window.removeEventListener("unhandledrejection", handleRejection);
|
|
488
|
+
};
|
|
489
|
+
} catch (e) {
|
|
490
|
+
}
|
|
491
|
+
}, [token, baseUrl, onError, onReportSent]);
|
|
492
|
+
const report = (0, import_react2.useCallback)(
|
|
493
|
+
async (type, description, metadata) => {
|
|
494
|
+
var _a;
|
|
495
|
+
try {
|
|
496
|
+
const context = captureContext(visitedPagesRef.current);
|
|
497
|
+
const payload = {
|
|
498
|
+
token,
|
|
499
|
+
source: "SDK_USER_REPORT",
|
|
500
|
+
type,
|
|
501
|
+
description,
|
|
502
|
+
pageUrl: context.url,
|
|
503
|
+
userAgent: context.userAgent,
|
|
504
|
+
breadcrumbs: context.breadcrumbs,
|
|
505
|
+
deviceInfo: (_a = context.deviceInfo) != null ? _a : void 0,
|
|
506
|
+
metadata: {
|
|
507
|
+
timestamp: context.timestamp,
|
|
508
|
+
visitedPages: JSON.stringify(context.visitedPages),
|
|
509
|
+
...metadata
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const result = await sendReport(payload, baseUrl);
|
|
513
|
+
if (result && onReportSent) onReportSent(result);
|
|
514
|
+
return result;
|
|
515
|
+
} catch (e) {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
[token, baseUrl, onReportSent]
|
|
520
|
+
);
|
|
521
|
+
const reportBug = (0, import_react2.useCallback)(
|
|
522
|
+
(description, metadata) => report("BUG", description, metadata),
|
|
523
|
+
[report]
|
|
524
|
+
);
|
|
525
|
+
const addBreadcrumb2 = (0, import_react2.useCallback)(
|
|
526
|
+
(message, data) => {
|
|
527
|
+
addBreadcrumb("custom", message, data);
|
|
528
|
+
},
|
|
529
|
+
[]
|
|
530
|
+
);
|
|
531
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
532
|
+
GlitchgrabContext.Provider,
|
|
533
|
+
{
|
|
534
|
+
value: {
|
|
535
|
+
token,
|
|
536
|
+
baseUrl: baseUrl != null ? baseUrl : DEFAULT_BASE_URL2,
|
|
537
|
+
reportBug,
|
|
538
|
+
report,
|
|
539
|
+
addBreadcrumb: addBreadcrumb2
|
|
540
|
+
},
|
|
541
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
542
|
+
GlitchgrabErrorBoundary,
|
|
543
|
+
{
|
|
544
|
+
token,
|
|
545
|
+
baseUrl,
|
|
546
|
+
onError,
|
|
547
|
+
fallback,
|
|
548
|
+
visitedPages: visitedPagesRef.current,
|
|
549
|
+
children
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
function GlitchgrabProvider(props) {
|
|
556
|
+
try {
|
|
557
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GlitchgrabProviderInner, { ...props });
|
|
558
|
+
} catch (e) {
|
|
559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: props.children });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/report-button.tsx
|
|
564
|
+
var import_react3 = require("react");
|
|
565
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
566
|
+
function ReportButton({
|
|
567
|
+
position = "bottom-right",
|
|
568
|
+
label = "Report Bug",
|
|
569
|
+
className
|
|
570
|
+
}) {
|
|
571
|
+
const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
|
|
572
|
+
const [description, setDescription] = (0, import_react3.useState)("");
|
|
573
|
+
const [isSubmitting, setIsSubmitting] = (0, import_react3.useState)(false);
|
|
574
|
+
const [submitted, setSubmitted] = (0, import_react3.useState)(false);
|
|
575
|
+
const modalRef = (0, import_react3.useRef)(null);
|
|
576
|
+
const { reportBug } = useGlitchgrab();
|
|
577
|
+
(0, import_react3.useEffect)(() => {
|
|
578
|
+
try {
|
|
579
|
+
if (!isOpen) return;
|
|
580
|
+
const handleClickOutside = (event) => {
|
|
581
|
+
if (modalRef.current && !modalRef.current.contains(event.target)) {
|
|
582
|
+
setIsOpen(false);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
586
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
587
|
+
} catch (e) {
|
|
588
|
+
}
|
|
589
|
+
}, [isOpen]);
|
|
590
|
+
const handleSubmit = () => {
|
|
591
|
+
try {
|
|
592
|
+
if (!description.trim() || isSubmitting) return;
|
|
593
|
+
setIsSubmitting(true);
|
|
594
|
+
reportBug(description.trim());
|
|
595
|
+
setSubmitted(true);
|
|
596
|
+
setDescription("");
|
|
597
|
+
setIsSubmitting(false);
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
setSubmitted(false);
|
|
600
|
+
setIsOpen(false);
|
|
601
|
+
}, 2e3);
|
|
602
|
+
} catch (e) {
|
|
603
|
+
setIsSubmitting(false);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
const positionStyles = position === "bottom-left" ? { left: "16px", bottom: "16px" } : { right: "16px", bottom: "16px" };
|
|
607
|
+
const modalPositionStyles = position === "bottom-left" ? { left: "16px", bottom: "64px" } : { right: "16px", bottom: "64px" };
|
|
608
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
609
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
610
|
+
"button",
|
|
611
|
+
{
|
|
612
|
+
type: "button",
|
|
613
|
+
onClick: () => {
|
|
614
|
+
setIsOpen(!isOpen);
|
|
615
|
+
setSubmitted(false);
|
|
616
|
+
},
|
|
617
|
+
className,
|
|
618
|
+
style: {
|
|
619
|
+
position: "fixed",
|
|
620
|
+
...positionStyles,
|
|
621
|
+
zIndex: 99999,
|
|
622
|
+
padding: "10px 18px",
|
|
623
|
+
borderRadius: "24px",
|
|
624
|
+
border: "none",
|
|
625
|
+
backgroundColor: "#18181b",
|
|
626
|
+
color: "#fafafa",
|
|
627
|
+
fontSize: "14px",
|
|
628
|
+
fontWeight: 500,
|
|
629
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
630
|
+
cursor: "pointer",
|
|
631
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15), 0 1px 3px rgba(0, 0, 0, 0.1)",
|
|
632
|
+
transition: "transform 0.15s ease, box-shadow 0.15s ease",
|
|
633
|
+
display: "flex",
|
|
634
|
+
alignItems: "center",
|
|
635
|
+
gap: "6px"
|
|
636
|
+
},
|
|
637
|
+
onMouseEnter: (e) => {
|
|
638
|
+
e.target.style.transform = "scale(1.05)";
|
|
639
|
+
},
|
|
640
|
+
onMouseLeave: (e) => {
|
|
641
|
+
e.target.style.transform = "scale(1)";
|
|
642
|
+
},
|
|
643
|
+
"aria-label": label,
|
|
644
|
+
children: [
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
646
|
+
"svg",
|
|
647
|
+
{
|
|
648
|
+
width: "16",
|
|
649
|
+
height: "16",
|
|
650
|
+
viewBox: "0 0 16 16",
|
|
651
|
+
fill: "none",
|
|
652
|
+
style: { flexShrink: 0 },
|
|
653
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
654
|
+
"path",
|
|
655
|
+
{
|
|
656
|
+
d: "M8 1C4.134 1 1 4.134 1 8s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zm.75-3.5a.75.75 0 01-1.5 0V5a.75.75 0 011.5 0v5z",
|
|
657
|
+
fill: "currentColor"
|
|
658
|
+
}
|
|
659
|
+
)
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
label
|
|
663
|
+
]
|
|
664
|
+
}
|
|
665
|
+
),
|
|
666
|
+
isOpen && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
667
|
+
"div",
|
|
668
|
+
{
|
|
669
|
+
ref: modalRef,
|
|
670
|
+
style: {
|
|
671
|
+
position: "fixed",
|
|
672
|
+
...modalPositionStyles,
|
|
673
|
+
zIndex: 1e5,
|
|
674
|
+
width: "320px",
|
|
675
|
+
backgroundColor: "#ffffff",
|
|
676
|
+
borderRadius: "12px",
|
|
677
|
+
boxShadow: "0 20px 60px rgba(0, 0, 0, 0.15), 0 4px 16px rgba(0, 0, 0, 0.1)",
|
|
678
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
679
|
+
overflow: "hidden"
|
|
680
|
+
},
|
|
681
|
+
children: [
|
|
682
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
683
|
+
"div",
|
|
684
|
+
{
|
|
685
|
+
style: {
|
|
686
|
+
padding: "16px 16px 12px",
|
|
687
|
+
borderBottom: "1px solid #e4e4e7"
|
|
688
|
+
},
|
|
689
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
690
|
+
"div",
|
|
691
|
+
{
|
|
692
|
+
style: {
|
|
693
|
+
display: "flex",
|
|
694
|
+
justifyContent: "space-between",
|
|
695
|
+
alignItems: "center"
|
|
696
|
+
},
|
|
697
|
+
children: [
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
699
|
+
"span",
|
|
700
|
+
{
|
|
701
|
+
style: {
|
|
702
|
+
fontSize: "15px",
|
|
703
|
+
fontWeight: 600,
|
|
704
|
+
color: "#18181b"
|
|
705
|
+
},
|
|
706
|
+
children: "Report a Bug"
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
710
|
+
"button",
|
|
711
|
+
{
|
|
712
|
+
type: "button",
|
|
713
|
+
onClick: () => setIsOpen(false),
|
|
714
|
+
style: {
|
|
715
|
+
background: "none",
|
|
716
|
+
border: "none",
|
|
717
|
+
cursor: "pointer",
|
|
718
|
+
padding: "4px",
|
|
719
|
+
color: "#71717a",
|
|
720
|
+
fontSize: "18px",
|
|
721
|
+
lineHeight: 1
|
|
722
|
+
},
|
|
723
|
+
"aria-label": "Close",
|
|
724
|
+
children: "\xD7"
|
|
725
|
+
}
|
|
726
|
+
)
|
|
727
|
+
]
|
|
728
|
+
}
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
),
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
733
|
+
"div",
|
|
734
|
+
{
|
|
735
|
+
style: {
|
|
736
|
+
textAlign: "center",
|
|
737
|
+
padding: "20px 0",
|
|
738
|
+
color: "#16a34a",
|
|
739
|
+
fontSize: "14px",
|
|
740
|
+
fontWeight: 500
|
|
741
|
+
},
|
|
742
|
+
children: "Bug report sent. Thank you!"
|
|
743
|
+
}
|
|
744
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
745
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
746
|
+
"textarea",
|
|
747
|
+
{
|
|
748
|
+
value: description,
|
|
749
|
+
onChange: (e) => setDescription(e.target.value),
|
|
750
|
+
placeholder: "Describe what went wrong...",
|
|
751
|
+
style: {
|
|
752
|
+
width: "100%",
|
|
753
|
+
minHeight: "100px",
|
|
754
|
+
padding: "10px 12px",
|
|
755
|
+
borderRadius: "8px",
|
|
756
|
+
border: "1px solid #d4d4d8",
|
|
757
|
+
fontSize: "14px",
|
|
758
|
+
fontFamily: "inherit",
|
|
759
|
+
resize: "vertical",
|
|
760
|
+
outline: "none",
|
|
761
|
+
boxSizing: "border-box",
|
|
762
|
+
color: "#18181b",
|
|
763
|
+
backgroundColor: "#fafafa"
|
|
764
|
+
},
|
|
765
|
+
onFocus: (e) => {
|
|
766
|
+
e.target.style.borderColor = "#18181b";
|
|
767
|
+
},
|
|
768
|
+
onBlur: (e) => {
|
|
769
|
+
e.target.style.borderColor = "#d4d4d8";
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
),
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
774
|
+
"button",
|
|
775
|
+
{
|
|
776
|
+
type: "button",
|
|
777
|
+
onClick: handleSubmit,
|
|
778
|
+
disabled: !description.trim() || isSubmitting,
|
|
779
|
+
style: {
|
|
780
|
+
marginTop: "12px",
|
|
781
|
+
width: "100%",
|
|
782
|
+
padding: "10px",
|
|
783
|
+
borderRadius: "8px",
|
|
784
|
+
border: "none",
|
|
785
|
+
backgroundColor: !description.trim() || isSubmitting ? "#a1a1aa" : "#18181b",
|
|
786
|
+
color: "#fafafa",
|
|
787
|
+
fontSize: "14px",
|
|
788
|
+
fontWeight: 500,
|
|
789
|
+
cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
|
|
790
|
+
fontFamily: "inherit",
|
|
791
|
+
transition: "background-color 0.15s ease"
|
|
792
|
+
},
|
|
793
|
+
children: isSubmitting ? "Sending..." : "Send Report"
|
|
794
|
+
}
|
|
795
|
+
)
|
|
796
|
+
] }) }),
|
|
797
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
798
|
+
"div",
|
|
799
|
+
{
|
|
800
|
+
style: {
|
|
801
|
+
padding: "8px 16px 10px",
|
|
802
|
+
borderTop: "1px solid #e4e4e7",
|
|
803
|
+
textAlign: "center"
|
|
804
|
+
},
|
|
805
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "11px", color: "#a1a1aa" }, children: "Powered by Glitchgrab" })
|
|
806
|
+
}
|
|
807
|
+
)
|
|
808
|
+
]
|
|
809
|
+
}
|
|
810
|
+
)
|
|
811
|
+
] });
|
|
812
|
+
}
|
|
813
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
814
|
+
0 && (module.exports = {
|
|
815
|
+
GlitchgrabErrorBoundary,
|
|
816
|
+
GlitchgrabProvider,
|
|
817
|
+
ReportButton,
|
|
818
|
+
addBreadcrumb,
|
|
819
|
+
captureContext,
|
|
820
|
+
captureDeviceInfo,
|
|
821
|
+
clearBreadcrumbs,
|
|
822
|
+
getBreadcrumbs,
|
|
823
|
+
initBreadcrumbs,
|
|
824
|
+
sanitizeUrl,
|
|
825
|
+
sendReport,
|
|
826
|
+
useGlitchgrab
|
|
827
|
+
});
|
|
828
|
+
//# sourceMappingURL=index.js.map
|