Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: too fast polling #4007

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,49 @@ const Authorization = WebexPlugin.extend({

namespace: 'Credentials',

/**
* EventEmitter for authorization events
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {EventEmitter}
* @public
*/
eventEmitter: new EventEmitter(),

/**
* Stores the interval ID for QR code polling
* Stores the timer ID for QR code polling
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {?number}
* @private
*/
pollingRequest: null,
pollingTimer: null,
/**
* Stores the expiration timer ID for QR code polling
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {?number}
* @private
*/
pollingExpirationTimer: null,

eventEmitter: new EventEmitter(),
/**
* Monotonically increasing id to identify the current polling request
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {number}
* @private
*/
pollingId: 0,

/**
* Identifier for the current polling request
* @instance
* @memberof AuthorizationBrowserFirstParty
* @type {?number}
* @private
*/
currentPollingId: null,

/**
* Initializer
Expand Down Expand Up @@ -260,7 +292,7 @@ const Authorization = WebexPlugin.extend({
* @emits #qRCodeLogin
*/
initQRCodeLogin() {
if (this.pollingRequest) {
if (this.pollingTimer) {
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'getUserCodeFailure',
data: {message: 'There is already a polling request'},
Expand Down Expand Up @@ -291,7 +323,7 @@ const Authorization = WebexPlugin.extend({
userCode: user_code,
verificationUri: verification_uri,
verificationUriComplete: verification_uri_complete,
}
},
});
// if device authorization success, then start to poll server to check whether the user has completed authorization
this._startQRCodePolling(res.body);
Expand Down Expand Up @@ -320,23 +352,29 @@ const Authorization = WebexPlugin.extend({
return;
}

if (this.pollingRequest) {
if (this.pollingTimer) {
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationFailure',
data: {message: 'There is already a polling request'},
});
return;
}

const {device_code: deviceCode, interval = 2, expires_in: expiresIn = 300} = options;
const {device_code: deviceCode, expires_in: expiresIn = 300} = options;
let interval = options.interval ?? 2;

let attempts = 0;
const maxAttempts = expiresIn / interval;
this.pollingExpirationTimer = setTimeout(() => {
this.cancelQRCodePolling(false);
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationFailure',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the emitted event types should probably be in some enum or some other type so that client using this API can easily know what are all possible events they can get without reading the implementation.

data: {message: 'Authorization timed out'},
});
}, expiresIn * 1000);

this.pollingRequest = setInterval(() => {
attempts += 1;
const polling = () => {
this.pollingId += 1;
this.currentPollingId = this.pollingId;

const currentAttempts = attempts;
this.webex
.request({
method: 'POST',
Expand All @@ -354,7 +392,8 @@ const Authorization = WebexPlugin.extend({
},
})
.then((res) => {
if (this.pollingRequest === null) return;
// if the pollingId has changed, it means that the polling request has been canceled
if (this.currentPollingId !== this.pollingId) return;

this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationSuccess',
Expand All @@ -363,34 +402,40 @@ const Authorization = WebexPlugin.extend({
this.cancelQRCodePolling();
})
.catch((res) => {
if (this.pollingRequest === null) return;
// if the pollingId has changed, it means that the polling request has been canceled
if (this.currentPollingId !== this.pollingId) return;

if (currentAttempts >= maxAttempts) {
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationFailure',
data: {message: 'Authorization timed out'}
});
this.cancelQRCodePolling();
// When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
// So, skip one interval and then poll again.
if (res.statusCode === 400 && res.body.message === 'slow_down') {
schedulePolling(interval * 2);
marcin-bazyl marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// if the statusCode is 428 which means that the authorization request is still pending
// as the end user hasn't yet completed the user-interaction steps. So keep polling.
if (res.statusCode === 428) {
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationPending',
data: res.body
data: res.body,
});
schedulePolling(interval);
return;
}

this.cancelQRCodePolling();

this.eventEmitter.emit('qRCodeLogin', {
eventType: 'authorizationFailure',
data: res.body
data: res.body,
});
});
}, interval * 1000);
};

const schedulePolling = (interval) =>
(this.pollingTimer = setTimeout(polling, interval * 1000));

maxinteger marked this conversation as resolved.
Show resolved Hide resolved
schedulePolling(interval);
},

/**
Expand All @@ -399,14 +444,19 @@ const Authorization = WebexPlugin.extend({
* @memberof AuthorizationBrowserFirstParty
* @returns {void}
*/
cancelQRCodePolling() {
if (this.pollingRequest) {
clearInterval(this.pollingRequest);
cancelQRCodePolling(withCancelEvent = true) {
if (this.pollingTimer && withCancelEvent) {
this.eventEmitter.emit('qRCodeLogin', {
eventType: 'pollingCanceled',
});
this.pollingRequest = null;
}

this.currentPollingId = null;

clearTimeout(this.pollingExpirationTimer);
this.pollingExpirationTimer = null;
clearTimeout(this.pollingTimer);
this.pollingTimer = null;
},

/**
Expand Down
Loading