Loader icon in Bot Framework Webchat - botframework

I am using Bot Framework Webchat. There are few user related data which I am posting using back channel post activity through the store option to greet the user.
<ReactWebChat
activityMiddleware={ activityMiddleware }
directLine={ window.WebChat.createDirectLine( this.state.token ) }
store = {this.handleGetStore()}
styleOptions={styleOptions}
/>
handleGetStore returns the store data:
handleGetStore(){
const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'userDetail',
value: this.state.userDetail
}
});
}
return next(action);
});
return store;
}
When the connection initiates the loader appears.
After that there is delay of about 3-5 seconds before the welcome message appears and in the meantime the Webchat seems ready for the user.
A slight delay of 3 seconds is acceptable but quite often the delay is upto 10 seconds or more. I understand that this can be slightly improved by using the Always On feature of the App Service and scaling up the plan. Is there a way I can wait for the back channel welcome message to appear and show the loader until then?
Reference: https://github.com/microsoft/BotFramework-WebChat/pull/1866

Unfortunately, the connection status display relies on events received from DirectLineJs and Web Chat does not support customizing its behavior at the moment. That being said, there is a hacky way to accomplish what you're trying to do by dispatching pseudo DirectLine events.
Here are the steps below:
Create a flag that will indicate whether or not the bot has sent a welcome message - received_welcome_message.
When Web Chat dispatches a connection fulfilled event, check the flag
to ensure a welcome message has been received. If the bot has not
sent a welcome message, dispatch the welcome event to the bot and reset the
connection status to fulfilling.
When Web Chat receives an activity
from the bot, check if it is a welcome message. I would recommend
adding a name attribute to message on the bot side to check - await
context.sendActivity({ text: 'Welcome', name: 'welcome'}). If the
activity is a welcome message, dispatch a connection fulfilled event and set the flag to true.
For more details take a look at the code snippets below.
let received_welcome_message = false;
const store = createStore(
{},
({ dispatch}) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
if (!received_welcome_message) {
dispatch({
type: 'DIRECT_LINE/CONNECT_FULFILLING'
});
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: { name: 'webchat/join' }
});
return
}
} else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' && action.payload.activity.name === 'welcome') {
received_welcome_message = true;
dispatch({
type: 'DIRECT_LINE/CONNECT_FULFILLED',
});
}
return next(action);
}
);
Edit
A less hacky approach is to dispatch a post activity pending event when the connection to the bot is fulfilled to mimic the bot sending a welcome message. Note, that the bot is unaware of the mimicked activity. See the code snippet below.
const store = createStore(
{},
({ dispatch}) => next => action => {
console.log(action)
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'DIRECT_LINE/POST_ACTIVITY_PENDING',
meta: { method: 'keyboard' },
payload: {
activity: {
from: { role: "bot" },
text: "Welcome Message",
textFormat: "plain",
timestamp: new Date().toString(),
type: "message"
}
}
})
}
return next(action);
}
Hope this helps!

Related

How to listen for Modal submission on Slack Bolt?

I've been looking through the docs for a day now, and still can't figure out this simple question: what is the #slack/bolt ^2.3.0 method that is used to listen for a Modal submission?
const slackbot = new App({ token, signingSecret });
slackbot.shortcut(
"open_modal",
async ({ ack, body, client, context, payload }) => {
await ack();
const result = await client.views.open({ ... });
}
);
slackbot.METHOD_HERE(ID_HERE,
async ({ ack }) => {
ack();
// handle modal submitted data here...
}
);
What values take the place of METHOD_HERE and ID_HERE in the code above?
I am able to open a modal without problem via the global shortcuts menu; but can't seem to figure out how to capture user's submission of the modal.
For example, this never captures any submissions:
slackbot.view("open_modal", async ({ ack }) => {
ack();
// do things here...
});
You need to use the callback_id used when you create the Modal view:
slackbot.shortcut(
"open_modal",
async ({ ack, body, client, context, payload }) => {
await ack();
const result = await client.views.open({
trigger_id: body.trigger_id,
view: {
type: "modal",
callback_id: "YOUR_CALLBACK_ID", // <= listen for this ID
title: {
type: "plain_text",
text: "Modal Title",
},
blocks: [ ... ],
},
});
}
);
Then, to listen to submissions on the above Modal, use this:
app.view('YOUR_CALLBACK_ID', optionalMiddleWareFunction, async ({ payload }) => {
const submittedValues = payload.view.state.values
// do stuff with submittedValues
});
callback_id is the ID that you defined when creating modal view. You can see an example here.
You can read the corresponding official documentation here.

