Session management / Direct Line events
See original GitHub issueVersion 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:
- Created 3 years ago
- Reactions:3
- Comments:8
Top GitHub Comments
@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 thetoken
,conversationId
, andwatermark
(optional). The response will include a newtoken
andstreamUrl
which, along with theconversationId
(andwatermark
, if supplied), are necessary components when callingcreateDirectLine()
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. TheRECONNECT
occurs within seconds, afterwards.Hope of help!
@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.