deepgram / deepgram-js-sdk

Official JavaScript SDK for Deepgram's automated speech recognition APIs.
https://developers.deepgram.com
MIT License
127 stars 45 forks source link

Websocket connection errors go unhandled. #223

Closed lukeocodes closed 5 months ago

lukeocodes commented 5 months ago

Here is the full error I get

listening on localhost:3000
socket: client connected
node:events:505
    throw err; // Unhandled 'error' event
    ^

Error [ERR_UNHANDLED_ERROR]: Unhandled error. (_Event {
  type: 'error',
  isTrusted: false,
  _yaeti: true,
  target: W3CWebSocket {
    _listeners: {},
    addEventListener: [Function: _addEventListener],
    removeEventListener: [Function: _removeEventListener],
    dispatchEvent: [Function: _dispatchEvent],
    _url: 'ws://localhost/v1/listen?language=en&punctuate=true&smart_format=true&model=nova',
    _readyState: 3,
    _protocol: undefined,
    _extensions: '',
    _bufferedAmount: 0,
    _binaryType: 'arraybuffer',
    _connection: undefined,
    _client: WebSocketClient {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      config: [Object],
      _req: null,
      protocols: [Array],
      origin: undefined,
      url: [Url],
      secure: false,
      base64nonce: 'ZBoO+g2pzojE9qmJbfYe3Q==',
      [Symbol(kCapture)]: false
    },
    onopen: [Function (anonymous)],
    onclose: [Function (anonymous)],
    onerror: [Function (anonymous)],
    onmessage: [Function (anonymous)]
  },
  cancelable: true,
  stopImmediatePropagation: [Function (anonymous)]
})
    at new NodeError (node:internal/errors:405:5)
    at LiveClient.emit (node:events:503:17)
    at LiveClient._socket.onerror (/home/damien/Desktop/node-live-example-main/node_modules/@deepgram/sdk/dist/main/packages/LiveClient.js:36:18)
    at W3CWebSocket._dispatchEvent [as dispatchEvent] (/home/damien/Desktop/node-live-example-main/node_modules/yaeti/lib/EventTarget.js:107:17)
    at W3CWebSocket.onConnectFailed (/home/damien/Desktop/node-live-example-main/node_modules/websocket/lib/W3CWebSocket.js:217:14)
    at WebSocketClient.<anonymous> (/home/damien/Desktop/node-live-example-main/node_modules/websocket/lib/W3CWebSocket.js:59:25)
    at WebSocketClient.emit (node:events:514:28)
    at WebSocketClient.failHandshake (/home/damien/Desktop/node-live-example-main/node_modules/websocket/lib/WebSocketClient.js:339:10)
    at ClientRequest.<anonymous> (/home/damien/Desktop/node-live-example-main/node_modules/websocket/lib/WebSocketClient.js:278:18)
    at ClientRequest.emit (node:events:514:28) {
  code: 'ERR_UNHANDLED_ERROR',
  context: _Event {
    type: 'error',
    isTrusted: false,
    _yaeti: true,
    target: W3CWebSocket {
      _listeners: {},
      addEventListener: [Function: _addEventListener],
      removeEventListener: [Function: _removeEventListener],
      dispatchEvent: [Function: _dispatchEvent],
      _url: 'ws://localhost/v1/listen?language=en&punctuate=true&smart_format=true&model=nova',
      _readyState: 3,
      _protocol: undefined,
      _extensions: '',
      _bufferedAmount: 0,
      _binaryType: 'arraybuffer',
      _connection: undefined,
      _client: WebSocketClient {
        _events: [Object: null prototype] {},
        _eventsCount: 0,
        _maxListeners: undefined,
        config: {
          maxReceivedFrameSize: 1048576,
          maxReceivedMessageSize: 8388608,
          fragmentOutgoingMessages: true,
          fragmentationThreshold: 16384,
          webSocketVersion: 13,
          assembleFragments: true,
          disableNagleAlgorithm: true,
          closeTimeout: 5000,
          tlsOptions: {}
        },
        _req: null,
        protocols: [ 'token', 'f1e75452fbafeaaf1e4ee5a2b424e84336d0fb04' ],
        origin: undefined,
        url: Url {
          protocol: 'ws:',
          slashes: true,
          auth: null,
          host: 'localhost',
          port: '80',
          hostname: 'localhost',
          hash: null,
          search: '?language=en&punctuate=true&smart_format=true&model=nova',
          query: 'language=en&punctuate=true&smart_format=true&model=nova',
          pathname: '/v1/listen',
          path: '/v1/listen?language=en&punctuate=true&smart_format=true&model=nova',
          href: 'ws://localhost/v1/listen?language=en&punctuate=true&smart_format=true&model=nova'
        },
        secure: false,
        base64nonce: 'ZBoO+g2pzojE9qmJbfYe3Q==',
        [Symbol(kCapture)]: false
      },
      onopen: [Function (anonymous)],
      onclose: [Function (anonymous)],
      onerror: [Function (anonymous)],
      onmessage: [Function (anonymous)]
    },
    cancelable: true,
    stopImmediatePropagation: [Function (anonymous)]
  }
}

