zerorate-voip-sdk 0.1.0 → 0.1.2
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/voip-sdk.esm.js +1 -1
- package/dist/voip-sdk.js +13 -13
- package/package.json +1 -1
package/dist/voip-sdk.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class t{constructor(){this.handlers=new Map}on(t,e){this.handlers.has(t)||this.handlers.set(t,new Set),this.handlers.get(t).add(e)}off(t,e){const i=this.handlers.get(t);i&&(i.delete(e),0===i.size&&this.handlers.delete(t))}once(t,e){const i=n=>{this.off(t,i),e(n)};this.on(t,i)}emit(t,e){const i=this.handlers.get(t);if(i)for(const t of Array.from(i))t(e)}}class e extends t{constructor(t,e,i=!0,n=!1,s){super(),this.state="disconnected",this.queue=[],this.reconnectAttempts=0,this.url=t,this.authToken=e,this.autoReconnect=i,this.debug=n,this.userId=s}getState(){return this.state}isConnected(){return"connected"===this.state}async connect(){if(!this.ws||"connecting"!==this.state&&"connected"!==this.state){this.state="connecting";try{this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.state="connected",this.emit("connected"),this.userId&&this.authToken?this.send({type:"authenticate",userId:this.userId,token:this.authToken}):this.authToken&&this.send({type:"authenticate",token:this.authToken}),this.flushQueue(),this.startHeartbeat(),this.reconnectAttempts=0},this.ws.onmessage=t=>{if("string"==typeof t.data)try{const e=JSON.parse(t.data);this.emit("message",e)}catch(e){this.emit("message",t.data)}else this.emit("message",t.data)},this.ws.onerror=()=>{this.state="error",this.emit("error",{type:"network"})},this.ws.onclose=()=>{this.stopHeartbeat();const t="connected"===this.state;this.state="disconnected",this.emit("disconnected"),this.autoReconnect&&this.scheduleReconnect(t)}}catch(t){this.state="error",this.emit("error",{type:"network"})}}}disconnect(){if(this.autoReconnect=!1,this.stopHeartbeat(),this.ws&&("connecting"===this.state||"connected"===this.state))try{this.ws.close()}catch(t){}this.ws=void 0,this.state="disconnected",this.emit("disconnected")}send(t){const e="string"==typeof t?t:JSON.stringify(t);if(this.ws&&"connected"===this.state)try{this.ws.send(e)}catch(t){this.queue.push(e)}else this.queue.push(e)}flushQueue(){if(this.ws&&"connected"===this.state)for(const t of this.queue.splice(0))try{this.ws.send(t)}catch(t){}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatId=window.setInterval(()=>{if(this.ws&&"connected"===this.state)try{this.ws.send(JSON.stringify({type:"heartbeat"})),this.pongTimeoutId&&window.clearTimeout(this.pongTimeoutId),this.pongTimeoutId=window.setTimeout(()=>{try{this.ws?.close()}catch(t){}},5e3)}catch(t){}},3e4)}stopHeartbeat(){this.heartbeatId&&(window.clearInterval(this.heartbeatId),this.heartbeatId=void 0),this.pongTimeoutId&&(window.clearTimeout(this.pongTimeoutId),this.pongTimeoutId=void 0)}scheduleReconnect(t){this.reconnectAttempts+=1;const e=t?1:2,i=Math.min(3e4,1e3*Math.pow(2,this.reconnectAttempts-1)*e);this.emit("reconnecting",{attempt:this.reconnectAttempts,delay:i}),window.setTimeout(()=>{this.autoReconnect&&this.connect()},i)}}class i extends t{constructor(t,e){super(),this.timeoutMs=1e4,this.retries=3,this.baseUrl=t.replace(/\/+$/,""),this.authToken=e}setAuthToken(t){this.authToken=t}async initiateCall(t){return this.request("/api/webrtc/v1/calls/initiate","POST",t)}async acceptCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/accept`,"POST",{userId:e})}async declineCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/decline`,"POST",{userId:e})}async endCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/end`,"POST",{userId:e})}async getCallStatus(t){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}`,"GET")}async request(t,e,i){const n=`${this.baseUrl}${t}`;let s,a=0;for(;a<this.retries;){a+=1;const t=new AbortController,o=window.setTimeout(()=>t.abort(),this.timeoutMs);try{const a=await fetch(n,{method:e,headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}},body:i?JSON.stringify(i):void 0,signal:t.signal});if(window.clearTimeout(o),a.ok){return await this.safeJson(a)}{const t=await this.safeJson(a);s={status:a.status,data:t},this.emit("error",{type:"network",message:"http_error",originalError:s})}}catch(t){window.clearTimeout(o),s=t,this.emit("error",{type:"network",message:"fetch_error",originalError:t})}}throw s??new Error("request_failed")}async safeJson(t){try{return await t.json()}catch(t){return null}}}class n extends t{async connectToRoom(t,e,i){const n=await import("livekit-client");console.log(t,e),console.log(n);const s=n.Room,a=n.RoomEvent;this.room=new s(i),await this.room.connect(t,e),this.room.on(a.TrackSubscribed,(t,e,i)=>{this.emit("media:track-added",{participantId:i.sid,trackType:t.kind})}),this.room.on(a.TrackUnsubscribed,(t,e,i)=>{this.emit("media:track-removed",{participantId:i.sid,trackType:t.kind})}),this.room.on(a.Disconnected,()=>{this.emit("disconnected")})}disconnectFromRoom(){this.room&&(this.room.disconnect(),this.room=void 0)}async enableMicrophone(){this.room&&(await this.room.localParticipant.setMicrophoneEnabled(!0),this.emit("media:microphone-changed",{enabled:!0}))}async disableMicrophone(){this.room&&(await this.room.localParticipant.setMicrophoneEnabled(!1),this.emit("media:microphone-changed",{enabled:!1}))}async toggleMicrophone(){if(!this.room)return!1;const t=this.room.localParticipant.isMicrophoneEnabled;return await this.room.localParticipant.setMicrophoneEnabled(!t),this.emit("media:microphone-changed",{enabled:!t}),!t}async enableVideo(){this.room&&(await this.room.localParticipant.setCameraEnabled(!0),this.emit("media:video-changed",{enabled:!0}))}async disableVideo(){this.room&&(await this.room.localParticipant.setCameraEnabled(!1),this.emit("media:video-changed",{enabled:!1}))}async toggleVideo(){if(!this.room)return!1;const t=this.room.localParticipant.isCameraEnabled;return await this.room.localParticipant.setCameraEnabled(!t),this.emit("media:video-changed",{enabled:!t}),!t}getLocalTracks(){return this.room?Array.from(this.room.localParticipant.tracks.values()).map(t=>t.track).filter(Boolean):[]}getRemoteTracks(){if(!this.room)return[];const t=[];for(const e of this.room.participants.values())for(const i of e.tracks.values())i.track&&t.push(i.track);return t}getRoom(){return this.room}attachTrack(t,e){if(t)try{t.attach(e)}catch(t){}}detachTrack(t,e){if(t)try{t.detach(e)}catch(t){}}}class s{constructor(t){this.vol=1,this.playing=!1,t?.incoming&&(this.incoming=new Audio(t.incoming),this.incoming.loop=!0,this.incoming.volume=this.vol),t?.outgoing&&(this.outgoing=new Audio(t.outgoing),this.outgoing.loop=!0,this.outgoing.volume=this.vol)}preloadRingtones(){this.incoming&&this.incoming.load(),this.outgoing&&this.outgoing.load()}async playIncoming(){if(this.incoming)try{await this.incoming.play(),this.playing=!0}catch(t){this.playing=!1}}async playOutgoing(){if(this.outgoing)try{await this.outgoing.play(),this.playing=!0}catch(t){this.playing=!1}}stop(){this.incoming&&(this.incoming.pause(),this.incoming.currentTime=0),this.outgoing&&(this.outgoing.pause(),this.outgoing.currentTime=0),this.playing=!1}setVolume(t){this.vol=Math.max(0,Math.min(1,t)),this.incoming&&(this.incoming.volume=this.vol),this.outgoing&&(this.outgoing.volume=this.vol)}isPlaying(){return this.playing}}class a extends t{constructor(t,e,i,n,s,a){super(),this.state="idle",this.currentCall=null,this.api=t,this.ws=e,this.livekit=i,this.audio=n,this.userId=s,this.userName=a,this.ws.on("message",t=>this.handleSocketMessage(t))}getCallState(){return this.state}getCurrentCall(){return this.currentCall}async initiateCall(t,e,i){if("idle"===this.state){this.setState("calling"),await this.audio.playOutgoing();try{const n=await this.api.initiateCall({callerId:this.userId,callerName:this.userName,calleeId:t,calleeName:e,isVideo:i}),s={callId:n?.callId??"",roomName:n?.roomName??"",token:n?.token??"",livekitUrl:n?.livekitUrl??"",participantId:this.userId,participantName:this.userName,calleeId:t,calleeName:e,isVideo:i,startTime:Date.now()};console.log(s),this.currentCall=s,this.emit("call_started",{callId:s.callId,participants:[this.userId,t]}),this.setState("connecting"),await this.livekit.connectToRoom(s.livekitUrl,s.token,{connect:!0}),this.audio.stop(),this.setState("connected")}catch(t){this.audio.stop(),this.emit("call:error",{error:t}),this.setState("idle")}}}async acceptCall(t){if("ringing"===this.state)try{await this.api.acceptCall(t,this.userId),this.setState("connecting")}catch(e){this.emit("call:error",{callId:t,error:e})}}async declineCall(t){if("ringing"===this.state)try{await this.api.declineCall(t,this.userId),this.audio.stop(),this.emit("call_declined",{callId:t}),this.cleanupCall()}catch(e){this.emit("call:error",{callId:t,error:e})}}async endCall(t){if(this.currentCall&&this.currentCall.callId===t&&("connected"===this.state||"connecting"===this.state)){try{await this.api.endCall(t,this.userId)}catch(t){}this.finishCall()}}async handleIncomingCall(t){if("idle"!==this.state)return;this.setState("ringing"),await this.audio.playIncoming();const e={callId:t.callId,roomName:t.roomName??"",token:t.token??"",livekitUrl:t.livekitUrl??"",participantId:t.callerId,participantName:t.callerName,isVideo:!!t.isVideo};this.currentCall=e,this.emit("incoming_call",{callId:e.callId,callerId:e.participantId,callerName:e.participantName,isVideo:e.isVideo})}async handleAccepted(){if(this.currentCall){this.setState("connecting");try{await this.livekit.connectToRoom(this.currentCall.livekitUrl,this.currentCall.token,{connect:!0}),this.audio.stop(),this.setState("connected"),this.emit("call_accepted",{callId:this.currentCall.callId})}catch(t){this.emit("call:error",{callId:this.currentCall.callId,error:t})}}}handleDeclined(){this.currentCall&&(this.audio.stop(),this.emit("call_declined",{callId:this.currentCall.callId}),this.cleanupCall())}handleMissed(){this.currentCall&&(this.audio.stop(),this.emit("call_missed",{callId:this.currentCall.callId}),this.cleanupCall())}handleEnded(){this.currentCall&&this.finishCall()}handleSocketMessage(t){const e=t?.type;"incoming_call"===e&&this.handleIncomingCall(t.data??t),"call_accepted"===e&&this.handleAccepted(),"call_declined"===e&&this.handleDeclined(),"call_missed"===e&&this.handleMissed(),"call_ended"===e&&this.handleEnded()}setState(t){const e=this.state;this.state=t,this.emit("state:changed",{oldState:e,newState:t})}finishCall(){const t=this.currentCall?.callId??"",e=this.currentCall?.startTime??Date.now(),i=Math.max(0,Math.floor((Date.now()-e)/1e3));this.livekit.disconnectFromRoom(),this.emit("call_ended",{callId:t,duration:i}),this.cleanupCall()}cleanupCall(){this.currentCall=null,this.setState("idle")}}function o(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.callerName||"Unknown";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=e?.callerName?`${e.callerName}`:"Incoming Call";const r=document.createElement("div");r.className="voip-subtitle",r.textContent=e?.isVideo?"Incoming video call":"Incoming audio call",c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const h=document.createElement("div");h.className="voip-actions";const d=document.createElement("button");d.className="voip-btn voip-btn-accept",d.textContent="Accept";const m=document.createElement("button");m.className="voip-btn voip-btn-decline",m.textContent="Decline";const u=()=>{window.removeEventListener("keydown",p)},p=n=>{"Enter"===n.key&&(e?.callId&&t.acceptCall(e.callId),i.remove(),u()),"Escape"===n.key&&(e?.callId&&t.declineCall(e.callId),i.remove(),u())};return window.addEventListener("keydown",p),d.onclick=()=>{e?.callId&&t.acceptCall(e.callId),i.remove(),u()},m.onclick=()=>{e?.callId&&t.declineCall(e.callId),i.remove(),u()},h.appendChild(d),h.appendChild(m),n.appendChild(s),n.appendChild(h),i.appendChild(n),i}function c(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.calleeName||"Unknown";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=e?.calleeName?`Calling ${e.calleeName}`:"Calling";const r=document.createElement("div");r.className="voip-subtitle",r.textContent=e?.isVideo?"Video call":"Audio call",c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const h=document.createElement("div");h.className="voip-actions";const d=document.createElement("button");return d.className="voip-btn voip-btn-cancel",d.textContent="Cancel",d.onclick=()=>{e?.callId&&t.endCall(e.callId),i.remove()},h.appendChild(d),n.appendChild(s),n.appendChild(h),i.appendChild(n),i}function l(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card",n.style.position="relative";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.participantName||e?.calleeName||e?.callerName||"Participant";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=o;const r=document.createElement("div");r.className="voip-subtitle";const h=document.createElement("span");h.className="voip-timer",h.textContent="00:00";const d="number"==typeof e?.startTime?e.startTime:Date.now(),m=()=>{const t=Math.max(0,Math.floor((Date.now()-d)/1e3)),e=String(Math.floor(t/60)).padStart(2,"0"),i=String(t%60).padStart(2,"0");h.textContent=`${e}:${i}`},u=window.setInterval(m,1e3);m(),r.textContent="In call • ",r.appendChild(h),c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const p=document.createElement("div");p.style.display="grid",p.style.gap="16px";const g=()=>{p.style.gridTemplateColumns=window.innerWidth>=768?"1fr 1fr":"1fr"};g();const v=()=>g();window.addEventListener("resize",v);const C=document.createElement("div"),w=document.createElement("div");w.className="voip-video-container";const y=document.createElement("video");y.autoplay=!0,y.playsInline=!0,w.appendChild(y),C.appendChild(w);const k=document.createElement("div");k.style.background="#1f2937",k.style.borderRadius="12px",k.style.padding="16px",k.style.height="260px",k.style.display="flex",k.style.alignItems="center",k.style.justifyContent="center";const f=document.createElement("video");f.autoplay=!0,f.muted=!0,f.playsInline=!0,f.style.maxWidth="100%",f.style.maxHeight="100%",k.appendChild(f),p.appendChild(C),p.appendChild(k);const I=document.createElement("div");I.className="voip-controls",I.style.position="absolute",I.style.left="0",I.style.right="0",I.style.bottom="16px",I.style.display="flex",I.style.justifyContent="center",I.style.gap="12px";let b=!0;const E=document.createElement("button");E.className="voip-btn voip-btn-primary";const T=()=>E.textContent=b?"Mute":"Unmute";T(),E.onclick=async()=>{const e=await t.toggleMicrophone();b=e,T()};let N=!1;const S=document.createElement("button");S.className="voip-btn voip-btn-primary";const M=()=>S.textContent=N?"Hide Video":"Show Video";M(),S.onclick=async()=>{const e=await t.toggleVideo();N=e,M()};const x=document.createElement("button");return x.className="voip-btn voip-btn-decline",x.textContent="End Call",x.onclick=()=>{e?.callId&&t.endCall(e.callId),i.remove(),window.clearInterval(u),window.removeEventListener("resize",v)},I.appendChild(E),I.appendChild(S),I.appendChild(x),n.appendChild(s),n.appendChild(p),n.appendChild(I),i.appendChild(n),i}class r{constructor(t){this.client=t}show(){this.ensureRoot()}hide(){this.root?.remove(),this.root=void 0}setTheme(t){this.ensureRoot(),this.root?this.root.setAttribute("data-voip-theme",t):document.documentElement.setAttribute("data-voip-theme",t)}showIncomingCall(t){this.ensureRoot();const e=o(this.client,t);this.root.appendChild(e)}showOutgoingCall(t){this.ensureRoot();const e=c(this.client,t);this.root.appendChild(e)}showActiveCall(t){this.ensureRoot();const e=l(this.client,t);this.root.appendChild(e);const i=e.querySelectorAll("video"),n=i[0],s=i[1],a=this.client.getRemoteTracks(),o=this.client.getLocalTracks(),c=a.find(t=>"video"===t?.kind)??a[0],r=o.find(t=>"video"===t?.kind)??o[0];c&&n&&this.client.attachTrack(c,n),r&&s&&this.client.attachTrack(r,s)}hideAllModals(){if(this.root)for(;this.root.firstChild;)this.root.removeChild(this.root.firstChild)}destroy(){this.hide()}ensureRoot(){this.root&&document.body.contains(this.root)||(this.root=document.createElement("div"),document.body.appendChild(this.root))}}class h{constructor(o){this.emitter=new t,this.microphoneEnabled=!0,this.videoEnabled=!1,this.config={enableUI:!0,autoReconnect:!0,debug:!1,...o},this.ws=new e(this.config.wsUrl,this.config.authToken,!!this.config.autoReconnect,!!this.config.debug,this.config.userId),this.api=new i(this.config.backendUrl,this.config.authToken),this.livekit=new n,this.audio=new s(this.config.ringTones),this.calls=new a(this.api,this.ws,this.livekit,this.audio,this.config.userId,this.config.userName),this.config.enableUI&&(this.ui=new r(this)),this.bindEvents(),this.audio.preloadRingtones()}bindEvents(){const t=t=>e=>this.emitter.emit(t,e);this.ws.on("connected",t("connected")),this.ws.on("disconnected",t("disconnected")),this.ws.on("reconnecting",t("reconnecting")),this.ws.on("error",t("error")),this.calls.on("state:changed",e=>{if(t("state:changed")(e),!this.ui)return;const i=e?.newState;"calling"===i||"connecting"===i?(this.ui.hideAllModals(),this.ui.showOutgoingCall(this.calls.getCurrentCall()??{})):"connected"===i?(this.ui.hideAllModals(),this.ui.showActiveCall(this.calls.getCurrentCall()??{})):"idle"!==i&&"ending"!==i||this.ui.hideAllModals()}),this.calls.on("incoming_call",e=>{t("incoming_call")(e),this.ui&&(this.ui.hideAllModals(),this.ui.showIncomingCall(e))}),this.calls.on("call_started",e=>{t("call_started")(e)}),this.calls.on("call_accepted",e=>{t("call_accepted")(e)}),this.calls.on("call_declined",e=>{t("call_declined")(e),this.ui&&this.ui.hideAllModals()}),this.calls.on("call_missed",e=>{t("call_missed")(e),this.ui&&this.ui.hideAllModals()}),this.calls.on("call_ended",e=>{t("call_ended")(e),this.ui&&this.ui.hideAllModals()}),this.livekit.on("media:microphone-changed",t=>{this.microphoneEnabled=!!t?.enabled,this.emitter.emit("media:microphone-changed",t)}),this.livekit.on("media:video-changed",t=>{this.videoEnabled=!!t?.enabled,this.emitter.emit("media:video-changed",t)})}async connect(){await this.ws.connect()}disconnect(){this.ws.disconnect()}isConnected(){return this.ws.isConnected()}async initiateCall(t,e,i){await this.calls.initiateCall(t,e,!!i?.isVideo)}async acceptCall(t){await this.calls.acceptCall(t)}async declineCall(t){await this.calls.declineCall(t)}async endCall(t){await this.calls.endCall(t)}async toggleMicrophone(){return await this.livekit.toggleMicrophone()}async toggleVideo(){return await this.livekit.toggleVideo()}async setMicrophoneEnabled(t){t?await this.livekit.enableMicrophone():await this.livekit.disableMicrophone()}async setVideoEnabled(t){t?await this.livekit.enableVideo():await this.livekit.disableVideo()}getCallState(){return this.calls.getCallState()}getActiveCall(){return this.calls.getCurrentCall()}isInCall(){return"connected"===this.getCallState()||"connecting"===this.getCallState()||"ringing"===this.getCallState()||"calling"===this.getCallState()}getLocalTracks(){return this.livekit.getLocalTracks()}getRemoteTracks(){return this.livekit.getRemoteTracks()}attachTrack(t,e){this.livekit.attachTrack(t,e)}detachTrack(t,e){this.livekit.detachTrack(t,e)}on(t,e){this.emitter.on(t,e)}off(t,e){this.emitter.off(t,e)}once(t,e){this.emitter.once(t,e)}showUI(){this.ui||(this.ui=new r(this)),this.ui.show()}hideUI(){this.ui?.hide()}setTheme(t){this.config.theme=t,this.ui?.setTheme(t)}destroy(){this.disconnect(),this.hideUI()}}export{i as APIClient,s as AudioManager,a as CallManager,t as EventEmitter,n as LiveKitManager,e as WebSocketManager,h as ZerorateVoIPClient,h as default};
|
|
1
|
+
class t{constructor(){this.handlers=new Map}on(t,e){this.handlers.has(t)||this.handlers.set(t,new Set),this.handlers.get(t).add(e)}off(t,e){const i=this.handlers.get(t);i&&(i.delete(e),0===i.size&&this.handlers.delete(t))}once(t,e){const i=n=>{this.off(t,i),e(n)};this.on(t,i)}emit(t,e){const i=this.handlers.get(t);if(i)for(const t of Array.from(i))t(e)}}class e extends t{constructor(t,e,i=!0,n=!1,s){super(),this.state="disconnected",this.queue=[],this.reconnectAttempts=0,this.url=t,this.authToken=e,this.autoReconnect=i,this.debug=n,this.userId=s}getState(){return this.state}isConnected(){return"connected"===this.state}async connect(){if(!this.ws||"connecting"!==this.state&&"connected"!==this.state){this.state="connecting";try{this.ws=new WebSocket(this.url),this.ws.onopen=()=>{this.state="connected",this.emit("connected"),this.userId&&this.authToken?this.send({type:"authenticate",userId:this.userId,token:this.authToken}):this.authToken&&this.send({type:"authenticate",token:this.authToken}),this.flushQueue(),this.startHeartbeat(),this.reconnectAttempts=0},this.ws.onmessage=t=>{if("string"==typeof t.data)try{const e=JSON.parse(t.data);this.emit("message",e)}catch(e){this.emit("message",t.data)}else this.emit("message",t.data)},this.ws.onerror=()=>{this.state="error",this.emit("error",{type:"network"})},this.ws.onclose=()=>{this.stopHeartbeat();const t="connected"===this.state;this.state="disconnected",this.emit("disconnected"),this.autoReconnect&&this.scheduleReconnect(t)}}catch(t){this.state="error",this.emit("error",{type:"network"})}}}disconnect(){if(this.autoReconnect=!1,this.stopHeartbeat(),this.ws&&("connecting"===this.state||"connected"===this.state))try{this.ws.close()}catch(t){}this.ws=void 0,this.state="disconnected",this.emit("disconnected")}send(t){const e="string"==typeof t?t:JSON.stringify(t);if(this.ws&&"connected"===this.state)try{this.ws.send(e)}catch(t){this.queue.push(e)}else this.queue.push(e)}flushQueue(){if(this.ws&&"connected"===this.state)for(const t of this.queue.splice(0))try{this.ws.send(t)}catch(t){}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatId=window.setInterval(()=>{if(this.ws&&"connected"===this.state)try{this.ws.send(JSON.stringify({type:"heartbeat"})),this.pongTimeoutId&&window.clearTimeout(this.pongTimeoutId),this.pongTimeoutId=window.setTimeout(()=>{try{this.ws?.close()}catch(t){}},5e3)}catch(t){}},3e4)}stopHeartbeat(){this.heartbeatId&&(window.clearInterval(this.heartbeatId),this.heartbeatId=void 0),this.pongTimeoutId&&(window.clearTimeout(this.pongTimeoutId),this.pongTimeoutId=void 0)}scheduleReconnect(t){this.reconnectAttempts+=1;const e=t?1:2,i=Math.min(3e4,1e3*Math.pow(2,this.reconnectAttempts-1)*e);this.emit("reconnecting",{attempt:this.reconnectAttempts,delay:i}),window.setTimeout(()=>{this.autoReconnect&&this.connect()},i)}}class i extends t{constructor(t,e){super(),this.timeoutMs=1e4,this.retries=3,this.baseUrl=t.replace(/\/+$/,""),this.authToken=e}setAuthToken(t){this.authToken=t}async initiateCall(t){return this.request("/api/webrtc/v1/calls/initiate","POST",t)}async acceptCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/accept`,"POST",{userId:e})}async declineCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/decline`,"POST",{userId:e})}async endCall(t,e){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}/end`,"POST",{userId:e})}async getCallStatus(t){return this.request(`/api/webrtc/v1/calls/${encodeURIComponent(t)}`,"GET")}async request(t,e,i){const n=`${this.baseUrl}${t}`;let s,a=0;for(;a<this.retries;){a+=1;const t=new AbortController,o=window.setTimeout(()=>t.abort(),this.timeoutMs);try{const a=await fetch(n,{method:e,headers:{"Content-Type":"application/json",...this.authToken?{Authorization:`Bearer ${this.authToken}`}:{}},body:i?JSON.stringify(i):void 0,signal:t.signal});if(window.clearTimeout(o),a.ok){return await this.safeJson(a)}{const t=await this.safeJson(a);s={status:a.status,data:t},this.emit("error",{type:"network",message:"http_error",originalError:s})}}catch(t){window.clearTimeout(o),s=t,this.emit("error",{type:"network",message:"fetch_error",originalError:t})}}throw s??new Error("request_failed")}async safeJson(t){try{return await t.json()}catch(t){return null}}}class n extends t{async connectToRoom(t,e,i){console.log(t,e);const n=await import("livekit-client"),s=n.Room,a=n.RoomEvent;this.room=new s(i),await this.room.connect(t,e),this.room.on(a.TrackSubscribed,(t,e,i)=>{this.emit("media:track-added",{participantId:i.sid,trackType:t.kind})}),this.room.on(a.TrackUnsubscribed,(t,e,i)=>{this.emit("media:track-removed",{participantId:i.sid,trackType:t.kind})}),this.room.on(a.Disconnected,()=>{this.emit("disconnected")})}disconnectFromRoom(){this.room&&(this.room.disconnect(),this.room=void 0)}async enableMicrophone(){this.room&&(await this.room.localParticipant.setMicrophoneEnabled(!0),this.emit("media:microphone-changed",{enabled:!0}))}async disableMicrophone(){this.room&&(await this.room.localParticipant.setMicrophoneEnabled(!1),this.emit("media:microphone-changed",{enabled:!1}))}async toggleMicrophone(){if(!this.room)return!1;const t=this.room.localParticipant.isMicrophoneEnabled;return await this.room.localParticipant.setMicrophoneEnabled(!t),this.emit("media:microphone-changed",{enabled:!t}),!t}async enableVideo(){this.room&&(await this.room.localParticipant.setCameraEnabled(!0),this.emit("media:video-changed",{enabled:!0}))}async disableVideo(){this.room&&(await this.room.localParticipant.setCameraEnabled(!1),this.emit("media:video-changed",{enabled:!1}))}async toggleVideo(){if(!this.room)return!1;const t=this.room.localParticipant.isCameraEnabled;return await this.room.localParticipant.setCameraEnabled(!t),this.emit("media:video-changed",{enabled:!t}),!t}getLocalTracks(){return this.room?Array.from(this.room.localParticipant.tracks.values()).map(t=>t.track).filter(Boolean):[]}getRemoteTracks(){if(!this.room)return[];const t=[];for(const e of this.room.participants.values())for(const i of e.tracks.values())i.track&&t.push(i.track);return t}getRoom(){return this.room}attachTrack(t,e){if(t)try{t.attach(e)}catch(t){}}detachTrack(t,e){if(t)try{t.detach(e)}catch(t){}}}class s{constructor(t){this.vol=1,this.playing=!1,t?.incoming&&(this.incoming=new Audio(t.incoming),this.incoming.loop=!0,this.incoming.volume=this.vol),t?.outgoing&&(this.outgoing=new Audio(t.outgoing),this.outgoing.loop=!0,this.outgoing.volume=this.vol)}preloadRingtones(){this.incoming&&this.incoming.load(),this.outgoing&&this.outgoing.load()}async playIncoming(){if(this.incoming)try{await this.incoming.play(),this.playing=!0}catch(t){this.playing=!1}}async playOutgoing(){if(this.outgoing)try{await this.outgoing.play(),this.playing=!0}catch(t){this.playing=!1}}stop(){this.incoming&&(this.incoming.pause(),this.incoming.currentTime=0),this.outgoing&&(this.outgoing.pause(),this.outgoing.currentTime=0),this.playing=!1}setVolume(t){this.vol=Math.max(0,Math.min(1,t)),this.incoming&&(this.incoming.volume=this.vol),this.outgoing&&(this.outgoing.volume=this.vol)}isPlaying(){return this.playing}}class a extends t{constructor(t,e,i,n,s,a){super(),this.state="idle",this.currentCall=null,this.api=t,this.ws=e,this.livekit=i,this.audio=n,this.userId=s,this.userName=a,this.ws.on("message",t=>this.handleSocketMessage(t))}getCallState(){return this.state}getCurrentCall(){return this.currentCall}async initiateCall(t,e,i){if("idle"===this.state){this.setState("calling"),await this.audio.playOutgoing();try{const n=await this.api.initiateCall({callerId:this.userId,callerName:this.userName,calleeId:t,calleeName:e,isVideo:i}),s={callId:n?.callId??"",roomName:n?.roomName??"",token:n?.token??"",livekitUrl:n?.livekitUrl??"",participantId:this.userId,participantName:this.userName,calleeId:t,calleeName:e,isVideo:i,startTime:Date.now()};this.currentCall=s}catch(t){this.audio.stop(),this.emit("call:error",{error:t}),this.setState("idle")}}}async acceptCall(t){if("ringing"===this.state)try{await this.api.acceptCall(t,this.userId),this.setState("connecting")}catch(e){this.emit("call:error",{callId:t,error:e})}}async declineCall(t){if("ringing"===this.state)try{await this.api.declineCall(t,this.userId),this.audio.stop(),this.emit("call_declined",{callId:t}),this.cleanupCall()}catch(e){this.emit("call:error",{callId:t,error:e})}}async endCall(t){if(this.currentCall&&this.currentCall.callId===t&&("connected"===this.state||"connecting"===this.state)){try{await this.api.endCall(t,this.userId)}catch(t){}this.finishCall()}}async handleIncomingCall(t){if("idle"!==this.state)return;this.setState("ringing"),await this.audio.playIncoming();const e={callId:t.callId,roomName:t.roomName??"",token:t.token??"",livekitUrl:t.livekitUrl??"",participantId:t.callerId,participantName:t.callerName,isVideo:!!t.isVideo};this.currentCall=e,this.emit("incoming_call",{callId:e.callId,callerId:e.participantId,callerName:e.participantName,isVideo:e.isVideo})}async handleAccepted(){if(this.currentCall){this.setState("connecting");try{await this.livekit.connectToRoom(this.currentCall.livekitUrl,this.currentCall.token,{connect:!0}),this.audio.stop(),this.setState("connected"),this.emit("call_accepted",{callId:this.currentCall.callId})}catch(t){console.error("Some error occured:"),console.log(t),this.emit("call:error",{callId:this.currentCall.callId,error:t})}}}handleDeclined(){this.currentCall&&(this.audio.stop(),this.emit("call_declined",{callId:this.currentCall.callId}),this.cleanupCall())}handleMissed(){this.currentCall&&(this.audio.stop(),this.emit("call_missed",{callId:this.currentCall.callId}),this.cleanupCall())}handleEnded(){this.currentCall&&this.finishCall()}handleSocketMessage(t){const e=t?.type;"incoming_call"===e&&this.handleIncomingCall(t.data??t),"call_accepted"===e&&this.handleAccepted(),"call_declined"===e&&this.handleDeclined(),"call_missed"===e&&this.handleMissed(),"call_ended"===e&&this.handleEnded()}setState(t){const e=this.state;this.state=t,this.emit("state:changed",{oldState:e,newState:t})}finishCall(){const t=this.currentCall?.callId??"",e=this.currentCall?.startTime??Date.now(),i=Math.max(0,Math.floor((Date.now()-e)/1e3));this.livekit.disconnectFromRoom(),this.emit("call_ended",{callId:t,duration:i}),this.cleanupCall()}cleanupCall(){this.currentCall=null,this.setState("idle")}}function o(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.callerName||"Unknown";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=e?.callerName?`${e.callerName}`:"Incoming Call";const r=document.createElement("div");r.className="voip-subtitle",r.textContent=e?.isVideo?"Incoming video call":"Incoming audio call",c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const h=document.createElement("div");h.className="voip-actions";const d=document.createElement("button");d.className="voip-btn voip-btn-accept",d.textContent="Accept";const m=document.createElement("button");m.className="voip-btn voip-btn-decline",m.textContent="Decline";const u=()=>{window.removeEventListener("keydown",p)},p=n=>{"Enter"===n.key&&(e?.callId&&t.acceptCall(e.callId),i.remove(),u()),"Escape"===n.key&&(e?.callId&&t.declineCall(e.callId),i.remove(),u())};return window.addEventListener("keydown",p),d.onclick=()=>{e?.callId&&t.acceptCall(e.callId),i.remove(),u()},m.onclick=()=>{e?.callId&&t.declineCall(e.callId),i.remove(),u()},h.appendChild(d),h.appendChild(m),n.appendChild(s),n.appendChild(h),i.appendChild(n),i}function c(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.calleeName||"Unknown";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=e?.calleeName?`Calling ${e.calleeName}`:"Calling";const r=document.createElement("div");r.className="voip-subtitle",r.textContent=e?.isVideo?"Video call":"Audio call",c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const h=document.createElement("div");h.className="voip-actions";const d=document.createElement("button");return d.className="voip-btn voip-btn-cancel",d.textContent="Cancel",d.onclick=()=>{e?.callId&&t.endCall(e.callId),i.remove()},h.appendChild(d),n.appendChild(s),n.appendChild(h),i.appendChild(n),i}function l(t,e){const i=document.createElement("div");i.className="voip-overlay";const n=document.createElement("div");n.className="voip-card",n.style.position="relative";const s=document.createElement("div");s.className="voip-header";const a=document.createElement("div");a.className="voip-avatar";const o=e?.participantName||e?.calleeName||e?.callerName||"Participant";a.textContent=function(t){const e=String(t).trim().split(/\s+/),i=e[0]?.[0]||"",n=e.length>1?e[e.length-1][0]:"";return(i+n).toUpperCase()||"U"}(o);const c=document.createElement("div");c.className="voip-meta";const l=document.createElement("div");l.className="voip-title",l.textContent=o;const r=document.createElement("div");r.className="voip-subtitle";const h=document.createElement("span");h.className="voip-timer",h.textContent="00:00";const d="number"==typeof e?.startTime?e.startTime:Date.now(),m=()=>{const t=Math.max(0,Math.floor((Date.now()-d)/1e3)),e=String(Math.floor(t/60)).padStart(2,"0"),i=String(t%60).padStart(2,"0");h.textContent=`${e}:${i}`},u=window.setInterval(m,1e3);m(),r.textContent="In call • ",r.appendChild(h),c.appendChild(l),c.appendChild(r),s.appendChild(a),s.appendChild(c);const p=document.createElement("div");p.style.display="grid",p.style.gap="16px";const g=()=>{p.style.gridTemplateColumns=window.innerWidth>=768?"1fr 1fr":"1fr"};g();const v=()=>g();window.addEventListener("resize",v);const C=document.createElement("div"),w=document.createElement("div");w.className="voip-video-container";const y=document.createElement("video");y.autoplay=!0,y.playsInline=!0,w.appendChild(y),C.appendChild(w);const f=document.createElement("div");f.style.background="#1f2937",f.style.borderRadius="12px",f.style.padding="16px",f.style.height="260px",f.style.display="flex",f.style.alignItems="center",f.style.justifyContent="center";const k=document.createElement("video");k.autoplay=!0,k.muted=!0,k.playsInline=!0,k.style.maxWidth="100%",k.style.maxHeight="100%",f.appendChild(k),p.appendChild(C),p.appendChild(f);const b=document.createElement("div");b.className="voip-controls",b.style.position="absolute",b.style.left="0",b.style.right="0",b.style.bottom="16px",b.style.display="flex",b.style.justifyContent="center",b.style.gap="12px";let I=!0;const E=document.createElement("button");E.className="voip-btn voip-btn-primary";const T=()=>E.textContent=I?"Mute":"Unmute";T(),E.onclick=async()=>{const e=await t.toggleMicrophone();I=e,T()};let N=!1;const S=document.createElement("button");S.className="voip-btn voip-btn-primary";const M=()=>S.textContent=N?"Hide Video":"Show Video";M(),S.onclick=async()=>{const e=await t.toggleVideo();N=e,M()};const x=document.createElement("button");return x.className="voip-btn voip-btn-decline",x.textContent="End Call",x.onclick=()=>{e?.callId&&t.endCall(e.callId),i.remove(),window.clearInterval(u),window.removeEventListener("resize",v)},b.appendChild(E),b.appendChild(S),b.appendChild(x),n.appendChild(s),n.appendChild(p),n.appendChild(b),i.appendChild(n),i}class r{constructor(t){this.client=t}show(){this.ensureRoot()}hide(){this.root?.remove(),this.root=void 0}setTheme(t){this.ensureRoot(),this.root?this.root.setAttribute("data-voip-theme",t):document.documentElement.setAttribute("data-voip-theme",t)}showIncomingCall(t){this.ensureRoot();const e=o(this.client,t);this.root.appendChild(e)}showOutgoingCall(t){this.ensureRoot();const e=c(this.client,t);this.root.appendChild(e)}showActiveCall(t){this.ensureRoot();const e=l(this.client,t);this.root.appendChild(e);const i=e.querySelectorAll("video"),n=i[0],s=i[1],a=this.client.getRemoteTracks(),o=this.client.getLocalTracks(),c=a.find(t=>"video"===t?.kind)??a[0],r=o.find(t=>"video"===t?.kind)??o[0];c&&n&&this.client.attachTrack(c,n),r&&s&&this.client.attachTrack(r,s)}hideAllModals(){if(this.root)for(;this.root.firstChild;)this.root.removeChild(this.root.firstChild)}destroy(){this.hide()}ensureRoot(){this.root&&document.body.contains(this.root)||(this.root=document.createElement("div"),document.body.appendChild(this.root))}}class h{constructor(o){this.emitter=new t,this.microphoneEnabled=!0,this.videoEnabled=!1,this.config={enableUI:!0,autoReconnect:!0,debug:!1,...o},this.ws=new e(this.config.wsUrl,this.config.authToken,!!this.config.autoReconnect,!!this.config.debug,this.config.userId),this.api=new i(this.config.backendUrl,this.config.authToken),this.livekit=new n,this.audio=new s(this.config.ringTones),this.calls=new a(this.api,this.ws,this.livekit,this.audio,this.config.userId,this.config.userName),this.config.enableUI&&(this.ui=new r(this)),this.bindEvents(),this.audio.preloadRingtones()}bindEvents(){const t=t=>e=>this.emitter.emit(t,e);this.ws.on("connected",t("connected")),this.ws.on("disconnected",t("disconnected")),this.ws.on("reconnecting",t("reconnecting")),this.ws.on("error",t("error")),this.calls.on("state:changed",e=>{if(t("state:changed")(e),!this.ui)return;const i=e?.newState;"calling"===i||"connecting"===i?(this.ui.hideAllModals(),this.ui.showOutgoingCall(this.calls.getCurrentCall()??{})):"connected"===i?(this.ui.hideAllModals(),this.ui.showActiveCall(this.calls.getCurrentCall()??{})):"idle"!==i&&"ending"!==i||this.ui.hideAllModals()}),this.calls.on("incoming_call",e=>{t("incoming_call")(e),this.ui&&(this.ui.hideAllModals(),this.ui.showIncomingCall(e))}),this.calls.on("call_started",e=>{t("call_started")(e)}),this.calls.on("call_accepted",e=>{t("call_accepted")(e)}),this.calls.on("call_declined",e=>{t("call_declined")(e),this.ui&&this.ui.hideAllModals()}),this.calls.on("call_missed",e=>{t("call_missed")(e),this.ui&&this.ui.hideAllModals()}),this.calls.on("call_ended",e=>{t("call_ended")(e),this.ui&&this.ui.hideAllModals()}),this.livekit.on("media:microphone-changed",t=>{this.microphoneEnabled=!!t?.enabled,this.emitter.emit("media:microphone-changed",t)}),this.livekit.on("media:video-changed",t=>{this.videoEnabled=!!t?.enabled,this.emitter.emit("media:video-changed",t)})}async connect(){await this.ws.connect()}disconnect(){this.ws.disconnect()}isConnected(){return this.ws.isConnected()}async initiateCall(t,e,i){await this.calls.initiateCall(t,e,!!i?.isVideo)}async acceptCall(t){await this.calls.acceptCall(t)}async declineCall(t){await this.calls.declineCall(t)}async endCall(t){await this.calls.endCall(t)}async toggleMicrophone(){return await this.livekit.toggleMicrophone()}async toggleVideo(){return await this.livekit.toggleVideo()}async setMicrophoneEnabled(t){t?await this.livekit.enableMicrophone():await this.livekit.disableMicrophone()}async setVideoEnabled(t){t?await this.livekit.enableVideo():await this.livekit.disableVideo()}getCallState(){return this.calls.getCallState()}getActiveCall(){return this.calls.getCurrentCall()}isInCall(){return"connected"===this.getCallState()||"connecting"===this.getCallState()||"ringing"===this.getCallState()||"calling"===this.getCallState()}getLocalTracks(){return this.livekit.getLocalTracks()}getRemoteTracks(){return this.livekit.getRemoteTracks()}attachTrack(t,e){this.livekit.attachTrack(t,e)}detachTrack(t,e){this.livekit.detachTrack(t,e)}on(t,e){this.emitter.on(t,e)}off(t,e){this.emitter.off(t,e)}once(t,e){this.emitter.once(t,e)}showUI(){this.ui||(this.ui=new r(this)),this.ui.show()}hideUI(){this.ui?.hide()}setTheme(t){this.config.theme=t,this.ui?.setTheme(t)}destroy(){this.disconnect(),this.hideUI()}}export{i as APIClient,s as AudioManager,a as CallManager,t as EventEmitter,n as LiveKitManager,e as WebSocketManager,h as ZerorateVoIPClient,h as default};
|
package/dist/voip-sdk.js
CHANGED
|
@@ -282,9 +282,8 @@
|
|
|
282
282
|
|
|
283
283
|
class LiveKitManager extends EventEmitter {
|
|
284
284
|
async connectToRoom(livekitUrl, token, options) {
|
|
285
|
-
const mod = await import('livekit-client');
|
|
286
285
|
console.log(livekitUrl, token);
|
|
287
|
-
|
|
286
|
+
const mod = await import('livekit-client');
|
|
288
287
|
const Room = mod.Room;
|
|
289
288
|
const RoomEvent = mod.RoomEvent;
|
|
290
289
|
this.room = new Room(options);
|
|
@@ -503,18 +502,17 @@
|
|
|
503
502
|
isVideo,
|
|
504
503
|
startTime: Date.now(),
|
|
505
504
|
};
|
|
506
|
-
console.log(call);
|
|
507
505
|
this.currentCall = call;
|
|
508
|
-
this.emit("call_started", {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
});
|
|
512
|
-
this.setState("connecting");
|
|
513
|
-
await this.livekit.connectToRoom(call.livekitUrl, call.token, {
|
|
514
|
-
|
|
515
|
-
});
|
|
516
|
-
this.audio.stop();
|
|
517
|
-
this.setState("connected");
|
|
506
|
+
// this.emit("call_started", {
|
|
507
|
+
// callId: call.callId,
|
|
508
|
+
// participants: [this.userId, calleeId],
|
|
509
|
+
// });
|
|
510
|
+
// this.setState("connecting");
|
|
511
|
+
// await this.livekit.connectToRoom(call.livekitUrl, call.token, {
|
|
512
|
+
// connect: true,
|
|
513
|
+
// });
|
|
514
|
+
// this.audio.stop();
|
|
515
|
+
// this.setState("connected");
|
|
518
516
|
}
|
|
519
517
|
catch (e) {
|
|
520
518
|
this.audio.stop();
|
|
@@ -591,6 +589,8 @@
|
|
|
591
589
|
this.emit("call_accepted", { callId: this.currentCall.callId });
|
|
592
590
|
}
|
|
593
591
|
catch (e) {
|
|
592
|
+
console.error("Some error occured:");
|
|
593
|
+
console.log(e);
|
|
594
594
|
this.emit("call:error", { callId: this.currentCall.callId, error: e });
|
|
595
595
|
}
|
|
596
596
|
}
|