Skip to content

Commit 61b9c5e

Browse files
authored
Slow down polling on congested traffic (#98)
* Do not poll if last polling was not complete * Revert expired token check * Bump to 0.10.0-0 * Update CHANGELOG.md * Fix for speedy retry on 403 * Update directLine.ts * Cleanup
1 parent 799e477 commit 61b9c5e

4 files changed

Lines changed: 62 additions & 37 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010

1111
### Changed
1212
- Delay before retrying Web Socket, in [#97](https://github.com/Microsoft/BotFramework-WebChat/pull/97)
13+
- Slow down polling on congested traffic, in [#98](https://github.com/Microsoft/BotFramework-DirectLineJS/pull/98)
1314

1415
## [0.9.17] - 2018-08-31
1516
### Changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "botframework-directlinejs",
3-
"version": "0.9.18-0",
3+
"version": "0.10.0-0",
44
"description": "client library for the Microsoft Bot Framework Direct Line 3.0 protocol",
55
"main": "built/directLine.js",
66
"types": "built/directLine.d.ts",

src/directLine.ts

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ const errorFailedToConnect = new Error("failed to connect");
279279

280280
const konsole = {
281281
log: (message?: any, ... optionalParams: any[]) => {
282-
if (typeof(window) !== 'undefined' && (window as any)["botchatDebug"] && message)
282+
if (typeof window !== 'undefined' && (window as any)["botchatDebug"] && message)
283283
console.log(message, ... optionalParams);
284284
}
285285
}
@@ -319,23 +319,23 @@ export class DirectLine implements IBotConnection {
319319
if (options.domain) {
320320
this.domain = options.domain;
321321
}
322-
322+
323323
if (options.conversationId) {
324324
this.conversationId = options.conversationId;
325325
}
326-
326+
327327
if (options.watermark) {
328328
this.watermark = options.watermark;
329329
}
330-
330+
331331
if (options.streamUrl) {
332332
if (options.token && options.conversationId) {
333333
this.streamUrl = options.streamUrl;
334334
} else {
335335
console.warn('streamUrl was ignored: you need to provide a token and a conversationid');
336336
}
337337
}
338-
338+
339339
if (options.pollingInterval !== undefined) {
340340
this.pollingInterval = options.pollingInterval;
341341
}
@@ -461,7 +461,11 @@ export class DirectLine implements IBotConnection {
461461
// if the token is expired there's no reason to keep trying
462462
this.expiredToken();
463463
return Observable.throw(error);
464+
} else if (error.status === 404) {
465+
// If the bot is gone, we should stop retrying
466+
return Observable.throw(error);
464467
}
468+
465469
return Observable.of(error);
466470
})
467471
.delay(timeout)
@@ -600,37 +604,55 @@ export class DirectLine implements IBotConnection {
600604
}
601605

602606
private pollingGetActivity$() {
603-
return Observable.interval(this.pollingInterval)
604-
.combineLatest(this.checkConnection())
605-
.flatMap(([_, connectionStatus]) => {
606-
if (connectionStatus !== ConnectionStatus.Online)
607-
return Observable.empty<Activity>()
608-
609-
return Observable.ajax({
610-
method: "GET",
611-
url: `${this.domain}/conversations/${this.conversationId}/activities?watermark=${this.watermark}`,
612-
timeout,
613-
headers: {
614-
"Accept": "application/json",
615-
"Authorization": `Bearer ${this.token}`
616-
}
617-
})
618-
.catch(error => {
619-
if (error.status === 403) {
620-
// This is slightly ugly. We want to update this.connectionStatus$ to ExpiredToken so that subsequent
621-
// calls to checkConnection will throw an error. But when we do so, it causes this.checkConnection()
622-
// to immediately throw an error, which is caught by the catch() below and transformed into an empty
623-
// object. Then next() returns, and we emit an empty object. Which means one 403 is causing
624-
// two empty objects to be emitted. Which is harmless but, again, slightly ugly.
625-
this.expiredToken();
607+
const poller$: Observable<AjaxResponse> = Observable.create((subscriber: Subscriber<any>) => {
608+
// A BehaviorSubject to trigger polling. Since it is a BehaviorSubject
609+
// the first event is produced immediately.
610+
const trigger$ = new BehaviorSubject<any>({});
611+
612+
trigger$.subscribe(() => {
613+
if (this.connectionStatus$.getValue() === ConnectionStatus.Online) {
614+
const startTimestamp = Date.now();
615+
616+
Observable.ajax({
617+
headers: {
618+
Accept: 'application/json',
619+
Authorization: `Bearer ${ this.token }`
620+
},
621+
method: 'GET',
622+
url: `${ this.domain }/conversations/${ this.conversationId }/activities?watermark=${ this.watermark }`,
623+
timeout
624+
}).subscribe(
625+
(result: AjaxResponse) => {
626+
subscriber.next(result);
627+
setTimeout(() => trigger$.next(null), Math.max(0, this.pollingInterval - Date.now() + startTimestamp));
628+
},
629+
(error: any) => {
630+
switch (error.status) {
631+
case 403:
632+
this.connectionStatus$.next(ConnectionStatus.ExpiredToken);
633+
setTimeout(() => trigger$.next(null), this.pollingInterval);
634+
break;
635+
636+
case 404:
637+
this.connectionStatus$.next(ConnectionStatus.Ended);
638+
break;
639+
640+
default:
641+
// propagate the error
642+
subscriber.error(error);
643+
break;
644+
}
645+
}
646+
);
626647
}
627-
return Observable.empty<AjaxResponse>();
628-
})
629-
// .do(ajaxResponse => konsole.log("getActivityGroup ajaxResponse", ajaxResponse))
648+
});
649+
});
650+
651+
return this.checkConnection()
652+
.flatMap(_ => poller$
653+
.catch(() => Observable.empty<AjaxResponse>())
630654
.map(ajaxResponse => ajaxResponse.response as ActivityGroup)
631-
.flatMap(activityGroup => this.observableFromActivityGroup(activityGroup))
632-
})
633-
.catch(error => Observable.empty<Activity>());
655+
.flatMap(activityGroup => this.observableFromActivityGroup(activityGroup)));
634656
}
635657

636658
private observableFromActivityGroup(activityGroup: ActivityGroup) {
@@ -711,13 +733,15 @@ export class DirectLine implements IBotConnection {
711733
// token has expired. We can't recover from this here, but the embedding
712734
// website might eventually call reconnect() with a new token and streamUrl.
713735
this.expiredToken();
736+
} else if (error.status === 404) {
737+
return Observable.throw(errorConversationEnded);
714738
}
739+
715740
return Observable.of(error);
716741
})
717742
.delay(timeout)
718743
.take(retries)
719744
)
720745
)
721746
}
722-
723747
}

0 commit comments

Comments
 (0)