Botframework-webchat: How to send Welcome Message in Web Chat

Created on 25 Jun 2019  路  22Comments  路  Source: microsoft/BotFramework-WebChat

Flow

ASP.NET Core MVC View [loads webchat] -> Controller gets token generated -> Bot Channels Registration -> Bot service

Though followed backchannel sample, it does not get the welcome message. The same backchannel works for simple html page, but not Razor based view page.

Any thoughts what could be missing here?

View page (token is generated on server side)

@model EchoAuthBot.ClientApp.ChatConfig;

@{
    ViewData["Title"] = "Index";
}

<!DOCTYPE html>
<html>
<body>
    <h1>Index</h1>

    <div id="webchat" role="main"></div>
    @*<script src="https://cdn.botframework.com/botframework-webchat/master/webchat-es5.js"></script>*@
    <script src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
    <script>
        // Get welcome message
        // We are using a customized store to add hooks to connect event
        const store = window.WebChat.createStore({}, ({ dispatch }) => next => action => {
            //console.log(action);
            if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
                dispatch({
                    type: 'WEB_CHAT/SEND_EVENT',
                    payload: {
                        name: 'webchat/join',
                        value: { language: window.navigator.language }
                    }
                });
            }

            return next(action);
        });

        // Set the StyleOptions for avatar
        const styleOptions = {
            botAvatarInitials: 'WC',
            userAvatarInitials: 'WW'
        };

        // Render the webchat control
        window.WebChat.renderWebChat({
            directLine: window.WebChat.createDirectLine({ token: `@Model.Token.ToString()` }),
            store,
            userID: `@Model.UserId.ToString()`,
            username: 'Web Chat User',
            locale: 'en-US',
            styleOptions
        }, document.getElementById('webchat'));
        document.querySelector('#webchat > *').focus();
    </script>
</body>
</html>
Bot Services Question customer-replied-to customer-reported

Most helpful comment

Here is how Welcome Messages in Web Chat should be handled with the updates to the Direct Line Connector Service.

Web Chat Welcome Messages

Channels generally send two conversation update events when the conversation is initialized - the first for the bot and another for the user. The second conversation update - the event for the user - is intended to trigger the welcome message. At the moment, Web Chat has two different welcome message scenarios that are slightly different from other channels and based on how the developer generates the Direct Line token.

Tokens with User IDs

The first scenario is dependent on the token request including a user ID. If the developer includes a user ID when generating the token, Direct Line will only send one conversation update event to the bot that includes two user IDs in the activity's membersAdded property - one for the bot and one for the user. Following this configuration should trigger the traditional welcome message in the onMembersAdded handler before the user messages the bot.

In the example below, the user ID is added to the token request and the welcome message is sent from the onMembersAdded handler.

Web Chat

(async function () {
  // Note, for the simplicity of this example, we are generating the Direct Line token on client side;
  // however, this is not a recommended practice and you should create and manage your tokens from the server. 
  // You should never put the Direct Line secret in the browser or client app.
  // https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
  const secret = '<DIRECT_LINE_SECRET> | <WEB_CHAT_SECRET>';
  const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', { 
    body: JSON.stringify({ user: { id: 'dl_user_id', name: 'username' }}),
    headers: {
      Authorization: `Bearer ${secret}`,
      'Content-type': 'application/json'
    },
    method: 'POST',
  });
  const { token } = await res.json();

  window.WebChat.renderWebChat({
    directLine: window.WebChat.createDirectLine({ token })
  }, document.getElementById('webchat'));

  document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));

Bot Framework SDK v4 (Node.js)

this.onMembersAdded(async (context, next) => {
  const { membersAdded } = context.activity;

  for (let member of membersAdded) {
    if (member.id !== context.activity.recipient.id) {
      await context.sendActivity("Welcome Message from `onMembersAdded` handler!");
    }
  }
  await next();
});

Tokens, User IDs, and iFrames

To achieve this in an iFrame of Web Chat, retrieve your token with user ID as described above and pass the token within the src attribute of the iFrame:
<iframe src='https://webchat.botframework.com/embed/YOUR_BOT_HERE?t=YOUR_TOKEN_HERE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>

Secrets and Tokens without User IDs

Alternatively, conversations created with tokens that do not include a user ID send two conversation update events. Direct Line sends the first conversation update - the one for the bot - when the connection with the bot is established. Direct Line sends the second conversation update for the user after they send their first message.

