@@ -22,6 +22,16 @@ const lodash = require('lodash');
22
22
const OAUTH2_CSRF_TOKEN = 'oauth2-csrf-token' ;
23
23
const OAUTH2_CODE_VERIFIER = 'oauth2-code-verifier' ;
24
24
25
+ /**
26
+ * Authorization plugin events
27
+ */
28
+ export const Events = {
29
+ /**
30
+ * QR code login events
31
+ */
32
+ qRCodeLogin : 'qRCodeLogin' ,
33
+ } ;
34
+
25
35
/**
26
36
* Browser support for OAuth2. Automatically parses the URL query for an
27
37
* authorization code
@@ -67,17 +77,49 @@ const Authorization = WebexPlugin.extend({
67
77
68
78
namespace : 'Credentials' ,
69
79
80
+ /**
81
+ * EventEmitter for authorization events
82
+ * @instance
83
+ * @memberof AuthorizationBrowserFirstParty
84
+ * @type {EventEmitter }
85
+ * @public
86
+ */
87
+ eventEmitter : new EventEmitter ( ) ,
70
88
71
89
/**
72
- * Stores the interval ID for QR code polling
90
+ * Stores the timer ID for QR code polling
73
91
* @instance
74
92
* @memberof AuthorizationBrowserFirstParty
75
93
* @type {?number }
76
94
* @private
77
95
*/
78
- pollingRequest : null ,
96
+ pollingTimer : null ,
97
+ /**
98
+ * Stores the expiration timer ID for QR code polling
99
+ * @instance
100
+ * @memberof AuthorizationBrowserFirstParty
101
+ * @type {?number }
102
+ * @private
103
+ */
104
+ pollingExpirationTimer : null ,
79
105
80
- eventEmitter : new EventEmitter ( ) ,
106
+ /**
107
+ * Monotonically increasing id to identify the current polling request
108
+ * @instance
109
+ * @memberof AuthorizationBrowserFirstParty
110
+ * @type {number }
111
+ * @private
112
+ */
113
+ pollingId : 0 ,
114
+
115
+ /**
116
+ * Identifier for the current polling request
117
+ * @instance
118
+ * @memberof AuthorizationBrowserFirstParty
119
+ * @type {?number }
120
+ * @private
121
+ */
122
+ currentPollingId : null ,
81
123
82
124
/**
83
125
* Initializer
@@ -260,8 +302,8 @@ const Authorization = WebexPlugin.extend({
260
302
* @emits #qRCodeLogin
261
303
*/
262
304
initQRCodeLogin ( ) {
263
- if ( this . pollingRequest ) {
264
- this . eventEmitter . emit ( ' qRCodeLogin' , {
305
+ if ( this . pollingTimer ) {
306
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
265
307
eventType : 'getUserCodeFailure' ,
266
308
data : { message : 'There is already a polling request' } ,
267
309
} ) ;
@@ -285,19 +327,19 @@ const Authorization = WebexPlugin.extend({
285
327
} )
286
328
. then ( ( res ) => {
287
329
const { user_code, verification_uri, verification_uri_complete} = res . body ;
288
- this . eventEmitter . emit ( ' qRCodeLogin' , {
330
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
289
331
eventType : 'getUserCodeSuccess' ,
290
332
userData : {
291
333
userCode : user_code ,
292
334
verificationUri : verification_uri ,
293
335
verificationUriComplete : verification_uri_complete ,
294
- }
336
+ } ,
295
337
} ) ;
296
338
// if device authorization success, then start to poll server to check whether the user has completed authorization
297
339
this . _startQRCodePolling ( res . body ) ;
298
340
} )
299
341
. catch ( ( res ) => {
300
- this . eventEmitter . emit ( ' qRCodeLogin' , {
342
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
301
343
eventType : 'getUserCodeFailure' ,
302
344
data : res . body ,
303
345
} ) ;
@@ -313,30 +355,36 @@ const Authorization = WebexPlugin.extend({
313
355
*/
314
356
_startQRCodePolling ( options = { } ) {
315
357
if ( ! options . device_code ) {
316
- this . eventEmitter . emit ( ' qRCodeLogin' , {
358
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
317
359
eventType : 'authorizationFailure' ,
318
360
data : { message : 'A deviceCode is required' } ,
319
361
} ) ;
320
362
return ;
321
363
}
322
364
323
- if ( this . pollingRequest ) {
324
- this . eventEmitter . emit ( ' qRCodeLogin' , {
365
+ if ( this . pollingTimer ) {
366
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
325
367
eventType : 'authorizationFailure' ,
326
368
data : { message : 'There is already a polling request' } ,
327
369
} ) ;
328
370
return ;
329
371
}
330
372
331
- const { device_code : deviceCode , interval = 2 , expires_in : expiresIn = 300 } = options ;
373
+ const { device_code : deviceCode , expires_in : expiresIn = 300 } = options ;
374
+ let interval = options . interval ?? 2 ;
332
375
333
- let attempts = 0 ;
334
- const maxAttempts = expiresIn / interval ;
376
+ this . pollingExpirationTimer = setTimeout ( ( ) => {
377
+ this . cancelQRCodePolling ( false ) ;
378
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
379
+ eventType : 'authorizationFailure' ,
380
+ data : { message : 'Authorization timed out' } ,
381
+ } ) ;
382
+ } , expiresIn * 1000 ) ;
335
383
336
- this . pollingRequest = setInterval ( ( ) => {
337
- attempts += 1 ;
384
+ const polling = ( ) => {
385
+ this . pollingId += 1 ;
386
+ this . currentPollingId = this . pollingId ;
338
387
339
- const currentAttempts = attempts ;
340
388
this . webex
341
389
. request ( {
342
390
method : 'POST' ,
@@ -354,43 +402,50 @@ const Authorization = WebexPlugin.extend({
354
402
} ,
355
403
} )
356
404
. then ( ( res ) => {
357
- if ( this . pollingRequest === null ) return ;
405
+ // if the pollingId has changed, it means that the polling request has been canceled
406
+ if ( this . currentPollingId !== this . pollingId ) return ;
358
407
359
- this . eventEmitter . emit ( ' qRCodeLogin' , {
408
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
360
409
eventType : 'authorizationSuccess' ,
361
410
data : res . body ,
362
411
} ) ;
363
412
this . cancelQRCodePolling ( ) ;
364
413
} )
365
414
. catch ( ( res ) => {
366
- if ( this . pollingRequest === null ) return ;
415
+ // if the pollingId has changed, it means that the polling request has been canceled
416
+ if ( this . currentPollingId !== this . pollingId ) return ;
367
417
368
- if ( currentAttempts >= maxAttempts ) {
369
- this . eventEmitter . emit ( 'qRCodeLogin' , {
370
- eventType : 'authorizationFailure' ,
371
- data : { message : 'Authorization timed out' }
372
- } ) ;
373
- this . cancelQRCodePolling ( ) ;
418
+ // When server sends 400 status code with message 'slow_down', it means that last request happened too soon.
419
+ // So, skip one interval and then poll again.
420
+ if ( res . statusCode === 400 && res . body . message === 'slow_down' ) {
421
+ schedulePolling ( interval * 2 ) ;
374
422
return ;
375
423
}
424
+
376
425
// if the statusCode is 428 which means that the authorization request is still pending
377
426
// as the end user hasn't yet completed the user-interaction steps. So keep polling.
378
427
if ( res . statusCode === 428 ) {
379
- this . eventEmitter . emit ( ' qRCodeLogin' , {
428
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
380
429
eventType : 'authorizationPending' ,
381
- data : res . body
430
+ data : res . body ,
382
431
} ) ;
432
+ schedulePolling ( interval ) ;
383
433
return ;
384
434
}
385
435
386
436
this . cancelQRCodePolling ( ) ;
387
437
388
- this . eventEmitter . emit ( ' qRCodeLogin' , {
438
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
389
439
eventType : 'authorizationFailure' ,
390
- data : res . body
440
+ data : res . body ,
391
441
} ) ;
392
442
} ) ;
393
- } , interval * 1000 ) ;
443
+ } ;
444
+
445
+ const schedulePolling = ( interval ) =>
446
+ ( this . pollingTimer = setTimeout ( polling , interval * 1000 ) ) ;
447
+
448
+ schedulePolling ( interval ) ;
394
449
} ,
395
450
396
451
/**
@@ -399,14 +454,19 @@ const Authorization = WebexPlugin.extend({
399
454
* @memberof AuthorizationBrowserFirstParty
400
455
* @returns {void }
401
456
*/
402
- cancelQRCodePolling ( ) {
403
- if ( this . pollingRequest ) {
404
- clearInterval ( this . pollingRequest ) ;
405
- this . eventEmitter . emit ( 'qRCodeLogin' , {
457
+ cancelQRCodePolling ( withCancelEvent = true ) {
458
+ if ( this . pollingTimer && withCancelEvent ) {
459
+ this . eventEmitter . emit ( Events . qRCodeLogin , {
406
460
eventType : 'pollingCanceled' ,
407
461
} ) ;
408
- this . pollingRequest = null ;
409
462
}
463
+
464
+ this . currentPollingId = null ;
465
+
466
+ clearTimeout ( this . pollingExpirationTimer ) ;
467
+ this . pollingExpirationTimer = null ;
468
+ clearTimeout ( this . pollingTimer ) ;
469
+ this . pollingTimer = null ;
410
470
} ,
411
471
412
472
/**
0 commit comments