Here is what the emitted error contains

Server responded with a non-101 status: 404 Not Found
Response Headers Follow:
server: nginx/1.18.0 (Ubuntu)
date: Tue, 02 Jan 2024 20:06:02 GMT
content-type: text/html
content-length: 162
connection: keep-alive
lukeocodes commented 5 months ago

@DamienDeepgram curious about this. Was an error event listener set up at the time this errored?

For e.g.

const dgConnection = deepgram.listen.live({ model: "nova" });

dgConnection.on(LiveTranscriptionEvents.Open, () => {
  dgConnection.on(LiveTranscriptionEvents.Transcript, (data) => {
    console.log(data);
  });

  dgConnection.on(LiveTranscriptionEvents.Error, (err) => {
    console.error(err);
  });
});

I've been reading a bit about this. It appears that (despite what you and I are both seeing) neither object throws an error?? The docs say it won't throw, it will only ever emit an error event. I don't get an error event, but I do see an uncaught exception.

:notlikethis:

lukeocodes commented 5 months ago

Further reading, I realise now our websocket library is using a custom event when a connection handshake errors with a 404. We should add a listener for this and expose it to the users.

DamienDeepgram commented 5 months ago

@DamienDeepgram curious about this. Was an error event listener set up at the time this errored?

For e.g.

const dgConnection = deepgram.listen.live({ model: "nova" });

dgConnection.on(LiveTranscriptionEvents.Open, () => {
  dgConnection.on(LiveTranscriptionEvents.Transcript, (data) => {
    console.log(data);
  });

  dgConnection.on(LiveTranscriptionEvents.Error, (err) => {
    console.error(err);
  });
});

I was using the node-live sample in this repo here: https://github.com/deepgram/deepgram-js-sdk/blob/main/examples/node-live/index.js

It looks like that does not handle the errors.

So if I add the missing error handler this would catch there errors automatically nice.

I added a PR to add in the error handling

See: https://github.com/deepgram/deepgram-js-sdk/pull/231

AntonDobrev commented 4 weeks ago

Hi dear all,

Allow me to re-open this issue as I believe it is still present in the Deepgram JS SDK - after a good amount of debugging the issue. It is easy reproducible using the server implementation here - https://github.com/deepgram-devs/node-live-example/blob/main/server.js

It turned out that my API key has expired - however, this is not the root cause of the issue. If attempting to connect to the Deepgram WSS API directly I’ve received the correct error - 401 Unauthorised and the connection was denied. The SDK does not handle the 401 Unauthorised event properly perhaps because another error is thrown in the middle of the whole process. The error is the same as reported in the beginning of this issue.

Environment:

Steps to Reproduce:

  1. Get yourself an expired API key for Deepgram

  2. Clone the aforementioned repo and put your API key

  3. Run the server from the sample app

  4. Connect to your server at localhost (via a client or a tool)

  5. This will trigger the Node server implementation to try to connect to the Deepgram API

  6. An error is thrown unexpectedly from inside the SDK:

    node:events:505
    throw err; // Unhandled 'error' event
     ^
    
    Error [ERR_UNHANDLED_ERROR]
    // omitted for brevity
  7. (OPTIONAL) If you have correctly attached the LiveTranscriptionEvent.Close listener (not inside of the event handler for LiveTranscriptionEvents.Open but on the main level) you will also receive the event handled by the SDK that correctly understood the connection was closed:

{
   "type":"close",
   "isTrusted":false,
   "_yaeti":true,
   "code":1006,
   "reason":"connection failed",
   "wasClean":false,
   "target":{
      "_listeners":{

      },
      "_url":"wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true",
      "_readyState":3,
      "_extensions":"",
      "_bufferedAmount":0,
      "_binaryType":"arraybuffer",
      "_client":{
         "_events":{

         },
         "_eventsCount":0,
         "config":{
            "maxReceivedFrameSize":1048576,
            "maxReceivedMessageSize":8388608,
            "fragmentOutgoingMessages":true,
            "fragmentationThreshold":16384,
            "webSocketVersion":13,
            "assembleFragments":true,
            "disableNagleAlgorithm":true,
            "closeTimeout":5000,
            "tlsOptions":{

            }
         },
         "_req":null,
         "protocols":[
....
         ],
         "url":{
            "protocol":"wss:",
            "slashes":true,
            "auth":null,
            "host":"api.deepgram.com",
            "port":"443",
            "hostname":"api.deepgram.com",
            "hash":null,
            "search":"?smart_format=true&model=nova-2&language=fr&interim_results=true",
            "query":"smart_format=true&model=nova-2&language=fr&interim_results=true",
            "pathname":"/v1/listen",
            "path":"/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true",
            "href":"wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true"
         },
         "secure":true,
         "base64nonce":"vKmLdRFpuL3GyGm4p9dHJw=="
      }
   },
   "cancelable":true
}

