botframework 网络聊天对话应该如何维持一个多小时?

How should a botframework webchat conversation be maintained for over an hour?

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

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

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

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

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

尽管可行的解决方案总比没有解决方案好。

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

谢谢。

您可以通过设置 "token" 服务器来实现。在下面的示例中,当我是 developing/testing 我的机器人时,我 运行 在本地。

您可以使用任何您想要的包,但我选择了 "restify",因为我将它包含在我的机器人的 index.js 文件中。我只是创建了一个新服务器,与机器人的服务器分开,并为其分配了一个自己的端口。然后,当我 运行 机器人时,它也会自动 运行。将您的 appId、appPassword 和机密放入 .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>

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

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

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

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

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