Search code examples
c#botframework

Open browser window on click


I am using botframework 3.9 and for some reasons I can't upgrade now. I would like to know if there is a way to open a new browser window where I can render a page or fire a JavaScript function. Here is how I am opening link:

await context.PostAsync(@"please [click here](http://www.example.com/)");

This does renders the link however, I wanna open a link in JavaScript window so I can close the window programmatically or if possible if I can fire some JavaScript function.


Solution

  • This is actually much easier than you think. If you have a look at the WebChat README you can see there are many ways WebChat can be customized. Pay particular attention to sample 11, which you can demo here. The body of that page looks like this:

    <div id="webchat" role="main"></div>
    <script>
      (async function () {
        // In this demo, we are using Direct Line token from MockBot.
        // To talk to your bot, you should use the token exchanged using your Direct Line secret.
        // You should never put the Direct Line secret in the browser or client app.
        // https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-direct-line-3-0-authentication
    
        const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
        const { token } = await res.json();
    
        // We are creating our own version of Redux store, which include middleware and optional initial state.
        const store = window.WebChat.createStore(
          {},
          ({ dispatch }) => next => action => {
            if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
              // After connected, we will send a message by dispatching a Redux action.
              dispatch({ type: 'WEB_CHAT/SEND_MESSAGE', payload: { text: 'sample:backchannel' } });
            } else if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
              // When receiving an activity of type "event" with name of "sample:backchannel", prompt it out.
              const { activity } = action.payload;
    
              if (activity.type === 'event' && activity.name === 'sample:backchannel') {
                alert(JSON.stringify(activity, null, 2));
              }
            }
    
            return next(action);
          }
        );
    
        window.WebChat.renderWebChat({
          directLine: window.WebChat.createDirectLine({ token }),
          // We will use a custom version of Redux store, which we added middleware to handle backchannel messages.
          store
        }, document.getElementById('webchat'));
    
        document.querySelector('#webchat > *').focus();
      })().catch(err => console.error(err));
    </script>
    

    You can see in this sample that WebChat has been modified to respond to certain activities from the bot by opening a popup window using JavaScript's alert function. The modification is done by creating a store and then passing that store as an argument to renderWebChat.

    Rather than opening an alert window, you want to open a window you can close. This could be achieved if you modify the store to look like this:

    let windows = {};
    
    const store = window.WebChat.createStore(
        {},
        ({ dispatch }) => next => action => {
            if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
                const { activity } = action.payload;
    
                if (activity.type === 'event') {
                    let url = activity.value;
    
                    if (activity.name == 'open' && !windows[url]) {
                        windows[url] = window.open(url);
                    }
    
                    if (activity.name == 'close' && windows[url]) {
                        windows[url].close();
                        windows[url] = null;
                    }
                }
            }
    
            return next(action);
        }
    );
    

    You don't have to implement it this way, but I've implemented it so that when WebChat receives an event activity named open it will open a window and when it receives an event activity named close it will close a window. It even keeps track of multiple windows so you can choose which window to close.

    I've set up a bot that sends open and close events when the user types "open [url]" or "close [url]". The bot code looks like this:

    var connector = new ConnectorClient(new Uri(activity.ServiceUrl));
    
    var text = activity.Text;
    var words = text.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
    var firstWord = words.FirstOrDefault().ToLower();
    var secondWord = words.Length > 1 ? words[1] : "https://stackoverflow.com/";
    Activity reply = null;
    
    switch (firstWord)
    {
        case "open":
        case "close":
            reply = activity.CreateReply();
            reply.Type = ActivityTypes.Event;
            reply.Name = firstWord;
            reply.Value = secondWord;
            break;
        default:
            reply = activity.CreateReply("Try \"open ...\" or \"close ...\"");
            break;
    }
    
    await connector.Conversations.SendToConversationAsync(reply);
    

    Hopefully you can use this information and modify it to suit your needs.