Generally, users anticipate the bot to send a welcome message before they send a message. To do this, you can dispatch a backchannel welcome event from Web Chat's store middleware when the Direct Line connection is established. Then in the onEvent handler, you can send a welcome message. Note, in the onMembersAdded handler you should check which channel is sending the event before sending the welcome message. If the channel id is "webchat" or "directline" you should not send the traditional welcome message to avoid sending multiple welcome messages.

Web Chat

(async function () {
  // Note, for the simplicity of this example, we are generating the Direct Line token on client side;
  // however, this is not a recommended practice and you should create and manage your tokens from the server. 
  // You should never put the Direct Line secret in the browser or client app.
  // https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
  const secret = '<DIRECT_LINE_SECRET> | <WEB_CHAT_SECRET>';
  const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', { 
    headers: {
      Authorization: `Bearer ${secret}`,
    },
    method: 'POST'
  });
  const { token } = await res.json();

  const store = window.WebChat.createStore(
    {},
    ({ dispatch }) => next => action => {
      if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
        dispatch({
          type: 'WEB_CHAT/SEND_EVENT',
          payload: {
            name: 'webchat/join',
          }
        });
      }
      return next(action);
    }
  );

  window.WebChat.renderWebChat({
    directLine: window.WebChat.createDirectLine({ token }),
    store
  }, document.getElementById('webchat'));

  document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));

Bot Framework SDK v4 (Node.js)

this.onEvent(async (context, next) => {
  if (context.activity.name === 'webchat/join') {
    await context.sendActivity('Back Channel Welcome Message!');
  }
  await next();
});

this.onMembersAdded(async (context, next) => {
  const { channelId, membersAdded } = context.activity;

  if (channelId !== 'directline' && channelId !== 'webchat') {
    for (let member of membersAdded) {
      if (member.id !== context.activity.recipient.id) {
        await context.sendActivity("Welcome Message from `onMembersAdded` handler!");
      }
    }
  }
  await next();
});

For more details regarding backchannel welcome events in Web Chat, take a look at this sample.

Additional Context

All 22 comments

Everything with your Web Chat configuration looks fine. Do you have an onEventAsync handler set up on your bot?

protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
    if (turnContext.Activity.Name == "webchat/join") {
      await turnContext.SendActivityAsync("Got webchat/join event.");
    }
}

@tdurnford - I do not have that kind of logic written on bot service side. I assume that kind of API needs to be added on bot service side. Is it correct? Without that OnEventActivityAsync API, how does the same flow work with a plain html page, but not ASP.NET Core MVC based view page? Any comments?

Correct, you would need to handle event activities on the bot server side. Are you just running the the Backchannel Welcome Event sample? If so, that sample is connected to MockBot which handles the welcome event.

On my service end, I have OnMembersAddedAsync API that handles the welcome message part. This is actually triggered for plain vanilla html to bot service welcome message. However, the same is not triggered for a View page. Is it an anomaly?

Could you add your code for the OnMembersAddedAsync handler? It seems like an abnormality since the conversation update for the user is not triggered until the user messages the bot.

Here is how the method looks like:

  protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
        {
            foreach (var member in turnContext.Activity.MembersAdded)
            {
                if (member.Id != turnContext.Activity.Recipient.Id)
                {
                    await turnContext.SendActivityAsync(MessageFactory.Text("Welcome to AuthenticationBot. Type anything to get logged in. Type 'logout' to sign-out."), cancellationToken);
                }
            }
        }

That looks fine. Typically, the channel sends two conversation update events when the conversation is initialized - one for the bot and another for the user. The second one - the event for the user - is intended to trigger the welcome message.

Unlike some other channels, Web Chat waits to send the second conversation update event until after the user messages the bot. Evidently, the welcome message won't be sent until after the first message. To workaround this issue, developers can send a back channel welcome event event to the bot when the DirectLine connection is established and send a welcome message from the onEventAsync handler.

In the case you mentioned above, is the bot sending another welcome message after the user messages the bot? The first conversation update might be unintentionally triggering the welcome message.

There appears to be some discrepancy in how conversation update events are sent depending on which secret you are using. If you are using a DirectLine Secret, the conversation update is only being sent when the bot joins the conversation. However, if you are using a Web Chat secret, it sends both of the conversation update events. Can you check that you are using a Web Chat secret for both the simple html page and the Razor based view page?

So two questions. Are you using DirectLine enhanced authentication and are you adding the user id to either of your post requests when you exchange the secret for a token? If so, try removing the body from the request temporarily. We believe adding the user id to the request might be causing an issue with the conversation updates. Removing the user id is to test our hypothesis - not a solution.

