1

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

zape
  • 57
  • 6
  • Can you provide code for what you have tried (you can edit your original post)? And, can you elaborate on what you action(s) you would like to take place when a user leaves? – Steven Kanberg Dec 27 '19 at 17:59
  • Also, which SDK are you using (v3 or v4, C# or Node)? – Steven Kanberg Dec 27 '19 at 18:02
  • I don't have any code yet. I simply overridden OnMembersRemovedAsync method and trying to see when it is invoked. So far I only managed to do that by sending a conversation update via postman like described here: https://github.com/microsoft/BotFramework-Emulator/issues/2027#issuecomment-569131238 – zape Dec 27 '19 at 21:05
  • I'm trying to figure out if this event is being sent when a user closes a web chat or Facebook conversation. So I can handle that event. I plan to have human agents stepping in when bot can't help and they would need to know if user is still there or closed webchat or facebook chat. – zape Dec 27 '19 at 21:08

1 Answers1

3

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.

enter image description here

(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.`);
      });
    }
  });
Steven Kanberg
  • 6,078
  • 2
  • 16
  • 35
  • Is there a schema for the event payload? Why name/value are nested like this: payload: { name: 'user_event', value: { name: 'end_conversation', value: 'user ended conversation' }? What should I set to outer and inner "name" if I were to send my custom event? – zape Feb 04 '20 at 21:22
  • how do I receive this event form the bot side? I overridden the OnEventActivityAsync method but it's not firing. – zape Feb 18 '20 at 14:34
  • Apologies, @zape. I didn't see your comments. Here's a list of available dispatch [actions](https://github.com/microsoft/BotFramework-WebChat/tree/master/packages/core/src/actions), including `SEND_EVENT`. The schema is detailed inside each. The payload is nested the way it is because that is how the Web Chat developers coded it. As for any naming, that is entirely up to you. Follow whatever convention you already have going. – Steven Kanberg Feb 20 '20 at 18:47
  • Regarding the bot receiving the activity, it can be picked up by an activity handler. These are located in your [bot].cs file and accessed via the `ActivityHandler` class. There you will likely find `OnTurnAsync()` and/or `OnMessageAsync()`. There are others that can be added to better fit your need. See the list of available handlers [here](https://learn.microsoft.com/en-us/dotnet/api/microsoft.bot.builder.activityhandler?view=botbuilder-dotnet-stable). – Steven Kanberg Feb 20 '20 at 18:51
  • 1
    store must be passed over to WebChat.createDirectLine in order for this to work. Took a while to figure this out – zape Feb 21 '20 at 19:10
  • the event is dispatched, but the window closes before the event caught by the listener – CSharpMinor Jul 02 '21 at 19:29
  • @CSharpMinor, please see the update I provided in my original answer. – Steven Kanberg Aug 06 '21 at 23:39
  • @StevenKanberg - how reliably does onbeforeunload() fire though? I thought the whole problem was that the function itself won't reliably execute if the user force closes a browser (in desktop or mobile for that matter). – user3191449 Jan 05 '22 at 16:52
  • @user3191449, I didn't perform _extensive_ testing of onbeforeunload() + sendBeacon(). I did perform a variety of tests, including force closing, iterating thru those a number of times and my experience was never negatively impacted. That's about the most I can say on that. – Steven Kanberg Jan 11 '22 at 18:30