【问题标题】:How should a botframework webchat conversation be maintained for over an hour?一个 botframework 网络聊天对话应该如何维持一个多小时?
【发布时间】:2020-02-05 22:58:45
【问题描述】:

我查看了 botframework-webchat 的文档,但找不到任何关于如何正确处理超过 1 小时的对话的文档。如果网页在后台长时间处于空闲状态,则最有可能发生这种情况。

只要网络聊天在网页上保持活动状态,就会保持直接连接。页面刷新后出现问题。

最初的短期解决方案是将相关对话信息存储在会话存储中,例如令牌。问题是对话的令牌每 15 分钟刷新一次。必须检索刷新的令牌才能在页面刷新时保持对话。

我确信存在一个 hacky 解决方法,用于使用事件回调从直达线客户端对象检索刷新的令牌。

理想情况下,我正在寻找一种干净的框架设计方法来处理这种情况。

虽然可行的解决方案总比没有解决方案要好。

相关链接: https://github.com/microsoft/BotFramework-WebChat

谢谢。

【问题讨论】:

  • 接受/投票支持更大的 Stack Overflow 社区和任何有类似问题的人。如果您觉得我的回答足够,请“接受”并点赞。如果没有,请告诉我我还能提供哪些帮助!

标签: botframework direct-line-botframework web-chat


【解决方案1】:

您可以通过在客户端实现 cookie 来实现此目的。您可以将 cookie 过期时间设置为 60 分钟,并且可以使用水印使您的聊天持续一小时。 Passing cookie to and from Bot Service.

【讨论】:

    【解决方案2】:

    您可以通过设置“令牌”服务器来实现此目的。在下面的示例中,我在开发/测试我的机器人时在本地运行它。

    你可以使用任何你想要的包,但是我选择了“restify”,因为我将它包含在我的机器人的index.js 文件中。我只需创建一个新服务器,与机器人服务器分开,并为其分配一个自己的端口。然后,当我运行机器人时,它也会自动运行。将您的 appIds、appPasswords 和 secrets 放入 .env 文件中。

    然后,在托管机器人的网页中,只需调用端点来获取令牌。您还会注意到代码会检查令牌是否已存在。如果是这样,那么它会设置一个带有计时器的间隔来刷新令牌。间隔时间为 1500000 毫秒,设置为在令牌过期之前运行(1800000 毫秒)。因此,令牌总是被刷新。 (只是突然出现在我的脑海中:如果用户导航离开,记录剩余时间和经过的时间量可能很聪明,以便将间隔设置为准确的数字,以便它在应该时刷新。否则,间隔将重置到期时间要少得多。)

    另外,我包含了一些注释掉的代码。这是如果您希望您的对话在页面刷新或用户导航离开并返回之后持续存在。这样,当前的对话就不会丢失,并且令牌仍然有效。根据您的需要,可能不是必需的,但适用于上述情况。

    希望有帮助!


    令牌服务器

    /**
     * Creates token server
     */
    const path = require('path');
    const restify = require('restify');
    const request = require('request');
    const bodyParser = require('body-parser');
    
    const ENV_FILE = path.join(__dirname, '.env');
    require('dotenv').config({ path: ENV_FILE });
    
    const corsToken = corsMiddleware({
      origins: [ '*' ]
    });
    
    // Create HTTP server.
    let server = restify.createServer();
    server.pre(cors.preflight);
    server.use(cors.actual);
    server.use(bodyParser.json({
      extended: false
    }));
    
    server.listen(process.env.port || process.env.PORT || 3500, function() {
      console.log(`\n${ server.name } listening to ${ server.url }.`);
    });
    
    // Listen for incoming requests.
    server.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) => {
        // response.statusCode = 400;
        if (!error && response.statusCode < 300) {
          res.send(body);
          console.log('Someone requested a token...');
        } else if (response.statusCode === 400) {
          res.send(400);
        } else {
          res.status(500);
          res.send('Call to retrieve token from DirectLine failed');
        }
      });
    });
    
    // Listen for incoming requests.
    server.post('/directline/refresh', (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/refresh',
        headers: {
          'Authorization': `Bearer ${ req.body.token }`,
          'Content-Type': 'application/json'
        },
        json: {
          user: {
            ID: userId
          }
        }
      };
      request.post(options, (error, response, body) => {
        if (!error && response.statusCode < 300) {
          res.send(body);
          console.log('Someone refreshed a token...');
        } else {
          res.status(500);
          res.send('Call to retrieve token from DirectLine failed');
        }
      });
    });
    

    webchat.html

    <script>
      (async function () {
        let { token, conversationId } = sessionStorage;
    
        [...]
    
        if ( !token || errorCode === "TokenExpired" ) {
          let res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
    
          const { token: directLineToken, conversationId, error } = await res.json();
          // sessionStorage[ 'token' ] = directLineToken;
          // sessionStorage[ 'conversationId' ] = conversationId;
          token = directLineToken;
        }
    
        if (token) {
          await setInterval(async () => {
            let res = await fetch( 'http://localhost:3500/directline/refresh', {
              method: 'POST',
              headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
              },
              body: JSON.stringify( { token: token } )
            } );
            const { token: directLineToken, conversationId } = await res.json();
            // sessionStorage[ 'token' ] = directLineToken;
            // sessionStorage[ 'conversationId' ] = conversationId;
            token = directLineToken;
          }, 1500000)
        }
    
        // if ( conversationId ) {
        //   let res = await fetch( `https://webchat.botframework.com/v3/directline/conversations/${ conversationId }`, {
        //     method: 'GET',
        //     headers: {
        //       'Authorization': `Bearer ${ token }`,
        //       'Content-Type': 'application/json'
        //     },
        //   } );
    
        //   const { conversationId: conversation_Id, error } = await res.json();
        //   if(error) {
        //     console.log(error.code)
        //     errorCode = error.code;
        //   }
        //   conversationId = conversation_Id;
        // }
    
        [...]
    
        window.ReactDOM.render(
          <ReactWebChat
            directLine={ window.WebChat.createDirectLine({ token });
          />
        ),
        document.getElementById( 'webchat' );
      });
    </script>
    

    【讨论】:

    • 顺便说一句,要参考的文档是here
    • 如果您的网页的生存时间超过 15 分钟,那么 react webchat 组件将在内部刷新令牌。此示例仅说明直接刷新令牌。上面的答案不会从网络聊天组件中检索更新的令牌。存储在 sessionstorage 中的令牌已在 15 分钟后被网络聊天组件刷新。这将导致 sessionStorage 中的原始令牌失效。
    • 即使存储在会话存储中的令牌已更新。网络聊天组件仍在内部自动刷新令牌。这将导致多个同时调用来刷新令牌。同一个token可以多次刷新吗?我假设每个对话都将令牌维护在单个线性链中。
    • 我的情况和这里介绍的差不多。 github.com/microsoft/… 不同的是连接不会丢失。我只需要来自内部刷新的更新令牌。
    • 这接近于我发现可行但不完全的解决方案。
    【解决方案3】:

    解决方案涉及将会话 ID 存储在会话存储中而不是令牌中。刷新页面后,将检索新令牌。

    https://github.com/microsoft/BotFramework-WebChat/issues/2899

    https://github.com/microsoft/BotFramework-WebChat/issues/2396#issuecomment-530931579

    此解决方案有效,但不是最佳的。更好的解决方案是检索直达对象中的活动令牌并将其存储在会话存储中。问题是,目前尚不存在从直线对象中检索刷新令牌的干净方法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-28
      相关资源
      最近更新 更多