Skip to content

Commit 93bc951

Browse files
authored
Merge pull request #16 from meulta/master
Support for resuming an existing conversation
2 parents 534920b + cf4984d commit 93bc951

2 files changed

Lines changed: 101 additions & 23 deletions

File tree

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,55 @@ var conversation = /* a Conversation object obtained from your app's server */;
140140
directLine.reconnect(conversation);
141141
```
142142

143+
### Resume an existing conversation
144+
145+
When using DirectLine with WebChat, closing the current tab or refreshing the page will create a new conversation in most cases. You can resume an existing conversation to keep the user in the same context.
146+
147+
**When using a secret** you can resume a conversation by:
148+
- Storing the conversationid (in a *permanent* place, like local storage)
149+
- Giving this value back while creating the DirectLine object along with the secret
150+
151+
```typescript
152+
import { DirectLine } from 'botframework-directlinejs';
153+
154+
const dl = new DirectLine({
155+
secret: /* SECRET */,
156+
conversationId: /* the conversationid you stored from previous conversation */
157+
});
158+
```
159+
160+
**When using a token** you can resume a conversation by:
161+
- Storing the conversationid and your token (in a *permanent* place, like local storage)
162+
- Calling the DirectLine reconnect API yourself to get a refreshed token and a streamurl
163+
- Creating the DirectLine object using the ConversationId, Token, and StreamUrl
164+
165+
```typescript
166+
import { DirectLine } from 'botframework-directlinejs';
167+
168+
const dl = new DirectLine({
169+
token: /* the token you retrieved while reconnecting */,
170+
streamUrl: /* the streamUrl you retrieved while reconnecting */,
171+
conversationId: /* the conversationid you stored from previous conversation */
172+
});
173+
```
174+
175+
**Getting any history that Direct Line has cached** : you can retrieve history using watermarks:
176+
You can see the watermark as an *activity 'bookmark'*. The resuming scenario will replay all the conversation activities from the watermark you specify. For now, this only works when using the polling version of DirectLine.
177+
178+
```typescript
179+
import { DirectLine } from 'botframework-directlinejs';
180+
181+
const dl = new DirectLine({
182+
token: /* the token you retrieved while reconnecting */,
183+
streamUrl: /* the streamUrl you retrieved while reconnecting */,
184+
conversationId: /* the conversationid you stored from previous conversation */,
185+
watermark: /* a watermark you saved from a previous conversation */,
186+
webSocket: false
187+
});
188+
```
189+
190+
*Watermark with websocket will be supported in the future.*
191+
143192
## Copyright & License
144193

145194
© 2017 Microsoft Corporation

src/directLine.ts

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,13 @@ export enum ConnectionStatus {
223223

224224
export interface DirectLineOptions {
225225
secret?: string,
226-
token?: string
226+
token?: string,
227+
conversationId?: string,
228+
watermark?: string,
227229
domain?: string,
228230
webSocket?: boolean,
229-
pollingInterval?: number
231+
pollingInterval?: number,
232+
streamUrl?: string
230233
}
231234

232235
const lifetimeRefreshToken = 30 * 60 * 1000;
@@ -257,7 +260,7 @@ export class DirectLine implements IBotConnection {
257260
public activity$: Observable<Activity>;
258261

259262
private domain = "https://directline.botframework.com/v3/directline";
260-
private webSocket = true;
263+
private webSocket;
261264

262265
private conversationId: string;
263266
private secret: string;
@@ -272,15 +275,29 @@ export class DirectLine implements IBotConnection {
272275
constructor(options: DirectLineOptions) {
273276
this.secret = options.secret;
274277
this.token = options.secret || options.token;
278+
this.webSocket = (options.webSocket === undefined ? true : options.webSocket) && typeof WebSocket !== 'undefined' && WebSocket !== undefined;
279+
275280
if (options.domain)
276281
this.domain = options.domain;
277-
if (options.webSocket !== undefined)
278-
this.webSocket = options.webSocket;
279-
282+
if (options.conversationId) {
283+
this.conversationId = options.conversationId;
284+
}
285+
if (options.watermark) {
286+
if (this.webSocket)
287+
console.warn("Watermark was ignored: it is not supported using websockets at the moment");
288+
else
289+
this.watermark = options.watermark;
290+
}
291+
if (options.streamUrl) {
292+
if (options.token && options.conversationId)
293+
this.streamUrl = options.streamUrl;
294+
else
295+
console.warn("streamUrl was ignored: you need to provide a token and a conversationid");
296+
}
280297
if (options.pollingInterval !== undefined)
281298
this.pollingInterval = options.pollingInterval;
282299

283-
this.activity$ = (this.webSocket && typeof WebSocket !== 'undefined' && WebSocket
300+
this.activity$ = (this.webSocket
284301
? this.webSocketActivity$()
285302
: this.pollingGetActivity$()
286303
).share();
@@ -294,21 +311,27 @@ export class DirectLine implements IBotConnection {
294311
if (connectionStatus === ConnectionStatus.Uninitialized) {
295312
this.connectionStatus$.next(ConnectionStatus.Connecting);
296313

297-
return this.startConversation()
298-
.do(conversation => {
299-
this.conversationId = conversation.conversationId;
300-
this.token = this.secret || conversation.token;
301-
this.streamUrl = conversation.streamUrl;
302-
if (!this.secret)
303-
this.refreshTokenLoop();
304-
314+
//if token and streamUrl are defined it means reconnect has already been done. Skipping it.
315+
if (this.token && this.streamUrl) {
305316
this.connectionStatus$.next(ConnectionStatus.Online);
306-
}, error => {
307-
this.connectionStatus$.next(ConnectionStatus.FailedToConnect);
308-
})
309-
.map(_ => connectionStatus);
310-
} else {
311-
return Observable.of(connectionStatus);
317+
return Observable.of(connectionStatus);
318+
} else {
319+
return this.startConversation().do(conversation => {
320+
this.conversationId = conversation.conversationId;
321+
this.token = this.secret || conversation.token;
322+
this.streamUrl = conversation.streamUrl;
323+
if (!this.secret)
324+
this.refreshTokenLoop();
325+
326+
this.connectionStatus$.next(ConnectionStatus.Online);
327+
}, error => {
328+
this.connectionStatus$.next(ConnectionStatus.FailedToConnect);
329+
})
330+
.map(_ => connectionStatus);
331+
}
332+
}
333+
else {
334+
return Observable.of(connectionStatus);
312335
}
313336
})
314337
.filter(connectionStatus => connectionStatus != ConnectionStatus.Uninitialized && connectionStatus != ConnectionStatus.Connecting)
@@ -338,9 +361,15 @@ export class DirectLine implements IBotConnection {
338361
}
339362

340363
private startConversation() {
364+
//if conversationid is set here, it means we need to call the reconnect api, else it is a new conversation
365+
const url = this.conversationId
366+
? `${this.domain}/conversations/${this.conversationId}?watermark=${this.watermark}`
367+
: `${this.domain}/conversations`;
368+
const method = this.conversationId ? "GET" : "POST";
369+
341370
return Observable.ajax({
342-
method: "POST",
343-
url: `${this.domain}/conversations`,
371+
method,
372+
url,
344373
timeout,
345374
headers: {
346375
"Accept": "application/json",

0 commit comments

Comments
 (0)