@sphoro/bugcatch-web-sdk 0.19.0 → 0.21.0
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/README.md +54 -1
- package/dist/bugcatch-call.umd.js +1 -1
- package/dist/bugcatch-core.es.js +652 -497
- package/dist/bugcatch-core.es.js.map +1 -1
- package/dist/bugcatch-panel.es.js +1307 -964
- package/dist/bugcatch-panel.es.js.map +1 -1
- package/dist/bugcatch-panel.umd.js +291 -158
- package/dist/bugcatch-panel.umd.js.map +1 -1
- package/dist/bugcatch.standalone.umd.js +284 -151
- package/dist/bugcatch.standalone.umd.js.map +1 -1
- package/dist/bugcatch.umd.js +8 -8
- package/dist/bugcatch.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/feedback.js +94 -0
- package/src/identity.js +1 -1
- package/src/index.d.ts +65 -6
- package/src/index.js +111 -7
- package/src/panel/call.js +3 -2
- package/src/panel/compose.js +8 -4
- package/src/panel/feedback.js +205 -0
- package/src/panel/icons.js +6 -0
- package/src/panel/index.js +91 -12
- package/src/panel/rate-due.js +155 -0
- package/src/panel/rating.js +87 -0
- package/src/panel/signin.js +1 -1
- package/src/panel/styles.js +75 -0
- package/src/panel/thread.js +3 -72
- package/src/widget.js +14 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ BugCatch.init({
|
|
|
55
55
|
```html
|
|
56
56
|
<!-- Pin the version. Without one the CDN serves whatever is latest, so a
|
|
57
57
|
future release changes behaviour on a site nobody redeployed. -->
|
|
58
|
-
<script src="https://unpkg.com/@sphoro/bugcatch-web-sdk@0.
|
|
58
|
+
<script src="https://unpkg.com/@sphoro/bugcatch-web-sdk@0.21.0/dist/bugcatch.umd.js"></script>
|
|
59
59
|
<script>
|
|
60
60
|
BugCatch.init({ apiBase: '…', apiKey: 'bc_live_…', projectId: '…' });
|
|
61
61
|
</script>
|
|
@@ -381,6 +381,7 @@ On top of that:
|
|
|
381
381
|
| `drafts` | `true` | Recover an unfinished report on this device for seven days. |
|
|
382
382
|
| `agentIssues` | `true` | Show the one-tap questions the assistant answers by itself. Draws nothing until the project turns the agent on. |
|
|
383
383
|
| `knownIssues` | `true` | Show what this project already knows is broken, with "me too". Draws nothing until the project turns it on. |
|
|
384
|
+
| `feedback` | `true` | Offer **Give feedback** beside **Report a bug** in the panel, where the project has it on (Settings → Project → General, on by default). `false` hides it on this site; `feedback()` still sends. |
|
|
384
385
|
| `announcements` | `true` | Show what your team publishes under Settings → Project → Widget announcements, in the panel and beside the button. Draws nothing until one is published. `false` to show them your own way with `announcements()`. |
|
|
385
386
|
| `beforeSend` | — | Mutate the payload, or return `false` to cancel. |
|
|
386
387
|
| `updates` | `true` | Tell the reporter live when a report they filed moves. |
|
|
@@ -426,6 +427,8 @@ await BugCatch.agentIssues(); // one-tap questions the assistant ans
|
|
|
426
427
|
|
|
427
428
|
await BugCatch.knownIssues(); await BugCatch.matchKnownIssues(q, { signal }); await BugCatch.meToo(bugId);
|
|
428
429
|
await BugCatch.announcements(); BugCatch.dismissAnnouncement(a); // what your team is telling visitors
|
|
430
|
+
await BugCatch.feedbackEnabled(); await BugCatch.feedback({ message, kind, score }); // not a bug
|
|
431
|
+
BugCatch.open({ mode: 'feedback' }); // the panel, on the feedback form
|
|
429
432
|
|
|
430
433
|
await BugCatch.canCallNow(); await BugCatch.startCall(bugId?); BugCatch.endCall();
|
|
431
434
|
|
|
@@ -731,6 +734,21 @@ tells them it was fixed, so whichever they answer is the one that counts.
|
|
|
731
734
|
Rating again replaces the earlier score. Whether a project asks at all is
|
|
732
735
|
**Project Settings → Resolutions → Ask the reporter to rate it**, on by default.
|
|
733
736
|
|
|
737
|
+
The reporter does not have to go looking for it. The next time they open the
|
|
738
|
+
widget, the latest report settled for them in the last 30 days waits above the
|
|
739
|
+
form as a **How did we do?** card: one tap on a star rates it. How firmly it
|
|
740
|
+
asks is **Project Settings → Resolutions → Where the widget asks**:
|
|
741
|
+
|
|
742
|
+
| Setting | The first screen |
|
|
743
|
+
| --- | --- |
|
|
744
|
+
| Only on the report | Nothing. The stars are on the report's own screen, under Your reports. |
|
|
745
|
+
| When they next open the widget (default) | The card, with **Not now**, which hides that report's card on this device. |
|
|
746
|
+
| Before they can report again | The card, and Send and Talk to the team wait until they have rated. One rating unlocks the form; if the rating cannot be sent, **Skip for now** appears so an outage never blocks a report. |
|
|
747
|
+
|
|
748
|
+
`myReports()` rows carry it as `ratingDue`: `'ask'`, `'require'` or `null`,
|
|
749
|
+
with `resolvedAt` beside it. Only the widget's own form is held —
|
|
750
|
+
`BugCatch.report()` from your code never is.
|
|
751
|
+
|
|
734
752
|
To drive it from your own UI:
|
|
735
753
|
|
|
736
754
|
```js
|
|
@@ -902,6 +920,41 @@ BugCatch.dismissAnnouncement(notices[0]);
|
|
|
902
920
|
`announcements()` is already narrowed to the visitor's day and to what this
|
|
903
921
|
browser has not closed, and resolves to an empty list on every failure.
|
|
904
922
|
|
|
923
|
+
### Feedback that is not a bug
|
|
924
|
+
|
|
925
|
+
The panel has two forms: **Report a bug** and **Give feedback**, switched at
|
|
926
|
+
the top. Feedback is for everything that is not "this is broken" — an idea,
|
|
927
|
+
praise, something that confused them — and is deliberately short: one box, an
|
|
928
|
+
optional kind (idea, praise, confusing, other), an optional 1–5 face for how
|
|
929
|
+
the product feels so far, and an address only when the page does not already
|
|
930
|
+
know one. Nobody is asked to sign in, even on a project that requires it for
|
|
931
|
+
reports.
|
|
932
|
+
|
|
933
|
+
It is not filed as a bug. Nothing analyses it, assigns it, holds it to an SLA
|
|
934
|
+
or emails anyone about it; it lands on the dashboard's **Feedback** page, where
|
|
935
|
+
the team reads it, writes back if there is an address, and archives it. It is
|
|
936
|
+
sent with the page and browser details, identify()'s user and your
|
|
937
|
+
setContext() context — never the console or network logs — and goes through
|
|
938
|
+
`beforeSend`, whose second argument is `{ type: 'feedback' }`.
|
|
939
|
+
|
|
940
|
+
On by default; a project switches it off under **Settings → Project →
|
|
941
|
+
General**, and the widget then stops drawing the switch.
|
|
942
|
+
|
|
943
|
+
```js
|
|
944
|
+
// Your own "Send feedback" menu item
|
|
945
|
+
BugCatch.open({ mode: 'feedback' });
|
|
946
|
+
|
|
947
|
+
// Or your own form (works with widget: false)
|
|
948
|
+
if (await BugCatch.feedbackEnabled()) {
|
|
949
|
+
await BugCatch.feedback({
|
|
950
|
+
message: 'Export to CSV would save me an hour a week',
|
|
951
|
+
kind: 'idea', // 'idea' | 'praise' | 'confusing' | 'other' — optional
|
|
952
|
+
score: 4, // 1–5, optional
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
BugCatch.on('feedback', ({ id, kind, score }) => analytics.track('feedback', { kind, score }));
|
|
956
|
+
```
|
|
957
|
+
|
|
905
958
|
### Talking a report through
|
|
906
959
|
|
|
907
960
|
Where the workspace has sphoro.voice configured, a reporter can talk to the team
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(){"use strict";function it(e=8e3){const r=window.AudioContext||window.webkitAudioContext;try{return new r({sampleRate:e})}catch{return new r}}async function st(e){if(e.state==="suspended")try{await e.resume()}catch{}}function lt(e,r,o){const a=r/o,s=new Float32Array(Math.floor(e.length/a));for(let f=0;f<s.length;f++){const l=Math.floor(f*a),m=Math.min(Math.floor((f+1)*a),e.length);let g=0;for(let i=l;i<m;i++)g+=e[i];s[f]=m>l?g/(m-l):0}return s}const ut=132,j=32635;function ft(e){const r=new Uint8Array(e.length);for(let o=0;o<e.length;o++){const a=Math.max(-1,Math.min(1,e[o]));let s=Math.round(a*32767);const f=s<0?128:0;f&&(s=-s),s>j&&(s=j),s+=ut;let l=7;for(let g=16384;!(s&g)&&l>0;l--,g>>=1);const m=s>>l+3&15;r[o]=~(f|l<<4|m)&255}return r}const dt=512;function pt(e,r,o,a=8e3){const s=e.createMediaStreamSource(r),f=e.createScriptProcessor(dt,1,1);f.onaudioprocess=m=>{const g=m.inputBuffer.getChannelData(0),i=e.sampleRate===a?g:lt(g,e.sampleRate,a);o(ft(i))},s.connect(f);const l=e.createGain();return l.gain.value=0,f.connect(l),l.connect(e.destination),{stop(){f.onaudioprocess=null;try{s.disconnect()}catch{}try{f.disconnect()}catch{}try{l.disconnect()}catch{}}}}const mt="voiceai.realtime.v1",yt=[{urls:"stun:stun.l.google.com:19302"}];function z(){return navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,channelCount:1}})}function F(){var e;return typeof window<"u"&&typeof window.RTCPeerConnection=="function"&&!!((e=navigator.mediaDevices)!=null&&e.getUserMedia)}async function gt({realtimeUrl:e,stream:r,ringsUntil:o,onState:a,onAudioBlocked:s,onMicrophone:f}){const l=(t,n)=>{try{a==null||a(t,n)}catch{}},m=t=>{try{s==null||s(t)}catch{}},g=t=>{try{f==null||f(t)}catch{}};if(!F()){const t=new Error("[BugCatch] this browser cannot make live calls.");throw l("failed",{reason:"unsupported"}),t}let i=r;if(!i)try{i=await z()}catch(t){throw l("failed",{reason:"microphone",error:String((t==null?void 0:t.name)||t)}),t}let y;try{y=await ht(e)}catch(t){throw W(i),l("failed",{reason:"connect"}),t}let c=null,u=null,p=!1,w=null,h=null,E=null,S=!1,b=null,C=null,O=!1;const x=[];let M=!1,D=!1,P=!1,R=null,I=null,B=0;const Ot=2e3,K=5e3,_t=3e4,At=4,k=(t,n)=>{if(!p){p=!0,w&&(clearTimeout(w),w=null),et(),C==null||C(),C=null,Mt(),b==null||b(),b=null;try{c==null||c.close()}catch{}W(i),u&&(u.srcObject=null,u.remove(),u=null);try{y.readyState===WebSocket.OPEN&&y.close(1e3,"call over")}catch{}l(t,n)}},_=t=>{y.readyState===WebSocket.OPEN&&y.send(JSON.stringify(t))},q=async()=>{if(O||E||p||!h||!h.audio&&!h.transcript)return;O=!0;const t=h.encoding||"mulaw";if(t!=="mulaw"){V(`this call is kept as ${t}, which this SDK cannot produce.`);return}const n=Number(h.sample_rate)||8e3;try{const d=it(n);if(await st(d),p){try{d.close()}catch{}return}const v=pt(d,i,T=>{y.readyState===WebSocket.OPEN&&y.send(T)},n);E={context:d,tap:v}}catch(d){V(`could not send the copy this call is keeping: ${String((d==null?void 0:d.message)||d)}`)}};function Mt(){if(O=!1,!E)return;const{context:t,tap:n}=E;E=null;try{n.stop()}catch{}try{t.close()}catch{}}function X(){var n;if(!u||p)return;let t;try{t=(n=u.play)==null?void 0:n.call(u)}catch{Q();return}if(!t||typeof t.then!="function"){H();return}t.then(H,Q)}function H(){b==null||b(),b=null,S&&(S=!1,m(!1))}function Q(){p||(S||(S=!0,m(!0)),It())}function It(){if(b||typeof window>"u"||typeof window.addEventListener!="function")return;const t=()=>X(),n=["pointerdown","keydown","touchstart"];for(const d of n)window.addEventListener(d,t,{capture:!0,passive:!0});b=()=>{for(const d of n)try{window.removeEventListener(d,t,{capture:!0})}catch{}}}const $t=t=>{c=new RTCPeerConnection({iceServers:t!=null&&t.length?t:yt});for(const n of i.getAudioTracks())c.addTrack(n,i);C=Nt(),c.onicecandidate=n=>{n.candidate&&_({type:"signal",signal:{kind:"candidate",payload:n.candidate.toJSON()}})},c.ontrack=n=>{var v;const[d]=n.streams;d&&(u||(u=document.createElement("audio"),u.autoplay=!0,u.playsInline=!0,(v=u.setAttribute)==null||v.call(u,"playsinline",""),u.style.cssText="position:fixed;width:0;height:0;opacity:0;pointer-events:none",document.body.appendChild(u)),u.srcObject=d,X())},c.onconnectionstatechange=Z,c.oniceconnectionstatechange=Z};function Z(){if(!c||p)return;const t=c.connectionState||c.iceConnectionState;if(t==="connected"||t==="completed"){D=!0,B=0,et(),P=!1,l("connected");return}if(t==="disconnected"){if(!D)return;tt(Ot);return}if(t==="failed"){if(!D){k("failed",{reason:"ice"});return}tt(0)}}function tt(t){p||(P||(P=!0,l("reconnecting"),I=setTimeout(()=>k("failed",{reason:"network"}),_t)),!R&&(R=setTimeout(U,t)))}function et(){R&&(clearTimeout(R),R=null),I&&(clearTimeout(I),I=null)}async function U(){if(R=null,p||!c)return;const t=c.connectionState||c.iceConnectionState;if(!(t==="connected"||t==="completed")){if(!M){_({type:"signal",signal:{kind:"restart",payload:{}}}),R=setTimeout(U,K);return}B>=At||(B+=1,await nt(),R=setTimeout(U,K))}}async function nt(){if(!(p||!c))try{const t=await c.createOffer({iceRestart:!0});await c.setLocalDescription(t),_({type:"signal",signal:{kind:"offer",payload:c.localDescription.toJSON()}})}catch{}}function Nt(){let n=null,d=!1;const v=T=>{if(n&&(clearTimeout(n),n=null),T){if(!d)return;d=!1,g(!0);return}d||(n=setTimeout(()=>{n=null,!p&&(d=!0,g(!1))},600))};for(const T of i.getAudioTracks())T.onmute=()=>v(!1),T.onunmute=()=>v(!0),T.onended=()=>{n&&(clearTimeout(n),n=null),!d&&(d=!0,g(!1))};return()=>{n&&(clearTimeout(n),n=null);for(const T of i.getAudioTracks())T.onmute=null,T.onunmute=null,T.onended=null}}const Lt=async()=>{try{const t=await c.createOffer();await c.setLocalDescription(t),_({type:"signal",signal:{kind:"offer",payload:c.localDescription.toJSON()}})}catch{k("failed",{reason:"negotiation"})}},Dt=async t=>{try{await c.setRemoteDescription(new RTCSessionDescription(t));const n=await c.createAnswer();await c.setLocalDescription(n),_({type:"signal",signal:{kind:"answer",payload:c.localDescription.toJSON()}}),await ot()}catch{k("failed",{reason:"negotiation"})}},ot=async()=>{for(;x.length;){const t=x.shift();try{await c.addIceCandidate(new RTCIceCandidate(t))}catch{}}};let rt=Promise.resolve();y.onmessage=t=>{rt=rt.then(()=>Pt(t)).catch(()=>{})};const Pt=async t=>{var d,v,T,at,ct;if(typeof t.data!="string")return;let n;try{n=JSON.parse(t.data)}catch{return}switch(n.type){case"session.created":{if($t((d=n.data)==null?void 0:d.ice_servers),h=((v=n.data)==null?void 0:v.capture)||null,(T=n.data)!=null&&T.peer_present){l("connecting"),q();break}l("ringing"),Bt();break}case"peer.joined":{w&&(clearTimeout(w),w=null),l("connecting"),M=!!((at=n.data)!=null&&at.should_offer),M&&await Lt(),q();break}case"signal":{if(!c)break;const{kind:$,payload:N}=n.data||{};if($==="offer")await Dt(N);else if($==="answer")try{await c.setRemoteDescription(new RTCSessionDescription(N)),await ot()}catch{k("failed",{reason:"negotiation"})}else if($==="candidate")if(c.remoteDescription)try{await c.addIceCandidate(new RTCIceCandidate(N))}catch{}else x.push(N);else $==="restart"&&M&&await nt();break}case"peer.left":k("ended",{reason:"peer_left"});break;case"error":k("failed",{reason:"server",detail:(ct=n.error)==null?void 0:ct.detail});break;case"session.ended":k("ended",{reason:"server"});break}};y.onclose=()=>k("ended",{reason:"disconnected"}),y.onerror=()=>k("failed",{reason:"socket"});function Bt(){if(!o)return;const t=new Date(o).getTime()-Date.now();Number.isFinite(t)&&(w=setTimeout(()=>k("missed"),Math.max(0,t)))}return{hangUp(){_({type:"session.end"}),k("ended",{reason:"hung_up"})},setMuted(t){for(const n of i.getAudioTracks())n.enabled=!t;return t},isMuted(){return i.getAudioTracks().every(t=>!t.enabled)}}}function ht(e){return new Promise((r,o)=>{let a;try{a=new WebSocket(e,mt)}catch(s){o(s);return}a.onopen=()=>{a.onopen=null,a.onerror=null,r(a)},a.onerror=()=>{a.onopen=null,a.onerror=null,o(new Error("[BugCatch] could not connect the call."))}})}function V(e){try{console.warn(`[BugCatch] ${e}`)}catch{}}function W(e){var r;for(const o of((r=e==null?void 0:e.getTracks)==null?void 0:r.call(e))??[])try{o.stop()}catch{}}const J="#0e0e10",L="#ffffff";function wt(e){const r=bt(e);if(!r)return L;const[o,a,s]=r.map(l=>{const m=l/255;return m<=.03928?m/12.92:((m+.055)/1.055)**2.4});return .2126*o+.7152*a+.0722*s>.179?J:L}function bt(e){const r=String(e||"").trim();let o=/^#([0-9a-f]{3})$/i.exec(r);return o?[...o[1]].map(a=>parseInt(a+a,16)):(o=/^#([0-9a-f]{6})$/i.exec(r),o?[0,2,4].map(a=>parseInt(o[1].slice(a,a+2),16)):(o=/^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i.exec(r),o?[o[1],o[2],o[3]].map(Number):null))}const A={cycleMs:3e3,peak:.045,bursts:[{at:0,seconds:.4,hz:[440,660]},{at:.55,seconds:.4,hz:[440,660]}]};function Tt(){var e;try{return!!((e=navigator.userActivation)!=null&&e.hasBeenActive)}catch{return!1}}function kt(e){if(!e()&&!Tt())return()=>{};const r=typeof window<"u"&&(window.AudioContext||window.webkitAudioContext);if(!r)return()=>{};let o;try{o=new r}catch{return()=>{}}let a=!1,s=null;const f=[],l=({at:i,seconds:y,hz:c})=>{const u=o.currentTime+i,p=o.createGain();p.connect(o.destination),p.gain.setValueAtTime(0,u),p.gain.linearRampToValueAtTime(A.peak,u+.02),p.gain.setValueAtTime(A.peak,u+y-.06),p.gain.linearRampToValueAtTime(0,u+y);for(const w of c){const h=o.createOscillator();h.type="sine",h.frequency.value=w,h.connect(p),h.start(u),h.stop(u+y+.02),f.push(h)}},m=()=>{if(!a)try{A.bursts.forEach(l)}catch{}},g=()=>{a||(m(),s=setInterval(m,A.cycleMs))};return o.state==="running"?g():o.resume().then(g).catch(()=>{}),()=>{a=!0,s&&clearInterval(s);for(const i of f)try{i.stop()}catch{}try{o.close()}catch{}}}function xt(e){const r=Date.parse(e||"");return Number.isNaN(r)?0:Math.max(0,Math.round((r-Date.now())/1e3))}const Y=["flex:1","padding:9px 12px","border:0","border-radius:999px","font:560 13px/1.4 inherit","cursor:pointer"].join(";");function Ct({call:e,accent:r=null,position:o="bottom-right",onAnswer:a,onDecline:s,onExpire:f,touched:l=()=>!1}){if(typeof document>"u"||!document.body)return()=>{};const[m,g]=String(o).split("-"),i=document.createElement("div");i.setAttribute("role","alertdialog"),i.setAttribute("aria-live","assertive"),i.setAttribute("aria-label",`${e.from||"Support"} is calling about ${e.reference}`),i.style.cssText=["position:fixed",`${m==="top"?"top":"bottom"}:20px`,`${g==="left"?"left":"right"}:20px`,"z-index:2147483647","width:300px","max-width:calc(100vw - 40px)","padding:16px","border-radius:16px","background:#111113","color:#f3f2ef",'font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',`box-shadow:0 0 0 1px ${r?`color-mix(in srgb, ${r} 55%, transparent)`:"rgba(255,255,255,.12)"},0 24px 50px -12px rgba(0,0,0,.6)`,"opacity:0","transform:translateY(8px)","transition:opacity .2s ease,transform .2s ease"].join(";");const y=document.createElement("div");y.style.cssText="font-weight:600;font-size:14px;margin-bottom:2px",y.textContent=`${e.from||"Support"} is calling`;const c=document.createElement("div");c.style.cssText="color:#b3b0aa;margin-bottom:10px",c.textContent=e.project?`About your report ${e.reference} · ${e.project}`:`About your report ${e.reference}`;const u=document.createElement("div");u.style.cssText="color:#8f8c86;font-size:12px;margin-bottom:10px";const p=document.createElement("button");p.type="button",p.textContent="Answer",p.style.cssText=`${Y};background:${r||L};color:${r?wt(r):J}`;const w=document.createElement("button");w.type="button",w.textContent="Not now",w.style.cssText=`${Y};background:rgba(255,255,255,.08);color:#f3f2ef`;const h=document.createElement("div");if(h.style.cssText="display:flex;gap:8px",h.append(p,w),i.append(y,c,u),e.recorded){const x=document.createElement("div");x.style.cssText="color:#8f8c86;font-size:12px;margin-bottom:10px",x.textContent="This call will be recorded.",i.append(x)}i.append(h),document.body.appendChild(i);const E=kt(l);let S=!1,b=null;const C=()=>{S||(S=!0,b&&clearInterval(b),E(),i.style.opacity="0",i.style.transform="translateY(8px)",setTimeout(()=>i.remove(),250))},O=()=>{const x=xt(e.ringsUntil);if(u.textContent=x?`Ringing — ${x}s`:"The call has stopped ringing.",!x){C();try{f==null||f()}catch{}}};return O(),b=setInterval(O,1e3),p.addEventListener("click",()=>{C();try{a==null||a()}catch{}}),w.addEventListener("click",()=>{C();try{s==null||s()}catch{}}),requestAnimationFrame(()=>{i.style.opacity="1",i.style.transform="translateY(0)"}),C}const vt=Object.freeze(Object.defineProperty({__proto__:null,joinLiveCall:gt,liveCallSupported:F,openMicrophone:z,showIncomingCall:Ct},Symbol.toStringTag,{value:"Module"})),Rt="0.19.0",G="__bugcatchChunks",Et=e=>`${e}@${Rt}`;function St(e,r){const o=window[G]||(window[G]={});o[Et(e)]=r}St("call",vt)})();
|
|
1
|
+
(function(){"use strict";function it(e=8e3){const r=window.AudioContext||window.webkitAudioContext;try{return new r({sampleRate:e})}catch{return new r}}async function st(e){if(e.state==="suspended")try{await e.resume()}catch{}}function lt(e,r,o){const a=r/o,s=new Float32Array(Math.floor(e.length/a));for(let f=0;f<s.length;f++){const l=Math.floor(f*a),m=Math.min(Math.floor((f+1)*a),e.length);let g=0;for(let i=l;i<m;i++)g+=e[i];s[f]=m>l?g/(m-l):0}return s}const ut=132,j=32635;function ft(e){const r=new Uint8Array(e.length);for(let o=0;o<e.length;o++){const a=Math.max(-1,Math.min(1,e[o]));let s=Math.round(a*32767);const f=s<0?128:0;f&&(s=-s),s>j&&(s=j),s+=ut;let l=7;for(let g=16384;!(s&g)&&l>0;l--,g>>=1);const m=s>>l+3&15;r[o]=~(f|l<<4|m)&255}return r}const dt=512;function pt(e,r,o,a=8e3){const s=e.createMediaStreamSource(r),f=e.createScriptProcessor(dt,1,1);f.onaudioprocess=m=>{const g=m.inputBuffer.getChannelData(0),i=e.sampleRate===a?g:lt(g,e.sampleRate,a);o(ft(i))},s.connect(f);const l=e.createGain();return l.gain.value=0,f.connect(l),l.connect(e.destination),{stop(){f.onaudioprocess=null;try{s.disconnect()}catch{}try{f.disconnect()}catch{}try{l.disconnect()}catch{}}}}const mt="voiceai.realtime.v1",yt=[{urls:"stun:stun.l.google.com:19302"}];function z(){return navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0,channelCount:1}})}function F(){var e;return typeof window<"u"&&typeof window.RTCPeerConnection=="function"&&!!((e=navigator.mediaDevices)!=null&&e.getUserMedia)}async function gt({realtimeUrl:e,stream:r,ringsUntil:o,onState:a,onAudioBlocked:s,onMicrophone:f}){const l=(t,n)=>{try{a==null||a(t,n)}catch{}},m=t=>{try{s==null||s(t)}catch{}},g=t=>{try{f==null||f(t)}catch{}};if(!F()){const t=new Error("[BugCatch] this browser cannot make live calls.");throw l("failed",{reason:"unsupported"}),t}let i=r;if(!i)try{i=await z()}catch(t){throw l("failed",{reason:"microphone",error:String((t==null?void 0:t.name)||t)}),t}let y;try{y=await ht(e)}catch(t){throw W(i),l("failed",{reason:"connect"}),t}let c=null,u=null,p=!1,w=null,h=null,E=null,S=!1,b=null,C=null,O=!1;const x=[];let M=!1,D=!1,P=!1,R=null,I=null,B=0;const Ot=2e3,K=5e3,_t=3e4,At=4,k=(t,n)=>{if(!p){p=!0,w&&(clearTimeout(w),w=null),et(),C==null||C(),C=null,Mt(),b==null||b(),b=null;try{c==null||c.close()}catch{}W(i),u&&(u.srcObject=null,u.remove(),u=null);try{y.readyState===WebSocket.OPEN&&y.close(1e3,"call over")}catch{}l(t,n)}},_=t=>{y.readyState===WebSocket.OPEN&&y.send(JSON.stringify(t))},q=async()=>{if(O||E||p||!h||!h.audio&&!h.transcript)return;O=!0;const t=h.encoding||"mulaw";if(t!=="mulaw"){V(`this call is kept as ${t}, which this SDK cannot produce.`);return}const n=Number(h.sample_rate)||8e3;try{const d=it(n);if(await st(d),p){try{d.close()}catch{}return}const v=pt(d,i,T=>{y.readyState===WebSocket.OPEN&&y.send(T)},n);E={context:d,tap:v}}catch(d){V(`could not send the copy this call is keeping: ${String((d==null?void 0:d.message)||d)}`)}};function Mt(){if(O=!1,!E)return;const{context:t,tap:n}=E;E=null;try{n.stop()}catch{}try{t.close()}catch{}}function X(){var n;if(!u||p)return;let t;try{t=(n=u.play)==null?void 0:n.call(u)}catch{Q();return}if(!t||typeof t.then!="function"){H();return}t.then(H,Q)}function H(){b==null||b(),b=null,S&&(S=!1,m(!1))}function Q(){p||(S||(S=!0,m(!0)),It())}function It(){if(b||typeof window>"u"||typeof window.addEventListener!="function")return;const t=()=>X(),n=["pointerdown","keydown","touchstart"];for(const d of n)window.addEventListener(d,t,{capture:!0,passive:!0});b=()=>{for(const d of n)try{window.removeEventListener(d,t,{capture:!0})}catch{}}}const $t=t=>{c=new RTCPeerConnection({iceServers:t!=null&&t.length?t:yt});for(const n of i.getAudioTracks())c.addTrack(n,i);C=Nt(),c.onicecandidate=n=>{n.candidate&&_({type:"signal",signal:{kind:"candidate",payload:n.candidate.toJSON()}})},c.ontrack=n=>{var v;const[d]=n.streams;d&&(u||(u=document.createElement("audio"),u.autoplay=!0,u.playsInline=!0,(v=u.setAttribute)==null||v.call(u,"playsinline",""),u.style.cssText="position:fixed;width:0;height:0;opacity:0;pointer-events:none",document.body.appendChild(u)),u.srcObject=d,X())},c.onconnectionstatechange=Z,c.oniceconnectionstatechange=Z};function Z(){if(!c||p)return;const t=c.connectionState||c.iceConnectionState;if(t==="connected"||t==="completed"){D=!0,B=0,et(),P=!1,l("connected");return}if(t==="disconnected"){if(!D)return;tt(Ot);return}if(t==="failed"){if(!D){k("failed",{reason:"ice"});return}tt(0)}}function tt(t){p||(P||(P=!0,l("reconnecting"),I=setTimeout(()=>k("failed",{reason:"network"}),_t)),!R&&(R=setTimeout(U,t)))}function et(){R&&(clearTimeout(R),R=null),I&&(clearTimeout(I),I=null)}async function U(){if(R=null,p||!c)return;const t=c.connectionState||c.iceConnectionState;if(!(t==="connected"||t==="completed")){if(!M){_({type:"signal",signal:{kind:"restart",payload:{}}}),R=setTimeout(U,K);return}B>=At||(B+=1,await nt(),R=setTimeout(U,K))}}async function nt(){if(!(p||!c))try{const t=await c.createOffer({iceRestart:!0});await c.setLocalDescription(t),_({type:"signal",signal:{kind:"offer",payload:c.localDescription.toJSON()}})}catch{}}function Nt(){let n=null,d=!1;const v=T=>{if(n&&(clearTimeout(n),n=null),T){if(!d)return;d=!1,g(!0);return}d||(n=setTimeout(()=>{n=null,!p&&(d=!0,g(!1))},600))};for(const T of i.getAudioTracks())T.onmute=()=>v(!1),T.onunmute=()=>v(!0),T.onended=()=>{n&&(clearTimeout(n),n=null),!d&&(d=!0,g(!1))};return()=>{n&&(clearTimeout(n),n=null);for(const T of i.getAudioTracks())T.onmute=null,T.onunmute=null,T.onended=null}}const Lt=async()=>{try{const t=await c.createOffer();await c.setLocalDescription(t),_({type:"signal",signal:{kind:"offer",payload:c.localDescription.toJSON()}})}catch{k("failed",{reason:"negotiation"})}},Dt=async t=>{try{await c.setRemoteDescription(new RTCSessionDescription(t));const n=await c.createAnswer();await c.setLocalDescription(n),_({type:"signal",signal:{kind:"answer",payload:c.localDescription.toJSON()}}),await ot()}catch{k("failed",{reason:"negotiation"})}},ot=async()=>{for(;x.length;){const t=x.shift();try{await c.addIceCandidate(new RTCIceCandidate(t))}catch{}}};let rt=Promise.resolve();y.onmessage=t=>{rt=rt.then(()=>Pt(t)).catch(()=>{})};const Pt=async t=>{var d,v,T,at,ct;if(typeof t.data!="string")return;let n;try{n=JSON.parse(t.data)}catch{return}switch(n.type){case"session.created":{if($t((d=n.data)==null?void 0:d.ice_servers),h=((v=n.data)==null?void 0:v.capture)||null,(T=n.data)!=null&&T.peer_present){l("connecting"),q();break}l("ringing"),Bt();break}case"peer.joined":{w&&(clearTimeout(w),w=null),l("connecting"),M=!!((at=n.data)!=null&&at.should_offer),M&&await Lt(),q();break}case"signal":{if(!c)break;const{kind:$,payload:N}=n.data||{};if($==="offer")await Dt(N);else if($==="answer")try{await c.setRemoteDescription(new RTCSessionDescription(N)),await ot()}catch{k("failed",{reason:"negotiation"})}else if($==="candidate")if(c.remoteDescription)try{await c.addIceCandidate(new RTCIceCandidate(N))}catch{}else x.push(N);else $==="restart"&&M&&await nt();break}case"peer.left":k("ended",{reason:"peer_left"});break;case"error":k("failed",{reason:"server",detail:(ct=n.error)==null?void 0:ct.detail});break;case"session.ended":k("ended",{reason:"server"});break}};y.onclose=()=>k("ended",{reason:"disconnected"}),y.onerror=()=>k("failed",{reason:"socket"});function Bt(){if(!o)return;const t=new Date(o).getTime()-Date.now();Number.isFinite(t)&&(w=setTimeout(()=>k("missed"),Math.max(0,t)))}return{hangUp(){_({type:"session.end"}),k("ended",{reason:"hung_up"})},setMuted(t){for(const n of i.getAudioTracks())n.enabled=!t;return t},isMuted(){return i.getAudioTracks().every(t=>!t.enabled)}}}function ht(e){return new Promise((r,o)=>{let a;try{a=new WebSocket(e,mt)}catch(s){o(s);return}a.onopen=()=>{a.onopen=null,a.onerror=null,r(a)},a.onerror=()=>{a.onopen=null,a.onerror=null,o(new Error("[BugCatch] could not connect the call."))}})}function V(e){try{console.warn(`[BugCatch] ${e}`)}catch{}}function W(e){var r;for(const o of((r=e==null?void 0:e.getTracks)==null?void 0:r.call(e))??[])try{o.stop()}catch{}}const J="#0e0e10",L="#ffffff";function wt(e){const r=bt(e);if(!r)return L;const[o,a,s]=r.map(l=>{const m=l/255;return m<=.03928?m/12.92:((m+.055)/1.055)**2.4});return .2126*o+.7152*a+.0722*s>.179?J:L}function bt(e){const r=String(e||"").trim();let o=/^#([0-9a-f]{3})$/i.exec(r);return o?[...o[1]].map(a=>parseInt(a+a,16)):(o=/^#([0-9a-f]{6})$/i.exec(r),o?[0,2,4].map(a=>parseInt(o[1].slice(a,a+2),16)):(o=/^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)/i.exec(r),o?[o[1],o[2],o[3]].map(Number):null))}const A={cycleMs:3e3,peak:.045,bursts:[{at:0,seconds:.4,hz:[440,660]},{at:.55,seconds:.4,hz:[440,660]}]};function Tt(){var e;try{return!!((e=navigator.userActivation)!=null&&e.hasBeenActive)}catch{return!1}}function kt(e){if(!e()&&!Tt())return()=>{};const r=typeof window<"u"&&(window.AudioContext||window.webkitAudioContext);if(!r)return()=>{};let o;try{o=new r}catch{return()=>{}}let a=!1,s=null;const f=[],l=({at:i,seconds:y,hz:c})=>{const u=o.currentTime+i,p=o.createGain();p.connect(o.destination),p.gain.setValueAtTime(0,u),p.gain.linearRampToValueAtTime(A.peak,u+.02),p.gain.setValueAtTime(A.peak,u+y-.06),p.gain.linearRampToValueAtTime(0,u+y);for(const w of c){const h=o.createOscillator();h.type="sine",h.frequency.value=w,h.connect(p),h.start(u),h.stop(u+y+.02),f.push(h)}},m=()=>{if(!a)try{A.bursts.forEach(l)}catch{}},g=()=>{a||(m(),s=setInterval(m,A.cycleMs))};return o.state==="running"?g():o.resume().then(g).catch(()=>{}),()=>{a=!0,s&&clearInterval(s);for(const i of f)try{i.stop()}catch{}try{o.close()}catch{}}}function xt(e){const r=Date.parse(e||"");return Number.isNaN(r)?0:Math.max(0,Math.round((r-Date.now())/1e3))}const Y=["flex:1","padding:9px 12px","border:0","border-radius:999px","font:560 13px/1.4 inherit","cursor:pointer"].join(";");function Ct({call:e,accent:r=null,position:o="bottom-right",onAnswer:a,onDecline:s,onExpire:f,touched:l=()=>!1}){if(typeof document>"u"||!document.body)return()=>{};const[m,g]=String(o).split("-"),i=document.createElement("div");i.setAttribute("role","alertdialog"),i.setAttribute("aria-live","assertive"),i.setAttribute("aria-label",`${e.from||"Support"} is calling about ${e.reference}`),i.style.cssText=["position:fixed",`${m==="top"?"top":"bottom"}:20px`,`${g==="left"?"left":"right"}:20px`,"z-index:2147483647","width:300px","max-width:calc(100vw - 40px)","padding:16px","border-radius:16px","background:#111113","color:#f3f2ef",'font:13px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif',`box-shadow:0 0 0 1px ${r?`color-mix(in srgb, ${r} 55%, transparent)`:"rgba(255,255,255,.12)"},0 24px 50px -12px rgba(0,0,0,.6)`,"opacity:0","transform:translateY(8px)","transition:opacity .2s ease,transform .2s ease"].join(";");const y=document.createElement("div");y.style.cssText="font-weight:600;font-size:14px;margin-bottom:2px",y.textContent=`${e.from||"Support"} is calling`;const c=document.createElement("div");c.style.cssText="color:#b3b0aa;margin-bottom:10px",c.textContent=e.project?`About your report ${e.reference} · ${e.project}`:`About your report ${e.reference}`;const u=document.createElement("div");u.style.cssText="color:#8f8c86;font-size:12px;margin-bottom:10px";const p=document.createElement("button");p.type="button",p.textContent="Answer",p.style.cssText=`${Y};background:${r||L};color:${r?wt(r):J}`;const w=document.createElement("button");w.type="button",w.textContent="Not now",w.style.cssText=`${Y};background:rgba(255,255,255,.08);color:#f3f2ef`;const h=document.createElement("div");if(h.style.cssText="display:flex;gap:8px",h.append(p,w),i.append(y,c,u),e.recorded){const x=document.createElement("div");x.style.cssText="color:#8f8c86;font-size:12px;margin-bottom:10px",x.textContent="This call will be recorded.",i.append(x)}i.append(h),document.body.appendChild(i);const E=kt(l);let S=!1,b=null;const C=()=>{S||(S=!0,b&&clearInterval(b),E(),i.style.opacity="0",i.style.transform="translateY(8px)",setTimeout(()=>i.remove(),250))},O=()=>{const x=xt(e.ringsUntil);if(u.textContent=x?`Ringing — ${x}s`:"The call has stopped ringing.",!x){C();try{f==null||f()}catch{}}};return O(),b=setInterval(O,1e3),p.addEventListener("click",()=>{C();try{a==null||a()}catch{}}),w.addEventListener("click",()=>{C();try{s==null||s()}catch{}}),requestAnimationFrame(()=>{i.style.opacity="1",i.style.transform="translateY(0)"}),C}const vt=Object.freeze(Object.defineProperty({__proto__:null,joinLiveCall:gt,liveCallSupported:F,openMicrophone:z,showIncomingCall:Ct},Symbol.toStringTag,{value:"Module"})),Rt="0.21.0",G="__bugcatchChunks",Et=e=>`${e}@${Rt}`;function St(e,r){const o=window[G]||(window[G]={});o[Et(e)]=r}St("call",vt)})();
|
|
2
2
|
//# sourceMappingURL=bugcatch-call.umd.js.map
|