So two questions. Are you using DirectLine enhanced authentication and are you adding the user id to either of your post requests when you exchange the secret for a token? If so, try removing the body from the request temporarily. We believe adding the user id to the request might be causing an issue with the conversation updates. Removing the user id is to test our hypothesis - not a solution.

Yes, on my Bot Channels Registration page, enhanced authentication is ON with trusted origins test - for ngrok URI, and directline.botframework.com. Added and removed the userID - however, there's no impact! It seems "OnMembersAddedAsync" API is not being hit .. so, not getting the welcome message.

What could be next course on this then?

There appears to be some discrepancy in how conversation update events are sent depending on which secret you are using. If you are using a DirectLine Secret, the conversation update is only being sent when the bot joins the conversation. However, if you are using a Web Chat secret, it sends both of the conversation update events. Can you check that you are using a Web Chat secret for both the simple html page and the Razor based view page?

For simple html page, using "webchat" secret key. However, for view page, using "DirectLine" secret key.

That looks fine. Typically, the channel sends two conversation update events when the conversation is initialized - one for the bot and another for the user. The second one - the event for the user - is intended to trigger the welcome message.

Unlike some other channels, Web Chat waits to send the second conversation update event until after the user messages the bot. Evidently, the welcome message won't be sent until after the first message. To workaround this issue, developers can send a back channel welcome event event to the bot when the DirectLine connection is established and send a welcome message from the onEventAsync handler.

In the case you mentioned above, is the bot sending another welcome message after the user messages the bot? The first conversation update might be unintentionally triggering the welcome message.

For view page case, not able to hit OnMembersAddedAsync API, but can hit from a simple html page.

Can you disable Enhanced Authentication in Azure - it May take up to 15 minutes for the change to take effect - remove the user id from the body of the request, request the token using a DirectLine secret, and see if onMembersAddedAsync is being triggered after you message the bot? In a perfect world, you should see the welcome message sent after the initial message.

We believe the issue is surrounding the user id being added to the token, and our development team is working to resolve the issue.

If you could provide your bot id, bot handle, and a conversation id for a conversation where the welcome message is not being triggered that would be helpful.

Can you disable Enhanced Authentication in Azure - it May take up to 15 minutes for the change to take effect - remove the user id from the body of the request, request the token using a DirectLine secret, and see if onMembersAddedAsync is being triggered after you message the bot? In a perfect world, you should see the welcome message sent after the initial message.

We believe the issue is surrounding the user id being added to the token, and our development team is working to resolve the issue.

If you could provide your bot id, bot handle, and a conversation id for a conversation where the welcome message is not being triggered that would be helpful.

Disabled enhanced auth in DirectLine - helped me get the welcome message, i.e. OnMembersAddedAsync triggered. Got it triggered with userid and without userid. It is not userid related. It is definitely something else - you could quickly create a test flow and observe the same behavior for sure.

bot id: channelsregwithaad (it's Bot Channels Registration)
OnMembersAddedAsync is triggered from View page, and "conversationId": "1TsBAIx1zpwIfKuT4tbBwM-k".
OnMembersAddedAsync is triggered from View page, and "conversationId": "HmIO7PSmlom8GOnLJejjWU-k". (i.e. enhanced auth added)

Were you using the a Web Chat or a DirectLine secret? In this case, it does make a difference.

Were you using the a Web Chat or a DirectLine secret? In this case, it does make a difference.

It's DirectLine secret that being used in controller, and generated token passed to view page.

Here is how Welcome Messages in Web Chat should be handled with the updates to the Direct Line Connector Service.

Web Chat Welcome Messages

Channels generally send two conversation update events when the conversation is initialized - the first for the bot and another for the user. The second conversation update - the event for the user - is intended to trigger the welcome message. At the moment, Web Chat has two different welcome message scenarios that are slightly different from other channels and based on how the developer generates the Direct Line token.

Tokens with User IDs

The first scenario is dependent on the token request including a user ID. If the developer includes a user ID when generating the token, Direct Line will only send one conversation update event to the bot that includes two user IDs in the activity's membersAdded property - one for the bot and one for the user. Following this configuration should trigger the traditional welcome message in the onMembersAdded handler before the user messages the bot.

In the example below, the user ID is added to the token request and the welcome message is sent from the onMembersAdded handler.

Web Chat

(async function () {
  // Note, for the simplicity of this example, we are generating the Direct Line token on client side;
  // however, this is not a recommended practice and you should create and manage your tokens from the server. 
  // You should never put the Direct Line secret in the browser or client app.
  // https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
  const secret = '<DIRECT_LINE_SECRET> | <WEB_CHAT_SECRET>';
  const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', { 
    body: JSON.stringify({ user: { id: 'dl_user_id', name: 'username' }}),
    headers: {
      Authorization: `Bearer ${secret}`,
      'Content-type': 'application/json'
    },
    method: 'POST',
  });
  const { token } = await res.json();

  window.WebChat.renderWebChat({
    directLine: window.WebChat.createDirectLine({ token })
  }, document.getElementById('webchat'));

  document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));