Further info:

When connecting via Postman directly to the WSS API - wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true and using the expired token in the Authorization header I receive the expected error - 401 Unauthorised.

Expected behaviour:

  1. The Deepgram JS SDK doesn't throw an error - proper error handling is done in the SDK
  2. The LiveTranscriptionEvent.Error event is called.

I hope this helps improving the SDK or someone stuck like me in debugging the error.

Thank you for the well-thought and architected SDK.

Ivan-juni commented 4 weeks ago

Hi dear all,

Allow me to re-open this issue as I believe it is still present in the Deepgram JS SDK - after a good amount of debugging the issue. It is easy reproducible using the server implementation here - https://github.com/deepgram-devs/node-live-example/blob/main/server.js

It turned out that my API key has expired - however, this is not the root cause of the issue. If attempting to connect to the Deepgram WSS API directly I’ve received the correct error - 401 Unauthorised and the connection was denied. The SDK does not handle the 401 Unauthorised event properly perhaps because another error is thrown in the middle of the whole process. The error is the same as reported in the beginning of this issue.

Environment:

  • Deepgram JS SDK 3.3.3
  • Node.js v20.9.0

Steps to Reproduce:

  1. Get yourself an expired API key for Deepgram
  2. Clone the aforementioned repo and put your API key
  3. Run the server from the sample app
  4. Connect to your server at localhost (via a client or a tool)
  5. This will trigger the Node server implementation to try to connect to the Deepgram API
  6. An error is thrown unexpectedly from inside the SDK:
node:events:505
   throw err; // Unhandled 'error' event
    ^

Error [ERR_UNHANDLED_ERROR]
// omitted for brevity
  1. (OPTIONAL) If you have correctly attached the LiveTranscriptionEvent.Close listener (not inside of the event handler for LiveTranscriptionEvents.Open but on the main level) you will also receive the event handled by the SDK that correctly understood the connection was closed:
{
   "type":"close",
   "isTrusted":false,
   "_yaeti":true,
   "code":1006,
   "reason":"connection failed",
   "wasClean":false,
   "target":{
      "_listeners":{

      },
      "_url":"wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true",
      "_readyState":3,
      "_extensions":"",
      "_bufferedAmount":0,
      "_binaryType":"arraybuffer",
      "_client":{
         "_events":{

         },
         "_eventsCount":0,
         "config":{
            "maxReceivedFrameSize":1048576,
            "maxReceivedMessageSize":8388608,
            "fragmentOutgoingMessages":true,
            "fragmentationThreshold":16384,
            "webSocketVersion":13,
            "assembleFragments":true,
            "disableNagleAlgorithm":true,
            "closeTimeout":5000,
            "tlsOptions":{

            }
         },
         "_req":null,
         "protocols":[
....
         ],
         "url":{
            "protocol":"wss:",
            "slashes":true,
            "auth":null,
            "host":"api.deepgram.com",
            "port":"443",
            "hostname":"api.deepgram.com",
            "hash":null,
            "search":"?smart_format=true&model=nova-2&language=fr&interim_results=true",
            "query":"smart_format=true&model=nova-2&language=fr&interim_results=true",
            "pathname":"/v1/listen",
            "path":"/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true",
            "href":"wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true"
         },
         "secure":true,
         "base64nonce":"vKmLdRFpuL3GyGm4p9dHJw=="
      }
   },
   "cancelable":true
}

Further info:

When connecting via Postman directly to the WSS API - wss://api.deepgram.com/v1/listen?smart_format=true&model=nova-2&language=fr&interim_results=true and using the expired token in the Authorization header I receive the expected error - 401 Unauthorised.

Expected behaviour:

  1. The Deepgram JS SDK doesn't throw an error - proper error handling is done in the SDK
  2. The LiveTranscriptionEvent.Error event is called.

I hope this helps improving the SDK or someone stuck like me in debugging the error.

Thank you for the well-thought and architected SDK.

+1, having some problem. moved the error and close listeners at the same level as open listener, but still throws unhandled and crushes the app