@bugjar/reporter 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -173,4 +173,4 @@ PERFORMANCE OF THIS SOFTWARE.
173
173
  margin-top: 8px;
174
174
  border-radius: 2px;
175
175
  }
176
- `}),m.jsxs("div",{style:{background:"#111827",color:"white",padding:"12px 16px",borderRadius:10,fontFamily:"system-ui, -apple-system, sans-serif",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.3)",minWidth:240,maxWidth:320},children:[m.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10},children:[m.jsx("span",{className:"bugjar-spinner"}),m.jsx("span",{children:i[e]})]}),m.jsx("div",{className:"bugjar-upload-bar"})]})]})}function cw({color:r,onDismiss:e}){return m.jsxs("div",{style:{background:"white",padding:16,borderRadius:8,border:`2px solid ${r}`,fontFamily:"system-ui",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.2)",maxWidth:320},children:[m.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"Report sent — thank you!"}),m.jsx("div",{style:{color:"#475569",fontSize:12,lineHeight:1.5},children:"The team has been notified and will follow up by email if they need more from you."}),m.jsx("div",{style:{marginTop:12,textAlign:"right"},children:m.jsx(xs,{onClick:e,children:"Close"})})]})}function uw({error:r,onDismiss:e,onDownload:t}){return m.jsxs("div",{style:{background:"white",padding:16,borderRadius:8,border:"2px solid #ef4444",fontFamily:"system-ui",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.2)",maxWidth:360},children:[m.jsx("div",{style:{fontWeight:600,marginBottom:6,color:"#b91c1c"},children:"Upload failed"}),m.jsx("div",{style:{color:"#4b5563",marginBottom:4,fontFamily:"ui-monospace, monospace",fontSize:12,wordBreak:"break-word"},children:r}),m.jsx("div",{style:{color:"#9ca3af",fontSize:11,lineHeight:1.5,marginTop:6},children:"Your report is still in memory. You can save it as a file and email it to support, or dismiss this toast to discard."}),m.jsxs("div",{style:{marginTop:12,display:"flex",gap:8,justifyContent:"flex-end"},children:[m.jsx(xs,{onClick:e,children:"Discard"}),m.jsx(xs,{onClick:t,children:"Download as file"})]})]})}function xs({onClick:r,children:e}){return m.jsx("button",{onClick:r,style:{background:"white",color:"#111827",border:"1px solid #d1d5db",borderRadius:6,padding:"10px 18px",fontWeight:500,fontSize:13,cursor:"pointer"},children:e})}const hw=typeof window<"u"?window.fetch.bind(window):fetch;async function fw(r){const{apiUrl:e,projectKey:t,userToken:i,captureToken:s,report:n,attachments:o}=r,a=n.rrwebEvents.length>0,l={replay:a?{sizeBytes:yw(n.rrwebEvents),durationMs:n.durationMs}:null,audio:n.audioBlob?{sizeBytes:n.audioBlob.size}:null,screenshot:{sizeBytes:n.screenshotBlob.size},attachments:o.map(p=>({filename:p.name,sizeBytes:p.size,mimeType:p.type}))},c=await Es(`${e}/upload-url`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({projectKey:t,manifest:l,...i?{userToken:i}:{},...s?{captureToken:s}:{}})});if(!c.ok)throw new Error(`upload-url failed: ${c.status} ${(await c.text()).slice(0,200)}`);const{reportId:u,finalizeToken:h,uploads:f}=await c.json(),d=[];f.replay&&a&&d.push(Cr(f.replay.url,new Blob([JSON.stringify(n.rrwebEvents)],{type:"application/json"}))),f.audio&&n.audioBlob&&d.push(Cr(f.audio.url,n.audioBlob)),d.push(Cr(f.screenshot.url,n.screenshotBlob));for(let p=0;p<o.length;p++){const y=o[p],b=f.attachments[p];if(!y||!b)throw new Error(`Server returned ${f.attachments.length} presigns for ${o.length} attachments`);d.push(Cr(b.url,y))}await Promise.all(d);const g=await Es(`${e}/reports/${u}/finalize`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({projectKey:t,finalizeToken:h,description:n.description,durationMs:n.durationMs,consoleLogs:n.consoleLogs,networkEvents:n.networkEvents,annotations:n.annotations,url:n.url,userAgent:n.userAgent,viewport:n.viewport,redactionSummary:n.redactionSummary,strictMode:n.strictMode,attachmentIds:f.attachments.map(p=>p.id)})});if(!g.ok)throw new Error(`finalize failed: ${g.status}`);const{dashboardUrl:w}=await g.json();return{reportId:u,dashboardUrl:w}}async function Cr(r,e){const t=await Es(r,{method:"PUT",headers:{"Content-Type":e.type||"application/octet-stream"},body:e});if(!t.ok){const i=await t.text().catch(()=>"");throw new Error(`PUT ${Yr(r)} failed: ${t.status} ${i.slice(0,160)}`)}}const xr=[200,800,3200],Ho=3e4;async function Es(r,e){let t=null;for(let i=0;i<=xr.length;i++){try{const o=await gw(r,e);if(!dw(o.status)||i===xr.length)return o}catch(o){if(t=o,i===xr.length)throw o}const s=xr[i],n=(Math.random()-.5)*.5*s;await pw(s+n)}throw t instanceof Error?t:new Error(`retryingFetch exhausted attempts for ${Yr(r)}`)}function dw(r){return r===408||r===502||r===503||r===504}function pw(r){return new Promise(e=>setTimeout(e,Math.max(0,r)))}function mw(r,e){return!r||typeof AbortSignal.any!="function"?e:AbortSignal.any([r,e])}async function gw(r,e){const t=AbortSignal.timeout(Ho),i=mw(e.signal,t);try{return await hw(r,{...e,signal:i})}catch(s){const n=(e.method??"GET").toUpperCase();if(t.aborted&&!e.signal?.aborted)throw new Error(`${n} ${Yr(r)} timed out after ${Ho/1e3}s`);const a=s,l=a?.cause instanceof Error?`: ${a.cause.message}`:a?.cause?`: ${String(a.cause)}`:"",c=s instanceof Error?s.message:String(s);throw new Error(`${n} ${Yr(r)} failed: ${c}${l}`)}}function Yr(r){try{const e=new URL(r),t=e.pathname.length>60?`${e.pathname.slice(0,57)}…`:e.pathname;return`${e.host}${t}`}catch{return r.slice(0,80)}}function yw(r){try{return new Blob([JSON.stringify(r)]).size}catch{return 0}}async function ww(r,e){const t={reportId:r.reportId,createdAt:r.createdAt,durationMs:r.durationMs,description:r.description,url:r.url,userAgent:r.userAgent,viewport:r.viewport,consoleLogs:r.consoleLogs,networkEvents:r.networkEvents,annotations:r.annotations,redactionSummary:r.redactionSummary,strictMode:r.strictMode,attachmentNames:e.map(n=>n.name)},i=new Blob([JSON.stringify({manifest:t,rrwebEvents:r.rrwebEvents},null,2)],{type:"application/json"}),s=document.createElement("a");s.href=URL.createObjectURL(i),s.download=`bugjar-report-${r.reportId}.json`,s.click(),URL.revokeObjectURL(s.href)}class bw{constructor(){M(this,"config",null);M(this,"root",null);M(this,"host",null);M(this,"markupHost",null);M(this,"markupController",null);M(this,"consoleCapture",null);M(this,"networkCapture",null);M(this,"recorder",null);M(this,"view","idle");M(this,"isRecording",!1);M(this,"elapsedMs",0);M(this,"capturedReport",null);M(this,"preview",null);M(this,"previewObjectUrls",[]);M(this,"error",null);M(this,"failedAttachments",null);M(this,"errorObserved",!1);M(this,"sessionId",null);M(this,"persistedActiveMs",0);M(this,"resumingToast",null);M(this,"lastRecording",null);M(this,"finalizingInFlight",!1)}init(e){if(this.config)return this.publicApi();this.config=vw(e),this.host=document.createElement("div"),this.host.setAttribute("data-bugjar-block","true"),this.host.id="bugjar-root",document.body.appendChild(this.host),this.root=Ii(this.host);const t=()=>this.onErrorObserved();this.consoleCapture=Tc(this.config.capture.consoleBufferSize,t),this.networkCapture=$c(this.config.capture.networkBufferSize,t);const i=Sw(this.config.captureTokenParam);return i&&this.applyCaptureToken(i),this.render(),this.attemptRecovery(),this.publicApi()}async attemptRecovery(){if(!this.config)return;const e=this.config.projectKey,t=Zc(e);if(!t)return;if(!Xc(t)){We(e);try{await Yt(t.sessionId)}catch{}return}let i=null;try{i=await Uc(t.sessionId)}catch{We(e);return}if(!i){We(e);return}const s=Math.max(0,i.maxDurationMs-i.activeMs);if(s<500){We(e);try{await Yt(i.sessionId)}catch{}return}try{this.consoleCapture.hydrate(i.consoleEntries),this.networkCapture.hydrate(i.networkEvents)}catch(o){this.config.onError?.(o instanceof Error?o:new Error(String(o)))}this.sessionId=i.sessionId,this.persistedActiveMs=i.activeMs,this.view="active",this.isRecording=!0,this.elapsedMs=i.activeMs,this.resumingToast={remainingMs:s},this.ensureMarkupLayer(),this.render();const n=new pn({sessionId:i.sessionId,projectKey:e,persistedActiveMs:i.activeMs,strictMode:this.config.strictMode,disabledCategories:this.config.disabledCategories,captureMicrophone:this.config.capture.captureMicrophone,maxDurationSec:this.config.capture.maxDurationSec,consoleCapture:this.consoleCapture,networkCapture:this.networkCapture,onTick:o=>{this.elapsedMs=o,this.render()},onStop:o=>{this.lastRecording={events:o.events,audioBlob:o.audioBlob,durationMs:o.durationMs},this.isRecording=!1,this.recorder=null,this.render()}});n.hydrateEvents(i.events),n.hydrateAudioChunks(i.audioChunks),this.recorder=n;try{await n.start()}catch(o){this.recorder=null,this.sessionId=null,this.persistedActiveMs=0,this.isRecording=!1,this.view="idle",this.resumingToast=null,We(e);try{await Yt(i.sessionId)}catch{}this.config.onError?.(o instanceof Error?o:new Error(String(o))),this.render();return}window.setTimeout(()=>{this.resumingToast=null,this.render()},3500)}onErrorObserved(){this.errorObserved||(this.errorObserved=!0,this.render())}publicApi(){return{open:()=>this.open(),close:()=>this.close()}}async applyCaptureToken(e){if(this.config&&this.config.captureToken!==e)try{const t=await fetch(`${this.config.apiUrl}/capture-invitations/${encodeURIComponent(e)}/scope`,{method:"GET",headers:{Accept:"application/json"}});if(!t.ok){const n=await t.json().catch(()=>({}));throw new Error(`capture-token scope fetch failed (${t.status}): ${n.error?.code??"unknown"}`)}const{disableCategories:i}=await t.json(),s=eu(i);this.config.captureToken=e,this.config.disabledCategories=s,this.view==="idle"&&this.open()}catch(t){this.config.onError?.(t instanceof Error?t:new Error(String(t)))}}ensureMarkupLayer(){if(this.markupController)return this.markupController;const e=document.createElement("div");return e.id="bugjar-markup-layer",document.body.appendChild(e),this.markupHost=e,this.markupController=new Pc(e),this.markupController}teardownMarkupLayer(){this.markupController&&(this.markupController.destroy(),this.markupController=null),this.markupHost&&(this.markupHost.remove(),this.markupHost=null)}open(){if(!this.config)throw new Error("BugJar.init() must be called first");this.view="active",this.isRecording=!1,this.elapsedMs=0,this.lastRecording=null,this.ensureMarkupLayer(),this.render()}close(){this.view="idle",this.isRecording=!1,this.elapsedMs=0,this.capturedReport=null,this.preview=null,this.error=null,this.lastRecording=null,this.resumingToast=null,this.releasePreviewObjectUrls(),this.recorder&&(this.recorder.stop().catch(()=>{}),this.recorder=null),this.discardPersistedSession(),this.teardownMarkupLayer(),this.render()}discardPersistedSession(){if(!this.config||!this.sessionId)return;const e=this.config.projectKey,t=this.sessionId;We(e),Yt(t).catch(()=>{}),this.sessionId=null,this.persistedActiveMs=0}releasePreviewObjectUrls(){for(const e of this.previewObjectUrls)try{URL.revokeObjectURL(e)}catch{}this.previewObjectUrls=[]}render(){if(!this.root||!this.config)return;const{showTrigger:e,showOnError:t}=this.config.ui,i=e&&(this.errorObserved||!t);this.root.render(m.jsx(oe.StrictMode,{children:m.jsx(rw,{projectKey:this.config.projectKey,primaryColor:this.config.ui.primaryColor,position:this.config.ui.position,maxDurationSec:this.config.capture.maxDurationSec,strictMode:this.config.strictMode,triggerVisible:i,triggerLabel:this.config.ui.triggerLabel,view:this.view,isRecording:this.isRecording,hasRecording:this.lastRecording!==null,elapsedMs:this.elapsedMs,preview:this.preview,error:this.error,resumingToast:this.resumingToast,onOpenActive:()=>this.open(),onCancel:()=>this.close(),onStartRecording:async()=>{await this.startRecording()},onStopRecording:async()=>{await this.stopRecording()},onToggleMicMute:s=>this.recorder?.toggleMic(s),onFinalize:async()=>{await this.finalizeReport()},onBackToMarkup:()=>this.backToMarkup(),onSendReport:async s=>{await this.sendReport(s)},onDownloadFailed:async()=>{await this.downloadOnDemand()},onSelectTool:s=>this.markupController?.setTool(s),onSelectColor:s=>this.markupController?.setColor(s),onUndoMarkup:()=>this.markupController?.undo()})}))}async startRecording(){if(!this.config||this.isRecording||this.recorder)return;this.discardPersistedSession(),this.isRecording=!0,this.elapsedMs=0,this.lastRecording=null,this.persistedActiveMs=0;const e=hn();this.sessionId=e;const t={sessionId:e,projectKey:this.config.projectKey,startedAt:Date.now(),activeMs:0,maxDurationMs:this.config.capture.maxDurationSec*1e3,events:[],audioChunks:[],consoleEntries:[],networkEvents:[],lastSeenAt:Date.now()};try{await zc(t)}catch(i){this.config.onError?.(i instanceof Error?i:new Error(String(i)))}this.render(),this.recorder=new pn({sessionId:e,projectKey:this.config.projectKey,persistedActiveMs:0,strictMode:this.config.strictMode,disabledCategories:this.config.disabledCategories,captureMicrophone:this.config.capture.captureMicrophone,maxDurationSec:this.config.capture.maxDurationSec,consoleCapture:this.consoleCapture,networkCapture:this.networkCapture,onTick:i=>{this.elapsedMs=i,this.render()},onStop:i=>{this.lastRecording={events:i.events,audioBlob:i.audioBlob,durationMs:i.durationMs},this.isRecording=!1,this.recorder=null,this.render()}});try{await this.recorder.start()}catch(i){await this.recorder.stop().catch(()=>{}),this.recorder=null,this.isRecording=!1,this.lastRecording=null,this.discardPersistedSession(),this.error=i instanceof Error?i.message:String(i),this.config.onError?.(i instanceof Error?i:new Error(String(i))),this.render()}}async stopRecording(){this.recorder&&await this.recorder.stop()}async finalizeReport(){if(this.config&&!this.finalizingInFlight){this.finalizingInFlight=!0;try{this.recorder&&await this.recorder.stop(),this.discardPersistedSession();const e=Jo(),t={disabledCategories:this.config.disabledCategories},i=this.lastRecording,s=i?nu(i.events,e,t):[],n=i?this.consoleCapture.snapshot().map(g=>xt(g,e,t)):[],o=i?this.networkCapture.snapshot().map(g=>{const w=xt(g,e,t);return{...w,url:Di(w.url,e)}}):[],a=await gh(),l={width:window.innerWidth,height:window.innerHeight},c=i?.audioBlob??null,u=i?.durationMs??0,h={reportId:hn(),createdAt:Date.now(),durationMs:u,rrwebEvents:s,audioBlob:c,screenshotBlob:a,consoleLogs:n,networkEvents:o,annotations:[],description:"",url:Di(location.href,e),userAgent:navigator.userAgent,viewport:l,redactionSummary:e,strictMode:this.config.strictMode};this.capturedReport=h,this.releasePreviewObjectUrls();const f=URL.createObjectURL(a),d=c?URL.createObjectURL(c):null;this.previewObjectUrls=[f,d].filter(g=>g!==null),this.preview={hasReplay:s.length>0,durationMs:u,replayEvents:s,replayEventCount:s.length,hasAudio:c!==null,audioBlobUrl:d,consoleLogs:n,networkEvents:o,redactionSummary:e,screenshotUrl:f,capturedViewport:l,pageUrl:location.href,userAgent:navigator.userAgent,reportUrl:null},this.view="preview",this.render()}finally{this.finalizingInFlight=!1}}}backToMarkup(){this.preview=null,this.capturedReport=null,this.view="active",this.render()}async sendReport(e){if(!(!this.capturedReport||!this.config)){this.capturedReport.description=e.description,this.capturedReport.annotations=e.annotations,this.teardownMarkupLayer(),this.view="uploading",this.render();try{const{reportId:t,dashboardUrl:i}=await fw({apiUrl:this.config.apiUrl,projectKey:this.config.projectKey,userToken:this.config.userToken,captureToken:this.config.captureToken,report:this.capturedReport,attachments:e.attachments});this.preview&&(this.preview.reportUrl=i),this.view="success",this.config.onReportSubmitted?.(t),this.render()}catch(t){this.error=t instanceof Error?t.message:String(t),this.failedAttachments=e.attachments,this.view="failed",this.config.onError?.(t instanceof Error?t:new Error(String(t))),this.render()}}}async downloadOnDemand(){if(this.capturedReport)try{await ww(this.capturedReport,this.failedAttachments??[])}catch(e){this.config?.onError?.(e instanceof Error?e:new Error(String(e)))}}}function vw(r){return{projectKey:r.projectKey,apiUrl:Ch,userToken:r.userToken??null,user:r.user??{id:""},strictMode:r.strictMode??!1,capture:{...vh,...r.capture??{}},ui:{...Sh,...r.ui??{}},onReportSubmitted:r.onReportSubmitted??(()=>{}),onError:r.onError??(()=>{}),captureTokenParam:r.captureTokenParam??"bugjar_capture",captureToken:null,disabledCategories:new Set}}function Sw(r){if(typeof window>"u"||!window.location)return null;try{const t=new URLSearchParams(window.location.search).get(r);return t&&t.length>0?t:null}catch{return null}}const yc=new bw,_i=yc,wc={init:r=>_i.init(r),open:()=>_i.open(),close:()=>_i.close()};typeof window<"u"&&(window.BugJar=wc);exports.BugJar=wc;exports._client=yc;
176
+ `}),m.jsxs("div",{style:{background:"#111827",color:"white",padding:"12px 16px",borderRadius:10,fontFamily:"system-ui, -apple-system, sans-serif",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.3)",minWidth:240,maxWidth:320},children:[m.jsxs("div",{style:{display:"flex",alignItems:"center",gap:10},children:[m.jsx("span",{className:"bugjar-spinner"}),m.jsx("span",{children:i[e]})]}),m.jsx("div",{className:"bugjar-upload-bar"})]})]})}function cw({color:r,onDismiss:e}){return m.jsxs("div",{style:{background:"white",padding:16,borderRadius:8,border:`2px solid ${r}`,fontFamily:"system-ui",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.2)",maxWidth:320},children:[m.jsx("div",{style:{fontWeight:600,marginBottom:6},children:"Report sent — thank you!"}),m.jsx("div",{style:{color:"#475569",fontSize:12,lineHeight:1.5},children:"The team has been notified and will follow up by email if they need more from you."}),m.jsx("div",{style:{marginTop:12,textAlign:"right"},children:m.jsx(xs,{onClick:e,children:"Close"})})]})}function uw({error:r,onDismiss:e,onDownload:t}){return m.jsxs("div",{style:{background:"white",padding:16,borderRadius:8,border:"2px solid #ef4444",fontFamily:"system-ui",fontSize:13,boxShadow:"0 10px 30px rgba(0,0,0,0.2)",maxWidth:360},children:[m.jsx("div",{style:{fontWeight:600,marginBottom:6,color:"#b91c1c"},children:"Upload failed"}),m.jsx("div",{style:{color:"#4b5563",marginBottom:4,fontFamily:"ui-monospace, monospace",fontSize:12,wordBreak:"break-word"},children:r}),m.jsx("div",{style:{color:"#9ca3af",fontSize:11,lineHeight:1.5,marginTop:6},children:"Your report is still in memory. You can save it as a file and email it to support, or dismiss this toast to discard."}),m.jsxs("div",{style:{marginTop:12,display:"flex",gap:8,justifyContent:"flex-end"},children:[m.jsx(xs,{onClick:e,children:"Discard"}),m.jsx(xs,{onClick:t,children:"Download as file"})]})]})}function xs({onClick:r,children:e}){return m.jsx("button",{onClick:r,style:{background:"white",color:"#111827",border:"1px solid #d1d5db",borderRadius:6,padding:"10px 18px",fontWeight:500,fontSize:13,cursor:"pointer"},children:e})}const hw=typeof window<"u"?window.fetch.bind(window):fetch;async function fw(r){const{apiUrl:e,projectKey:t,userToken:i,captureToken:s,report:n,attachments:o}=r,a=n.rrwebEvents.length>0,l={replay:a?{sizeBytes:yw(n.rrwebEvents),durationMs:n.durationMs}:null,audio:n.audioBlob?{sizeBytes:n.audioBlob.size}:null,screenshot:{sizeBytes:n.screenshotBlob.size},attachments:o.map(p=>({filename:p.name,sizeBytes:p.size,mimeType:p.type}))},c=await Es(`${e}/upload-url`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({projectKey:t,manifest:l,...i?{userToken:i}:{},...s?{captureToken:s}:{}})});if(!c.ok)throw new Error(`upload-url failed: ${c.status} ${(await c.text()).slice(0,200)}`);const{reportId:u,finalizeToken:h,uploads:f}=await c.json(),d=[];f.replay&&a&&d.push(Cr(f.replay.url,new Blob([JSON.stringify(n.rrwebEvents)],{type:"application/json"}))),f.audio&&n.audioBlob&&d.push(Cr(f.audio.url,n.audioBlob)),d.push(Cr(f.screenshot.url,n.screenshotBlob));for(let p=0;p<o.length;p++){const y=o[p],b=f.attachments[p];if(!y||!b)throw new Error(`Server returned ${f.attachments.length} presigns for ${o.length} attachments`);d.push(Cr(b.url,y))}await Promise.all(d);const g=await Es(`${e}/reports/${u}/finalize`,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({projectKey:t,finalizeToken:h,description:n.description,durationMs:n.durationMs,consoleLogs:n.consoleLogs,networkEvents:n.networkEvents,annotations:n.annotations,url:n.url,userAgent:n.userAgent,viewport:n.viewport,redactionSummary:n.redactionSummary,strictMode:n.strictMode,attachmentIds:f.attachments.map(p=>p.id)})});if(!g.ok)throw new Error(`finalize failed: ${g.status}`);const{dashboardUrl:w}=await g.json();return{reportId:u,dashboardUrl:w}}async function Cr(r,e){const t=await Es(r,{method:"PUT",headers:{"Content-Type":e.type||"application/octet-stream"},body:e});if(!t.ok){const i=await t.text().catch(()=>"");throw new Error(`PUT ${Yr(r)} failed: ${t.status} ${i.slice(0,160)}`)}}const xr=[200,800,3200],Ho=3e4;async function Es(r,e){let t=null;for(let i=0;i<=xr.length;i++){try{const o=await gw(r,e);if(!dw(o.status)||i===xr.length)return o}catch(o){if(t=o,i===xr.length)throw o}const s=xr[i],n=(Math.random()-.5)*.5*s;await pw(s+n)}throw t instanceof Error?t:new Error(`retryingFetch exhausted attempts for ${Yr(r)}`)}function dw(r){return r===408||r===502||r===503||r===504}function pw(r){return new Promise(e=>setTimeout(e,Math.max(0,r)))}function mw(r,e){return!r||typeof AbortSignal.any!="function"?e:AbortSignal.any([r,e])}async function gw(r,e){const t=AbortSignal.timeout(Ho),i=mw(e.signal,t);try{return await hw(r,{...e,signal:i})}catch(s){const n=(e.method??"GET").toUpperCase();if(t.aborted&&!e.signal?.aborted)throw new Error(`${n} ${Yr(r)} timed out after ${Ho/1e3}s`);const a=s,l=a?.cause instanceof Error?`: ${a.cause.message}`:a?.cause?`: ${String(a.cause)}`:"",c=s instanceof Error?s.message:String(s);throw new Error(`${n} ${Yr(r)} failed: ${c}${l}`)}}function Yr(r){try{const e=new URL(r),t=e.pathname.length>60?`${e.pathname.slice(0,57)}…`:e.pathname;return`${e.host}${t}`}catch{return r.slice(0,80)}}function yw(r){try{return new Blob([JSON.stringify(r)]).size}catch{return 0}}async function ww(r,e){const t={reportId:r.reportId,createdAt:r.createdAt,durationMs:r.durationMs,description:r.description,url:r.url,userAgent:r.userAgent,viewport:r.viewport,consoleLogs:r.consoleLogs,networkEvents:r.networkEvents,annotations:r.annotations,redactionSummary:r.redactionSummary,strictMode:r.strictMode,attachmentNames:e.map(n=>n.name)},i=new Blob([JSON.stringify({manifest:t,rrwebEvents:r.rrwebEvents},null,2)],{type:"application/json"}),s=document.createElement("a");s.href=URL.createObjectURL(i),s.download=`bugjar-report-${r.reportId}.json`,s.click(),URL.revokeObjectURL(s.href)}class bw{constructor(){M(this,"config",null);M(this,"root",null);M(this,"host",null);M(this,"markupHost",null);M(this,"markupController",null);M(this,"consoleCapture",null);M(this,"networkCapture",null);M(this,"recorder",null);M(this,"view","idle");M(this,"isRecording",!1);M(this,"elapsedMs",0);M(this,"capturedReport",null);M(this,"preview",null);M(this,"previewObjectUrls",[]);M(this,"error",null);M(this,"failedAttachments",null);M(this,"errorObserved",!1);M(this,"sessionId",null);M(this,"persistedActiveMs",0);M(this,"resumingToast",null);M(this,"lastRecording",null);M(this,"finalizingInFlight",!1)}init(e){if(this.config)return this.publicApi();this.config=vw(e),this.host=document.createElement("div"),this.host.setAttribute("data-bugjar-block","true"),this.host.id="bugjar-root",document.body.appendChild(this.host),this.root=Ii(this.host);const t=()=>this.onErrorObserved();this.consoleCapture=Tc(this.config.capture.consoleBufferSize,t),this.networkCapture=$c(this.config.capture.networkBufferSize,t);const i=Sw(this.config.captureTokenParam);return i&&(Cw(this.config.captureTokenParam),this.applyCaptureToken(i)),this.render(),this.attemptRecovery(),this.publicApi()}async attemptRecovery(){if(!this.config)return;const e=this.config.projectKey,t=Zc(e);if(!t)return;if(!Xc(t)){We(e);try{await Yt(t.sessionId)}catch{}return}let i=null;try{i=await Uc(t.sessionId)}catch{We(e);return}if(!i){We(e);return}const s=Math.max(0,i.maxDurationMs-i.activeMs);if(s<500){We(e);try{await Yt(i.sessionId)}catch{}return}try{this.consoleCapture.hydrate(i.consoleEntries),this.networkCapture.hydrate(i.networkEvents)}catch(o){this.config.onError?.(o instanceof Error?o:new Error(String(o)))}this.sessionId=i.sessionId,this.persistedActiveMs=i.activeMs,this.view="active",this.isRecording=!0,this.elapsedMs=i.activeMs,this.resumingToast={remainingMs:s},this.ensureMarkupLayer(),this.render();const n=new pn({sessionId:i.sessionId,projectKey:e,persistedActiveMs:i.activeMs,strictMode:this.config.strictMode,disabledCategories:this.config.disabledCategories,captureMicrophone:this.config.capture.captureMicrophone,maxDurationSec:this.config.capture.maxDurationSec,consoleCapture:this.consoleCapture,networkCapture:this.networkCapture,onTick:o=>{this.elapsedMs=o,this.render()},onStop:o=>{this.lastRecording={events:o.events,audioBlob:o.audioBlob,durationMs:o.durationMs},this.isRecording=!1,this.recorder=null,this.render()}});n.hydrateEvents(i.events),n.hydrateAudioChunks(i.audioChunks),this.recorder=n;try{await n.start()}catch(o){this.recorder=null,this.sessionId=null,this.persistedActiveMs=0,this.isRecording=!1,this.view="idle",this.resumingToast=null,We(e);try{await Yt(i.sessionId)}catch{}this.config.onError?.(o instanceof Error?o:new Error(String(o))),this.render();return}window.setTimeout(()=>{this.resumingToast=null,this.render()},3500)}onErrorObserved(){this.errorObserved||(this.errorObserved=!0,this.render())}publicApi(){return{open:()=>this.open(),close:()=>this.close()}}async applyCaptureToken(e){if(this.config&&this.config.captureToken!==e)try{const t=await fetch(`${this.config.apiUrl}/capture-invitations/${encodeURIComponent(e)}/scope`,{method:"GET",headers:{Accept:"application/json"}});if(!t.ok){const n=await t.json().catch(()=>({}));throw new Error(`capture-token scope fetch failed (${t.status}): ${n.error?.code??"unknown"}`)}const{disableCategories:i}=await t.json(),s=eu(i);this.config.captureToken=e,this.config.disabledCategories=s,this.view==="idle"&&this.open()}catch(t){this.config.onError?.(t instanceof Error?t:new Error(String(t)))}}ensureMarkupLayer(){if(this.markupController)return this.markupController;const e=document.createElement("div");return e.id="bugjar-markup-layer",document.body.appendChild(e),this.markupHost=e,this.markupController=new Pc(e),this.markupController}teardownMarkupLayer(){this.markupController&&(this.markupController.destroy(),this.markupController=null),this.markupHost&&(this.markupHost.remove(),this.markupHost=null)}open(){if(!this.config)throw new Error("BugJar.init() must be called first");this.view="active",this.isRecording=!1,this.elapsedMs=0,this.lastRecording=null,this.ensureMarkupLayer(),this.render()}close(){this.view="idle",this.isRecording=!1,this.elapsedMs=0,this.capturedReport=null,this.preview=null,this.error=null,this.lastRecording=null,this.resumingToast=null,this.releasePreviewObjectUrls(),this.recorder&&(this.recorder.stop().catch(()=>{}),this.recorder=null),this.discardPersistedSession(),this.teardownMarkupLayer(),this.render()}discardPersistedSession(){if(!this.config||!this.sessionId)return;const e=this.config.projectKey,t=this.sessionId;We(e),Yt(t).catch(()=>{}),this.sessionId=null,this.persistedActiveMs=0}releasePreviewObjectUrls(){for(const e of this.previewObjectUrls)try{URL.revokeObjectURL(e)}catch{}this.previewObjectUrls=[]}render(){if(!this.root||!this.config)return;const{showTrigger:e,showOnError:t}=this.config.ui,i=e&&(this.errorObserved||!t);this.root.render(m.jsx(oe.StrictMode,{children:m.jsx(rw,{projectKey:this.config.projectKey,primaryColor:this.config.ui.primaryColor,position:this.config.ui.position,maxDurationSec:this.config.capture.maxDurationSec,strictMode:this.config.strictMode,triggerVisible:i,triggerLabel:this.config.ui.triggerLabel,view:this.view,isRecording:this.isRecording,hasRecording:this.lastRecording!==null,elapsedMs:this.elapsedMs,preview:this.preview,error:this.error,resumingToast:this.resumingToast,onOpenActive:()=>this.open(),onCancel:()=>this.close(),onStartRecording:async()=>{await this.startRecording()},onStopRecording:async()=>{await this.stopRecording()},onToggleMicMute:s=>this.recorder?.toggleMic(s),onFinalize:async()=>{await this.finalizeReport()},onBackToMarkup:()=>this.backToMarkup(),onSendReport:async s=>{await this.sendReport(s)},onDownloadFailed:async()=>{await this.downloadOnDemand()},onSelectTool:s=>this.markupController?.setTool(s),onSelectColor:s=>this.markupController?.setColor(s),onUndoMarkup:()=>this.markupController?.undo()})}))}async startRecording(){if(!this.config||this.isRecording||this.recorder)return;this.discardPersistedSession(),this.isRecording=!0,this.elapsedMs=0,this.lastRecording=null,this.persistedActiveMs=0;const e=hn();this.sessionId=e;const t={sessionId:e,projectKey:this.config.projectKey,startedAt:Date.now(),activeMs:0,maxDurationMs:this.config.capture.maxDurationSec*1e3,events:[],audioChunks:[],consoleEntries:[],networkEvents:[],lastSeenAt:Date.now()};try{await zc(t)}catch(i){this.config.onError?.(i instanceof Error?i:new Error(String(i)))}this.render(),this.recorder=new pn({sessionId:e,projectKey:this.config.projectKey,persistedActiveMs:0,strictMode:this.config.strictMode,disabledCategories:this.config.disabledCategories,captureMicrophone:this.config.capture.captureMicrophone,maxDurationSec:this.config.capture.maxDurationSec,consoleCapture:this.consoleCapture,networkCapture:this.networkCapture,onTick:i=>{this.elapsedMs=i,this.render()},onStop:i=>{this.lastRecording={events:i.events,audioBlob:i.audioBlob,durationMs:i.durationMs},this.isRecording=!1,this.recorder=null,this.render()}});try{await this.recorder.start()}catch(i){await this.recorder.stop().catch(()=>{}),this.recorder=null,this.isRecording=!1,this.lastRecording=null,this.discardPersistedSession(),this.error=i instanceof Error?i.message:String(i),this.config.onError?.(i instanceof Error?i:new Error(String(i))),this.render()}}async stopRecording(){this.recorder&&await this.recorder.stop()}async finalizeReport(){if(this.config&&!this.finalizingInFlight){this.finalizingInFlight=!0;try{this.recorder&&await this.recorder.stop(),this.discardPersistedSession();const e=Jo(),t={disabledCategories:this.config.disabledCategories},i=this.lastRecording,s=i?nu(i.events,e,t):[],n=i?this.consoleCapture.snapshot().map(g=>xt(g,e,t)):[],o=i?this.networkCapture.snapshot().map(g=>{const w=xt(g,e,t);return{...w,url:Di(w.url,e)}}):[],a=await gh(),l={width:window.innerWidth,height:window.innerHeight},c=i?.audioBlob??null,u=i?.durationMs??0,h={reportId:hn(),createdAt:Date.now(),durationMs:u,rrwebEvents:s,audioBlob:c,screenshotBlob:a,consoleLogs:n,networkEvents:o,annotations:[],description:"",url:Di(location.href,e),userAgent:navigator.userAgent,viewport:l,redactionSummary:e,strictMode:this.config.strictMode};this.capturedReport=h,this.releasePreviewObjectUrls();const f=URL.createObjectURL(a),d=c?URL.createObjectURL(c):null;this.previewObjectUrls=[f,d].filter(g=>g!==null),this.preview={hasReplay:s.length>0,durationMs:u,replayEvents:s,replayEventCount:s.length,hasAudio:c!==null,audioBlobUrl:d,consoleLogs:n,networkEvents:o,redactionSummary:e,screenshotUrl:f,capturedViewport:l,pageUrl:location.href,userAgent:navigator.userAgent,reportUrl:null},this.view="preview",this.render()}finally{this.finalizingInFlight=!1}}}backToMarkup(){this.preview=null,this.capturedReport=null,this.view="active",this.render()}async sendReport(e){if(!(!this.capturedReport||!this.config)){this.capturedReport.description=e.description,this.capturedReport.annotations=e.annotations,this.teardownMarkupLayer(),this.view="uploading",this.render();try{const{reportId:t,dashboardUrl:i}=await fw({apiUrl:this.config.apiUrl,projectKey:this.config.projectKey,userToken:this.config.userToken,captureToken:this.config.captureToken,report:this.capturedReport,attachments:e.attachments});this.preview&&(this.preview.reportUrl=i),this.view="success",this.config.onReportSubmitted?.(t),this.render()}catch(t){this.error=t instanceof Error?t.message:String(t),this.failedAttachments=e.attachments,this.view="failed",this.config.onError?.(t instanceof Error?t:new Error(String(t))),this.render()}}}async downloadOnDemand(){if(this.capturedReport)try{await ww(this.capturedReport,this.failedAttachments??[])}catch(e){this.config?.onError?.(e instanceof Error?e:new Error(String(e)))}}}function vw(r){return{projectKey:r.projectKey,apiUrl:Ch,userToken:r.userToken??null,user:r.user??{id:""},strictMode:r.strictMode??!1,capture:{...vh,...r.capture??{}},ui:{...Sh,...r.ui??{}},onReportSubmitted:r.onReportSubmitted??(()=>{}),onError:r.onError??(()=>{}),captureTokenParam:r.captureTokenParam??"bugjar_capture",captureToken:null,disabledCategories:new Set}}function Sw(r){if(typeof window>"u"||!window.location)return null;try{const t=new URLSearchParams(window.location.search).get(r);return t&&t.length>0?t:null}catch{return null}}function Cw(r){if(!(typeof window>"u")&&!(!window.history||typeof window.history.replaceState!="function"))try{const e=new URL(window.location.href);if(!e.searchParams.has(r))return;e.searchParams.delete(r);const t=e.searchParams.toString(),i=e.pathname+(t?`?${t}`:"")+e.hash;window.history.replaceState({},"",i)}catch{}}const yc=new bw,_i=yc,wc={init:r=>_i.init(r),open:()=>_i.open(),close:()=>_i.close()};typeof window<"u"&&(window.BugJar=wc);exports.BugJar=wc;exports._client=yc;
package/dist/index.js CHANGED
@@ -13498,7 +13498,7 @@ class Cw {
13498
13498
  t
13499
13499
  );
