@rithuu/bugblitz-sdk 1.0.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/bugblitz.cjs.js +338 -0
- package/dist/bugblitz.esm.js +336 -0
- package/dist/bugblitz.js +341 -0
- package/package.json +29 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const BugBlitz = {
|
|
4
|
+
apiKey: null,
|
|
5
|
+
projectId: null,
|
|
6
|
+
errors: [],
|
|
7
|
+
consoleErrors: [],
|
|
8
|
+
networkErrors: [],
|
|
9
|
+
|
|
10
|
+
init(config) {
|
|
11
|
+
if (!config.apiKey) {
|
|
12
|
+
console.error("BugBlitz: apiKey is required");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!config.triggerElement) {
|
|
17
|
+
console.error("BugBlitz: triggerElement is required");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.apiKey = config.apiKey;
|
|
22
|
+
|
|
23
|
+
console.log("BugBlitz initialized with key:", this.apiKey);
|
|
24
|
+
|
|
25
|
+
this.captureErrors();
|
|
26
|
+
this.interceptNetworkErrors();
|
|
27
|
+
|
|
28
|
+
this.attachTrigger(config.triggerElement);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
attachTrigger(selector) {
|
|
32
|
+
const element = document.querySelector(selector);
|
|
33
|
+
|
|
34
|
+
if (!element) {
|
|
35
|
+
console.error(`BugBlitz: No element found for selector "${selector}"`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
element.addEventListener("click", () => {
|
|
40
|
+
this.openPopup();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log("BugBlitz: trigger attached to", selector);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
openPopup() {
|
|
48
|
+
// Prevent multiple popups
|
|
49
|
+
if (document.getElementById("bugblitz-popup")) return;
|
|
50
|
+
|
|
51
|
+
const browserInfo = this.collectBrowserInfo();
|
|
52
|
+
console.log("BugBlitz: browser info collected", browserInfo);
|
|
53
|
+
|
|
54
|
+
// Overlay (dark background)
|
|
55
|
+
const overlay = document.createElement("div");
|
|
56
|
+
overlay.id = "bugblitz-overlay";
|
|
57
|
+
overlay.style.cssText = `
|
|
58
|
+
position: fixed;
|
|
59
|
+
top: 0; left: 0;
|
|
60
|
+
width: 100%; height: 100%;
|
|
61
|
+
background: rgba(0,0,0,0.5);
|
|
62
|
+
z-index: 999998;
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
// Popup box
|
|
66
|
+
const popup = document.createElement("div");
|
|
67
|
+
popup.id = "bugblitz-popup";
|
|
68
|
+
popup.style.cssText = `
|
|
69
|
+
position: fixed;
|
|
70
|
+
top: 50%; left: 50%;
|
|
71
|
+
transform: translate(-50%, -50%);
|
|
72
|
+
background: white;
|
|
73
|
+
padding: 24px;
|
|
74
|
+
border-radius: 12px;
|
|
75
|
+
z-index: 999999;
|
|
76
|
+
width: 400px;
|
|
77
|
+
font-family: sans-serif;
|
|
78
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.2);
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
// Title
|
|
82
|
+
const title = document.createElement("h3");
|
|
83
|
+
title.innerText = "Report a Bug 🐛";
|
|
84
|
+
title.style.cssText = `
|
|
85
|
+
margin: 0 0 16px 0;
|
|
86
|
+
font-size: 18px;
|
|
87
|
+
color: #111;
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
// Textarea
|
|
91
|
+
const textarea = document.createElement("textarea");
|
|
92
|
+
textarea.id = "bugblitz-description";
|
|
93
|
+
textarea.placeholder = "Describe the bug...";
|
|
94
|
+
textarea.style.cssText = `
|
|
95
|
+
width: 100%;
|
|
96
|
+
height: 120px;
|
|
97
|
+
padding: 10px;
|
|
98
|
+
border: 1px solid #ddd;
|
|
99
|
+
border-radius: 8px;
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
resize: none;
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
// Submit button
|
|
106
|
+
const submitBtn = document.createElement("button");
|
|
107
|
+
submitBtn.innerText = "Submit Report";
|
|
108
|
+
submitBtn.style.cssText = `
|
|
109
|
+
margin-top: 12px;
|
|
110
|
+
width: 100%;
|
|
111
|
+
padding: 10px;
|
|
112
|
+
background: #6c47ff;
|
|
113
|
+
color: white;
|
|
114
|
+
border: none;
|
|
115
|
+
border-radius: 8px;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
`;
|
|
119
|
+
|
|
120
|
+
// Close button
|
|
121
|
+
const closeBtn = document.createElement("button");
|
|
122
|
+
closeBtn.innerText = "✕";
|
|
123
|
+
closeBtn.style.cssText = `
|
|
124
|
+
position: absolute;
|
|
125
|
+
top: 12px; right: 16px;
|
|
126
|
+
background: none;
|
|
127
|
+
border: none;
|
|
128
|
+
font-size: 18px;
|
|
129
|
+
cursor: pointer;
|
|
130
|
+
color: #888;
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
// Close logic
|
|
134
|
+
closeBtn.addEventListener("click", () => this.closePopup());
|
|
135
|
+
overlay.addEventListener("click", () => this.closePopup());
|
|
136
|
+
|
|
137
|
+
// Submit logic
|
|
138
|
+
submitBtn.addEventListener("click", async () => {
|
|
139
|
+
const description = document.getElementById("bugblitz-description").value;
|
|
140
|
+
|
|
141
|
+
if (!description.trim()) {
|
|
142
|
+
alert("Please describe the bug before submitting.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
submitBtn.innerText = "Capturing screenshot..";
|
|
147
|
+
submitBtn.disabled = true;
|
|
148
|
+
|
|
149
|
+
const screenshot = await this.captureScreenshot();
|
|
150
|
+
|
|
151
|
+
submitBtn.innerText = "Sending...";
|
|
152
|
+
|
|
153
|
+
// const payload = {
|
|
154
|
+
// apiKey: this.apiKey,
|
|
155
|
+
// description,
|
|
156
|
+
// browserInfo: this.collectBrowserInfo(),
|
|
157
|
+
// errors: this.errors,
|
|
158
|
+
// };
|
|
159
|
+
|
|
160
|
+
const formData = new FormData();
|
|
161
|
+
formData.append("apiKey", this.apiKey);
|
|
162
|
+
formData.append("description", description);
|
|
163
|
+
formData.append("browserInfo", JSON.stringify(this.collectBrowserInfo()));
|
|
164
|
+
formData.append("errors", JSON.stringify(this.consoleErrors));
|
|
165
|
+
formData.append("networkErrors", JSON.stringify(this.networkErrors));
|
|
166
|
+
if (screenshot) {
|
|
167
|
+
formData.append("screenshot", screenshot, "screenshot.png");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch("https://bugblitz-backend.onrender.com/api/bugs", {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: formData,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
throw new Error("Server error");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
submitBtn.innerText = "Submitted! ✓";
|
|
182
|
+
setTimeout(() => this.closePopup(), 1500);
|
|
183
|
+
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error("BugBlitz: failed to send report", err);
|
|
186
|
+
submitBtn.innerText = "Failed. Try again.";
|
|
187
|
+
submitBtn.disabled = false;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Assemble popup
|
|
192
|
+
popup.style.position = "fixed";
|
|
193
|
+
popup.appendChild(closeBtn);
|
|
194
|
+
popup.appendChild(title);
|
|
195
|
+
popup.appendChild(textarea);
|
|
196
|
+
popup.appendChild(submitBtn);
|
|
197
|
+
|
|
198
|
+
document.body.appendChild(overlay);
|
|
199
|
+
document.body.appendChild(popup);
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
closePopup() {
|
|
203
|
+
document.getElementById("bugblitz-popup")?.remove();
|
|
204
|
+
document.getElementById("bugblitz-overlay")?.remove();
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
collectBrowserInfo() {
|
|
208
|
+
return {
|
|
209
|
+
userAgent: navigator.userAgent,
|
|
210
|
+
url: window.location.href,
|
|
211
|
+
screenWidth: screen.width,
|
|
212
|
+
screenHeight: screen.height,
|
|
213
|
+
platform: navigator.platform,
|
|
214
|
+
timestamp: new Date().toISOString(),
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
captureErrors() {
|
|
219
|
+
// Capture JS errors
|
|
220
|
+
window.onerror = (message, source, line, column, error) => {
|
|
221
|
+
const entry = {
|
|
222
|
+
type: "error",
|
|
223
|
+
message,
|
|
224
|
+
source,
|
|
225
|
+
line,
|
|
226
|
+
column,
|
|
227
|
+
stack: error?.stack,
|
|
228
|
+
timestamp: new Date().toISOString(),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
this.consoleErrors.push(entry);
|
|
232
|
+
this.errors.push(entry);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// Capture unhandled promise rejections
|
|
236
|
+
window.onunhandledrejection = (event) => {
|
|
237
|
+
const entry = {
|
|
238
|
+
type: "unhandledrejection",
|
|
239
|
+
message: event.reason?.message || String(event.reason),
|
|
240
|
+
stack: event.reason?.stack,
|
|
241
|
+
timestamp: new Date().toISOString(),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
this.consoleErrors.push(entry);
|
|
245
|
+
this.errors.push(entry);
|
|
246
|
+
};
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
interceptNetworkErrors() {
|
|
250
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function") {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const originalFetch = window.fetch.bind(window);
|
|
255
|
+
|
|
256
|
+
window.fetch = async (...args) => {
|
|
257
|
+
const [resource, init] = args;
|
|
258
|
+
const url = typeof resource === "string" ? resource : resource?.url || "";
|
|
259
|
+
const method = init?.method || "GET";
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const response = await originalFetch(...args);
|
|
263
|
+
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
this.networkErrors.push({
|
|
266
|
+
type: "network",
|
|
267
|
+
url,
|
|
268
|
+
method,
|
|
269
|
+
status: response.status,
|
|
270
|
+
statusText: response.statusText,
|
|
271
|
+
timestamp: new Date().toISOString(),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return response;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
this.networkErrors.push({
|
|
278
|
+
type: "network",
|
|
279
|
+
url,
|
|
280
|
+
method,
|
|
281
|
+
status: null,
|
|
282
|
+
statusText: error?.message || "Network request failed",
|
|
283
|
+
timestamp: new Date().toISOString(),
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
loadHtml2Canvas() {
|
|
292
|
+
return new Promise((resolve, reject) => {
|
|
293
|
+
if (window.html2canvas) {
|
|
294
|
+
resolve(window.html2canvas);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const script = document.createElement("script");
|
|
299
|
+
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
|
|
300
|
+
|
|
301
|
+
script.onload = () => resolve(window.html2canvas);
|
|
302
|
+
script.onerror = () => reject(new Error("BugBlitz: failed to load html2canvas"));
|
|
303
|
+
|
|
304
|
+
document.head.appendChild(script);
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
async captureScreenshot() {
|
|
309
|
+
try {
|
|
310
|
+
const html2canvas = await this.loadHtml2Canvas();
|
|
311
|
+
|
|
312
|
+
const popup = document.getElementById("bugblitz-popup");
|
|
313
|
+
const overlay = document.getElementById("bugblitz-overlay");
|
|
314
|
+
if (popup) popup.style.display = "none";
|
|
315
|
+
if (overlay) overlay.style.display = "none";
|
|
316
|
+
|
|
317
|
+
const canvas = await html2canvas(document.body);
|
|
318
|
+
|
|
319
|
+
if (popup) popup.style.display = "block";
|
|
320
|
+
if (overlay) overlay.style.display = "block";
|
|
321
|
+
|
|
322
|
+
return new Promise((resolve) => {
|
|
323
|
+
canvas.toBlob((blob) => resolve(blob), "image/png");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.error("BugBlitz: screenshot failed", err);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
if (typeof window !== "undefined") {
|
|
335
|
+
window.BugBlitz = BugBlitz;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = BugBlitz;
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
const BugBlitz = {
|
|
2
|
+
apiKey: null,
|
|
3
|
+
projectId: null,
|
|
4
|
+
errors: [],
|
|
5
|
+
consoleErrors: [],
|
|
6
|
+
networkErrors: [],
|
|
7
|
+
|
|
8
|
+
init(config) {
|
|
9
|
+
if (!config.apiKey) {
|
|
10
|
+
console.error("BugBlitz: apiKey is required");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!config.triggerElement) {
|
|
15
|
+
console.error("BugBlitz: triggerElement is required");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.apiKey = config.apiKey;
|
|
20
|
+
|
|
21
|
+
console.log("BugBlitz initialized with key:", this.apiKey);
|
|
22
|
+
|
|
23
|
+
this.captureErrors();
|
|
24
|
+
this.interceptNetworkErrors();
|
|
25
|
+
|
|
26
|
+
this.attachTrigger(config.triggerElement);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
attachTrigger(selector) {
|
|
30
|
+
const element = document.querySelector(selector);
|
|
31
|
+
|
|
32
|
+
if (!element) {
|
|
33
|
+
console.error(`BugBlitz: No element found for selector "${selector}"`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
element.addEventListener("click", () => {
|
|
38
|
+
this.openPopup();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
console.log("BugBlitz: trigger attached to", selector);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
openPopup() {
|
|
46
|
+
// Prevent multiple popups
|
|
47
|
+
if (document.getElementById("bugblitz-popup")) return;
|
|
48
|
+
|
|
49
|
+
const browserInfo = this.collectBrowserInfo();
|
|
50
|
+
console.log("BugBlitz: browser info collected", browserInfo);
|
|
51
|
+
|
|
52
|
+
// Overlay (dark background)
|
|
53
|
+
const overlay = document.createElement("div");
|
|
54
|
+
overlay.id = "bugblitz-overlay";
|
|
55
|
+
overlay.style.cssText = `
|
|
56
|
+
position: fixed;
|
|
57
|
+
top: 0; left: 0;
|
|
58
|
+
width: 100%; height: 100%;
|
|
59
|
+
background: rgba(0,0,0,0.5);
|
|
60
|
+
z-index: 999998;
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
// Popup box
|
|
64
|
+
const popup = document.createElement("div");
|
|
65
|
+
popup.id = "bugblitz-popup";
|
|
66
|
+
popup.style.cssText = `
|
|
67
|
+
position: fixed;
|
|
68
|
+
top: 50%; left: 50%;
|
|
69
|
+
transform: translate(-50%, -50%);
|
|
70
|
+
background: white;
|
|
71
|
+
padding: 24px;
|
|
72
|
+
border-radius: 12px;
|
|
73
|
+
z-index: 999999;
|
|
74
|
+
width: 400px;
|
|
75
|
+
font-family: sans-serif;
|
|
76
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.2);
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
// Title
|
|
80
|
+
const title = document.createElement("h3");
|
|
81
|
+
title.innerText = "Report a Bug 🐛";
|
|
82
|
+
title.style.cssText = `
|
|
83
|
+
margin: 0 0 16px 0;
|
|
84
|
+
font-size: 18px;
|
|
85
|
+
color: #111;
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
// Textarea
|
|
89
|
+
const textarea = document.createElement("textarea");
|
|
90
|
+
textarea.id = "bugblitz-description";
|
|
91
|
+
textarea.placeholder = "Describe the bug...";
|
|
92
|
+
textarea.style.cssText = `
|
|
93
|
+
width: 100%;
|
|
94
|
+
height: 120px;
|
|
95
|
+
padding: 10px;
|
|
96
|
+
border: 1px solid #ddd;
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
font-size: 14px;
|
|
99
|
+
resize: none;
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
// Submit button
|
|
104
|
+
const submitBtn = document.createElement("button");
|
|
105
|
+
submitBtn.innerText = "Submit Report";
|
|
106
|
+
submitBtn.style.cssText = `
|
|
107
|
+
margin-top: 12px;
|
|
108
|
+
width: 100%;
|
|
109
|
+
padding: 10px;
|
|
110
|
+
background: #6c47ff;
|
|
111
|
+
color: white;
|
|
112
|
+
border: none;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
font-size: 14px;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
// Close button
|
|
119
|
+
const closeBtn = document.createElement("button");
|
|
120
|
+
closeBtn.innerText = "✕";
|
|
121
|
+
closeBtn.style.cssText = `
|
|
122
|
+
position: absolute;
|
|
123
|
+
top: 12px; right: 16px;
|
|
124
|
+
background: none;
|
|
125
|
+
border: none;
|
|
126
|
+
font-size: 18px;
|
|
127
|
+
cursor: pointer;
|
|
128
|
+
color: #888;
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
// Close logic
|
|
132
|
+
closeBtn.addEventListener("click", () => this.closePopup());
|
|
133
|
+
overlay.addEventListener("click", () => this.closePopup());
|
|
134
|
+
|
|
135
|
+
// Submit logic
|
|
136
|
+
submitBtn.addEventListener("click", async () => {
|
|
137
|
+
const description = document.getElementById("bugblitz-description").value;
|
|
138
|
+
|
|
139
|
+
if (!description.trim()) {
|
|
140
|
+
alert("Please describe the bug before submitting.");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
submitBtn.innerText = "Capturing screenshot..";
|
|
145
|
+
submitBtn.disabled = true;
|
|
146
|
+
|
|
147
|
+
const screenshot = await this.captureScreenshot();
|
|
148
|
+
|
|
149
|
+
submitBtn.innerText = "Sending...";
|
|
150
|
+
|
|
151
|
+
// const payload = {
|
|
152
|
+
// apiKey: this.apiKey,
|
|
153
|
+
// description,
|
|
154
|
+
// browserInfo: this.collectBrowserInfo(),
|
|
155
|
+
// errors: this.errors,
|
|
156
|
+
// };
|
|
157
|
+
|
|
158
|
+
const formData = new FormData();
|
|
159
|
+
formData.append("apiKey", this.apiKey);
|
|
160
|
+
formData.append("description", description);
|
|
161
|
+
formData.append("browserInfo", JSON.stringify(this.collectBrowserInfo()));
|
|
162
|
+
formData.append("errors", JSON.stringify(this.consoleErrors));
|
|
163
|
+
formData.append("networkErrors", JSON.stringify(this.networkErrors));
|
|
164
|
+
if (screenshot) {
|
|
165
|
+
formData.append("screenshot", screenshot, "screenshot.png");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch("https://bugblitz-backend.onrender.com/api/bugs", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: formData,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
throw new Error("Server error");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
submitBtn.innerText = "Submitted! ✓";
|
|
180
|
+
setTimeout(() => this.closePopup(), 1500);
|
|
181
|
+
|
|
182
|
+
} catch (err) {
|
|
183
|
+
console.error("BugBlitz: failed to send report", err);
|
|
184
|
+
submitBtn.innerText = "Failed. Try again.";
|
|
185
|
+
submitBtn.disabled = false;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Assemble popup
|
|
190
|
+
popup.style.position = "fixed";
|
|
191
|
+
popup.appendChild(closeBtn);
|
|
192
|
+
popup.appendChild(title);
|
|
193
|
+
popup.appendChild(textarea);
|
|
194
|
+
popup.appendChild(submitBtn);
|
|
195
|
+
|
|
196
|
+
document.body.appendChild(overlay);
|
|
197
|
+
document.body.appendChild(popup);
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
closePopup() {
|
|
201
|
+
document.getElementById("bugblitz-popup")?.remove();
|
|
202
|
+
document.getElementById("bugblitz-overlay")?.remove();
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
collectBrowserInfo() {
|
|
206
|
+
return {
|
|
207
|
+
userAgent: navigator.userAgent,
|
|
208
|
+
url: window.location.href,
|
|
209
|
+
screenWidth: screen.width,
|
|
210
|
+
screenHeight: screen.height,
|
|
211
|
+
platform: navigator.platform,
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
};
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
captureErrors() {
|
|
217
|
+
// Capture JS errors
|
|
218
|
+
window.onerror = (message, source, line, column, error) => {
|
|
219
|
+
const entry = {
|
|
220
|
+
type: "error",
|
|
221
|
+
message,
|
|
222
|
+
source,
|
|
223
|
+
line,
|
|
224
|
+
column,
|
|
225
|
+
stack: error?.stack,
|
|
226
|
+
timestamp: new Date().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
this.consoleErrors.push(entry);
|
|
230
|
+
this.errors.push(entry);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Capture unhandled promise rejections
|
|
234
|
+
window.onunhandledrejection = (event) => {
|
|
235
|
+
const entry = {
|
|
236
|
+
type: "unhandledrejection",
|
|
237
|
+
message: event.reason?.message || String(event.reason),
|
|
238
|
+
stack: event.reason?.stack,
|
|
239
|
+
timestamp: new Date().toISOString(),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
this.consoleErrors.push(entry);
|
|
243
|
+
this.errors.push(entry);
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
interceptNetworkErrors() {
|
|
248
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function") {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const originalFetch = window.fetch.bind(window);
|
|
253
|
+
|
|
254
|
+
window.fetch = async (...args) => {
|
|
255
|
+
const [resource, init] = args;
|
|
256
|
+
const url = typeof resource === "string" ? resource : resource?.url || "";
|
|
257
|
+
const method = init?.method || "GET";
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const response = await originalFetch(...args);
|
|
261
|
+
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
this.networkErrors.push({
|
|
264
|
+
type: "network",
|
|
265
|
+
url,
|
|
266
|
+
method,
|
|
267
|
+
status: response.status,
|
|
268
|
+
statusText: response.statusText,
|
|
269
|
+
timestamp: new Date().toISOString(),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return response;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
this.networkErrors.push({
|
|
276
|
+
type: "network",
|
|
277
|
+
url,
|
|
278
|
+
method,
|
|
279
|
+
status: null,
|
|
280
|
+
statusText: error?.message || "Network request failed",
|
|
281
|
+
timestamp: new Date().toISOString(),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
throw error;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
loadHtml2Canvas() {
|
|
290
|
+
return new Promise((resolve, reject) => {
|
|
291
|
+
if (window.html2canvas) {
|
|
292
|
+
resolve(window.html2canvas);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const script = document.createElement("script");
|
|
297
|
+
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
|
|
298
|
+
|
|
299
|
+
script.onload = () => resolve(window.html2canvas);
|
|
300
|
+
script.onerror = () => reject(new Error("BugBlitz: failed to load html2canvas"));
|
|
301
|
+
|
|
302
|
+
document.head.appendChild(script);
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
async captureScreenshot() {
|
|
307
|
+
try {
|
|
308
|
+
const html2canvas = await this.loadHtml2Canvas();
|
|
309
|
+
|
|
310
|
+
const popup = document.getElementById("bugblitz-popup");
|
|
311
|
+
const overlay = document.getElementById("bugblitz-overlay");
|
|
312
|
+
if (popup) popup.style.display = "none";
|
|
313
|
+
if (overlay) overlay.style.display = "none";
|
|
314
|
+
|
|
315
|
+
const canvas = await html2canvas(document.body);
|
|
316
|
+
|
|
317
|
+
if (popup) popup.style.display = "block";
|
|
318
|
+
if (overlay) overlay.style.display = "block";
|
|
319
|
+
|
|
320
|
+
return new Promise((resolve) => {
|
|
321
|
+
canvas.toBlob((blob) => resolve(blob), "image/png");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
} catch (err) {
|
|
325
|
+
console.error("BugBlitz: screenshot failed", err);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (typeof window !== "undefined") {
|
|
333
|
+
window.BugBlitz = BugBlitz;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export { BugBlitz as default };
|
package/dist/bugblitz.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
var BugBlitz = (function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const BugBlitz = {
|
|
5
|
+
apiKey: null,
|
|
6
|
+
projectId: null,
|
|
7
|
+
errors: [],
|
|
8
|
+
consoleErrors: [],
|
|
9
|
+
networkErrors: [],
|
|
10
|
+
|
|
11
|
+
init(config) {
|
|
12
|
+
if (!config.apiKey) {
|
|
13
|
+
console.error("BugBlitz: apiKey is required");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!config.triggerElement) {
|
|
18
|
+
console.error("BugBlitz: triggerElement is required");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.apiKey = config.apiKey;
|
|
23
|
+
|
|
24
|
+
console.log("BugBlitz initialized with key:", this.apiKey);
|
|
25
|
+
|
|
26
|
+
this.captureErrors();
|
|
27
|
+
this.interceptNetworkErrors();
|
|
28
|
+
|
|
29
|
+
this.attachTrigger(config.triggerElement);
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
attachTrigger(selector) {
|
|
33
|
+
const element = document.querySelector(selector);
|
|
34
|
+
|
|
35
|
+
if (!element) {
|
|
36
|
+
console.error(`BugBlitz: No element found for selector "${selector}"`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
element.addEventListener("click", () => {
|
|
41
|
+
this.openPopup();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log("BugBlitz: trigger attached to", selector);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
openPopup() {
|
|
49
|
+
// Prevent multiple popups
|
|
50
|
+
if (document.getElementById("bugblitz-popup")) return;
|
|
51
|
+
|
|
52
|
+
const browserInfo = this.collectBrowserInfo();
|
|
53
|
+
console.log("BugBlitz: browser info collected", browserInfo);
|
|
54
|
+
|
|
55
|
+
// Overlay (dark background)
|
|
56
|
+
const overlay = document.createElement("div");
|
|
57
|
+
overlay.id = "bugblitz-overlay";
|
|
58
|
+
overlay.style.cssText = `
|
|
59
|
+
position: fixed;
|
|
60
|
+
top: 0; left: 0;
|
|
61
|
+
width: 100%; height: 100%;
|
|
62
|
+
background: rgba(0,0,0,0.5);
|
|
63
|
+
z-index: 999998;
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
// Popup box
|
|
67
|
+
const popup = document.createElement("div");
|
|
68
|
+
popup.id = "bugblitz-popup";
|
|
69
|
+
popup.style.cssText = `
|
|
70
|
+
position: fixed;
|
|
71
|
+
top: 50%; left: 50%;
|
|
72
|
+
transform: translate(-50%, -50%);
|
|
73
|
+
background: white;
|
|
74
|
+
padding: 24px;
|
|
75
|
+
border-radius: 12px;
|
|
76
|
+
z-index: 999999;
|
|
77
|
+
width: 400px;
|
|
78
|
+
font-family: sans-serif;
|
|
79
|
+
box-shadow: 0 4px 24px rgba(0,0,0,0.2);
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
// Title
|
|
83
|
+
const title = document.createElement("h3");
|
|
84
|
+
title.innerText = "Report a Bug 🐛";
|
|
85
|
+
title.style.cssText = `
|
|
86
|
+
margin: 0 0 16px 0;
|
|
87
|
+
font-size: 18px;
|
|
88
|
+
color: #111;
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
// Textarea
|
|
92
|
+
const textarea = document.createElement("textarea");
|
|
93
|
+
textarea.id = "bugblitz-description";
|
|
94
|
+
textarea.placeholder = "Describe the bug...";
|
|
95
|
+
textarea.style.cssText = `
|
|
96
|
+
width: 100%;
|
|
97
|
+
height: 120px;
|
|
98
|
+
padding: 10px;
|
|
99
|
+
border: 1px solid #ddd;
|
|
100
|
+
border-radius: 8px;
|
|
101
|
+
font-size: 14px;
|
|
102
|
+
resize: none;
|
|
103
|
+
box-sizing: border-box;
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
// Submit button
|
|
107
|
+
const submitBtn = document.createElement("button");
|
|
108
|
+
submitBtn.innerText = "Submit Report";
|
|
109
|
+
submitBtn.style.cssText = `
|
|
110
|
+
margin-top: 12px;
|
|
111
|
+
width: 100%;
|
|
112
|
+
padding: 10px;
|
|
113
|
+
background: #6c47ff;
|
|
114
|
+
color: white;
|
|
115
|
+
border: none;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
// Close button
|
|
122
|
+
const closeBtn = document.createElement("button");
|
|
123
|
+
closeBtn.innerText = "✕";
|
|
124
|
+
closeBtn.style.cssText = `
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: 12px; right: 16px;
|
|
127
|
+
background: none;
|
|
128
|
+
border: none;
|
|
129
|
+
font-size: 18px;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
color: #888;
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
// Close logic
|
|
135
|
+
closeBtn.addEventListener("click", () => this.closePopup());
|
|
136
|
+
overlay.addEventListener("click", () => this.closePopup());
|
|
137
|
+
|
|
138
|
+
// Submit logic
|
|
139
|
+
submitBtn.addEventListener("click", async () => {
|
|
140
|
+
const description = document.getElementById("bugblitz-description").value;
|
|
141
|
+
|
|
142
|
+
if (!description.trim()) {
|
|
143
|
+
alert("Please describe the bug before submitting.");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
submitBtn.innerText = "Capturing screenshot..";
|
|
148
|
+
submitBtn.disabled = true;
|
|
149
|
+
|
|
150
|
+
const screenshot = await this.captureScreenshot();
|
|
151
|
+
|
|
152
|
+
submitBtn.innerText = "Sending...";
|
|
153
|
+
|
|
154
|
+
// const payload = {
|
|
155
|
+
// apiKey: this.apiKey,
|
|
156
|
+
// description,
|
|
157
|
+
// browserInfo: this.collectBrowserInfo(),
|
|
158
|
+
// errors: this.errors,
|
|
159
|
+
// };
|
|
160
|
+
|
|
161
|
+
const formData = new FormData();
|
|
162
|
+
formData.append("apiKey", this.apiKey);
|
|
163
|
+
formData.append("description", description);
|
|
164
|
+
formData.append("browserInfo", JSON.stringify(this.collectBrowserInfo()));
|
|
165
|
+
formData.append("errors", JSON.stringify(this.consoleErrors));
|
|
166
|
+
formData.append("networkErrors", JSON.stringify(this.networkErrors));
|
|
167
|
+
if (screenshot) {
|
|
168
|
+
formData.append("screenshot", screenshot, "screenshot.png");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const response = await fetch("https://bugblitz-backend.onrender.com/api/bugs", {
|
|
174
|
+
method: "POST",
|
|
175
|
+
body: formData,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
throw new Error("Server error");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
submitBtn.innerText = "Submitted! ✓";
|
|
183
|
+
setTimeout(() => this.closePopup(), 1500);
|
|
184
|
+
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error("BugBlitz: failed to send report", err);
|
|
187
|
+
submitBtn.innerText = "Failed. Try again.";
|
|
188
|
+
submitBtn.disabled = false;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Assemble popup
|
|
193
|
+
popup.style.position = "fixed";
|
|
194
|
+
popup.appendChild(closeBtn);
|
|
195
|
+
popup.appendChild(title);
|
|
196
|
+
popup.appendChild(textarea);
|
|
197
|
+
popup.appendChild(submitBtn);
|
|
198
|
+
|
|
199
|
+
document.body.appendChild(overlay);
|
|
200
|
+
document.body.appendChild(popup);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
closePopup() {
|
|
204
|
+
document.getElementById("bugblitz-popup")?.remove();
|
|
205
|
+
document.getElementById("bugblitz-overlay")?.remove();
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
collectBrowserInfo() {
|
|
209
|
+
return {
|
|
210
|
+
userAgent: navigator.userAgent,
|
|
211
|
+
url: window.location.href,
|
|
212
|
+
screenWidth: screen.width,
|
|
213
|
+
screenHeight: screen.height,
|
|
214
|
+
platform: navigator.platform,
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
};
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
captureErrors() {
|
|
220
|
+
// Capture JS errors
|
|
221
|
+
window.onerror = (message, source, line, column, error) => {
|
|
222
|
+
const entry = {
|
|
223
|
+
type: "error",
|
|
224
|
+
message,
|
|
225
|
+
source,
|
|
226
|
+
line,
|
|
227
|
+
column,
|
|
228
|
+
stack: error?.stack,
|
|
229
|
+
timestamp: new Date().toISOString(),
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
this.consoleErrors.push(entry);
|
|
233
|
+
this.errors.push(entry);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Capture unhandled promise rejections
|
|
237
|
+
window.onunhandledrejection = (event) => {
|
|
238
|
+
const entry = {
|
|
239
|
+
type: "unhandledrejection",
|
|
240
|
+
message: event.reason?.message || String(event.reason),
|
|
241
|
+
stack: event.reason?.stack,
|
|
242
|
+
timestamp: new Date().toISOString(),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
this.consoleErrors.push(entry);
|
|
246
|
+
this.errors.push(entry);
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
interceptNetworkErrors() {
|
|
251
|
+
if (typeof window === "undefined" || typeof window.fetch !== "function") {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const originalFetch = window.fetch.bind(window);
|
|
256
|
+
|
|
257
|
+
window.fetch = async (...args) => {
|
|
258
|
+
const [resource, init] = args;
|
|
259
|
+
const url = typeof resource === "string" ? resource : resource?.url || "";
|
|
260
|
+
const method = init?.method || "GET";
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const response = await originalFetch(...args);
|
|
264
|
+
|
|
265
|
+
if (!response.ok) {
|
|
266
|
+
this.networkErrors.push({
|
|
267
|
+
type: "network",
|
|
268
|
+
url,
|
|
269
|
+
method,
|
|
270
|
+
status: response.status,
|
|
271
|
+
statusText: response.statusText,
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return response;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.networkErrors.push({
|
|
279
|
+
type: "network",
|
|
280
|
+
url,
|
|
281
|
+
method,
|
|
282
|
+
status: null,
|
|
283
|
+
statusText: error?.message || "Network request failed",
|
|
284
|
+
timestamp: new Date().toISOString(),
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
loadHtml2Canvas() {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
if (window.html2canvas) {
|
|
295
|
+
resolve(window.html2canvas);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const script = document.createElement("script");
|
|
300
|
+
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
|
|
301
|
+
|
|
302
|
+
script.onload = () => resolve(window.html2canvas);
|
|
303
|
+
script.onerror = () => reject(new Error("BugBlitz: failed to load html2canvas"));
|
|
304
|
+
|
|
305
|
+
document.head.appendChild(script);
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
async captureScreenshot() {
|
|
310
|
+
try {
|
|
311
|
+
const html2canvas = await this.loadHtml2Canvas();
|
|
312
|
+
|
|
313
|
+
const popup = document.getElementById("bugblitz-popup");
|
|
314
|
+
const overlay = document.getElementById("bugblitz-overlay");
|
|
315
|
+
if (popup) popup.style.display = "none";
|
|
316
|
+
if (overlay) overlay.style.display = "none";
|
|
317
|
+
|
|
318
|
+
const canvas = await html2canvas(document.body);
|
|
319
|
+
|
|
320
|
+
if (popup) popup.style.display = "block";
|
|
321
|
+
if (overlay) overlay.style.display = "block";
|
|
322
|
+
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
canvas.toBlob((blob) => resolve(blob), "image/png");
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error("BugBlitz: screenshot failed", err);
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
if (typeof window !== "undefined") {
|
|
336
|
+
window.BugBlitz = BugBlitz;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return BugBlitz;
|
|
340
|
+
|
|
341
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
{
|
|
4
|
+
"name": "@rithuu/bugblitz-sdk",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "Embeddable bug-reporting SDK for any website",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/bugblitz.cjs.js",
|
|
9
|
+
"module": "dist/bugblitz.esm.js",
|
|
10
|
+
"unpkg": "dist/bugblitz.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "rollup -c",
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"bug-reporting",
|
|
20
|
+
"sdk",
|
|
21
|
+
"feedback",
|
|
22
|
+
"widget"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"rollup": "^4.62.2"
|
|
28
|
+
}
|
|
29
|
+
}
|