Bot Framework SDK v4 (Node.js)

this.onMembersAdded(async (context, next) => {
  const { membersAdded } = context.activity;

  for (let member of membersAdded) {
    if (member.id !== context.activity.recipient.id) {
      await context.sendActivity("Welcome Message from `onMembersAdded` handler!");
    }
  }
  await next();
});

Tokens, User IDs, and iFrames

To achieve this in an iFrame of Web Chat, retrieve your token with user ID as described above and pass the token within the src attribute of the iFrame:
<iframe src='https://webchat.botframework.com/embed/YOUR_BOT_HERE?t=YOUR_TOKEN_HERE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>

Secrets and Tokens without User IDs

Alternatively, conversations created with tokens that do not include a user ID send two conversation update events. Direct Line sends the first conversation update - the one for the bot - when the connection with the bot is established. Direct Line sends the second conversation update for the user after they send their first message.

Generally, users anticipate the bot to send a welcome message before they send a message. To do this, you can dispatch a backchannel welcome event from Web Chat's store middleware when the Direct Line connection is established. Then in the onEvent handler, you can send a welcome message. Note, in the onMembersAdded handler you should check which channel is sending the event before sending the welcome message. If the channel id is "webchat" or "directline" you should not send the traditional welcome message to avoid sending multiple welcome messages.

Web Chat

(async function () {
  // Note, for the simplicity of this example, we are generating the Direct Line token on client side;
  // however, this is not a recommended practice and you should create and manage your tokens from the server. 
  // You should never put the Direct Line secret in the browser or client app.
  // https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
  const secret = '<DIRECT_LINE_SECRET> | <WEB_CHAT_SECRET>';
  const res = await fetch('https://directline.botframework.com/v3/directline/tokens/generate', { 
    headers: {
      Authorization: `Bearer ${secret}`,
    },
    method: 'POST'
  });
  const { token } = await res.json();

  const store = window.WebChat.createStore(
    {},
    ({ dispatch }) => next => action => {
      if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
        dispatch({
          type: 'WEB_CHAT/SEND_EVENT',
          payload: {
            name: 'webchat/join',
          }
        });
      }
      return next(action);
    }
  );

  window.WebChat.renderWebChat({
    directLine: window.WebChat.createDirectLine({ token }),
    store
  }, document.getElementById('webchat'));

  document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));

Bot Framework SDK v4 (Node.js)

this.onEvent(async (context, next) => {
  if (context.activity.name === 'webchat/join') {
    await context.sendActivity('Back Channel Welcome Message!');
  }
  await next();
});

this.onMembersAdded(async (context, next) => {
  const { channelId, membersAdded } = context.activity;

  if (channelId !== 'directline' && channelId !== 'webchat') {
    for (let member of membersAdded) {
      if (member.id !== context.activity.recipient.id) {
        await context.sendActivity("Welcome Message from `onMembersAdded` handler!");
      }
    }
  }
  await next();
});

For more details regarding backchannel welcome events in Web Chat, take a look at this sample.

Additional Context

I'm trying to implement this to pass parameters when a chat session starts.

const store = window.WebChat.createStore(
{},
function() {
return function(next) {
return function(action) {
if (action.type === 'DIRECT_LINE/POST_ACTIVITY') { // tried various type such as DIRECT_LINE/CONNECT_FULFILLED
action = window.simpleUpdateIn(
action,
['payload', 'activity', 'channelData'],
() => ({
'email': "[email protected]",
'projectId': projectId,
})
)
}
return next(action);
}
}
}
);

The question is: How can I access this data from the first waterfall step of MainDialog ?

Right now, In DialogAndWelcomeBot, where OnMembersAddedAsync resides, I have managed to override OnEventAsync and I have access to the parameters but I'm unable to access them and use them from Maindialog.