13500
13500
  const i = xw(this.config.captureTokenParam);
13501
- return i && this.applyCaptureToken(i), this.render(), this.attemptRecovery(), this.publicApi();
13501
+ return i && (Aw(this.config.captureTokenParam), this.applyCaptureToken(i)), this.render(), this.attemptRecovery(), this.publicApi();
13502
13502
  }
13503
13503
  /** Reload-recovery path. Called once during init(). If a fresh
13504
13504
  * active-session flag exists, hydrate state and restart a recorder
@@ -13880,13 +13880,24 @@ function xw(r) {
13880
13880
  return null;
13881
13881
  }
13882
13882
  }
13883
- const Aw = new Cw(), Di = Aw, Rw = {
13883
+ function Aw(r) {
13884
+ if (!(typeof window > "u") && !(!window.history || typeof window.history.replaceState != "function"))
13885
+ try {
13886
+ const e = new URL(window.location.href);
13887
+ if (!e.searchParams.has(r)) return;
13888
+ e.searchParams.delete(r);
13889
+ const t = e.searchParams.toString(), i = e.pathname + (t ? `?${t}` : "") + e.hash;
13890
+ window.history.replaceState({}, "", i);
13891
+ } catch {
13892
+ }
13893
+ }
13894
+ const Rw = new Cw(), Di = Rw, kw = {
13884
13895
  init: (r) => Di.init(r),
13885
13896
  open: () => Di.open(),
13886
13897
  close: () => Di.close()
13887
13898
  };
13888
- typeof window < "u" && (window.BugJar = Rw);
13899
+ typeof window < "u" && (window.BugJar = kw);
13889
13900
  export {
13890
- Rw as BugJar,
13891
- Aw as _client
13901
+ kw as BugJar,
13902
+ Rw as _client
13892
13903
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bugjar/reporter",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Privacy-first bug reporting SDK. DOM replay + redaction + markup, runs only on user consent.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",