DirectLineJS is a chat adapter. It is designed as a state machine with specific behaviors.
The state is defined by the connection status observable (a.k.a. connectionStatus$).
stateDiagram-v2
[*] --> Uninitialized: Constructor
Ended --> [*]
Uninitialized --> Connecting: Subscribe to activity$
Connecting --> Online: Connected
Online --> Connecting: Disconnected and will retry
Online --> FailedToConnect: Disconnected while retries exhausted
Online --> Ended: Call end()
Connecting --> FailedToConnect: Retry failed and retries exhausted
FailedToConnect --> Ended: Call end()
Connecting --> Ended: Call end()
Uninitialized --> Ended: Call end()
FailedToConnect --> Connecting: Call reconnect()
This is the initial state of the chat adapter.
- Start terminator: when the chat adapter object is created
- Call to
new DirectLine()
- Call to
When the chat adapter is establishing a connection to the bot.
To prevent service overloading, a backoff must be introduced for retries.
When retrying to connect, it should first transit to this state, before performing the backoff (a.k.a. sleep).
The chat adapter will not transit from Connecting to Connecting when retrying multiple times. It is done transparently and cannot be observed.
Uninitialized: when subscribing toactivity$- Call to
directLine.activity$.subscribe()
- Call to
Online: when disconnected while retry is possibleFailedToConnect: when explicitly reconnect- Call to
directLine.reconnect()
- Call to
When the chat adapter is connected to the bot.
A connection will be marked as "stable" after it has been in this state for more than 1 minute. A stable connection has these benefits:
- No backoff at
Connectingstate at the first attempt - Retry counter is reset
Connecting: when connection established
When all retry attempts are exhausted.
Connecting: retrying connection and failed to connect after all retry attempts are exhuastedOnline: when disconnected and all retry attempts are exhausted
When end() is called.
The activity observable (a.k.a. activity$) will be completed.
The connection status observable (a.k.a. connectionStatus$) will be completed after transitioned into Ended.
After transitioned to this state, all resources should be released.
Uninitialized: callend()before subscribing toactivity$Connecting: callend()while it is attempting to connectOnline: callend()while it is connectedFailedToConnect: callend()after all retry attempts are exhausted
To establish the connection, subscribe to the activity observable (a.k.a. activity$).
Subscribing to the connection status observable (a.k.a. connectionStatus$) will not establish the connection.
To subscribe to incoming activities, call activity$.subscribe(). The activity$ is an observable.
Unlike ES Observable, the activity$ is shared amongst subscribers. The first subscriber will kick off connection and observe all activities. Subscribers which joined later will only observe activities from the time they are subscribed.
Similarly, connectionStatus$ is also a shared observable.
To send an activity to the bot, call postActivity(). This would put the activity on the bot queue and returns an observable.
There will be two acknowledgements for the activity:
- Acknowledgement when the activity is on the bot queue
- Acknowledgement when the bot finished processing the activity (a.k.a. read receipt)
Due to distributed nature of the system, these acknowledgements may come in random order.
The observable returned by the postActivity() call will tell if the activity is successfully queued or not.
- When the queue operation completed successfully:
- The activity ID generated by the bot or service will be observed
- Then, the observable is completed
- When the queue operation failed:
- The observable will be errored out
The activity will be echoed back via the activity observable (a.k.a. activity$). It may come with more details about the activity, such as activity ID, channel ID, conversation ID, timestamp, etc.
Before the bot finished processing of the activity, it may send responses. In other words, bot response could arrive sooner than the read receipt. The replyToId in the activity should be used to determine which activity the bot is responding to.
Connection should be automatically retried. A backoff should be applied if the network environment is unstable.
If the connection is stable, it should reset retry counter.
Depends on the service, a stable connection could means:
- a minute has been passed;
- a certain number of activities are exchanged;
- a certain size of packets are exchanged.
If the connection was stable and is disconnected, it should retry the connection without backoff in its first attempt. This can be done through retry counter or exponential backoff.
When all retry attempts are exhausted, the chat adapter will "hibernate". Calling reconnect() will wake up and revive the chat adapter and reset the retry counter.
reconnect() can be called multiple times, as long as the chat adapter is not ended.
Calling reconnect() while the chat adapter is active, should do nothing.
Thus, if the chat adapter is not ended, reconnect() is safe to call immediately after app switching or visibility change, regardless of the connection status.
When end() is called, all resources held by the chat adapter must be released. Further postActivity() or reconnect() should fail.