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