How to handle user leaving conversation

We have welcome examples using OnMembersAddedAsync method but no examples showing how to handle user leaving conversation. I tried to override OnMembersRemovedAsync but it does not seem to be invoked (at least when I use bot framework emulator).
I need to do some cleanup at the event of user leaving/left conversation.
An example or any tips would be appreciated.
Update: I'm using C# and Bot framework v4
This is going to be channel specific as it is dependent on the channel providing a feature that sends an update when the user leaves a conversation. Any other channels, you will need to research.
For Facebook, I was unable to find a scope that covers such an action. These are the available scopes which you can reference more closely here:
messages
message_deliveries
message_echoes
message_reads
messaging_account_linking
messaging_checkout_updates (beta)
messaging_game_plays
messaging_handovers
messaging_optins
messaging_payments(beta)
messaging_policy_enforcement
messaging_postbacks
messaging_pre_checkouts (beta)
messaging_referrals
standby
Web Chat, as a feature, also does not include this. However, given this is a web page, you can utilize the onbeforeunload() window function to dispatch an event. The event listener will make use of Web Chat's store to dispatch either a message or event to the bot. For the sake of clarity, I'm sending different types of data via SEND_MESSAGE and SEND_EVENT.
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => async action => {
return next( action );
};
window.addEventListener( 'sendEventActivity', ( { data } ) => {
store.dispatch({
type: 'WEB_CHAT/SEND_MESSAGE',
payload: {
text: data
}
} )
,
store.dispatch( {
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'user_event',
value: {
name: 'end_conversation',
value: 'user ended conversation'
},
text: 'The user has left the conversation.'
}
} )
} );
window.onbeforeunload = function() {
const eventSendActivity = new Event( 'sendEventActivity' );
eventSendActivity.data = 'User left conversation';
window.dispatchEvent( eventSendActivity );
}
{ type: 'message',
id: '4uPdpZhlTFfBMziBE7EmEI-f|0000004',
timestamp: 2020-01-10T18:21:26.767Z,
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: { id: 'dl_123', name: 'johndoe', role: 'user' },
conversation: { id: '4uPdpZhlTFfBMziBE7EmEI-f' },
recipient: { id: 'botberg#QaeuoeEamLg', name: 'Dungeon Runner' },
textFormat: 'plain',
locale: 'en-US',
text: 'User left conversation',
channelData:
{ clientActivityID: '15786804807910gegwkp2kai',
clientTimestamp: '2020-01-10T18:21:20.792Z' }
}
{ type: 'event',
id: '4uPdpZhlTFfBMziBE7EmEI-f|0000005',
timestamp: 2020-01-10T18:21:26.780Z,
serviceUrl: 'https://directline.botframework.com/',
channelId: 'directline',
from: { id: 'dl_123', name: 'johndoe', role: 'user' },
conversation: { id: '4uPdpZhlTFfBMziBE7EmEI-f' },
recipient: { id: 'botberg#QaeuoeEamLg', name: 'Dungeon Runner' },
locale: 'en-US',
channelData:
{ clientActivityID: '1578680480821h7kgfm9cyz',
clientTimestamp: '2020-01-10T18:21:20.821Z' },
value:
{ name: 'end_conversation', value: 'user ended conversation' },
name: 'user_event'
}
Hope of help!
Update (Aug. 6th, 2021):
As Chrome, and other browsers, disallow blocking / delaying the closing of a window during onbeforeunload(), sending an event using an event handler or listener is usually unreliable, at best. At worst, it just doesn't work.
However, there is another method that does appear work by using 'window.navigator.sendBeacon()' (providing it's supported).
sendBeacon is a low-level, simplified version of fetch that “asynchronously sends a small amount of data over HTTP to a web server”. It only sends as a POST, only takes the URL or URL + data as properties, and doesn’t wait for a response. As the docs state:
The data is sent reliably
It's sent asynchronously
It doesn't impact the loading of the next page
In testing, I have coupled it with a proactive messaging endpoint in my bot and it appears to work perfectly. (My code sample below is in JS, pulled from a project - for reference, here is the proactive messaging C# sample, available in other languages, as well).
When I navigate to another page or close the browser, sendBeacon posts the message to the endpoint creating the proactive message, which, in turn, sends an activity to the bot. When I return to the Web Chat page, the message is now visible in the chat window. Keep in mind, I also have persistence set up in Web Chat allowing me to return to a conversation previously started.
In the below image, I demo loading Web Chat, navigating to my browser’s home page, and then return – the proactive message is now visible in the chat.
(Web Chat) hosting page:
window.onbeforeunload = () => {
let body = { user: { userName: 'McUser', userId: 'abc123' } };
const headers = { type: 'application/json', 'Access-Control-Allow-Origin': '*' };
const blob = new Blob( [ JSON.stringify( body ) ], headers )
navigator.sendBeacon( 'http://localhost:3978/api/notify', blob )
}
Bot's proactive messaging endpoint:
server.post('/api/notify', async (req, res) => {
const userName = req.body.user.userName;
const userId = req.body.user.userId;
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async (turnContext) => {
await turnContext.sendActivity(`${ userName } (userId: ${ userId }) exited chat.`);
});
}
});

