Search code examples
node.jsreactjsweb-applicationsbotframework

How to Pass token(data) in react-Nodejs web application (from nodejs file to react tsx file)


I am testing a bot framework code using nodeJS and reactjs web application, the code works as expected however since the bearer token is in reactjs it is visible in the debugger tools from the browser, so i am planning to have the rest call to get the token in the server.js file in the nodejs side. Currently the token generation happens on the reactjs side as shown in the code below. This is the link to the application build up

declare var require: any
var React = require('react');
var ReactDOM = require('react-dom');
import ReactWebChat, { createDirectLine } from 'botframework-webchat';
import { Provider } from 'react-redux'
import { createStyleSet } from 'botframework-webchat';
import { getAccessToken } from './server';

export class Hello extends React.Component {
    constructor() {
        super();

        this.state = {
            directLine: null,
            styleSet: null,
            userName: null
        };
    }
    async componentDidMount() {
        var s = getAccessToken;
        var responseFromAuthCall = "";

        this.setState(() => ({
            directLine: createDirectLine({ token }),

        }));
    }
    render() {
        const {
            state: { data }
        } = this

        return (
            this.state.directLine ?
                <ReactWebChat
                    className="chat"
                    directLine={this.state.directLine}
                    styleSet={this.state.styleSet}
                />
                :
                <div>Connecting to bot&hellip;</div>
        );
    }
}
ReactDOM.render(<Hello />, document.getElementById('root'));

Below is the code for the server.js file on the nodejs side

'use strict';
var path = require('path');
var express = require('express');
var reactredux = require('react-redux');

const https = require('https');
var Request = require("request");
var app = express();
const axios = require('axios');
var staticPath = path.join(__dirname, '/');
app.use(express.static(staticPath));


exports.getAccessToken = function () {
    Request.post({
        "headers": { "Authorization": "Bearer token", 'Accept': 'application/json', 'Content-Type': 'application/json' },
        "url": "https://directline.botframework.com/v3/directline/tokens/generate",
        "body": JSON.stringify({
            "accessLevel": "View",
            "allowSaveAs": "false"
        })
    }, (error, response, body) => {
        if (error) {
            return console.dir(error);
        }
        return JSON.parse(body);
        //console.dir(JSON.parse(body));
        //console.log("test");
    });
};

var server = app.listen(app.get('port'), function () {
    console.log('listening');
});


// Allows you to set port in the project properties.    
app.set('port', process.env.PORT || 3000);

I am exporting the getAccessToken function and importing the same in the reactjs side, but seems like there is some issue with the way i am importing it in the reatjs file....any suggestions?

Error:


C:\Users\loc>node_modules\.bin\webpack app.tsx --config webpack-config.js
Hash: 86894ea9d43d1a2268fb
Version: webpack 4.23.1
Time: 11781ms
Built at: 2019-06-24 16:10:48
              Asset      Size  Chunks             Chunk Names
    ./app-bundle.js  9.55 MiB    main  [emitted]  main
./app-bundle.js.map   9.7 MiB    main  [emitted]  main
Entrypoint main = ./app-bundle.js ./app-bundle.js.map
 [0] util (ignored) 15 bytes {main} [built]
 [1] util (ignored) 15 bytes {main} [built]
 [2] buffer (ignored) 15 bytes {main} [optional] [built]
 [3] crypto (ignored) 15 bytes {main} [optional] [built]
 [4] readable-stream (ignored) 15 bytes {main} [built]
 [5] supports-color (ignored) 15 bytes {main} [built]
 [6] chalk (ignored) 15 bytes {main} [built]
 [7] ./terminal-highlight (ignored) 15 bytes {main} [built]