protected override async Task OnEventAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
await base.OnEventAsync(turnContext, cancellationToken);
string channelObj = string.Empty;
Newtonsoft.Json.Linq.JObject channelData = null;
string email = string.Empty;
try
{
channelObj = turnContext.Activity.ChannelData.ToString();
}
catch (Exception exc)
{
string errorMsg = string.Empty;
errorMsg = "channelObj error in OnEventAsync: " + exc.Message;
LogMessage(turnContext, cancellationToken, errorMsg);
}

        try
        {
            channelData = Newtonsoft.Json.Linq.JObject.Parse(channelObj);
        }
        catch (Exception exc)
        {
            string errorMsg = string.Empty;
            errorMsg = "channelData error in OnEventAsync" + exc.Message;
            LogMessage(turnContext, cancellationToken, errorMsg);
        }
        try
        {
            email = channelData["email"].ToString();
        }
        catch (Exception exc)
        {
            string errorMsg = string.Empty;
            errorMsg = "email error in OnEventAsync: " + exc.Message;
            LogMessage(turnContext, cancellationToken, errorMsg);
        }

---> The following is an attempt to store in UserState but it doesn't work
IStatePropertyAccessor accessor = UserState.CreateProperty(nameof(OnboardingState));
OnboardingState state = await accessor.GetAsync(turnContext, () => new OnboardingState());
state.Ticket = new CIWTicket();
state.Ticket.SourceEmailAddress = email;
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
LogMessage(turnContext, cancellationToken, "email in OnEventAsync : " + email);
}

    private async void LogMessage(ITurnContext sc, CancellationToken cancellationToken, string message)
    {
        var msg = MessageFactory.Text(message, message, InputHints.IgnoringInput);
        await sc.SendActivityAsync(msg, cancellationToken);
    }

@edreux
'How to' questions such as this one are better suited for Stack Overflow. Please feel free to post other questions you have about developing your own features over there so the community at large may help out. If you need help with a Web Chat implementation, you can post a question to the Web Chat tag. Thank you!

ok, thanks.
Regarding botframework, I have never got any answer to any questions in stackoverflow, only "ticket" management wher months later I've been asked if I had still the problem and the project had been given up due to the impossibility to address the issue.
Same issue here. More than 50 hours of human work for nothing.

I'm trying to implement this to pass parameters when a chat session starts.

const store = window.WebChat.createStore(
{},
function() {
return function(next) {
return function(action) {
if (action.type === 'DIRECT_LINE/POST_ACTIVITY') { // tried various type such as DIRECT_LINE/CONNECT_FULFILLED
action = window.simpleUpdateIn(
action,
['payload', 'activity', 'channelData'],
() => ({
'email': "[email protected]",
'projectId': projectId,
})
)
}
return next(action);
}
}
}
);

The question is: How can I access this data from the first waterfall step of MainDialog ?

Right now, In DialogAndWelcomeBot, where OnMembersAddedAsync resides, I have managed to override OnEventAsync and I have access to the parameters but I'm unable to access them and use them from Maindialog.

protected override async Task OnEventAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
await base.OnEventAsync(turnContext, cancellationToken);
string channelObj = string.Empty;
Newtonsoft.Json.Linq.JObject channelData = null;
string email = string.Empty;
try
{
channelObj = turnContext.Activity.ChannelData.ToString();
}
catch (Exception exc)
{
string errorMsg = string.Empty;
errorMsg = "channelObj error in OnEventAsync: " + exc.Message;
LogMessage(turnContext, cancellationToken, errorMsg);
}

        try
        {
            channelData = Newtonsoft.Json.Linq.JObject.Parse(channelObj);
        }
        catch (Exception exc)
        {
            string errorMsg = string.Empty;
            errorMsg = "channelData error in OnEventAsync" + exc.Message;
            LogMessage(turnContext, cancellationToken, errorMsg);
        }
        try
        {
            email = channelData["email"].ToString();
        }
        catch (Exception exc)
        {
            string errorMsg = string.Empty;
            errorMsg = "email error in OnEventAsync: " + exc.Message;
            LogMessage(turnContext, cancellationToken, errorMsg);
        }

---> The following is an attempt to store in UserState but it doesn't work
IStatePropertyAccessor accessor = UserState.CreateProperty(nameof(OnboardingState));
OnboardingState state = await accessor.GetAsync(turnContext, () => new OnboardingState());
state.Ticket = new CIWTicket();
state.Ticket.SourceEmailAddress = email;
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
LogMessage(turnContext, cancellationToken, "email in OnEventAsync : " + email);
}

    private async void LogMessage(ITurnContext sc, CancellationToken cancellationToken, string message)
    {
        var msg = MessageFactory.Text(message, message, InputHints.IgnoringInput);
        await sc.SendActivityAsync(msg, cancellationToken);
    }

Thanks a lot for the code. Saved my day!

Was this page helpful?
0 / 5 - 0 ratings