How to send the arguments to post back dialog

I have created a post back dialog but trying to post some arguments while calling the postback dialog in microsoft bot framework in v#4 doesn't work as expected.
I have written this code to call the post back dialog from hero card. Here i am able to call the post back but i'm not able to send values(arguments) to postback dialog.
var card1 = CardFactory.heroCard(
Name,
Address1+", "City",
CardFactory.images([Image]),
CardFactory.actions([
{
type: 'postBack',
title: Service,
value: PostBack_DIALOG,
text: 'arguments for postback dialog'
},
{
type: 'call',
title: phone ,
value: "tel:" + phoneNumber
}
])
Please suggest how to send the arguments to post back dialog in bot framework v#4
You can achieve this via sending the argument, not as part of the card, but as part of a successive sendActivities step once the hero card has rendered and the user has selected a button.
In the following code example, I have two steps. One for rendering the card and one for sending the data after the user has interacted with the card. In the first step, called postBackCardStep, I am capturing a previous value from a suggested action where the user selected either 'blue' or 'green'. That value is then passed in the Service button when that button is pressed.
In the second step, called postBackPostStep, I capture and send the user's selection in resultDetails. I also define a property, called postBackArg, and send a property vlaue for validation on the client side. I send this in a data dictionary object in a sendActivities activity. This allows me to send, as one action, both a message and the data.
async postBackCardStep ( stepContext ) {
const PostBack_DIALOG = stepContext.context.activity.text;
const reply = { type: ActivityTypes.Message };
const postBackBtn = { type: ActionTypes.PostBack, title: 'Service', value: PostBack_DIALOG }
const callBtn = { type: ActionTypes.Call, title: 'Phone', value: '123-456-7890' }
const card = CardFactory.heroCard(
'<Name>',
'<Address>',
['<link to some image>'],
[
postBackBtn,
callBtn
]
);
reply.attachments = [ card ];
await stepContext.context.sendActivity( reply );
return { status: DialogTurnStatus.waiting };
}
async postBackPostStep ( stepContext ) {
const resultDetails = stepContext.context.activity.text;
const data = { value: resultDetails, postBackArg: 'true' };
await stepContext.context.sendActivities( [
{ text: `Sending ${ resultDetails } via postBack`, type: 'message' },
{ name: 'data', type: 'event', channelData: { 'data': data } }
] );
return await stepContext.next();
}
Next, in the web chat script in the html page, I have the following code. I create a store, create and name an event, and pass the acquired data from the bot into that event. I then create an event listener that validates the data by checking if the received data is an event type and a postBackArg value of 'true' (this value should be passed from the bot as a string and validated as a string). If both checks pass, then the console message is posted with the values. The store is then passed into renderWebChat.
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => action => {
if ( action.type === 'DIRECT_LINE/INCOMING_ACTIVITY' ) {
const event = new Event('incomingActivity');
event.data = action.payload.activity;
window.dispatchEvent(event);
}
return next( action );
} );
window.addEventListener('incomingActivity', ({ data }) => {
const type = data.type;
if (type === 'event' && data.channelData.data.postBackArg === 'true') {
console.log('DATA ', data);
const resultDetails = data.channelData.data.value;
const args = data.channelData.data.postBackArg;
console.log(`Received a message from the bot (${resultDetails}) with argument value ${args}.`);
}
} );
window.WebChat.renderWebChat( {
directLine: directLine,
store
}, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
Here, you can see the data has successfully been passed from the bot to the page which, as specified in the incomingActivity event listener, only occurs if the correct data properties were passed and successfully validated.
Unless I'm much mistaken, at this point you should be good to go!
Hope of help!

BotBuilder backchannel event data into session

TLDR: How can I initialise my conversations with data sent from the backchannel and use that information throughout the conversation with a user?
Using Microsoft botbuilder, a "backchannel" mechanism is provided with which I can send data to and from the bot.
Similar to this answer I am using the backchannel method to send a conversationStarted event to the bot in order to start a conversation as you might expect.
Upon receiving the event, I start a dialog which fires off my proactive messages to the user.
bot.on('event', event => {
if (event.name === 'conversationStarted') {
bot.beginDialog(event.address, determineWhichDialogToStart(event))
}
})
I can then use middleware or event listeners to intercept this event and see its contents. Sure enough I can see the event
{ type: 'event',
name: 'conversationStarted',
text: '',
myMetaProperty: 'foobar',
attachments: [],
entities: [],
address:
{ id: '8f5d3952-df3b-4340-89b4-97360f8d4965',
channelId: 'emulator',
user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' },
conversation: { id: '4e8a943d-6a45-41f2-aa11-6671cc1ca4f3' },
bot: { id: 'bot' },
serviceUrl: 'http://localhost:3010/' },
source: 'emulator',
agent: 'botbuilder',
user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' } }
But the events I can listen to to see this event, don't have access to the session because the message hasn't yet been dispatched to one it seems.
If I use the botbuilder middleware, or the routing event, I then see that it's been turned into a message rather than an event, and has lost my meta data passed to the event. (see myMetaProperty)
{ type: 'message',
agent: 'botbuilder',
source: 'emulator',
sourceEvent: {},
address:
{ id: '8f5d3952-df3b-4340-89b4-97360f8d4965',
channelId: 'emulator',
user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' },
conversation: { id: '4e8a943d-6a45-41f2-aa11-6671cc1ca4f3' },
bot: { id: 'bot' },
serviceUrl: 'http://localhost:3010/' },
text: '',
user: { id: '33abc508-86d7-49c1-aa68-00a3491fda12' } }
I tried getting access to the session in my receive event/middleware per the comment here on Github but whilst I get access to a session, it's not the session.
bot.on('receive', event => {
bot.loadSessionWithoutDispatching(event.address, (error, session) => {
session.conversationData.myMetaProperty = event.myMetaProperty
})
})
This actually ends up starting a new session - having looked into the loadSession/loadSessionWithoutDispatching, they both lead to startSession and therefore me adding data to the session is lost when i try to use it in a dialog
bot.dialog('example', [
(session, args, next) => {
console.log(session.conversationData.myMetaProperty) // nope
console.log(session.cantUseThisEither) // nope
session.send('foo')
next()
}
])
Just to reiterate the question now that there's been a bit of background, how can I initialise my conversations with data sent from the backchannel and use that information throughout the conversation with a user?
I ended up starting a generic dialog which sets up my session and then moves onto the root dialog.
bot.on('event', event => {
if (event.name === 'conversationStarted') {
bot.beginDialog(event.address, 'initialise-conversation', {
myMetaProperty: event.myMetaProperty
});
}
});
bot.dialog('initialise-conversation', (session, args, next) => {
session.conversationData.myMetaProperty = args.myMetaProperty
session.beginDialog('/');
});
Then whenever I need myMetaProperty throughout my conversation I can get it from session.conversationData
--
In response to the comment below, here's some additional information which may help someone.
The shape of the event you receive on the bot must contain an address obviously. For example here's the one I send to my bot.
{ myMetaProperty: 'foo',
name: 'conversationStarted',
type: 'event',
address:
{ id: '4q5aaBw77aNGGvrpkeji8A',
channelId: 'custom-directline',
user: { id: 'c214imy6PCJZY4G1x2AXSD' },
conversation: { id: 'bGvMBjEmFTg3DMxsbvJGjH' },
bot: { id: 'bot' },
serviceUrl: 'https://localhost:3010/' },
source: 'custom-directline',
agent: 'botbuilder',
user: { id: 'c214imy6PCJZY4G1x2AXSD' } }
Start by ensuring that you're sending the address. The key bits of information for routing messages back to an individual are the user and conversation objects.
For example, if you're sending information using the backchannel of the botframework webchat you'd do it as follows.
var user = { id: uuidv4() };
var botConnection = new BotChat.DirectLine({...});
botConnection.postActivity({
type: 'event',
from: user,
name: 'conversationStarted',
myMetaProperty: 'foo',
}).subscribe();
BotChat.App({
user: user,
botConnection: botConnection,
}, document.getElementById("bot"));
This will be picked up by the event listener described above.
Another thing to note is that the emulator (and possibly other clients/channels send a conversationUpdate event which I just passed onto my conversationStarted event when I was using it. Here's the listener to do that for reference in case it's useful.
bot.on('conversationUpdate', message => {
message.membersAdded &&
message.membersAdded
.filter(identity => identity.id === message.address.bot.id)
.forEach(identity => bot.emit('conversationStarted', message));
});
You can pass data via beginDialog(address: IAddress, dialogId: string, dialogArgs?: any, done?: (err: Error) => void): void;, you can pass the data you want to send as the third parameter in this function.
You can refer to the source code at https://github.com/Microsoft/BotBuilder/blob/master/Node/core/src/bots/UniversalBot.ts#L243 for more details.

How can I let users share their location in Bot Framework webchat channel?

I read that you can share location with the backchannel.
I was thinking to use this code to talk directly to the bot:
function postButtonMessage() {
botConnection.postActivity({
entities:{type: "ClientCapabilities", requiresBotState: true, supportsTts: true, supportsListening: true},
from: { id: 'userid', name: 'username' },
name: 'botname',
type: 'message',
value: 'Hi',
textFormat: 'plain'
})
.subscribe(function (id) {
console.log('"buttonClicked" sent');
});
};
But I get an error saying "bad gateway 502", but when I talk through the web channel windows it works perfectly so I know that the direct line key is configured correctly. And when I use the type: event instead of message it works fine and I don't have problems, so I am confused about that.
(Question originally asked at https://github.com/Microsoft/BotFramework-WebChat/issues/778#)
There are two approaches here.
One is to use the backchannel to send an event activity to the bot with whatever data you like. You could do this when a button is clicked, at regular intervals, or whenever the location changes.
var dl = new BotChat.DirectLine({secret});
BotChat.App({
botConnection: dl,
// other Chat props go here
});
function postButtonMessage() {
dl.postActivity({
from: { id: 'userid', name: 'username' },
type: 'event',
name: 'location',
value: { /* location goes here */ }
})
.subscribe(id => {
console.log('"buttonClicked" sent');
});
};
The other is to use client middleware to send that data with every message, by intercepting and modifying each message as it goes out. The advantage to this approach is that each message is 'tagged' with its location. The disadvantage is that you only get location updates when the user sends a message.
var dl = new BotChat.DirectLine({secret});
BotChat.App({
botConnection: {
... dl,
postActivity: activity => dl.postActivity({
... activity,
channelData: { location: /* location goes here */ }
})
},
// other Chat props go here
});
And of course you could do both!

Resources