Skip to content

Commit 46623f8

Browse files
committed
fix: too fast polling
- skip 1 interval in polling after the server respond with 'slow_down' - prevent emitting events from previous polling sessions - refactored timeout logic issue: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-586223
1 parent c4ba3e5 commit 46623f8

File tree

2 files changed

+256
-127
lines changed

2 files changed

+256
-127
lines changed

packages/@webex/plugin-authorization-browser-first-party/src/authorization.js

+78-27
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {cloneDeep, isEmpty, omit} from 'lodash';
1414
import uuid from 'uuid';
1515
import base64url from 'crypto-js/enc-base64url';
1616
import CryptoJS from 'crypto-js';
17+
import {interfaceExtends} from '@babel/types';
1718

1819
// Necessary to require lodash this way in order to stub
1920
// methods in the unit test
@@ -67,17 +68,49 @@ const Authorization = WebexPlugin.extend({
6768

6869
namespace: 'Credentials',
6970

71+
/**
72+
* EventEmitter for authorization events
73+
* @instance
74+
* @memberof AuthorizationBrowserFirstParty
75+
* @type {EventEmitter}
76+
* @public
77+
*/
78+
eventEmitter: new EventEmitter(),
7079

7180
/**
72-
* Stores the interval ID for QR code polling
81+
* Stores the timer ID for QR code polling
7382
* @instance
7483
* @memberof AuthorizationBrowserFirstParty
7584
* @type {?number}
7685
* @private
7786
*/
78-
pollingRequest: null,
87+
pollingTimer: null,
88+
/**
89+
* Stores the expiration timer ID for QR code polling
90+
* @instance
91+
* @memberof AuthorizationBrowserFirstParty
92+
* @type {?number}
93+
* @private
94+
*/
95+
pollingExpirationTimer: null,
7996

80-
eventEmitter: new EventEmitter(),
97+
/**
98+
* Monotonically increasing id to identify the current polling request
99+
* @instance
100+
* @memberof AuthorizationBrowserFirstParty
101+
* @type {number}
102+
* @private
103+
*/
104+
pollingId: 0,
105+
106+
/**
107+
* Identifier for the current polling request
108+
* @instance
109+
* @memberof AuthorizationBrowserFirstParty
110+
* @type {?number}
111+
* @private
112+
*/
113+
currentPollingId: null,
81114

82115
/**
83116
* Initializer
@@ -260,7 +293,7 @@ const Authorization = WebexPlugin.extend({
260293
* @emits #qRCodeLogin
261294
*/
262295
initQRCodeLogin() {
263-
if (this.pollingRequest) {
296+
if (this.pollingTimer) {
264297
this.eventEmitter.emit('qRCodeLogin', {
265298
eventType: 'getUserCodeFailure',
266299
data: {message: 'There is already a polling request'},
@@ -291,7 +324,7 @@ const Authorization = WebexPlugin.extend({
291324
userCode: user_code,
292325
verificationUri: verification_uri,
293326
verificationUriComplete: verification_uri_complete,
294-
}
327+
},
295328
});
296329
// if device authorization success, then start to poll server to check whether the user has completed authorization
297330
this._startQRCodePolling(res.body);
@@ -320,23 +353,29 @@ const Authorization = WebexPlugin.extend({
320353
return;
321354
}
322355

323-
if (this.pollingRequest) {
356+
if (this.pollingTimer) {
324357
this.eventEmitter.emit('qRCodeLogin', {
325358
eventType: 'authorizationFailure',
326359
data: {message: 'There is already a polling request'},
327360
});
328361
return;
329362
}
330363

331-
const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options;
364+
const {device_code: deviceCode, expires_in: expiresIn = 300} = options;
365+
let interval = options.interval ?? 2;
332366

333-
let attempts = 0;
334-
const maxAttempts = expiresIn / interval;
367+
this.pollingExpirationTimer = setTimeout(() => {
368+
this.cancelQRCodePolling(false);
369+
this.eventEmitter.emit('qRCodeLogin', {
370+
eventType: 'authorizationFailure',
371+
data: {message: 'Authorization timed out'},
372+
});
373+
}, expiresIn * 1000);
335374

336-
this.pollingRequest = setInterval(() => {
337-
attempts += 1;
375+
const polling = () => {
376+
this.pollingId += 1;
377+
this.currentPollingId = this.pollingId;
338378

339-
const currentAttempts = attempts;
340379
this.webex
341380
.request({
342381
method: 'POST',
@@ -354,7 +393,8 @@ const Authorization = WebexPlugin.extend({
354393
},
355394
})
356395
.then((res) => {
357-
if (this.pollingRequest === null) return;
396+
// if the pollingId has changed, it means that the polling request has been canceled
397+
if (this.currentPollingId !== this.pollingId) return;
358398

359399
this.eventEmitter.emit('qRCodeLogin', {
360400
eventType: 'authorizationSuccess',
@@ -363,34 +403,40 @@ const Authorization = WebexPlugin.extend({
363403
this.cancelQRCodePolling();
364404
})
365405
.catch((res) => {
366-
if (this.pollingRequest === null) return;
406+
// if the pollingId has changed, it means that the polling request has been canceled
407+
if (this.currentPollingId !== this.pollingId) return;
367408

368-
if (currentAttempts >= maxAttempts) {
369-
this.eventEmitter.emit('qRCodeLogin', {
370-
eventType: 'authorizationFailure',
371-
data: {message: 'Authorization timed out'}
372-
});
373-
this.cancelQRCodePolling();
409+
// When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
410+
// So, skip one interval and then poll again.
411+
if (res.statusCode === 400 && res.body.message === 'slow_down') {
412+
schedulePolling(interval * 2);
374413
return;
375414
}
415+
376416
// if the statusCode is 428 which means that the authorization request is still pending
377417
// as the end user hasn't yet completed the user-interaction steps. So keep polling.
378418
if (res.statusCode === 428) {
379419
this.eventEmitter.emit('qRCodeLogin', {
380420
eventType: 'authorizationPending',
381-
data: res.body
421+
data: res.body,
382422
});
423+
schedulePolling(interval);
383424
return;
384425
}
385426

386427
this.cancelQRCodePolling();
387428

388429
this.eventEmitter.emit('qRCodeLogin', {
389430
eventType: 'authorizationFailure',
390-
data: res.body
431+
data: res.body,
391432
});
392433
});
393-
}, interval * 1000);
434+
};
435+
436+
const schedulePolling = (interval) =>
437+
(this.pollingTimer = setTimeout(polling, interval * 1000));
438+
439+
schedulePolling(interval);
394440
},
395441

396442
/**
@@ -399,14 +445,19 @@ const Authorization = WebexPlugin.extend({
399445
* @memberof AuthorizationBrowserFirstParty
400446
* @returns {void}
401447
*/
402-
cancelQRCodePolling() {
403-
if (this.pollingRequest) {
404-
clearInterval(this.pollingRequest);
448+
cancelQRCodePolling(withCancelEvent = true) {
449+
if (this.pollingTimer && withCancelEvent) {
405450
this.eventEmitter.emit('qRCodeLogin', {
406451
eventType: 'pollingCanceled',
407452
});
408-
this.pollingRequest = null;
409453
}
454+
455+
this.currentPollingId = null;
456+
457+
clearTimeout(this.pollingExpirationTimer);
458+
this.pollingExpirationTimer = null;
459+
clearTimeout(this.pollingTimer);
460+
this.pollingTimer = null;
410461
},
411462

412463
/**

0 commit comments

Comments
 (0)