Skip to content

WebSocket connection fails silently in Bun — getGlobalWebSocket should fall back to ws for Bun runtime #466

Description

@rogerpadilla

Bug Report

Environment

  • SDK version: @deepgram/sdk v5.0.0
  • Runtime: Bun v1.3.10 (macOS arm64)
  • Feature affected: Live/streaming WebSocket connections (listen.v1.connect())

Description

The listen.v1.connect() WebSocket connection fails silently in Bun. The returned socket has readyState: 3 (CLOSED) immediately, and the open event never fires. REST API endpoints (listen.v1.media.transcribeUrl, etc.) work correctly.

Root Cause

The issue is in core/websocket/ws.ts — the getGlobalWebSocket() function:

const getGlobalWebSocket = () => {
    if (typeof WebSocket !== "undefined") {
        return WebSocket;                          // ← Bun hits this (native WebSocket)
    } else if (RUNTIME.type === "node") {
        return NodeWebSocket;                      // ← never reached for Bun
    }
    return undefined;
};

What happens:

  1. The SDK correctly detects RUNTIME.type === "bun" in runtime.mjs
  2. But getGlobalWebSocket() checks typeof WebSocket !== "undefined" first — which is true in Bun (it has a native WebSocket global)
  3. Bun's native WebSocket constructor ignores the 3rd options argument (including headers)
  4. The ReconnectingWebSocket._connect() calls new WebSocket(url, protocols, { headers }) — the Authorization header is silently dropped
  5. Deepgram's server rejects the handshake (no auth) → socket goes to CLOSED

The ws npm package (imported as NodeWebSocket) handles headers correctly, but the fallback else if (RUNTIME.type === "node") is never reached because Bun satisfies the first condition.

Reproduction

import { DeepgramClient } from '@deepgram/sdk';

const client = new DeepgramClient({ apiKey: 'YOUR_KEY' });
const dg = await client.listen.v1.connect({
  model: 'nova-3',
  language: 'multi',
  smart_format: 'true',
  Authorization: 'Token YOUR_KEY',
});

console.log(dg.readyState); // 3 (CLOSED) — should be 0 (CONNECTING)

dg.on('open', () => console.log('open'));   // Never fires
dg.on('error', (e) => console.log(e));      // Never fires

Workaround: Using the ws library directly to connect works perfectly in Bun:

import WS from 'ws';

const ws = new WS('wss://api.deepgram.com/v1/listen?model=nova-3', {
  headers: { Authorization: 'Token YOUR_KEY' },
});

ws.on('open', () => console.log('connected!')); // ✅ Works

Suggested Fix

Add "bun" to the NodeWebSocket fallback in getGlobalWebSocket():

const getGlobalWebSocket = () => {
-   if (typeof WebSocket !== "undefined") {
+   if (RUNTIME.type !== "bun" && typeof WebSocket !== "undefined") {
        return WebSocket;
-   } else if (RUNTIME.type === "node") {
+   } else if (RUNTIME.type === "node" || RUNTIME.type === "bun") {
        return NodeWebSocket;
    }
    return undefined;
};

This ensures Bun uses the ws library (which supports custom headers) instead of its native WebSocket (which doesn't).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions