From 9e874ba7f52e71ebf6bf4ad0ce865efb62d6227c Mon Sep 17 00:00:00 2001 From: Mingze Xiao Date: Wed, 23 Oct 2019 15:48:00 -0700 Subject: [PATCH 1/5] chore(mediaviewer): refresh token if token expired --- src/lib/Preview.js | 5 +++- src/lib/viewers/media/DashViewer.js | 12 +++++++++ src/lib/viewers/media/MediaBaseViewer.js | 33 +++++++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/lib/Preview.js b/src/lib/Preview.js index 031087f8e..8a4dbc952 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -109,7 +109,7 @@ class Preview extends EventEmitter { /** @property {Object} - Map of disabled viewer names */ disabledViewers = {}; - /** @property {string} - Access token */ + /** @property {string|Function} - Access token */ token = ''; /** @property {Object} - Current viewer instance */ @@ -974,6 +974,9 @@ class Preview extends EventEmitter { // Add the response interceptor to the preview instance this.options.responseInterceptor = options.responseInterceptor; + // Add the token generator to refresh the token if necessary + this.options.tokenGenerator = options.token; + // Disable or enable viewers based on viewer options Object.keys(this.options.viewers).forEach(viewerName => { const isDisabled = this.options.viewers[viewerName].disabled; diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index 592a2b539..0e7768f46 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -505,6 +505,18 @@ class DashViewer extends VideoBaseViewer { this.handleDownloadError(error, downloadURL); return; } + + if ( + normalizedShakaError.code === shaka.util.Error.Code.BAD_HTTP_STATUS && + normalizedShakaError.data[1] === 401 // token expired + ) { + this.refreshToken().then(newToken => { + this.options.token = newToken; + this.player.retryStreaming(); + }); + return; + } + // critical error this.triggerError(error); } diff --git a/src/lib/viewers/media/MediaBaseViewer.js b/src/lib/viewers/media/MediaBaseViewer.js index 0eef859ab..1e6120428 100644 --- a/src/lib/viewers/media/MediaBaseViewer.js +++ b/src/lib/viewers/media/MediaBaseViewer.js @@ -4,6 +4,7 @@ import Browser from '../../Browser'; import MediaControls from './MediaControls'; import PreviewError from '../../PreviewError'; import Timer from '../../Timer'; +import getTokens from '../../tokens'; import { CLASS_ELEM_KEYBOARD_FOCUS, CLASS_HIDDEN, CLASS_IS_BUFFERING, CLASS_IS_VISIBLE } from '../../constants'; import { ERROR_CODE, MEDIA_METRIC, MEDIA_METRIC_EVENTS, VIEWER_EVENT } from '../../events'; import { getProp } from '../../util'; @@ -21,6 +22,7 @@ const INITIAL_TIME_IN_SECONDS = 0; const ONE_MINUTE_IN_SECONDS = 60; const ONE_HOUR_IN_SECONDS = 60 * ONE_MINUTE_IN_SECONDS; const PLAY_PROMISE_NOT_SUPPORTED = 'play_promise_not_supported'; +const MEDIA_TOKEN_EXPIRE_ERROR = 'PIPELINE_ERROR_READ'; class MediaBaseViewer extends BaseViewer { /** @property {Object} - Keeps track of the different media metrics */ @@ -229,6 +231,11 @@ class MediaBaseViewer extends BaseViewer { return; } + if (this.loaded) { + this.play(this.currentTime); + return; + } + this.loadUI(); if (this.isAutoplayEnabled()) { @@ -260,6 +267,17 @@ class MediaBaseViewer extends BaseViewer { this.wrapperEl.classList.add(CLASS_IS_VISIBLE); } + /** + * Refresh the access token + * + * @private + * @return {Promise} + */ + refreshToken = async () => { + const tokenMap = await getTokens(this.options.file.id, this.options.tokenGenerator); + return tokenMap[this.options.file.id]; + }; + /** * Handles media element loading errors. * @@ -273,7 +291,20 @@ class MediaBaseViewer extends BaseViewer { console.error(err); const errorCode = getProp(err, 'target.error.code'); - const errorDetails = errorCode ? { error_code: errorCode } : {}; + const errorMessage = getProp(err, 'target.error.message'); + const errorDetails = errorCode ? { error_code: errorCode, error_message: errorMessage } : {}; + + // refresh the token if token expired + if (errorCode === MediaError.MEDIA_ERR_NETWORK && errorMessage.includes(MEDIA_TOKEN_EXPIRE_ERROR)) { + this.refreshToken().then(newToken => { + const { currentTime } = this.mediaEl; + this.currentTime = currentTime; + this.options.token = newToken; + this.mediaUrl = this.createContentUrlWithAuthParams(this.options.representation.content.url_template); + this.mediaEl.src = this.mediaUrl; + }); + return; + } const error = new PreviewError(ERROR_CODE.LOAD_MEDIA, __('error_refresh'), errorDetails); From a91af2a3081add4d4a7e1f091872ee62118ec03e Mon Sep 17 00:00:00 2001 From: Mingze Xiao Date: Wed, 23 Oct 2019 17:09:58 -0700 Subject: [PATCH 2/5] chore(mediaviewer): add test --- .../media/__tests__/MediaBaseViewer-test.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js index c3e53285e..246019f8d 100644 --- a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js +++ b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js @@ -185,6 +185,30 @@ describe('lib/viewers/media/MediaBaseViewer', () => { }); }); + describe('refreshToken()', () => { + it('should return the same token if the tokenGenerator is a string', done => { + media.options.file = { + id: 'file_123', + }; + media.options.tokenGenerator = 'new_token'; + media.refreshToken().then(token => { + expect(token).to.equal('new_token'); + done(); + }); + }); + + it('should return a new token if the tokenGenerator is a function', done => { + media.options.file = { + id: 'file_123', + }; + media.options.tokenGenerator = id => Promise.resolve({ [id]: 'new_token' }); + media.refreshToken().then(token => { + expect(token).to.equal('new_token'); + done(); + }); + }); + }); + describe('errorHandler()', () => { it('should handle download error if the viewer was not yet loaded', () => { media.mediaUrl = 'foo'; From ab3b9856fb2b9b43f57283209483908dc8095223 Mon Sep 17 00:00:00 2001 From: Mingze Xiao Date: Thu, 24 Oct 2019 11:23:40 -0700 Subject: [PATCH 3/5] fix(mediaviewer): address comments --- src/lib/Preview.js | 22 +++-- src/lib/__tests__/Preview-test.js | 22 ++++- src/lib/events.js | 1 + src/lib/viewers/media/DashViewer.js | 38 ++++++-- src/lib/viewers/media/MediaBaseViewer.js | 94 +++++++++++++++---- .../media/__tests__/MediaBaseViewer-test.js | 53 ++++++----- 6 files changed, 174 insertions(+), 56 deletions(-) diff --git a/src/lib/Preview.js b/src/lib/Preview.js index 8a4dbc952..ffb50cfe6 100644 --- a/src/lib/Preview.js +++ b/src/lib/Preview.js @@ -109,9 +109,6 @@ class Preview extends EventEmitter { /** @property {Object} - Map of disabled viewer names */ disabledViewers = {}; - /** @property {string|Function} - Access token */ - token = ''; - /** @property {Object} - Current viewer instance */ viewer; @@ -974,9 +971,6 @@ class Preview extends EventEmitter { // Add the response interceptor to the preview instance this.options.responseInterceptor = options.responseInterceptor; - // Add the token generator to refresh the token if necessary - this.options.tokenGenerator = options.token; - // Disable or enable viewers based on viewer options Object.keys(this.options.viewers).forEach(viewerName => { const isDisabled = this.options.viewers[viewerName].disabled; @@ -1005,6 +999,7 @@ class Preview extends EventEmitter { location: this.location, cache: this.cache, ui: this.ui, + refreshToken: this.refreshToken, }); } @@ -1885,6 +1880,21 @@ class Preview extends EventEmitter { const fileId = typeof fileIdOrFile === 'string' ? fileIdOrFile : fileIdOrFile.id; return getProp(this.previewOptions, `fileOptions.${fileId}.${optionName}`); } + + /** + * Refresh the access token + * + * @private + * @return {Promise} + */ + refreshToken = () => { + if (typeof this.previewOptions.token !== 'function') { + return Promise.reject(new Error('Token is not a function and cannot be refreshed.')); + } + return getTokens(this.file.id, this.previewOptions.token).then( + tokenOrTokenMap => tokenOrTokenMap[this.file.id], + ); + }; } global.Box = global.Box || {}; diff --git a/src/lib/__tests__/Preview-test.js b/src/lib/__tests__/Preview-test.js index b0db3fe09..d5b7df861 100644 --- a/src/lib/__tests__/Preview-test.js +++ b/src/lib/__tests__/Preview-test.js @@ -62,7 +62,6 @@ describe('lib/Preview', () => { expect(preview.file).to.deep.equal({}); expect(preview.options).to.deep.equal({}); expect(preview.disabledViewers).to.deep.equal({ Office: 1 }); - expect(preview.token).to.equal(''); expect(preview.loaders).to.equal(loaders); expect(preview.location.hostname).to.equal('localhost'); }); @@ -2837,5 +2836,26 @@ describe('lib/Preview', () => { expect(preview.getFileOption('123', 'fileVersionId')).to.equal(undefined); }); }); + + describe('refreshToken()', () => { + it('should return a new token if the previewOptions.token is a function', done => { + preview.file = { + id: 'file_123', + }; + preview.previewOptions.token = id => Promise.resolve({ [id]: 'new_token' }); + preview.refreshToken().then(token => { + expect(token).to.equal('new_token'); + done(); + }); + }); + + it('should reject if previewOptions.token is not a function', done => { + preview.previewOptions.token = 'token'; + preview.refreshToken().catch(error => { + expect(error.message).to.equal('Token is not a function and cannot be refreshed.'); + done(); + }); + }); + }); }); /* eslint-enable no-unused-expressions */ diff --git a/src/lib/events.js b/src/lib/events.js index 4569e0f82..d925c2ac1 100644 --- a/src/lib/events.js +++ b/src/lib/events.js @@ -45,6 +45,7 @@ export const ERROR_CODE = { PREFETCH_FILE: 'error_prefetch_file', RATE_LIMIT: 'error_rate_limit', SHAKA: 'error_shaka', + TOKEN_NOT_VALID: 'error_token_function_not_valid', UNSUPPORTED_FILE_TYPE: 'error_unsupported_file_type', VIEWER_LOAD_TIMEOUT: 'error_viewer_load_timeout', }; diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index 0e7768f46..b4dbe9458 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -40,6 +40,7 @@ class DashViewer extends VideoBaseViewer { this.loadeddataHandler = this.loadeddataHandler.bind(this); this.requestFilter = this.requestFilter.bind(this); this.shakaErrorHandler = this.shakaErrorHandler.bind(this); + this.restartPlayback = this.restartPlayback.bind(this); } /** @@ -477,6 +478,33 @@ class DashViewer extends VideoBaseViewer { this.hideLoadingIcon(); } + /** + * Determain whether is an expired token error + * + * @private + * @param {PreviewError} error + * @return {bool} + */ + isExpiredTokenError(error) { + const errorDetails = error.details; + // unauthorized error may be cuased by token expired + return errorDetails.code === shaka.util.Error.Code.BAD_HTTP_STATUS && errorDetails.data[1] === 401; + } + + /** + * Restart playback using new token + * + * @private + * @param {string} newToken - new token + * @return {void} + */ + restartPlayback(newToken) { + this.options.token = newToken; + if (this.player.retryStreaming()) { + this.retryTokenCount = 0; + } + } + /** * Handles errors thrown by shaka player. See https://shaka-player-demo.appspot.com/docs/api/shaka.util.Error.html * @@ -491,6 +519,7 @@ class DashViewer extends VideoBaseViewer { __('error_refresh'), { code: normalizedShakaError.code, + data: normalizedShakaError.data, severity: normalizedShakaError.severity, }, `Shaka error. Code = ${normalizedShakaError.code}, Category = ${ @@ -506,14 +535,7 @@ class DashViewer extends VideoBaseViewer { return; } - if ( - normalizedShakaError.code === shaka.util.Error.Code.BAD_HTTP_STATUS && - normalizedShakaError.data[1] === 401 // token expired - ) { - this.refreshToken().then(newToken => { - this.options.token = newToken; - this.player.retryStreaming(); - }); + if (this.handleExpiredTokenError(error)) { return; } diff --git a/src/lib/viewers/media/MediaBaseViewer.js b/src/lib/viewers/media/MediaBaseViewer.js index 1e6120428..2edbb38ee 100644 --- a/src/lib/viewers/media/MediaBaseViewer.js +++ b/src/lib/viewers/media/MediaBaseViewer.js @@ -1,10 +1,10 @@ import debounce from 'lodash/debounce'; +import isEmpty from 'lodash/isEmpty'; import BaseViewer from '../BaseViewer'; import Browser from '../../Browser'; import MediaControls from './MediaControls'; import PreviewError from '../../PreviewError'; import Timer from '../../Timer'; -import getTokens from '../../tokens'; import { CLASS_ELEM_KEYBOARD_FOCUS, CLASS_HIDDEN, CLASS_IS_BUFFERING, CLASS_IS_VISIBLE } from '../../constants'; import { ERROR_CODE, MEDIA_METRIC, MEDIA_METRIC_EVENTS, VIEWER_EVENT } from '../../events'; import { getProp } from '../../util'; @@ -23,6 +23,7 @@ const ONE_MINUTE_IN_SECONDS = 60; const ONE_HOUR_IN_SECONDS = 60 * ONE_MINUTE_IN_SECONDS; const PLAY_PROMISE_NOT_SUPPORTED = 'play_promise_not_supported'; const MEDIA_TOKEN_EXPIRE_ERROR = 'PIPELINE_ERROR_READ'; +const MAX_RETRY_TOKEN = 3; // number of times to retry refreshing token for unauthorized error class MediaBaseViewer extends BaseViewer { /** @property {Object} - Keeps track of the different media metrics */ @@ -35,6 +36,9 @@ class MediaBaseViewer extends BaseViewer { [MEDIA_METRIC.watchLength]: 0, }; + /** @property {number} - Number of times refreshing token has been retried for unauthorized error */ + retryTokenCount = 0; + /** * @inheritdoc */ @@ -63,6 +67,7 @@ class MediaBaseViewer extends BaseViewer { this.toggleMute = this.toggleMute.bind(this); this.togglePlay = this.togglePlay.bind(this); this.updateVolumeIcon = this.updateVolumeIcon.bind(this); + this.restartPlayback = this.restartPlayback.bind(this); window.addEventListener('beforeunload', this.processMetrics); } @@ -231,8 +236,11 @@ class MediaBaseViewer extends BaseViewer { return; } + // If it's already loaded, this handler should be triggered by refreshing token, + // so we want to continue playing from the previous time, and don't need to load UI again. if (this.loaded) { this.play(this.currentTime); + this.retryTokenCount = 0; return; } @@ -268,15 +276,72 @@ class MediaBaseViewer extends BaseViewer { } /** - * Refresh the access token + * Determain whether is an expired token error * - * @private - * @return {Promise} + * @protected + * @param {PreviewError} error + * @return {bool} */ - refreshToken = async () => { - const tokenMap = await getTokens(this.options.file.id, this.options.tokenGenerator); - return tokenMap[this.options.file.id]; - }; + isExpiredTokenError(error) { + const errorDetails = error.details; + return ( + !isEmpty(errorDetails) && + errorDetails.error_code === MediaError.MEDIA_ERR_NETWORK && + errorDetails.error_message.includes(MEDIA_TOKEN_EXPIRE_ERROR) + ); + } + + /** + * Restart playback using new token + * + * @protected + * @param {string} newToken - new token + * @return {void} + */ + restartPlayback(newToken) { + const { currentTime } = this.mediaEl; + this.currentTime = currentTime; + this.options.token = newToken; + this.mediaUrl = this.createContentUrlWithAuthParams(this.options.representation.content.url_template); + this.mediaEl.src = this.mediaUrl; + } + + /** + * Handle expired token error + * + * @protected + * @param {PreviewError} error + * @return {boolean} True if it is a token error and is handled + */ + handleExpiredTokenError(error) { + if (this.isExpiredTokenError(error)) { + if (this.retryTokenCount >= MAX_RETRY_TOKEN) { + const tokenError = new PreviewError( + ERROR_CODE.TOKEN_NOT_VALID, + null, + { silent: true }, + 'Reach refreshing token limit for unauthorized error.', + ); + this.triggerError(tokenError); + } else { + this.options + .refreshToken() + .then(this.restartPlayback) + .catch(e => { + const tokenError = new PreviewError( + ERROR_CODE.TOKEN_NOT_VALID, + null, + { silent: true }, + e.message, + ); + this.triggerError(tokenError); + }); + this.retryTokenCount += 1; + } + return true; + } + return false; + } /** * Handles media element loading errors. @@ -293,21 +358,12 @@ class MediaBaseViewer extends BaseViewer { const errorCode = getProp(err, 'target.error.code'); const errorMessage = getProp(err, 'target.error.message'); const errorDetails = errorCode ? { error_code: errorCode, error_message: errorMessage } : {}; + const error = new PreviewError(ERROR_CODE.LOAD_MEDIA, __('error_refresh'), errorDetails); - // refresh the token if token expired - if (errorCode === MediaError.MEDIA_ERR_NETWORK && errorMessage.includes(MEDIA_TOKEN_EXPIRE_ERROR)) { - this.refreshToken().then(newToken => { - const { currentTime } = this.mediaEl; - this.currentTime = currentTime; - this.options.token = newToken; - this.mediaUrl = this.createContentUrlWithAuthParams(this.options.representation.content.url_template); - this.mediaEl.src = this.mediaUrl; - }); + if (this.handleExpiredTokenError(error)) { return; } - const error = new PreviewError(ERROR_CODE.LOAD_MEDIA, __('error_refresh'), errorDetails); - if (!this.isLoaded()) { this.handleDownloadError(error, this.mediaUrl); } else { diff --git a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js index 246019f8d..abe34fc2b 100644 --- a/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js +++ b/src/lib/viewers/media/__tests__/MediaBaseViewer-test.js @@ -4,7 +4,10 @@ import MediaBaseViewer from '../MediaBaseViewer'; import BaseViewer from '../../BaseViewer'; import Timer from '../../../Timer'; import { CLASS_ELEM_KEYBOARD_FOCUS } from '../../../constants'; -import { VIEWER_EVENT } from '../../../events'; +import { ERROR_CODE, VIEWER_EVENT } from '../../../events'; +import PreviewError from '../../../PreviewError'; + +const MAX_RETRY_TOKEN = 3; // number of times to retry refreshing token for unauthorized error let media; let stubs; @@ -185,36 +188,42 @@ describe('lib/viewers/media/MediaBaseViewer', () => { }); }); - describe('refreshToken()', () => { - it('should return the same token if the tokenGenerator is a string', done => { - media.options.file = { - id: 'file_123', - }; - media.options.tokenGenerator = 'new_token'; - media.refreshToken().then(token => { - expect(token).to.equal('new_token'); - done(); - }); + describe('handleExpiredTokenError()', () => { + it('should not trigger error if is not an ExpiredTokenError', () => { + sandbox.stub(media, 'isExpiredTokenError').returns(false); + sandbox.stub(media, 'triggerError'); + const error = new PreviewError(ERROR_CODE.LOAD_MEDIA); + media.handleExpiredTokenError(error); + expect(media.triggerError).to.not.be.called; }); - it('should return a new token if the tokenGenerator is a function', done => { - media.options.file = { - id: 'file_123', - }; - media.options.tokenGenerator = id => Promise.resolve({ [id]: 'new_token' }); - media.refreshToken().then(token => { - expect(token).to.equal('new_token'); - done(); - }); + it('should trigger error if retry token count reaches max retry limit', () => { + media.retryTokenCount = MAX_RETRY_TOKEN + 1; + sandbox.stub(media, 'isExpiredTokenError').returns(true); + sandbox.stub(media, 'triggerError'); + const error = new PreviewError(ERROR_CODE.LOAD_MEDIA); + media.handleExpiredTokenError(error); + expect(media.triggerError).to.be.calledWith(sinon.match.has('code', ERROR_CODE.TOKEN_NOT_VALID)); + }); + + it('should call refreshToken if retry token count did not reach max retry limit', () => { + media.retryTokenCount = 0; + sandbox.stub(media, 'isExpiredTokenError').returns(true); + media.options.refreshToken = sandbox.stub().returns(Promise.resolve()); + const error = new PreviewError(ERROR_CODE.LOAD_MEDIA); + media.handleExpiredTokenError(error); + + expect(media.options.refreshToken).to.be.called; + expect(media.retryTokenCount).to.equal(1); }); }); describe('errorHandler()', () => { it('should handle download error if the viewer was not yet loaded', () => { + const err = new Error(); media.mediaUrl = 'foo'; sandbox.stub(media, 'isLoaded').returns(false); sandbox.stub(media, 'handleDownloadError'); - const err = new Error(); media.errorHandler(err); @@ -222,9 +231,9 @@ describe('lib/viewers/media/MediaBaseViewer', () => { }); it('should trigger an error if Preview is already loaded', () => { + const err = new Error(); sandbox.stub(media, 'isLoaded').returns(true); sandbox.stub(media, 'triggerError'); - const err = new Error(); media.errorHandler(err); From 3de63aa66111f336cfd4673defaf77da87d124be Mon Sep 17 00:00:00 2001 From: Mingze Xiao Date: Mon, 28 Oct 2019 14:16:28 -0700 Subject: [PATCH 4/5] fix(mediaviewer): address comments --- src/lib/viewers/media/DashViewer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index b4dbe9458..d61b7f422 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -527,6 +527,10 @@ class DashViewer extends VideoBaseViewer { }, Severity = ${normalizedShakaError.severity}, Data = ${normalizedShakaError.data.toString()}`, ); + if (this.handleExpiredTokenError(error)) { + return; + } + if (normalizedShakaError.severity > SHAKA_CODE_ERROR_RECOVERABLE) { // Anything greater than a recoverable error should be critical if (normalizedShakaError.code === shaka.util.Error.Code.HTTP_ERROR) { @@ -535,10 +539,6 @@ class DashViewer extends VideoBaseViewer { return; } - if (this.handleExpiredTokenError(error)) { - return; - } - // critical error this.triggerError(error); } From f67a784401bcbbd81527ec9136bbc8cc12d0e023 Mon Sep 17 00:00:00 2001 From: Mingze Xiao Date: Tue, 29 Oct 2019 13:49:10 -0700 Subject: [PATCH 5/5] fix(mediaviewer): address comments --- src/lib/viewers/media/DashViewer.js | 9 ++++----- src/lib/viewers/media/MediaBaseViewer.js | 11 +++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib/viewers/media/DashViewer.js b/src/lib/viewers/media/DashViewer.js index d61b7f422..13bf53126 100644 --- a/src/lib/viewers/media/DashViewer.js +++ b/src/lib/viewers/media/DashViewer.js @@ -482,13 +482,12 @@ class DashViewer extends VideoBaseViewer { * Determain whether is an expired token error * * @private - * @param {PreviewError} error + * @param {Object} details - error details * @return {bool} */ - isExpiredTokenError(error) { - const errorDetails = error.details; - // unauthorized error may be cuased by token expired - return errorDetails.code === shaka.util.Error.Code.BAD_HTTP_STATUS && errorDetails.data[1] === 401; + isExpiredTokenError({ details }) { + // unauthorized error may be caused by token expired + return details.code === shaka.util.Error.Code.BAD_HTTP_STATUS && details.data[1] === 401; } /** diff --git a/src/lib/viewers/media/MediaBaseViewer.js b/src/lib/viewers/media/MediaBaseViewer.js index 2edbb38ee..73b2b80b3 100644 --- a/src/lib/viewers/media/MediaBaseViewer.js +++ b/src/lib/viewers/media/MediaBaseViewer.js @@ -279,15 +279,14 @@ class MediaBaseViewer extends BaseViewer { * Determain whether is an expired token error * * @protected - * @param {PreviewError} error + * @param {Object} details - error details * @return {bool} */ - isExpiredTokenError(error) { - const errorDetails = error.details; + isExpiredTokenError({ details }) { return ( - !isEmpty(errorDetails) && - errorDetails.error_code === MediaError.MEDIA_ERR_NETWORK && - errorDetails.error_message.includes(MEDIA_TOKEN_EXPIRE_ERROR) + !isEmpty(details) && + details.error_code === MediaError.MEDIA_ERR_NETWORK && + details.error_message.includes(MEDIA_TOKEN_EXPIRE_ERROR) ); }