UNPKG

bug-reporterjs-lib

Version:

A lightweight and framework-agnostic JavaScript bug reporter with screenshot capture, error logging, and Axios error tracking. Designed to work seamlessly in Vue, React, and plain JavaScript projects.

558 lines (531 loc) 17.7 kB
async function E() { const m = await import("./index-D-6arYiK.mjs"), e = document.body; return await m.toJpeg(e, { quality: 1, pixelRatio: 1, skipAutoScale: !0, width: e.clientWidth, height: e.clientHeight }); } const _ = "CLIENT_ERROR_LOGS", b = "LAST_ERROR_REQUEST", w = "http://p-c-ers.asakabank.com/report", v = { title: "Report a Bug", leave_comment: "Leave a comment", send: "Send", noClientErrors: "No client errors", noNetworkErrors: "No network errors", report_success_sent: "Message successfully sent", report_failed: "Failed to send message, please try again later", jira_creds_required: "JIRA credentials are required", username: "Username", password: "Password", continue: "Continue", enter_jira_creds: "Enter JIRA Credentials" }, y = { title: "Сообщить об ошибке", leave_comment: "Оставить комментарий", send: "Отправить", noClientErrors: "Нет клиентских ошибок", noNetworkErrors: "Нет сетевых ошибок", report_success_sent: "Cообщение успешно отправлено", report_failed: "Не удалось отправить сообщение, пожалуйста, попробуйте позже", jira_creds_required: "JIRA учетные данные обязательны", username: "Имя пользователя", password: "Пароль", continue: "Продолжить", enter_jira_creds: "Введите учетные данные JIRA" }, q = { title: "Xatolik haqida xabar berish", leave_comment: "Izoh qoldirish", send: "Jo'natish", noClientErrors: "Mijoz xatoliklari yo'q", noNetworkErrors: "Tarmoq xatoliklari yo'q", report_success_sent: "Xabar muvaffaqiyatli yuborildi", report_failed: "Xabar yuborishda xato, iltimos keyinroq qaytadan urinib ko'ring", jira_creds_required: "JIRA foydalanuvchi ma'lumotlari talab qilinadi", username: "Foydalanuvchi nomi", password: "Parol", continue: "Davom etish", enter_jira_creds: "JIRA foydalanuvchi ma'lumotlarini kiriting" }, R = { title: "Хатолик ҳақида хабар бериш", leave_comment: "Изоҳ қолдириш", send: "Жўнатиш", noClientErrors: "Мижоз хатоликлари йўқ", noNetworkErrors: "Тармоқ хатоликлари йўқ", report_success_sent: "Хабар муваффақиятли юборилди", report_failed: "Хабарни юборишда хатолик юз берди, илтимос, кейинроқ қайта уриниб кўринг", jira_creds_required: "JIRA фойдаланувчи маълумотлари талаб қилинади", username: "Фойдаланувчи номи", password: "Пароль", continue: "Давом этиш", enter_jira_creds: "JIRA фойдаланувчи маълумотларини киритинг" }, x = (m) => { let e = {}; switch (m) { case "uz": e = R; break; case "o'z": e = q; break; case "en": e = v; break; case "ru": e = y; default: e = y; } return (r) => e[r] || r; }; function S(m, e, r, a) { const i = x(r), o = document.createElement("div"); document.body.style.overflow = "hidden", o.id = "bug-modal", o.innerHTML = ` <style> #bug-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 9999; } #bug-modal * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } #bug-modal .modal-content { background: #fff; width: 80%; max-width: 95vw; padding: 30px; border-radius: 30px; box-shadow: 0px 2px 6px 0px #0000001F, 0px 0px 2px 0px #0000000F, 0px 4px 10px 0px #00000008; font-family: sans-serif; display: flex; flex-direction: column; gap:30px; animation: fadeIn 0.2s ease-in-out; } #bug-modal form { display: flex; flex-direction: column; gap:16px; } #bug-modal .modal-header { display: flex; justify-content: space-between; align-items: center; } #bug-modal .modal-header span{ cursor: pointer; font-size: 24px; color: #333; transition: all 0.2s; line-height: 1; } #bug-modal h2 { margin: 0; font-size: 16px; font-weight: 600; color: #333; } #bug-modal h3 { margin: 0; font-size: 20px; color: #065cb9; text-align: center; } #bug-modal img { width: 100%; max-height: 400px; object-fit: cover; object-position: top; border: 1px solid #CED4DA; } #bug-modal textarea { padding: 15px; font-size: 14px; border: 1px solid #CED4DA; border-radius: 30px; outline: none; resize: none; } #bug-modal input { padding: 15px; font-size: 14px; border: 1px solid #CED4DA; border-radius: 30px; outline: none; resize: none; } #bug-modal textarea:hover,#bug-modal textarea:focus { border-color: #232323; } #bug-modal .button-container { display: flex; justify-content: center; } #bug-modal button { padding: 12px 24px; height: 54px; display: flex; justify-content: center; align-items: center; font-size: 14px; background: #333333; color: white; border: none; border-radius: 30px; gap: 10px; cursor: pointer; transition: all 0.2s; min-width: 150px; } #bug-modal button:hover { background: #232323; box-shadow: 0px 5px 5px 0px #00000040; } #bug-modal button:disabled { background: #E5E5E5; cursor: not-allowed; color: #6C757D; } #bug-modal .loader { width: 24px; height: 24px; border-radius: 50%; position: relative; animation: rotate 1s linear infinite } #bug-modal .loader.hide { display: none; } #bug-modal .jira-creds { display: flex; flex-direction: column; gap:16px; } #bug-modal .loader::before { content: ""; box-sizing: border-box; position: absolute; inset: 0px; border-radius: 50%; border: 3px solid #6C757D; animation: prixClipFix 2s linear infinite ; } @keyframes rotate { 100% {transform: rotate(360deg)} } @keyframes prixClipFix { 0% {clip-path:polygon(50% 50%,0 0,0 0,0 0,0 0,0 0)} 25% {clip-path:polygon(50% 50%,0 0,100% 0,100% 0,100% 0,100% 0)} 50% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,100% 100%,100% 100%)} 75% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 100%)} 100% {clip-path:polygon(50% 50%,0 0,100% 0,100% 100%,0 100%,0 0)} } @keyframes fadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } </style> <div class="modal-content"> <div class="modal-header"> <h2>${i("title")}</h2> <span id="close-modal">&times;</span> </div> <form id="bug-modal-form" class="report-form"> <img src="${m}" alt="Screenshot"/> <input type="tel" pattern="^+998(9[0-9]|8[1-9]|7[1-9])[0-9]{7}$" placeholder="+998901234567" id="bug-phone" /> <textarea id="bug-comment" rows="3" placeholder="${i( "leave_comment" )}..."></textarea> <div class="button-container"> <button ${a ? "" : 'id="send-btn"'} type="submit"> ${a ? "" : '<span id="bug-loader" class="loader hide"></span>'} <span>${i(a ? "continue" : "send")}</span> </button> </div> </form> ${a ? ` <form id="bug-jira-creds-form" class="report-form" style="display:none;"> <h3>${i("enter_jira_creds")}</h3> <div class="jira-creds"> <input type="text" id="bug-username" required placeholder="${i( "username" )}" /> <input type="password" id="bug-password" required placeholder="${i( "password" )}" /> </div> <div class="button-container"> <button id="send-btn" type="submit"> <span id="bug-loader" class="loader hide"></span> <span>${i("send")}</span> </button> </div> </form> ` : ""} </div> `, document.body.appendChild(o); const t = document.querySelectorAll(".report-form"); let u = !1; const d = () => { o.remove(), sessionStorage.removeItem(_), sessionStorage.removeItem(b), document.body.style.overflow = "auto"; }; document.getElementById("close-modal")?.addEventListener("click", () => { d(); }); const s = async () => { const n = document.getElementById("send-btn"), l = document.getElementById("bug-loader"), f = document.getElementById("bug-comment")?.value, p = document.getElementById("bug-phone")?.value, g = document.getElementById("bug-username")?.value, h = document.getElementById("bug-password")?.value; if (console.log(g, h), a && (!g || !h)) { alert(i("jira_creds_required")); return; } n?.setAttribute("disabled", "true"), l?.classList.remove("hide"), await e({ comment: f, phone: p, ...a ? { username: g, password: h } : {} }), n?.removeAttribute("disabled"), l?.classList.add("hide"), d(); }; t.forEach((n) => { n?.addEventListener("submit", async (l) => { if (l.preventDefault(), console.log(u), a && !u) { u = !0; const f = document.getElementById("bug-jira-creds-form"), p = document.getElementById("bug-modal-form"); f && (f.style.display = "flex"), p && (p.style.display = "none"); return; } console.log(1), s(); }); }), document.getElementById("bug-phone")?.addEventListener("input", (n) => { let l = n.target?.value.replace(/[^\d+]/g, ""); l.includes("+") && (l = "+" + l.replace(/\+/g, "")), l.length > 13 && (l = l.slice(0, 13)), n.target && (n.target.value = l); }); } class k { project; isJiraCredsRequired; locale; constructor(e) { this.project = e.project, this.isJiraCredsRequired = e.isJiraCredsRequired || !1, this.setupErrorListeners(), this.setupGlobalErrorListeners(); } setupErrorListeners() { const e = window.fetch; window.fetch = async (...o) => { try { const t = await e(...o); if (!t.ok || !t.status) { const d = await t.clone().text(); this.storeNetworkError({ request_url: typeof o[0] == "string" ? o[0] : o[0] instanceof Request ? o[0].url : o[0] instanceof URL ? o[0].toString() : void 0, request_header: o[1]?.headers || {}, request_status_code: t.status, response_data: (() => { try { const s = JSON.parse(d); return typeof s == "object" && s !== null ? s : { message: d }; } catch { return { message: d }; } })() }); } return t; } catch (t) { throw this.storeNetworkError({ request_url: typeof o[0] == "string" ? o[0] : o[0] instanceof Request ? o[0].url : o[0] instanceof URL ? o[0].toString() : void 0, response_data: { message: t?.message || t.toString() } }), t; } }; const r = XMLHttpRequest.prototype.open, a = XMLHttpRequest.prototype.send, i = this; XMLHttpRequest.prototype.open = function(o, t) { return this._bug_report_url = t, r.apply(this, arguments); }, XMLHttpRequest.prototype.send = function(o) { const t = this, u = (s) => { if (s) { if (typeof s == "string") try { return JSON.parse(s); } catch { return s; } else if (s instanceof FormData) { const c = {}; for (const [n, l] of s.entries()) c[n] = l; return c; } else if (s instanceof URLSearchParams) { const c = {}; for (const [n, l] of s.entries()) c[n] = l; return c; } else { if (s instanceof Blob) return "[Blob]"; if (typeof s == "object") return s; } return String(s); } }, d = () => { const s = t.status; if (!(s >= 200 && s < 300)) { let c; try { const n = JSON.parse(t.responseText); n && typeof n == "object" && !Array.isArray(n) ? c = n : c = n ? { message: n } : { message: "Unknown error" }; } catch { c = t.responseText ? { message: t.responseText } : { message: "Unknown error" }; } try { const n = {}; t.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach((f) => { const p = f.split(": "), g = p.shift(), h = p.join(": "); g && (n[g] = h); }), i.storeNetworkError({ request_url: t._bug_report_url, request_payload: u(o), request_header: n, request_status_code: s, response_data: c }); } catch { i.storeNetworkError({ request_url: t._bug_report_url, request_status_code: s, response_data: { message: t.responseText } }); } } t.removeEventListener("loadend", d); }; return t.addEventListener("loadend", d), a.call(t, o); }; } isHttpError(e) { const r = e?.message || e?.toString?.() || (typeof e == "string" ? e : ""); return [ "Load failed", "fetch", "Network Error", "Request failed", "Failed to fetch", "status code", "timeout", "axios", "net::ERR", "Failed to load resource", "A server with the specified hostname could not be found", "Failed to fetch", "ERR_CONNECTION_REFUSED", "ERR_NAME_NOT_RESOLVED" ].some((i) => r.includes(i)); } setupGlobalErrorListeners() { window.onerror = (e, r, a, i, o) => { this.isHttpError(o || e) || this.storeConsoleError({ data: { message: o?.message || String(e), source: r, line: a, column: i } }); }, window.onunhandledrejection = (e) => { const r = e?.reason; this.isHttpError(r) || this.storeConsoleError({ data: { message: r?.message || r?.toString?.() || "Unhandled rejection" } }); }; } storeNetworkError(e) { const r = { request_status_code: e.request_status_code, request_url: e.request_url || "", location_url: window.location.href, request_header: e.request_header ?? {}, request_payload: e.request_payload ?? {}, response_data: e.response_data ?? {} }; sessionStorage.setItem(b, JSON.stringify(r)); } storeConsoleError(e) { let r = JSON.parse( sessionStorage.getItem(_) || "[]" ); const a = { location_url: window.location.href, console_error: e.data }; sessionStorage.setItem( _, JSON.stringify([...r, a].slice(-10)) ); } async openModal({ callback: e, locale: r }) { this.locale = r; const a = await E(); S( a, (i) => this.sendReport({ image: a, ...i }, e), this.locale, this.isJiraCredsRequired ); } collectData({ comment: e, image: r, phone: a, password: i, username: o }) { const t = { project_name: this.project, user_agent: navigator.userAgent, image: r, location_url: window.location.href, request_header: {}, request_payload: {}, response_data: {}, request_status_code: 0, request_url: "", console_error: { data: [] }, comment: e, phone: a, username: o, password: i }, u = JSON.parse( sessionStorage.getItem(_) || "[]" ), d = JSON.parse( sessionStorage.getItem(b) || "{}" ); return u.length > 0 && (t.console_error = { data: u.filter((s) => s.location_url === window.location.href).map((s) => s.console_error).filter((s) => s !== void 0) }), d.location_url === window.location.href && (t.request_url = d.request_url, t.request_header = d.request_header, t.request_status_code = d.request_status_code, t.request_payload = d.request_payload, t.response_data = d.response_data), t; } async sendReport(e, r) { const a = x(this.locale), i = this.collectData(e); try { const o = await fetch(w, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(i) }), t = await o.json(); o.ok ? (r ? r(!0) : alert(a("report_success_sent")), sessionStorage.removeItem(b)) : (console.error("Server returned error response:", t), r ? r(!1) : alert(a("report_failed"))); } catch (o) { console.error("Network or other error:", o), r ? r(!1) : alert(a("report_failed")); } } } export { k as BugReporter };