diff --git a/src/core/Utils.js b/src/core/Utils.js index a7e7b7c2ac..1d78219622 100644 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -180,6 +180,12 @@ class Utils { } } + static getHostFromUrl(urlString) { + const url = new URL(urlString); + + return url.host + } + static parseUserAgent(ua = null) { try { const uaString = ua === null ? typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase() : '' : ''; diff --git a/src/streaming/constants/Constants.js b/src/streaming/constants/Constants.js index 89f5c4d391..27d6a4481f 100644 --- a/src/streaming/constants/Constants.js +++ b/src/streaming/constants/Constants.js @@ -289,6 +289,7 @@ export default { * @memberof Constants# * @static */ - ID3_SCHEME_ID_URI: 'https://aomedia.org/emsg/ID3' + ID3_SCHEME_ID_URI: 'https://aomedia.org/emsg/ID3', + COMMON_ACCESS_TOKEN_HEADER: 'common-access-token' } diff --git a/src/streaming/controllers/CommonAccessTokenController.js b/src/streaming/controllers/CommonAccessTokenController.js new file mode 100644 index 0000000000..8847bc2f5d --- /dev/null +++ b/src/streaming/controllers/CommonAccessTokenController.js @@ -0,0 +1,83 @@ +/** + * The copyright in this software is being made available under the BSD License, + * included below. This software may be subject to other third party and contributor + * rights, including patent rights, and no such rights are granted under this license. + * + * Copyright (c) 2013, Dash Industry Forum. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * Neither the name of Dash Industry Forum nor the names of its + * contributors may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +import FactoryMaker from '../../core/FactoryMaker.js'; +import Constants from '../constants/Constants.js'; +import Utils from '../../core/Utils.js'; + +function CommonAccessTokenController() { + + let instance, + hostTokenMap; + + function processResponseHeaders(httpResponse) { + if (!httpResponse || !httpResponse.headers || !httpResponse.request || !httpResponse.request.url) { + return + } + + const commonAccessTokenHeader = httpResponse.headers[Constants.COMMON_ACCESS_TOKEN_HEADER] + const host = Utils.getHostFromUrl(httpResponse.request.url) + hostTokenMap[host] = commonAccessTokenHeader + } + + function getCommonAccessTokenForUrl(url) { + if (!url) { + return null + } + + const host = Utils.getHostFromUrl(url); + return hostTokenMap[host] ? hostTokenMap[host] : null; + } + + function setup() { + _resetInitialSettings(); + } + + function reset() { + _resetInitialSettings() + } + + function _resetInitialSettings() { + hostTokenMap = {} + } + + instance = { + reset, + processResponseHeaders, + getCommonAccessTokenForUrl + }; + + setup(); + return instance; +} + +CommonAccessTokenController.__dashjs_factory_name = 'CommonAccessTokenController'; +export default FactoryMaker.getSingletonFactory(CommonAccessTokenController); diff --git a/src/streaming/net/HTTPLoader.js b/src/streaming/net/HTTPLoader.js index a2dafef08e..8050942188 100644 --- a/src/streaming/net/HTTPLoader.js +++ b/src/streaming/net/HTTPLoader.js @@ -42,6 +42,7 @@ import Events from '../../core/events/Events.js'; import Settings from '../../core/Settings.js'; import Constants from '../constants/Constants.js'; import CustomParametersModel from '../models/CustomParametersModel.js'; +import CommonAccessTokenController from '../controllers/CommonAccessTokenController.js'; /** * @module HTTPLoader @@ -73,6 +74,7 @@ function HTTPLoader(cfg) { xhrLoader, fetchLoader, customParametersModel, + commonAccessTokenController, logger; function setup() { @@ -83,6 +85,7 @@ function HTTPLoader(cfg) { cmcdModel = CmcdModel(context).getInstance(); cmsdModel = CmsdModel(context).getInstance(); customParametersModel = CustomParametersModel(context).getInstance(); + commonAccessTokenController = CommonAccessTokenController(context).getInstance(); downloadErrorToRequestTypeMap = { [HTTPRequest.MPD_TYPE]: errors.DOWNLOAD_ERROR_ID_MANIFEST_CODE, @@ -95,6 +98,16 @@ function HTTPLoader(cfg) { }; } + function setConfig(config) { + if (!config) { + return; + } + + if (config.commonAccessTokenController) { + commonAccessTokenController = config.commonAccessTokenController + } + } + /** * Initiates a download of the resource described by config.request. * @param {Object} config - contains request (FragmentRequest or derived type), and callbacks @@ -195,13 +208,15 @@ function HTTPLoader(cfg) { } }; - const _oncomplete = function() { + const _oncomplete = function () { // Update request timing info requestObject.startDate = requestStartTime; requestObject.endDate = new Date(); requestObject.firstByteDate = requestObject.firstByteDate || requestStartTime; httpResponse.resourceTiming.responseEnd = requestObject.endDate.getTime(); + commonAccessTokenController.processResponseHeaders(httpResponse) + // If enabled the ResourceTimingApi we add the corresponding information to the request object. // These values are more accurate and can be used by the ThroughputController later _addResourceTimingValues(httpRequest, httpResponse); @@ -286,12 +301,13 @@ function HTTPLoader(cfg) { eventBus.trigger(Events.ATTEMPT_BACKGROUND_SYNC); } } - } catch (e) {} + } catch (e) { + } _retriggerRequest(); } - const _loadRequest = function(loader, httpRequest, httpResponse) { + const _loadRequest = function (loader, httpRequest, httpResponse) { return new Promise((resolve) => { _applyRequestInterceptors(httpRequest).then((_httpRequest) => { httpRequest = _httpRequest; @@ -580,6 +596,12 @@ function HTTPLoader(cfg) { }) request.url = Utils.addAditionalQueryParameterToUrl(request.url, queryParams); } + + // Add headers from CommonAccessToken + const commonAccessToken = commonAccessTokenController.getCommonAccessTokenForUrl(request.url) + if (commonAccessToken) { + request.headers[Constants.COMMON_ACCESS_TOKEN_HEADER] = commonAccessToken + } } /** @@ -644,7 +666,8 @@ function HTTPLoader(cfg) { instance = { load, - abort + abort, + setConfig }; setup(); diff --git a/test/unit/core.Utils.js b/test/unit/core.Utils.js index 3e06cb4d6a..10555d34f1 100644 --- a/test/unit/core.Utils.js +++ b/test/unit/core.Utils.js @@ -109,4 +109,16 @@ describe('Utils', () => { expect(Utils.stringHasProtocol('dash.akamaized.net')).to.be.false; }) }) + + describe('getHostFromUrl', () => { + + it('Should return a valid host for an http URL', () => { + expect(Utils.getHostFromUrl('http://dash.akamaized.net')).to.be.equal('dash.akamaized.net'); + }) + + it('Should return a valid host for an http URL', () => { + expect(Utils.getHostFromUrl('https://dash.akamaized.net')).to.be.equal('dash.akamaized.net'); + }) + + }) }) diff --git a/test/unit/streaming.controllers.CommonAccessTokenController.js b/test/unit/streaming.controllers.CommonAccessTokenController.js new file mode 100644 index 0000000000..b111ced513 --- /dev/null +++ b/test/unit/streaming.controllers.CommonAccessTokenController.js @@ -0,0 +1,46 @@ +import {expect} from 'chai'; +import CommonAccessTokenController from '../../src/streaming/controllers/CommonAccessTokenController.js'; +import Constants from '../../src/streaming/constants/Constants.js' + +describe('CommonAccessTokenController', () => { + + let commonAccessTokenController; + + beforeEach(() => { + commonAccessTokenController = CommonAccessTokenController({}).getInstance(); + }) + + it('getCommonAccessTokenForUrl should return null if no url is provided', () => { + expect(commonAccessTokenController.getCommonAccessTokenForUrl()).to.be.null + }) + + it('getCommonAccessTokenForUrl should return null if no value was added', () => { + const url = 'http://someurl.com' + expect(commonAccessTokenController.getCommonAccessTokenForUrl(url)).to.be.null + }) + + it('getCommonAccessTokenForUrl should return null if the added response does not contain the right header', () => { + const httpResponse = { + headers: { + 'someheader': 'someheadervalue' + }, + request: { + url: 'http://someurl.com' + } + } + commonAccessTokenController.processResponseHeaders(httpResponse); + expect(commonAccessTokenController.getCommonAccessTokenForUrl(httpResponse.request.url)).to.be.null + }) + + it('getCommonAccessTokenForUrl should return a CAT if the added response does contain the right header', () => { + const httpResponse = { + request: { + url: 'http://someurl.com' + } + } + httpResponse.headers = {}; + httpResponse.headers[Constants.COMMON_ACCESS_TOKEN_HEADER] = 'cat'; + commonAccessTokenController.processResponseHeaders(httpResponse); + expect(commonAccessTokenController.getCommonAccessTokenForUrl(httpResponse.request.url)).to.be.equal('cat') + }) +})