Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DirectlineStreaming doesn't work with Node.js #386

Closed
orgads opened this issue Jan 4, 2023 · 7 comments
Closed

DirectlineStreaming doesn't work with Node.js #386

orgads opened this issue Jan 4, 2023 · 7 comments
Assignees
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. bug Indicates an unexpected problem or an unintended behavior. customer-replied-to Required for internal reporting. Do not delete. customer-reported Required for internal Azure reporting. Do not delete.

Comments

@orgads
Copy link
Contributor

orgads commented Jan 4, 2023

When trying to replace Directline with DirectlineStreaming, the websocket connection fails.

The failure is in botframework-streaming/lib/webSocket/nodeWebSocketClient. this._url is the full URL (in my case that's wss://my-streaming-bot.azurewebsites.net/.bot/v3/directline/conversations/connect?token=eyJh...), and not the host alone, so websocket initialization throws an exception.

Moreover, the error message is not reflected to the calling code. It just writes "Unable to connect client to Node transport.".

Minimal example follows.

import crypto from 'crypto';
import XMLHttpRequest from 'xhr2';
import WebSocket from 'ws';
global.XMLHttpRequest = XMLHttpRequest;
global.WebSocket = WebSocket;
import { DirectLineStreaming, ConnectionStatus } from 'botframework-directlinejs';
import axios from 'axios';

const botUrl = 'https://my-streaming-bot.azurewebsites.net/.bot/v3/directline';
const userId = crypto.randomUUID();
const username = `dl_ac_test-${userId.substring(0, userId.indexOf('-'))}`;

const botSecret = 'XXXYYYZZZ';

async function main() {
  const data = { user: { id: username } };
  const token = (await axios.post('tokens/generate', data, {
    baseURL: botUrl,
    headers: { Authorization: `Bearer ${botSecret}` }
  })).data;
  const directLine = new DirectLineStreaming({
    conversationId: token.conversationId,
    token: token.token,
    domain: botUrl,
    webSocket: true,
  });
  directLine.connectionStatus$.subscribe(
    (connectionStatus) => console.log('DirectLine status: ' + ConnectionStatus[connectionStatus])
  );

  directLine.activity$.subscribe({
    next(activity) { console.log(activity); }
  });
}
main();
@stevkan
Copy link

stevkan commented Jan 4, 2023

@orgads - I'll be taking a look at this and will attempt a repro. Just to verify, can you please tell me which version of botframework-directlinejs, axios, and Node you have installed?

@orgads
Copy link
Contributor Author

orgads commented Jan 4, 2023

Node 18, all the packages are latest. This is the dependencies part in package.json

{
  "dependencies": {
    "axios": "^1.2.2",
    "botframework-directlinejs": "^0.15.1",
    "xhr2": "^0.2.1"
  }
}

@infinite-ram infinite-ram added customer-reported Required for internal Azure reporting. Do not delete. Bot Services Required for internal Azure reporting. Do not delete. Do not change color. bug Indicates an unexpected problem or an unintended behavior. labels Jan 4, 2023
@stevkan
Copy link

stevkan commented Jan 5, 2023

@orgads - I was able to repro the issue. I'm currently researching it to try and identify where the specific failure is occuring and how it can be fixed or if there is a possible workaround. I will keep you posted.

@infinite-ram infinite-ram added the customer-replied-to Required for internal reporting. Do not delete. label Jan 5, 2023
@stevkan
Copy link

stevkan commented Jan 12, 2023

For reference, here are the results of my repro noted above.

I used the customer's code with the exception of the observables. For those, I copied the code from the BotFramework-DirectLineJS readme doc just to ensure I was using tested code.

const XMLHttpRequest = require( 'xhr2');
const WebSocket = require( 'ws');
global.XMLHttpRequest = XMLHttpRequest;
global.WebSocket = WebSocket;
const crypto = require( 'crypto');
const { DirectLineStreaming, ConnectionStatus } = require('botframework-directlinejs');
const axios = require( 'axios');

const botUrl = 'https://some.site.azurewebsites.net/.bot/v3/directline';
const userId = crypto.randomUUID();
const username = `dl_ac_test-${userId.substring(0, userId.indexOf('-'))}`;

const botSecret = 'yEeoO.....FjSHI';

async function main() {
  const data = { user: { id: username } };
  const token = (
    await axios.post('tokens/generate', data, {
      baseURL: botUrl,
      headers: { Authorization: `Bearer ${botSecret}` },
    })
  ).data;
  
  console.log('TOKEN ', token)
  
  const directLine = new DirectLineStreaming({
    // conversationId: token.conversationId,
    token: token.token,
    domain: botUrl,
    // webSocket: true,
  });

  console.log('DIRECTLINE ', directLine)
  
  directLine.connectionStatus$.subscribe(connectionStatus => {
    switch (connectionStatus) {
      case ConnectionStatus.Uninitialized: // the status when the DirectLine object is first created/constructed
        console.log('CONNECTION STATUS: UNINITIATED');
        break;
      case ConnectionStatus.Connecting: // currently trying to connect to the conversation
        console.log('CONNECTION STATUS: CONNECTING');
        break;
      case ConnectionStatus.Online: // successfully connected to the converstaion. Connection is healthy so far as we know.
        console.log('CONNECTION STATUS: ONLINE');
        break;
      case ConnectionStatus.ExpiredToken: // last operation errored out with an expired token. Your app should supply a new one.
        console.log('CONNECTION STATUS: EXPIRED TOKEN');
        break;
      case ConnectionStatus.FailedToConnect: // the initial attempt to connect to the conversation failed. No recovery possible.
        console.log('CONNECTION STATUS: FAILED TO CONNECT');
        break;
      case ConnectionStatus.Ended: // the bot ended the conversation
        console.log('CONNECTION STATUS: ENDED');
        break;
    }
  });

  directLine
    .postActivity({
      from: { id: 'myUserId', name: 'myUserName' }, // required (from.name is optional)
      type: 'message',
      text: 'a message for you, Rudy',
    })
    .subscribe(
      id => console.log('Posted activity, assigned ID ', id),
      error => console.log('Error posting activity', error)
    );

  directLine.activity$.subscribe(activity => console.log('received activity ', activity));
}
main();

When running, if directLine.postActivity() is run first then I receive this error:

CONNECTION STATUS: UNINITIATED
CONNECTION STATUS: CONNECTING
C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-directlinejs\lib\directLineStreaming.js:363
                  return _this3.streamConnection.send(request);
                                                 ^

TypeError: Cannot read properties of undefined (reading 'send')
    at _callee4$ (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-directlinejs\lib\directLineStreaming.js:363:50)
    at tryCatch (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\regenerator-runtime\runtime.js:64:40)
    at Generator.invoke (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\regenerator-runtime\runtime.js:299:22)
    at Generator.next (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\regenerator-runtime\runtime.js:124:21)
    at asyncGeneratorStep (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:24)
    at _next (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\@babel\runtime\helpers\asyncToGenerator.js:25:9)
    at C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\@babel\runtime\helpers\asyncToGenerator.js:32:7
    at new Promise (<anonymous>)
    at Observable.<anonymous> (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\@babel\runtime\helpers\asyncToGenerator.js:21:12)
    at Observable._subscribe (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-directlinejs\lib\directLineStreaming.js:416:24)

However, if directLine.activity$.subscribe() is ran first then I receive this error:

CONNECTION STATUS: UNINITIATED
CONNECTION STATUS: CONNECTING
Failed to connect Error: Unable to connect client to Node transport.
C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-streaming\lib\webSocket\nodeWebSocketClient.js:63
                throw new Error(`Unable to connect client to Node transport.`);
                      ^

Error: Unable to connect client to Node transport.
    at WebSocketClient.<anonymous> (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-streaming\lib\webSocket\nodeWebSocketClient.js:63:23)
    at Generator.throw (<anonymous>)
    at rejected (C:\Users\Steven.Kanberg\Source\CustomerSubmits\DirectlineSteaming-386\node_modules\botframework-streaming\lib\webSocket\nodeWebSocketClient.js:13:65)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

With both observables commented out, the DirectLine object is created and the connection status waits in the UNINITIATED state.

Here is what the DirectLineStreaming object looks like, if of use:

{
  'connectionStatus$': BehaviorSubject {
    _isScalar: false,
    observers: [ [Subscriber] ],
    closed: false,
    isStopped: false,
    hasError: false,
    thrownError: null,
    _value: 0
  },
  _botAgent: 'DirectLine/3.0 (directlineStreaming)',
  token: 'eyJhb.....dnurE',
  domain: 'https://some.site.azurewebsites.net/.bot/v3/directline',
  queueActivities: true,
  'activity$': Observable {
    _isScalar: false,
    source: Observable {
      source: [Observable],
      subjectFactory: [Function: shareSubjectFactory]
    },
    operator: RefCountOperator { connectable: [Observable] }
  }
}

While debugging, I noted the two following observations:

  • When connect() is called in the NodeWebSocket class (ref here), the req object's res property is showing null. With my limited knowledge on the inner workings of web sockets, I don't know if this is expected or not.
  • The this.wsSocket property is showing as undefined on line 101. This property is initiated as undefined in the class's constructor and I am unable to access the this.wsSocket = websocket assignment taking place within the completeUpgrade() method. I can't verify if an assignment is actually taking place, so I'm unsure if this is processing correctly.

In either case, the promise on line 105 is returning an error. The issue may be tied to something else, but this is what I noted.

@munozemilio
Copy link

Closing this due to inactivity. Feel free to reopen

@orgads
Copy link
Contributor Author

orgads commented Jan 23, 2023

Please reopen. The issue is not resolved.

@orgads
Copy link
Contributor Author

orgads commented Jan 27, 2023

Superseded by microsoft/botbuilder-js#4412.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bot Services Required for internal Azure reporting. Do not delete. Do not change color. bug Indicates an unexpected problem or an unintended behavior. customer-replied-to Required for internal reporting. Do not delete. customer-reported Required for internal Azure reporting. Do not delete.
Projects
None yet
Development

No branches or pull requests

4 participants