question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Session management / Direct Line events

See original GitHub issue

Version 4.6.0

The solution I’m currently developing requires me to handle the token expiration of Direct Line. Often users of the chat leave it open for quite some time as they are required to take a photo and upload it. During this time the chat might be left there for longer periods than 30 minutes and the Direct Line session expires, leaving users with unresponsive UI once they return.

So to get a glimpse of what I’m dealing with here is the chatStoreMiddleware I initially used to pass into WebChat:

import ReactWebChat from 'botframework-webchat';
import { createStore } from 'botframework-webchat';

enum DirectLineConnectionStatus {
  Uninitialized,
  Connecting,
  Online,
  ExpiredToken,
  FailedToConnect,
  Ended,
}

export const chatStoreMiddleware = 
    ({ getState, dispatch }: Store): any => 
        (next: Dispatch<AnyAction>) => 
            (action: AnyAction): AnyAction | null => 
    {  
        switch (action.type) {  
            case 'DIRECT_LINE/CONNECTION_STATUS_UPDATE': {   
                if (  
                    action.payload &&
                    action.payload.connectionStatus === 
                        DirectLineConnectionStatus.Online  
                ) {  
                    handleConnectionOnline();  
                }  
                break;  
            }  
            
            case 'DIRECT_LINE/CONNECT_FULFILLED': {  
                handleConnectFulfilled(action);  
                break;  
            }  
            
            // other cases omitted 
      }  
  
      return next(action);  
};

const chatStore = createStore(null, chatStoreMiddleware);

<ReactWebChat  
  store={chatStore} 
  // other props omitted
/>

However, as it can be seen in WebChat connectivityStatus.js reducers, they not expose expired token connection status.

So I kept digging and stumbled upon WebChat connectionStatusUpdate.js action where it states that 'DIRECT_LINE/CONNECTION_STATUS_UPDATE' action is obsolete and it only dispatches online status.

Alright, I decided to try my luck with WebChat updateConnectionStatus.js action like so

import ReactWebChat from 'botframework-webchat';
import { createStore } from 'botframework-webchat';

export const chatStoreMiddleware = 
    ({ getState, dispatch }: Store): any => 
        (next: Dispatch<AnyAction>) => 
            (action: AnyAction): AnyAction | null => 
    {  
        switch (action.type) { 
            /*
            case 'DIRECT_LINE/CONNECTION_STATUS_UPDATE': {   
                if (  
                    action.payload &&
                    action.payload.connectionStatus === 
                        DirectLineConnectionStatus.Online  
                ) {  
                    handleConnectionOnline();  
                }  
                break;  
            }  
            */
             
            case 'DIRECT_LINE/UPDATE_CONNECTION_STATUS': {  
                if (  
                    action.payload &&  
                    action.payload.connectionStatus ===  
                        DirectLineConnectionStatus.ExpiredToken  
                ) {  
                    handleExpiredToken();  
                }  
                
                if (  
                    action.payload &&  
                    action.payload.connectionStatus === 
                        DirectLineConnectionStatus.Online  
                ) {  
                    handleConnectionOnline();  
                }  
                break;  
            } 
            
            case 'DIRECT_LINE/CONNECT_FULFILLED': {  
                handleConnectFulfilled(action);  
                break;  
            }  
            
            // other cases omitted  
      }  
  
      return next(action);  
};

const chatStore = createStore(null, chatStoreMiddleware);

<ReactWebChat  
  store={chatStore} 
  // other props omitted
/>

Now when I try to launch the chat - it just loads forever. 😳

Ok, let’s console.log things:

// code before omitted
case 'DIRECT_LINE/UPDATE_CONNECTION_STATUS': {  
    console.log('\n>>> UPDATE_CONNECTION_STATUS');  
    console.log('TYPE:', action.type);  
    console.log('PAYLOAD:', action.payload);
    // handle update connection status
    break;


case 'DIRECT_LINE/CONNECT_FULFILLED': {  
    console.log('\n>>> CONNECT_FULFILLED');  
    console.log('TYPE:', action.type);  
    console.log('PAYLOAD:', action.payload);  
    // handle connect fulfilled
    break;  
}

// code after omitted
10:19:32.056 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
10:19:32.057 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
10:19:32.057 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 0}

10:19:32.057 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
10:19:32.057 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
10:19:32.057 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 1}

10:19:32.058 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
10:19:32.058 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
10:19:32.058 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 2}

10:19:32.069 chatStoreMiddleware.ts:104 >>> CONNECT_FULFILLED
10:19:32.069 chatStoreMiddleware.ts:105 TYPE: DIRECT_LINE/CONNECT_FULFILLED
10:19:32.069 chatStoreMiddleware.ts:106 PAYLOAD: {directLine: DirectLine}

Alright, we have all the events in order, so what’s the problem? Let’s add back the 'DIRECT_LINE/CONNECTION_STATUS_UPDATE' case and expose the freshly added DirectLineConnectionStatus.ExpiredToken status in 'DIRECT_LINE/UPDATE_CONNECTION_STATUS' case :

