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.
213 lines (186 loc) • 14.7 kB
JavaScript
;Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});async function E(){const m=await Promise.resolve().then(()=>require("./index-DBV_9mEJ.js")),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">×</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"),g=document.getElementById("bug-comment")?.value,p=document.getElementById("bug-phone")?.value,f=document.getElementById("bug-username")?.value,h=document.getElementById("bug-password")?.value;if(console.log(f,h),a&&(!f||!h)){alert(i("jira_creds_required"));return}n?.setAttribute("disabled","true"),l?.classList.remove("hide"),await e({comment:g,phone:p,...a?{username:f,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 g=document.getElementById("bug-jira-creds-form"),p=document.getElementById("bug-modal-form");g&&(g.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(g=>{const p=g.split(": "),f=p.shift(),h=p.join(": ");f&&(n[f]=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"))}}}exports.BugReporter=k;