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(`
${ 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>