// code before omitted
switch (action.type) { 

    // initial case
    case 'DIRECT_LINE/CONNECTION_STATUS_UPDATE': {   
        console.log('\n>>> CONNECTION_STATUS_UPDATE');  
        console.log('TYPE:', action.type);  
        console.log('PAYLOAD:', action.payload);
        if (  
            action.payload &&  
            action.payload.connectionStatus === 
                DirectLineConnectionStatus.Online  
        ) {  
            handleConnectionOnline();  
        }
        // unable to handle DirectLineConnectionStatus.ExpiredToken here
        break;  
    }  

    // new case
    case 'DIRECT_LINE/UPDATE_CONNECTION_STATUS': {  
        console.log('\n>>> UPDATE_CONNECTION_STATUS');  
        console.log('TYPE:', action.type);  
        console.log('PAYLOAD:', action.payload);
        if (  
            action.payload &&  
            action.payload.connectionStatus ===  
                DirectLineConnectionStatus.ExpiredToken  
        ) {  
            handleExpiredToken();  
        }
        // do not handle DirectLineConnectionStatus.Online
        break;  
    } 
    
    case 'DIRECT_LINE/CONNECT_FULFILLED': {
        console.log('\n>>> CONNECT_FULFILLED');  
        console.log('TYPE:', action.type);  
        console.log('PAYLOAD:', action.payload);  
        // handle connect fulfilled
        break;  
    }  
    
    // other cases omitted  
 }
 // code after omitted
/**
* these get logged from the *new* case of 'DIRECT_LINE/UPDATE_CONNECTION_STATUS'
*/
13:00:43.375 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
13:00:43.375 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
13:00:43.375 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 0}

13:00:43.376 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
13:00:43.376 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
13:00:43.376 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 1}

13:00:43.376 chatStoreMiddleware.ts:64 >>> UPDATE_CONNECTION_STATUS
13:00:43.377 chatStoreMiddleware.ts:65 TYPE: DIRECT_LINE/UPDATE_CONNECTION_STATUS
13:00:43.377 chatStoreMiddleware.ts:66 PAYLOAD: {connectionStatus: 2}

/**
* CONNECT_FULFILLED acknowledgement
*/
13:00:43.379 chatStoreMiddleware.ts:104 >>> CONNECT_FULFILLED
13:00:43.379 chatStoreMiddleware.ts:105 TYPE: DIRECT_LINE/CONNECT_FULFILLED
13:00:43.379 chatStoreMiddleware.ts:106 PAYLOAD: {directLine: DirectLine}

/**
* these get logged from the *initial* case of 'DIRECT_LINE/CONNECTION_STATUS_UPDATE'
*/
13:00:43.381 chatStoreMiddleware.ts:91 >>> CONNECTION_STATUS_UPDATE
13:00:43.381 chatStoreMiddleware.ts:92 >>> TYPE: DIRECT_LINE/CONNECTION_STATUS_UPDATE
13:00:43.381 chatStoreMiddleware.ts:93 >>> PAYLOAD: {connectionStatus: 2}

Voila! the chat now starts!

So to the good part:

  • Why there are two similar Direct Line statuses, which are named similarly, behave differently and are not documented?
  • What is the proper way to handle token expiration?
  • Is it so that DIRECT_LINE/UPDATE_CONNECTION_STATUS can be used only for token expiration and 'DIRECT_LINE/CONNECTION_STATUS_UPDATE' only for checking that Direct Line is fully initialized and ready to accept messages?

I would love to hear the voice of authors!

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:8

github_iconTop GitHub Comments

1reaction
stevkancommented, May 7, 2020

@cimbis, It appears that the essence of what you are wanting can be achieved by use of the browser’s built-in navigator.onLine API. When the browser goes online or offline, either by simulation (as shown below) or by sleeping/waking the machine (closing the laptop) or a loss of internet connectivity, an “online”/“offline” browser event is emitted setting the above API to a truthy value.

This value can be acted on via an event listener.

When the browser goes offline, we use the store to dispatch a DIRECT_LINE/DISCONNECT action which disconnects Web Chat and, effectively, disallows any new actions to be taken.

When the browser goes online, we again use the store to, this time, dispatch the DIRECT_LINE/RECONNECT action. Prior to the dispatch call, we first need to re-establish our connection to direct line. For this to happen, in short, you will want to call the reconnect API at /v3/directline/conversations/{conversationId}/?watermark={watermark} passing in the token, conversationId, and watermark (optional). The response will include a new token and streamUrl which, along with the conversationId (and watermark, if supplied), are necessary components when calling createDirectLine() to resume the conversation.

I tested this via simulating going offline as well as sleeping the laptop. In both instances, the conversation was able to be resumed. There were still errors that populated in the developer console, but they didn’t hinder the re-connection nor affect the conversation.

Undoubtedly, this will require some massaging to capture different instances such as a closed web socket or an expired token if the machine is asleep longer than the token expiry.

Lastly, just of note, the DISCONNECT is actually dispatched when the machine is awoken which is the first instance the system is able to recognize it was not running. The RECONNECT occurs within seconds, afterwards.

Hope of help!

window.addEventListener('offline', (e) => {
  store.dispatch({
    type: 'DIRECT_LINE/DISCONNECT'
  })
});

window.addEventListener('online', async (e) => {
  <<RECONNECT TO DIRECT LINE>>
  store.dispatch( {
    type: 'DIRECT_LINE/RECONNECT'
  } )
});

onlineOffline

1reaction
stevkancommented, May 5, 2020

@cimbis, thank you for the clarification. I’m not aware of a method that would allow for this, but let me do a little research / testing and see what I may find.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Session Management - OWASP Cheat Sheet Series
A web session is a sequence of network HTTP request and response transactions associated with the same user. Modern and complex web applications...
Read more >
Session Management in Java - HttpServlet, Cookies, URL ...
HttpSession allows us to set objects as attributes that can be retrieved in future requests.
Read more >
Quickstart: Extended Events in SQL Server - Microsoft Learn
Connect with SSMS. In the Object Explorer, click Management > Extended Events > New Session. The New Session dialog is preferable to the...
Read more >
Session Control - an overview | ScienceDirect Topics
Sessions allow applications to maintain state. After the user logs in, the server and client will operate together to maintain an authenticated state...
Read more >
Event Scheduling Software by Sched
Sched is event scheduling software for planning, organizing, promoting, managing, and executing excellent in-person, online, and hybrid events.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found