Is there a way to persist chat history in MS Bot Framework Web Chat after a page reload/navigation to another page?
I'm trying to persist the conversation a user has had with the bot during page reloads and navigation to other pages on the site the bot is linked to.
Currently these actions close the bot window and restart the conversation entirely, to the point that the bot's welcome message is triggered again.
The bot in question is embedded in the webpage following the instructions from the docs: https://docs.microsoft.com/bs-latn-ba/azure/bot-service/bot-service-channel-connect-webchat?view=azure-bot-service-4.0
I have read other articles that have used the conversationId to maintain the chat history between page loads, though this was for the DirectLine channel. As well as some other articles that suggested persisting the conversation in a database and passing the messages back into the chat window. Though this seems doesn't seem the best way to go about it.
I attempted to pass conversationId into the iframe but it did not work. Is there a way persist the conversation by passing the conversationId into the iframe?
This is the code for showing the chatbot in an iframe:
<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>
This is my attempt at passing conversationId as a parameter:
<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE&conversationId?=THE_CONVERSATIONID_VALUE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>
I expect the chat window to be populated with the conversation the user previously had, what I am getting is the conversation resets and no history is maintained.
If you are looking to do any sort of web chat customization, then I would highly recommend you steer away from using the Web Chat channel <iframe>
option. It is useful if you need a simple plugin component, but it doesn't offer anywhere near the number of customization options that BotFramework-WebChat offers.
If you will consider using the v4 react-based Web Chat offering (referenced in the link above), then the following example will provide you with the functionality you are seeking.
Please note, for simplicity, I'm saving the conversationId in session storage.
Also, I'm generating a token by making an API call against a locally run direct line endpoint. I've included code at the end for doing the same. You could pass in your direct line secret against the directline/tokens/generate
endpoint in the html file, however that is highly discouraged for security reasons.
Lastly, the watermark
property used in the createDirectLine() method specifies the number of past activities to display (messages, cards, etc.).
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>WebChat</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html,
body {
height: 100%;
width: 100%;
margin: 0;
}
#webchat {
height: 100%;
width: 40%;
}
#webchat>* {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="webchat" role="main"></div>
<script type="text/javascript"
src="https://unpkg.com/markdown-it/dist/markdown-it.min.js"></script>
<script
src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
<script>
( async function () {
let { token, conversationId } = sessionStorage;
if (!token) {
const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
const { token: directLineToken } = await res.json();
sessionStorage['token'] = directLineToken;
token = directLineToken;
}
if (conversationId) {
const res = await fetch(`https://directline.botframework.com/v3/directline/conversations/${ conversationId }`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${ token }`,
},
});
let { conversationId } = await res.json();
sessionStorage['conversationId'] = conversationId;
}
const directLine = createDirectLine({
token,
webSockets: true,
watermark: 10
});
window.WebChat.renderWebChat( {
directLine: directLine,
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>
</body>
</html>
Here is the code for generating the token. I have this appended to the end of my index.js file in my bot. You can also run this as a separate project.
As I run my bot locally, the endpoint becomes available. You should be able to do something similar if you are running a C# bot. The port used here should be the same port referenced in the above directline/token
call.
The directLineSecret
is stored and accessed from a .env file.
/**
* Creates token server
*/
const bodyParser = require('body-parser');
const request = require('request');
const corsMiddleware = require('restify-cors-middleware');
const cors = corsMiddleware({
origins: ['*']
});
// Create server.
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');
}
});
});
Hope of help!
Re-updated - 8/6/2021
The script in the HTML above, technically, works though its implementation is not really clear. I've provided this snippet which simplifies the code.
Also, note the first line below: The CDN has since changed slightly. The current stable version pulls from latest
, not master
.
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
( async function () {
let { token, conversation_Id } = sessionStorage;
if ( !token ) {
const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
const { token: directLineToken, conversationId } = await res.json();
sessionStorage[ 'token' ] = directLineToken;
sessionStorage[ 'conversation_Id' ] = conversationId
token = directLineToken;
}
const directLine = createDirectLine( {
token
} );
window.WebChat.renderWebChat( {
directLine: directLine,
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
} )().catch( err => console.error( err ) );
</script>