@sphoro/bugcatch-web-sdk 0.13.1
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/LICENSE +21 -0
- package/README.md +717 -0
- package/dist/bugcatch.es.js +4393 -0
- package/dist/bugcatch.es.js.map +1 -0
- package/dist/bugcatch.umd.js +952 -0
- package/dist/bugcatch.umd.js.map +1 -0
- package/package.json +57 -0
- package/src/audio.js +191 -0
- package/src/capture.js +300 -0
- package/src/collectors.js +213 -0
- package/src/incoming-call.js +324 -0
- package/src/index.d.ts +684 -0
- package/src/index.js +1217 -0
- package/src/live-call.js +870 -0
- package/src/redact.js +177 -0
- package/src/session.js +189 -0
- package/src/updates.js +1062 -0
- package/src/voice.js +372 -0
- package/src/widget.js +2481 -0
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
(function(V,ee){typeof exports=="object"&&typeof module<"u"?module.exports=ee():typeof define=="function"&&define.amd?define(ee):(V=typeof globalThis<"u"?globalThis:V||self,V.BugCatch=ee())})(this,function(){"use strict";var gn=V=>{throw TypeError(V)};var Zo=(V,ee,we)=>ee.has(V)||gn("Cannot "+we);var mn=(V,ee,we)=>ee.has(V)?gn("Cannot add the same private member more than once"):ee instanceof WeakSet?ee.add(V):ee.set(V,we);var N=(V,ee,we)=>(Zo(V,ee,"access private method"),we);var A,be,vt,xt,bn,kt,wn,Je,yn;const V="[REDACTED]",ee=new Set(["auth","bearer","session","sid","phpsessid","otp","pass","passwd","pwd","sig","codeverifier"]),we=["token","secret","password","passwd","apikey","signature","credential","sessionid","privatekey","accesskey","authorization"],vn=new Set(["key","code","state","nonce","salt","hash"]),St=[/eyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]+/g,/(?:AKIA|ASIA|AROA|AIDA|AGPA|ANPA|ANVA|APKA)[0-9A-Z]{16}/g,/AIza[\w-]{35}/g,/gh[pousr]_[A-Za-z0-9]{36,}/g,/github_pat_\w{22,}/g,/[sprk]k_(?:live|test)_[0-9A-Za-z]{10,}/g,/xox[abposr]-[A-Za-z0-9-]{10,}/g,/sk-ant-[\w-]{20,}/g,/sk-[\w-]{20,}/g,/glpat-[\w-]{20,}/g,/shp(?:at|ss|ca|pa)_[A-Za-z0-9]{32}/g],xn=/\bBearer\s+[\w.~+/=-]{8,}/gi,kn=/^([A-Za-z][A-Za-z0-9+.-]*:\/\/)([^/?#@]+)@/;function Ct(t){try{return decodeURIComponent(t.replace(/\+/g," "))}catch{return t}}function Sn(t){return Ct(t).toLowerCase().replace(/[^a-z0-9]/g,"")}function Cn(t){return t.length>=20?!0:St.some(e=>(e.lastIndex=0,e.test(t)))}function En(t,e){const n=Sn(t);if(!n)return!1;if(ee.has(n))return!0;for(const o of we)if(n.includes(o))return!0;return vn.has(n)?Cn(Ct(e)):!1}function Ge(t){return t.split("&").map(e=>{const n=e.indexOf("=");if(n===-1)return e;const o=e.slice(0,n),a=e.slice(n+1);return a&&En(o,a)?`${o}=${V}`:e}).join("&")}function Et(t){let e=t;for(const n of St)n.lastIndex=0,e=e.replace(n,V);return e}function Tt(t){if(t==null)return t;let e=String(t);if(!e)return e;e=e.replace(kn,`$1${V}@`);const n=e.indexOf("#");let o=n===-1?e:e.slice(0,n),a=n===-1?"":e.slice(n);const r=o.indexOf("?");if(r!==-1&&(o=o.slice(0,r+1)+Ge(o.slice(r+1))),a.length>1){const s=a.slice(1),l=s.indexOf("?");l!==-1?a=`#${s.slice(0,l+1)}${Ge(s.slice(l+1))}`:s.includes("=")&&(a=`#${Ge(s)}`)}return Et(o+a)}function Tn(t){if(t==null)return t;let e=String(t);return e&&(e=e.replace(/[A-Za-z][A-Za-z0-9+.-]{0,30}:\/\/[^\s"'<>)\]]+/g,n=>Tt(n)),e=e.replace(xn,`Bearer ${V}`),Et(e))}function je(t){const e=[];return{push(n){e.push(n),e.length>t&&e.splice(0,e.length-t)},all:()=>e.slice(),clear:()=>{e.length=0},get size(){return e.length}}}const $t=["log","info","warn","error","debug"];function $n(t,{levels:e=["warn","error","log","info"]}={}){const n={};for(const o of $t)n[o]=console[o],e.includes(o)&&(console[o]=(...a)=>{try{t.push({level:o,text:a.map(Lt).join(" ").slice(0,2e3),at:new Date().toISOString()})}catch{}n[o].apply(console,a)});return()=>{for(const o of $t)console[o]=n[o]}}function Bn(t,{slowMs:e=2e3}={}){const n=window.fetch,o=XMLHttpRequest.prototype.open,a=XMLHttpRequest.prototype.send;return n&&(window.fetch=async function(...s){var b,d,w;const l=performance.now(),u=typeof s[0]=="string"?s[0]:(b=s[0])==null?void 0:b.url,g=((d=s[1])==null?void 0:d.method)||(typeof s[0]=="object"?(w=s[0])==null?void 0:w.method:"GET")||"GET";try{const h=await n.apply(this,s);return et(t,{url:u,method:g,status:h.status,ms:Math.round(performance.now()-l),slowMs:e}),h}catch(h){throw et(t,{url:u,method:g,status:0,error:String((h==null?void 0:h.message)||h),ms:Math.round(performance.now()-l),slowMs:e}),h}}),XMLHttpRequest.prototype.open=function(s,l,...u){return this.__bc={method:s,url:l,started:0},o.call(this,s,l,...u)},XMLHttpRequest.prototype.send=function(...s){return this.__bc&&(this.__bc.started=performance.now(),this.addEventListener("loadend",()=>{et(t,{url:this.__bc.url,method:this.__bc.method,status:this.status,ms:Math.round(performance.now()-this.__bc.started),slowMs:e})})),a.apply(this,s)},()=>{n&&(window.fetch=n),XMLHttpRequest.prototype.open=o,XMLHttpRequest.prototype.send=a}}function et(t,{url:e,method:n,status:o,ms:a,error:r,slowMs:s}){try{t.push({url:Tt(String(e)).slice(0,500),method:n,status:o,ms:a,error:r===void 0?void 0:Tn(r),failed:o===0||o>=400,slow:a>=s,at:new Date().toISOString()})}catch{}}function Mn(t){const e=o=>{const a=String(o.message||"Error"),r=o.filename?` (${o.filename}:${o.lineno}:${o.colno})`:"";t.push({level:"error",text:(/^uncaught/i.test(a)?a:`Uncaught ${a}`)+r,at:new Date().toISOString()})},n=o=>{t.push({level:"error",text:`Unhandled rejection: ${Lt(o.reason)}`,at:new Date().toISOString()})};return window.addEventListener("error",e),window.addEventListener("unhandledrejection",n),()=>{window.removeEventListener("error",e),window.removeEventListener("unhandledrejection",n)}}async function Bt(){const t=Mt();try{if(navigator.getBattery){const e=await navigator.getBattery();t.battery=Math.round(e.level*100),t.charging=e.charging}}catch{}return t}function Mt(){var e,n,o,a,r,s,l,u,g;const t=window.navigator||{};return{userAgent:t.userAgent,platform:t.platform,language:t.language,languages:t.languages?[...t.languages]:null,online:t.onLine,cookieEnabled:t.cookieEnabled,maxTouchPoints:t.maxTouchPoints??null,viewport:{width:window.innerWidth,height:window.innerHeight},screen:{width:(e=window.screen)==null?void 0:e.width,height:(n=window.screen)==null?void 0:n.height,availWidth:(o=window.screen)==null?void 0:o.availWidth,availHeight:(a=window.screen)==null?void 0:a.availHeight,colorDepth:(r=window.screen)==null?void 0:r.colorDepth,orientation:((l=(s=window.screen)==null?void 0:s.orientation)==null?void 0:l.type)||null},devicePixelRatio:window.devicePixelRatio,referrer:document.referrer||null,title:document.title,capturedAt:new Date().toISOString(),timezone:Intl.DateTimeFormat().resolvedOptions().timeZone,timezoneOffsetMinutes:new Date().getTimezoneOffset(),browserMode:t.webdriver?"Automated":"Normal",doNotTrack:t.doNotTrack||null,hardwareConcurrency:t.hardwareConcurrency??null,deviceMemory:t.deviceMemory??null,connection:t.connection?{effectiveType:t.connection.effectiveType,downlink:t.connection.downlink,rtt:t.connection.rtt,saveData:t.connection.saveData}:null,prefersColorScheme:(u=window.matchMedia)!=null&&u.call(window,"(prefers-color-scheme: dark)").matches?"dark":"light",prefersReducedMotion:((g=window.matchMedia)==null?void 0:g.call(window,"(prefers-reduced-motion: reduce)").matches)||!1}}function Lt(t){if(typeof t=="string")return t;if(t instanceof Error)return`${t.name}: ${t.message}`;try{return JSON.stringify(t)}catch{return String(t)}}const Ln=typeof window<"u"&&window.fetch?window.fetch.bind(window):null,In=typeof XMLHttpRequest<"u"?XMLHttpRequest.prototype.open:null,An=typeof XMLHttpRequest<"u"?XMLHttpRequest.prototype.send:null,It=["image/png","image/jpeg","image/webp","image/gif","video/webm","video/mp4","audio/webm","audio/ogg","audio/mpeg","audio/mp4","audio/wav","application/pdf","text/plain","text/csv","application/json","application/zip","application/msword","application/vnd.openxmlformats-officedocument.wordprocessingml.document","application/vnd.ms-excel","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],ze=["image/png","image/jpeg","image/webp","video/webm","video/mp4"],Qn=()=>{var t;return!!((t=navigator==null?void 0:navigator.mediaDevices)!=null&&t.getUserMedia&&typeof MediaRecorder<"u")},Rn=()=>{var t;return!!((t=navigator==null?void 0:navigator.mediaDevices)!=null&&t.getDisplayMedia&&typeof MediaRecorder<"u")},jn=()=>{var t;return!!((t=navigator==null?void 0:navigator.mediaDevices)!=null&&t.getDisplayMedia)};function Oe(t,e=It){const n=String(t||"").split(";")[0].trim().toLowerCase();return e.includes(n)?n:null}async function zn(){const t=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:1},audio:!1});try{const e=t.getVideoTracks()[0],n=document.createElement("video");n.srcObject=t,n.muted=!0,n.playsInline=!0,await n.play(),await new Promise(l=>requestAnimationFrame(()=>requestAnimationFrame(l)));const{width:o,height:a}=e.getSettings(),r=document.createElement("canvas");r.width=o||n.videoWidth,r.height=a||n.videoHeight,r.getContext("2d").drawImage(n,0,0,r.width,r.height);const s=await new Promise(l=>r.toBlob(l,"image/png"));if(!s)throw new Error("The browser returned an empty screenshot.");return{blob:s,mime:"image/png",kind:"SCREENSHOT"}}finally{t.getTracks().forEach(e=>e.stop())}}async function On({maxMs:t=12e4,onTick:e,onAutoStop:n}={}){const o=await navigator.mediaDevices.getDisplayMedia({video:{frameRate:15},audio:!1}),a=["video/webm;codecs=vp9","video/webm;codecs=vp8","video/webm"].find(h=>MediaRecorder.isTypeSupported(h))||"video/webm",r=new MediaRecorder(o,{mimeType:a,videoBitsPerSecond:25e5}),s=[];r.ondataavailable=h=>{h.data.size&&s.push(h.data)},r.start(1e3);const l=Date.now(),u=setInterval(()=>e==null?void 0:e(Math.floor((Date.now()-l)/1e3)),500),g=()=>{clearInterval(u),clearTimeout(w),o.getTracks().forEach(h=>h.stop())},b=new Promise(h=>{r.onstop=()=>{g(),h(new Blob(s,{type:a}))}}),d=()=>{r.state!=="inactive"&&r.stop(),n==null||n()};o.getVideoTracks()[0].addEventListener("ended",d);const w=setTimeout(d,t);return{mime:a,maxMs:t,async stop(){return clearTimeout(w),r.state!=="inactive"&&r.stop(),{blob:await b,mime:a,kind:"VIDEO"}},cancel(){g(),r.state!=="inactive"&&r.stop()}}}async function Hn({maxMs:t=12e4,onTick:e,onAutoStop:n}={}){const o=await navigator.mediaDevices.getUserMedia({audio:!0}),a=["audio/webm;codecs=opus","audio/webm","audio/mp4","audio/ogg"].find(h=>MediaRecorder.isTypeSupported(h))||"",r=new MediaRecorder(o,a?{mimeType:a}:void 0),s=[];r.ondataavailable=h=>{h.data.size&&s.push(h.data)},r.start(1e3);const l=Date.now(),u=setInterval(()=>e==null?void 0:e(Math.floor((Date.now()-l)/1e3)),500),g=()=>{clearInterval(u),clearTimeout(w),o.getTracks().forEach(h=>h.stop())},b=new Promise(h=>{r.onstop=()=>{g(),h(new Blob(s,{type:r.mimeType||a}))}}),w=setTimeout(()=>{r.state!=="inactive"&&r.stop(),n==null||n()},t);return{mime:r.mimeType||a,maxMs:t,async stop(){clearTimeout(w),r.state!=="inactive"&&r.stop();const h=await b;return{blob:h,mime:h.type||r.mimeType||a,kind:"AUDIO"}},cancel(){g(),r.state!=="inactive"&&r.stop()}}}async function qn({apiBase:t,apiKey:e,blob:n,mime:o,onProgress:a}){const r=Oe(o||n.type);if(!r)throw new Error(`[BugCatch] ${o||n.type||"that file"} can't be attached.`);const s=await Ln(`${t}/ingest/presign`,{method:"POST",headers:{"Content-Type":"application/json","X-BugCatch-Key":e},body:JSON.stringify({mime:r})});if(!s.ok)throw new Error(`[BugCatch] could not start the upload (HTTP ${s.status}).`);const{key:l,uploadUrl:u}=await s.json();return await Un(u,n,r,a),{storageKey:l,mime:r}}function Un(t,e,n,o){return new Promise((a,r)=>{const s=new XMLHttpRequest;In.call(s,"PUT",t,!0),s.setRequestHeader("Content-Type",n),s.upload.onprogress=l=>{l.lengthComputable&&(o==null||o(l.loaded/l.total))},s.onload=()=>{s.status>=200&&s.status<300?a():r(new Error(`[BugCatch] upload failed (HTTP ${s.status}).`))},s.onerror=()=>r(new Error("[BugCatch] the browser blocked the upload to storage. If this is a new environment, the media bucket is probably missing its CORS policy.")),s.onabort=()=>r(new Error("[BugCatch] upload cancelled.")),An.call(s,e)})}function Se(t){return t<1024?`${t} B`:t<1024*1024?`${(t/1024).toFixed(0)} KB`:`${(t/1024/1024).toFixed(1)} MB`}function He(t){const e=Math.floor(t/60),n=t%60;return`${e}:${String(n).padStart(2,"0")}`}const tt="bugcatch.watch",At=[2e3,5e3,15e3,3e4,6e4],Dn=5e3;function ae(){var t;try{return((t=window.localStorage)==null?void 0:t.getItem(tt))||null}catch{return null}}function nt(t){var e;if(t)try{(e=window.localStorage)==null||e.setItem(tt,t)}catch{}}function ue(){var t;try{(t=window.localStorage)==null||t.removeItem(tt)}catch{}Qt()}const qe="bugcatch.unread";function Nn(t){return[(t==null?void 0:t.bugId)||"report",(t==null?void 0:t.kind)||"status",(t==null?void 0:t.at)||""].join("|")}function Le(){var t;try{const e=(t=window.localStorage)==null?void 0:t.getItem(qe),n=e?JSON.parse(e):[];return Array.isArray(n)?n.filter(o=>o&&typeof o.id=="string"):[]}catch{return[]}}function Ue(t){var e,n;try{t.length?(e=window.localStorage)==null||e.setItem(qe,JSON.stringify(t)):(n=window.localStorage)==null||n.removeItem(qe)}catch{}}function Vn(t){const e={...t,id:Nn(t)},n=Le().filter(o=>o.id!==e.id);return n.push(e),Ue(n.slice(-20)),e}function Pn(t){const e=Le().filter(n=>n.id!==t);return Ue(e),e}function Kn(t){const e=Le().filter(n=>n.bugId!==t);return Ue(e),e}function Qt(){return Ue([]),[]}function _n(t,e){let n=t,o=n.indexOf(`
|
|
2
|
+
|
|
3
|
+
`);for(;o!==-1;){const a=n.slice(0,o);n=n.slice(o+2);const r=a.split(`
|
|
4
|
+
`).filter(s=>s.startsWith("data:")).map(s=>s.slice(5).trim()).join(`
|
|
5
|
+
`);if(r)try{e(JSON.parse(r))}catch{}o=n.indexOf(`
|
|
6
|
+
|
|
7
|
+
`)}return n}async function Wn({apiBase:t}){const e=ae();if(!e)return{reporter:null,reports:[]};const n=await fetch(`${t}/ingest/watch/reports`,{headers:{Authorization:`Bearer ${e}`}});if(n.status===401)return ue(),{reporter:null,reports:[]};if(!n.ok)throw new Error(`[BugCatch] could not load your reports (HTTP ${n.status})`);return n.json()}async function Fn({apiBase:t,bugId:e}){const n=ae();if(!n)return null;const o=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}`,{headers:{Authorization:`Bearer ${n}`}});if(o.status===401)return ue(),null;if(o.status===404)return null;if(!o.ok)throw new Error(`[BugCatch] could not load that report (HTTP ${o.status})`);return o.json()}async function Rt({apiBase:t,bugId:e,body:n,attachments:o}){const a=ae();if(!a)throw new Error("[BugCatch] nothing filed from this browser yet.");const r=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/messages`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({...n?{body:n}:{},...o!=null&&o.length?{attachments:o}:{}})});if(r.status===401)throw ue(),new Error("[BugCatch] this browser can no longer prove it filed that report.");if(!r.ok){let s="";try{const l=await r.json();s=Array.isArray(l.message)?l.message.join(", "):l.message||""}catch{}throw new Error(s||`[BugCatch] could not send that (HTTP ${r.status}).`)}return r.json()}async function Yn({apiBase:t,apiKey:e,projectId:n}){try{const o=await fetch(`${t}/ingest/calls?projectId=${encodeURIComponent(n)}`,{headers:{"X-BugCatch-Key":e}});if(!o.ok)return null;const a=await o.json();return a!=null&&a.available?a.kind==="live"||a.kind==="agent"?a.kind:"agent":null}catch{return null}}async function Zn({apiBase:t,apiKey:e,token:n,payload:o}){const a=await fetch(`${t}/ingest/calls`,{method:"POST",headers:{"Content-Type":"application/json","X-BugCatch-Key":e,...n?{"X-BugCatch-Session":n}:{}},body:JSON.stringify(o)});if(!a.ok){let r="";try{const l=await a.json();r=Array.isArray(l.message)?l.message.join(", "):l.message||""}catch{}const s=new Error(r||`[BugCatch] the call could not be started (HTTP ${a.status}).`);throw s.status=a.status,s}return a.json()}async function Xn({apiBase:t,bugId:e}){const n=ae();if(!n)return!1;try{const o=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/voice-call`,{headers:{Authorization:`Bearer ${n}`}});if(!o.ok)return!1;const a=await o.json();return!!(a!=null&&a.offered)}catch{return!1}}async function Jn({apiBase:t,bugId:e}){const n=ae();if(!n)throw new Error("[BugCatch] nothing filed from this browser yet.");const o=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/voice-call`,{method:"POST",headers:{Authorization:`Bearer ${n}`}});if(o.status===401)throw ue(),new Error("[BugCatch] this browser can no longer prove it filed that report.");if(!o.ok){let a="";try{const r=await o.json();a=Array.isArray(r.message)?r.message.join(", "):r.message||""}catch{}throw new Error(a||`[BugCatch] the call could not be started (HTTP ${o.status}).`)}return o.json()}async function Gn({apiBase:t,bugId:e}){const n=ae();if(!n)return!1;try{const o=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/live-call`,{headers:{Authorization:`Bearer ${n}`}});if(!o.ok)return!1;const a=await o.json();return!!(a!=null&&a.offered)}catch{return!1}}async function eo({apiBase:t,bugId:e}){const n=ae();if(!n)throw new Error("[BugCatch] nothing filed from this browser yet.");const o=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/live-call`,{method:"POST",headers:{Authorization:`Bearer ${n}`}});if(o.status===401)throw ue(),new Error("[BugCatch] this browser can no longer prove it filed that report.");if(!o.ok){let a="";try{const r=await o.json();a=Array.isArray(r.message)?r.message.join(", "):r.message||""}catch{}throw new Error(a||`[BugCatch] the call could not be started (HTTP ${o.status}).`)}return o.json()}async function to({apiBase:t,bugId:e,callId:n}){const o=ae();if(o)try{await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/live-call/${encodeURIComponent(n)}/give-up`,{method:"POST",headers:{Authorization:`Bearer ${o}`}})}catch{}}async function no({apiBase:t,bugId:e,callId:n}){const o=ae();if(!o)throw new Error("[BugCatch] nothing filed from this browser yet.");const a=await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/live-call/${encodeURIComponent(n)}/answer`,{method:"POST",headers:{Authorization:`Bearer ${o}`}});if(a.status===401)throw ue(),new Error("[BugCatch] this browser can no longer prove it filed that report.");if(a.status===409)throw new Error("[BugCatch] that call is no longer ringing.");if(!a.ok){let r="";try{const s=await a.json();r=Array.isArray(s.message)?s.message.join(", "):s.message||""}catch{}throw new Error(r||`[BugCatch] the call could not be answered (HTTP ${a.status}).`)}return a.json()}async function jt({apiBase:t,bugId:e,callId:n}){const o=ae();if(o)try{await fetch(`${t}/ingest/watch/reports/${encodeURIComponent(e)}/live-call/${encodeURIComponent(n)}/decline`,{method:"POST",headers:{Authorization:`Bearer ${o}`}})}catch{}}function oo({apiBase:t,onUpdate:e}){let n=!0,o=0,a=null;async function r(){const s=ae();if(!s)throw new Error("nothing filed from this browser yet");a=new AbortController;const l=await fetch(`${t}/ingest/watch/stream`,{headers:{Authorization:`Bearer ${s}`,Accept:"text/event-stream"},signal:a.signal});if(l.status===401)throw n=!1,ue(),new Error("subscription no longer valid");if(!l.ok||!l.body)throw new Error(`stream failed (${l.status})`);const u=Date.now(),g=l.body.getReader(),b=new TextDecoder;let d="";try{for(;;){const{value:w,done:h}=await g.read();if(h)break;d+=b.decode(w,{stream:!0}),d=_n(d,S=>{if(S.type!=="ping")try{e(S)}catch{}})}}finally{Date.now()-u>=Dn&&(o=0)}}return(async()=>{for(;n;){try{await r()}catch(l){if((l==null?void 0:l.name)==="AbortError")return}if(!n)return;const s=At[Math.min(o,At.length-1)];o+=1,await new Promise(l=>setTimeout(l,s))}})(),()=>{n=!1,a==null||a.abort(),a=null}}const ao={OPEN:"is open",IN_PROGRESS:"is being worked on",RESOLVED:"has been fixed",CLOSED:"has been closed"},ro={status:null,severity:"#e8912a",assignment:"#4da3ff",reply:"#35b87f"};function zt(t){return t!=null&&t.summary?t.summary:`Your report ${ao[t==null?void 0:t.status]||"was updated"}`}function io(t){var e;return(t==null?void 0:t.kind)==="reply"&&((e=t.reply)!=null&&e.body)?`${t.reply.author?`${t.reply.author}: `:""}${t.reply.body}`:(t==null?void 0:t.title)||""}function so(t){const e=Date.parse(t||"");if(!Number.isFinite(e))return"";const n=Math.round((Date.now()-e)/6e4);if(n<1)return"just now";if(n<60)return`${n}m ago`;const o=Math.round(n/60);if(o<24)return`${o}h ago`;const a=Math.round(o/24);return a===1?"yesterday":`${a}d ago`}function co(){var t;try{const e=document.querySelector('[data-bugcatch="widget"]'),n=(t=e==null?void 0:e.shadowRoot)==null?void 0:t.querySelector(".dock"),o=(n==null?void 0:n.getBoundingClientRect().height)||0;return o?Math.round(o)+12:0}catch{return 0}}function lo({accent:t="#4F46E5",position:e="bottom-right",onRead:n=null,onReadAll:o=null,onCall:a=null}={}){const[r,s]=String(e).split("-"),l=r==="top",u=s==="left",g=new Map;let b=null,d=null;function w(){return b!=null&&b.isConnected||(b=document.createElement("div"),b.setAttribute("data-bugcatch","updates"),b.style.cssText=["position:fixed",`${l?"top":"bottom"}:${20+co()}px`,`${u?"left":"right"}:20px`,"z-index:2147482998","display:flex","flex-direction:column","gap:10px",`align-items:${u?"flex-start":"flex-end"}`,"width:min(320px,calc(100vw - 40px))","pointer-events:none"].join(";"),document.body.appendChild(b)),b}function h(k){if(!k._bcShown){k.remove();return}k.style.opacity="0",k.style.transform=`translateY(${l?-8:8}px)`,setTimeout(()=>k.remove(),250)}function S(k,y){const x=document.createElement("button");return x.type="button",x.textContent=k,x.style.cssText=["padding:5px 11px",`border:${y?"0":"1px solid rgba(236,238,242,.24)"}`,"border-radius:6px",`background:${y?t:"transparent"}`,`color:${y?"#fff":"#eceef2"}`,"font:600 12px/1.4 inherit","cursor:pointer","white-space:nowrap"].join(";"),x}function O(k){var _;const y=document.createElement("div");y.setAttribute("role","status"),y.style.cssText=["box-sizing:border-box","width:100%","padding:12px 14px","border-radius:10px","background:#1b1e26","color:#eceef2",'font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',"box-shadow:0 12px 30px rgba(0,0,0,0.45)",`border-left:3px solid ${ro[k==null?void 0:k.kind]||t}`,"opacity:0",`transform:translateY(${l?-8:8}px)`,"transition:opacity .2s ease,transform .2s ease","pointer-events:auto"].join(";");const x=document.createElement("div");x.style.cssText="font-weight:600;margin-bottom:2px",x.textContent=zt(k);const C=document.createElement("div");C.style.cssText=(k==null?void 0:k.kind)==="reply"?"color:#a4abba;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden":"color:#a4abba",C.textContent=io(k);const R=document.createElement("div");R.style.cssText="display:flex;align-items:center;gap:8px;margin-top:10px;flex-wrap:wrap";const Z=document.createElement("span");Z.style.cssText="color:#7b8394;font-size:11px;margin-right:auto",Z.textContent=so(k==null?void 0:k.at);const H=S("Mark as read",!1);if(H.setAttribute("aria-label",`Mark as read: ${zt(k)}`),H.addEventListener("click",()=>n==null?void 0:n(k.id)),R.append(Z,H),(_=k==null?void 0:k.call)!=null&&_.offered&&typeof a=="function"){const B=S("Talk it through",!0);B.addEventListener("click",()=>a(k)),R.insertBefore(B,H)}return y.append(x,C,R),y}function Y(k){const y=document.createElement("div");y.style.cssText=["box-sizing:border-box","width:100%","display:flex","align-items:center","gap:8px","padding:8px 12px","border-radius:10px","background:#1b1e26","color:#a4abba",'font:12px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',"box-shadow:0 12px 30px rgba(0,0,0,0.45)","pointer-events:auto"].join(";");const x=document.createElement("span");x.style.cssText="margin-right:auto",x.textContent=`${k} more update${k===1?"":"s"}`;const C=S("Mark all as read",!1);return C.style.cssText+=";padding:4px 9px",C.addEventListener("click",()=>o==null?void 0:o()),y.append(x,C),y}return{render(k){const y=Array.isArray(k)?k:[];if(!y.length){for(const B of g.values())h(B);g.clear(),d==null||d.remove(),d=null,b==null||b.remove(),b=null;return}const x=y.slice(-3),C=new Set(x.map(B=>B.id)),R=w();for(const[B,J]of g)C.has(B)||(h(J),g.delete(B));const Z=[];for(const B of x){if(g.has(B.id))continue;const J=O(B);g.set(B.id,J),Z.push(J)}const H=l?[...x].reverse():x;for(const B of H)R.appendChild(g.get(B.id));const _=y.length-x.length;_>0?(d==null||d.remove(),d=Y(_),l?R.appendChild(d):R.insertBefore(d,R.firstChild)):(d==null||d.remove(),d=null),requestAnimationFrame(()=>{for(const B of Z)B.isConnected&&(B._bcShown=!0,B.style.opacity="1",B.style.transform="translateY(0)")})},destroy(){g.clear(),d=null,b==null||b.remove(),b=null}}}async function uo({apiBase:t,apiKey:e,projectId:n,sessionToken:o}){try{const a=await fetch(`${t}/ingest/known-issues?projectId=${encodeURIComponent(n)}`,{headers:ot(e,o)});if(!a.ok)return[];const r=await a.json();return Array.isArray(r==null?void 0:r.issues)?r.issues:[]}catch{return[]}}async function po({apiBase:t,apiKey:e,projectId:n,q:o,sessionToken:a,signal:r}){try{const s=`${t}/ingest/known-issues/match?projectId=${encodeURIComponent(n)}&q=${encodeURIComponent(o)}`,l=await fetch(s,{headers:ot(e,a),signal:r});if(!l.ok)return[];const u=await l.json();return Array.isArray(u==null?void 0:u.issues)?u.issues:[]}catch{return[]}}async function ho({apiBase:t,apiKey:e,projectId:n,bugId:o,reporterEmail:a,reporterName:r,metadata:s,logs:l,sessionToken:u}){const g=await fetch(`${t}/ingest/known-issues/${encodeURIComponent(o)}/join`,{method:"POST",headers:ot(e,u),body:JSON.stringify({projectId:n,reporterEmail:a,reporterName:r,metadata:s,logs:l})});if(!g.ok)throw new Error(await fo(g));const b=await g.json();return b!=null&&b.watchToken&&nt(b.watchToken),b}function ot(t,e){return{"Content-Type":"application/json","X-BugCatch-Key":t,...e?{"X-BugCatch-Session":e}:{}}}async function fo(t){try{const e=await t.json(),n=Array.isArray(e.message)?e.message.join(", "):e.message;if(n)return n}catch{}return`Could not add you to this issue (HTTP ${t.status})`}const Ot={"bottom-right":"bottom:20px;right:20px;","bottom-left":"bottom:20px;left:20px;","top-right":"top:20px;right:20px;","top-left":"top:20px;left:20px;"},go=[{value:"LOW",label:"Low"},{value:"MEDIUM",label:"Medium"},{value:"HIGH",label:"High"},{value:"CRITICAL",label:"Critical"}],z={camera:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 8.5A1.5 1.5 0 0 1 4.5 7h2.2a1 1 0 0 0 .83-.45l.94-1.4A1 1 0 0 1 9.3 4.7h5.4a1 1 0 0 1 .83.45l.94 1.4a1 1 0 0 0 .83.45h2.2A1.5 1.5 0 0 1 21 8.5v9A1.5 1.5 0 0 1 19.5 19h-15A1.5 1.5 0 0 1 3 17.5z"/><circle cx="12" cy="13" r="3.4"/></svg>',video:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="12.5" height="12" rx="2.2"/><path d="M15.5 10.8l4.2-2.6a.6.6 0 0 1 .92.5v6.6a.6.6 0 0 1-.92.5l-4.2-2.6z"/></svg>',paperclip:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M20 11.5l-7.6 7.6a4.6 4.6 0 0 1-6.5-6.5l8-8a3.2 3.2 0 0 1 4.5 4.5l-8 8a1.8 1.8 0 0 1-2.5-2.5l7.1-7.1"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>',mic:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2.5" width="6" height="11" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M9 21h6"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12l16-8-6 16-2.6-6.2L4 12Z"/></svg>',stopSquare:'<svg viewBox="0 0 24 24" fill="currentColor"><rect x="7" y="7" width="10" height="10" rx="2"/></svg>',back:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 5.5L8 12l6.5 6.5"/></svg>',phone:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z"/></svg>',phoneOff:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6.2 3.5h3l1.5 3.7-2 1.4a11.5 11.5 0 0 0 5.7 5.7l1.4-2 3.7 1.5v3a1.8 1.8 0 0 1-2 1.8A15.6 15.6 0 0 1 4.4 5.5a1.8 1.8 0 0 1 1.8-2z"/><path d="M3 21L21 3"/></svg>',bug:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8 6a4 4 0 0 1 8 0"/><rect x="7" y="7.5" width="10" height="12" rx="5"/><path d="M7 11H3.5M7 15.5H4M17 11h3.5M17 15.5h3M12 8v11.5"/></svg>',check:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.8l4.6 4.4L19 7"/></svg>',retry:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 12a8 8 0 1 1-2.6-5.9M20 4v4.5h-4.5"/></svg>',sun:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4.2"/><path d="M12 2.6v2.2M12 19.2v2.2M2.6 12h2.2M19.2 12h2.2M5.4 5.4l1.6 1.6M17 17l1.6 1.6M18.6 5.4L17 7M7 17l-1.6 1.6"/></svg>',moon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13.4A8.2 8.2 0 1 1 10.6 4a6.6 6.6 0 0 0 9.4 9.4z"/></svg>',auto:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="2.8" y="4.4" width="18.4" height="12.6" rx="2.2"/><path d="M8.6 20.2h6.8"/><path d="M12 4.4v12.6"/><path d="M12 7.2a3.6 3.6 0 0 1 0 7.2z" fill="currentColor" stroke="none"/></svg>'},mo='<svg class="bc-brand" viewBox="0 0 254 60" fill="none" role="img" aria-label="sphoro BugCatch"><g fill="currentColor"><path transform="translate(0.00 40.00) scale(0.04000 -0.04000)" d="M276.00 -14.00Q179.00 -14.00 117.00 28.00Q55.00 70.00 42.00 148.00L158.00 178.00Q165.00 143.00 181.50 123.00Q198.00 103.00 222.50 94.50Q247.00 86.00 276.00 86.00Q320.00 86.00 341.00 101.50Q362.00 117.00 362.00 140.00Q362.00 163.00 342.00 175.50Q322.00 188.00 278.00 196.00L250.00 201.00Q198.00 211.00 155.00 228.50Q112.00 246.00 86.00 277.00Q60.00 308.00 60.00 357.00Q60.00 431.00 114.00 470.50Q168.00 510.00 256.00 510.00Q339.00 510.00 394.00 473.00Q449.00 436.00 466.00 376.00L349.00 340.00Q341.00 378.00 316.50 394.00Q292.00 410.00 256.00 410.00Q220.00 410.00 201.00 397.50Q182.00 385.00 182.00 363.00Q182.00 339.00 202.00 327.50Q222.00 316.00 256.00 310.00L284.00 305.00Q340.00 295.00 385.50 278.50Q431.00 262.00 457.50 231.50Q484.00 201.00 484.00 149.00Q484.00 71.00 427.50 28.50Q371.00 -14.00 276.00 -14.00Z"/><path transform="translate(19.56 40.00) scale(0.04000 -0.04000)" d="M70.00 -200.00V496.00H194.00V436.00H212.00Q229.00 465.00 265.00 487.50Q301.00 510.00 368.00 510.00Q428.00 510.00 479.00 480.50Q530.00 451.00 561.00 394.00Q592.00 337.00 592.00 256.00V240.00Q592.00 159.00 561.00 102.00Q530.00 45.00 479.00 15.50Q428.00 -14.00 368.00 -14.00Q323.00 -14.00 292.50 -3.50Q262.00 7.00 243.50 23.50Q225.00 40.00 214.00 57.00H196.00V-200.00ZM330.00 96.00Q389.00 96.00 427.50 133.50Q466.00 171.00 466.00 243.00V253.00Q466.00 325.00 427.00 362.50Q388.00 400.00 330.00 400.00Q272.00 400.00 233.00 362.50Q194.00 325.00 194.00 253.00V243.00Q194.00 171.00 233.00 133.50Q272.00 96.00 330.00 96.00Z"/><path transform="translate(43.68 40.00) scale(0.04000 -0.04000)" d="M70.00 0.00V700.00H196.00V435.00H214.00Q222.00 451.00 239.00 467.00Q256.00 483.00 284.50 493.50Q313.00 504.00 357.00 504.00Q415.00 504.00 458.50 477.50Q502.00 451.00 526.00 404.50Q550.00 358.00 550.00 296.00V0.00H424.00V286.00Q424.00 342.00 396.50 370.00Q369.00 398.00 318.00 398.00Q260.00 398.00 228.00 359.50Q196.00 321.00 196.00 252.00V0.00Z"/><path transform="translate(66.92 40.00) scale(0.04000 -0.04000)" d="M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z"/><path transform="translate(90.00 40.00) scale(0.04000 -0.04000)" d="M70.00 0.00V496.00H194.00V440.00H212.00Q223.00 470.00 248.50 484.00Q274.00 498.00 308.00 498.00H368.00V386.00H306.00Q258.00 386.00 227.00 360.50Q196.00 335.00 196.00 282.00V0.00Z"/><path transform="translate(104.44 40.00) scale(0.04000 -0.04000)" d="M306.00 -14.00Q232.00 -14.00 173.00 16.00Q114.00 46.00 80.00 103.00Q46.00 160.00 46.00 240.00V256.00Q46.00 336.00 80.00 393.00Q114.00 450.00 173.00 480.00Q232.00 510.00 306.00 510.00Q380.00 510.00 439.00 480.00Q498.00 450.00 532.00 393.00Q566.00 336.00 566.00 256.00V240.00Q566.00 160.00 532.00 103.00Q498.00 46.00 439.00 16.00Q380.00 -14.00 306.00 -14.00ZM306.00 98.00Q364.00 98.00 402.00 135.50Q440.00 173.00 440.00 243.00V253.00Q440.00 323.00 402.50 360.50Q365.00 398.00 306.00 398.00Q248.00 398.00 210.00 360.50Q172.00 323.00 172.00 253.00V243.00Q172.00 173.00 210.00 135.50Q248.00 98.00 306.00 98.00Z"/></g><rect x="139.52" y="16" width="1.6" height="26" rx="0.8" fill="var(--bc-brand-mark, #5647ee)" opacity="0.4"/><g fill="var(--bc-brand-mark, #5647ee)"><path transform="translate(154.52 35.00) scale(0.01400 -0.01400)" d="M46.35 0.00V105.85H139.75V594.15H46.35V700.00H404.08Q467.38 700.00 514.79 678.41Q562.20 656.83 588.70 617.33Q615.20 577.83 615.20 523.35V513.70Q615.20 465.52 597.03 434.76Q578.85 404.00 553.91 387.24Q528.98 370.48 506.28 363.30V346.35Q528.98 340.35 555.24 323.50Q581.50 306.65 600.00 275.48Q618.50 244.30 618.50 194.48V184.48Q618.50 126.65 591.41 85.24Q564.33 43.83 516.83 21.91Q469.33 0.00 406.85 0.00ZM259.85 109.15H392.60Q441.20 109.15 469.80 132.60Q498.40 156.05 498.40 198.55V208.20Q498.40 250.70 470.21 274.15Q442.02 297.60 392.60 297.60H259.85ZM259.85 406.75H391.65Q437.55 406.75 466.32 430.11Q495.10 453.48 495.10 493.80V503.80Q495.10 544.95 466.74 567.90Q438.37 590.85 391.65 590.85H259.85Z"/><path transform="translate(167.00 35.00) scale(0.01400 -0.01400)" d="M335.30 -14.00Q251.22 -14.00 190.60 16.63Q129.97 47.25 97.56 104.96Q65.15 162.68 65.15 243.03V700.00H185.25V239.73Q185.25 170.48 224.03 131.99Q262.80 93.50 335.30 93.50Q407.80 93.50 446.57 131.99Q485.35 170.48 485.35 239.73V700.00H605.45V243.03Q605.45 162.68 573.13 104.96Q540.80 47.25 480.09 16.63Q419.38 -14.00 335.30 -14.00Z"/><path transform="translate(179.60 35.00) scale(0.01400 -0.01400)" d="M311.85 -14.00Q238.45 -14.00 179.69 18.50Q120.92 51.00 86.69 113.67Q52.45 176.35 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.23 645.59Q598.33 577.17 598.33 460.87V456.70H479.87V464.52Q479.87 504.67 463.76 536.86Q447.65 569.05 414.99 587.77Q382.32 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 138.89Q257.63 91.85 334.00 91.85Q410.02 91.85 445.12 132.83Q480.22 173.80 480.22 237.92V249.67H302.85V351.88H598.33V0.00H487.52V69.10H470.57Q462.70 50.87 446.06 31.44Q429.42 12.00 397.64 -1.00Q365.85 -14.00 311.85 -14.00Z"/><path transform="translate(192.05 35.00) scale(0.01400 -0.01400)" d="M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z"/><path transform="translate(204.25 35.00) scale(0.01400 -0.01400)" d="M19.75 0.00 211.62 700.00H421.33L613.20 0.00H489.27L447.42 159.60H185.53L143.68 0.00ZM214.25 270.40H418.70L324.95 624.87H308.00Z"/><path transform="translate(216.31 35.00) scale(0.01400 -0.01400)" d="M233.95 0.00V590.85H24.70V700.00H563.30V590.85H354.05V0.00Z"/><path transform="translate(227.75 35.00) scale(0.01400 -0.01400)" d="M332.35 -14.00Q203.57 -14.00 128.01 58.50Q52.45 131.00 52.45 266.87V433.13Q52.45 569.00 128.01 641.50Q203.57 714.00 332.35 714.00Q460.13 714.00 529.58 643.85Q599.03 573.70 599.03 451.57V445.75H480.57V455.22Q480.57 521.42 444.15 563.96Q407.72 606.50 332.35 606.50Q257.63 606.50 215.09 560.70Q172.55 514.90 172.55 435.13V264.87Q172.55 185.93 215.09 139.71Q257.63 93.50 332.35 93.50Q407.72 93.50 444.15 136.45Q480.57 179.40 480.57 244.78V262.25H599.03V248.43Q599.03 126.30 529.58 56.15Q460.13 -14.00 332.35 -14.00Z"/><path transform="translate(239.95 35.00) scale(0.01400 -0.01400)" d="M69.50 0.00V700.00H189.60V405.58H466.40V700.00H586.50V0.00H466.40V296.42H189.60V0.00Z"/></g></svg>',Ht=[{value:"light",label:"Light",icon:z.sun},{value:"dark",label:"Dark",icon:z.moon},{value:"auto",label:"System default",icon:z.auto}],qt="bugcatch:theme";function bo(){try{const t=localStorage.getItem(qt);return Ht.some(e=>e.value===t)?t:null}catch{return null}}function wo(t){try{localStorage.setItem(qt,t)}catch{}}const yo=t=>`
|
|
8
|
+
:host { all: initial; }
|
|
9
|
+
:host {
|
|
10
|
+
--bc-accent: ${t};
|
|
11
|
+
--bc-on-accent: #fff;
|
|
12
|
+
--bc-bg: #fff;
|
|
13
|
+
--bc-elevated: #fff;
|
|
14
|
+
--bc-fg: #14161c;
|
|
15
|
+
--bc-muted: #6b7280;
|
|
16
|
+
--bc-faint: #9aa1ad;
|
|
17
|
+
--bc-border: #e3e6ec;
|
|
18
|
+
--bc-subtle: #f5f6f8;
|
|
19
|
+
--bc-raised: #fff;
|
|
20
|
+
--bc-danger: #dc2626;
|
|
21
|
+
--bc-ok: #059669;
|
|
22
|
+
/* The lockup's own two colours, kept off --bc-accent on purpose: that one is
|
|
23
|
+
the customer's brand, and this mark is ours. Values are the light cut of
|
|
24
|
+
brand/sphoro-bugcatch-primary.svg, pulled up from near-black so the mark
|
|
25
|
+
identifies itself without out-shouting the host's page. */
|
|
26
|
+
--bc-brand-ink: #4b5563;
|
|
27
|
+
--bc-brand-mark: #5647ee;
|
|
28
|
+
--bc-shadow: 0 24px 60px rgba(10,12,16,.28), 0 2px 8px rgba(10,12,16,.08);
|
|
29
|
+
--bc-dur: 240ms;
|
|
30
|
+
--bc-ease: cubic-bezier(.22,1,.36,1);
|
|
31
|
+
}
|
|
32
|
+
:host(.bc-dark) {
|
|
33
|
+
--bc-bg: #14161c;
|
|
34
|
+
--bc-elevated: #1c1f27;
|
|
35
|
+
--bc-fg: #eef0f4;
|
|
36
|
+
--bc-muted: #9aa1ad;
|
|
37
|
+
--bc-faint: #6b7280;
|
|
38
|
+
--bc-border: #2b2f39;
|
|
39
|
+
--bc-subtle: #21242c;
|
|
40
|
+
--bc-raised: #363d4b;
|
|
41
|
+
--bc-brand-ink: #9aa1ad;
|
|
42
|
+
--bc-brand-mark: #9b8fff;
|
|
43
|
+
--bc-shadow: 0 24px 60px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);
|
|
44
|
+
}
|
|
45
|
+
@media (prefers-color-scheme: dark) {
|
|
46
|
+
:host(.bc-auto) {
|
|
47
|
+
--bc-bg: #14161c;
|
|
48
|
+
--bc-elevated: #1c1f27;
|
|
49
|
+
--bc-fg: #eef0f4;
|
|
50
|
+
--bc-muted: #9aa1ad;
|
|
51
|
+
--bc-faint: #6b7280;
|
|
52
|
+
--bc-border: #2b2f39;
|
|
53
|
+
--bc-subtle: #21242c;
|
|
54
|
+
--bc-raised: #363d4b;
|
|
55
|
+
--bc-brand-ink: #9aa1ad;
|
|
56
|
+
--bc-brand-mark: #9b8fff;
|
|
57
|
+
--bc-shadow: 0 24px 60px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif; }
|
|
62
|
+
svg { width: 1em; height: 1em; display: block; }
|
|
63
|
+
/* The hidden attribute has to actually hide. Nearly every element in here is given a
|
|
64
|
+
display by a rule below, and any of them beats the attribute's own UA style
|
|
65
|
+
— which is why .dock and .launcher each had to say so themselves. Said once,
|
|
66
|
+
for all of them, so the next element hidden from JavaScript works. */
|
|
67
|
+
[hidden] { display: none !important; }
|
|
68
|
+
|
|
69
|
+
/* Motion is decoration here — every animation below is an entrance or a state
|
|
70
|
+
hint, so switching them off costs nothing but the polish. */
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
*, *::before, *::after {
|
|
73
|
+
animation-duration: .01ms !important;
|
|
74
|
+
animation-iteration-count: 1 !important;
|
|
75
|
+
transition-duration: .01ms !important;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@keyframes bc-fade-in { from { opacity: 0 } to { opacity: 1 } }
|
|
80
|
+
@keyframes bc-fade-out { to { opacity: 0 } }
|
|
81
|
+
@keyframes bc-panel-in { from { opacity: 0; transform: translateY(16px) scale(.97) } to { opacity: 1; transform: none } }
|
|
82
|
+
@keyframes bc-panel-out { to { opacity: 0; transform: translateY(8px) scale(.985) } }
|
|
83
|
+
@keyframes bc-launcher-in { from { opacity: 0; transform: translateY(12px) scale(.86) } to { opacity: 1; transform: none } }
|
|
84
|
+
@keyframes bc-item-in { from { opacity: 0; transform: translateY(8px) scale(.92) } to { opacity: 1; transform: none } }
|
|
85
|
+
@keyframes bc-pulse { 0%, 100% { opacity: 1; transform: scale(1) } 50% { opacity: .3; transform: scale(.78) } }
|
|
86
|
+
@keyframes bc-spin { to { transform: rotate(360deg) } }
|
|
87
|
+
@keyframes bc-pop { 0% { transform: scale(.5); opacity: 0 } 65% { transform: scale(1.1) } 100% { transform: scale(1); opacity: 1 } }
|
|
88
|
+
@keyframes bc-nudge { 25% { transform: translateX(-3px) } 75% { transform: translateX(3px) } }
|
|
89
|
+
|
|
90
|
+
/* ---------- dock: theme switch stacked above the launcher ---------- */
|
|
91
|
+
.dock {
|
|
92
|
+
position: fixed; z-index: 2147483000;
|
|
93
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
94
|
+
}
|
|
95
|
+
.dock[hidden] { display: none; }
|
|
96
|
+
/* While the panel is open the switch rises above the scrim so it stays usable
|
|
97
|
+
— the panel shares the shadow root, so a change re-themes the open form. */
|
|
98
|
+
.dock.over { z-index: 2147483002; }
|
|
99
|
+
.dock.at-right { align-items: flex-end; }
|
|
100
|
+
.dock.at-left { align-items: flex-start; }
|
|
101
|
+
/* Anchored to the top, "above the launcher" would be off-screen, so the
|
|
102
|
+
switch goes under it and the launcher stays the outermost thing. */
|
|
103
|
+
.dock.at-top { flex-direction: column-reverse; }
|
|
104
|
+
|
|
105
|
+
.themer {
|
|
106
|
+
display: inline-flex; align-items: center; gap: 2px;
|
|
107
|
+
padding: 3px; border-radius: 999px;
|
|
108
|
+
background: var(--bc-elevated); border: 1px solid var(--bc-border);
|
|
109
|
+
box-shadow: 0 6px 18px rgba(10,12,16,.16), 0 1px 3px rgba(10,12,16,.10);
|
|
110
|
+
animation: bc-launcher-in 420ms var(--bc-ease) both;
|
|
111
|
+
}
|
|
112
|
+
.themer button {
|
|
113
|
+
display: grid; place-items: center;
|
|
114
|
+
width: 28px; height: 28px; padding: 0;
|
|
115
|
+
border: none; border-radius: 999px; background: transparent;
|
|
116
|
+
color: var(--bc-muted); cursor: pointer;
|
|
117
|
+
transition: background var(--bc-dur) var(--bc-ease), color var(--bc-dur) var(--bc-ease), transform 140ms var(--bc-ease);
|
|
118
|
+
}
|
|
119
|
+
.themer button svg { font-size: 15px; }
|
|
120
|
+
.themer button:hover { background: var(--bc-subtle); color: var(--bc-fg); }
|
|
121
|
+
.themer button:active { transform: scale(.92); }
|
|
122
|
+
.themer button[aria-pressed="true"] { background: var(--bc-accent); color: var(--bc-on-accent); }
|
|
123
|
+
.themer button[aria-pressed="true"]:hover { background: var(--bc-accent); filter: brightness(1.06); }
|
|
124
|
+
.themer button:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 2px; }
|
|
125
|
+
|
|
126
|
+
/* ---------- launcher ---------- */
|
|
127
|
+
.launcher {
|
|
128
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
129
|
+
padding: 11px 17px; border: none; border-radius: 999px;
|
|
130
|
+
background: var(--bc-accent); color: var(--bc-on-accent);
|
|
131
|
+
font-size: 14px; font-weight: 600; line-height: 1; letter-spacing: -.01em;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
box-shadow: 0 6px 18px rgba(10,12,16,.22), 0 1px 3px rgba(10,12,16,.16);
|
|
134
|
+
transition: transform var(--bc-dur) var(--bc-ease), box-shadow var(--bc-dur) var(--bc-ease), filter 160ms linear;
|
|
135
|
+
animation: bc-launcher-in 420ms var(--bc-ease) both;
|
|
136
|
+
}
|
|
137
|
+
.launcher svg { font-size: 17px; }
|
|
138
|
+
.launcher:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(10,12,16,.26), 0 2px 6px rgba(10,12,16,.18); filter: brightness(1.06); }
|
|
139
|
+
.launcher:active { transform: translateY(0) scale(.97); transition-duration: 90ms; }
|
|
140
|
+
.launcher:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 3px; }
|
|
141
|
+
.launcher[hidden] { display: none; }
|
|
142
|
+
|
|
143
|
+
/* ---------- scrim + panel ---------- */
|
|
144
|
+
.scrim {
|
|
145
|
+
position: fixed; inset: 0; z-index: 2147483001;
|
|
146
|
+
background: rgba(10,12,16,.5);
|
|
147
|
+
animation: bc-fade-in 200ms ease both;
|
|
148
|
+
}
|
|
149
|
+
.scrim.closing { animation: bc-fade-out 150ms ease both; }
|
|
150
|
+
|
|
151
|
+
/* The panel belongs to the launcher, so it opens out of that corner rather
|
|
152
|
+
than in the middle of the screen. --bc-dock-h is measured at open time
|
|
153
|
+
because the dock's height depends on whether the launcher is showing. */
|
|
154
|
+
.panel {
|
|
155
|
+
position: fixed;
|
|
156
|
+
width: calc(100vw - 40px); max-width: 440px;
|
|
157
|
+
max-height: calc(100vh - var(--bc-dock-h, 84px) - 52px);
|
|
158
|
+
overflow-y: auto; overscroll-behavior: contain;
|
|
159
|
+
background: var(--bc-bg); color: var(--bc-fg);
|
|
160
|
+
border-radius: 16px; box-shadow: var(--bc-shadow);
|
|
161
|
+
animation: bc-panel-in 320ms var(--bc-ease) both;
|
|
162
|
+
}
|
|
163
|
+
.scrim.at-right .panel { right: 20px; }
|
|
164
|
+
.scrim.at-left .panel { left: 20px; }
|
|
165
|
+
.scrim.at-bottom .panel { bottom: calc(20px + var(--bc-dock-h, 84px) + 12px); }
|
|
166
|
+
.scrim.at-top .panel { top: calc(20px + var(--bc-dock-h, 84px) + 12px); }
|
|
167
|
+
.scrim.closing .panel { animation: bc-panel-out 150ms ease both; }
|
|
168
|
+
|
|
169
|
+
.head { display: flex; align-items: flex-start; gap: 11px; padding: 18px 18px 0; }
|
|
170
|
+
.head .mark {
|
|
171
|
+
flex: none; display: grid; place-items: center;
|
|
172
|
+
width: 32px; height: 32px; border-radius: 9px;
|
|
173
|
+
background: color-mix(in srgb, var(--bc-accent) 13%, transparent);
|
|
174
|
+
color: var(--bc-accent); font-size: 18px;
|
|
175
|
+
}
|
|
176
|
+
.head .titles { flex: 1; min-width: 0; }
|
|
177
|
+
.head h2 { margin: 1px 0 3px; font-size: 16px; font-weight: 650; letter-spacing: -.015em; }
|
|
178
|
+
.head p { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--bc-muted); }
|
|
179
|
+
.icon-btn {
|
|
180
|
+
flex: none; display: grid; place-items: center;
|
|
181
|
+
width: 28px; height: 28px; padding: 0; font-size: 15px;
|
|
182
|
+
border: none; border-radius: 8px; background: none; color: var(--bc-faint);
|
|
183
|
+
cursor: pointer; transition: background 160ms, color 160ms;
|
|
184
|
+
}
|
|
185
|
+
.icon-btn:hover { background: var(--bc-subtle); color: var(--bc-fg); }
|
|
186
|
+
.icon-btn:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 1px; }
|
|
187
|
+
|
|
188
|
+
.body { padding: 16px 18px 0; }
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
label { display: block; font-size: 12px; font-weight: 600; color: var(--bc-fg); margin-bottom: 6px; }
|
|
192
|
+
input[type=text], input[type=email], textarea {
|
|
193
|
+
width: 100%; padding: 10px 12px; font-size: 14px; line-height: 1.45;
|
|
194
|
+
color: var(--bc-fg); background: var(--bc-elevated);
|
|
195
|
+
border: 1px solid var(--bc-border); border-radius: 10px;
|
|
196
|
+
transition: border-color 160ms, box-shadow 160ms;
|
|
197
|
+
}
|
|
198
|
+
input[type=text]::placeholder, input[type=email]::placeholder, textarea::placeholder { color: var(--bc-faint); }
|
|
199
|
+
input[type=text]:focus, input[type=email]:focus, textarea:focus {
|
|
200
|
+
outline: none; border-color: var(--bc-accent);
|
|
201
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bc-accent) 18%, transparent);
|
|
202
|
+
}
|
|
203
|
+
input.invalid { border-color: var(--bc-danger); animation: bc-nudge 240ms ease; }
|
|
204
|
+
textarea { min-height: 84px; resize: vertical; }
|
|
205
|
+
.field { margin-bottom: 14px; }
|
|
206
|
+
|
|
207
|
+
/* ---------- what is already known ----------
|
|
208
|
+
A help surface in front of the form, not a step in it. Everything here is
|
|
209
|
+
dismissible by ignoring it: the reporter can read what is already known and
|
|
210
|
+
leave, tap "this is happening to me too" and leave, or type straight past all
|
|
211
|
+
of it and file the report they came to file. Nothing is ever in their way,
|
|
212
|
+
because a reporter who feels handled rather than helped stops reporting. */
|
|
213
|
+
.known { margin-bottom: 14px; }
|
|
214
|
+
.known .known-h {
|
|
215
|
+
margin: 0 0 8px; font-size: 12px; font-weight: 650; letter-spacing: .02em;
|
|
216
|
+
text-transform: uppercase; color: var(--bc-faint);
|
|
217
|
+
}
|
|
218
|
+
.known .issue {
|
|
219
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
220
|
+
padding: 10px 12px; margin-bottom: 6px;
|
|
221
|
+
background: var(--bc-subtle); border: 1px solid transparent; border-radius: 10px;
|
|
222
|
+
}
|
|
223
|
+
.known .issue.is-joined { border-color: var(--bc-ok); }
|
|
224
|
+
.known .issue .who { flex: 1; min-width: 0; }
|
|
225
|
+
.known .issue .t {
|
|
226
|
+
display: block; font-size: 13px; font-weight: 620; line-height: 1.4;
|
|
227
|
+
color: var(--bc-fg); overflow-wrap: anywhere;
|
|
228
|
+
}
|
|
229
|
+
.known .issue .meta {
|
|
230
|
+
display: block; margin-top: 3px; font-size: 12px; line-height: 1.45; color: var(--bc-muted);
|
|
231
|
+
}
|
|
232
|
+
.known .issue .join {
|
|
233
|
+
flex: none; padding: 6px 10px; font-size: 12px; font-weight: 620;
|
|
234
|
+
color: var(--bc-accent); background: var(--bc-bg);
|
|
235
|
+
border: 1px solid var(--bc-border); border-radius: 8px; cursor: pointer;
|
|
236
|
+
}
|
|
237
|
+
.known .issue .join:hover { border-color: var(--bc-accent); }
|
|
238
|
+
.known .issue .join:disabled { color: var(--bc-ok); border-color: var(--bc-border); cursor: default; }
|
|
239
|
+
.known .issue .join:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 2px; }
|
|
240
|
+
.known .ask { display: flex; gap: 8px; margin-top: 8px; }
|
|
241
|
+
.known .ask input { flex: 1; min-width: 0; }
|
|
242
|
+
.known .ask button { flex: none; }
|
|
243
|
+
.known .err { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: var(--bc-danger); }
|
|
244
|
+
|
|
245
|
+
/* ---------- who is reporting ----------
|
|
246
|
+
A strip above the form rather than a screen in front of it: where signing in
|
|
247
|
+
is optional this is a line the reporter may ignore, and it should read as
|
|
248
|
+
one. Where it is required the composer below is not rendered at all and this
|
|
249
|
+
strip is the whole panel. */
|
|
250
|
+
.whoami {
|
|
251
|
+
display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
|
|
252
|
+
margin-bottom: 14px; padding: 10px 12px;
|
|
253
|
+
font-size: 12.5px; line-height: 1.45; color: var(--bc-muted);
|
|
254
|
+
background: var(--bc-subtle); border-radius: 10px;
|
|
255
|
+
}
|
|
256
|
+
.whoami.asking { display: block; }
|
|
257
|
+
.whoami .who { flex: 1; min-width: 0; }
|
|
258
|
+
.whoami .who strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }
|
|
259
|
+
.whoami .link {
|
|
260
|
+
padding: 0; font-size: 12.5px; font-weight: 600; line-height: 1.45;
|
|
261
|
+
color: var(--bc-accent); background: none; border: none; cursor: pointer;
|
|
262
|
+
}
|
|
263
|
+
.whoami .link:hover { text-decoration: underline; }
|
|
264
|
+
.whoami .link:disabled { color: var(--bc-faint); cursor: default; text-decoration: none; }
|
|
265
|
+
.whoami .link:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 2px; border-radius: 4px; }
|
|
266
|
+
.whoami .why { margin: 0 0 10px; font-size: 12.5px; line-height: 1.5; color: var(--bc-muted); }
|
|
267
|
+
.whoami .why strong { color: var(--bc-fg); font-weight: 620; word-break: break-all; }
|
|
268
|
+
.whoami .row { display: flex; gap: 8px; align-items: stretch; }
|
|
269
|
+
.whoami .row input { flex: 1; min-width: 0; }
|
|
270
|
+
.whoami .row button { flex: none; }
|
|
271
|
+
.whoami .after { display: flex; gap: 12px; align-items: center; margin-top: 9px; }
|
|
272
|
+
.whoami .err { color: var(--bc-danger); }
|
|
273
|
+
.whoami .code { letter-spacing: .28em; font-variant-numeric: tabular-nums; }
|
|
274
|
+
|
|
275
|
+
/* ---------- severity ---------- */
|
|
276
|
+
.seg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 3px; background: var(--bc-subtle); border-radius: 10px; }
|
|
277
|
+
.seg button {
|
|
278
|
+
padding: 7px 4px; font-size: 12.5px; font-weight: 550; line-height: 1;
|
|
279
|
+
color: var(--bc-muted); background: none; border: none; border-radius: 7px;
|
|
280
|
+
cursor: pointer; transition: background 180ms var(--bc-ease), color 180ms, box-shadow 180ms, font-weight 180ms;
|
|
281
|
+
}
|
|
282
|
+
.seg button:hover { color: var(--bc-fg); }
|
|
283
|
+
.seg button[aria-pressed=true] {
|
|
284
|
+
background: var(--bc-raised); color: var(--bc-accent); font-weight: 680;
|
|
285
|
+
box-shadow: 0 1px 3px rgba(10,12,16,.14);
|
|
286
|
+
}
|
|
287
|
+
.seg button:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: -1px; }
|
|
288
|
+
|
|
289
|
+
/* ---------- attachments ---------- */
|
|
290
|
+
.tools { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
291
|
+
.tool {
|
|
292
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
293
|
+
padding: 8px 12px; font-size: 12.5px; font-weight: 550; line-height: 1;
|
|
294
|
+
color: var(--bc-fg); background: var(--bc-elevated);
|
|
295
|
+
border: 1px solid var(--bc-border); border-radius: 9px; cursor: pointer;
|
|
296
|
+
transition: border-color 160ms, background 160ms, transform 160ms var(--bc-ease);
|
|
297
|
+
}
|
|
298
|
+
.tool svg { font-size: 15px; color: var(--bc-muted); transition: color 160ms; }
|
|
299
|
+
.tool:hover:not(:disabled) { border-color: var(--bc-accent); transform: translateY(-1px); }
|
|
300
|
+
.tool:hover:not(:disabled) svg { color: var(--bc-accent); }
|
|
301
|
+
.tool:active:not(:disabled) { transform: translateY(0) scale(.98); }
|
|
302
|
+
.tool:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 1px; }
|
|
303
|
+
.tool:disabled { opacity: .45; cursor: not-allowed; }
|
|
304
|
+
|
|
305
|
+
.thumbs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
|
306
|
+
.thumb {
|
|
307
|
+
position: relative; width: 74px; height: 74px; flex: none;
|
|
308
|
+
border-radius: 10px; overflow: hidden;
|
|
309
|
+
border: 1px solid var(--bc-border); background: var(--bc-subtle);
|
|
310
|
+
animation: bc-item-in 300ms var(--bc-ease) both;
|
|
311
|
+
}
|
|
312
|
+
.thumb img, .thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
313
|
+
.thumb .kind {
|
|
314
|
+
position: absolute; left: 4px; bottom: 4px;
|
|
315
|
+
display: inline-flex; align-items: center; gap: 3px;
|
|
316
|
+
padding: 2px 5px; border-radius: 5px;
|
|
317
|
+
background: rgba(10,12,16,.72); color: #fff; font-size: 9.5px; font-weight: 600; line-height: 1.3;
|
|
318
|
+
}
|
|
319
|
+
.thumb .drop {
|
|
320
|
+
position: absolute; top: 3px; right: 3px;
|
|
321
|
+
width: 19px; height: 19px; font-size: 11px; border-radius: 6px;
|
|
322
|
+
background: rgba(10,12,16,.68); color: #fff; border: none;
|
|
323
|
+
display: grid; place-items: center; cursor: pointer;
|
|
324
|
+
opacity: 0; transition: opacity 160ms;
|
|
325
|
+
}
|
|
326
|
+
.thumb:hover .drop, .thumb:focus-within .drop { opacity: 1; }
|
|
327
|
+
.thumb .drop:hover { background: var(--bc-danger); }
|
|
328
|
+
.thumb .veil {
|
|
329
|
+
position: absolute; inset: 0; display: grid; place-items: center; gap: 6px;
|
|
330
|
+
background: rgba(10,12,16,.62); color: #fff; font-size: 10.5px; font-weight: 600;
|
|
331
|
+
align-content: center;
|
|
332
|
+
}
|
|
333
|
+
.thumb .veil.err { background: color-mix(in srgb, var(--bc-danger) 82%, transparent); }
|
|
334
|
+
.spinner {
|
|
335
|
+
width: 17px; height: 17px; border-radius: 50%;
|
|
336
|
+
border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
|
|
337
|
+
animation: bc-spin 720ms linear infinite;
|
|
338
|
+
}
|
|
339
|
+
.bar { width: 46px; height: 3px; border-radius: 2px; background: rgba(255,255,255,.3); overflow: hidden; }
|
|
340
|
+
.bar i { display: block; height: 100%; width: 0; background: #fff; transition: width 180ms linear; }
|
|
341
|
+
.veil button {
|
|
342
|
+
background: none; border: none; color: #fff; font-size: 14px; padding: 2px;
|
|
343
|
+
cursor: pointer; display: grid; place-items: center;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* ---------- messages ---------- */
|
|
347
|
+
.msg {
|
|
348
|
+
display: flex; align-items: flex-start; gap: 7px;
|
|
349
|
+
font-size: 12.5px; line-height: 1.45; padding: 9px 11px; border-radius: 9px; margin-bottom: 12px;
|
|
350
|
+
animation: bc-item-in 240ms var(--bc-ease) both;
|
|
351
|
+
}
|
|
352
|
+
.msg.err { background: color-mix(in srgb, var(--bc-danger) 11%, transparent); color: var(--bc-danger); }
|
|
353
|
+
.msg.info { background: var(--bc-subtle); color: var(--bc-muted); }
|
|
354
|
+
|
|
355
|
+
/* ---------- footer ---------- */
|
|
356
|
+
.foot {
|
|
357
|
+
display: flex; align-items: center; gap: 9px;
|
|
358
|
+
margin-top: 16px; padding: 14px 18px; border-top: 1px solid var(--bc-border);
|
|
359
|
+
background: var(--bc-subtle);
|
|
360
|
+
}
|
|
361
|
+
.foot .spacer { flex: 1; }
|
|
362
|
+
.foot .note { font-size: 11.5px; color: var(--bc-faint); }
|
|
363
|
+
/* Quiet, but still legible: this sits in someone else's product, so it identifies
|
|
364
|
+
where the report goes without competing with their interface. Quiet is bought
|
|
365
|
+
with colour (--bc-brand-ink sits a step below body text) rather than with a
|
|
366
|
+
blanket opacity — the BUGCATCH half is only about 3px of cap height at this
|
|
367
|
+
size, and dimming it on top of that left it as a smudge in both themes. */
|
|
368
|
+
.brand-credit { display: flex; align-items: center; transition: color 0.15s; }
|
|
369
|
+
.bc-brand { height: 18px; width: auto; display: block; color: var(--bc-brand-ink); }
|
|
370
|
+
.brand-credit:hover .bc-brand { color: var(--bc-fg); }
|
|
371
|
+
.brand-credit:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 3px; border-radius: 4px; }
|
|
372
|
+
button.ghost, button.primary {
|
|
373
|
+
padding: 9px 15px; font-size: 13px; font-weight: 600; line-height: 1;
|
|
374
|
+
border-radius: 9px; cursor: pointer;
|
|
375
|
+
transition: transform 160ms var(--bc-ease), filter 160ms, background 160ms, border-color 160ms;
|
|
376
|
+
}
|
|
377
|
+
button.ghost { background: none; border: 1px solid var(--bc-border); color: var(--bc-muted); }
|
|
378
|
+
button.ghost:hover { color: var(--bc-fg); border-color: var(--bc-faint); }
|
|
379
|
+
button.primary {
|
|
380
|
+
display: inline-flex; align-items: center; gap: 7px;
|
|
381
|
+
background: var(--bc-accent); border: 1px solid var(--bc-accent); color: var(--bc-on-accent);
|
|
382
|
+
}
|
|
383
|
+
button.primary:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.07); }
|
|
384
|
+
button.primary:active:not(:disabled) { transform: none; }
|
|
385
|
+
button.ghost:focus-visible, button.primary:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: 2px; }
|
|
386
|
+
button:disabled { opacity: .5; cursor: not-allowed; }
|
|
387
|
+
button.primary .spinner { border-color: rgba(255,255,255,.4); border-top-color: #fff; width: 13px; height: 13px; }
|
|
388
|
+
|
|
389
|
+
/* ---------- success ---------- */
|
|
390
|
+
/* ---------- your reports ---------- */
|
|
391
|
+
.history { padding: 6px 20px 18px; max-height: 340px; overflow-y: auto; }
|
|
392
|
+
/* On the conversation screen the inner thread does the scrolling, so this
|
|
393
|
+
wrapper must not — two nested scrollers means the composer drifts out of
|
|
394
|
+
reach exactly when someone reaches for it. */
|
|
395
|
+
.history.chatting {
|
|
396
|
+
max-height: none; overflow: visible; display: flex; flex-direction: column;
|
|
397
|
+
}
|
|
398
|
+
.history .row {
|
|
399
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
400
|
+
padding: 11px 0; border-bottom: 1px solid var(--bc-border);
|
|
401
|
+
}
|
|
402
|
+
.history .row:last-child { border-bottom: 0; }
|
|
403
|
+
.history .who { flex: 1; min-width: 0; }
|
|
404
|
+
.history .ttl {
|
|
405
|
+
display: block; font-size: 13px; font-weight: 550; color: var(--bc-fg);
|
|
406
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
407
|
+
}
|
|
408
|
+
.history .sub { display: block; margin-top: 2px; font-size: 11.5px; color: var(--bc-faint); }
|
|
409
|
+
.history .empty { padding: 26px 0; text-align: center; font-size: 12.5px; color: var(--bc-muted); }
|
|
410
|
+
/* A history row is a button now — it opens the report — so the UA styling has
|
|
411
|
+
to go, and the hit area has to read as one. */
|
|
412
|
+
.history button.row {
|
|
413
|
+
width: 100%; background: none; border: 0; border-bottom: 1px solid var(--bc-border);
|
|
414
|
+
font: inherit; color: inherit; text-align: left; cursor: pointer;
|
|
415
|
+
border-radius: 8px; padding: 11px 8px; margin: 0 -8px; width: calc(100% + 16px);
|
|
416
|
+
}
|
|
417
|
+
.history button.row:hover { background: var(--bc-subtle); }
|
|
418
|
+
.history button.row:focus-visible { outline: 2px solid var(--bc-accent); outline-offset: -2px; }
|
|
419
|
+
|
|
420
|
+
/* ---------- one report, and what happened to it ---------- */
|
|
421
|
+
.report-head {
|
|
422
|
+
display: grid; grid-template-columns: 1fr auto; gap: 2px 10px;
|
|
423
|
+
padding: 4px 0 14px; border-bottom: 1px solid var(--bc-border);
|
|
424
|
+
}
|
|
425
|
+
.report-head .ttl { font-size: 13.5px; font-weight: 600; color: var(--bc-fg); }
|
|
426
|
+
.report-head .sub { font-size: 11.5px; color: var(--bc-faint); }
|
|
427
|
+
.report-head .pill { grid-row: 1 / span 2; align-self: center; }
|
|
428
|
+
|
|
429
|
+
/* The thread scrolls; the composer under it does not. Both live inside the
|
|
430
|
+
panel, which stops scrolling as a whole on this screen — a composer that
|
|
431
|
+
scrolls away is a composer people think isn't there. */
|
|
432
|
+
.thread {
|
|
433
|
+
display: flex; flex-direction: column; gap: 10px;
|
|
434
|
+
padding: 14px 0 8px; overflow-y: auto; overscroll-behavior: contain;
|
|
435
|
+
max-height: min(46vh, 330px);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* A status change is not a message and is not drawn as one: no side, no
|
|
439
|
+
bubble, no author. Centred and quiet, the way every chat marks the things
|
|
440
|
+
that happened rather than the things that were said. */
|
|
441
|
+
.sys {
|
|
442
|
+
align-self: center; max-width: 90%; text-align: center;
|
|
443
|
+
font-size: 11.5px; line-height: 1.45; color: var(--bc-faint);
|
|
444
|
+
}
|
|
445
|
+
.sys .dot {
|
|
446
|
+
display: inline-block; width: 6px; height: 6px; margin-right: 6px;
|
|
447
|
+
border-radius: 50%; background: var(--bc-muted); vertical-align: middle;
|
|
448
|
+
}
|
|
449
|
+
.sys.k-status .dot { background: #4da3ff; }
|
|
450
|
+
.sys.k-severity .dot { background: #e8912a; }
|
|
451
|
+
.sys.k-assignment .dot { background: #8b7dff; }
|
|
452
|
+
.sys.k-filed .dot { background: var(--bc-faint); }
|
|
453
|
+
.icon-btn.back { margin-right: 2px; }
|
|
454
|
+
|
|
455
|
+
/* ---------- the conversation ----------
|
|
456
|
+
|
|
457
|
+
A status change and a message are different things and are drawn as such: a
|
|
458
|
+
change is a line on the rail above, a message is a bubble with a side. The
|
|
459
|
+
reporter's own words sit right and accented, the team's left and neutral —
|
|
460
|
+
the arrangement every chat has taught people to read without a legend. */
|
|
461
|
+
.msgs { display: flex; flex-direction: column; gap: 10px; padding: 4px 0 6px; }
|
|
462
|
+
.msg-row { display: flex; flex-direction: column; max-width: 84%; gap: 4px; }
|
|
463
|
+
.msg-row.mine { align-self: flex-end; align-items: flex-end; }
|
|
464
|
+
.msg-row.theirs { align-self: flex-start; }
|
|
465
|
+
.msg-who { font-size: 11px; font-weight: 600; color: var(--bc-faint); padding: 0 2px; }
|
|
466
|
+
.bubble {
|
|
467
|
+
padding: 8px 11px; border-radius: 12px; font-size: 12.5px; line-height: 1.5;
|
|
468
|
+
background: var(--bc-subtle); color: var(--bc-fg);
|
|
469
|
+
border: 1px solid var(--bc-border);
|
|
470
|
+
}
|
|
471
|
+
/* pre-wrap belongs to the text, not the box — on the box it would also render
|
|
472
|
+
the markup's own indentation. */
|
|
473
|
+
.msg-text { display: block; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
474
|
+
.msg-row.mine .bubble {
|
|
475
|
+
background: var(--bc-accent); border-color: var(--bc-accent); color: var(--bc-on-accent);
|
|
476
|
+
border-bottom-right-radius: 4px;
|
|
477
|
+
}
|
|
478
|
+
.msg-row.theirs .bubble { border-bottom-left-radius: 4px; }
|
|
479
|
+
/* A reply nobody wrote. Outlined rather than recoloured — it is still the
|
|
480
|
+
team's side of the conversation — with the label above carrying the meaning,
|
|
481
|
+
so it reads the same to someone who cannot see the border at all. */
|
|
482
|
+
.msg-row.auto .bubble { border-style: dashed; }
|
|
483
|
+
.msg-who.auto { color: var(--bc-accent); }
|
|
484
|
+
/* An attachment-only message has no text to wrap, so the bubble would be an
|
|
485
|
+
empty coloured box around it. */
|
|
486
|
+
.bubble.silent { padding: 0; background: none; border: 0; }
|
|
487
|
+
.msg-when { font-size: 10.5px; color: var(--bc-faint); padding: 0 2px; }
|
|
488
|
+
.msg-row.pending { opacity: .55; }
|
|
489
|
+
.msg-row.failed .bubble { border: 1px solid var(--bc-danger); }
|
|
490
|
+
|
|
491
|
+
.att { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
|
|
492
|
+
.bubble.silent .att { margin-top: 0; }
|
|
493
|
+
.att img {
|
|
494
|
+
display: block; max-width: 100%; max-height: 190px; border-radius: 9px;
|
|
495
|
+
border: 1px solid var(--bc-border); object-fit: cover; cursor: zoom-in;
|
|
496
|
+
}
|
|
497
|
+
.att audio { width: 232px; max-width: 100%; height: 34px; }
|
|
498
|
+
.att video { max-width: 100%; max-height: 190px; border-radius: 9px; }
|
|
499
|
+
.att .file {
|
|
500
|
+
display: flex; align-items: center; gap: 8px; text-decoration: none;
|
|
501
|
+
padding: 8px 10px; border-radius: 9px; font-size: 12px;
|
|
502
|
+
background: var(--bc-elevated); border: 1px solid var(--bc-border); color: var(--bc-fg);
|
|
503
|
+
}
|
|
504
|
+
.att .file:hover { border-color: var(--bc-accent); }
|
|
505
|
+
.att .file .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
506
|
+
.att .file .sz { flex: none; color: var(--bc-faint); font-size: 11px; }
|
|
507
|
+
|
|
508
|
+
/* ---------- the composer ---------- */
|
|
509
|
+
.composer {
|
|
510
|
+
position: sticky; bottom: 0; margin-top: auto;
|
|
511
|
+
padding: 10px 0 2px;
|
|
512
|
+
/* The panel's own background, not --bc-elevated: the two are the same white
|
|
513
|
+
in light mode but differ in dark, where a raised composer reads as a band
|
|
514
|
+
laid over the panel rather than the foot of it. */
|
|
515
|
+
background: var(--bc-bg);
|
|
516
|
+
border-top: 1px solid var(--bc-border);
|
|
517
|
+
}
|
|
518
|
+
.composer .row { display: flex; align-items: flex-end; gap: 8px; }
|
|
519
|
+
.composer textarea {
|
|
520
|
+
flex: 1; min-height: 38px; max-height: 120px; resize: none; padding: 9px 11px;
|
|
521
|
+
font: inherit; font-size: 12.5px; line-height: 1.45;
|
|
522
|
+
color: var(--bc-fg); background: var(--bc-subtle);
|
|
523
|
+
border: 1px solid var(--bc-border); border-radius: 10px;
|
|
524
|
+
}
|
|
525
|
+
.composer textarea:focus { outline: none; border-color: var(--bc-accent); }
|
|
526
|
+
.composer .tools { display: flex; gap: 4px; }
|
|
527
|
+
.composer .tool {
|
|
528
|
+
width: 34px; height: 34px; display: grid; place-items: center; cursor: pointer;
|
|
529
|
+
border-radius: 9px; border: 1px solid transparent; background: none;
|
|
530
|
+
color: var(--bc-muted);
|
|
531
|
+
}
|
|
532
|
+
.composer .tool:hover { background: var(--bc-subtle); color: var(--bc-fg); }
|
|
533
|
+
.composer .tool:disabled { opacity: .4; cursor: default; }
|
|
534
|
+
.composer .tool.recording { color: var(--bc-danger); background: color-mix(in srgb, var(--bc-danger) 12%, transparent); }
|
|
535
|
+
.composer .send {
|
|
536
|
+
width: 34px; height: 34px; flex: none; border-radius: 9px; cursor: pointer;
|
|
537
|
+
background: var(--bc-accent); border: 1px solid var(--bc-accent); color: #fff;
|
|
538
|
+
display: grid; place-items: center;
|
|
539
|
+
}
|
|
540
|
+
.composer .send:disabled { opacity: .4; cursor: default; }
|
|
541
|
+
.composer .staged { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 0 8px; }
|
|
542
|
+
.composer .chip {
|
|
543
|
+
display: flex; align-items: center; gap: 6px; max-width: 190px;
|
|
544
|
+
padding: 5px 7px; border-radius: 8px; font-size: 11.5px;
|
|
545
|
+
background: var(--bc-subtle); border: 1px solid var(--bc-border); color: var(--bc-muted);
|
|
546
|
+
}
|
|
547
|
+
.composer .chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
548
|
+
.composer .chip button {
|
|
549
|
+
flex: none; width: 16px; height: 16px; display: grid; place-items: center;
|
|
550
|
+
border: 0; border-radius: 4px; background: none; color: var(--bc-faint); cursor: pointer;
|
|
551
|
+
}
|
|
552
|
+
.composer .chip button:hover { color: var(--bc-danger); }
|
|
553
|
+
.composer .chip.uploading { border-style: dashed; }
|
|
554
|
+
.composer .note { padding: 6px 2px 0; font-size: 11.5px; color: var(--bc-faint); }
|
|
555
|
+
.composer .note.err { color: var(--bc-danger); }
|
|
556
|
+
.reccount { font-variant-numeric: tabular-nums; }
|
|
557
|
+
|
|
558
|
+
/* Status carries the only colour in this list, so it is the thing the eye
|
|
559
|
+
lands on — which is what someone opening this screen came to check. */
|
|
560
|
+
.pill {
|
|
561
|
+
flex: none; font-size: 10.5px; font-weight: 650; letter-spacing: .03em;
|
|
562
|
+
padding: 3px 8px; border-radius: 999px; text-transform: uppercase;
|
|
563
|
+
border: 1px solid currentColor;
|
|
564
|
+
}
|
|
565
|
+
.pill.open { color: #e8912a; }
|
|
566
|
+
.pill.in_progress { color: #4da3ff; }
|
|
567
|
+
.pill.resolved { color: #35b87f; }
|
|
568
|
+
.pill.closed { color: var(--bc-faint); }
|
|
569
|
+
|
|
570
|
+
/* A call in progress, in place of the form.
|
|
571
|
+
---------------------------------------------------------------------------
|
|
572
|
+
Deliberately not styled as a chat. What is on screen is a transcript of
|
|
573
|
+
something being said out loud, and bubbles would invite the reporter to type
|
|
574
|
+
into it — which is the one thing this surface cannot accept. */
|
|
575
|
+
.call { padding: 20px 22px 14px; animation: bc-fade-in 240ms ease both; }
|
|
576
|
+
.call-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
|
577
|
+
.call-head strong { font-size: 14.5px; font-weight: 650; }
|
|
578
|
+
.call-dot {
|
|
579
|
+
width: 8px; height: 8px; border-radius: 50%; background: var(--bc-accent);
|
|
580
|
+
animation: bc-call-pulse 1.6s ease-in-out infinite;
|
|
581
|
+
}
|
|
582
|
+
.call-dot.off { background: var(--bc-faint); animation: none; }
|
|
583
|
+
@keyframes bc-call-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
|
|
584
|
+
/* Said before the first word is, because a voice answering is exactly when
|
|
585
|
+
somebody assumes they have reached a person. */
|
|
586
|
+
.call-note { margin: 0 0 12px; font-size: 12px; color: var(--bc-muted); line-height: 1.5; }
|
|
587
|
+
|
|
588
|
+
/* ---------- a call to a person, on top of the report it is about ---------- */
|
|
589
|
+
/* A bar rather than its own screen, unlike the agent call. There is nothing to
|
|
590
|
+
read during this one — no transcript arrives, because nobody is transcribing
|
|
591
|
+
it — and the thread underneath is exactly what both people are talking about,
|
|
592
|
+
so taking it off screen would be taking away the only shared reference. */
|
|
593
|
+
.livecall {
|
|
594
|
+
display: flex; align-items: center; gap: 9px;
|
|
595
|
+
margin: 12px 0 0; padding: 9px 11px;
|
|
596
|
+
border: 1px solid var(--bc-border); border-radius: 10px;
|
|
597
|
+
background: var(--bc-subtle); font-size: 12.5px; color: var(--bc-fg);
|
|
598
|
+
}
|
|
599
|
+
.livecall .who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
600
|
+
.livecall .secs { font-variant-numeric: tabular-nums; color: var(--bc-muted); }
|
|
601
|
+
.livecall .act {
|
|
602
|
+
padding: 5px 10px; border-radius: 999px; border: 1px solid var(--bc-border);
|
|
603
|
+
background: var(--bc-elevated); color: var(--bc-fg);
|
|
604
|
+
font: inherit; font-size: 12px; cursor: pointer;
|
|
605
|
+
}
|
|
606
|
+
.livecall .act:hover { background: var(--bc-subtle); }
|
|
607
|
+
.livecall .act.danger { border-color: transparent; background: var(--bc-danger); color: #fff; }
|
|
608
|
+
.livecall.over { color: var(--bc-muted); }
|
|
609
|
+
.call-note code { font-size: 11px; }
|
|
610
|
+
.call-lines {
|
|
611
|
+
max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 7px;
|
|
612
|
+
padding: 10px 12px; border: 1px solid var(--bc-line); border-radius: 8px;
|
|
613
|
+
background: var(--bc-surface-2, transparent);
|
|
614
|
+
}
|
|
615
|
+
.call-line { font-size: 13px; line-height: 1.55; overflow-wrap: anywhere; }
|
|
616
|
+
.call-line.agent { color: var(--bc-muted); }
|
|
617
|
+
.call-line.you { color: var(--bc-ink); font-weight: 500; }
|
|
618
|
+
.call-line.you::before { content: 'You: '; color: var(--bc-faint); font-weight: 400; }
|
|
619
|
+
@media (prefers-reduced-motion: reduce) { .call-dot { animation: none; } }
|
|
620
|
+
|
|
621
|
+
.done { padding: 34px 24px 38px; text-align: center; animation: bc-fade-in 240ms ease both; }
|
|
622
|
+
.done .tick {
|
|
623
|
+
width: 46px; height: 46px; margin: 0 auto 14px; border-radius: 50%;
|
|
624
|
+
display: grid; place-items: center; font-size: 24px;
|
|
625
|
+
background: color-mix(in srgb, var(--bc-ok) 14%, transparent); color: var(--bc-ok);
|
|
626
|
+
animation: bc-pop 420ms var(--bc-ease) both;
|
|
627
|
+
}
|
|
628
|
+
.done h3 { margin: 0 0 5px; font-size: 15.5px; font-weight: 650; }
|
|
629
|
+
.done p { margin: 0; font-size: 12.5px; color: var(--bc-muted); }
|
|
630
|
+
.done code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
631
|
+
/* The likely wait. Set apart from the reference line above it by a rule rather
|
|
632
|
+
than by weight: one is bookkeeping and the other is the answer to the
|
|
633
|
+
question the reporter actually has, and they should not read as one
|
|
634
|
+
paragraph. */
|
|
635
|
+
.done .eta {
|
|
636
|
+
margin: 12px auto 0;
|
|
637
|
+
padding-top: 11px;
|
|
638
|
+
border-top: 1px solid var(--bc-line);
|
|
639
|
+
max-width: 30ch;
|
|
640
|
+
line-height: 1.5;
|
|
641
|
+
}
|
|
642
|
+
.done .eta strong { color: var(--bc-ink); font-weight: 600; }
|
|
643
|
+
/* The same figure on the report the reporter came back to. Sits under the
|
|
644
|
+
status pill in the header block, muted — it is context, not the headline the
|
|
645
|
+
status is. */
|
|
646
|
+
.report-head .eta-line {
|
|
647
|
+
grid-column: 1 / -1;
|
|
648
|
+
font-size: 11.5px;
|
|
649
|
+
color: var(--bc-muted);
|
|
650
|
+
margin-top: 2px;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/* ---------- recording bar ---------- */
|
|
654
|
+
.recbar {
|
|
655
|
+
position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
|
|
656
|
+
z-index: 2147483002;
|
|
657
|
+
display: flex; align-items: center; gap: 12px;
|
|
658
|
+
padding: 10px 12px 10px 15px; border-radius: 999px;
|
|
659
|
+
background: var(--bc-elevated); color: var(--bc-fg);
|
|
660
|
+
border: 1px solid var(--bc-border); box-shadow: var(--bc-shadow);
|
|
661
|
+
animation: bc-item-in 300ms var(--bc-ease) both;
|
|
662
|
+
}
|
|
663
|
+
.recbar .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--bc-danger); animation: bc-pulse 1.4s ease-in-out infinite; }
|
|
664
|
+
.recbar .time { font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600; min-width: 38px; }
|
|
665
|
+
.recbar .hint { font-size: 12px; color: var(--bc-muted); }
|
|
666
|
+
.recbar button { padding: 7px 13px; font-size: 12.5px; font-weight: 600; border-radius: 999px; cursor: pointer; }
|
|
667
|
+
.recbar .stop { background: var(--bc-danger); border: 1px solid var(--bc-danger); color: #fff; }
|
|
668
|
+
.recbar .stop:hover { filter: brightness(1.08); }
|
|
669
|
+
|
|
670
|
+
@media (max-width: 520px) {
|
|
671
|
+
.panel { width: calc(100vw - 24px); max-width: none; }
|
|
672
|
+
.scrim.at-right .panel, .scrim.at-left .panel { left: 12px; right: 12px; }
|
|
673
|
+
.recbar .hint { display: none; }
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* ---------- phones ----------------------------------------------------------
|
|
677
|
+
This widget runs inside a shadow root on somebody else's site, so its own
|
|
678
|
+
stylesheet is the whole story: the host page cannot correct any of this, and
|
|
679
|
+
anything that goes wrong here goes wrong on their site, in front of their
|
|
680
|
+
visitor, at the moment that visitor is already annoyed enough to file a bug.
|
|
681
|
+
-------------------------------------------------------------------------- */
|
|
682
|
+
|
|
683
|
+
/* 100vh on a phone is the viewport with the browser chrome hidden, so the
|
|
684
|
+
panel could be taller than the space it actually has and put "Send report"
|
|
685
|
+
under the address bar. */
|
|
686
|
+
@supports (height: 100dvh) {
|
|
687
|
+
.panel { max-height: calc(100dvh - var(--bc-dock-h, 84px) - 52px); }
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/* The dock sits in a corner of a page the widget does not own. On a notched
|
|
691
|
+
phone that corner is under the home indicator, and in landscape it is under
|
|
692
|
+
the camera housing. env() is 0 everywhere else, so the offsets are unchanged
|
|
693
|
+
on hardware that has no insets. */
|
|
694
|
+
.dock {
|
|
695
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
696
|
+
padding-right: env(safe-area-inset-right, 0px);
|
|
697
|
+
padding-left: env(safe-area-inset-left, 0px);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
@media (pointer: coarse) {
|
|
701
|
+
/* iOS zooms the whole page when a field it focuses is under 16px, and does
|
|
702
|
+
not zoom back out. On the host's site that is not the widget's page to
|
|
703
|
+
zoom — the visitor is left on a magnified checkout wondering what they
|
|
704
|
+
touched. !important because this is a property of the device, not a
|
|
705
|
+
preference about type. */
|
|
706
|
+
input, textarea, select { font-size: 16px !important; }
|
|
707
|
+
|
|
708
|
+
/* 44px is the floor a thumb hits reliably. Every control in this form was
|
|
709
|
+
under it, on the one surface in the product designed for someone who is
|
|
710
|
+
not an operator and gets no second attempt. */
|
|
711
|
+
.launcher { min-height: 48px; padding: 14px 20px; }
|
|
712
|
+
.seg button { min-height: 44px; padding: 12px 8px; }
|
|
713
|
+
.tool { min-height: 44px; padding: 12px 14px; }
|
|
714
|
+
button.primary, .foot button { min-height: 44px; padding: 12px 18px; }
|
|
715
|
+
textarea { min-height: 104px; }
|
|
716
|
+
|
|
717
|
+
/* The theme switch and the close button are 28px glyphs whose boxes cannot
|
|
718
|
+
grow without moving the header text beside them, so each keeps its size
|
|
719
|
+
and gets an invisible target drawn around it instead. */
|
|
720
|
+
.themer button, .head .icon-btn, .thumb .x, .brand-credit { position: relative; }
|
|
721
|
+
.themer button::after, .head .icon-btn::after, .thumb .x::after, .brand-credit::after {
|
|
722
|
+
content: '';
|
|
723
|
+
position: absolute;
|
|
724
|
+
inset: -9px;
|
|
725
|
+
}
|
|
726
|
+
/* The close button is the one people reach for in a hurry, and it sits in
|
|
727
|
+
the corner where a thumb is least accurate. */
|
|
728
|
+
.head .icon-btn::after { inset: -12px; }
|
|
729
|
+
/* The themer's three buttons sit side by side, so their expanders would
|
|
730
|
+
overlap each other without a gap to grow into. */
|
|
731
|
+
.themer { gap: 4px; }
|
|
732
|
+
|
|
733
|
+
/* Thumb-sized controls make the form taller, which on a short phone pushed
|
|
734
|
+
"Send report" below the fold of a panel that scrolls — so the one button
|
|
735
|
+
the whole widget exists for was reachable only by scrolling past the
|
|
736
|
+
attachment row. It stays on screen instead. */
|
|
737
|
+
.foot {
|
|
738
|
+
position: sticky;
|
|
739
|
+
bottom: 0;
|
|
740
|
+
margin-top: 16px;
|
|
741
|
+
/* The panel scrolls under this, so the bar cannot be translucent. */
|
|
742
|
+
background: var(--bc-subtle);
|
|
743
|
+
padding-bottom: max(14px, env(safe-area-inset-bottom, 0px));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* Hover is a lie on a touchscreen: it latches on tap and stays. The launcher's
|
|
748
|
+
lift in particular was left applied after the panel closed. */
|
|
749
|
+
@media (hover: none) {
|
|
750
|
+
.launcher:hover { transform: none; filter: none; box-shadow: 0 6px 18px rgba(10,12,16,.22), 0 1px 3px rgba(10,12,16,.16); }
|
|
751
|
+
.tool:hover, .themer button:hover { border-color: var(--bc-border); }
|
|
752
|
+
}
|
|
753
|
+
`;function vo(t){const e=document.createElement("div");e.setAttribute("data-bugcatch","widget");let n=bo()||xo(t.config.theme);e.className=Ut(n);const o=e.attachShadow({mode:"open"}),a=document.createElement("style");a.textContent=yo(t.config.accent||"#4F46E5"),o.appendChild(a);const r=Ot[t.config.widgetPosition]?t.config.widgetPosition:"bottom-right",s=document.createElement("div");s.className=`dock ${r.includes("right")?"at-right":"at-left"}`+(r.startsWith("top")?" at-top":""),s.style.cssText=Ot[r];const l=document.createElement("div");l.className="themer",l.setAttribute("role","group"),l.setAttribute("aria-label","Report widget theme"),l.innerHTML=Ht.map(b=>`<button type="button" data-theme="${b.value}" title="${b.label}" aria-label="${b.label}" aria-pressed="${b.value===n}">${b.icon}</button>`).join(""),l.addEventListener("click",b=>{const d=b.target.closest("button[data-theme]");!d||d.dataset.theme===n||(n=d.dataset.theme,e.className=Ut(n),wo(n),l.querySelectorAll("button").forEach(w=>{w.setAttribute("aria-pressed",String(w.dataset.theme===n))}))});const u=document.createElement("button");u.className="launcher",u.type="button",u.innerHTML=`${z.bug}<span>Report a bug</span>`,u.setAttribute("aria-haspopup","dialog"),u.setAttribute("aria-expanded","false");let g=null;return u.addEventListener("click",()=>{if(g){g.close();return}s.classList.add("over"),u.setAttribute("aria-expanded","true"),g=ko({root:o,host:e,dock:s,client:t,onClose:()=>{g=null,s.classList.remove("over"),u.setAttribute("aria-expanded","false")}})}),s.append(l,u),o.appendChild(s),document.body.appendChild(e),()=>{g==null||g.destroy(),e.remove()}}function xo(t){return t==="dark"||t==="light"?t:"auto"}function Ut(t){return t==="dark"?"bc-dark":t==="light"?"bc-light":"bc-auto"}function ko({root:t,host:e,dock:n,client:o,onClose:a}){var cn,ln,dn,un,pn;const r=o.config,s=r.attachments!==!1,l=s&&jn(),u=s&&Rn(),g=[];let b="MEDIUM",d=null,w=!1;const h=document.createElement("div");h.className=`scrim ${n.classList.contains("at-left")?"at-left":"at-right"} ${n.classList.contains("at-top")?"at-top":"at-bottom"}`,h.style.setProperty("--bc-dock-h",`${Math.round(n.getBoundingClientRect().height)}px`),h.innerHTML=`
|
|
754
|
+
<div class="panel" role="dialog" aria-modal="true" aria-labelledby="bc-h">
|
|
755
|
+
<div class="head">
|
|
756
|
+
<div class="mark">${z.bug}</div>
|
|
757
|
+
<div class="titles">
|
|
758
|
+
<h2 id="bc-h">Report a bug</h2>
|
|
759
|
+
<p>We'll attach what the page was doing, so you don't have to explain it.</p>
|
|
760
|
+
</div>
|
|
761
|
+
<button class="icon-btn" type="button" data-act="close" aria-label="Close">${z.close}</button>
|
|
762
|
+
</div>
|
|
763
|
+
<div class="body">
|
|
764
|
+
<div class="slot"></div>
|
|
765
|
+
<div class="whoami" hidden></div>
|
|
766
|
+
<div class="known" hidden></div>
|
|
767
|
+
<div class="compose">
|
|
768
|
+
<div class="field">
|
|
769
|
+
<label for="bc-title">What went wrong?</label>
|
|
770
|
+
<input id="bc-title" type="text" placeholder="Checkout button does nothing" autocomplete="off" />
|
|
771
|
+
</div>
|
|
772
|
+
<div class="field">
|
|
773
|
+
<label for="bc-desc">Any detail that helps <span style="font-weight:450;color:var(--bc-faint)">(optional)</span></label>
|
|
774
|
+
<textarea id="bc-desc" placeholder="I clicked Pay and nothing happened."></textarea>
|
|
775
|
+
</div>
|
|
776
|
+
<div class="field">
|
|
777
|
+
<label id="bc-prio-l">Severity</label>
|
|
778
|
+
<div class="seg" role="group" aria-labelledby="bc-prio-l">
|
|
779
|
+
${go.map(i=>`
|
|
780
|
+
<button type="button" data-prio="${i.value}" aria-pressed="${i.value===b}">${i.label}</button>
|
|
781
|
+
`).join("")}
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
${s?`
|
|
785
|
+
<div class="field">
|
|
786
|
+
<label>Attach <span style="font-weight:450;color:var(--bc-faint)">(optional)</span></label>
|
|
787
|
+
<div class="tools">
|
|
788
|
+
${l?`<button class="tool" type="button" data-act="shot">${z.camera}Screenshot</button>`:""}
|
|
789
|
+
${u?`<button class="tool" type="button" data-act="rec">${z.video}Record</button>`:""}
|
|
790
|
+
<button class="tool" type="button" data-act="file">${z.paperclip}File</button>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="thumbs"></div>
|
|
793
|
+
<input type="file" accept="${ze.join(",")}" multiple hidden />
|
|
794
|
+
</div>`:""}
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
<div class="foot">
|
|
798
|
+
<a class="brand-credit" href="https://bugcatch.sphoro.com" target="_blank" rel="noopener noreferrer" aria-label="BugCatch">${mo}</a>
|
|
799
|
+
<span class="note"></span>
|
|
800
|
+
<span class="spacer"></span>
|
|
801
|
+
${o.hasReports()?'<button class="ghost" type="button" data-act="history">Your reports</button>':""}
|
|
802
|
+
<button class="ghost" type="button" data-act="cancel">Cancel</button>
|
|
803
|
+
<!-- Hidden until the API confirms this project can take a call, which
|
|
804
|
+
most cannot. Drawn here rather than as a second launcher because it
|
|
805
|
+
is the same errand as Send report — the difference is only whether
|
|
806
|
+
the reporter would rather say it than type it. -->
|
|
807
|
+
<button class="ghost" type="button" data-act="call" hidden>Talk to us</button>
|
|
808
|
+
<button class="primary" type="button" data-act="send">Send report</button>
|
|
809
|
+
</div>
|
|
810
|
+
</div>`;const S=i=>h.querySelector(i),O=S("#bc-title"),Y=S(".slot"),k=S(".thumbs"),y=h.querySelector("input[type=file]"),x=S('[data-act="send"]'),C=S('[data-act="call"]'),R=S(".note"),Z=S(".compose"),H=S(".whoami"),_=S(".known"),B=S('[data-act="history"]'),J=document.activeElement,ye=document.documentElement.style,ht=ye.overflow;ye.overflow="hidden";let Ce=!1,Ee=null;function _e(){var i;Ce||(Ce=!0,d==null||d.cancel(),Ee==null||Ee(),g.forEach(c=>c.previewUrl&&URL.revokeObjectURL(c.previewUrl)),ye.overflow=ht,document.removeEventListener("keydown",se,!0),h.remove(),(i=t.querySelector(".recbar"))==null||i.remove(),n.hidden=!1,e.style.opacity="",a==null||a())}function q(){var i;if(!Ce){h.classList.add("closing"),setTimeout(_e,160);try{(i=J==null?void 0:J.focus)==null||i.call(J)}catch{}}}function se(i){if(i.key==="Escape"){if(i.stopPropagation(),d){ve({keep:!1});return}w||q();return}if(i.key!=="Tab"||d)return;const c=[...h.querySelectorAll("button:not(:disabled), input:not([hidden]), textarea, select, [href]")].filter(E=>E.offsetParent!==null||E===document.activeElement);if(!c.length)return;const p=c[0],m=c[c.length-1],M=h.getRootNode().activeElement;i.shiftKey&&M===p?(i.preventDefault(),m.focus()):!i.shiftKey&&M===m&&(i.preventDefault(),p.focus())}document.addEventListener("keydown",se,!0),h.addEventListener("click",i=>{i.target===h&&!w&&q()}),S('[data-act="close"]').addEventListener("click",q),S('[data-act="cancel"]').addEventListener("click",q),h.querySelectorAll("[data-prio]").forEach(i=>{i.addEventListener("click",()=>{b=i.dataset.prio,h.querySelectorAll("[data-prio]").forEach(c=>{c.setAttribute("aria-pressed",String(c===i))})})});function P(i,c="err"){Y.innerHTML=i?`<div class="msg ${c}">${T(i)}</div>`:""}function ie(){const i=g.some(c=>c.status==="uploading");x.disabled=w||i,R.textContent=i?"Waiting for the upload to finish…":"",h.querySelectorAll(".tool").forEach(c=>{c.disabled=w||g.length>=(r.maxAttachments??5)})}function Ae(i){var E;const c=i.el,p=i.kind==="VIDEO"?"Video":"Shot",m=i.kind==="VIDEO"?`<video src="${i.previewUrl}" muted playsinline preload="metadata"></video>`:`<img src="${i.previewUrl}" alt="" />`;let M="";i.status==="uploading"?M=`<div class="veil"><div class="spinner"></div><div class="bar"><i style="width:${Math.round(i.progress*100)}%"></i></div></div>`:i.status==="failed"&&(M=`<div class="veil err"><button type="button" data-act="retry" aria-label="Retry upload">${z.retry}</button>Failed</div>`),c.innerHTML=`
|
|
811
|
+
${m}
|
|
812
|
+
<span class="kind">${p} · ${Se(i.size)}</span>
|
|
813
|
+
<button class="drop" type="button" data-act="drop" aria-label="Remove ${p.toLowerCase()}">${z.close}</button>
|
|
814
|
+
${M}`,c.querySelector('[data-act="drop"]').addEventListener("click",()=>We(i)),(E=c.querySelector('[data-act="retry"]'))==null||E.addEventListener("click",()=>Qe(i))}function We(i){const c=g.indexOf(i);c>=0&&g.splice(c,1),i.previewUrl&&URL.revokeObjectURL(i.previewUrl),i.el.remove(),ie()}async function Qe(i){i.status="uploading",i.progress=0,Ae(i),ie();try{const{storageKey:c,mime:p}=await o.uploadAttachment(i.blob,i.mime,m=>{i.progress=m;const M=i.el.querySelector(".bar i");M&&(M.style.width=`${Math.round(m*100)}%`)});i.storageKey=c,i.mime=p,i.status="ready"}catch(c){i.status="failed",P(c.message.replace("[BugCatch] ",""))}Ae(i),ie()}function Re({blob:i,mime:c,kind:p}){const m=r.maxAttachmentBytes??26214400;if(i.size>m){P(`That file is ${Se(i.size)} — the limit is ${Se(m)}.`);return}const M=Oe(c||i.type,ze);if(!M){P("Only PNG, JPEG, WebP, WebM and MP4 files can be attached to a report.");return}const E={blob:i,kind:p,mime:M,size:i.size,previewUrl:URL.createObjectURL(i),status:"uploading",progress:0,storageKey:null,el:document.createElement("div")};E.el.className="thumb",g.push(E),k.appendChild(E.el),P(""),Qe(E)}async function ft(i){e.style.opacity="0",e.style.pointerEvents="none",await new Promise(c=>requestAnimationFrame(()=>requestAnimationFrame(c)));try{return await i()}finally{e.style.opacity="",e.style.pointerEvents=""}}(cn=h.querySelector('[data-act="shot"]'))==null||cn.addEventListener("click",async()=>{try{const i=await ft(zn);Re(i)}catch(i){(i==null?void 0:i.name)!=="NotAllowedError"&&P(`Screenshot failed: ${i.message}`)}}),(ln=h.querySelector('[data-act="file"]'))==null||ln.addEventListener("click",()=>y.click()),y==null||y.addEventListener("change",()=>{const i=(r.maxAttachments??5)-g.length;[...y.files].slice(0,Math.max(i,0)).forEach(c=>{Re({blob:c,mime:c.type,kind:c.type.startsWith("video/")?"VIDEO":"SCREENSHOT"})}),y.value=""}),(dn=h.querySelector('[data-act="rec"]'))==null||dn.addEventListener("click",async()=>{const i=r.maxRecordingMs??12e4;let c;try{d=await On({maxMs:i,onTick:p=>{c&&(c.querySelector(".time").textContent=He(p))},onAutoStop:()=>{d&&ve({keep:!0})}})}catch(p){(p==null?void 0:p.name)!=="NotAllowedError"&&P(`Recording failed: ${p.message}`);return}h.style.display="none",n.hidden=!0,c=document.createElement("div"),c.className="recbar",c.innerHTML=`
|
|
815
|
+
<span class="dot"></span>
|
|
816
|
+
<span class="time">0:00</span>
|
|
817
|
+
<span class="hint">Recording — up to ${He(Math.round(i/1e3))}</span>
|
|
818
|
+
<button class="stop" type="button">Stop</button>
|
|
819
|
+
<button class="ghost" type="button">Discard</button>`,c.querySelector(".stop").addEventListener("click",()=>ve({keep:!0})),c.querySelector(".ghost").addEventListener("click",()=>ve({keep:!1})),t.appendChild(c),c.querySelector(".stop").focus()});async function ve({keep:i}){var p;const c=d;if(c){if(d=null,(p=t.querySelector(".recbar"))==null||p.remove(),h.style.display="",n.hidden=!1,!i){c.cancel(),O.focus();return}try{const m=await c.stop();m.blob.size?Re(m):P("That recording came back empty — nothing was captured.")}catch(m){P(`Recording failed: ${m.message}`)}O.focus()}}x.addEventListener("click",async()=>{const i=O.value;if(!i.trim()){P("Please describe what went wrong."),O.classList.add("invalid"),O.focus(),setTimeout(()=>O.classList.remove("invalid"),500);return}w=!0,P(""),ie();const c=x.innerHTML;x.innerHTML='<span class="spinner"></span>Sending…';try{const p=await o.report({title:i,description:S("#bc-desc").value,priority:b,attachments:g.filter(m=>m.status==="ready").map(m=>({type:m.kind,storageKey:m.storageKey,mime:m.mime}))});rn(p)}catch(p){w=!1,x.innerHTML=c,P(p.message.replace("[BugCatch] ","")),ie(),f.mode!=="off"&&bt().catch(()=>{})}}),(un=h.querySelector('[data-act="history"]'))==null||un.addEventListener("click",sn);let ce=[],xe="",ke=null,he=null;function Fe(){r.knownIssues!==!1&&o.knownIssues().then(i=>{ce=i,fe()}).catch(()=>{})}let Ye=null;function gt(i){if(r.knownIssues===!1)return;clearTimeout(Ye),he==null||he.abort();const c=(i||"").trim();if(c.length<3){Fe();return}Ye=setTimeout(()=>{he=new AbortController,o.matchKnownIssues(c,{signal:he.signal}).then(p=>{p.length&&(ce=p,fe())}).catch(()=>{})},250)}function fe(){if(!ce.length){_.hidden=!0,_.innerHTML="";return}_.hidden=!1,_.innerHTML=`
|
|
820
|
+
<p class="known-h">Already reported</p>
|
|
821
|
+
${ce.map(i=>`
|
|
822
|
+
<div class="issue ${i.joined?"is-joined":""}">
|
|
823
|
+
<span class="who">
|
|
824
|
+
<span class="t">${T(i.title)}</span>
|
|
825
|
+
<span class="meta">${T(Ze(i))}</span>
|
|
826
|
+
</span>
|
|
827
|
+
<button class="join" type="button" data-join="${T(i.id)}"
|
|
828
|
+
${i.joined?"disabled":""}>${i.joined?"Following":"Me too"}</button>
|
|
829
|
+
</div>
|
|
830
|
+
${ke===i.id?`
|
|
831
|
+
<div class="ask">
|
|
832
|
+
<input type="email" class="join-email" placeholder="you@example.com"
|
|
833
|
+
aria-label="Your email, so we can tell you when this is fixed" />
|
|
834
|
+
<button class="ghost" type="button" data-join-confirm="${T(i.id)}">Follow</button>
|
|
835
|
+
</div>`:""}
|
|
836
|
+
`).join("")}
|
|
837
|
+
${xe?`<p class="err">${T(xe)}</p>`:""}`}function Ze(i){const c=i.status==="IN_PROGRESS"?"Being worked on now":"Not fixed yet",p=i.affectedCount===0?"":` · ${i.affectedCount} ${i.affectedCount===1?"person has":"people have"} said this is happening to them`;return`${c} · reported ${De(i.since)}${p}`}_.addEventListener("click",async i=>{var E,le,K,ne,oe;const c=i.target.closest("[data-join]"),p=i.target.closest("[data-join-confirm]");if(!c&&!p)return;const m=(p||c).getAttribute(p?"data-join-confirm":"data-join");xe="";const M=((le=(E=_.querySelector(".join-email"))==null?void 0:E.value)==null?void 0:le.trim())||((K=o.signedInAs())==null?void 0:K.email)||((ne=o.config.user)==null?void 0:ne.email);if(!M){ke=m,fe(),(oe=_.querySelector(".join-email"))==null||oe.focus();return}try{const Q=await o.meToo(m,{email:M});ke=null,ce=ce.map(D=>D.id===m?{...D,joined:!0,affectedCount:Q.affectedCount??D.affectedCount}:D),fe()}catch(Q){xe=Q.message.replace("[BugCatch] ",""),fe()}}),Fe(),O.addEventListener("input",()=>gt(O.value));let Te=null;function Xe(){o.callKind().then(i=>{i&&(Te=i,C.textContent=i==="live"?"Talk to the team":"Talk to us",C.hidden=$e())}).catch(()=>{})}Xe();let f={mode:o.lastLoginMode||"off",signedIn:null},v="idle",$=((pn=o.config.user)==null?void 0:pn.email)||"",W="",I=!1,te="";const $e=()=>f.mode==="required"&&!f.signedIn;function Be(){const i=$e();Z.hidden=i,x.hidden=i,B&&(B.hidden=i),i?C.hidden=!0:Te&&(C.hidden=!1)}function Me(){var c;if(f.mode==="off"){H.hidden=!0,H.innerHTML="";return}H.hidden=!1,H.classList.toggle("asking",v!=="idle");const i=te?`<div class="after err">${T(te)}</div>`:"";if(v==="idle"){if(f.signedIn){H.innerHTML=`<span class="who">Signed in as <strong>${T(f.signedIn.email)}</strong></span><button type="button" class="link" data-act="signout">Sign out</button>`;return}const p=(c=o.config.user)==null?void 0:c.email;H.innerHTML=`<span class="who">${f.mode==="required"?"Sign in to report a bug here.":p?`Reporting as <strong>${T(p)}</strong>.`:"Reporting anonymously — sign in and we can tell you what happens next."}</span><button type="button" class="link" data-act="signin">Sign in</button>`;return}if(v==="email"){H.innerHTML=`
|
|
838
|
+
<p class="why">Sign in with any email address — we'll send you a code.
|
|
839
|
+
It's how you hear back about this report${f.mode==="required"?", and this project asks for it":""}.</p>
|
|
840
|
+
<div class="row">
|
|
841
|
+
<input type="email" data-fld="email" value="${T($)}"
|
|
842
|
+
placeholder="you@example.com" autocomplete="email" spellcheck="false"
|
|
843
|
+
aria-label="Your email address" ${I?"disabled":""} />
|
|
844
|
+
<button type="button" class="tool" data-act="code" ${I?"disabled":""}>
|
|
845
|
+
${I?"Sending…":"Send code"}
|
|
846
|
+
</button>
|
|
847
|
+
</div>
|
|
848
|
+
${i}
|
|
849
|
+
${f.mode==="optional"?'<div class="after"><button type="button" class="link" data-act="skip">Not now</button></div>':""}`;return}H.innerHTML=`
|
|
850
|
+
<p class="why">We sent a code to <strong>${T($)}</strong>. It expires in ten minutes.</p>
|
|
851
|
+
<div class="row">
|
|
852
|
+
<input type="text" class="code" data-fld="code" value="${T(W)}"
|
|
853
|
+
placeholder="123456" inputmode="numeric" autocomplete="one-time-code"
|
|
854
|
+
maxlength="8" aria-label="The code we emailed you" ${I?"disabled":""} />
|
|
855
|
+
<button type="button" class="tool" data-act="confirm" ${I?"disabled":""}>
|
|
856
|
+
${I?"Checking…":"Sign in"}
|
|
857
|
+
</button>
|
|
858
|
+
</div>
|
|
859
|
+
${i}
|
|
860
|
+
<div class="after">
|
|
861
|
+
<button type="button" class="link" data-act="resend" ${I?"disabled":""}>Send it again</button>
|
|
862
|
+
<button type="button" class="link" data-act="signin-back" ${I?"disabled":""}>Use another address</button>
|
|
863
|
+
</div>`}function re({focus:i=!1}={}){var p;if(Me(),Be(),!i||I)return;const c=H.querySelector("[data-fld]");c&&(c.focus(),(p=c.setSelectionRange)==null||p.call(c,c.value.length,c.value.length))}H.addEventListener("input",i=>{const c=i.target.closest("[data-fld]");c&&(c.dataset.fld==="email"?$=c.value:W=c.value)}),H.addEventListener("keydown",i=>{i.key!=="Enter"||!i.target.closest("[data-fld]")||(i.preventDefault(),v==="email"?mt():an())});async function mt({resent:i=!1}={}){const c=$.trim();if(!c){te="Enter the address to send the code to.",re({focus:!0});return}I=!0,te="",re();try{await o.signIn(c),$=c,i||(W=""),v="code"}catch(p){te=p.message}finally{I=!1,re({focus:!0})}}async function an(){const i=W.trim();if(!i){te="Enter the code we emailed you.",re({focus:!0});return}I=!0,te="",re();try{const c=await o.confirmSignIn($.trim(),i);f.signedIn=c,v="idle",W="",P(""),Xe()}catch(c){te=c.message}finally{I=!1,re({focus:v!=="idle"})}}H.addEventListener("click",async i=>{const c=i.target.closest("button[data-act]");if(!c)return;const p=c.dataset.act;if(p==="signin"){v="email",te="",re({focus:!0});return}if(p==="skip"){v="idle",te="",re();return}if(p==="signin-back"){v="email",te="",W="",re({focus:!0});return}if(p==="code"){mt();return}if(p==="resend"){mt({resent:!0});return}if(p==="confirm"){an();return}if(p==="signout"){I=!0,re();try{await o.signOut()}finally{f.signedIn=null,I=!1,v=f.mode==="required"?"email":"idle",re({focus:v!=="idle"})}}});async function bt({announce:i=!1}={}){const c=await o.loginState();f={mode:c.mode,signedIn:c.signedIn},o.lastLoginMode=c.mode,$e()&&v==="idle"&&(v="email"),f.signedIn&&(v="idle"),i&&c.signedOutReason&&P(c.signedOutReason,"info"),re()}bt({announce:!0}).catch(()=>{}),C.addEventListener("click",async()=>{w=!0,P(""),ie(),C.disabled=!0,C.innerHTML=`<span class="spinner"></span>${Te==="live"?"Ringing…":"Connecting…"}`;try{const i=await o.startCall();Ko(i)}catch(i){w=!1,C.disabled=!1,C.textContent=Te==="live"?"Talk to the team":"Talk to us",ie(),i.filed?rn(i.filed):(P(i.message.replace("[BugCatch] ","")),f.mode!=="off"&&bt().catch(()=>{}))}});function Ko(i){const c=h.querySelector(".panel"),p=i!=null&&i.bugId?String(i.bugId).slice(0,8):null,m=!!(i!=null&&i.live);c.innerHTML=`
|
|
864
|
+
<div class="call">
|
|
865
|
+
<div class="call-head">
|
|
866
|
+
<span class="call-dot" aria-hidden="true"></span>
|
|
867
|
+
<strong class="call-state">${m?"Ringing…":"Connecting…"}</strong>
|
|
868
|
+
</div>
|
|
869
|
+
<p class="call-note">
|
|
870
|
+
${m?`We're ringing the team now. Someone will pick up if they're free — your report${p?` <code>${T(p)}</code>`:""} is already filed either way, so nothing is lost if nobody answers.`:`You're talking to our automated assistant, not a person. What you say is
|
|
871
|
+
added to your report${p?` <code>${T(p)}</code>`:""} for the team to read.`}
|
|
872
|
+
</p>
|
|
873
|
+
<div class="call-lines" role="log" aria-live="polite"></div>
|
|
874
|
+
<div class="foot">
|
|
875
|
+
<span class="spacer"></span>
|
|
876
|
+
<button class="primary" type="button" data-act="hangup">${m?"Cancel":"Hang up"}</button>
|
|
877
|
+
</div>
|
|
878
|
+
</div>`;const M=c.querySelector(".call-state"),E=c.querySelector(".call-lines"),le=c.querySelector(".call-dot"),K=m?{ringing:"Ringing the team…",connecting:"Someone answered — connecting…",connected:"Connected",missed:"Nobody was free",ended:"Call ended",failed:"The call dropped"}:{connecting:"Connecting…",listening:"Listening",speaking:"Assistant is speaking",ended:"Call ended",failed:"The call dropped"},ne=["ended","failed","missed"],oe=o.on("call",({state:G})=>{if(M.textContent=K[G]||G,le.classList.toggle("off",ne.includes(G)),ne.includes(G)&&(c.querySelector('[data-act="hangup"]').textContent="Close",G==="missed")){const j=c.querySelector(".call-note");j&&(j.textContent="Nobody was free to pick up just now. Your report is filed and the team will reply in the chat.")}});let Q=null;const D=o.on("call-transcript",({role:G,text:j,final:F})=>{!j&&!F||(G==="agent"?((!Q||Q.dataset.role!=="agent")&&(Q=document.createElement("div"),Q.className="call-line agent",Q.dataset.role="agent",E.appendChild(Q)),j&&(Q.textContent+=j),F&&(Q=null)):((!Q||Q.dataset.role!=="reporter")&&(Q=document.createElement("div"),Q.className="call-line you",Q.dataset.role="reporter",E.appendChild(Q)),Q.textContent=j,F&&(Q=null)),E.scrollTop=E.scrollHeight)});c.querySelector('[data-act="hangup"]').addEventListener("click",()=>{oe(),D(),o.endCall(),q()})}function rn(i){var m;const c=i!=null&&i.id?String(i.id).slice(0,8):null,p=(m=i==null?void 0:i.estimate)!=null&&m.label?String(i.estimate.label):null;h.querySelector(".panel").innerHTML=`
|
|
879
|
+
<div class="done">
|
|
880
|
+
<div class="tick">${z.check}</div>
|
|
881
|
+
<h3>Thanks — that's on its way.</h3>
|
|
882
|
+
<p>${c?`Filed as <code>${T(c)}</code>.`:"The team has what they need to reproduce it."}</p>
|
|
883
|
+
${p?`<p class="eta">Reports like this usually take <strong>${T(p)}</strong>. We'll let you know when it moves.</p>`:""}
|
|
884
|
+
</div>`,setTimeout(q,p?4e3:2e3)}async function sn(){const i=h.querySelector(".panel");i.innerHTML=`
|
|
885
|
+
<div class="head">
|
|
886
|
+
<div class="mark">${z.bug}</div>
|
|
887
|
+
<div class="titles">
|
|
888
|
+
<h2>Your reports</h2>
|
|
889
|
+
<p>Everything you've reported, and where each one got to.</p>
|
|
890
|
+
</div>
|
|
891
|
+
<button class="icon-btn" type="button" data-act="close" aria-label="Close">${z.close}</button>
|
|
892
|
+
</div>
|
|
893
|
+
<div class="history"><div class="empty">Loading…</div></div>`,i.querySelector('[data-act="close"]').addEventListener("click",q);let c=[];try{({reports:c}=await o.myReports())}catch{i.querySelector(".history").innerHTML=`<div class="empty">Couldn't load your reports just now.</div>`;return}const p=i.querySelector(".history");if(p){p.innerHTML=c.length?c.map(m=>`
|
|
894
|
+
<button class="row" type="button" data-report="${T(m.id)}">
|
|
895
|
+
<span class="who">
|
|
896
|
+
<span class="ttl">${T(m.title)}</span>
|
|
897
|
+
<span class="sub">${T(m.reference)} · ${T(De(m.createdAt))}</span>
|
|
898
|
+
</span>
|
|
899
|
+
<span class="pill ${T(String(m.status).toLowerCase())}">${T(rt(m.status))}</span>
|
|
900
|
+
</button>`).join(""):`<div class="empty">You haven't reported anything yet.</div>`;for(const m of p.querySelectorAll("[data-report]"))m.addEventListener("click",()=>_o(m.dataset.report))}}async function _o(i){var E;const c=h.querySelector(".panel");c.innerHTML=`
|
|
901
|
+
<div class="head">
|
|
902
|
+
<button class="icon-btn back" type="button" data-act="back" aria-label="Back to your reports">${z.back}</button>
|
|
903
|
+
<div class="titles">
|
|
904
|
+
<h2>Your report</h2>
|
|
905
|
+
<p>Everything that has happened, and a way to answer.</p>
|
|
906
|
+
</div>
|
|
907
|
+
<button class="icon-btn" type="button" data-act="close" aria-label="Close">${z.close}</button>
|
|
908
|
+
</div>
|
|
909
|
+
<div class="history"><div class="empty">Loading…</div></div>`,c.querySelector('[data-act="close"]').addEventListener("click",q),c.querySelector('[data-act="back"]').addEventListener("click",sn);let p=null;try{p=await o.myReport(i)}catch{p=null}const m=c.querySelector(".history");if(!m||!c.querySelector('[data-act="back"]'))return;if(!p){m.innerHTML=`<div class="empty">Couldn't load that report just now.</div>`;return}ge=i,o.markReportRead(i),m.classList.add("chatting"),m.innerHTML=`
|
|
910
|
+
<div class="report-head">
|
|
911
|
+
<span class="ttl">${T(p.title)}</span>
|
|
912
|
+
<span class="sub">${T(p.reference)} · ${T(p.project||"")}</span>
|
|
913
|
+
<span class="pill ${T(String(p.status).toLowerCase())}">${T(rt(p.status))}</span>
|
|
914
|
+
${(E=p.estimate)!=null&&E.label?`<span class="eta-line">Usually takes ${T(String(p.estimate.label))}</span>`:""}
|
|
915
|
+
</div>
|
|
916
|
+
<div class="thread">${Dt(p.timeline)}</div>`;const M=m.querySelector(".thread");at(M),p.canReply&&Wo(m,M,i)}async function wt(i){const c=h.querySelector(".history.chatting"),p=c==null?void 0:c.querySelector(".thread");if(!p||ge!==i)return;let m=null;try{m=await o.myReport(i)}catch{}if(!m||ge!==i)return;const M=Eo(p);p.innerHTML=Dt(m.timeline),M&&at(p);const E=c.querySelector(".report-head .pill");E&&(E.className=`pill ${String(m.status).toLowerCase()}`,E.textContent=rt(m.status))}function Wo(i,c,p){const m=document.createElement("div");m.className="composer",m.innerHTML=`
|
|
917
|
+
<div class="staged" hidden></div>
|
|
918
|
+
<div class="row">
|
|
919
|
+
<div class="tools">
|
|
920
|
+
<button class="tool" type="button" data-act="attach" title="Attach a file" aria-label="Attach a file">${z.paperclip}</button>
|
|
921
|
+
<button class="tool" type="button" data-act="voice" title="Record a voice note" aria-label="Record a voice note">${z.mic}</button>
|
|
922
|
+
<button class="tool" type="button" data-act="livecall" title="Call the team" aria-label="Call the team" hidden>${z.phone}</button>
|
|
923
|
+
</div>
|
|
924
|
+
<textarea rows="1" placeholder="Write a reply…" aria-label="Write a reply"></textarea>
|
|
925
|
+
<button class="send" type="button" data-act="send" title="Send" aria-label="Send" disabled>${z.send}</button>
|
|
926
|
+
</div>
|
|
927
|
+
<div class="note" hidden></div>
|
|
928
|
+
<input type="file" hidden multiple accept="${It.join(",")}">`,i.appendChild(m);const M=m.querySelector(".staged"),E=m.querySelector("textarea"),le=m.querySelector('[data-act="send"]'),K=m.querySelector('[data-act="voice"]'),ne=m.querySelector('[data-act="livecall"]'),oe=m.querySelector("input[type=file]"),Q=m.querySelector(".note"),D=[];let G=null,j=!1;function F(L,U=""){Q.hidden=!L,Q.className=`note ${U}`,Q.textContent=L||""}function de(){const L=D.filter(U=>U.storageKey);le.disabled=j||!E.value.trim()&&!L.length,M.hidden=!D.length,M.innerHTML=D.map((U,me)=>`
|
|
929
|
+
<span class="chip ${U.storageKey?"":"uploading"}">
|
|
930
|
+
<span class="nm">${T(U.name)}</span>
|
|
931
|
+
<span class="sz">${U.storageKey?Se(U.size):"sending…"}</span>
|
|
932
|
+
<button type="button" data-drop="${me}" aria-label="Remove ${T(U.name)}">${z.close}</button>
|
|
933
|
+
</span>`).join("");for(const U of M.querySelectorAll("[data-drop]"))U.addEventListener("click",()=>{D.splice(Number(U.dataset.drop),1),de()})}async function hn(L,U,me){const X={name:me,size:L.size,storageKey:null};D.push(X),de();try{const{storageKey:yt,mime:Yo}=await o.uploadAttachment(L,U);X.storageKey=yt,X.mime=Yo}catch(yt){D.splice(D.indexOf(X),1),F(yt.message||"That file could not be sent.","err")}de()}E.addEventListener("input",()=>{E.style.height="auto",E.style.height=`${Math.min(E.scrollHeight,120)}px`,de()}),E.addEventListener("keydown",L=>{L.key==="Enter"&&!L.shiftKey&&!L.isComposing&&(L.preventDefault(),fn())}),m.querySelector('[data-act="attach"]').addEventListener("click",()=>oe.click()),oe.addEventListener("change",()=>{F("");for(const L of oe.files){if(L.size>o.config.maxAttachmentBytes){F(`${L.name} is larger than ${Se(o.config.maxAttachmentBytes)}.`,"err");continue}hn(L,L.type,L.name)}oe.value=""}),Qn()||(K.disabled=!0,K.title="This browser cannot record audio"),K.addEventListener("click",async()=>{if(G){const L=G;G=null,K.classList.remove("recording"),K.innerHTML=z.mic,F("");const{blob:U,mime:me}=await L.stop();U.size&&hn(U,me,"Voice note");return}try{F(""),G=await Hn({maxMs:o.config.maxRecordingMs,onTick:L=>F(`Recording — ${He(L)}. Tap the mic to stop.`),onAutoStop:()=>K.click()}),K.classList.add("recording"),K.innerHTML=z.stopSquare}catch{G=null,F("The microphone is not available — check the browser permission.","err")}}),o.canCallTeam(p).then(L=>{L&&(ne.hidden=!1)}).catch(()=>{}),ne.addEventListener("click",async()=>{ne.disabled=!0,F("");try{const L=await o.startLiveCall(p);Fo(i,c,L)}catch(L){F(L.message.replace("[BugCatch] ",""),"err")}finally{ne.disabled=!1}}),le.addEventListener("click",fn);async function fn(){var me;const L=E.value.trim(),U=D.filter(X=>X.storageKey);if(!(j||!L&&!U.length)){j=!0,de(),F(""),c.insertAdjacentHTML("beforeend",Nt({from:"reporter",body:L,at:new Date().toISOString(),attachments:U.map(X=>({type:"FILE",name:X.name,size:X.size}))},"pending")),at(c);try{await Rt({apiBase:o.config.apiBase,bugId:p,body:L,attachments:U.map(X=>({storageKey:X.storageKey,mime:X.mime,name:X.name,size:X.size}))}),E.value="",E.style.height="auto",D.length=0,await wt(p)}catch(X){(me=c.querySelector(".msg-row.pending"))==null||me.classList.replace("pending","failed"),F(X.message||"That could not be sent — try again.","err")}finally{j=!1,de()}}}de()}function Fo(i,c,p){const m=document.createElement("div");m.className="livecall",m.innerHTML=`
|
|
934
|
+
<span class="call-dot" aria-hidden="true"></span>
|
|
935
|
+
<span class="who" role="status" aria-live="polite">Calling the team…</span>
|
|
936
|
+
<span class="secs" hidden></span>
|
|
937
|
+
<button class="act" type="button" data-act="mute" hidden>Mute</button>
|
|
938
|
+
<button class="act danger" type="button" data-act="hangup">Cancel</button>`,i.insertBefore(m,c);const M=m.querySelector(".who"),E=m.querySelector(".secs"),le=m.querySelector(".call-dot"),K=m.querySelector('[data-act="mute"]'),ne=m.querySelector('[data-act="hangup"]');let oe=null;const Q=()=>{oe&&(clearInterval(oe),oe=null)},D=j=>{Q(),le.classList.add("off"),m.classList.add("over"),M.textContent=j,E.hidden=!0,K.hidden=!0,ne.textContent="Dismiss",ne.classList.remove("danger")},G=o.on("call",({state:j,live:F})=>{if(F){if(j==="ringing"&&(M.textContent="Calling the team…"),j==="connecting"&&(M.textContent="Someone answered — connecting…"),j==="connected"){M.textContent="You're on a call with the team",K.hidden=!1,E.hidden=!1;const de=Date.now();Q(),oe=setInterval(()=>{E.textContent=He(Math.floor((Date.now()-de)/1e3))},1e3),E.textContent="0s"}j==="missed"&&D("Nobody was free. Send a message instead and the team will pick it up."),j==="ended"&&D("Call ended."),j==="failed"&&D("The call dropped.")}});K.addEventListener("click",()=>{const j=p.setMuted(!p.isMuted());K.textContent=j?"Unmute":"Mute",M.textContent=j?"You're muted":"You're on a call with the team"}),ne.addEventListener("click",()=>{G(),Q(),o.endCall(),m.remove(),wt(ge)})}let ge=null;return Ee=o.on("update",i=>{!ge||(i==null?void 0:i.bugId)!==ge||(o.markReportRead(ge),wt(ge))}),t.appendChild(h),ie(),O.focus(),{destroy:_e,close:q}}function Dt(t){const e=t||[];return e.length?e.map(n=>{var o,a,r,s;return n.from?Nt({from:n.from,author:(o=n.reply)==null?void 0:o.author,automated:(a=n.reply)==null?void 0:a.automated,body:(r=n.reply)==null?void 0:r.body,at:n.at,attachments:(s=n.reply)==null?void 0:s.attachments}):So(n)}).join(""):""}function So(t){return`
|
|
939
|
+
<div class="sys k-${T(t.kind)}">
|
|
940
|
+
<span class="dot" aria-hidden="true"></span>${T(t.summary||"")}
|
|
941
|
+
· ${T(De(t.at))}
|
|
942
|
+
</div>`}function Nt(t,e=""){const n=t.from==="reporter",o=t.body||"",a=t.attachments||[],r=!n&&!!t.automated,s=o?`<span class="msg-text">${T(o)}</span>`:"",l=a.length?`<div class="att">${a.map(Co).join("")}</div>`:"";let u="";return r?u='<span class="msg-who auto">Automatic reply</span>':!n&&t.author&&(u=`<span class="msg-who">${T(t.author)}</span>`),`
|
|
943
|
+
<div class="msg-row ${n?"mine":"theirs"} ${r?"auto":""} ${e}">
|
|
944
|
+
${u}
|
|
945
|
+
<div class="bubble ${o?"":"silent"}">${s}${l}</div>
|
|
946
|
+
<span class="msg-when">${T(De(t.at))}</span>
|
|
947
|
+
</div>`}function Co(t){const e=T(t.name||"attachment");if(!t.url)return`<span class="file"><span class="nm">${e}</span></span>`;const n=T(t.url);return t.type==="SCREENSHOT"?`<a href="${n}" target="_blank" rel="noopener noreferrer"><img src="${n}" alt="${e}" loading="lazy"></a>`:t.type==="AUDIO"?`<audio controls preload="metadata" src="${n}"></audio>`:t.type==="VIDEO"?`<video controls preload="metadata" src="${n}"></video>`:`
|
|
948
|
+
<a class="file" href="${n}" target="_blank" rel="noopener noreferrer" download>
|
|
949
|
+
<span class="nm">${e}</span>
|
|
950
|
+
${t.size?`<span class="sz">${T(Se(t.size))}</span>`:""}
|
|
951
|
+
</a>`}function Eo(t){return t.scrollHeight-t.scrollTop-t.clientHeight<40}function at(t){t.scrollTop=t.scrollHeight}function rt(t){return{OPEN:"Open",IN_PROGRESS:"In progress",RESOLVED:"Fixed",CLOSED:"Closed"}[t]||String(t||"").toLowerCase()}function De(t){const e=new Date(t).getTime();if(!Number.isFinite(e))return"";const n=Math.floor((Date.now()-e)/864e5);if(n<=0)return"today";if(n===1)return"yesterday";if(n<30)return`${n} days ago`;const o=Math.floor(n/30);return o===1?"a month ago":`${o} months ago`}function T(t){return String(t).replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e])}const To="bugcatch.session.",it=t=>`${To}${t}`,st=new Map;function pe(t){var e;if(!t)return null;try{const n=(e=window.localStorage)==null?void 0:e.getItem(it(t));if(n){const o=JSON.parse(n);if(o!=null&&o.token)return o}}catch{}return st.get(t)||null}function $o(t,e){var o;if(!t||!(e!=null&&e.token))return;const n={token:e.token,email:e.email||null,name:e.name||null};st.set(t,n);try{(o=window.localStorage)==null||o.setItem(it(t),JSON.stringify(n))}catch{}}function ct(t){var e;if(t){st.delete(t);try{(e=window.localStorage)==null||e.removeItem(it(t))}catch{}}}function Ne({apiKey:t,token:e}){return{"Content-Type":"application/json","X-BugCatch-Key":t,...e?{"X-BugCatch-Session":e}:{}}}async function Vt(t){try{const e=await t.json(),n=Array.isArray(e.message)?e.message.join(", "):e.message;if(n)return n}catch{}return`Request failed (HTTP ${t.status})`}async function Bo({apiBase:t,apiKey:e,projectId:n,token:o}){const a={mode:"off",signedIn:null,signedOutReason:null};try{const r=await fetch(`${t}/ingest/login?projectId=${encodeURIComponent(n)}`,{headers:Ne({apiKey:e,token:o})});if(!r.ok)return a;const s=await r.json();return{mode:s.mode==="optional"||s.mode==="required"?s.mode:"off",signedIn:s.signedIn||null,signedOutReason:s.signedOutReason||null}}catch{return a}}async function Mo({apiBase:t,apiKey:e,projectId:n,email:o,name:a}){const r=await fetch(`${t}/ingest/login/start`,{method:"POST",headers:Ne({apiKey:e}),body:JSON.stringify({projectId:n,email:o,...a?{name:a}:{}})});if(!r.ok)throw new Error(await Vt(r));return r.json()}async function Lo({apiBase:t,apiKey:e,projectId:n,email:o,code:a}){const r=await fetch(`${t}/ingest/login`,{method:"POST",headers:Ne({apiKey:e}),body:JSON.stringify({projectId:n,email:o,code:a})});if(!r.ok)throw new Error(await Vt(r));const s=await r.json(),l={token:s.sessionToken,email:s.email,name:s.name};return $o(n,l),l}async function Io({apiBase:t,apiKey:e,projectId:n,token:o}){if(ct(n),!o)return{signedOut:!0};try{await fetch(`${t}/ingest/logout`,{method:"POST",headers:Ne({apiKey:e,token:o}),body:JSON.stringify({projectId:n})})}catch{}return{signedOut:!0}}const Ie=8e3;function Pt(t=Ie){const e=window.AudioContext||window.webkitAudioContext;try{return new e({sampleRate:t})}catch{return new e}}async function lt(t){if(t.state==="suspended")try{await t.resume()}catch{}}function Kt(t,e,n){const o=e/n,a=new Float32Array(Math.floor(t.length/o));for(let r=0;r<a.length;r++){const s=Math.floor(r*o),l=Math.min(Math.floor((r+1)*o),t.length);let u=0;for(let g=s;g<l;g++)u+=t[g];a[r]=l>s?u/(l-s):0}return a}const dt=132,_t=32635;function Wt(t){const e=new Uint8Array(t.length);for(let n=0;n<t.length;n++){const o=Math.max(-1,Math.min(1,t[n]));let a=Math.round(o*32767);const r=a<0?128:0;r&&(a=-a),a>_t&&(a=_t),a+=dt;let s=7;for(let u=16384;!(a&u)&&s>0;s--,u>>=1);const l=a>>s+3&15;e[n]=~(r|s<<4|l)&255}return e}function Ao(t){const e=new Float32Array(t.length);for(let n=0;n<t.length;n++){const o=~t[n]&255,a=o&128,r=o>>4&7,l=(((o&15)<<3)+dt<<r)-dt;e[n]=(a?-l:l)/32768}return e}const Ft=512;function Qo(t,e,n,o=Ie){const a=t.createMediaStreamSource(e),r=t.createScriptProcessor(Ft,1,1);r.onaudioprocess=l=>{const u=l.inputBuffer.getChannelData(0),g=t.sampleRate===o?u:Kt(u,t.sampleRate,o);n(Wt(g))},a.connect(r);const s=t.createGain();return s.gain.value=0,r.connect(s),s.connect(t.destination),{stop(){r.onaudioprocess=null;try{a.disconnect()}catch{}try{r.disconnect()}catch{}try{s.disconnect()}catch{}}}}const Ro="voiceai.realtime.v1";async function jo({realtimeUrl:t,onState:e,onTranscript:n}){const o=(y,x)=>{try{e==null||e(y,x)}catch{}},a=y=>{try{n==null||n(y)}catch{}};o("connecting");let r;try{r=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,channelCount:1}})}catch(y){throw o("failed",{reason:"microphone",error:String((y==null?void 0:y.name)||y)}),y}const s=Pt();await lt(s);const l=s.createMediaStreamSource(r);let u;try{u=await zo(t)}catch(y){throw Yt(r,s,null,null),o("failed",{reason:"connect"}),y}const g=s.createScriptProcessor(Ft,1,1);let b=!0;g.onaudioprocess=y=>{if(!b||u.readyState!==WebSocket.OPEN)return;const x=y.inputBuffer.getChannelData(0),C=s.sampleRate===Ie?x:Kt(x,s.sampleRate,Ie);u.send(Wt(C))},l.connect(g);const d=s.createGain();d.gain.value=0,g.connect(d),d.connect(s.destination);let w=0;const h=new Set,S=y=>{s.state==="suspended"&<(s);const x=Ao(y),C=s.createBuffer(1,x.length,Ie);C.getChannelData(0).set(x);const R=s.createBufferSource();R.buffer=C,R.connect(s.destination),R.onended=()=>h.delete(R);const Z=s.currentTime;w<Z&&(w=Z),R.start(w),w+=C.duration,h.add(R)},O=()=>{for(const y of h)try{y.stop()}catch{}h.clear(),w=0};u.addEventListener("message",y=>{var C,R;if(typeof y.data!="string")return;let x;try{x=JSON.parse(y.data)}catch{return}switch(x.type){case"session.created":o("listening");break;case"transcript.partial":ut(x.text)&&a({role:"reporter",text:x.text,final:!1});break;case"transcript.final":ut(x.text)&&a({role:"reporter",text:x.text,final:!0});break;case"response.started":o("speaking");break;case"response.audio.delta":x.audio&&S(Oo(x.audio));break;case"response.text.delta":ut(x.text)&&a({role:"agent",text:x.text,final:!1});break;case"response.done":a({role:"agent",text:"",final:!0}),o("listening");break;case"state.changed":(x.state==="listening"||x.state==="interrupted")&&O(),x.state&&o(x.state);break;case"session.ended":k("ended",{reason:((C=x.data)==null?void 0:C.reason)||null});break;case"error":k("failed",{reason:((R=x.error)==null?void 0:R.detail)||"the call failed"});break}}),u.addEventListener("close",()=>k("ended")),u.addEventListener("error",()=>k("failed",{reason:"transport"}));let Y=!1;function k(y,x){Y||(Y=!0,b=!1,O(),Yt(r,s,g,u),o(y,x))}return{hangUp(){if(!Y&&u.readyState===WebSocket.OPEN)try{u.send(JSON.stringify({type:"session.end"}))}catch{}k("ended",{reason:"hung_up"})}}}function zo(t){return new Promise((e,n)=>{let o;try{o=new WebSocket(t,Ro)}catch(l){n(l);return}o.binaryType="arraybuffer";const a=()=>{s(),e(o)},r=()=>{s(),n(new Error("the call could not be connected"))},s=()=>{o.removeEventListener("open",a),o.removeEventListener("error",r),o.removeEventListener("close",r)};o.addEventListener("open",a),o.addEventListener("error",r),o.addEventListener("close",r)})}function Yt(t,e,n,o){try{n==null||n.disconnect()}catch{}n&&(n.onaudioprocess=null);for(const a of(t==null?void 0:t.getTracks())??[])try{a.stop()}catch{}try{e==null||e.close()}catch{}try{o==null||o.close()}catch{}}function ut(t){return typeof t!="string"||!t||t.includes("\0")?!1:(t.match(/[\u0001-\u0008\u000e-\u001f]/g)||[]).length<=t.length/10}function Oo(t){const e=window.atob(t),n=new Uint8Array(e.length);for(let o=0;o<e.length;o++)n[o]=e.charCodeAt(o);return n}const Ho="voiceai.realtime.v1",qo=[{urls:"stun:stun.l.google.com:19302"}];function pt(){return navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,channelCount:1}})}function Ve(){var t;return typeof window<"u"&&typeof window.RTCPeerConnection=="function"&&!!((t=navigator.mediaDevices)!=null&&t.getUserMedia)}async function Zt({realtimeUrl:t,stream:e,ringsUntil:n,onState:o,onAudioBlocked:a,onMicrophone:r}){const s=(f,v)=>{try{o==null||o(f,v)}catch{}},l=f=>{try{a==null||a(f)}catch{}},u=f=>{try{r==null||r(f)}catch{}};if(!Ve()){const f=new Error("[BugCatch] this browser cannot make live calls.");throw s("failed",{reason:"unsupported"}),f}let g=e;if(!g)try{g=await pt()}catch(f){throw s("failed",{reason:"microphone",error:String((f==null?void 0:f.name)||f)}),f}let b;try{b=await Uo(t)}catch(f){throw Jt(g),s("failed",{reason:"connect"}),f}let d=null,w=null,h=!1,S=null,O=null,Y=null,k=!1,y=null,x=null,C=!1;const R=[];let Z=!1,H=!1,_=!1,B=null,J=null,ye=0;const ht=2e3,Ce=5e3,Ee=3e4,_e=4,q=(f,v)=>{if(!h){h=!0,S&&(clearTimeout(S),S=null),xe(),x==null||x(),x=null,ie(),y==null||y(),y=null;try{d==null||d.close()}catch{}Jt(g),w&&(w.srcObject=null,w.remove(),w=null);try{b.readyState===WebSocket.OPEN&&b.close(1e3,"call over")}catch{}s(f,v)}},se=f=>{b.readyState===WebSocket.OPEN&&b.send(JSON.stringify(f))},P=async()=>{if(C||Y||h||!O||!O.audio&&!O.transcript)return;C=!0;const f=O.encoding||"mulaw";if(f!=="mulaw"){Xt(`this call is kept as ${f}, which this SDK cannot produce.`);return}const v=Number(O.sample_rate)||8e3;try{const $=Pt(v);if(await lt($),h){try{$.close()}catch{}return}const W=Qo($,g,I=>{b.readyState===WebSocket.OPEN&&b.send(I)},v);Y={context:$,tap:W}}catch($){Xt(`could not send the copy this call is keeping: ${String(($==null?void 0:$.message)||$)}`)}};function ie(){if(C=!1,!Y)return;const{context:f,tap:v}=Y;Y=null;try{v.stop()}catch{}try{f.close()}catch{}}function Ae(){var v;if(!w||h)return;let f;try{f=(v=w.play)==null?void 0:v.call(w)}catch{Qe();return}if(!f||typeof f.then!="function"){We();return}f.then(We,Qe)}function We(){y==null||y(),y=null,k&&(k=!1,l(!1))}function Qe(){h||(k||(k=!0,l(!0)),Re())}function Re(){if(y||typeof window>"u"||typeof window.addEventListener!="function")return;const f=()=>Ae(),v=["pointerdown","keydown","touchstart"];for(const $ of v)window.addEventListener($,f,{capture:!0,passive:!0});y=()=>{for(const $ of v)try{window.removeEventListener($,f,{capture:!0})}catch{}}}const ft=f=>{d=new RTCPeerConnection({iceServers:f!=null&&f.length?f:qo});for(const v of g.getAudioTracks())d.addTrack(v,g);x=Fe(),d.onicecandidate=v=>{v.candidate&&se({type:"signal",signal:{kind:"candidate",payload:v.candidate.toJSON()}})},d.ontrack=v=>{var W;const[$]=v.streams;$&&(w||(w=document.createElement("audio"),w.autoplay=!0,w.playsInline=!0,(W=w.setAttribute)==null||W.call(w,"playsinline",""),w.style.cssText="position:fixed;width:0;height:0;opacity:0;pointer-events:none",document.body.appendChild(w)),w.srcObject=$,Ae())},d.onconnectionstatechange=ve,d.oniceconnectionstatechange=ve};function ve(){if(!d||h)return;const f=d.connectionState||d.iceConnectionState;if(f==="connected"||f==="completed"){H=!0,ye=0,xe(),_=!1,s("connected");return}if(f==="disconnected"){if(!H)return;ce(ht);return}if(f==="failed"){if(!H){q("failed",{reason:"ice"});return}ce(0)}}function ce(f){h||(_||(_=!0,s("reconnecting"),J=setTimeout(()=>q("failed",{reason:"network"}),Ee)),!B&&(B=setTimeout(ke,f)))}function xe(){B&&(clearTimeout(B),B=null),J&&(clearTimeout(J),J=null)}async function ke(){if(B=null,h||!d)return;const f=d.connectionState||d.iceConnectionState;if(!(f==="connected"||f==="completed")){if(!Z){se({type:"signal",signal:{kind:"restart",payload:{}}}),B=setTimeout(ke,Ce);return}ye>=_e||(ye+=1,await he(),B=setTimeout(ke,Ce))}}async function he(){if(!(h||!d))try{const f=await d.createOffer({iceRestart:!0});await d.setLocalDescription(f),se({type:"signal",signal:{kind:"offer",payload:d.localDescription.toJSON()}})}catch{}}function Fe(){let v=null,$=!1;const W=I=>{if(v&&(clearTimeout(v),v=null),I){if(!$)return;$=!1,u(!0);return}$||(v=setTimeout(()=>{v=null,!h&&($=!0,u(!1))},600))};for(const I of g.getAudioTracks())I.onmute=()=>W(!1),I.onunmute=()=>W(!0),I.onended=()=>{v&&(clearTimeout(v),v=null),!$&&($=!0,u(!1))};return()=>{v&&(clearTimeout(v),v=null);for(const I of g.getAudioTracks())I.onmute=null,I.onunmute=null,I.onended=null}}const Ye=async()=>{try{const f=await d.createOffer();await d.setLocalDescription(f),se({type:"signal",signal:{kind:"offer",payload:d.localDescription.toJSON()}})}catch{q("failed",{reason:"negotiation"})}},gt=async f=>{try{await d.setRemoteDescription(new RTCSessionDescription(f));const v=await d.createAnswer();await d.setLocalDescription(v),se({type:"signal",signal:{kind:"answer",payload:d.localDescription.toJSON()}}),await fe()}catch{q("failed",{reason:"negotiation"})}},fe=async()=>{for(;R.length;){const f=R.shift();try{await d.addIceCandidate(new RTCIceCandidate(f))}catch{}}};let Ze=Promise.resolve();b.onmessage=f=>{Ze=Ze.then(()=>Te(f)).catch(()=>{})};const Te=async f=>{var $,W,I,te,$e;if(typeof f.data!="string")return;let v;try{v=JSON.parse(f.data)}catch{return}switch(v.type){case"session.created":{if(ft(($=v.data)==null?void 0:$.ice_servers),O=((W=v.data)==null?void 0:W.capture)||null,(I=v.data)!=null&&I.peer_present){s("connecting"),P();break}s("ringing"),Xe();break}case"peer.joined":{S&&(clearTimeout(S),S=null),s("connecting"),Z=!!((te=v.data)!=null&&te.should_offer),Z&&await Ye(),P();break}case"signal":{if(!d)break;const{kind:Be,payload:Me}=v.data||{};if(Be==="offer")await gt(Me);else if(Be==="answer")try{await d.setRemoteDescription(new RTCSessionDescription(Me)),await fe()}catch{q("failed",{reason:"negotiation"})}else if(Be==="candidate")if(d.remoteDescription)try{await d.addIceCandidate(new RTCIceCandidate(Me))}catch{}else R.push(Me);else Be==="restart"&&Z&&await he();break}case"peer.left":q("ended",{reason:"peer_left"});break;case"error":q("failed",{reason:"server",detail:($e=v.error)==null?void 0:$e.detail});break;case"session.ended":q("ended",{reason:"server"});break}};b.onclose=()=>q("ended",{reason:"disconnected"}),b.onerror=()=>q("failed",{reason:"socket"});function Xe(){if(!n)return;const f=new Date(n).getTime()-Date.now();Number.isFinite(f)&&(S=setTimeout(()=>q("missed"),Math.max(0,f)))}return{hangUp(){se({type:"session.end"}),q("ended",{reason:"hung_up"})},setMuted(f){for(const v of g.getAudioTracks())v.enabled=!f;return f},isMuted(){return g.getAudioTracks().every(f=>!f.enabled)}}}function Uo(t){return new Promise((e,n)=>{let o;try{o=new WebSocket(t,Ho)}catch(a){n(a);return}o.onopen=()=>{o.onopen=null,o.onerror=null,e(o)},o.onerror=()=>{o.onopen=null,o.onerror=null,n(new Error("[BugCatch] could not connect the call."))}})}function Xt(t){try{console.warn(`[BugCatch] ${t}`)}catch{}}function Jt(t){var e;for(const n of((e=t==null?void 0:t.getTracks)==null?void 0:e.call(t))??[])try{n.stop()}catch{}}const Pe={cycleMs:3e3,peak:.045,bursts:[{at:0,seconds:.4,hz:[440,660]},{at:.55,seconds:.4,hz:[440,660]}]};let Gt=!1;if(typeof window<"u"&&window.addEventListener)for(const t of["pointerdown","keydown","touchstart"])window.addEventListener(t,()=>{Gt=!0},{once:!0,capture:!0,passive:!0});function Do(){var t;try{return!!((t=navigator.userActivation)!=null&&t.hasBeenActive)}catch{return!1}}function No(){if(!Gt&&!Do())return()=>{};const t=typeof window<"u"&&(window.AudioContext||window.webkitAudioContext);if(!t)return()=>{};let e;try{e=new t}catch{return()=>{}}let n=!1,o=null;const a=[],r=({at:u,seconds:g,hz:b})=>{const d=e.currentTime+u,w=e.createGain();w.connect(e.destination),w.gain.setValueAtTime(0,d),w.gain.linearRampToValueAtTime(Pe.peak,d+.02),w.gain.setValueAtTime(Pe.peak,d+g-.06),w.gain.linearRampToValueAtTime(0,d+g);for(const h of b){const S=e.createOscillator();S.type="sine",S.frequency.value=h,S.connect(w),S.start(d),S.stop(d+g+.02),a.push(S)}},s=()=>{if(!n)try{Pe.bursts.forEach(r)}catch{}},l=()=>{n||(s(),o=setInterval(s,Pe.cycleMs))};return e.state==="running"?l():e.resume().then(l).catch(()=>{}),()=>{n=!0,o&&clearInterval(o);for(const u of a)try{u.stop()}catch{}try{e.close()}catch{}}}function Vo(t){const e=Date.parse(t||"");return Number.isNaN(e)?0:Math.max(0,Math.round((e-Date.now())/1e3))}const en=["flex:1","padding:9px 12px","border:0","border-radius:8px","font:600 13px/1.4 inherit","cursor:pointer"].join(";");function Po({call:t,accent:e="#5b6ef5",position:n="bottom-right",onAnswer:o,onDecline:a,onExpire:r}){if(typeof document>"u"||!document.body)return()=>{};const[s,l]=String(n).split("-"),u=document.createElement("div");u.setAttribute("role","alertdialog"),u.setAttribute("aria-live","assertive"),u.setAttribute("aria-label",`${t.from||"Support"} is calling about ${t.reference}`),u.style.cssText=["position:fixed",`${s==="top"?"top":"bottom"}:20px`,`${l==="left"?"left":"right"}:20px`,"z-index:2147483647","width:300px","max-width:calc(100vw - 40px)","padding:14px","border-radius:12px","background:#1b1e26","color:#eceef2",'font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',"box-shadow:0 18px 44px rgba(0,0,0,0.5)",`border:1px solid ${e}`,"opacity:0","transform:translateY(8px)","transition:opacity .2s ease,transform .2s ease"].join(";");const g=document.createElement("div");g.style.cssText="font-weight:600;font-size:14px;margin-bottom:2px",g.textContent=`${t.from||"Support"} is calling`;const b=document.createElement("div");b.style.cssText="color:#a4abba;margin-bottom:10px",b.textContent=t.project?`About your report ${t.reference} · ${t.project}`:`About your report ${t.reference}`;const d=document.createElement("div");d.style.cssText="color:#7d8598;font-size:12px;margin-bottom:10px";const w=document.createElement("button");w.type="button",w.textContent="Answer",w.style.cssText=`${en};background:${e};color:#fff`;const h=document.createElement("button");h.type="button",h.textContent="Not now",h.style.cssText=`${en};background:#2b2f3a;color:#c8cedb`;const S=document.createElement("div");if(S.style.cssText="display:flex;gap:8px",S.append(w,h),u.append(g,b,d),t.recorded){const C=document.createElement("div");C.style.cssText="color:#7d8598;font-size:12px;margin-bottom:10px",C.textContent="This call will be recorded.",u.append(C)}u.append(S),document.body.appendChild(u);const O=No();let Y=!1,k=null;const y=()=>{Y||(Y=!0,k&&clearInterval(k),O(),u.style.opacity="0",u.style.transform="translateY(8px)",setTimeout(()=>u.remove(),250))},x=()=>{const C=Vo(t.ringsUntil);if(d.textContent=C?`Ringing — ${C}s`:"The call has stopped ringing.",!C){y();try{r==null||r()}catch{}}};return x(),k=setInterval(x,1e3),w.addEventListener("click",()=>{y();try{o==null||o()}catch{}}),h.addEventListener("click",()=>{y();try{a==null||a()}catch{}}),requestAnimationFrame(()=>{u.style.opacity="1",u.style.transform="translateY(0)"}),y}const tn={name:"@sphoro/bugcatch-web-sdk",version:"0.13.1"},Ke={apiBase:"http://localhost:3000",apiKey:null,projectId:null,consoleLimit:100,networkLimit:50,captureConsole:!0,captureNetwork:!0,captureErrors:!0,widget:!0,widgetPosition:"bottom-right",theme:"auto",accent:"#4F46E5",attachments:!0,maxAttachments:5,maxAttachmentBytes:25*1024*1024,maxRecordingMs:12e4,beforeSend:null,user:null,updates:!0,updateToast:!0,onUpdate:null,voice:!0,onCallState:null,onCallTranscript:null,onIncomingCall:null};class nn{constructor(){mn(this,A);this.config={...Ke},this.started=!1,this.teardown=[],this.consoleBuffer=je(Ke.consoleLimit),this.networkBuffer=je(Ke.networkLimit),this.stopWatching=null,this.notifier=null,this.call=null,this.callStarting=!1,this.ringing=null,this.listeners=new Map}init(e={}){if(this.started)return console.warn("[BugCatch] init() called twice — ignoring the second call."),this;if(this.config={...Ke,...e},!this.config.apiKey)throw new Error("[BugCatch] apiKey is required.");if(!this.config.projectId)throw new Error("[BugCatch] projectId is required.");return this.consoleBuffer=je(this.config.consoleLimit),this.networkBuffer=je(this.config.networkLimit),this.config.captureConsole&&this.teardown.push($n(this.consoleBuffer)),this.config.captureNetwork&&this.teardown.push(Bn(this.networkBuffer)),this.config.captureErrors&&this.teardown.push(Mn(this.consoleBuffer)),this.config.widget&&this.teardown.push(vo(this)),this.config.updates&&ae()&&(this.startWatching(),queueMicrotask(()=>N(this,A,be).call(this))),this.started=!0,this}startWatching(){if(this.stopWatching)return this;this.stopWatching=oo({apiBase:this.config.apiBase,onUpdate:n=>{if(n!=null&&n.incomingCall){N(this,A,wn).call(this,n);return}if(n!=null&&n.callWithdrawn){N(this,A,Je).call(this,n.callWithdrawn.callId);return}const o=Vn(n);typeof this.config.onUpdate=="function"&&this.config.onUpdate(o),this.emit("update",o),N(this,A,be).call(this)}}),this.teardown.push(()=>{var n;return(n=this.stopWatching)==null?void 0:n.call(this)}),this.teardown.push(()=>N(this,A,Je).call(this));const e=n=>{(n.key===qe||n.key===null)&&N(this,A,be).call(this)};return window.addEventListener("storage",e),this.teardown.push(()=>window.removeEventListener("storage",e)),this}on(e,n){if(typeof n!="function")return()=>{};const o=this.listeners.get(e)||new Set;return o.add(n),this.listeners.set(e,o),()=>this.off(e,n)}off(e,n){var o;return(o=this.listeners.get(e))==null||o.delete(n),this}emit(e,n){for(const o of this.listeners.get(e)||[])try{o(n)}catch(a){console.error("[BugCatch] an update listener threw:",a)}}unreadUpdates(){return Le()}markUpdateRead(e){return Pn(e),N(this,A,be).call(this),this}markReportRead(e){return Kn(e),N(this,A,be).call(this),this}markUpdatesRead(){return Qt(),N(this,A,be).call(this),this}myReport(e){return Fn({apiBase:this.config.apiBase,bugId:e})}replyToReport(e,{body:n,attachments:o}={}){return Rt({apiBase:this.config.apiBase,bugId:e,body:n,attachments:o})}async canCallNow(){return!!await this.callKind()}async callKind(){var e;return!this.config.voice||!((e=this.config.user)!=null&&e.email)?null:this.kind!==void 0?this.kind:(this.kind=await Yn({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId}),this.kind)}canCall(e){return this.config.voice?Xn({apiBase:this.config.apiBase,bugId:e}):Promise.resolve(!1)}async startCall(e){if(!this.config.voice)throw new Error("[BugCatch] calling is switched off.");if(this.call||this.callStarting)throw new Error("[BugCatch] a call is already running.");this.callStarting=!0;try{return await N(this,A,bn).call(this,e)}finally{this.callStarting=!1}}async startLiveCall(e){if(!this.config.voice)throw new Error("[BugCatch] calling is switched off.");if(!e)throw new Error("[BugCatch] startLiveCall needs the id of a filed report.");if(!Ve())throw new Error("[BugCatch] this browser cannot make live calls.");if(this.call||this.callStarting)throw new Error("[BugCatch] a call is already running.");this.callStarting=!0;try{return await N(this,A,kt).call(this,e)}finally{this.callStarting=!1}}canCallTeam(e){return this.config.voice?Gn({apiBase:this.config.apiBase,bugId:e}):Promise.resolve(!1)}endCall(){var e;return(e=this.call)==null||e.hangUp(),this.call=null,this}myReports(){return Wn({apiBase:this.config.apiBase})}hasReports(){return!!ae()}forgetReports(){var e;return(e=this.stopWatching)==null||e.call(this),this.stopWatching=null,this.endCall(),ue(),N(this,A,be).call(this),this}identify(e){return this.config.user=e,this}async uploadAttachment(e,n,o){if(!this.started)throw new Error("[BugCatch] call init() first.");return qn({apiBase:this.config.apiBase,apiKey:this.config.apiKey,blob:e,mime:n||(e==null?void 0:e.type),onProgress:o})}async report({title:e,description:n="",priority:o="MEDIUM",metadata:a={},attachments:r=[]}={}){if(!this.started)throw new Error("[BugCatch] call init() first.");if(!e||!e.trim())throw new Error("[BugCatch] a title is required.");const s=(r||[]).filter(d=>(d==null?void 0:d.storageKey)&&Oe(d.mime,ze)).map(d=>({type:d.type==="VIDEO"?"VIDEO":"SCREENSHOT",storageKey:d.storageKey,mime:Oe(d.mime,ze)}));let l=await N(this,A,vt).call(this,{title:e.trim(),description:n,priority:o,metadata:a,media:s});if(typeof this.config.beforeSend=="function"){const d=this.config.beforeSend(l);if(d===!1)return{cancelled:!0};d&&typeof d=="object"&&(l=d)}const u=pe(this.config.projectId),g=await fetch(`${this.config.apiBase}/ingest/bugs`,{method:"POST",headers:{"Content-Type":"application/json","X-BugCatch-Key":this.config.apiKey,...u!=null&&u.token?{"X-BugCatch-Session":u.token}:{}},body:JSON.stringify(l)});if(g.status===401&&u&&ct(this.config.projectId),!g.ok){let d="";try{const w=await g.json();d=Array.isArray(w.message)?w.message.join(", "):w.message||""}catch{}throw new Error(`[BugCatch] report failed (HTTP ${g.status})${d?": "+d:""}`)}const b=await g.json();return b.watchToken&&(nt(b.watchToken),this.config.updates&&this.startWatching()),b}loginState(){var e;return Bo({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,token:(e=pe(this.config.projectId))==null?void 0:e.token})}signedInAs(){const e=pe(this.config.projectId);return e?{email:e.email,name:e.name}:null}signIn(e,{name:n}={}){var o;return Mo({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,email:e,name:n??((o=this.config.user)==null?void 0:o.name)??null})}async confirmSignIn(e,n){var a;const o=await Lo({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,email:e,code:n});return(a=this.config.user)!=null&&a.email||(this.config.user={email:o.email,name:o.name||void 0}),this.emit("signin",{email:o.email,name:o.name}),{email:o.email,name:o.name}}async signOut(){var o;const e=pe(this.config.projectId),n=await Io({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,token:e==null?void 0:e.token});return e!=null&&e.email&&((o=this.config.user)==null?void 0:o.email)===e.email&&(this.config.user=null),this.emit("signout",{email:(e==null?void 0:e.email)||null}),n}knownIssues(){var e;return uo({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,sessionToken:(e=pe(this.config.projectId))==null?void 0:e.token})}matchKnownIssues(e,{signal:n}={}){var o;return po({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,q:e||"",sessionToken:(o=pe(this.config.projectId))==null?void 0:o.token,signal:n})}async meToo(e,{email:n,name:o}={}){var s,l;const a=pe(this.config.projectId),r=await Bt();return ho({apiBase:this.config.apiBase,apiKey:this.config.apiKey,projectId:this.config.projectId,bugId:e,reporterEmail:n||((s=this.config.user)==null?void 0:s.email),reporterName:o||((l=this.config.user)==null?void 0:l.name),metadata:{...r,user:this.config.user||void 0,sdk:tn},logs:{console:{entries:this.consoleBuffer.all()},network:{entries:this.networkBuffer.all()}},sessionToken:a==null?void 0:a.token})}snapshot(){return{console:this.consoleBuffer.all(),network:this.networkBuffer.all(),environment:Mt()}}clear(){this.consoleBuffer.clear(),this.networkBuffer.clear()}destroy(){this.endCall(),this.teardown.forEach(e=>{try{e()}catch{}}),this.teardown=[],this.notifier=null,this.started=!1}}A=new WeakSet,be=function(){const e=Le();if(this.emit("unread",{count:e.length,updates:e}),!!this.config.updateToast){if(!this.notifier){if(!e.length)return;this.notifier=lo({accent:this.config.accent,position:this.config.widgetPosition,onRead:n=>this.markUpdateRead(n),onReadAll:()=>this.markUpdatesRead(),onCall:this.config.voice?n=>{this.markUpdateRead(n.id),this.startCall(n.bugId)}:null}),this.teardown.push(()=>{var n;return(n=this.notifier)==null?void 0:n.destroy()})}this.notifier.render(e)}},vt=async function({title:e,description:n="",priority:o="MEDIUM",metadata:a={},media:r=[]}){var s,l;return{projectId:this.config.projectId,...e?{title:e}:{},description:n,priority:o,url:window.location.href,reporterEmail:(s=this.config.user)==null?void 0:s.email,reporterName:(l=this.config.user)==null?void 0:l.name,metadata:{...await Bt(),...a,user:this.config.user||void 0,sdk:tn},logs:{console:{entries:this.consoleBuffer.all()},network:{entries:this.networkBuffer.all()}},...r.length?{attachments:r}:{}}},xt=async function(){const e=pe(this.config.projectId);let n;try{n=await Zn({apiBase:this.config.apiBase,apiKey:this.config.apiKey,token:e==null?void 0:e.token,payload:await N(this,A,vt).call(this,{})})}catch(o){throw o.status===401&&e&&ct(this.config.projectId),o}return n.watchToken&&(nt(n.watchToken),this.config.updates&&this.startWatching()),n},bn=async function(e){if(await this.callKind()==="live")return N(this,A,kt).call(this,e);let n,o=null;if(e)({realtime_url:n}=await Jn({apiBase:this.config.apiBase,bugId:e}));else if(o=await N(this,A,xt).call(this),e=o.id,n=o.realtime_url,!n){const r=new Error("[BugCatch] the report was filed, but no call could be started.");throw r.filed=o,r}const a=await jo({realtimeUrl:n,onState:(r,s)=>{(r==="ended"||r==="failed")&&(this.call=null),typeof this.config.onCallState=="function"&&this.config.onCallState(r,{bugId:e,...s}),this.emit("call",{bugId:e,state:r,...s})},onTranscript:r=>{typeof this.config.onCallTranscript=="function"&&this.config.onCallTranscript({bugId:e,...r}),this.emit("call-transcript",{bugId:e,...r})}});return this.call=a,Object.assign(a,{bugId:e,filed:o})},kt=async function(e){if(!Ve())throw new Error("[BugCatch] this browser cannot make live calls.");const n=await pt();let o,a=null;try{if(e)o=await eo({apiBase:this.config.apiBase,bugId:e});else if(a=await N(this,A,xt).call(this),e=a.id,o=a.live_call,!(o!=null&&o.realtime_url)){const s=new Error("[BugCatch] the report was filed, but the team could not be rung.");throw s.filed=a,s}}catch(s){for(const l of n.getTracks())l.stop();throw s}const r=await Zt({realtimeUrl:o.realtime_url,stream:n,ringsUntil:o.rings_until,onState:(s,l)=>{(s==="ended"||s==="failed"||s==="missed")&&(this.call=null),s==="missed"&&to({apiBase:this.config.apiBase,bugId:e,callId:o.call_id}),typeof this.config.onCallState=="function"&&this.config.onCallState(s,{bugId:e,live:!0,...l}),this.emit("call",{bugId:e,live:!0,state:s,...l})}});return this.call=r,Object.assign(r,{bugId:e,callId:o.call_id,live:!0,filed:a})},wn=function(e){var a;const n=e.incomingCall;if(!this.config.voice||!Ve()||((a=this.ringing)==null?void 0:a.callId)===n.callId||this.call||this.callStarting)return;N(this,A,Je).call(this);const o=Po({call:n,accent:this.config.accent,position:this.config.widgetPosition,onAnswer:()=>{N(this,A,yn).call(this,e.bugId,n)},onDecline:()=>{this.ringing=null,jt({apiBase:this.config.apiBase,bugId:e.bugId,callId:n.callId}),this.emit("incoming-call",{bugId:e.bugId,callId:n.callId,state:"declined"})},onExpire:()=>{this.ringing=null,this.emit("incoming-call",{bugId:e.bugId,callId:n.callId,state:"missed"})}});if(this.ringing={callId:n.callId,dismiss:o},typeof this.config.onIncomingCall=="function")try{this.config.onIncomingCall({bugId:e.bugId,...n})}catch{}this.emit("incoming-call",{bugId:e.bugId,callId:n.callId,state:"ringing"})},Je=function(e){if(this.ringing&&!(e&&this.ringing.callId!==e)){try{this.ringing.dismiss()}catch{}this.ringing=null}},yn=async function(e,n){if(this.ringing=null,this.call||this.callStarting)return;this.callStarting=!0;let o;try{o=await pt()}catch(r){this.callStarting=!1,jt({apiBase:this.config.apiBase,bugId:e,callId:n.callId}),this.emit("incoming-call",{bugId:e,callId:n.callId,state:"failed",error:r});return}let a;try{a=await no({apiBase:this.config.apiBase,bugId:e,callId:n.callId})}catch(r){for(const s of o.getTracks())s.stop();this.callStarting=!1,this.emit("incoming-call",{bugId:e,callId:n.callId,state:"missed",error:r});return}try{const r=await Zt({realtimeUrl:a.realtime_url,stream:o,onState:(s,l)=>{(s==="ended"||s==="failed"||s==="missed")&&(this.call=null),typeof this.config.onCallState=="function"&&this.config.onCallState(s,{bugId:e,live:!0,incoming:!0,...l}),this.emit("call",{bugId:e,live:!0,incoming:!0,state:s,...l})}});this.call=r,this.emit("incoming-call",{bugId:e,callId:n.callId,state:"answered"})}catch(r){for(const s of o.getTracks())s.stop();this.emit("incoming-call",{bugId:e,callId:n.callId,state:"failed",error:r})}finally{this.callStarting=!1}};const on=new nn;return on.Client=nn,on});
|
|
952
|
+
//# sourceMappingURL=bugcatch.umd.js.map
|