()
| 220 | ws.send(Buffer.from(audioChunk)) |
| 221 | }, |
| 222 | finalize(): Promise<FinalizeSource> { |
| 223 | if (finalizing || finalized) { |
| 224 | // Already finalized or WebSocket already closed — resolve immediately. |
| 225 | return Promise.resolve('ws_already_closed') |
| 226 | } |
| 227 | finalizing = true |
| 228 | |
| 229 | return new Promise<FinalizeSource>(resolve => { |
| 230 | const safetyTimer = setTimeout( |
| 231 | () => resolveFinalize?.('safety_timeout'), |
| 232 | FINALIZE_TIMEOUTS_MS.safety, |
| 233 | ) |
| 234 | const noDataTimer = setTimeout( |
| 235 | () => resolveFinalize?.('no_data_timeout'), |
| 236 | FINALIZE_TIMEOUTS_MS.noData, |
| 237 | ) |
| 238 | cancelNoDataTimer = () => { |
| 239 | clearTimeout(noDataTimer) |
| 240 | cancelNoDataTimer = null |
| 241 | } |
| 242 | |
| 243 | resolveFinalize = (source: FinalizeSource) => { |
| 244 | clearTimeout(safetyTimer) |
| 245 | clearTimeout(noDataTimer) |
| 246 | resolveFinalize = null |
| 247 | cancelNoDataTimer = null |
| 248 | // Legacy Deepgram can leave an interim in lastTranscriptText |
| 249 | // with no TranscriptEndpoint (websocket_manager.py sends |
| 250 | // TranscriptChunk and TranscriptEndpoint as independent |
| 251 | // channel items). All resolve triggers must promote it; |
| 252 | // centralize here. No-op when the close handler already did. |
| 253 | if (lastTranscriptText) { |
| 254 | logForDebugging( |
| 255 | `[voice_stream] Promoting unreported interim before ${source} resolve`, |
| 256 | ) |
| 257 | const t = lastTranscriptText |
| 258 | lastTranscriptText = '' |
| 259 | callbacks.onTranscript(t, true) |
| 260 | } |
| 261 | logForDebugging(`[voice_stream] Finalize resolved via ${source}`) |
| 262 | resolve(source) |
| 263 | } |
| 264 | |
| 265 | // If the WebSocket is already closed, resolve immediately. |
| 266 | if ( |
| 267 | ws.readyState === WebSocket.CLOSED || |
| 268 | ws.readyState === WebSocket.CLOSING |
| 269 | ) { |
| 270 | resolveFinalize('ws_already_closed') |
| 271 | return |
| 272 | } |
| 273 | |
| 274 | // Defer CloseStream to the next event-loop iteration so any audio |
| 275 | // callbacks already queued by the native recording module are flushed |
| 276 | // to the WebSocket before the server is told to stop accepting audio. |
| 277 | // Without this, stopRecording() can return synchronously while the |
| 278 | // native module still has a pending onData callback in the event queue, |
| 279 | // causing audio to arrive after CloseStream. |
nothing calls this directly
no test coverage detected