UNPKG

@audin.ai/operator-sdk

Version:

Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.

193 lines (178 loc) 25.5 kB
(function(o,h){typeof exports=="object"&&typeof module<"u"?h(exports):typeof define=="function"&&define.amd?define(["exports"],h):(o=typeof globalThis<"u"?globalThis:o||self,h(o.AudinOperatorSDK={}))})(this,(function(o){"use strict";class h{constructor(){this.listeners=new Map}on(e,t){let s=this.listeners.get(e);return s||(s=new Set,this.listeners.set(e,s)),s.add(t),()=>this.off(e,t)}once(e,t){const s=this.on(e,n=>{s(),t(n)});return s}off(e,t){const s=this.listeners.get(e);s&&(s.delete(t),s.size===0&&this.listeners.delete(e))}emit(e,t){const s=this.listeners.get(e);if(s)for(const n of[...s])try{n(t)}catch(i){console.error("[audin-operator-sdk] listener threw:",i)}}removeAllListeners(){this.listeners.clear()}}class _{constructor(e){this.ws=null,this.heartbeatTimer=null,this.reconnectTimer=null,this.reconnectAttempt=0,this.desiredOnline=!1,this.lastAvailability=null,this.opts=e}async connect(){this.desiredOnline=!0,this.reconnectAttempt=0,await this.openOnce()}disconnect(){if(this.desiredOnline=!1,this.lastAvailability=null,this.clearTimers(),this.ws&&this.ws.readyState===WebSocket.OPEN&&this.sendRaw({type:"set_unavailable"}),this.ws){try{this.ws.close(1e3,"client_offline")}catch{}this.ws=null}}setAvailable(e){this.lastAvailability=[...e],this.sendRaw({type:"set_available",phoneNumberIds:e})}setUnavailable(){this.lastAvailability=null,this.sendRaw({type:"set_unavailable"})}accept(e){this.sendRaw({type:"accept",callSid:e})}reject(e){this.sendRaw({type:"reject",callSid:e})}startOutbound(e,t){this.sendRaw({type:"start_outbound",to:e,callerId:t})}get isOpen(){return this.ws?.readyState===WebSocket.OPEN}async openOnce(){let e;try{e=await this.opts.ensureToken()}catch(n){const i=n?.code??"TOKEN_FETCH_FAILED";this.opts.onError(i,"session token unavailable",n),this.scheduleReconnect();return}const t=`${this.opts.presenceWsUrl}?token=${encodeURIComponent(e)}`,s=new WebSocket(t);this.ws=s,s.onopen=()=>{this.opts.logger.debug("[presence] WS open"),this.reconnectAttempt=0,this.startHeartbeat(),this.opts.onOpen(),this.lastAvailability&&this.lastAvailability.length>0&&this.sendRaw({type:"set_available",phoneNumberIds:this.lastAvailability})},s.onmessage=n=>{let i;try{i=JSON.parse(typeof n.data=="string"?n.data:String(n.data))}catch{this.opts.logger.warn("[presence] non-JSON message ignored");return}this.opts.onMessage(i)},s.onerror=n=>{this.opts.logger.warn("[presence] WS error",n),this.opts.onError("WS_ERROR","presence socket error")},s.onclose=n=>{this.opts.logger.debug(`[presence] WS closed code=${n.code} reason=${n.reason}`),this.stopHeartbeat(),this.ws=null,A(n.code)&&this.opts.invalidateToken(),this.desiredOnline&&(this.opts.onReconnecting(),this.scheduleReconnect())}}scheduleReconnect(){if(!this.desiredOnline||this.reconnectTimer)return;const e=this.opts.reconnectBackoffMs,t=Math.min(this.reconnectAttempt,e.length-1),s=e[t];this.reconnectAttempt+=1,this.opts.logger.debug(`[presence] reconnect in ${s}ms (attempt ${this.reconnectAttempt})`),this.reconnectTimer=setTimeout(()=>{this.reconnectTimer=null,this.desiredOnline&&this.openOnce()},s)}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.sendRaw({type:"ping"})},this.opts.heartbeatIntervalMs)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}clearTimers(){this.stopHeartbeat(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}sendRaw(e){const t=this.ws;if(!t||t.readyState!==WebSocket.OPEN){this.opts.logger.warn(`[presence] dropped ${String(e.type)} — socket not open`);return}try{t.send(JSON.stringify(e))}catch(s){this.opts.logger.warn("[presence] send failed:",s)}}}function A(r){return r===1008||r===4001||r===4003}const S=String.raw` // ---- G.711 μ-law (mirrors src/codec/mulaw.ts; standard Sun convention) ---- var MULAW_BIAS = 0x84; var MULAW_CLIP = 32635; var MULAW_EXP_LUT = new Int8Array([ 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ]); function encodeMuLawSample(sample) { var pcm = sample; if (pcm > 32767) pcm = 32767; else if (pcm < -32768) pcm = -32768; var sign = (pcm >> 8) & 0x80; if (sign !== 0) pcm = -pcm; if (pcm > MULAW_CLIP) pcm = MULAW_CLIP; pcm += MULAW_BIAS; var exponent = MULAW_EXP_LUT[(pcm >> 7) & 0xff]; var mantissa = (pcm >> (exponent + 3)) & 0x0f; return (~(sign | (exponent << 4) | mantissa)) & 0xff; } function decodeMuLawSample(byte) { var u = ~byte & 0xff; var sign = u & 0x80; var exponent = (u >> 4) & 0x07; var mantissa = u & 0x0f; var sample = ((mantissa << 3) + MULAW_BIAS) << exponent; sample -= MULAW_BIAS; return (sign !== 0 ? -sample : sample) || 0; } var TARGET_RATE = 8000; // Cap the playback jitter buffer so a stall can't grow memory unbounded // (~2s of 8kHz audio decoded to context rate). var MAX_PLAYBACK_SAMPLES = 96000; class OperatorAudioProcessor extends AudioWorkletProcessor { constructor() { super(); // Capture accumulator (context-rate Float32) and its fractional read head // for resampling to 8kHz across process() boundaries. this._capBuf = new Float32Array(0); this._capPos = 0; // fractional position into _capBuf for the next 8k sample // Playback queue (context-rate Float32 already upsampled) + read head. this._playBuf = new Float32Array(0); this._playPos = 0; this._muted = false; this.port.onmessage = (e) => { var d = e.data; if (!d) return; if (d.type === "mulaw") { this._enqueuePlayback(d.payload); } else if (d.type === "mute") { this._muted = !!d.on; } else if (d.type === "flush") { this._playBuf = new Float32Array(0); this._playPos = 0; } }; } // Decode μ-law (8kHz) -> upsample to context rate -> append to playback queue. _enqueuePlayback(mulawBytes) { var n = mulawBytes.length; if (n === 0) return; var ctxRate = sampleRate; // global in AudioWorkletGlobalScope var ratio = ctxRate / TARGET_RATE; // e.g. 6 for 48k var outLen = Math.round(n * ratio); var upsampled = new Float32Array(outLen); var last = n - 1; for (var i = 0; i < outLen; i++) { var srcPos = i / ratio; var i0 = Math.floor(srcPos); var frac = srcPos - i0; var a = decodeMuLawSample(mulawBytes[i0 > last ? last : i0]) / 32768; var bIdx = i0 + 1 > last ? last : i0 + 1; var b = decodeMuLawSample(mulawBytes[bIdx]) / 32768; upsampled[i] = a + (b - a) * frac; } // Compact any already-consumed head, then append, bounded by the cap. var remaining = this._playBuf.length - this._playPos; var combinedLen = remaining + upsampled.length; if (combinedLen > MAX_PLAYBACK_SAMPLES) { // Drop oldest audio to stay bounded (better latency than OOM on a stall). var keep = MAX_PLAYBACK_SAMPLES - upsampled.length; if (keep < 0) keep = 0; var trimmed = new Float32Array(keep + upsampled.length); if (keep > 0) { trimmed.set( this._playBuf.subarray(this._playBuf.length - keep, this._playBuf.length), 0 ); } trimmed.set(upsampled, keep); this._playBuf = trimmed; this._playPos = 0; return; } var merged = new Float32Array(combinedLen); merged.set(this._playBuf.subarray(this._playPos), 0); merged.set(upsampled, remaining); this._playBuf = merged; this._playPos = 0; } // CAPTURE: append context-rate input, then emit as many 8k μ-law samples as // the accumulated buffer allows. Keeps a fractional read head so resampling // is continuous across blocks. _captureAndEmit(input) { var prevLen = this._capBuf.length; var headStart = Math.floor(this._capPos); // The resample read head can overshoot past the end of the previous buffer // (the loop below advances pos by up to one full step beyond the last // produced sample). Clamp how much of the old buffer we drop so grown.set never gets // a negative offset (which would throw and freeze the worklet after block 1). var dropped = headStart < prevLen ? headStart : prevLen; var tailLen = prevLen - dropped; // >= 0 var grown = new Float32Array(tailLen + input.length); if (tailLen > 0) grown.set(this._capBuf.subarray(dropped), 0); grown.set(input, tailLen); this._capBuf = grown; this._capPos -= dropped; // read head relative to the new buffer start var ctxRate = sampleRate; var step = ctxRate / TARGET_RATE; // input samples per 8k output sample var last = this._capBuf.length - 1; var out = []; var pos = this._capPos; while (pos + 1 <= last) { var i0 = Math.floor(pos); var frac = pos - i0; var a = this._capBuf[i0]; var b = this._capBuf[i0 + 1]; var s = a + (b - a) * frac; // float [-1,1] var pcm = s < 0 ? s * 0x8000 : s * 0x7fff; out.push(encodeMuLawSample(pcm | 0)); pos += step; } this._capPos = pos; if (out.length > 0 && !this._muted) { var bytes = new Uint8Array(out); // Transfer the buffer to avoid a copy on the way to the main thread. this.port.postMessage({ type: "mulaw", payload: bytes }, [bytes.buffer]); } } process(inputs, outputs) { // --- capture --- var input = inputs[0]; if (input && input.length > 0 && input[0] && input[0].length > 0) { this._captureAndEmit(input[0]); } // --- playback --- var output = outputs[0]; if (output && output.length > 0) { var chan0 = output[0]; var frames = chan0.length; var avail = this._playBuf.length - this._playPos; var toCopy = avail < frames ? avail : frames; for (var i = 0; i < toCopy; i++) { var v = this._playBuf[this._playPos + i]; for (var c = 0; c < output.length; c++) output[c][i] = v; } for (var j = toCopy; j < frames; j++) { for (var c2 = 0; c2 < output.length; c2++) output[c2][j] = 0; } this._playPos += toCopy; // Reclaim memory once the buffer is fully drained. if (this._playPos >= this._playBuf.length) { this._playBuf = new Float32Array(0); this._playPos = 0; } } return true; // keep processor alive } } registerProcessor("__PROCESSOR_NAME__", OperatorAudioProcessor); `,m="audin-operator-audio-processor",E=S.replace("__PROCESSOR_NAME__",m);function k(){const r=new Blob([E],{type:"application/javascript"});return URL.createObjectURL(r)}class C{constructor(e){this.ws=null,this.audioContext=null,this.micStream=null,this.sourceNode=null,this.workletNode=null,this.blobUrl=null,this.closed=!1,this.muted=!1,this.opts=e}async start(){try{await this.openSocket(),await this.setupAudioGraph()}catch(e){throw this.opts.logger.error("[audio] start failed:",e),this.close("mic_error"),e}}setMuted(e){this.muted=e,this.workletNode?.port.postMessage({type:"mute",on:e}),this.sendControl({type:"mute",on:e})}get isMuted(){return this.muted}sendDtmf(e){this.sendControl({type:"dtmf",digit:e})}hangup(){this.sendControl({type:"hangup"}),this.close("local_hangup")}close(e){if(!this.closed){if(this.closed=!0,this.micStream){for(const t of this.micStream.getTracks())try{t.stop()}catch{}this.micStream=null}try{this.workletNode?.port.postMessage({type:"flush"})}catch{}try{this.workletNode?.disconnect()}catch{}try{this.sourceNode?.disconnect()}catch{}if(this.workletNode=null,this.sourceNode=null,this.audioContext&&(this.audioContext.close().catch(()=>{}),this.audioContext=null),this.ws){try{this.ws.close(1e3,e)}catch{}this.ws=null}this.blobUrl&&(URL.revokeObjectURL(this.blobUrl),this.blobUrl=null),this.opts.onClosed(e)}}openSocket(){return new Promise((e,t)=>{let s=!1;const n=new WebSocket(this.opts.audioWsUrl);n.binaryType="arraybuffer",this.ws=n,n.onopen=()=>{s=!0,this.opts.logger.debug("[audio] WS open"),e()},n.onmessage=i=>this.onWsMessage(i),n.onerror=i=>{this.opts.logger.warn("[audio] WS error",i),s||(s=!0,t(new Error("audio WebSocket failed to open")))},n.onclose=i=>{this.opts.logger.debug(`[audio] WS closed code=${i.code} reason=${i.reason}`),this.closed||this.close("remote_close")}})}onWsMessage(e){const t=e.data;if(t instanceof ArrayBuffer){const n=new Uint8Array(t).slice();this.workletNode?.port.postMessage({type:"mulaw",payload:n},[n.buffer]);return}typeof t=="string"&&this.opts.logger.debug("[audio] control:",t)}async setupAudioGraph(){const e=window.AudioContext||window.webkitAudioContext;if(!e)throw new Error("Web Audio API not available in this environment");const t=new e;this.audioContext=t,t.state==="suspended"&&await t.resume().catch(()=>{}),this.blobUrl=k(),await t.audioWorklet.addModule(this.blobUrl);const s=navigator.mediaDevices?.getUserMedia?.bind(navigator.mediaDevices);if(!s)throw new Error("getUserMedia not available in this environment");let n;try{n=await s({audio:this.opts.audioConstraints})}catch(l){const c=new Error("microphone permission denied or unavailable");throw c.code="MIC_PERMISSION_DENIED",c.cause=l,c}this.micStream=n;const i=t.createMediaStreamSource(n),a=new AudioWorkletNode(t,m,{numberOfInputs:1,numberOfOutputs:1,outputChannelCount:[1]});this.sourceNode=i,this.workletNode=a,a.onprocessorerror=l=>this.opts.logger.error("[audio] worklet processor error",l),a.port.onmessage=l=>{const c=l.data;c?.type==="mulaw"&&c.payload&&this.sendAudio(c.payload)},i.connect(a),a.connect(t.destination),this.muted&&a.port.postMessage({type:"mute",on:!0})}sendAudio(e){const t=this.ws;if(!(!t||t.readyState!==WebSocket.OPEN))try{t.send(e)}catch(s){this.opts.logger.warn("[audio] send audio failed:",s)}}sendControl(e){const t=this.ws;if(!(!t||t.readyState!==WebSocket.OPEN))try{t.send(JSON.stringify(e))}catch(s){this.opts.logger.warn("[audio] send control failed:",s)}}}const T=/^[0-9*#]$/;class O{constructor(e,t){this._muted=!1,this.callSid=e.callSid,this.direction=e.direction,this.from=e.from,this.to=e.to,this._state=e.state,this.deps=t}get state(){return this._state}get endReason(){return this._endReason}get muted(){return this._muted}accept(){this._state==="ringing"&&(this._state="connecting",this.deps.onAccept(this))}reject(){this._state==="ringing"&&(this.markEnded("rejected"),this.deps.onReject(this))}mute(e){this._state!=="ended"&&(this._muted=e,this.deps.onMute(this,e))}sendDtmf(e){if(this._state!=="ended"){if(!T.test(e))throw new Error(`invalid DTMF digit: ${JSON.stringify(e)}`);this.deps.onDtmf(this,e)}}hangup(){this._state!=="ended"&&(this.markEnded("hangup"),this.deps.onHangup(this))}setConnecting(){this._state!=="ended"&&(this._state="connecting")}setActive(){this._state!=="ended"&&(this._state="active")}markEnded(e){this._state!=="ended"&&(this._state="ended",this._endReason=e)}}const M=3e4,P=6e4;class R{constructor(e){this.cached=null,this.inFlight=null,this.getToken=e}async ensureToken(){const e=Date.now();return this.cached&&this.cached.expiresAtMs>e?this.cached.token:this.inFlight?this.inFlight:(this.inFlight=this.fetchAndCache().finally(()=>{this.inFlight=null}),this.inFlight)}invalidate(){this.cached=null}async fetchAndCache(){const e=await this.getToken();if(!e?.token)throw new L("TOKEN_INVALID","getToken() returned no token");return this.cached={token:e.token,expiresAtMs:B(e,Date.now())},e.token}}class L extends Error{constructor(e,t){super(t),this.name="TokenError",this.code=e}}function B(r,e){if(r.expiresAt){const t=Date.parse(r.expiresAt);if(!Number.isNaN(t))return Math.max(t-M,e)}return e+P}const U=25e3,I=[1e3,2e3,5e3,1e4,3e4],N={echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0},W=15e3;class x extends h{constructor(e){super(),this.presenceState="offline",this.activeCall=null,this.activeBridge=null,this.pendingOutbound=null,this.tokens=new R(e.getToken),this.logger=e.logger??console,this.audioConstraints=e.audioConstraints??N,this.coreWsHost=F(e.coreUrl),this.coreHttpBase=D(e.coreUrl),this.presence=new _({presenceWsUrl:`${this.coreWsHost}/operator-presence/ws`,ensureToken:()=>this.tokens.ensureToken(),invalidateToken:()=>this.tokens.invalidate(),heartbeatIntervalMs:e.heartbeatIntervalMs??U,reconnectBackoffMs:e.reconnectBackoffMs??I,logger:this.logger,onMessage:t=>this.handlePresenceMessage(t),onOpen:()=>this.setPresenceState("online"),onReconnecting:()=>this.setPresenceState("reconnecting"),onError:(t,s,n)=>this.emitError(t,s,n)})}on(e,t){return super.on(e,t)}off(e,t){super.off(e,t)}once(e,t){return super.once(e,t)}async listPhoneNumbers(){return this.fetchPhoneNumbers(!0)}async fetchPhoneNumbers(e){const t=await this.tokens.ensureToken(),s=`${this.coreHttpBase}/operator/phone-numbers`;let n;try{n=await fetch(s,{headers:{Authorization:`Bearer ${t}`}})}catch(a){throw new d("REQUEST_FAILED","failed to reach the operator service",a)}if(n.status===401){if(this.tokens.invalidate(),e)return this.fetchPhoneNumbers(!1);throw new d("UNAUTHORIZED","session token rejected fetching phone numbers (401)")}if(!n.ok)throw new d("REQUEST_FAILED",`phone-numbers request failed with status ${n.status}`);let i;try{i=await n.json()}catch(a){throw new d("REQUEST_FAILED","phone-numbers response was not valid JSON",a)}return Array.isArray(i.phoneNumbers)?i.phoneNumbers:[]}async goOnline(e){if(!Array.isArray(e)||e.length===0)throw new Error("goOnline requires a non-empty phoneNumberIds array");this.setPresenceState("connecting"),this.presence.isOpen||await this.presence.connect(),this.presence.setAvailable(e)}async goOffline(){this.activeCall&&this.teardownActiveCall("offline"),this.presence.disconnect(),this.setPresenceState("offline")}get state(){return this.presenceState}get currentCall(){return this.activeCall}setAudioConstraints(e){this.audioConstraints=e}dial(e,t){return this.presence.isOpen?this.activeCall?Promise.reject(new Error("an active call is already in progress (MVP: one at a time)")):this.pendingOutbound?Promise.reject(new Error("an outbound dial is already pending")):!e||typeof e!="string"?Promise.reject(new Error("dial requires a destination number")):t?.callerId?new Promise((s,n)=>{const i=setTimeout(()=>{this.pendingOutbound=null,n(new Error("outbound call was not acknowledged in time"))},W);this.pendingOutbound={to:e,callerId:t.callerId,resolve:s,reject:n,timer:i},this.presence.startOutbound(e,t.callerId)}):Promise.reject(new Error("dial requires options.callerId")):Promise.reject(new Error("not online — call goOnline() before dialing"))}handlePresenceMessage(e){switch(e.type){case"connected":break;case"pong":break;case"available_set":{const t=g(e.accepted),s=g(e.rejected);this.emitEvent("availabilityChanged",{accepted:t,rejected:s});break}case"unavailable_set":break;case"incoming_call":this.handleIncomingCall(e);break;case"call_taken":this.handleCallTaken(e);break;case"call_assigned":this.handleCallAssigned(e);break;case"outbound_started":this.handleOutboundStarted(e);break;case"error":{const t=typeof e.code=="string"?e.code:"SERVER_ERROR",s=typeof e.message=="string"?e.message:"server error";this.emitError(t,s,e),this.failPendingOutbound(new Error(`${t}: ${s}`));break}default:this.logger.debug("[operator] unhandled presence message:",e.type)}}handleIncomingCall(e){const t=typeof e.callSid=="string"?e.callSid:"";if(!t)return;const s=typeof e.from=="string"?e.from:void 0,n=typeof e.to=="string"?e.to:void 0;if(this.activeCall){this.logger.debug(`[operator] auto-rejecting incoming ${t} — already on a call`),this.presence.reject(t);return}const i=this.makeCall({callSid:t,direction:"inbound",from:s,to:n,state:"ringing"});this.activeCall=i,this.emitEvent("incomingCall",i)}handleCallTaken(e){const t=typeof e.callSid=="string"?e.callSid:"",s=this.activeCall;!s||s.callSid!==t||(s.markEnded("taken_by_other"),this.activeCall=null,this.emitEvent("callEnded",s))}handleCallAssigned(e){const t=typeof e.callSid=="string"?e.callSid:"",s=this.activeCall;if(!s||s.callSid!==t){this.logger.warn(`[operator] call_assigned for unknown callSid=${t} — ignoring`);return}s.setConnecting(),this.openAudioBridge(s)}handleOutboundStarted(e){const t=typeof e.callSid=="string"?e.callSid:"",s=this.pendingOutbound;if(!s){this.logger.warn(`[operator] outbound_started without a pending dial (callSid=${t})`);return}if(clearTimeout(s.timer),this.pendingOutbound=null,!t){s.reject(new Error("outbound_started missing callSid"));return}const n=this.makeCall({callSid:t,direction:"outbound",to:s.to,state:"connecting"});this.activeCall=n,s.resolve(n),this.openAudioBridge(n)}async openAudioBridge(e){let t;try{t=await this.tokens.ensureToken()}catch(i){const a=i?.code??"TOKEN_FETCH_FAILED";this.emitError(a,"session token unavailable",i),this.endCallWithReason(e,"failed");return}const s=`${this.coreWsHost}/operator-audio/ws/${encodeURIComponent(e.callSid)}?token=${encodeURIComponent(t)}`,n=new C({audioWsUrl:s,audioConstraints:this.audioConstraints,logger:this.logger,onClosed:i=>this.onBridgeClosed(e,i)});this.activeBridge=n;try{await n.start()}catch(i){const a=i?.code??"AUDIO_START_FAILED";this.emitError(a,i.message??"audio start failed",i),this.endCallWithReason(e,"failed");return}e.muted&&n.setMuted(!0),e.setActive(),this.emitEvent("callStarted",e)}onBridgeClosed(e,t){if(this.activeCall!==e)return;this.activeBridge=null;const s=t==="local_hangup"?"hangup":t==="ws_error"||t==="mic_error"?"failed":"remote_hangup";e.markEnded(s),this.activeCall=null,this.emitEvent("callEnded",e)}makeCall(e){return new O(e,{onAccept:t=>{this.presence.accept(t.callSid)},onReject:t=>{this.presence.reject(t.callSid),this.activeCall===t&&(this.activeCall=null,this.emitEvent("callEnded",t))},onMute:(t,s)=>{this.activeBridge?.setMuted(s)},onDtmf:(t,s)=>{this.activeBridge?.sendDtmf(s)},onHangup:t=>{if(this.activeBridge){this.activeBridge.hangup();return}this.presence.reject(t.callSid),this.activeCall===t&&(this.activeCall=null,this.activeBridge=null,this.emitEvent("callEnded",t))}})}endCallWithReason(e,t){this.activeBridge&&(this.activeBridge.close("teardown"),this.activeBridge=null),e.markEnded(t),this.activeCall===e&&(this.activeCall=null),this.emitEvent("callEnded",e)}teardownActiveCall(e){const t=this.activeCall;t&&(this.activeBridge&&(this.activeBridge.close("teardown"),this.activeBridge=null),t.markEnded(e),this.activeCall=null,this.emitEvent("callEnded",t))}failPendingOutbound(e){const t=this.pendingOutbound;t&&(clearTimeout(t.timer),this.pendingOutbound=null,t.reject(e))}setPresenceState(e){this.presenceState!==e&&(this.presenceState=e,this.emitEvent("presenceStateChanged",e))}emitError(e,t,s){this.emitEvent("error",{code:e,message:t,cause:s})}emitEvent(e,t){this.emit(e,t)}}function F(r){let e;try{e=new URL(r)}catch{throw new Error(`invalid coreUrl: ${JSON.stringify(r)}`)}return`${e.protocol==="https:"||e.protocol==="wss:"?"wss:":"ws:"}//${e.host}`}function D(r){let e;try{e=new URL(r)}catch{throw new Error(`invalid coreUrl: ${JSON.stringify(r)}`)}return`${e.protocol==="https:"||e.protocol==="wss:"?"https:":"http:"}//${e.host}`}class d extends Error{constructor(e,t,s){super(t),this.name="OperatorRequestError",this.code=e,this.cause=s}}function g(r){return Array.isArray(r)?r.filter(e=>typeof e=="string"):[]}const u=132,w=32635,j=new Int8Array([0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7]);function y(r){let e=r;e>32767?e=32767:e<-32768&&(e=-32768);const t=e>>8&128;t!==0&&(e=-e),e>w&&(e=w),e+=u;const s=j[e>>7&255],n=e>>s+3&15;return~(t|s<<4|n)&255}function v(r){const e=~r&255,t=e&128,s=e>>4&7;let i=((e&15)<<3)+u<<s;return i-=u,(t!==0?-i:i)||0}function $(r){const e=new Uint8Array(r.length);for(let t=0;t<r.length;t++)e[t]=y(r[t]);return e}function H(r){const e=new Int16Array(r.length);for(let t=0;t<r.length;t++)e[t]=v(r[t]);return e}function p(r,e,t){if(e<=0||t<=0)throw new Error("resampleLinear: sample rates must be positive");if(e===t||r.length===0)return r.slice();const s=e/t,n=Math.round(r.length/s),i=new Float32Array(n),a=r.length-1;for(let l=0;l<n;l++){const c=l*s,f=Math.floor(c),X=c-f,b=r[Math.min(f,a)],z=r[Math.min(f+1,a)];i[l]=b+(z-b)*X}return i}function K(r,e){return p(r,e,8e3)}function q(r,e){return p(r,8e3,e)}function G(r){const e=new Int16Array(r.length);for(let t=0;t<r.length;t++){let s=r[t];s>1?s=1:s<-1&&(s=-1),e[t]=s<0?s*32768:s*32767}return e}function J(r){const e=new Float32Array(r.length);for(let t=0;t<r.length;t++){const s=r[t];e[t]=s<0?s/32768:s/32767}return e}o.AudinOperator=x,o.OperatorRequestError=d,o.decodeMuLaw=H,o.decodeMuLawSample=v,o.downsampleTo8k=K,o.encodeMuLaw=$,o.encodeMuLawSample=y,o.floatToInt16=G,o.int16ToFloat=J,o.resampleLinear=p,o.upsampleFrom8k=q,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})})); //# sourceMappingURL=audin-operator-sdk.umd.cjs.map