UNPKG

@triagly/sdk

Version:

JavaScript SDK for Triagly - Turn user feedback into GitHub issues instantly

3 lines (2 loc) 28.3 kB
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Triagly={})}(this,function(t){"use strict";class e{constructor(t){this.container=null,this.isOpen=!1,this.previouslyFocusedElement=null,this.focusableElements=[],this.config=t}init(){this.createButton(),this.injectStyles()}createButton(){const t=document.createElement("button");t.id="triagly-button",t.className="triagly-button";const e=this.config.buttonShape||"rounded";t.classList.add(`triagly-shape-${e}`);const n=this.config.orientation||"horizontal";t.classList.add(`triagly-orientation-${n}`);const i=this.config.buttonText||"🐛 Feedback";if("circular"===e)t.innerHTML="🐛",t.setAttribute("aria-label",i);else if("expandable"===e){if(t.innerHTML='<span class="triagly-btn-icon">🐛</span><span class="triagly-btn-text"> Feedback</span>',t.setAttribute("aria-label",i),this.config.buttonText){const e=t.querySelector(".triagly-btn-text");e&&(e.textContent=" "+this.config.buttonText.replace("🐛","").trim())}}else t.innerHTML=i;t.onclick=()=>this.toggle();const r=this.config.position||"bottom-right";t.classList.add(`triagly-${r}`),"expandable"===e&&(r.includes("right")?t.classList.add("triagly-expand-left"):r.includes("left")&&t.classList.add("triagly-expand-right")),this.config.offsetX&&(r.includes("right")?t.style.right=this.config.offsetX:r.includes("left")&&(t.style.left=this.config.offsetX)),this.config.offsetY&&(r.includes("top")?t.style.top=this.config.offsetY:r.includes("bottom")&&(t.style.bottom=this.config.offsetY)),document.body.appendChild(t)}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen||(this.previouslyFocusedElement=document.activeElement,this.container=this.createContainer(),document.body.appendChild(this.container),this.isOpen=!0,this.config.onOpen&&this.config.onOpen(),setTimeout(()=>{this.setupKeyboardEvents(),this.setupFocusTrap();const t=this.container?.querySelector('input[type="text"]');t?.focus()},0))}close(t){if(!this.isOpen||!this.container)return;const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0),this.container.remove(),this.container=null,this.isOpen=!1,this.previouslyFocusedElement&&(this.previouslyFocusedElement.focus(),this.previouslyFocusedElement=null),"cancel"===t&&this.config.onCancel?this.config.onCancel():"dismiss"===t&&this.config.onDismiss?this.config.onDismiss():"overlay"===t&&this.config.onOverlayClick&&this.config.onOverlayClick(),this.config.onClose&&this.config.onClose()}createContainer(){const t=document.createElement("div");t.className="triagly-overlay",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-labelledby","triagly-modal-title"),t.onclick=e=>{e.target===t&&this.close("overlay")};const e=document.createElement("div");e.className="triagly-modal",e.setAttribute("role","document");const n=document.createElement("div");n.className="triagly-header",n.innerHTML='\n <h3 id="triagly-modal-title">Send Feedback</h3>\n <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>\n ';const i=n.querySelector(".triagly-close");i?.addEventListener("click",()=>this.close("dismiss"));const r=document.createElement("form");r.className="triagly-form",r.innerHTML=`\n <div class="triagly-field">\n <label for="triagly-title">Title (optional)</label>\n <input\n type="text"\n id="triagly-title"\n placeholder="Brief summary of your feedback"\n />\n </div>\n\n <div class="triagly-field">\n <label for="triagly-description">Description *</label>\n <textarea\n id="triagly-description"\n required\n rows="5"\n placeholder="${this.config.placeholderText||"Describe what happened..."}"\n ></textarea>\n </div>\n\n <div class="triagly-field">\n <label for="triagly-email">Email (optional)</label>\n <input\n type="email"\n id="triagly-email"\n placeholder="your@email.com"\n />\n </div>\n\n <div class="triagly-field triagly-checkbox">\n <label>\n <input type="checkbox" id="triagly-screenshot" checked />\n <span>Include screenshot</span>\n </label>\n </div>\n\n ${this.config.turnstileSiteKey?`\n <div class="triagly-field triagly-turnstile">\n <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>\n </div>\n `:""}\n\n <div class="triagly-actions">\n <button type="button" class="triagly-btn-secondary" id="triagly-cancel" aria-label="Cancel and close feedback form">\n Cancel\n </button>\n <button type="submit" class="triagly-btn-primary" aria-label="Submit feedback">\n Send Feedback\n </button>\n </div>\n\n <div class="triagly-status" id="triagly-status" role="status" aria-live="polite"></div>\n `;const o=r.querySelector("#triagly-cancel");return o?.addEventListener("click",()=>this.close("cancel")),r.onsubmit=t=>{t.preventDefault(),this.handleSubmit(r)},e.appendChild(n),e.appendChild(r),t.appendChild(e),this.config.turnstileSiteKey&&setTimeout(()=>{this.renderTurnstileWidget(r)},100),t}renderTurnstileWidget(t){const e=t.querySelector(".cf-turnstile");if(e)if(window.turnstile)try{const t=window.turnstile.render(e,{sitekey:this.config.turnstileSiteKey,theme:"dark"===this.config.theme?"dark":"light",callback:n=>{e.setAttribute("data-turnstile-response",n),e.setAttribute("data-widget-id",t)},"error-callback":()=>{console.error("Triagly: Turnstile widget error")},"expired-callback":()=>{e.removeAttribute("data-turnstile-response")}});e.setAttribute("data-widget-id",t)}catch(t){console.error("Triagly: Failed to render Turnstile widget:",t)}else console.warn('Triagly: Turnstile script not loaded. Please include: <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer><\/script>')}async handleSubmit(t){const e=t.querySelector("#triagly-title"),n=t.querySelector("#triagly-description"),i=t.querySelector("#triagly-email"),r=t.querySelector("#triagly-screenshot"),o=t.querySelector("#triagly-status"),a=t.querySelector('button[type="submit"]'),s=t.querySelector(".cf-turnstile");a.disabled=!0,a.textContent="Sending...";try{let t;s&&(t=s.getAttribute("data-turnstile-response")||void 0);const a={title:e.value.trim()||void 0,description:n.value.trim(),reporterEmail:i.value.trim()||void 0,includeScreenshot:r.checked,turnstileToken:t},l=new Promise((t,e)=>{const n=()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),t()},i=t=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(t.detail)};document.addEventListener("triagly:success",n,{once:!0}),document.addEventListener("triagly:error",i,{once:!0}),setTimeout(()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(new Error("Submission timeout"))},3e4)}),c=new CustomEvent("triagly:submit",{detail:a,bubbles:!0});document.dispatchEvent(c),await l,o.className="triagly-status triagly-success",o.textContent=this.config.successMessage||"Feedback sent successfully!",setTimeout(()=>{this.close()},2e3)}catch(t){o.className="triagly-status triagly-error";const e=t instanceof Error?t.message:this.config.errorMessage||"Failed to send feedback. Please try again.";o.textContent=e,a.disabled=!1,a.textContent="Send Feedback"}}injectStyles(){if(document.getElementById("triagly-styles"))return;const t=document.createElement("style");t.id="triagly-styles",t.textContent="\n .triagly-button {\n position: fixed;\n z-index: 999999;\n padding: 12px 20px;\n background: var(--triagly-button-bg, #6366f1);\n color: var(--triagly-button-text, #ffffff);\n border: none;\n border-radius: var(--triagly-button-radius, 8px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(99, 102, 241, 0.3));\n transition: all 0.2s;\n }\n\n .triagly-button:hover {\n background: var(--triagly-button-bg-hover, #4f46e5);\n transform: translateY(-2px);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));\n }\n\n /* Prevent expandable buttons from shifting on hover */\n .triagly-button.triagly-shape-expandable:hover {\n transform: translateY(0) !important;\n }\n\n .triagly-bottom-right { bottom: 20px; right: 20px; }\n .triagly-bottom-left { bottom: 20px; left: 20px; }\n .triagly-top-right { top: 20px; right: 20px; }\n .triagly-top-left { top: 20px; left: 20px; }\n\n /* Edge-aligned positions (0 offset from edges) */\n .triagly-edge-bottom-right { bottom: 0; right: 0; }\n .triagly-edge-bottom-left { bottom: 0; left: 0; }\n .triagly-edge-top-right { top: 0; right: 0; }\n .triagly-edge-top-left { top: 0; left: 0; }\n .triagly-edge-right { top: 50%; right: 0; transform: translateY(-50%); }\n .triagly-edge-left { top: 50%; left: 0; transform: translateY(-50%); }\n .triagly-edge-top { top: 0; left: 50%; transform: translateX(-50%); }\n .triagly-edge-bottom { bottom: 0; left: 50%; transform: translateX(-50%); }\n\n /* Button shapes */\n .triagly-shape-rounded {\n border-radius: var(--triagly-button-radius, 8px);\n }\n .triagly-shape-circular {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .triagly-shape-square {\n border-radius: 0;\n }\n .triagly-shape-pill {\n border-radius: 30px;\n }\n .triagly-shape-expandable {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n min-width: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n background 0.2s,\n box-shadow 0.2s;\n white-space: nowrap;\n }\n \n /* Expansion direction - expands left for right-positioned buttons */\n .triagly-shape-expandable.triagly-expand-left {\n flex-direction: row-reverse;\n }\n \n /* Expansion direction - expands right for left-positioned buttons */\n .triagly-shape-expandable.triagly-expand-right {\n flex-direction: row;\n }\n \n .triagly-shape-expandable .triagly-btn-icon {\n display: inline-block;\n flex-shrink: 0;\n transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n .triagly-shape-expandable .triagly-btn-text {\n display: inline-block;\n width: 0;\n opacity: 0;\n overflow: hidden;\n font-size: 14px;\n font-weight: 500;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n \n /* Hover state */\n .triagly-shape-expandable:hover {\n width: auto;\n min-width: auto;\n padding: 12px 20px;\n border-radius: 30px;\n background: var(--triagly-button-bg-hover, #4f46e5);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(99, 102, 241, 0.4));\n }\n .triagly-shape-expandable:hover .triagly-btn-text {\n width: auto;\n opacity: 1;\n }\n\n /* Button orientations */\n .triagly-orientation-horizontal {\n writing-mode: horizontal-tb;\n }\n .triagly-orientation-vertical {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n }\n\n .triagly-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--triagly-overlay-bg, rgba(0, 0, 0, 0.5));\n z-index: 1000000;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: triagly-fadeIn 0.2s;\n }\n\n @keyframes triagly-fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n .triagly-modal {\n background: var(--triagly-modal-bg, #ffffff);\n border-radius: var(--triagly-modal-radius, 12px);\n width: 90%;\n max-width: var(--triagly-modal-max-width, 500px);\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: var(--triagly-modal-shadow, 0 20px 60px rgba(0, 0, 0, 0.3));\n animation: triagly-slideUp 0.3s;\n }\n\n @keyframes triagly-slideUp {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .triagly-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 20px 24px;\n background: var(--triagly-header-bg, #ffffff);\n border-bottom: 1px solid var(--triagly-header-border, #e5e7eb);\n }\n\n .triagly-header h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: var(--triagly-header-text, #111827);\n }\n\n .triagly-close {\n background: none;\n border: none;\n font-size: 28px;\n color: #6b7280;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n transition: all 0.2s;\n }\n\n .triagly-close:hover {\n background: #f3f4f6;\n color: #111827;\n }\n\n .triagly-form {\n padding: 24px;\n background: var(--triagly-form-bg, #ffffff);\n }\n\n .triagly-field {\n margin-bottom: 16px;\n }\n\n .triagly-field label {\n display: block;\n margin-bottom: 6px;\n font-size: 14px;\n font-weight: 500;\n color: var(--triagly-label-text, #374151);\n }\n\n .triagly-field input,\n .triagly-field textarea {\n width: 100%;\n padding: 10px 12px;\n background: var(--triagly-input-bg, #ffffff);\n border: 1px solid var(--triagly-input-border, #d1d5db);\n border-radius: var(--triagly-input-radius, 6px);\n color: var(--triagly-input-text, #111827);\n font-size: 14px;\n font-family: inherit;\n transition: border-color 0.2s;\n box-sizing: border-box;\n }\n\n .triagly-field input:focus,\n .triagly-field textarea:focus {\n outline: none;\n border-color: var(--triagly-input-border-focus, #6366f1);\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .triagly-checkbox label {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-weight: 400;\n }\n\n .triagly-checkbox label span {\n user-select: none;\n }\n\n .triagly-checkbox input {\n width: 16px;\n height: 16px;\n margin: 0;\n cursor: pointer;\n }\n\n /* Focus visible styles for accessibility */\n .triagly-button:focus-visible,\n .triagly-field input:focus-visible,\n .triagly-field textarea:focus-visible,\n .triagly-checkbox input:focus-visible,\n .triagly-btn-primary:focus-visible,\n .triagly-btn-secondary:focus-visible,\n .triagly-close:focus-visible {\n outline: 2px solid #6366f1;\n outline-offset: 2px;\n }\n\n /* Checkbox label gets visual indicator when checkbox is focused */\n .triagly-checkbox input:focus-visible + span {\n text-decoration: underline;\n }\n\n .triagly-turnstile {\n display: flex;\n justify-content: center;\n margin: 8px 0;\n }\n\n .triagly-actions {\n display: flex;\n gap: 12px;\n margin-top: 24px;\n }\n\n .triagly-btn-primary,\n .triagly-btn-secondary {\n flex: 1;\n padding: 10px 16px;\n border-radius: var(--triagly-btn-radius, 6px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .triagly-btn-primary {\n background: var(--triagly-btn-primary-bg, #6366f1);\n color: var(--triagly-btn-primary-text, #ffffff);\n }\n\n .triagly-btn-primary:hover:not(:disabled) {\n background: var(--triagly-btn-primary-bg-hover, #4f46e5);\n }\n\n .triagly-btn-primary:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .triagly-btn-secondary {\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n color: var(--triagly-btn-secondary-text, #374151);\n }\n\n .triagly-btn-secondary:hover {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n }\n\n .triagly-status {\n margin-top: 16px;\n padding: 12px;\n border-radius: 6px;\n font-size: 14px;\n display: none;\n }\n\n .triagly-status.triagly-success {\n display: block;\n background: var(--triagly-success-bg, #d1fae5);\n color: var(--triagly-success-text, #065f46);\n }\n\n .triagly-status.triagly-error {\n display: block;\n background: var(--triagly-error-bg, #fee2e2);\n color: var(--triagly-error-text, #991b1b);\n }\n ",document.head.appendChild(t)}setupKeyboardEvents(){const t=t=>{"Escape"===t.key&&this.isOpen&&(t.preventDefault(),this.close("dismiss"))};document.addEventListener("keydown",t),this.container&&(this.container._keydownHandler=t)}setupFocusTrap(){if(!this.container)return;const t=this.container.querySelector(".triagly-modal");if(!t)return;if(this.focusableElements=Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')),0===this.focusableElements.length)return void console.warn("Triagly: No focusable elements found in modal");const e=t=>{const e=t;if("Tab"!==e.key)return;if(!this.container?.contains(document.activeElement))return;const n=this.focusableElements[0],i=this.focusableElements[this.focusableElements.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),i?.focus()):document.activeElement===i&&(e.preventDefault(),n?.focus())};document.addEventListener("keydown",e,!0),this.container._tabHandler=e}destroy(){if(this.container){const t=this.container._keydownHandler;t&&document.removeEventListener("keydown",t);const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0)}this.close(),document.getElementById("triagly-button")?.remove(),document.getElementById("triagly-styles")?.remove()}}class n{constructor(t,e,n,i){this.apiUrl=(e||"https://sypkjlwfyvyuqnvzkaxb.supabase.co/functions/v1").replace(/\/$/,""),this.publishableKey=t,this.getToken=n,this.turnstileSiteKey=i}async getTurnstileToken(){const t=document.querySelector("[data-turnstile-response]");if(t){const e=t.getAttribute("data-turnstile-response");if(e)return e}if(window.turnstile)try{const t=document.querySelectorAll(".cf-turnstile");if(t.length>0){const e=t[0].getAttribute("data-widget-id");if(e){const t=window.turnstile.getResponse(e);if(t)return t}}}catch(t){console.warn("Failed to get Turnstile token:",t)}return null}async submitFeedback(t,e,n){if(n||(n=await this.getTurnstileToken()||void 0),this.turnstileSiteKey&&!n)throw new Error("Turnstile verification required. Please complete the captcha.");let i;if(this.getToken)try{i=await this.getToken()}catch(t){throw console.error("Failed to get hardened token:",t),new Error("Failed to authenticate. Please try again.")}const r={publishableKey:this.publishableKey,title:t.title,description:t.description,metadata:{...e,consoleLogs:t.consoleLogs},tags:t.tags,screenshot:t.screenshot,reporterEmail:t.reporterEmail,reporterName:t.reporterName,turnstileToken:n,hardenedToken:i},o=await fetch(`${this.apiUrl}/feedback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!o.ok){const t=await o.json().catch(()=>({error:"Unknown error",message:"Unknown error"}));if(401===o.status){if("invalid_publishable_key"===t.error)throw new Error("Invalid API key. Please contact support.");if("token_required"===t.error)throw new Error("Authentication required. Please refresh and try again.");throw new Error(t.message||"Authentication failed")}if(403===o.status){if("origin_not_allowed"===t.error)throw new Error("This website is not authorized to submit feedback.");throw new Error(t.message||"Access denied")}if(429===o.status){const t=o.headers.get("Retry-After");throw new Error(`Too many requests. Please try again ${t?`in ${t} seconds`:"later"}.`)}if(400===o.status&&"captcha_failed"===t.error)throw new Error("Captcha verification failed. Please try again.");throw new Error(t.message||t.error||`Failed to submit feedback (HTTP ${o.status})`)}return await o.json()}}function i(t){const e=`${window.innerWidth}x${window.innerHeight}`,n=function(){const t=navigator.userAgent;let e="Unknown";if(t.includes("Firefox/")){const n=t.match(/Firefox\/(\d+)/)?.[1];e=`Firefox ${n}`}else if(t.includes("Chrome/")&&!t.includes("Edg")){const n=t.match(/Chrome\/(\d+)/)?.[1];e=`Chrome ${n}`}else if(t.includes("Safari/")&&!t.includes("Chrome")){const n=t.match(/Version\/(\d+)/)?.[1];e=`Safari ${n}`}else if(t.includes("Edg/")){const n=t.match(/Edg\/(\d+)/)?.[1];e=`Edge ${n}`}return e}();return{url:window.location.href,browser:n,viewport:e,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),...t}}class r{constructor(t,e=3,n=3e5){this.key=`triagly_ratelimit_${t}`,this.maxAttempts=e,this.windowMs=n}canProceed(){const t=Date.now();return!(this.getData().attempts.filter(e=>t-e<this.windowMs).length>=this.maxAttempts)}recordAttempt(){const t=Date.now(),e=this.getData();e.attempts.push(t),e.attempts=e.attempts.filter(e=>t-e<this.windowMs),this.setData(e)}getTimeUntilReset(){const t=Date.now(),e=this.getData();if(0===e.attempts.length)return 0;const n=Math.min(...e.attempts)+this.windowMs;return Math.max(0,n-t)}getData(){try{const t=localStorage.getItem(this.key);return t?JSON.parse(t):{attempts:[]}}catch{return{attempts:[]}}}setData(t){try{localStorage.setItem(this.key,JSON.stringify(t))}catch(t){console.error("Failed to store rate limit data:",t)}}}class o{constructor(t=50,e=["error","warn"]){this.buffer=[],this.isActive=!1,this.maxLogs=t,this.levels=new Set(e),this.originalConsole={error:console.error,warn:console.warn,log:console.log}}start(){this.isActive||(this.isActive=!0,this.levels.has("error")&&(console.error=(...t)=>{this.captureLog("error",t),this.originalConsole.error.apply(console,t)}),this.levels.has("warn")&&(console.warn=(...t)=>{this.captureLog("warn",t),this.originalConsole.warn.apply(console,t)}),this.levels.has("log")&&(console.log=(...t)=>{this.captureLog("log",t),this.originalConsole.log.apply(console,t)}))}stop(){this.isActive&&(this.isActive=!1,console.error=this.originalConsole.error,console.warn=this.originalConsole.warn,console.log=this.originalConsole.log)}captureLog(t,e){try{const n=e.map(t=>{if("string"==typeof t)return t;if(t instanceof Error)return t.message;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");let i;if("error"===t){const t=e.find(t=>t instanceof Error);i=t?t.stack:(new Error).stack?.split("\n").slice(2).join("\n")}const r=this.sanitize(n),o=i?this.sanitize(i):void 0,a={level:t,message:r,timestamp:(new Date).toISOString(),stack:o};this.buffer.push(a),this.buffer.length>this.maxLogs&&this.buffer.shift()}catch(t){this.originalConsole.error("Failed to capture log:",t)}}sanitize(t){return t.replace(/[a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"token=***").replace(/[a-zA-Z0-9_-]*key[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"key=***").replace(/[a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"secret=***").replace(/gh[ps]_[a-zA-Z0-9]{36,}/g,"gh*_***").replace(/eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,"jwt.***").replace(/password\s*[:=]\s*["']?[^"'\s]+["']?/gi,"password=***").replace(/\b[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,}\b/g,"***@***.com").replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,"****-****-****-****").replace(/([?&])(token|key|secret|auth)=[^&\s]+/gi,"$1$2=***")}getLogs(){return[...this.buffer]}clear(){this.buffer=[]}getCount(){return this.buffer.length}}class a{constructor(t){this.consoleLogger=null;let i=t.publishableKey;if(!i&&t.projectId&&(console.warn("Triagly: projectId is deprecated. Please use publishableKey instead. See migration guide: https://docs.triagly.com/sdk/migration"),i=t.projectId),!i)throw new Error("Triagly: publishableKey is required. Get yours at https://triagly.com/dashboard");this.config={theme:"auto",position:"bottom-right",buttonShape:"rounded",buttonText:"🐛 Feedback",placeholderText:"Describe what happened...",successMessage:"Feedback sent successfully!",errorMessage:"Failed to send feedback. Please try again.",captureConsole:!0,consoleLogLimit:50,consoleLogLevels:["error","warn"],...t,publishableKey:i},this.api=new n(this.config.publishableKey,this.config.apiUrl,this.config.getToken,this.config.turnstileSiteKey),this.widget=new e(this.config),this.rateLimiter=new r(this.config.publishableKey,3,3e5),!1!==this.config.captureConsole&&(this.consoleLogger=new o(this.config.consoleLogLimit,this.config.consoleLogLevels),this.consoleLogger.start()),this.init()}init(){this.widget.init(),document.addEventListener("triagly:submit",t=>{const e=t;this.handleSubmit(e.detail)})}async handleSubmit(t){try{if(!this.rateLimiter.canProceed()){const t=Math.ceil(this.rateLimiter.getTimeUntilReset()/1e3/60);throw new Error(`Rate limit exceeded. Please try again in ${t} minute(s).`)}const e=i(this.config.metadata);let n=null;t.includeScreenshot&&(n=await async function(){try{return void 0!==window.html2canvas?(await window.html2canvas(document.body,{logging:!1,useCORS:!0,allowTaint:!0})).toDataURL("image/png"):"mediaDevices"in navigator&&"getDisplayMedia"in navigator.mediaDevices?(console.warn("Screenshot capture requires html2canvas library"),null):null}catch(t){return console.error("Screenshot capture failed:",t),null}}());const r={title:t.title,description:t.description,reporterEmail:t.reporterEmail,screenshot:n||void 0,consoleLogs:this.consoleLogger?.getLogs()},o=await this.api.submitFeedback(r,e,t.turnstileToken);this.rateLimiter.recordAttempt(),document.dispatchEvent(new CustomEvent("triagly:success",{detail:{feedbackId:o.id}})),this.config.onSuccess&&this.config.onSuccess(o.id),console.log("Feedback submitted successfully:",o.id)}catch(t){throw console.error("Failed to submit feedback:",t),document.dispatchEvent(new CustomEvent("triagly:error",{detail:t})),this.config.onError&&t instanceof Error&&this.config.onError(t),t}}open(){this.widget.open()}close(){this.widget.close()}async submit(t,e){if(this.config.turnstileSiteKey&&!e)throw new Error("Turnstile verification required. When Turnstile is enabled, you must:\n1. Use the widget UI (triagly.open()), or\n2. Implement Turnstile in your form and pass the token: triagly.submit(data, token)");const n=i(this.config.metadata);!t.consoleLogs&&this.consoleLogger&&(t.consoleLogs=this.consoleLogger.getLogs()),await this.api.submitFeedback(t,n,e),this.rateLimiter.recordAttempt()}destroy(){this.widget.destroy(),this.consoleLogger?.stop(),document.removeEventListener("triagly:submit",()=>{})}}if("undefined"!=typeof window){const t=window.TriaglyConfig;t&&(window.triagly=new a(t))}t.Triagly=a,t.default=a,Object.defineProperty(t,"__esModule",{value:!0})}); //# sourceMappingURL=index.min.js.map