[11] http (ignored) 15 bytes {main} [built]
 [./app.tsx] 9.15 KiB {main} [built]
 [./node_modules/express/lib sync recursive] ./node_modules/express/lib sync 160 bytes {main} [built]
 [./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {main} [built]
 [./node_modules/webpack/buildin/harmony-module.js] (webpack)/buildin/harmony-module.js 573 bytes {main} [built]
 [./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 497 bytes {main} [built]
 [./server.js] 3.59 KiB {main} [built]
    + 1277 hidden modules

WARNING in ./node_modules/express/lib/view.js 81:13-25
Critical dependency: the request of a dependency is an expression
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

WARNING in C:/Users/loc/node_modules/ajv/lib/async.js 96:20-33
Critical dependency: the request of a dependency is an expression
 @ C:/Users/loc/node_modules/ajv/lib/ajv.js
 @ C:/Users/loc/node_modules/har-validator/lib/node4/promise.js
 @ C:/Users/loc/node_modules/request/lib/har.js
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

WARNING in C:/Users/loc/node_modules/ajv/lib/async.js 119:15-28
Critical dependency: the request of a dependency is an expression
 @ C:/Users/loc/node_modules/ajv/lib/ajv.js
 @ C:/Users/loc/node_modules/har-validator/lib/node4/promise.js
 @ C:/Users/loc/node_modules/request/lib/har.js
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

WARNING in C:/Users/loc/node_modules/ajv/lib/compile/index.js 13:21-34
Critical dependency: the request of a dependency is an expression
 @ C:/Users/loc/node_modules/ajv/lib/ajv.js
 @ C:/Users/loc/node_modules/har-validator/lib/node4/promise.js
 @ C:/Users/loc/node_modules/request/lib/har.js
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/destroy/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\destroy'
 @ ./node_modules/destroy/index.js 14:17-30
 @ ./node_modules/send/index.js
 @ ./node_modules/express/lib/utils.js
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/etag/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\etag'
 @ ./node_modules/etag/index.js 22:12-25
 @ ./node_modules/express/lib/utils.js
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/express/lib/view.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\express\lib'
 @ ./node_modules/express/lib/view.js 18:9-22
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/mime/mime.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\mime'
 @ ./node_modules/mime/mime.js 2:9-22
 @ ./node_modules/send/index.js
 @ ./node_modules/express/lib/utils.js
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/send/index.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\send'
 @ ./node_modules/send/index.js 23:9-22
 @ ./node_modules/express/lib/utils.js
 @ ./node_modules/express/lib/application.js
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/request/lib/har.js
Module not found: Error: Can't resolve 'fs' in 'C:\Users\loc\node_modules\request\lib'
 @ C:/Users/loc/node_modules/request/lib/har.js 3:9-22
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in ./node_modules/express/lib/request.js
Module not found: Error: Can't resolve 'net' in 'C:\Users\loc\node_modules\express\lib'
 @ ./node_modules/express/lib/request.js 18:11-25
 @ ./node_modules/express/lib/express.js
 @ ./node_modules/express/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/forever-agent/index.js
Module not found: Error: Can't resolve 'net' in 'C:\Users\loc\node_modules\forever-agent'
 @ C:/Users/loc/node_modules/forever-agent/index.js 6:10-24
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/tough-cookie/lib/cookie.js
Module not found: Error: Can't resolve 'net' in 'C:\Users\loc\node_modules\tough-cookie\lib'
 @ C:/Users/loc/node_modules/tough-cookie/lib/cookie.js 32:10-24
 @ C:/Users/loc/node_modules/request/lib/cookies.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/tunnel-agent/index.js
Module not found: Error: Can't resolve 'net' in 'C:\Users\loc\node_modules\tunnel-agent'
 @ C:/Users/loc/node_modules/tunnel-agent/index.js 3:10-24
 @ C:/Users/loc/node_modules/request/lib/tunnel.js
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/forever-agent/index.js
Module not found: Error: Can't resolve 'tls' in 'C:\Users\loc\node_modules\forever-agent'
 @ C:/Users/loc/node_modules/forever-agent/index.js 7:10-24
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

ERROR in C:/Users/loc/node_modules/tunnel-agent/index.js
Module not found: Error: Can't resolve 'tls' in 'C:\Users\loc\node_modules\tunnel-agent'
 @ C:/Users/loc/node_modules/tunnel-agent/index.js 4:10-24
 @ C:/Users/loc/node_modules/request/lib/tunnel.js
 @ C:/Users/loc/node_modules/request/request.js
 @ C:/Users/loc/node_modules/request/index.js
 @ ./server.js
 @ ./app.tsx

Solution

  • This is how I have a similar project setup which works quite well.

    First, add the token generating endpoint into you bot's index.js file. The following code can be appended directly to the end of the file or you can organize however you choose. While your bot is running, this endpoint will also be available.

    You can, of course, setup your endpoint elsewhere. It literally can be anywhere. I originally had the below in a separate project, first on Azure, then locally running side-by-side with my React project but realized I could just run it in the bot. The important part is fetching the token (see further below).

    In this instance, I have the direct line secret (process.env.directLineSecret) in a .env file. I am also running it on port 3500 (fyi).

    /**
     * Creates token server
     */
    const bodyParser = require('body-parser');
    const request = require('request');
    const corsMiddleware = require('restify-cors-middleware');
    
    const cors = corsMiddleware({
      origins: ['*']
    });
    
    // Create HTTP tokenServer.
    let tokenServer = restify.createServer();
    tokenServer.pre(cors.preflight);
    tokenServer.use(cors.actual);
    tokenServer.use(bodyParser.json({
      extended: false
    }));
    tokenServer.dl_name = 'DirectLine';
    tokenServer.listen(process.env.port || process.env.PORT || 3500, function() {
      console.log(`\n${ tokenServer.dl_name } listening to ${ tokenServer.url }.`);
    });
    
    // Listen for incoming requests.
    tokenServer.post('/directline/token', (req, res) => {
      // userId must start with `dl_`
      const userId = (req.body && req.body.id) ? req.body.id : `dl_${ Date.now() + Math.random().toString(36) }`;
      const options = {
        method: 'POST',
        uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
        headers: {
          'Authorization': `Bearer ${ process.env.directLineSecret }`
        },
        json: {
          User: {
            Id: userId
          }
        }
      };
      request.post(options, (error, response, body) => {
        if (!error && response.statusCode < 300) {
          res.send({
            token: body.token
          });
        } else {
          res.status(500);
          res.send('Call to retrieve token from DirectLine failed');
        }
      });
    });
    

    Next, create your WebChat component. Here you will make your call to get the token. Be sure to use the same endpoint / port you specified in your bot's index.js file (or whatever endpoint you created). The token will generate with the page via componentDidMount(). The directLine connection settings will be saved to state and, subsequently, accessed when the Web Chat session begins via the render() method.

    import React from 'react';
    import ReactWebChat, { createDirectLine } from 'botframework-webchat';
    
    export default class WebChat extends React.Component {
      constructor(props) {
        super(props);
    
      componentDidMount() {
        this.fetchToken();
      }
    
      async fetchToken() {
        const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
        const { token } = await res.json();
    
        this.setState(() => ({
          directLine: createDirectLine({ token })
        }));
      }
    
      render() {
        return (
          this.state.directLine ?
              <ReactWebChat
                directLine={ this.state.directLine }
                styleSet={ this.state.styleSet }
                tableIndex="-1"
                { ...this.props }
              >
              </ReactWebChat>
          :
            <div>Connecting to bot&hellip;</div>
        )
      }
    }
    

    Lastly, I have the bot embedded as a separate component. I'm doing some other stuff which I've cut out of this code, so you can likely pass WebChat directly into your App class or wherever you are planting it.

    import React, { Component } from 'react';
    import WebChat from './webchat';
    
    class WebChatView extends Component {
      render() {
        return (
          <div>
            <WebChat id="webchat" role="main" />
          </div>
        )
      }
    }
    

    I hacked bits out of this to simplify it for you. If something doesn't work, let me know and I'll revisit it. Otherwise, you should be good to go.

    Hope of help!