@moveo-ai/web-client 0.106.0-true.3 → 0.106.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/dist/147.min.js.LICENSE.txt +1 -1
- package/dist/329.min.js.LICENSE.txt +1 -1
- package/dist/882.min.js +1 -1
- package/dist/882.min.js.LICENSE.txt +1 -1
- package/dist/882.min.js.map +1 -1
- package/dist/929.min.js.LICENSE.txt +1 -1
- package/dist/954.min.js.LICENSE.txt +1 -1
- package/dist/audio-client.min.js +1 -1
- package/dist/audio-client.min.js.LICENSE.txt +1 -1
- package/dist/audio-client.min.js.map +1 -1
- package/dist/iframe.min.js.LICENSE.txt +1 -1
- package/dist/iframe.v2.min.js +1 -1
- package/dist/iframe.v2.min.js.LICENSE.txt +1 -1
- package/dist/iframe.v2.min.js.map +1 -1
- package/dist/index.html +1 -1
- package/dist/preview/index.html +2 -2
- package/dist/preview-webrtc/index.html +52 -5
- package/dist/preview.css.min.js.LICENSE.txt +1 -1
- package/dist/telnyx-client.min.js +1 -1
- package/dist/telnyx-client.min.js.LICENSE.txt +1 -1
- package/dist/telnyx-client.min.js.map +1 -1
- package/dist/web-client.min.js.LICENSE.txt +1 -1
- package/dist/webrtc-client.min.js +1 -1
- package/dist/webrtc-client.min.js.LICENSE.txt +1 -1
- package/dist/webrtc-client.min.js.map +1 -1
- package/package.json +1 -1
package/dist/882.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"882.min.js","mappings":"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","sources":["webpack://MoveoAI/./src/controller/shared/audio-level-monitor.ts","webpack://MoveoAI/./src/controller/shared/media-errors.ts","webpack://MoveoAI/./src/controller/webrtc/call-quality-monitor.ts","webpack://MoveoAI/./src/controller/webrtc/config-errors.ts","webpack://MoveoAI/./src/controller/webrtc/media-stall-detector.ts","webpack://MoveoAI/./src/controller/webrtc/select-candidate-pair.ts","webpack://MoveoAI/./src/controller/webrtc/signaling-client.ts","webpack://MoveoAI/./src/controller/webrtc/webrtc-provider.ts","webpack://MoveoAI/./src/ts/webrtc.ts"],"sourcesContent":["const NOISE_GATE_THRESHOLD = 0.01;\n/**\n * Monitors a MediaStream's audio level via Web Audio API.\n * Extracted from AudioController for reuse across voice providers.\n *\n * Uses AnalyserNode with RMS calculation:\n * fftSize: 256, smoothingTimeConstant: 0.3\n * Noise gate: 0.01 threshold\n * Normalized output: 0.0 - 1.0\n */\nexport class AudioLevelMonitor {\n constructor() {\n this.analyserNode = null;\n this.audioSource = null;\n this.animationId = null;\n this.audioContext = null;\n }\n start(stream, onLevel) {\n try {\n this.audioContext = new AudioContext();\n this.audioSource = this.audioContext.createMediaStreamSource(stream);\n this.analyserNode = this.audioContext.createAnalyser();\n this.analyserNode.fftSize = 256;\n this.analyserNode.smoothingTimeConstant = 0.3;\n this.audioSource.connect(this.analyserNode);\n const dataArray = new Float32Array(this.analyserNode.fftSize);\n const checkAudioLevel = () => {\n if (!this.analyserNode) {\n this.stop();\n return;\n }\n this.analyserNode.getFloatTimeDomainData(dataArray);\n let sum = 0;\n for (const amplitude of dataArray) {\n sum += amplitude * amplitude;\n }\n const rms = Math.sqrt(sum / dataArray.length);\n const normalizedLevel = Math.min(1, rms * 10);\n const gatedLevel = normalizedLevel > NOISE_GATE_THRESHOLD ? normalizedLevel : 0;\n onLevel(gatedLevel.toFixed(3));\n this.animationId = requestAnimationFrame(checkAudioLevel);\n };\n checkAudioLevel();\n }\n catch (_a) {\n // Silently fail — audio monitoring is non-critical\n }\n }\n stop() {\n if (this.animationId !== null) {\n cancelAnimationFrame(this.animationId);\n this.animationId = null;\n }\n if (this.audioSource) {\n try {\n this.audioSource.disconnect();\n }\n catch (_a) {\n // ignore\n }\n this.audioSource = null;\n }\n if (this.audioContext) {\n void this.audioContext.close().catch(() => {\n // ignore\n });\n this.audioContext = null;\n }\n this.analyserNode = null;\n }\n}\n","/**\n * User-friendly messages for the common getUserMedia DOMException names.\n * Shared across voice providers (native WebRTC, Telnyx) so a single string\n * surfaces in the widget regardless of provider.\n */\nexport const MEDIA_ERRORS = {\n NotAllowedError: 'Microphone permission denied',\n NotFoundError: 'No microphone found',\n NotReadableError: 'Microphone is in use by another application',\n OverconstrainedError: 'Microphone does not support required settings',\n};\n","import { selectCandidatePair } from './select-candidate-pair';\nconst QUALITY_WARNINGS = {\n 'high-rtt': 'High network latency detected',\n 'low-mos': 'Poor call quality detected',\n 'high-jitter': 'Network instability detected',\n 'high-packet-loss': 'Packet loss detected',\n};\nconst THRESHOLDS = {\n RTT: 400, // ms\n JITTER: 0.03, // seconds (30ms)\n PACKET_LOSS: 0.03, // 3%\n MOS: 3.0,\n};\n/**\n * Replaces Twilio Voice Insights with standard WebRTC getStats().\n * Polls pc.getStats() and maps to warning events that AudioWidget handles.\n */\nexport class CallQualityMonitor {\n constructor(pc, onWarning, onWarningCleared, pollIntervalMs = 5000) {\n this.pc = pc;\n this.onWarning = onWarning;\n this.onWarningCleared = onWarningCleared;\n this.pollIntervalMs = pollIntervalMs;\n this.interval = null;\n this.prevStats = null;\n this.activeWarnings = new Set();\n }\n start() {\n this.interval = setInterval(() => {\n void this.pollStats();\n }, this.pollIntervalMs);\n }\n stop() {\n if (this.interval !== null) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.prevStats = null;\n this.activeWarnings.clear();\n }\n async pollStats() {\n try {\n const stats = await this.pc.getStats();\n let jitter = null;\n let packetsReceived = 0;\n let packetsLost = 0;\n // RTT from the pair carrying media, not any nominated one: a stale pair\n // would skew high-rtt / MOS warnings on relay networks.\n const pair = selectCandidatePair(stats);\n const rtt = (pair === null || pair === void 0 ? void 0 : pair.currentRoundTripTime) != null\n ? pair.currentRoundTripTime * 1000\n : null;\n stats.forEach((report) => {\n var _a, _b, _c;\n if (report.type === 'inbound-rtp' && report.kind === 'audio') {\n const rtp = report;\n jitter = (_a = rtp.jitter) !== null && _a !== void 0 ? _a : null;\n packetsReceived = (_b = rtp.packetsReceived) !== null && _b !== void 0 ? _b : 0;\n packetsLost = (_c = rtp.packetsLost) !== null && _c !== void 0 ? _c : 0;\n }\n });\n // Compute packet loss rate over delta\n let lossRate = 0;\n if (this.prevStats) {\n const deltaReceived = packetsReceived - this.prevStats.packetsReceived;\n const deltaLost = packetsLost - this.prevStats.packetsLost;\n const total = deltaReceived + deltaLost;\n lossRate = total > 0 ? deltaLost / total : 0;\n }\n this.prevStats = { packetsReceived, packetsLost };\n // Check thresholds\n this.checkWarning('high-rtt', rtt !== null && rtt > THRESHOLDS.RTT);\n this.checkWarning('high-jitter', jitter !== null && jitter > THRESHOLDS.JITTER);\n this.checkWarning('high-packet-loss', lossRate > THRESHOLDS.PACKET_LOSS);\n // MOS estimation (simplified E-model)\n if (rtt !== null && jitter !== null) {\n const jitterMs = jitter * 1000;\n const lossPercent = lossRate * 100;\n const R = 93.2 - jitterMs * 2 - lossPercent * 40 - Math.max(0, rtt - 150) * 0.1;\n const mos = 1 + 0.035 * R + R * (R - 60) * (100 - R) * 7e-6;\n this.checkWarning('low-mos', mos < THRESHOLDS.MOS);\n }\n }\n catch (_a) {\n // pc may have closed mid-poll; quality stats are non-critical\n }\n }\n checkWarning(name, isExceeded) {\n if (isExceeded && !this.activeWarnings.has(name)) {\n this.activeWarnings.add(name);\n this.onWarning(name, QUALITY_WARNINGS[name] || name);\n }\n else if (!isExceeded && this.activeWarnings.has(name)) {\n this.activeWarnings.delete(name);\n this.onWarningCleared(name);\n }\n }\n}\n","/**\n * Typed errors for the GET /config flow, shared by WebRTCController (initial\n * connect) and WebRTCProvider (reconnect-time re-fetch) so both can branch on\n * a server-busy 503 without parsing message strings.\n */\n/**\n * Raised when /config returns 503 with error_code \"server_busy\" — the pod is at\n * its concurrent-session cap. Retryable with backoff against a sibling pod.\n */\nexport class ServerBusyError extends Error {\n constructor(message = 'Voice service is busy, please try again.') {\n super(message);\n this.name = 'ServerBusyError';\n }\n}\n/**\n * Raised when /config fails in a way retrying can't fix (e.g. TURN or signing\n * key not configured, or origin not allowed). Terminal — the reconnect loop\n * must not burn retries on it.\n */\nexport class ConfigUnavailableError extends Error {\n constructor(message) {\n super(message);\n this.name = 'ConfigUnavailableError';\n }\n}\n/**\n * Raised when /config returns 429 — the integration hit its per-integration\n * rate limit. The caller should back off and let the user retry shortly.\n */\nexport class RateLimitedError extends Error {\n constructor(message = 'Too many attempts. Please try again in a moment.') {\n super(message);\n this.name = 'RateLimitedError';\n }\n}\n","import debug from 'debug';\nimport { describeCandidatePair, selectCandidatePair, } from './select-candidate-pair';\n// All audio inbound-rtp counters, keyed by report id. More than one report\n// can exist transiently (a stale report retained across an SSRC change), so\n// no single-report assumption.\nfunction collectAudioInboundRtp(stats) {\n const counts = new Map();\n stats.forEach((report) => {\n if (report.type === 'inbound-rtp') {\n const rtp = report;\n if (rtp.kind === 'audio' && rtp.packetsReceived !== undefined) {\n counts.set(rtp.id, rtp.packetsReceived);\n }\n }\n });\n return counts;\n}\n// True for any WebKit-backed browser: desktop Safari and every iOS browser\n// (CriOS/FxiOS are WebKit under the hood; iPadOS Safari reports a macOS UA,\n// caught by the Safari-without-Chrome clause).\nexport function isWebKitEngine(ua = navigator.userAgent) {\n return (/\\b(iPad|iPhone|iPod)\\b/.test(ua) ||\n (/AppleWebKit/.test(ua) && !/Chrome|Chromium|CriOS|Edg|OPR/.test(ua)));\n}\n/**\n * Detects a mid-call media-path stall (and its recovery) so the provider can\n * drive an ICE restart and confirm when media returns. Separate from\n * CallQualityMonitor: that polls every 5s for UX warnings, but recovery needs a\n * faster signal that beats the server's ~15s give-up.\n *\n * The signal is the audio inbound-rtp packetsReceived delta per poll: it\n * measures media actually received, independent of which candidate pair\n * delivered it. Candidate-pair and transport counters are deliberately not\n * used — on relay networks media legally arrives on a non-selected pair (the\n * two agents can nominate different relay allocations), and those signals also\n * move on non-media traffic (RTCP, DTLS, STUN), so either way they disagree\n * with real audio flow.\n *\n * Healthy = ANY audio inbound-rtp report advanced since the previous poll: a\n * stale report briefly retained across an SSRC change must not mask the live\n * one, and a genuinely stalled call has no advancing report, so this never\n * false-negatives.\n *\n * Correctness depends on the server streaming continuously (~50 packets/sec,\n * no DTX): that keeps packetsReceived advancing even while the user is silent.\n * If the server ever stops sending mid-call for longer than flatPollsThreshold\n * polls, the detector reports a false stall.\n *\n * With no audio inbound-rtp report (or none exposing packetsReceived) the\n * detector cannot assess and stays inert, logging once per episode. It does\n * not fall back to transport/pair byte counters, which count non-media traffic\n * and would defeat the point of watching received media.\n */\nexport class MediaStallDetector {\n constructor(pc, onStall, onRecover, pollIntervalMs = 1000, \n // Consecutive polls with no inbound media packet before declaring a stall;\n // debounces a brief blip while still beating the 15s server close.\n flatPollsThreshold = 3, \n // WebKit runs detection-only: the ICE-restart escalation is unproven on\n // that engine, so stalls are logged without acting on them.\n escalate = !isWebKitEngine()) {\n this.pc = pc;\n this.onStall = onStall;\n this.onRecover = onRecover;\n this.pollIntervalMs = pollIntervalMs;\n this.flatPollsThreshold = flatPollsThreshold;\n this.escalate = escalate;\n this.log = debug('moveo:webrtc:media');\n this.interval = null;\n this.prevCounts = null;\n this.flatPolls = 0;\n this.stalled = false;\n // Logs the \"cannot assess\" reason once per episode instead of every poll.\n this.cannotAssessLogged = false;\n }\n start() {\n this.log('started (escalate=%s)', this.escalate);\n this.interval = setInterval(() => {\n void this.poll();\n }, this.pollIntervalMs);\n }\n stop() {\n if (this.interval !== null) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.prevCounts = null;\n this.flatPolls = 0;\n this.stalled = false;\n this.cannotAssessLogged = false;\n }\n async poll() {\n try {\n const stats = await this.pc.getStats();\n const counts = collectAudioInboundRtp(stats);\n // No audio inbound-rtp with a packetsReceived counter: can't assess.\n // Baselines and stall state are preserved; log once per episode so a\n // silently-inert detector is visible.\n if (counts.size === 0) {\n if (!this.cannotAssessLogged) {\n this.cannotAssessLogged = true;\n this.log('cannot assess: no audio inbound-rtp with packetsReceived');\n }\n return;\n }\n this.cannotAssessLogged = false;\n const prev = this.prevCounts;\n this.prevCounts = counts;\n if (prev === null) {\n this.log('assessing %d audio inbound-rtp report(s)', counts.size);\n return; // first assessed poll: seed only\n }\n // Healthy if ANY report advanced. `> 0` only: a counter reset (browser\n // bug / id reuse) must not read as recovery — a fake recovery would\n // disarm a live ICE-restart attempt.\n let advanced = false;\n let shrunk = false;\n let comparable = 0;\n let hasNewReport = false;\n counts.forEach((packets, id) => {\n const before = prev.get(id);\n if (before === undefined) {\n hasNewReport = true;\n return;\n }\n comparable++;\n if (packets > before) {\n advanced = true;\n }\n else if (packets < before) {\n shrunk = true;\n }\n });\n if (advanced) {\n if (this.stalled) {\n this.stalled = false;\n this.log('recovered%s (pair %s)', this.escalate ? '' : ' (log-only)', this.describeSelectedPair(stats));\n if (this.escalate) {\n this.onRecover();\n }\n }\n this.flatPolls = 0;\n return;\n }\n // Baselines aren't comparable — reseed. Covers all ids replaced (new\n // SSRC), a counter reset under a reused id, or a freshly-appeared report\n // (no baseline yet, so a frozen sibling can't make it count as flat).\n // `stalled` is left as-is, so recovery lands one poll later.\n if (comparable === 0 || shrunk || hasNewReport) {\n this.log('inbound-rtp reports reseeded (comparable=%d new=%s)', comparable, hasNewReport);\n this.flatPolls = 0;\n return;\n }\n // Comparable reports exist and none advanced: a flat poll.\n this.flatPolls++;\n this.log('flat poll reports=%d flat=%d', counts.size, this.flatPolls);\n if (this.flatPolls >= this.flatPollsThreshold && !this.stalled) {\n this.stalled = true;\n this.log('stall declared%s after %d flat polls (pair %s)', this.escalate ? '' : ' (log-only, escalation disabled)', this.flatPolls, this.describeSelectedPair(stats));\n if (this.escalate) {\n this.onStall();\n }\n }\n }\n catch (_a) {\n // pc may have closed mid-poll; non-critical\n }\n }\n // Path shape for stall/recovery logs only — never used as the signal.\n describeSelectedPair(stats) {\n const pair = selectCandidatePair(stats);\n return pair ? describeCandidatePair(stats, pair) : '?';\n }\n}\n","/**\n * The candidate pair actually carrying media, resolved by\n * transport.selectedCandidatePairId → legacy `selected` flag → best nominated.\n * \"Any nominated\" is not enough: relay paths leave several pairs nominated and\n * a stale one can win, driving false stalls. Best nominated prefers the newest\n * lastPacketReceivedTimestamp (a frozen pair's is in the past), never displacing\n * a timestamped pair with a field-less one, and falls back to the first\n * nominated when none report a timestamp. Callers read the field they need.\n */\nexport function selectCandidatePair(stats) {\n let selectedPairId;\n const pairs = [];\n stats.forEach((report) => {\n if (report.type === 'transport') {\n const { selectedCandidatePairId } = report;\n if (selectedCandidatePairId && !selectedPairId) {\n selectedPairId = selectedCandidatePairId;\n }\n }\n else if (report.type === 'candidate-pair') {\n pairs.push(report);\n }\n });\n // A dangling transport id falls through to the legacy `selected` flag before\n // the nominated fallback.\n const selected = (selectedPairId && pairs.find((p) => p.id === selectedPairId)) ||\n pairs.find((p) => p.selected === true);\n if (selected) {\n return selected;\n }\n let best;\n for (const p of pairs) {\n if (!p.nominated) {\n continue;\n }\n if (best === undefined) {\n best = p;\n continue;\n }\n const bt = best.lastPacketReceivedTimestamp;\n const pt = p.lastPacketReceivedTimestamp;\n if (pt !== undefined && (bt === undefined || pt > bt)) {\n best = p;\n }\n }\n return best;\n}\n/**\n * Compact `local -> remote` candidate-type summary for a pair, e.g.\n * `relay/udp(udp) -> prflx/udp`. Resolves the pair's candidate stats so the\n * media path (relayed vs direct vs asymmetric) is visible in logs without a\n * webrtc-internals dump. Addresses are deliberately omitted (they are PII).\n */\nexport function describeCandidatePair(stats, pair) {\n const describe = (id) => {\n var _a, _b;\n if (!id) {\n return '?';\n }\n const c = stats.get(id);\n if (!c) {\n return '?';\n }\n const base = `${(_a = c.candidateType) !== null && _a !== void 0 ? _a : '?'}/${(_b = c.protocol) !== null && _b !== void 0 ? _b : '?'}`;\n return c.relayProtocol ? `${base}(${c.relayProtocol})` : base;\n };\n return `${describe(pair.localCandidateId)} -> ${describe(pair.remoteCandidateId)}`;\n}\n","import debug from 'debug';\nimport { WS_CLOSE_RESUME } from '@/ts/webrtc';\nconst CONNECT_TIMEOUT_MS = 10000;\n/**\n * JSON-over-WebSocket signaling for WebRTC voice sessions.\n * Stateless thin wrapper — all connection state lives in WebRTCProvider.\n */\nexport class SignalingClient {\n constructor(url, sessionToken, onMessage, onClose, onError) {\n this.url = url;\n this.sessionToken = sessionToken;\n this.onMessage = onMessage;\n this.onClose = onClose;\n this.onError = onError;\n this.log = debug('moveo:webrtc:media');\n this.ws = null;\n }\n connect() {\n return new Promise((resolve, reject) => {\n this.log('connecting to %s', this.url);\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.sessionToken)}`;\n this.ws = new WebSocket(wsUrl);\n const timeout = setTimeout(() => {\n // close() (not ws.close()) so the abandoned socket's late close event\n // can't re-enter the provider's onClose after we already rejected.\n this.close();\n reject(new Error('Signaling connection timed out'));\n }, CONNECT_TIMEOUT_MS);\n this.ws.onopen = () => {\n // Wait for welcome message before resolving\n };\n this.ws.onmessage = (event) => {\n let msg;\n try {\n msg = JSON.parse(event.data);\n }\n catch (_a) {\n // Ignore malformed JSON\n return;\n }\n this.log('recv %s', msg.type);\n if (msg.type === 'welcome') {\n clearTimeout(timeout);\n resolve();\n }\n this.onMessage(msg);\n };\n this.ws.onerror = (event) => {\n clearTimeout(timeout);\n this.log('websocket error');\n this.onError(event);\n reject(new Error('Signaling WebSocket error'));\n };\n this.ws.onclose = (event) => {\n clearTimeout(timeout);\n this.log('closed code=%d reason=%s', event.code, event.reason);\n reject(new Error('Signaling WebSocket closed before connection established'));\n this.onClose(event.code, event.reason);\n };\n });\n }\n /**\n * Open a fresh WS to resume a session (pod drain or signaling loss). Sends\n * `reconnect` as the first frame on open and resolves on `reconnect_ack`\n * (not `welcome` — a resumed session does not re-issue welcome).\n */\n connectForReconnect(reconnectFrame) {\n return new Promise((resolve, reject) => {\n this.log('reconnecting to %s', this.url);\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.sessionToken)}`;\n this.ws = new WebSocket(wsUrl);\n let settled = false;\n const timeout = setTimeout(() => {\n if (settled)\n return;\n settled = true;\n // The handshake may have committed server-side just as we timed out, so\n // close abnormally (4001) to keep the session resumable for the retry.\n this.close(WS_CLOSE_RESUME, 'Resume timed out');\n reject(new Error('Signaling reconnect timed out'));\n }, CONNECT_TIMEOUT_MS);\n this.ws.onopen = () => {\n var _a;\n // reconnect MUST be the first frame, before any offer.\n (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(reconnectFrame));\n };\n this.ws.onmessage = (event) => {\n let msg;\n try {\n msg = JSON.parse(event.data);\n }\n catch (_a) {\n // Ignore malformed JSON\n return;\n }\n this.log('recv %s', msg.type);\n // Deliver to the provider first so it records the credential rotation\n // (reconnect_ack) or the reject code (error) before we settle.\n this.onMessage(msg);\n if (settled)\n return;\n if (msg.type === 'reconnect_ack') {\n settled = true;\n clearTimeout(timeout);\n resolve();\n }\n else if (msg.type === 'error') {\n // Server rejected the resume. Settle now rather than waiting on the\n // WS close that follows — avoids a per-attempt CONNECT_TIMEOUT stall\n // and makes the reject deterministic w.r.t. the captured error code.\n settled = true;\n clearTimeout(timeout);\n reject(new Error(`reconnect rejected: ${msg.error.code}`));\n }\n };\n this.ws.onerror = (event) => {\n this.log('websocket error');\n this.onError(event);\n if (settled)\n return;\n settled = true;\n clearTimeout(timeout);\n reject(new Error('Signaling WebSocket error'));\n };\n this.ws.onclose = (event) => {\n this.log('closed code=%d reason=%s', event.code, event.reason);\n if (!settled) {\n settled = true;\n clearTimeout(timeout);\n reject(new Error('Signaling WebSocket closed before reconnect established'));\n }\n this.onClose(event.code, event.reason);\n };\n });\n }\n send(msg) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n this.log('send %s', msg.type);\n this.ws.send(JSON.stringify(msg));\n }\n /**\n * Close and detach the socket, nulling handlers first so a late close event\n * can't re-enter the provider. Default 1000 reads as an intentional end; the\n * resume flow passes 4001 to keep the session resumable.\n */\n close(code = 1000, reason = 'Client closed') {\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close(code, reason);\n this.ws = null;\n }\n }\n get isConnected() {\n var _a;\n return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;\n }\n}\n","import 'webrtc-adapter';\nimport debug from 'debug';\nimport { WS_CLOSE_RESUME } from '@/ts/webrtc';\nimport { AudioLevelMonitor } from '../shared/audio-level-monitor';\nimport { MEDIA_ERRORS } from '../shared/media-errors';\nimport { CallQualityMonitor } from './call-quality-monitor';\nimport { ConfigUnavailableError, ServerBusyError } from './config-errors';\nimport { MediaStallDetector } from './media-stall-detector';\nimport { SignalingClient } from './signaling-client';\nconst REMOTE_AUDIO_THRESHOLD = 0.02;\n// ICE-restart recovery bounds (mid-call media drop).\nconst MAX_ICE_RESTART_ATTEMPTS = 2;\nconst ICE_RESTART_TIMEOUT_MS = 10000; // per-attempt window to reach 'connected'\n// No answer this fast ⇒ the WS is dead (a network switch leaves it\n// readyState===OPEN with no TCP reset, so isConnected can't see it): a same-PC\n// restart can't recover, so bail to a fresh-WS resume, not the ~20s window.\nconst ICE_RESTART_ANSWER_TIMEOUT_MS = 3000;\n// Session-resume bounds (drain, abnormal signaling close, exhausted ICE restarts).\nconst MAX_CLIENT_RETRIES = 3;\nconst RECONNECT_BACKOFF_MS = 500; // scaled by attempt number\n// Client-detected resumes carry no server delay, so jitter to avoid a correlated\n// mass disconnect stampeding /config + /rtc at once. Small enough to feel instant.\nconst SIGNALING_LOSS_JITTER_MS = 500;\n// Settle window for the post-ack media resume; during a resume the WS-close\n// teardown is disabled, so this is the only guard against a wedged resume.\nconst RECONNECT_ICE_TIMEOUT_MS = 15000;\n// Reconnect error codes that cannot be resumed — give up without retrying.\n// Any other failure (transient WS/ICE, server_busy) is retryable.\nconst TERMINAL_RECONNECT_CODES = new Set([\n 'session_expired',\n 'session_inactive',\n 'invalid_credential',\n 'max_recovery_exceeded',\n 'unexpected_first_frame',\n]);\n/** User-facing copy for terminal reconnect failures. Infra-agnostic. */\nconst RECONNECT_ERROR_MESSAGES = {\n session_expired: 'Your session has expired.',\n session_inactive: 'The call has ended.',\n invalid_credential: 'Could not resume the call.',\n max_recovery_exceeded: 'Could not resume the call after several attempts.',\n unexpected_first_frame: 'Could not resume the call.',\n};\n/** User-friendly error messages for WebRTC API failures */\nconst WEBRTC_ERRORS = Object.assign(Object.assign({}, MEDIA_ERRORS), { setRemoteDescription: 'Connection setup failed', setLocalDescription: 'Connection setup failed', addIceCandidate: 'Network configuration failed', createOffer: 'Browser audio setup failed' });\n/** Native WebRTC voice provider for Moveo web-voice integrations. */\nexport class WebRTCProvider {\n constructor(wsUrl, \n // Re-fetches /config (fresh TURN + session token) for a resume; throws\n // ServerBusyError on a server-busy 503. Injected so signaling stays here.\n fetchConfig) {\n this.wsUrl = wsUrl;\n this.fetchConfig = fetchConfig;\n this.log = debug('moveo:webrtc:media');\n this.pc = null;\n this.signaling = null;\n this.localStream = null;\n this.remoteStream = null;\n this.remoteAudio = null;\n this.remoteAudioMonitor = null;\n this.audioMonitor = null;\n this.qualityMonitor = null;\n this.stallDetector = null;\n this.pendingCandidates = [];\n this.state = 'idle';\n this.eventListeners = new Map();\n // --- ICE-restart recovery state ---\n this.iceRestarting = false;\n this.iceRestartAttempts = 0;\n this.iceRestartTimeout = null;\n // Fires if an ICE-restart offer goes unanswered: signals a dead WS (vs a\n // media-only blip) so the restart escalates to a resume instead of stalling.\n this.iceRestartAnswerTimeout = null;\n this.connectResolve = null;\n this.connectReject = null;\n // True while a session resume is in flight (drain or abnormal signaling loss);\n // gates the ICE disconnect/failed handlers and the WS-close backstop so the\n // resume flow owns teardown.\n this.resuming = false;\n this.reconnectAttempts = 0;\n // Captured from an error frame mid-resume to classify terminal vs retryable.\n this.reconnectErrorCode = null;\n }\n /** Supply the /config result (TURN + session token) for the next connect(). */\n setConfig(config) {\n this.config = config;\n }\n async connect() {\n if (!this.config) {\n throw new Error('Voice config not set; call setConfig() before connect().');\n }\n this.setState('connecting');\n // 1. Get microphone access\n try {\n this.localStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n }\n catch (error) {\n const name = error instanceof Error ? error.name : '';\n const message = WEBRTC_ERRORS[name] || 'Microphone access failed';\n this.emitError(message);\n throw new Error(message);\n }\n // 2. Open signaling WebSocket\n this.signaling = new SignalingClient(this.wsUrl, this.config.sessionToken, this.handleSignalingMessage.bind(this), this.handleSignalingClose.bind(this), this.handleSignalingError.bind(this));\n try {\n await this.signaling.connect();\n }\n catch (error) {\n this.cleanupLocalStream();\n const message = error instanceof Error ? error.message : 'Signaling connection failed';\n this.emitError(message);\n throw new Error(message);\n }\n // 3. Create PeerConnection\n this.pc = new RTCPeerConnection({\n iceServers: this.config.iceServers,\n iceTransportPolicy: this.config.iceTransportPolicy || 'relay',\n });\n // 4. Add local audio track\n const audioTrack = this.localStream.getAudioTracks()[0];\n this.pc.addTrack(audioTrack, this.localStream);\n // 5. Set up PC handlers\n this.pc.ontrack = this.handleRemoteTrack.bind(this);\n this.pc.onicecandidate = (event) => {\n var _a;\n if (event.candidate) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({\n type: 'ice',\n candidate: event.candidate.toJSON(),\n });\n }\n };\n this.pc.onconnectionstatechange =\n this.handleConnectionStateChange.bind(this);\n // 6. Create and send offer\n try {\n const offer = await this.pc.createOffer();\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await this.pc.setLocalDescription(offer);\n this.signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (error) {\n this.cleanup();\n const name = error instanceof Error ? error.name : 'createOffer';\n const message = WEBRTC_ERRORS[name] || 'Failed to create call offer';\n this.emitError(message);\n throw new Error(message);\n }\n // 7. Start local audio monitoring\n this.audioMonitor = new AudioLevelMonitor();\n this.audioMonitor.start(this.localStream, (level) => {\n this.emit('audio-level', level, Number(level));\n });\n // 8. Wait for PeerConnection to reach 'connected' state\n return new Promise((resolve, reject) => {\n this.connectResolve = resolve;\n this.connectReject = reject;\n });\n }\n disconnect() {\n this.disconnectInternal(false);\n }\n disconnectInternal(suppressOutboundHangup) {\n // Fault-tolerant disconnect: each step wrapped in try/catch\n var _a, _b, _c, _d, _e, _f, _g;\n if (!suppressOutboundHangup) {\n try {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({ type: 'hangup' });\n }\n catch (_h) {\n // continue cleanup\n }\n }\n try {\n (_b = this.qualityMonitor) === null || _b === void 0 ? void 0 : _b.stop();\n this.qualityMonitor = null;\n }\n catch (_j) {\n // continue cleanup\n }\n try {\n (_c = this.stallDetector) === null || _c === void 0 ? void 0 : _c.stop();\n this.stallDetector = null;\n }\n catch (_k) {\n // continue cleanup\n }\n try {\n (_d = this.audioMonitor) === null || _d === void 0 ? void 0 : _d.stop();\n this.audioMonitor = null;\n }\n catch (_l) {\n // continue cleanup\n }\n try {\n (_e = this.remoteAudioMonitor) === null || _e === void 0 ? void 0 : _e.stop();\n this.remoteAudioMonitor = null;\n }\n catch (_m) {\n // continue cleanup\n }\n try {\n (_f = this.pc) === null || _f === void 0 ? void 0 : _f.close();\n this.pc = null;\n }\n catch (_o) {\n // continue cleanup\n }\n this.cleanupLocalStream();\n try {\n (_g = this.signaling) === null || _g === void 0 ? void 0 : _g.close();\n this.signaling = null;\n }\n catch (_p) {\n // continue cleanup\n }\n try {\n if (this.remoteAudio) {\n this.remoteAudio.srcObject = null;\n this.remoteAudio.remove();\n this.remoteAudio = null;\n }\n this.remoteStream = null;\n }\n catch (_q) {\n // continue cleanup\n }\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n if (this.connectReject) {\n this.connectReject(new Error('Call disconnected before connection completed'));\n }\n this.connectResolve = null;\n this.connectReject = null;\n // Clear session-resume state so a torn-down provider can't leave\n // resuming stuck true (which would make handleSignalingClose swallow a\n // future legitimate close), carry a stale retry count, or resume a new call\n // with the prior session's credentials before its own session_ready lands.\n this.resuming = false;\n this.reconnectAttempts = 0;\n this.reconnectErrorCode = null;\n this.originalSessionID = undefined;\n this.originalConnectionID = undefined;\n this.pendingCandidates = [];\n this.setState('idle');\n }\n toggleMute() {\n var _a, _b;\n const track = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];\n if (track) {\n track.enabled = !track.enabled;\n const muted = !track.enabled;\n (_b = this.signaling) === null || _b === void 0 ? void 0 : _b.send({ type: 'mute', muted });\n return muted;\n }\n return false;\n }\n isMuted() {\n var _a, _b, _c;\n return !((_c = (_b = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0]) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : true);\n }\n getLocalStream() {\n return this.localStream;\n }\n getRemoteStream() {\n return this.remoteStream;\n }\n sendDigits(digits) {\n var _a;\n // Server expects one digit per signaling message (matches Twilio path,\n // which lets the SDK split a multi-char string internally).\n for (const ch of digits) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({ type: 'dtmf', digit: ch });\n }\n }\n on(event, handler) {\n var _a;\n const listeners = (_a = this.eventListeners.get(event)) !== null && _a !== void 0 ? _a : new Set();\n listeners.add(handler);\n this.eventListeners.set(event, listeners);\n return () => this.off(event, handler);\n }\n off(event, handler) {\n const listeners = this.eventListeners.get(event);\n if (!listeners)\n return;\n listeners.delete(handler);\n if (listeners.size === 0) {\n this.eventListeners.delete(event);\n }\n }\n // --- Private methods ---\n handleRemoteTrack(event) {\n const [remoteStream] = event.streams;\n this.remoteStream = remoteStream !== null && remoteStream !== void 0 ? remoteStream : null;\n this.remoteAudio = document.createElement('audio');\n this.remoteAudio.srcObject = remoteStream;\n this.remoteAudio.autoplay = true;\n document.body.appendChild(this.remoteAudio);\n // Safari (incl. iOS, and stricter in Private mode) does not reliably\n // autoplay an <audio> whose source is a MediaStream — the `autoplay`\n // attribute alone leaves it silent. Kick off an explicit play(); the call\n // already ran getUserMedia behind a user gesture, so the audio session is\n // unlocked. Swallow a rejection so a blocked attempt stays silent, not fatal.\n void this.remoteAudio.play().catch(() => {\n // Playback blocked — the element stays live; nothing else to do here.\n });\n // Remote audio monitoring via AnalyserNode for thinking indicator\n this.remoteAudioMonitor = new AudioLevelMonitor();\n this.remoteAudioMonitor.start(remoteStream, (level) => {\n if (Number(level) > REMOTE_AUDIO_THRESHOLD) {\n this.emit('remote-audio', level, Number(level));\n }\n });\n }\n handleConnectionStateChange() {\n var _a, _b, _c, _d;\n if (!this.pc)\n return;\n this.log('connectionState %s', this.pc.connectionState);\n switch (this.pc.connectionState) {\n case 'connecting':\n this.setState('connecting');\n break;\n case 'connected': {\n const wasReconnecting = this.state === 'reconnecting';\n this.setState('connected');\n // Reaching 'connected' means any in-flight ICE restart recovered.\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n // A disconnected → connected recovery fires no stall onRecover, so the\n // \"Reconnecting…\" indicator is cleared here.\n if (wasReconnecting) {\n this.emit('warning-cleared', undefined, 'reconnect');\n }\n // 'connected' can re-fire after a recovery, so stop existing monitors\n // before recreating to avoid leaking their poll intervals.\n (_a = this.qualityMonitor) === null || _a === void 0 ? void 0 : _a.stop();\n this.qualityMonitor = new CallQualityMonitor(this.pc, (name, message) => this.emit('warning', message, name), (name) => this.emit('warning-cleared', undefined, name));\n this.qualityMonitor.start();\n (_b = this.stallDetector) === null || _b === void 0 ? void 0 : _b.stop();\n this.stallDetector = new MediaStallDetector(this.pc, () => {\n this.log('media stall -> ICE restart');\n void this.triggerIceRestart();\n }, () => {\n this.log('media recovered');\n // Media is flowing again: the restart (or a passive recovery) worked.\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n // A same-PC ICE restart fires no connectionState transition, so\n // recovery is confirmed here: leave 'reconnecting' and clear the\n // indicator.\n if (this.state === 'reconnecting') {\n this.setState('connected');\n }\n this.emit('warning-cleared', undefined, 'reconnect');\n });\n this.stallDetector.start();\n // Resolve connect() promise\n (_c = this.connectResolve) === null || _c === void 0 ? void 0 : _c.call(this);\n this.connectResolve = null;\n this.connectReject = null;\n break;\n }\n case 'disconnected':\n // A session resume owns the lifecycle; don't start a recovery while we\n // are intentionally tearing the PC down.\n if (this.resuming) {\n break;\n }\n void this.triggerIceRestart();\n break;\n case 'failed':\n // During a resume, just fail the pending handshake; reconnect() handles\n // retry/teardown. Do not emitError or tear the call down here.\n if (this.resuming) {\n (_d = this.connectReject) === null || _d === void 0 ? void 0 : _d.call(this, new Error('ICE connection failed'));\n this.connectResolve = null;\n this.connectReject = null;\n break;\n }\n // Initial connect that never reached 'connected': no session to recover.\n if (this.connectReject) {\n this.connectReject(new Error('ICE connection failed'));\n this.connectResolve = null;\n this.connectReject = null;\n this.emitError('Connection failed');\n this.cleanup();\n break;\n }\n // Established call: recover via ICE restart. A 'failed' while a restart is\n // already in flight means that attempt died, so react now (retry or give\n // up) instead of waiting out the recovery timeout.\n if (this.iceRestarting) {\n this.onRestartAttemptExpired();\n }\n else {\n void this.triggerIceRestart();\n }\n break;\n case 'closed':\n this.cleanup();\n break;\n }\n }\n /**\n * Recover a mid-call media drop by restarting ICE on the live PeerConnection\n * (same session, same WS). Bounded attempts; each waits ICE_RESTART_TIMEOUT_MS\n * to reach 'connected' before retrying or giving up. Never runs during a\n * session resume, which owns its own teardown.\n */\n async triggerIceRestart() {\n if (this.resuming || !this.pc || !this.signaling) {\n return;\n }\n // One restart at a time; bound total attempts.\n if (this.iceRestarting ||\n this.iceRestartAttempts >= MAX_ICE_RESTART_ATTEMPTS) {\n return;\n }\n // A re-offer can't reach the server over a dead WS (a combined media +\n // signaling drop needs WS reconnect, not ICE restart); don't burn attempts —\n // resume the session on a fresh WS when the call is resumable.\n if (!this.signaling.isConnected) {\n if (this.isResumeEligible()) {\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n return;\n }\n this.emitError('Connection lost');\n this.cleanup();\n return;\n }\n const pc = this.pc;\n const signaling = this.signaling;\n this.iceRestarting = true;\n this.iceRestartAttempts++;\n this.log('ICE restart attempt %d/%d', this.iceRestartAttempts, MAX_ICE_RESTART_ATTEMPTS);\n this.setState('reconnecting');\n // 'reconnect' warning drives the widgets' \"Reconnecting…\" indicator.\n this.emit('warning', 'Reconnecting…', 'reconnect');\n try {\n const offer = await pc.createOffer({ iceRestart: true });\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await pc.setLocalDescription(offer);\n signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (_a) {\n // A resume or teardown during the awaits closes this pc, so the offer\n // rejects — expected, not a media failure. Stay quiet if the resume now\n // owns the lifecycle or pc was replaced; only a still-current restart is\n // a real 'Connection lost'.\n if (this.resuming || !this.iceRestarting || this.pc !== pc) {\n return;\n }\n this.iceRestarting = false;\n this.emitError('Connection lost');\n this.cleanup();\n return;\n }\n // Teardown, a drain resume, or a new call on a reused provider may have run\n // during the awaits (clearing iceRestarting or replacing pc); don't arm it.\n if (!this.iceRestarting || this.pc !== pc) {\n return;\n }\n // Recovery is confirmed by media resuming (the detector's onRecover); this\n // timer only fires if it never does, then retries or gives up.\n this.iceRestartTimeout = setTimeout(() => {\n this.onRestartAttemptExpired();\n }, ICE_RESTART_TIMEOUT_MS);\n // Shorter deadline for the answer: no answer means the WS is dead, so cut\n // straight to a resume rather than waiting out the media timer (see const).\n this.iceRestartAnswerTimeout = setTimeout(() => {\n this.onIceRestartUnanswered();\n }, ICE_RESTART_ANSWER_TIMEOUT_MS);\n }\n /**\n * No answer to the ICE-restart offer within the deadline ⇒ the WS is dead (not\n * a media-only blip). Escalate to a fresh-WS resume if resumable; otherwise let\n * the per-attempt timer expire and onRestartAttemptExpired handle it.\n */\n onIceRestartUnanswered() {\n this.iceRestartAnswerTimeout = null;\n if (!this.iceRestarting) {\n return;\n }\n if (this.isResumeEligible()) {\n this.log('ICE restart offer unanswered -> resume');\n // beginResume() clears the in-flight restart, incl. the media timer.\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n }\n }\n /** A restart attempt yielded no recovery (timeout, or a hard ICE 'failed'). */\n onRestartAttemptExpired() {\n this.log('ICE restart attempt expired (attempts=%d)', this.iceRestartAttempts);\n if (this.iceRestartTimeout) {\n clearTimeout(this.iceRestartTimeout);\n this.iceRestartTimeout = null;\n }\n // Cancel this attempt's answer-deadline; the next attempt arms its own, and\n // a stale timer would fire against it (e.g. a fast 'failed' beats the 3s).\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n this.iceRestarting = false;\n if (this.iceRestartAttempts < MAX_ICE_RESTART_ATTEMPTS) {\n void this.triggerIceRestart();\n }\n else if (this.isResumeEligible()) {\n // Same-PC restarts exhausted; a resume rebuilds both planes (fresh WS +\n // fresh TURN), rescuing drops a restart can't (e.g. a dead relay\n // allocation). The old WS closes 4001 so the server keeps it resumable.\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n }\n else {\n this.emitError('Connection lost');\n this.cleanup();\n }\n }\n /** Cancel an in-flight ICE-restart recovery (on recovery or teardown). */\n clearIceRestart() {\n if (this.iceRestarting) {\n this.log('ICE restart cleared');\n }\n if (this.iceRestartTimeout) {\n clearTimeout(this.iceRestartTimeout);\n this.iceRestartTimeout = null;\n }\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n this.iceRestarting = false;\n }\n handleSignalingMessage(msg) {\n var _a;\n switch (msg.type) {\n case 'answer':\n void this.handleAnswer(msg.sdp);\n break;\n case 'ice':\n void this.addIceCandidate(msg.candidate);\n break;\n // 'transcript' and 'agent_response' are forward-looking — channels\n // Phase 1 (PR #756) does not emit them over signaling. Kept so the\n // client is ready when channels wires them up.\n case 'transcript':\n this.emit('transcript', msg.text, {\n text: msg.text,\n is_final: msg.is_final,\n });\n break;\n case 'agent_response':\n this.emit('agent-response', msg.text, {\n text: msg.text,\n message_id: msg.message_id,\n });\n break;\n case 'hangup':\n this.disconnectInternal(true);\n break;\n // session_ready (not welcome) is the resume-eligible signal: cache the\n // credentials for a future reconnect.\n case 'session_ready':\n this.originalSessionID = msg.session_id;\n this.originalConnectionID = msg.connection_id;\n break;\n // Rotated credential for the next reconnect.\n case 'reconnect_ack':\n this.originalConnectionID = msg.connection_id;\n break;\n case 'drain':\n void this.handleDrain(msg);\n break;\n case 'error':\n if (this.resuming) {\n // Record for reconnect()'s terminal-vs-retryable check. If a resume\n // is awaiting ICE (connectReject set), fail it now so the retry loop\n // reacts immediately instead of waiting out RECONNECT_ICE_TIMEOUT_MS.\n this.reconnectErrorCode = msg.error.code;\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`reconnect rejected: ${msg.error.code}`));\n }\n else {\n this.emitError(msg.error.message);\n }\n break;\n }\n }\n handleSignalingClose(_code, _reason) {\n var _a;\n // The resume flow owns teardown while in flight. If a resume is awaiting\n // ICE (connectReject set), fail it now so the retry loop reacts to the\n // dropped WS immediately; otherwise (pre-ack / jitter window) just ignore.\n if (this.resuming) {\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error('Signaling closed during resume'));\n return;\n }\n // Abnormal close of an established call (network switch, 1006, idle-proxy\n // drop): resume on a fresh WS instead of tearing down. Intentional closes\n // route elsewhere first (hangup nulls the handlers, drain sets resuming), so\n // being eligible here means the close was abnormal.\n if (this.isResumeEligible()) {\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n return;\n }\n if (this.state !== 'idle') {\n this.disconnect();\n }\n }\n handleSignalingError(_error) {\n // Browsers fire onerror just before the abnormal onclose. Emitting here\n // would flip state to 'error', killing resume eligibility and letting the\n // host UI dispose the engine mid-resume. Stay quiet; the close handler owns\n // the outcome.\n if (this.resuming || this.isResumeEligible()) {\n return;\n }\n if (this.state === 'connected') {\n this.emitError('Signaling error');\n }\n }\n /**\n * Resume needs a fully-established call: cached credentials, no resume already\n * in flight, and state connected/reconnecting ('reconnecting' = an in-flight\n * ICE restart, where the session is still established server-side).\n */\n isResumeEligible() {\n return (!this.resuming &&\n (this.state === 'connected' || this.state === 'reconnecting') &&\n this.originalSessionID !== undefined &&\n this.originalConnectionID !== undefined);\n }\n /**\n * Enter the resume flow (drain / signaling loss / exhausted ICE restarts): own\n * the lifecycle, cancel any in-flight ICE restart, optionally wait a jittered\n * delay, then run the reconnect loop. Callers must have checked isResumeEligible().\n */\n async beginResume(jitterMs = 0) {\n this.resuming = true;\n this.setState('reconnecting');\n this.clearIceRestart();\n // 'reconnect' warning drives the widgets' \"Reconnecting…\" indicator.\n this.emit('warning', 'Reconnecting…', 'reconnect');\n if (jitterMs > 0) {\n // Jitter 0..delay to avoid a thundering herd against the sibling pods.\n await this.sleep(Math.random() * jitterMs);\n // A teardown (user hangup / destroy) during the jitter clears resuming;\n // don't resurrect an ended call.\n if (!this.resuming) {\n return;\n }\n }\n await this.reconnect();\n }\n /**\n * Server is draining this pod: resume the same session on a sibling after a\n * jittered delay. Distinct from the ICE-level 'disconnected' path (a blip) —\n * this is a server-initiated handoff.\n */\n async handleDrain(msg) {\n // Already resuming: a second drain (sibling pod drains before ICE connects)\n // is handled by the in-flight retry loop. Re-entering would hard-kill it.\n if (this.resuming) {\n return;\n }\n // Only a fully-established session is resumable: a drain before\n // session_ready (no cached credentials) or while not connected can't resume.\n if (!this.isResumeEligible()) {\n this.emitError('Could not resume the call.');\n this.disconnectInternal(true);\n return;\n }\n await this.beginResume(Math.max(0, msg.reconnect_delay_ms || 0));\n }\n /**\n * Tear down the pod-local PC/WS and resume on a sibling pod, reusing the\n * existing mic MediaStream (no second permission prompt). Retries transient\n * failures up to MAX_CLIENT_RETRIES; gives up on a terminal server rejection.\n */\n async reconnect() {\n var _a, _b, _c;\n // Detach senders before pc.close() (ghost-sender defense), then confirm the\n // mic track survived the drain (device unplug / OS reset / BT switch kill it).\n try {\n (_a = this.pc) === null || _a === void 0 ? void 0 : _a.getSenders().forEach((sender) => {\n var _a;\n if (sender.track) {\n (_a = this.pc) === null || _a === void 0 ? void 0 : _a.removeTrack(sender);\n }\n });\n }\n catch (_d) {\n // continue — closing the pc below releases the senders anyway\n }\n const track = (_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getAudioTracks()[0];\n if (!track || track.readyState !== 'live') {\n this.emitError('Microphone is no longer available.');\n this.disconnectInternal(true);\n return;\n }\n // Drop pod-local resources but KEEP the MediaStream alive for reuse.\n this.teardownForReconnect();\n while (this.reconnectAttempts < MAX_CLIENT_RETRIES) {\n this.reconnectErrorCode = null;\n try {\n // Fresh /config → fresh TURN creds for the sibling pod.\n this.config = await this.fetchConfig();\n // A user hangup / destroy during the (up to 10s) fetch clears resuming;\n // don't open a new WS for an ended call.\n if (!this.resuming) {\n return;\n }\n this.signaling = new SignalingClient(this.wsUrl, this.config.sessionToken, this.handleSignalingMessage.bind(this), this.handleSignalingClose.bind(this), this.handleSignalingError.bind(this));\n // Sends `reconnect` as the first frame; resolves on reconnect_ack.\n await this.signaling.connectForReconnect({\n type: 'reconnect',\n session_id: this.originalSessionID,\n connection_id: this.originalConnectionID,\n });\n // Resumed — rebuild the media plane and wait for ICE to reconnect.\n await this.buildReconnectPeerConnection(track);\n this.reconnectAttempts = 0;\n this.resuming = false;\n // Clear any \"reconnecting…\" banner raised by a retried attempt.\n this.emit('warning-cleared', undefined, 'reconnect');\n return;\n }\n catch (error) {\n // A terminal server rejection (bad credential, expired/ended session,\n // recovery cap) is not retryable — surface and stop.\n const code = this.reconnectErrorCode;\n if (code && TERMINAL_RECONNECT_CODES.has(code)) {\n this.emitError((_c = RECONNECT_ERROR_MESSAGES[code]) !== null && _c !== void 0 ? _c : 'Could not resume the call.');\n this.disconnectInternal(true);\n return;\n }\n // A terminal /config failure (e.g. TURN unavailable) won't fix by\n // retrying — surface its message and stop.\n if (error instanceof ConfigUnavailableError) {\n this.emitError(error.message);\n this.disconnectInternal(true);\n return;\n }\n // Transient (WS/ICE, server_busy 503, rate-limited): back off and retry.\n // Only server_busy surfaces its message; a rate-limit reconnect stays\n // generic since \"too many attempts\" misreads an automatic retry.\n const message = error instanceof ServerBusyError ? error.message : 'Reconnecting…';\n this.emit('warning', message, 'reconnect');\n this.teardownForReconnect();\n this.reconnectAttempts++;\n if (this.reconnectAttempts >= MAX_CLIENT_RETRIES) {\n break;\n }\n await this.sleep(RECONNECT_BACKOFF_MS * this.reconnectAttempts);\n // Torn down (hangup / destroy) during the backoff — stop retrying.\n if (!this.resuming) {\n return;\n }\n }\n }\n this.emitError('Could not resume the call.');\n this.disconnectInternal(true);\n }\n /**\n * Build a fresh PeerConnection for a resume using the existing audio track.\n * Mirrors the fresh-connect PC setup but rejects on offer failure so the\n * reconnect retry loop can decide what to do (the fresh path tears down).\n */\n buildReconnectPeerConnection(audioTrack) {\n this.pc = new RTCPeerConnection({\n iceServers: this.config.iceServers,\n iceTransportPolicy: this.config.iceTransportPolicy || 'relay',\n });\n this.pc.addTrack(audioTrack, this.localStream);\n this.pc.ontrack = this.handleRemoteTrack.bind(this);\n this.pc.onicecandidate = (event) => {\n var _a;\n if (event.candidate) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({\n type: 'ice',\n candidate: event.candidate.toJSON(),\n });\n }\n };\n this.pc.onconnectionstatechange =\n this.handleConnectionStateChange.bind(this);\n this.audioMonitor = new AudioLevelMonitor();\n this.audioMonitor.start(this.localStream, (level) => {\n this.emit('audio-level', level, Number(level));\n });\n return new Promise((resolve, reject) => {\n // Bound the resume: if ICE never reaches 'connected' (stalled, or the\n // sibling-pod WS drops after the ack — both swallowed by the drain-path\n // handlers), reject so the retry loop can take over instead of hanging.\n const timer = setTimeout(() => {\n this.connectResolve = null;\n this.connectReject = null;\n reject(new Error('Reconnect media resume timed out'));\n }, RECONNECT_ICE_TIMEOUT_MS);\n // Resolved/rejected by handleConnectionStateChange ('connected'/'failed').\n this.connectResolve = () => {\n clearTimeout(timer);\n resolve();\n };\n this.connectReject = (error) => {\n clearTimeout(timer);\n reject(error);\n };\n void (async () => {\n var _a;\n try {\n const offer = await this.pc.createOffer();\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await this.pc.setLocalDescription(offer);\n this.signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (error) {\n const name = error instanceof Error ? error.name : 'createOffer';\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error(WEBRTC_ERRORS[name] || 'Failed to create call offer'));\n }\n })();\n });\n }\n /**\n * Stop pod-local resources (PC, signaling, monitors, stall detector, remote\n * audio element) but deliberately KEEP localStream so the mic is reused.\n */\n teardownForReconnect() {\n var _a, _b, _c, _d, _e, _f;\n try {\n (_a = this.qualityMonitor) === null || _a === void 0 ? void 0 : _a.stop();\n this.qualityMonitor = null;\n }\n catch (_g) {\n // continue cleanup\n }\n try {\n (_b = this.stallDetector) === null || _b === void 0 ? void 0 : _b.stop();\n this.stallDetector = null;\n }\n catch (_h) {\n // continue cleanup\n }\n try {\n (_c = this.audioMonitor) === null || _c === void 0 ? void 0 : _c.stop();\n this.audioMonitor = null;\n }\n catch (_j) {\n // continue cleanup\n }\n try {\n (_d = this.remoteAudioMonitor) === null || _d === void 0 ? void 0 : _d.stop();\n this.remoteAudioMonitor = null;\n }\n catch (_k) {\n // continue cleanup\n }\n try {\n (_e = this.pc) === null || _e === void 0 ? void 0 : _e.close();\n this.pc = null;\n }\n catch (_l) {\n // continue cleanup\n }\n try {\n // 4001 marks this close as resume-initiated so the server keeps the\n // session resumable; a clean 1000 would tear it down before the reconnect\n // lands (matters when the old WS is still alive, e.g. exhausted-restart).\n (_f = this.signaling) === null || _f === void 0 ? void 0 : _f.close(WS_CLOSE_RESUME, 'Resuming');\n this.signaling = null;\n }\n catch (_m) {\n // continue cleanup\n }\n try {\n if (this.remoteAudio) {\n this.remoteAudio.srcObject = null;\n this.remoteAudio.remove();\n this.remoteAudio = null;\n }\n this.remoteStream = null;\n }\n catch (_o) {\n // continue cleanup\n }\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n this.connectResolve = null;\n this.connectReject = null;\n this.pendingCandidates = [];\n }\n sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n async handleAnswer(sdp) {\n // Any answer proves the signaling round-trip works, so cancel the\n // ICE-restart \"unanswered → resume\" escalation (media-only blip path).\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n if (!this.pc)\n return;\n try {\n await this.pc.setRemoteDescription({ type: 'answer', sdp });\n await this.flushPendingCandidates();\n }\n catch (_a) {\n this.emitError(WEBRTC_ERRORS['setRemoteDescription']);\n }\n }\n async addIceCandidate(candidate) {\n var _a;\n if ((_a = this.pc) === null || _a === void 0 ? void 0 : _a.remoteDescription) {\n try {\n await this.pc.addIceCandidate(candidate);\n }\n catch (_b) {\n // Non-critical — log but don't fail\n }\n }\n else {\n this.pendingCandidates.push(candidate);\n }\n }\n async flushPendingCandidates() {\n for (const candidate of this.pendingCandidates) {\n if (!this.pc)\n break;\n try {\n await this.pc.addIceCandidate(candidate);\n }\n catch (_a) {\n // Non-critical\n }\n }\n this.pendingCandidates = [];\n }\n cleanup() {\n this.disconnect();\n }\n cleanupLocalStream() {\n var _a;\n try {\n (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());\n this.localStream = null;\n }\n catch (_b) {\n // continue cleanup\n }\n }\n setState(newState) {\n this.state = newState;\n this.emit('state-change', newState, newState);\n }\n emitError(message) {\n this.emit('error', message);\n this.setState('error');\n }\n emit(event, message, data) {\n const detail = { state: event, message, data };\n const invoke = (name) => {\n const listeners = this.eventListeners.get(name);\n if (!listeners)\n return;\n listeners.forEach((listener) => {\n try {\n listener(detail);\n }\n catch (_a) {\n // Ignore listener errors\n }\n });\n };\n invoke(event);\n invoke('any');\n }\n}\n","/**\n * Web-voice signaling message types.\n *\n * Canonical spec lives in the channels repo:\n * https://github.com/moveo-ai/channels/blob/main/docs/webvoice-signaling.md\n *\n * Protocol changes require lockstep updates to both repos plus the spec doc.\n */\n/**\n * App WS close code for a resume-initiated close (not a call end): channels\n * treats it as abnormal and keeps the session resumable, where a clean 1000\n * tears it down. Wire contract — keep in sync with the server's\n * resumableReadError classification.\n */\nexport const WS_CLOSE_RESUME = 4001;\n"],"names":["AudioLevelMonitor","constructor","this","analyserNode","audioSource","animationId","audioContext","start","stream","onLevel","AudioContext","createMediaStreamSource","createAnalyser","fftSize","smoothingTimeConstant","connect","dataArray","Float32Array","checkAudioLevel","stop","getFloatTimeDomainData","sum","amplitude","rms","Math","sqrt","length","normalizedLevel","min","toFixed","requestAnimationFrame","_a","cancelAnimationFrame","disconnect","close","catch","MEDIA_ERRORS","NotAllowedError","NotFoundError","NotReadableError","OverconstrainedError","QUALITY_WARNINGS","CallQualityMonitor","pc","onWarning","onWarningCleared","pollIntervalMs","interval","prevStats","activeWarnings","Set","setInterval","pollStats","clearInterval","clear","stats","getStats","jitter","packetsReceived","packetsLost","pair","rtt","currentRoundTripTime","forEach","report","_b","_c","type","kind","rtp","lossRate","deltaReceived","deltaLost","total","checkWarning","R","max","mos","name","isExceeded","has","add","delete","ServerBusyError","Error","message","super","ConfigUnavailableError","RateLimitedError","MediaStallDetector","onStall","onRecover","flatPollsThreshold","escalate","ua","navigator","userAgent","test","isWebKitEngine","log","prevCounts","flatPolls","stalled","cannotAssessLogged","poll","counts","Map","undefined","set","id","collectAudioInboundRtp","size","prev","advanced","shrunk","comparable","hasNewReport","packets","before","get","describeSelectedPair","selectCandidatePair","selectedPairId","pairs","selectedCandidatePairId","push","selected","find","p","best","nominated","bt","lastPacketReceivedTimestamp","pt","describeCandidatePair","describe","c","base","candidateType","protocol","relayProtocol","localCandidateId","remoteCandidateId","SignalingClient","url","sessionToken","onMessage","onClose","onError","ws","Promise","resolve","reject","wsUrl","encodeURIComponent","WebSocket","timeout","setTimeout","onopen","onmessage","event","msg","JSON","parse","data","clearTimeout","onerror","onclose","code","reason","connectForReconnect","reconnectFrame","settled","send","stringify","error","readyState","OPEN","isConnected","SIGNALING_LOSS_JITTER_MS","TERMINAL_RECONNECT_CODES","RECONNECT_ERROR_MESSAGES","session_expired","session_inactive","invalid_credential","max_recovery_exceeded","unexpected_first_frame","WEBRTC_ERRORS","Object","assign","setRemoteDescription","setLocalDescription","addIceCandidate","createOffer","WebRTCProvider","fetchConfig","signaling","localStream","remoteStream","remoteAudio","remoteAudioMonitor","audioMonitor","qualityMonitor","stallDetector","pendingCandidates","state","eventListeners","iceRestarting","iceRestartAttempts","iceRestartTimeout","iceRestartAnswerTimeout","connectResolve","connectReject","resuming","reconnectAttempts","reconnectErrorCode","setConfig","config","setState","mediaDevices","getUserMedia","audio","echoCancellation","noiseSuppression","autoGainControl","emitError","handleSignalingMessage","bind","handleSignalingClose","handleSignalingError","cleanupLocalStream","RTCPeerConnection","iceServers","iceTransportPolicy","audioTrack","getAudioTracks","addTrack","ontrack","handleRemoteTrack","onicecandidate","candidate","toJSON","onconnectionstatechange","handleConnectionStateChange","offer","sdp","cleanup","level","emit","Number","disconnectInternal","suppressOutboundHangup","_d","_e","_f","_g","_h","_j","_k","_l","_m","_o","_p","srcObject","remove","_q","clearIceRestart","originalSessionID","originalConnectionID","toggleMute","track","enabled","muted","isMuted","getLocalStream","getRemoteStream","sendDigits","digits","ch","digit","on","handler","listeners","off","streams","document","createElement","autoplay","body","appendChild","play","connectionState","wasReconnecting","triggerIceRestart","call","onRestartAttemptExpired","isResumeEligible","beginResume","iceRestart","onIceRestartUnanswered","handleAnswer","text","is_final","message_id","session_id","connection_id","handleDrain","_code","_reason","_error","jitterMs","sleep","random","reconnect","reconnect_delay_ms","getSenders","sender","removeTrack","teardownForReconnect","buildReconnectPeerConnection","timer","ms","flushPendingCandidates","remoteDescription","getTracks","t","newState","detail","invoke","listener","WS_CLOSE_RESUME"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"882.min.js","mappings":"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","sources":["webpack://MoveoAI/./src/controller/shared/audio-level-monitor.ts","webpack://MoveoAI/./src/controller/shared/media-errors.ts","webpack://MoveoAI/./src/controller/webrtc/call-quality-monitor.ts","webpack://MoveoAI/./src/controller/webrtc/config-errors.ts","webpack://MoveoAI/./src/controller/webrtc/media-stall-detector.ts","webpack://MoveoAI/./src/controller/webrtc/select-candidate-pair.ts","webpack://MoveoAI/./src/controller/webrtc/signaling-client.ts","webpack://MoveoAI/./src/controller/webrtc/webrtc-provider.ts","webpack://MoveoAI/./src/ts/webrtc.ts"],"sourcesContent":["const NOISE_GATE_THRESHOLD = 0.01;\n/**\n * Monitors a MediaStream's audio level via Web Audio API.\n * Extracted from AudioController for reuse across voice providers.\n *\n * Uses AnalyserNode with RMS calculation:\n * fftSize: 256, smoothingTimeConstant: 0.3\n * Noise gate: 0.01 threshold\n * Normalized output: 0.0 - 1.0\n */\nexport class AudioLevelMonitor {\n constructor() {\n this.analyserNode = null;\n this.audioSource = null;\n this.animationId = null;\n this.audioContext = null;\n }\n start(stream, onLevel) {\n try {\n this.audioContext = new AudioContext();\n this.audioSource = this.audioContext.createMediaStreamSource(stream);\n this.analyserNode = this.audioContext.createAnalyser();\n this.analyserNode.fftSize = 256;\n this.analyserNode.smoothingTimeConstant = 0.3;\n this.audioSource.connect(this.analyserNode);\n const dataArray = new Float32Array(this.analyserNode.fftSize);\n const checkAudioLevel = () => {\n if (!this.analyserNode) {\n this.stop();\n return;\n }\n this.analyserNode.getFloatTimeDomainData(dataArray);\n let sum = 0;\n for (const amplitude of dataArray) {\n sum += amplitude * amplitude;\n }\n const rms = Math.sqrt(sum / dataArray.length);\n const normalizedLevel = Math.min(1, rms * 10);\n const gatedLevel = normalizedLevel > NOISE_GATE_THRESHOLD ? normalizedLevel : 0;\n onLevel(gatedLevel.toFixed(3));\n this.animationId = requestAnimationFrame(checkAudioLevel);\n };\n checkAudioLevel();\n }\n catch (_a) {\n // Silently fail — audio monitoring is non-critical\n }\n }\n stop() {\n if (this.animationId !== null) {\n cancelAnimationFrame(this.animationId);\n this.animationId = null;\n }\n if (this.audioSource) {\n try {\n this.audioSource.disconnect();\n }\n catch (_a) {\n // ignore\n }\n this.audioSource = null;\n }\n if (this.audioContext) {\n void this.audioContext.close().catch(() => {\n // ignore\n });\n this.audioContext = null;\n }\n this.analyserNode = null;\n }\n}\n","/**\n * User-friendly messages for the common getUserMedia DOMException names.\n * Shared across voice providers (native WebRTC, Telnyx) so a single string\n * surfaces in the widget regardless of provider.\n */\nexport const MEDIA_ERRORS = {\n NotAllowedError: 'Microphone permission denied',\n NotFoundError: 'No microphone found',\n NotReadableError: 'Microphone is in use by another application',\n OverconstrainedError: 'Microphone does not support required settings',\n};\n","import { selectCandidatePair } from './select-candidate-pair';\nconst QUALITY_WARNINGS = {\n 'high-rtt': 'High network latency detected',\n 'low-mos': 'Poor call quality detected',\n 'high-jitter': 'Network instability detected',\n 'high-packet-loss': 'Packet loss detected',\n};\nconst THRESHOLDS = {\n RTT: 400, // ms\n JITTER: 0.03, // seconds (30ms)\n PACKET_LOSS: 0.03, // 3%\n MOS: 3.0,\n};\n/**\n * Replaces Twilio Voice Insights with standard WebRTC getStats().\n * Polls pc.getStats() and maps to warning events that AudioWidget handles.\n */\nexport class CallQualityMonitor {\n constructor(pc, onWarning, onWarningCleared, pollIntervalMs = 5000) {\n this.pc = pc;\n this.onWarning = onWarning;\n this.onWarningCleared = onWarningCleared;\n this.pollIntervalMs = pollIntervalMs;\n this.interval = null;\n this.prevStats = null;\n this.activeWarnings = new Set();\n }\n start() {\n this.interval = setInterval(() => {\n void this.pollStats();\n }, this.pollIntervalMs);\n }\n stop() {\n if (this.interval !== null) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.prevStats = null;\n this.activeWarnings.clear();\n }\n async pollStats() {\n try {\n const stats = await this.pc.getStats();\n let jitter = null;\n let packetsReceived = 0;\n let packetsLost = 0;\n // RTT from the pair carrying media, not any nominated one: a stale pair\n // would skew high-rtt / MOS warnings on relay networks.\n const pair = selectCandidatePair(stats);\n const rtt = (pair === null || pair === void 0 ? void 0 : pair.currentRoundTripTime) != null\n ? pair.currentRoundTripTime * 1000\n : null;\n stats.forEach((report) => {\n var _a, _b, _c;\n if (report.type === 'inbound-rtp' && report.kind === 'audio') {\n const rtp = report;\n jitter = (_a = rtp.jitter) !== null && _a !== void 0 ? _a : null;\n packetsReceived = (_b = rtp.packetsReceived) !== null && _b !== void 0 ? _b : 0;\n packetsLost = (_c = rtp.packetsLost) !== null && _c !== void 0 ? _c : 0;\n }\n });\n // Compute packet loss rate over delta\n let lossRate = 0;\n if (this.prevStats) {\n const deltaReceived = packetsReceived - this.prevStats.packetsReceived;\n const deltaLost = packetsLost - this.prevStats.packetsLost;\n const total = deltaReceived + deltaLost;\n lossRate = total > 0 ? deltaLost / total : 0;\n }\n this.prevStats = { packetsReceived, packetsLost };\n // Check thresholds\n this.checkWarning('high-rtt', rtt !== null && rtt > THRESHOLDS.RTT);\n this.checkWarning('high-jitter', jitter !== null && jitter > THRESHOLDS.JITTER);\n this.checkWarning('high-packet-loss', lossRate > THRESHOLDS.PACKET_LOSS);\n // MOS estimation (simplified E-model)\n if (rtt !== null && jitter !== null) {\n const jitterMs = jitter * 1000;\n const lossPercent = lossRate * 100;\n const R = 93.2 - jitterMs * 2 - lossPercent * 40 - Math.max(0, rtt - 150) * 0.1;\n const mos = 1 + 0.035 * R + R * (R - 60) * (100 - R) * 7e-6;\n this.checkWarning('low-mos', mos < THRESHOLDS.MOS);\n }\n }\n catch (_a) {\n // pc may have closed mid-poll; quality stats are non-critical\n }\n }\n checkWarning(name, isExceeded) {\n if (isExceeded && !this.activeWarnings.has(name)) {\n this.activeWarnings.add(name);\n this.onWarning(name, QUALITY_WARNINGS[name] || name);\n }\n else if (!isExceeded && this.activeWarnings.has(name)) {\n this.activeWarnings.delete(name);\n this.onWarningCleared(name);\n }\n }\n}\n","/**\n * Typed errors for the GET /config flow, shared by WebRTCController (initial\n * connect) and WebRTCProvider (reconnect-time re-fetch) so both can branch on\n * a server-busy 503 without parsing message strings.\n */\n/**\n * Raised when /config returns 503 with error_code \"server_busy\" — the pod is at\n * its concurrent-session cap. Retryable with backoff against a sibling pod.\n */\nexport class ServerBusyError extends Error {\n constructor(message = 'Voice service is busy, please try again.') {\n super(message);\n this.name = 'ServerBusyError';\n }\n}\n/**\n * Raised when /config fails in a way retrying can't fix (e.g. TURN or signing\n * key not configured, or origin not allowed). Terminal — the reconnect loop\n * must not burn retries on it.\n */\nexport class ConfigUnavailableError extends Error {\n constructor(message) {\n super(message);\n this.name = 'ConfigUnavailableError';\n }\n}\n/**\n * Raised when /config returns 429 — the integration hit its per-integration\n * rate limit. The caller should back off and let the user retry shortly.\n */\nexport class RateLimitedError extends Error {\n constructor(message = 'Too many attempts. Please try again in a moment.') {\n super(message);\n this.name = 'RateLimitedError';\n }\n}\n","import debug from 'debug';\nimport { describeCandidatePair, selectCandidatePair, } from './select-candidate-pair';\n// All audio inbound-rtp counters, keyed by report id. More than one report\n// can exist transiently (a stale report retained across an SSRC change), so\n// no single-report assumption.\nfunction collectAudioInboundRtp(stats) {\n const counts = new Map();\n stats.forEach((report) => {\n if (report.type === 'inbound-rtp') {\n const rtp = report;\n if (rtp.kind === 'audio' && rtp.packetsReceived !== undefined) {\n counts.set(rtp.id, rtp.packetsReceived);\n }\n }\n });\n return counts;\n}\n// True for any WebKit-backed browser: desktop Safari and every iOS browser\n// (CriOS/FxiOS are WebKit under the hood; iPadOS Safari reports a macOS UA,\n// caught by the Safari-without-Chrome clause).\nexport function isWebKitEngine(ua = navigator.userAgent) {\n return (/\\b(iPad|iPhone|iPod)\\b/.test(ua) ||\n (/AppleWebKit/.test(ua) && !/Chrome|Chromium|CriOS|Edg|OPR/.test(ua)));\n}\n/**\n * Detects a mid-call media-path stall (and its recovery) so the provider can\n * drive an ICE restart and confirm when media returns. Separate from\n * CallQualityMonitor: that polls every 5s for UX warnings, but recovery needs a\n * faster signal that beats the server's ~15s give-up.\n *\n * The signal is the audio inbound-rtp packetsReceived delta per poll: it\n * measures media actually received, independent of which candidate pair\n * delivered it. Candidate-pair and transport counters are deliberately not\n * used — on relay networks media legally arrives on a non-selected pair (the\n * two agents can nominate different relay allocations), and those signals also\n * move on non-media traffic (RTCP, DTLS, STUN), so either way they disagree\n * with real audio flow.\n *\n * Healthy = ANY audio inbound-rtp report advanced since the previous poll: a\n * stale report briefly retained across an SSRC change must not mask the live\n * one, and a genuinely stalled call has no advancing report, so this never\n * false-negatives.\n *\n * Correctness depends on the server streaming continuously (~50 packets/sec,\n * no DTX): that keeps packetsReceived advancing even while the user is silent.\n * If the server ever stops sending mid-call for longer than flatPollsThreshold\n * polls, the detector reports a false stall.\n *\n * With no audio inbound-rtp report (or none exposing packetsReceived) the\n * detector cannot assess and stays inert, logging once per episode. It does\n * not fall back to transport/pair byte counters, which count non-media traffic\n * and would defeat the point of watching received media.\n */\nexport class MediaStallDetector {\n constructor(pc, onStall, onRecover, pollIntervalMs = 1000, \n // Consecutive polls with no inbound media packet before declaring a stall;\n // debounces a brief blip while still beating the 15s server close.\n flatPollsThreshold = 3, \n // WebKit runs detection-only: the ICE-restart escalation is unproven on\n // that engine, so stalls are logged without acting on them.\n escalate = !isWebKitEngine()) {\n this.pc = pc;\n this.onStall = onStall;\n this.onRecover = onRecover;\n this.pollIntervalMs = pollIntervalMs;\n this.flatPollsThreshold = flatPollsThreshold;\n this.escalate = escalate;\n this.log = debug('moveo:webrtc:media');\n this.interval = null;\n this.prevCounts = null;\n this.flatPolls = 0;\n this.stalled = false;\n // Logs the \"cannot assess\" reason once per episode instead of every poll.\n this.cannotAssessLogged = false;\n }\n start() {\n this.log('started (escalate=%s)', this.escalate);\n this.interval = setInterval(() => {\n void this.poll();\n }, this.pollIntervalMs);\n }\n stop() {\n if (this.interval !== null) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.prevCounts = null;\n this.flatPolls = 0;\n this.stalled = false;\n this.cannotAssessLogged = false;\n }\n async poll() {\n try {\n const stats = await this.pc.getStats();\n const counts = collectAudioInboundRtp(stats);\n // No audio inbound-rtp with a packetsReceived counter: can't assess.\n // Baselines and stall state are preserved; log once per episode so a\n // silently-inert detector is visible.\n if (counts.size === 0) {\n if (!this.cannotAssessLogged) {\n this.cannotAssessLogged = true;\n this.log('cannot assess: no audio inbound-rtp with packetsReceived');\n }\n return;\n }\n this.cannotAssessLogged = false;\n const prev = this.prevCounts;\n this.prevCounts = counts;\n if (prev === null) {\n this.log('assessing %d audio inbound-rtp report(s)', counts.size);\n return; // first assessed poll: seed only\n }\n // Healthy if ANY report advanced. `> 0` only: a counter reset (browser\n // bug / id reuse) must not read as recovery — a fake recovery would\n // disarm a live ICE-restart attempt.\n let advanced = false;\n let shrunk = false;\n let comparable = 0;\n let hasNewReport = false;\n counts.forEach((packets, id) => {\n const before = prev.get(id);\n if (before === undefined) {\n hasNewReport = true;\n return;\n }\n comparable++;\n if (packets > before) {\n advanced = true;\n }\n else if (packets < before) {\n shrunk = true;\n }\n });\n if (advanced) {\n if (this.stalled) {\n this.stalled = false;\n this.log('recovered%s (pair %s)', this.escalate ? '' : ' (log-only)', this.describeSelectedPair(stats));\n if (this.escalate) {\n this.onRecover();\n }\n }\n this.flatPolls = 0;\n return;\n }\n // Baselines aren't comparable — reseed. Covers all ids replaced (new\n // SSRC), a counter reset under a reused id, or a freshly-appeared report\n // (no baseline yet, so a frozen sibling can't make it count as flat).\n // `stalled` is left as-is, so recovery lands one poll later.\n if (comparable === 0 || shrunk || hasNewReport) {\n this.log('inbound-rtp reports reseeded (comparable=%d new=%s)', comparable, hasNewReport);\n this.flatPolls = 0;\n return;\n }\n // Comparable reports exist and none advanced: a flat poll.\n this.flatPolls++;\n this.log('flat poll reports=%d flat=%d', counts.size, this.flatPolls);\n if (this.flatPolls >= this.flatPollsThreshold && !this.stalled) {\n this.stalled = true;\n this.log('stall declared%s after %d flat polls (pair %s)', this.escalate ? '' : ' (log-only, escalation disabled)', this.flatPolls, this.describeSelectedPair(stats));\n if (this.escalate) {\n this.onStall();\n }\n }\n }\n catch (_a) {\n // pc may have closed mid-poll; non-critical\n }\n }\n // Path shape for stall/recovery logs only — never used as the signal.\n describeSelectedPair(stats) {\n const pair = selectCandidatePair(stats);\n return pair ? describeCandidatePair(stats, pair) : '?';\n }\n}\n","/**\n * The candidate pair actually carrying media, resolved by\n * transport.selectedCandidatePairId → legacy `selected` flag → best nominated.\n * \"Any nominated\" is not enough: relay paths leave several pairs nominated and\n * a stale one can win, driving false stalls. Best nominated prefers the newest\n * lastPacketReceivedTimestamp (a frozen pair's is in the past), never displacing\n * a timestamped pair with a field-less one, and falls back to the first\n * nominated when none report a timestamp. Callers read the field they need.\n */\nexport function selectCandidatePair(stats) {\n let selectedPairId;\n const pairs = [];\n stats.forEach((report) => {\n if (report.type === 'transport') {\n const { selectedCandidatePairId } = report;\n if (selectedCandidatePairId && !selectedPairId) {\n selectedPairId = selectedCandidatePairId;\n }\n }\n else if (report.type === 'candidate-pair') {\n pairs.push(report);\n }\n });\n // A dangling transport id falls through to the legacy `selected` flag before\n // the nominated fallback.\n const selected = (selectedPairId && pairs.find((p) => p.id === selectedPairId)) ||\n pairs.find((p) => p.selected === true);\n if (selected) {\n return selected;\n }\n let best;\n for (const p of pairs) {\n if (!p.nominated) {\n continue;\n }\n if (best === undefined) {\n best = p;\n continue;\n }\n const bt = best.lastPacketReceivedTimestamp;\n const pt = p.lastPacketReceivedTimestamp;\n if (pt !== undefined && (bt === undefined || pt > bt)) {\n best = p;\n }\n }\n return best;\n}\n/**\n * Compact `local -> remote` candidate-type summary for a pair, e.g.\n * `relay/udp(udp) -> prflx/udp`. Resolves the pair's candidate stats so the\n * media path (relayed vs direct vs asymmetric) is visible in logs without a\n * webrtc-internals dump. Addresses are deliberately omitted (they are PII).\n */\nexport function describeCandidatePair(stats, pair) {\n const describe = (id) => {\n var _a, _b;\n if (!id) {\n return '?';\n }\n const c = stats.get(id);\n if (!c) {\n return '?';\n }\n const base = `${(_a = c.candidateType) !== null && _a !== void 0 ? _a : '?'}/${(_b = c.protocol) !== null && _b !== void 0 ? _b : '?'}`;\n return c.relayProtocol ? `${base}(${c.relayProtocol})` : base;\n };\n return `${describe(pair.localCandidateId)} -> ${describe(pair.remoteCandidateId)}`;\n}\n","import debug from 'debug';\nimport { WS_CLOSE_RESUME } from '@/ts/webrtc';\nconst CONNECT_TIMEOUT_MS = 10000;\n/**\n * JSON-over-WebSocket signaling for WebRTC voice sessions.\n * Stateless thin wrapper — all connection state lives in WebRTCProvider.\n */\nexport class SignalingClient {\n constructor(url, sessionToken, onMessage, onClose, onError) {\n this.url = url;\n this.sessionToken = sessionToken;\n this.onMessage = onMessage;\n this.onClose = onClose;\n this.onError = onError;\n this.log = debug('moveo:webrtc:media');\n this.ws = null;\n }\n connect() {\n return new Promise((resolve, reject) => {\n this.log('connecting to %s', this.url);\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.sessionToken)}`;\n this.ws = new WebSocket(wsUrl);\n const timeout = setTimeout(() => {\n // close() (not ws.close()) so the abandoned socket's late close event\n // can't re-enter the provider's onClose after we already rejected.\n this.close();\n reject(new Error('Signaling connection timed out'));\n }, CONNECT_TIMEOUT_MS);\n this.ws.onopen = () => {\n // Wait for welcome message before resolving\n };\n this.ws.onmessage = (event) => {\n let msg;\n try {\n msg = JSON.parse(event.data);\n }\n catch (_a) {\n // Ignore malformed JSON\n return;\n }\n this.log('recv %s', msg.type);\n if (msg.type === 'welcome') {\n clearTimeout(timeout);\n resolve();\n }\n this.onMessage(msg);\n };\n this.ws.onerror = (event) => {\n clearTimeout(timeout);\n this.log('websocket error');\n this.onError(event);\n reject(new Error('Signaling WebSocket error'));\n };\n this.ws.onclose = (event) => {\n clearTimeout(timeout);\n this.log('closed code=%d reason=%s', event.code, event.reason);\n reject(new Error('Signaling WebSocket closed before connection established'));\n this.onClose(event.code, event.reason);\n };\n });\n }\n /**\n * Open a fresh WS to resume a session (pod drain or signaling loss). Sends\n * `reconnect` as the first frame on open and resolves on `reconnect_ack`\n * (not `welcome` — a resumed session does not re-issue welcome).\n */\n connectForReconnect(reconnectFrame) {\n return new Promise((resolve, reject) => {\n this.log('reconnecting to %s', this.url);\n const wsUrl = `${this.url}?token=${encodeURIComponent(this.sessionToken)}`;\n this.ws = new WebSocket(wsUrl);\n let settled = false;\n const timeout = setTimeout(() => {\n if (settled)\n return;\n settled = true;\n // The handshake may have committed server-side just as we timed out, so\n // close abnormally (4001) to keep the session resumable for the retry.\n this.close(WS_CLOSE_RESUME, 'Resume timed out');\n reject(new Error('Signaling reconnect timed out'));\n }, CONNECT_TIMEOUT_MS);\n this.ws.onopen = () => {\n var _a;\n // reconnect MUST be the first frame, before any offer.\n (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify(reconnectFrame));\n };\n this.ws.onmessage = (event) => {\n let msg;\n try {\n msg = JSON.parse(event.data);\n }\n catch (_a) {\n // Ignore malformed JSON\n return;\n }\n this.log('recv %s', msg.type);\n // Deliver to the provider first so it records the credential rotation\n // (reconnect_ack) or the reject code (error) before we settle.\n this.onMessage(msg);\n if (settled)\n return;\n if (msg.type === 'reconnect_ack') {\n settled = true;\n clearTimeout(timeout);\n resolve();\n }\n else if (msg.type === 'error') {\n // Server rejected the resume. Settle now rather than waiting on the\n // WS close that follows — avoids a per-attempt CONNECT_TIMEOUT stall\n // and makes the reject deterministic w.r.t. the captured error code.\n settled = true;\n clearTimeout(timeout);\n reject(new Error(`reconnect rejected: ${msg.error.code}`));\n }\n };\n this.ws.onerror = (event) => {\n this.log('websocket error');\n this.onError(event);\n if (settled)\n return;\n settled = true;\n clearTimeout(timeout);\n reject(new Error('Signaling WebSocket error'));\n };\n this.ws.onclose = (event) => {\n this.log('closed code=%d reason=%s', event.code, event.reason);\n if (!settled) {\n settled = true;\n clearTimeout(timeout);\n reject(new Error('Signaling WebSocket closed before reconnect established'));\n }\n this.onClose(event.code, event.reason);\n };\n });\n }\n send(msg) {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n this.log('send %s', msg.type);\n this.ws.send(JSON.stringify(msg));\n }\n /**\n * Close and detach the socket, nulling handlers first so a late close event\n * can't re-enter the provider. Default 1000 reads as an intentional end; the\n * resume flow passes 4001 to keep the session resumable.\n */\n close(code = 1000, reason = 'Client closed') {\n if (this.ws) {\n this.ws.onclose = null;\n this.ws.onerror = null;\n this.ws.onmessage = null;\n this.ws.close(code, reason);\n this.ws = null;\n }\n }\n get isConnected() {\n var _a;\n return ((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === WebSocket.OPEN;\n }\n}\n","import 'webrtc-adapter';\nimport debug from 'debug';\nimport { WS_CLOSE_RESUME } from '@/ts/webrtc';\nimport { AudioLevelMonitor } from '../shared/audio-level-monitor';\nimport { MEDIA_ERRORS } from '../shared/media-errors';\nimport { CallQualityMonitor } from './call-quality-monitor';\nimport { ConfigUnavailableError, ServerBusyError } from './config-errors';\nimport { MediaStallDetector } from './media-stall-detector';\nimport { SignalingClient } from './signaling-client';\nconst REMOTE_AUDIO_THRESHOLD = 0.02;\n// ICE-restart recovery bounds (mid-call media drop).\nconst MAX_ICE_RESTART_ATTEMPTS = 2;\nconst ICE_RESTART_TIMEOUT_MS = 10000; // per-attempt window to reach 'connected'\n// No answer this fast ⇒ the WS is dead (a network switch leaves it\n// readyState===OPEN with no TCP reset, so isConnected can't see it): a same-PC\n// restart can't recover, so bail to a fresh-WS resume, not the ~20s window.\nconst ICE_RESTART_ANSWER_TIMEOUT_MS = 3000;\n// Session-resume bounds (drain, abnormal signaling close, exhausted ICE restarts).\nconst MAX_CLIENT_RETRIES = 3;\nconst RECONNECT_BACKOFF_MS = 500; // scaled by attempt number\n// Client-detected resumes carry no server delay, so jitter to avoid a correlated\n// mass disconnect stampeding /config + /rtc at once. Small enough to feel instant.\nconst SIGNALING_LOSS_JITTER_MS = 500;\n// Settle window for the post-ack media resume; during a resume the WS-close\n// teardown is disabled, so this is the only guard against a wedged resume.\nconst RECONNECT_ICE_TIMEOUT_MS = 15000;\n// Reconnect error codes that cannot be resumed — give up without retrying.\n// Any other failure (transient WS/ICE, server_busy) is retryable.\nconst TERMINAL_RECONNECT_CODES = new Set([\n 'session_expired',\n 'session_inactive',\n 'invalid_credential',\n 'max_recovery_exceeded',\n 'unexpected_first_frame',\n]);\n/** User-facing copy for terminal reconnect failures. Infra-agnostic. */\nconst RECONNECT_ERROR_MESSAGES = {\n session_expired: 'Your session has expired.',\n session_inactive: 'The call has ended.',\n invalid_credential: 'Could not resume the call.',\n max_recovery_exceeded: 'Could not resume the call after several attempts.',\n unexpected_first_frame: 'Could not resume the call.',\n};\n/** User-friendly error messages for WebRTC API failures */\nconst WEBRTC_ERRORS = Object.assign(Object.assign({}, MEDIA_ERRORS), { setRemoteDescription: 'Connection setup failed', setLocalDescription: 'Connection setup failed', addIceCandidate: 'Network configuration failed', createOffer: 'Browser audio setup failed' });\n/** Native WebRTC voice provider for Moveo web-voice integrations. */\nexport class WebRTCProvider {\n constructor(wsUrl, \n // Re-fetches /config (fresh TURN + session token) for a resume; throws\n // ServerBusyError on a server-busy 503. Injected so signaling stays here.\n fetchConfig) {\n this.wsUrl = wsUrl;\n this.fetchConfig = fetchConfig;\n this.log = debug('moveo:webrtc:media');\n this.pc = null;\n this.signaling = null;\n this.localStream = null;\n this.remoteStream = null;\n this.remoteAudio = null;\n this.remoteAudioMonitor = null;\n this.audioMonitor = null;\n this.qualityMonitor = null;\n this.stallDetector = null;\n this.pendingCandidates = [];\n this.state = 'idle';\n this.eventListeners = new Map();\n // --- ICE-restart recovery state ---\n this.iceRestarting = false;\n this.iceRestartAttempts = 0;\n this.iceRestartTimeout = null;\n // Fires if an ICE-restart offer goes unanswered: signals a dead WS (vs a\n // media-only blip) so the restart escalates to a resume instead of stalling.\n this.iceRestartAnswerTimeout = null;\n this.connectResolve = null;\n this.connectReject = null;\n // True while a session resume is in flight (drain or abnormal signaling loss);\n // gates the ICE disconnect/failed handlers and the WS-close backstop so the\n // resume flow owns teardown.\n this.resuming = false;\n this.reconnectAttempts = 0;\n // Captured from an error frame mid-resume to classify terminal vs retryable.\n this.reconnectErrorCode = null;\n // Buffered brain_response chunks per request_id (index -> text), assembled in\n // index order on emit. Retained past the terminal frame so a later interrupt\n // can still split them; cleared on cleanup().\n this.brainResponseChunks = new Map();\n // request_id -> smallest heard_index seen on an interrupt. Presence means the\n // turn was cut: later brain_response frames (incl. the terminal) are ignored\n // so the trimmed line is never re-rendered ungrayed, and repeated interrupts\n // keep the most conservative (smallest) boundary.\n this.interruptedRequests = new Map();\n }\n /** Supply the /config result (TURN + session token) for the next connect(). */\n setConfig(config) {\n this.config = config;\n }\n async connect() {\n if (!this.config) {\n throw new Error('Voice config not set; call setConfig() before connect().');\n }\n this.setState('connecting');\n // 1. Get microphone access\n try {\n this.localStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n }\n catch (error) {\n const name = error instanceof Error ? error.name : '';\n const message = WEBRTC_ERRORS[name] || 'Microphone access failed';\n this.emitError(message);\n throw new Error(message);\n }\n // 2. Open signaling WebSocket\n this.signaling = new SignalingClient(this.wsUrl, this.config.sessionToken, this.handleSignalingMessage.bind(this), this.handleSignalingClose.bind(this), this.handleSignalingError.bind(this));\n try {\n await this.signaling.connect();\n }\n catch (error) {\n this.cleanupLocalStream();\n const message = error instanceof Error ? error.message : 'Signaling connection failed';\n this.emitError(message);\n throw new Error(message);\n }\n // 3. Create PeerConnection\n this.pc = new RTCPeerConnection({\n iceServers: this.config.iceServers,\n iceTransportPolicy: this.config.iceTransportPolicy || 'relay',\n });\n // 4. Add local audio track\n const audioTrack = this.localStream.getAudioTracks()[0];\n this.pc.addTrack(audioTrack, this.localStream);\n // 5. Set up PC handlers\n this.pc.ontrack = this.handleRemoteTrack.bind(this);\n this.pc.onicecandidate = (event) => {\n var _a;\n if (event.candidate) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({\n type: 'ice',\n candidate: event.candidate.toJSON(),\n });\n }\n };\n this.pc.onconnectionstatechange =\n this.handleConnectionStateChange.bind(this);\n // 6. Create and send offer\n try {\n const offer = await this.pc.createOffer();\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await this.pc.setLocalDescription(offer);\n this.signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (error) {\n this.cleanup();\n const name = error instanceof Error ? error.name : 'createOffer';\n const message = WEBRTC_ERRORS[name] || 'Failed to create call offer';\n this.emitError(message);\n throw new Error(message);\n }\n // 7. Start local audio monitoring\n this.audioMonitor = new AudioLevelMonitor();\n this.audioMonitor.start(this.localStream, (level) => {\n this.emit('audio-level', level, Number(level));\n });\n // 8. Wait for PeerConnection to reach 'connected' state\n return new Promise((resolve, reject) => {\n this.connectResolve = resolve;\n this.connectReject = reject;\n });\n }\n disconnect() {\n this.disconnectInternal(false);\n }\n disconnectInternal(suppressOutboundHangup) {\n // Fault-tolerant disconnect: each step wrapped in try/catch\n var _a, _b, _c, _d, _e, _f, _g;\n if (!suppressOutboundHangup) {\n try {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({ type: 'hangup' });\n }\n catch (_h) {\n // continue cleanup\n }\n }\n try {\n (_b = this.qualityMonitor) === null || _b === void 0 ? void 0 : _b.stop();\n this.qualityMonitor = null;\n }\n catch (_j) {\n // continue cleanup\n }\n try {\n (_c = this.stallDetector) === null || _c === void 0 ? void 0 : _c.stop();\n this.stallDetector = null;\n }\n catch (_k) {\n // continue cleanup\n }\n try {\n (_d = this.audioMonitor) === null || _d === void 0 ? void 0 : _d.stop();\n this.audioMonitor = null;\n }\n catch (_l) {\n // continue cleanup\n }\n try {\n (_e = this.remoteAudioMonitor) === null || _e === void 0 ? void 0 : _e.stop();\n this.remoteAudioMonitor = null;\n }\n catch (_m) {\n // continue cleanup\n }\n try {\n (_f = this.pc) === null || _f === void 0 ? void 0 : _f.close();\n this.pc = null;\n }\n catch (_o) {\n // continue cleanup\n }\n this.cleanupLocalStream();\n try {\n (_g = this.signaling) === null || _g === void 0 ? void 0 : _g.close();\n this.signaling = null;\n }\n catch (_p) {\n // continue cleanup\n }\n try {\n if (this.remoteAudio) {\n this.remoteAudio.srcObject = null;\n this.remoteAudio.remove();\n this.remoteAudio = null;\n }\n this.remoteStream = null;\n }\n catch (_q) {\n // continue cleanup\n }\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n if (this.connectReject) {\n this.connectReject(new Error('Call disconnected before connection completed'));\n }\n this.connectResolve = null;\n this.connectReject = null;\n // Clear session-resume state so a torn-down provider can't leave\n // resuming stuck true (which would make handleSignalingClose swallow a\n // future legitimate close), carry a stale retry count, or resume a new call\n // with the prior session's credentials before its own session_ready lands.\n this.resuming = false;\n this.reconnectAttempts = 0;\n this.reconnectErrorCode = null;\n this.originalSessionID = undefined;\n this.originalConnectionID = undefined;\n this.pendingCandidates = [];\n this.brainResponseChunks.clear();\n this.interruptedRequests.clear();\n this.setState('idle');\n }\n toggleMute() {\n var _a, _b;\n const track = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0];\n if (track) {\n track.enabled = !track.enabled;\n const muted = !track.enabled;\n (_b = this.signaling) === null || _b === void 0 ? void 0 : _b.send({ type: 'mute', muted });\n return muted;\n }\n return false;\n }\n isMuted() {\n var _a, _b, _c;\n return !((_c = (_b = (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getAudioTracks()[0]) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : true);\n }\n getLocalStream() {\n return this.localStream;\n }\n getRemoteStream() {\n return this.remoteStream;\n }\n sendDigits(digits) {\n var _a;\n // Server expects one digit per signaling message (matches Twilio path,\n // which lets the SDK split a multi-char string internally).\n for (const ch of digits) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({ type: 'dtmf', digit: ch });\n }\n }\n on(event, handler) {\n var _a;\n const listeners = (_a = this.eventListeners.get(event)) !== null && _a !== void 0 ? _a : new Set();\n listeners.add(handler);\n this.eventListeners.set(event, listeners);\n return () => this.off(event, handler);\n }\n off(event, handler) {\n const listeners = this.eventListeners.get(event);\n if (!listeners)\n return;\n listeners.delete(handler);\n if (listeners.size === 0) {\n this.eventListeners.delete(event);\n }\n }\n // --- Private methods ---\n handleRemoteTrack(event) {\n const [remoteStream] = event.streams;\n this.remoteStream = remoteStream !== null && remoteStream !== void 0 ? remoteStream : null;\n this.remoteAudio = document.createElement('audio');\n this.remoteAudio.srcObject = remoteStream;\n this.remoteAudio.autoplay = true;\n document.body.appendChild(this.remoteAudio);\n // Safari (incl. iOS, and stricter in Private mode) does not reliably\n // autoplay an <audio> whose source is a MediaStream — the `autoplay`\n // attribute alone leaves it silent. Kick off an explicit play(); the call\n // already ran getUserMedia behind a user gesture, so the audio session is\n // unlocked. Swallow a rejection so a blocked attempt stays silent, not fatal.\n void this.remoteAudio.play().catch(() => {\n // Playback blocked — the element stays live; nothing else to do here.\n });\n // Remote audio monitoring via AnalyserNode for thinking indicator\n this.remoteAudioMonitor = new AudioLevelMonitor();\n this.remoteAudioMonitor.start(remoteStream, (level) => {\n if (Number(level) > REMOTE_AUDIO_THRESHOLD) {\n this.emit('remote-audio', level, Number(level));\n }\n });\n }\n handleConnectionStateChange() {\n var _a, _b, _c, _d;\n if (!this.pc)\n return;\n this.log('connectionState %s', this.pc.connectionState);\n switch (this.pc.connectionState) {\n case 'connecting':\n this.setState('connecting');\n break;\n case 'connected': {\n const wasReconnecting = this.state === 'reconnecting';\n this.setState('connected');\n // Reaching 'connected' means any in-flight ICE restart recovered.\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n // A disconnected → connected recovery fires no stall onRecover, so the\n // \"Reconnecting…\" indicator is cleared here.\n if (wasReconnecting) {\n this.emit('warning-cleared', undefined, 'reconnect');\n }\n // 'connected' can re-fire after a recovery, so stop existing monitors\n // before recreating to avoid leaking their poll intervals.\n (_a = this.qualityMonitor) === null || _a === void 0 ? void 0 : _a.stop();\n this.qualityMonitor = new CallQualityMonitor(this.pc, (name, message) => this.emit('warning', message, name), (name) => this.emit('warning-cleared', undefined, name));\n this.qualityMonitor.start();\n (_b = this.stallDetector) === null || _b === void 0 ? void 0 : _b.stop();\n this.stallDetector = new MediaStallDetector(this.pc, () => {\n this.log('media stall -> ICE restart');\n void this.triggerIceRestart();\n }, () => {\n this.log('media recovered');\n // Media is flowing again: the restart (or a passive recovery) worked.\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n // A same-PC ICE restart fires no connectionState transition, so\n // recovery is confirmed here: leave 'reconnecting' and clear the\n // indicator.\n if (this.state === 'reconnecting') {\n this.setState('connected');\n }\n this.emit('warning-cleared', undefined, 'reconnect');\n });\n this.stallDetector.start();\n // Resolve connect() promise\n (_c = this.connectResolve) === null || _c === void 0 ? void 0 : _c.call(this);\n this.connectResolve = null;\n this.connectReject = null;\n break;\n }\n case 'disconnected':\n // A session resume owns the lifecycle; don't start a recovery while we\n // are intentionally tearing the PC down.\n if (this.resuming) {\n break;\n }\n void this.triggerIceRestart();\n break;\n case 'failed':\n // During a resume, just fail the pending handshake; reconnect() handles\n // retry/teardown. Do not emitError or tear the call down here.\n if (this.resuming) {\n (_d = this.connectReject) === null || _d === void 0 ? void 0 : _d.call(this, new Error('ICE connection failed'));\n this.connectResolve = null;\n this.connectReject = null;\n break;\n }\n // Initial connect that never reached 'connected': no session to recover.\n if (this.connectReject) {\n this.connectReject(new Error('ICE connection failed'));\n this.connectResolve = null;\n this.connectReject = null;\n this.emitError('Connection failed');\n this.cleanup();\n break;\n }\n // Established call: recover via ICE restart. A 'failed' while a restart is\n // already in flight means that attempt died, so react now (retry or give\n // up) instead of waiting out the recovery timeout.\n if (this.iceRestarting) {\n this.onRestartAttemptExpired();\n }\n else {\n void this.triggerIceRestart();\n }\n break;\n case 'closed':\n this.cleanup();\n break;\n }\n }\n /**\n * Recover a mid-call media drop by restarting ICE on the live PeerConnection\n * (same session, same WS). Bounded attempts; each waits ICE_RESTART_TIMEOUT_MS\n * to reach 'connected' before retrying or giving up. Never runs during a\n * session resume, which owns its own teardown.\n */\n async triggerIceRestart() {\n if (this.resuming || !this.pc || !this.signaling) {\n return;\n }\n // One restart at a time; bound total attempts.\n if (this.iceRestarting ||\n this.iceRestartAttempts >= MAX_ICE_RESTART_ATTEMPTS) {\n return;\n }\n // A re-offer can't reach the server over a dead WS (a combined media +\n // signaling drop needs WS reconnect, not ICE restart); don't burn attempts —\n // resume the session on a fresh WS when the call is resumable.\n if (!this.signaling.isConnected) {\n if (this.isResumeEligible()) {\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n return;\n }\n this.emitError('Connection lost');\n this.cleanup();\n return;\n }\n const pc = this.pc;\n const signaling = this.signaling;\n this.iceRestarting = true;\n this.iceRestartAttempts++;\n this.log('ICE restart attempt %d/%d', this.iceRestartAttempts, MAX_ICE_RESTART_ATTEMPTS);\n this.setState('reconnecting');\n // 'reconnect' warning drives the widgets' \"Reconnecting…\" indicator.\n this.emit('warning', 'Reconnecting…', 'reconnect');\n try {\n const offer = await pc.createOffer({ iceRestart: true });\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await pc.setLocalDescription(offer);\n signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (_a) {\n // A resume or teardown during the awaits closes this pc, so the offer\n // rejects — expected, not a media failure. Stay quiet if the resume now\n // owns the lifecycle or pc was replaced; only a still-current restart is\n // a real 'Connection lost'.\n if (this.resuming || !this.iceRestarting || this.pc !== pc) {\n return;\n }\n this.iceRestarting = false;\n this.emitError('Connection lost');\n this.cleanup();\n return;\n }\n // Teardown, a drain resume, or a new call on a reused provider may have run\n // during the awaits (clearing iceRestarting or replacing pc); don't arm it.\n if (!this.iceRestarting || this.pc !== pc) {\n return;\n }\n // Recovery is confirmed by media resuming (the detector's onRecover); this\n // timer only fires if it never does, then retries or gives up.\n this.iceRestartTimeout = setTimeout(() => {\n this.onRestartAttemptExpired();\n }, ICE_RESTART_TIMEOUT_MS);\n // Shorter deadline for the answer: no answer means the WS is dead, so cut\n // straight to a resume rather than waiting out the media timer (see const).\n this.iceRestartAnswerTimeout = setTimeout(() => {\n this.onIceRestartUnanswered();\n }, ICE_RESTART_ANSWER_TIMEOUT_MS);\n }\n /**\n * No answer to the ICE-restart offer within the deadline ⇒ the WS is dead (not\n * a media-only blip). Escalate to a fresh-WS resume if resumable; otherwise let\n * the per-attempt timer expire and onRestartAttemptExpired handle it.\n */\n onIceRestartUnanswered() {\n this.iceRestartAnswerTimeout = null;\n if (!this.iceRestarting) {\n return;\n }\n if (this.isResumeEligible()) {\n this.log('ICE restart offer unanswered -> resume');\n // beginResume() clears the in-flight restart, incl. the media timer.\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n }\n }\n /** A restart attempt yielded no recovery (timeout, or a hard ICE 'failed'). */\n onRestartAttemptExpired() {\n this.log('ICE restart attempt expired (attempts=%d)', this.iceRestartAttempts);\n if (this.iceRestartTimeout) {\n clearTimeout(this.iceRestartTimeout);\n this.iceRestartTimeout = null;\n }\n // Cancel this attempt's answer-deadline; the next attempt arms its own, and\n // a stale timer would fire against it (e.g. a fast 'failed' beats the 3s).\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n this.iceRestarting = false;\n if (this.iceRestartAttempts < MAX_ICE_RESTART_ATTEMPTS) {\n void this.triggerIceRestart();\n }\n else if (this.isResumeEligible()) {\n // Same-PC restarts exhausted; a resume rebuilds both planes (fresh WS +\n // fresh TURN), rescuing drops a restart can't (e.g. a dead relay\n // allocation). The old WS closes 4001 so the server keeps it resumable.\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n }\n else {\n this.emitError('Connection lost');\n this.cleanup();\n }\n }\n /** Cancel an in-flight ICE-restart recovery (on recovery or teardown). */\n clearIceRestart() {\n if (this.iceRestarting) {\n this.log('ICE restart cleared');\n }\n if (this.iceRestartTimeout) {\n clearTimeout(this.iceRestartTimeout);\n this.iceRestartTimeout = null;\n }\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n this.iceRestarting = false;\n }\n handleSignalingMessage(msg) {\n var _a;\n switch (msg.type) {\n case 'answer':\n void this.handleAnswer(msg.sdp);\n break;\n case 'ice':\n void this.addIceCandidate(msg.candidate);\n break;\n // Final user transcript for the turn.\n case 'transcript':\n this.emit('transcript', msg.text, {\n text: msg.text,\n request_id: msg.request_id,\n timestamp: msg.timestamp,\n });\n break;\n // AI response, streamed as chunks and assembled per request_id.\n case 'brain_response':\n this.handleBrainResponse(msg);\n break;\n // Barge-in truncated the response: split the buffered chunks at\n // heard_index so listeners can show what was heard vs the unheard tail.\n case 'interrupt':\n this.handleInterrupt(msg);\n break;\n case 'hangup':\n this.disconnectInternal(true);\n break;\n // session_ready (not welcome) is the resume-eligible signal: cache the\n // credentials for a future reconnect.\n case 'session_ready':\n this.originalSessionID = msg.session_id;\n this.originalConnectionID = msg.connection_id;\n break;\n // Rotated credential for the next reconnect.\n case 'reconnect_ack':\n this.originalConnectionID = msg.connection_id;\n break;\n case 'drain':\n void this.handleDrain(msg);\n break;\n case 'error':\n if (this.resuming) {\n // Record for reconnect()'s terminal-vs-retryable check. If a resume\n // is awaiting ICE (connectReject set), fail it now so the retry loop\n // reacts immediately instead of waiting out RECONNECT_ICE_TIMEOUT_MS.\n this.reconnectErrorCode = msg.error.code;\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error(`reconnect rejected: ${msg.error.code}`));\n }\n else {\n this.emitError(msg.error.message);\n }\n break;\n }\n }\n handleSignalingClose(_code, _reason) {\n var _a;\n // The resume flow owns teardown while in flight. If a resume is awaiting\n // ICE (connectReject set), fail it now so the retry loop reacts to the\n // dropped WS immediately; otherwise (pre-ack / jitter window) just ignore.\n if (this.resuming) {\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error('Signaling closed during resume'));\n return;\n }\n // Abnormal close of an established call (network switch, 1006, idle-proxy\n // drop): resume on a fresh WS instead of tearing down. Intentional closes\n // route elsewhere first (hangup nulls the handlers, drain sets resuming), so\n // being eligible here means the close was abnormal.\n if (this.isResumeEligible()) {\n void this.beginResume(SIGNALING_LOSS_JITTER_MS);\n return;\n }\n if (this.state !== 'idle') {\n this.disconnect();\n }\n }\n handleSignalingError(_error) {\n // Browsers fire onerror just before the abnormal onclose. Emitting here\n // would flip state to 'error', killing resume eligibility and letting the\n // host UI dispose the engine mid-resume. Stay quiet; the close handler owns\n // the outcome.\n if (this.resuming || this.isResumeEligible()) {\n return;\n }\n if (this.state === 'connected') {\n this.emitError('Signaling error');\n }\n }\n /**\n * Resume needs a fully-established call: cached credentials, no resume already\n * in flight, and state connected/reconnecting ('reconnecting' = an in-flight\n * ICE restart, where the session is still established server-side).\n */\n isResumeEligible() {\n return (!this.resuming &&\n (this.state === 'connected' || this.state === 'reconnecting') &&\n this.originalSessionID !== undefined &&\n this.originalConnectionID !== undefined);\n }\n /**\n * Enter the resume flow (drain / signaling loss / exhausted ICE restarts): own\n * the lifecycle, cancel any in-flight ICE restart, optionally wait a jittered\n * delay, then run the reconnect loop. Callers must have checked isResumeEligible().\n */\n async beginResume(jitterMs = 0) {\n this.resuming = true;\n this.setState('reconnecting');\n this.clearIceRestart();\n // 'reconnect' warning drives the widgets' \"Reconnecting…\" indicator.\n this.emit('warning', 'Reconnecting…', 'reconnect');\n if (jitterMs > 0) {\n // Jitter 0..delay to avoid a thundering herd against the sibling pods.\n await this.sleep(Math.random() * jitterMs);\n // A teardown (user hangup / destroy) during the jitter clears resuming;\n // don't resurrect an ended call.\n if (!this.resuming) {\n return;\n }\n }\n await this.reconnect();\n }\n /**\n * Server is draining this pod: resume the same session on a sibling after a\n * jittered delay. Distinct from the ICE-level 'disconnected' path (a blip) —\n * this is a server-initiated handoff.\n */\n async handleDrain(msg) {\n // Already resuming: a second drain (sibling pod drains before ICE connects)\n // is handled by the in-flight retry loop. Re-entering would hard-kill it.\n if (this.resuming) {\n return;\n }\n // Only a fully-established session is resumable: a drain before\n // session_ready (no cached credentials) or while not connected can't resume.\n if (!this.isResumeEligible()) {\n this.emitError('Could not resume the call.');\n this.disconnectInternal(true);\n return;\n }\n await this.beginResume(Math.max(0, msg.reconnect_delay_ms || 0));\n }\n /**\n * Tear down the pod-local PC/WS and resume on a sibling pod, reusing the\n * existing mic MediaStream (no second permission prompt). Retries transient\n * failures up to MAX_CLIENT_RETRIES; gives up on a terminal server rejection.\n */\n async reconnect() {\n var _a, _b, _c;\n // Detach senders before pc.close() (ghost-sender defense), then confirm the\n // mic track survived the drain (device unplug / OS reset / BT switch kill it).\n try {\n (_a = this.pc) === null || _a === void 0 ? void 0 : _a.getSenders().forEach((sender) => {\n var _a;\n if (sender.track) {\n (_a = this.pc) === null || _a === void 0 ? void 0 : _a.removeTrack(sender);\n }\n });\n }\n catch (_d) {\n // continue — closing the pc below releases the senders anyway\n }\n const track = (_b = this.localStream) === null || _b === void 0 ? void 0 : _b.getAudioTracks()[0];\n if (!track || track.readyState !== 'live') {\n this.emitError('Microphone is no longer available.');\n this.disconnectInternal(true);\n return;\n }\n // Drop pod-local resources but KEEP the MediaStream alive for reuse.\n this.teardownForReconnect();\n while (this.reconnectAttempts < MAX_CLIENT_RETRIES) {\n this.reconnectErrorCode = null;\n try {\n // Fresh /config → fresh TURN creds for the sibling pod.\n this.config = await this.fetchConfig();\n // A user hangup / destroy during the (up to 10s) fetch clears resuming;\n // don't open a new WS for an ended call.\n if (!this.resuming) {\n return;\n }\n this.signaling = new SignalingClient(this.wsUrl, this.config.sessionToken, this.handleSignalingMessage.bind(this), this.handleSignalingClose.bind(this), this.handleSignalingError.bind(this));\n // Sends `reconnect` as the first frame; resolves on reconnect_ack.\n await this.signaling.connectForReconnect({\n type: 'reconnect',\n session_id: this.originalSessionID,\n connection_id: this.originalConnectionID,\n });\n // Resumed — rebuild the media plane and wait for ICE to reconnect.\n await this.buildReconnectPeerConnection(track);\n this.reconnectAttempts = 0;\n this.resuming = false;\n // Clear any \"reconnecting…\" banner raised by a retried attempt.\n this.emit('warning-cleared', undefined, 'reconnect');\n return;\n }\n catch (error) {\n // A terminal server rejection (bad credential, expired/ended session,\n // recovery cap) is not retryable — surface and stop.\n const code = this.reconnectErrorCode;\n if (code && TERMINAL_RECONNECT_CODES.has(code)) {\n this.emitError((_c = RECONNECT_ERROR_MESSAGES[code]) !== null && _c !== void 0 ? _c : 'Could not resume the call.');\n this.disconnectInternal(true);\n return;\n }\n // A terminal /config failure (e.g. TURN unavailable) won't fix by\n // retrying — surface its message and stop.\n if (error instanceof ConfigUnavailableError) {\n this.emitError(error.message);\n this.disconnectInternal(true);\n return;\n }\n // Transient (WS/ICE, server_busy 503, rate-limited): back off and retry.\n // Only server_busy surfaces its message; a rate-limit reconnect stays\n // generic since \"too many attempts\" misreads an automatic retry.\n const message = error instanceof ServerBusyError ? error.message : 'Reconnecting…';\n this.emit('warning', message, 'reconnect');\n this.teardownForReconnect();\n this.reconnectAttempts++;\n if (this.reconnectAttempts >= MAX_CLIENT_RETRIES) {\n break;\n }\n await this.sleep(RECONNECT_BACKOFF_MS * this.reconnectAttempts);\n // Torn down (hangup / destroy) during the backoff — stop retrying.\n if (!this.resuming) {\n return;\n }\n }\n }\n this.emitError('Could not resume the call.');\n this.disconnectInternal(true);\n }\n /**\n * Build a fresh PeerConnection for a resume using the existing audio track.\n * Mirrors the fresh-connect PC setup but rejects on offer failure so the\n * reconnect retry loop can decide what to do (the fresh path tears down).\n */\n buildReconnectPeerConnection(audioTrack) {\n this.pc = new RTCPeerConnection({\n iceServers: this.config.iceServers,\n iceTransportPolicy: this.config.iceTransportPolicy || 'relay',\n });\n this.pc.addTrack(audioTrack, this.localStream);\n this.pc.ontrack = this.handleRemoteTrack.bind(this);\n this.pc.onicecandidate = (event) => {\n var _a;\n if (event.candidate) {\n (_a = this.signaling) === null || _a === void 0 ? void 0 : _a.send({\n type: 'ice',\n candidate: event.candidate.toJSON(),\n });\n }\n };\n this.pc.onconnectionstatechange =\n this.handleConnectionStateChange.bind(this);\n this.audioMonitor = new AudioLevelMonitor();\n this.audioMonitor.start(this.localStream, (level) => {\n this.emit('audio-level', level, Number(level));\n });\n return new Promise((resolve, reject) => {\n // Bound the resume: if ICE never reaches 'connected' (stalled, or the\n // sibling-pod WS drops after the ack — both swallowed by the drain-path\n // handlers), reject so the retry loop can take over instead of hanging.\n const timer = setTimeout(() => {\n this.connectResolve = null;\n this.connectReject = null;\n reject(new Error('Reconnect media resume timed out'));\n }, RECONNECT_ICE_TIMEOUT_MS);\n // Resolved/rejected by handleConnectionStateChange ('connected'/'failed').\n this.connectResolve = () => {\n clearTimeout(timer);\n resolve();\n };\n this.connectReject = (error) => {\n clearTimeout(timer);\n reject(error);\n };\n void (async () => {\n var _a;\n try {\n const offer = await this.pc.createOffer();\n if (!offer.sdp) {\n throw new Error('createOffer returned no SDP');\n }\n await this.pc.setLocalDescription(offer);\n this.signaling.send({ type: 'offer', sdp: offer.sdp });\n }\n catch (error) {\n const name = error instanceof Error ? error.name : 'createOffer';\n (_a = this.connectReject) === null || _a === void 0 ? void 0 : _a.call(this, new Error(WEBRTC_ERRORS[name] || 'Failed to create call offer'));\n }\n })();\n });\n }\n /**\n * Stop pod-local resources (PC, signaling, monitors, stall detector, remote\n * audio element) but deliberately KEEP localStream so the mic is reused.\n */\n teardownForReconnect() {\n var _a, _b, _c, _d, _e, _f;\n try {\n (_a = this.qualityMonitor) === null || _a === void 0 ? void 0 : _a.stop();\n this.qualityMonitor = null;\n }\n catch (_g) {\n // continue cleanup\n }\n try {\n (_b = this.stallDetector) === null || _b === void 0 ? void 0 : _b.stop();\n this.stallDetector = null;\n }\n catch (_h) {\n // continue cleanup\n }\n try {\n (_c = this.audioMonitor) === null || _c === void 0 ? void 0 : _c.stop();\n this.audioMonitor = null;\n }\n catch (_j) {\n // continue cleanup\n }\n try {\n (_d = this.remoteAudioMonitor) === null || _d === void 0 ? void 0 : _d.stop();\n this.remoteAudioMonitor = null;\n }\n catch (_k) {\n // continue cleanup\n }\n try {\n (_e = this.pc) === null || _e === void 0 ? void 0 : _e.close();\n this.pc = null;\n }\n catch (_l) {\n // continue cleanup\n }\n try {\n // 4001 marks this close as resume-initiated so the server keeps the\n // session resumable; a clean 1000 would tear it down before the reconnect\n // lands (matters when the old WS is still alive, e.g. exhausted-restart).\n (_f = this.signaling) === null || _f === void 0 ? void 0 : _f.close(WS_CLOSE_RESUME, 'Resuming');\n this.signaling = null;\n }\n catch (_m) {\n // continue cleanup\n }\n try {\n if (this.remoteAudio) {\n this.remoteAudio.srcObject = null;\n this.remoteAudio.remove();\n this.remoteAudio = null;\n }\n this.remoteStream = null;\n }\n catch (_o) {\n // continue cleanup\n }\n this.clearIceRestart();\n this.iceRestartAttempts = 0;\n this.connectResolve = null;\n this.connectReject = null;\n this.pendingCandidates = [];\n }\n sleep(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n async handleAnswer(sdp) {\n // Any answer proves the signaling round-trip works, so cancel the\n // ICE-restart \"unanswered → resume\" escalation (media-only blip path).\n if (this.iceRestartAnswerTimeout) {\n clearTimeout(this.iceRestartAnswerTimeout);\n this.iceRestartAnswerTimeout = null;\n }\n if (!this.pc)\n return;\n try {\n await this.pc.setRemoteDescription({ type: 'answer', sdp });\n await this.flushPendingCandidates();\n }\n catch (_a) {\n this.emitError(WEBRTC_ERRORS['setRemoteDescription']);\n }\n }\n async addIceCandidate(candidate) {\n var _a;\n if ((_a = this.pc) === null || _a === void 0 ? void 0 : _a.remoteDescription) {\n try {\n await this.pc.addIceCandidate(candidate);\n }\n catch (_b) {\n // Non-critical — log but don't fail\n }\n }\n else {\n this.pendingCandidates.push(candidate);\n }\n }\n async flushPendingCandidates() {\n for (const candidate of this.pendingCandidates) {\n if (!this.pc)\n break;\n try {\n await this.pc.addIceCandidate(candidate);\n }\n catch (_a) {\n // Non-critical\n }\n }\n this.pendingCandidates = [];\n }\n cleanup() {\n this.disconnect();\n }\n cleanupLocalStream() {\n var _a;\n try {\n (_a = this.localStream) === null || _a === void 0 ? void 0 : _a.getTracks().forEach((t) => t.stop());\n this.localStream = null;\n }\n catch (_b) {\n // continue cleanup\n }\n }\n setState(newState) {\n this.state = newState;\n this.emit('state-change', newState, newState);\n }\n /**\n * Buffers brain_response chunks by request_id, ordered by index, and emits\n * 'brain-response' with the assembled text so far. The terminal frame (empty\n * text, final true) emits the final text; the buffer is retained for a later\n * interrupt. Frames for an already-interrupted turn are ignored so a late\n * terminal never re-renders the trimmed line ungrayed.\n */\n handleBrainResponse(msg) {\n const requestId = msg.request_id;\n // Turn already cut by barge-in: drop late chunks and the terminal so the\n // interrupt's trimmed rendering stands (the server emits the two frame\n // families independently, with no ordering guarantee between them).\n if (this.interruptedRequests.has(requestId))\n return;\n // Terminal frame: emit the fully assembled response. Keep the buffer: an\n // interrupt can arrive seconds later (barge-in during playback, long after\n // the reply finished streaming) and needs the chunks to split heard/unheard.\n // Cleared on cleanup() instead of here.\n if (msg.final) {\n const text = this.assembleBrainResponse(requestId);\n this.emit('brain-response', text, {\n text,\n request_id: requestId,\n final: true,\n });\n return;\n }\n let chunks = this.brainResponseChunks.get(requestId);\n if (!chunks) {\n chunks = new Map();\n this.brainResponseChunks.set(requestId, chunks);\n }\n chunks.set(msg.index, msg.text);\n const text = this.assembleBrainResponse(requestId);\n this.emit('brain-response', text, {\n text,\n request_id: requestId,\n final: false,\n });\n }\n /** Concatenate the buffered chunks for a request_id in index order. */\n assembleBrainResponse(requestId) {\n const chunks = this.brainResponseChunks.get(requestId);\n if (!chunks)\n return '';\n return [...chunks.entries()]\n .sort((a, b) => a[0] - b[0])\n .map(([, text]) => text)\n .join(' ');\n }\n /**\n * Splits the buffered chunks at heard_index and emits 'interrupt' with the\n * heard text and the unheard tail, so a listener can show the boundary\n * without discarding text. Marks the turn interrupted (so later brain_response\n * frames are dropped) and, on repeated interrupts, keeps the smallest\n * heard_index — the most conservative boundary. The buffer is left intact.\n */\n handleInterrupt(msg) {\n const prev = this.interruptedRequests.get(msg.request_id);\n const heardIndex = prev === undefined ? msg.heard_index : Math.min(prev, msg.heard_index);\n this.interruptedRequests.set(msg.request_id, heardIndex);\n const chunks = this.brainResponseChunks.get(msg.request_id);\n const ordered = chunks\n ? [...chunks.entries()].sort((a, b) => a[0] - b[0])\n : [];\n const join = (predicate) => ordered\n .filter(([index]) => predicate(index))\n .map(([, text]) => text)\n .join(' ');\n const heardText = join((index) => index <= heardIndex);\n const unheardText = join((index) => index > heardIndex);\n this.emit('interrupt', heardText, {\n request_id: msg.request_id,\n heard_index: heardIndex,\n timestamp: msg.timestamp,\n heardText,\n unheardText,\n });\n }\n emitError(message) {\n this.emit('error', message);\n this.setState('error');\n }\n emit(event, message, data) {\n const detail = { state: event, message, data };\n const invoke = (name) => {\n const listeners = this.eventListeners.get(name);\n if (!listeners)\n return;\n listeners.forEach((listener) => {\n try {\n listener(detail);\n }\n catch (_a) {\n // Ignore listener errors\n }\n });\n };\n invoke(event);\n invoke('any');\n }\n}\n","/**\n * Web-voice signaling message types.\n *\n * Canonical spec lives in the channels repo:\n * https://github.com/moveo-ai/channels/blob/main/docs/webvoice-signaling.md\n *\n * Protocol changes require lockstep updates to both repos plus the spec doc.\n */\n/**\n * App WS close code for a resume-initiated close (not a call end): channels\n * treats it as abnormal and keeps the session resumable, where a clean 1000\n * tears it down. Wire contract — keep in sync with the server's\n * resumableReadError classification.\n */\nexport const WS_CLOSE_RESUME = 4001;\n"],"names":["AudioLevelMonitor","constructor","this","analyserNode","audioSource","animationId","audioContext","start","stream","onLevel","AudioContext","createMediaStreamSource","createAnalyser","fftSize","smoothingTimeConstant","connect","dataArray","Float32Array","checkAudioLevel","stop","getFloatTimeDomainData","sum","amplitude","rms","Math","sqrt","length","normalizedLevel","min","toFixed","requestAnimationFrame","_a","cancelAnimationFrame","disconnect","close","catch","MEDIA_ERRORS","NotAllowedError","NotFoundError","NotReadableError","OverconstrainedError","QUALITY_WARNINGS","CallQualityMonitor","pc","onWarning","onWarningCleared","pollIntervalMs","interval","prevStats","activeWarnings","Set","setInterval","pollStats","clearInterval","clear","stats","getStats","jitter","packetsReceived","packetsLost","pair","rtt","currentRoundTripTime","forEach","report","_b","_c","type","kind","rtp","lossRate","deltaReceived","deltaLost","total","checkWarning","R","max","mos","name","isExceeded","has","add","delete","ServerBusyError","Error","message","super","ConfigUnavailableError","RateLimitedError","MediaStallDetector","onStall","onRecover","flatPollsThreshold","escalate","ua","navigator","userAgent","test","isWebKitEngine","log","prevCounts","flatPolls","stalled","cannotAssessLogged","poll","counts","Map","undefined","set","id","collectAudioInboundRtp","size","prev","advanced","shrunk","comparable","hasNewReport","packets","before","get","describeSelectedPair","selectCandidatePair","selectedPairId","pairs","selectedCandidatePairId","push","selected","find","p","best","nominated","bt","lastPacketReceivedTimestamp","pt","describeCandidatePair","describe","c","base","candidateType","protocol","relayProtocol","localCandidateId","remoteCandidateId","SignalingClient","url","sessionToken","onMessage","onClose","onError","ws","Promise","resolve","reject","wsUrl","encodeURIComponent","WebSocket","timeout","setTimeout","onopen","onmessage","event","msg","JSON","parse","data","clearTimeout","onerror","onclose","code","reason","connectForReconnect","reconnectFrame","settled","send","stringify","error","readyState","OPEN","isConnected","SIGNALING_LOSS_JITTER_MS","TERMINAL_RECONNECT_CODES","RECONNECT_ERROR_MESSAGES","session_expired","session_inactive","invalid_credential","max_recovery_exceeded","unexpected_first_frame","WEBRTC_ERRORS","Object","assign","setRemoteDescription","setLocalDescription","addIceCandidate","createOffer","WebRTCProvider","fetchConfig","signaling","localStream","remoteStream","remoteAudio","remoteAudioMonitor","audioMonitor","qualityMonitor","stallDetector","pendingCandidates","state","eventListeners","iceRestarting","iceRestartAttempts","iceRestartTimeout","iceRestartAnswerTimeout","connectResolve","connectReject","resuming","reconnectAttempts","reconnectErrorCode","brainResponseChunks","interruptedRequests","setConfig","config","setState","mediaDevices","getUserMedia","audio","echoCancellation","noiseSuppression","autoGainControl","emitError","handleSignalingMessage","bind","handleSignalingClose","handleSignalingError","cleanupLocalStream","RTCPeerConnection","iceServers","iceTransportPolicy","audioTrack","getAudioTracks","addTrack","ontrack","handleRemoteTrack","onicecandidate","candidate","toJSON","onconnectionstatechange","handleConnectionStateChange","offer","sdp","cleanup","level","emit","Number","disconnectInternal","suppressOutboundHangup","_d","_e","_f","_g","_h","_j","_k","_l","_m","_o","_p","srcObject","remove","_q","clearIceRestart","originalSessionID","originalConnectionID","toggleMute","track","enabled","muted","isMuted","getLocalStream","getRemoteStream","sendDigits","digits","ch","digit","on","handler","listeners","off","streams","document","createElement","autoplay","body","appendChild","play","connectionState","wasReconnecting","triggerIceRestart","call","onRestartAttemptExpired","isResumeEligible","beginResume","iceRestart","onIceRestartUnanswered","handleAnswer","text","request_id","timestamp","handleBrainResponse","handleInterrupt","session_id","connection_id","handleDrain","_code","_reason","_error","jitterMs","sleep","random","reconnect","reconnect_delay_ms","getSenders","sender","removeTrack","teardownForReconnect","buildReconnectPeerConnection","timer","ms","flushPendingCandidates","remoteDescription","getTracks","t","newState","requestId","final","assembleBrainResponse","chunks","index","entries","sort","a","b","map","join","heardIndex","heard_index","ordered","predicate","filter","heardText","unheardText","detail","invoke","listener","WS_CLOSE_RESUME"],"sourceRoot":""}
|