From 1b19f5f1c2173f4c8ed149f97cc5be20ce82ce10 Mon Sep 17 00:00:00 2001 From: vseventer Date: Tue, 7 Apr 2020 13:57:05 -0400 Subject: [PATCH 01/51] [AD-469] Add player vendor. --- modules/spotxBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index a8d874c57e9..21fee86b0a4 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -83,6 +83,7 @@ export const spec = { const ext = { sdk_name: 'Prebid 1+', + player_vendor: 'SpotXJW', versionOrtb: ORTB_VERSION }; From e4d5cf17da99893c46df9ccce0211722f0b94b57 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Sun, 5 Jul 2020 22:34:51 -0400 Subject: [PATCH 02/51] ssets up targeting module --- modules/jwplayer/index.js | 0 modules/jwplayerTargeting.js | 81 ++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 modules/jwplayer/index.js create mode 100644 modules/jwplayerTargeting.js diff --git a/modules/jwplayer/index.js b/modules/jwplayer/index.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js new file mode 100644 index 00000000000..b8cd99cde83 --- /dev/null +++ b/modules/jwplayerTargeting.js @@ -0,0 +1,81 @@ +import { config } from '../src/config.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { submodule } from '../src/hook.js'; + +// const segCache = {}; +export function getTargetingInfoForBid(bid) { + +} + +let subModules = []; + +/** + * enable submodule in User ID + * @param {RtdSubmodule} submodule + */ +export function attachProvider(submodule) { + subModules.push(submodule); +} + +/** + * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests` + * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information + */ +export function getTargetingForBid(bidRequest) { + /* + * */ + console.log('karim getTargetingForBid'); + const jwpTargeting = bidRequest.jwpTargeting; + if (jwpTargeting) { + /* + jwpTargeting.playerID + jwpTargeting.mediaID + */ + } +} + +export function setup () { + console.log('karim setup'); + config.getConfig('jwpTargeting', (config) => { + getTargetingForBid(); + // fetch media ids + console.log('karim jwpTargeting set:', config); + }); +} + +export function getMediaId(mediaId) { + let ajax = ajaxBuilder(1500); + ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}/`, + { + success: function (response, req) { + if (req.status === 200) { + try { + // const data = JSON.parse(response); + // if (data && data.p && data.kn) { + // setData({p: data.p, kn: data.kn, pmd: data.pmd}); + // } else { + // setData({}); + // } + // addBrowsiTag(data); + } catch (err) { + // utils.logError('unable to parse data'); + // setData({}) + } + } else if (req.status === 204) { + // unrecognized site key + // setData({}); + } + }, + error: function () { + // setData({}); + // utils.logError('unable to get prediction data'); + } + } + ); +} + +console.log('karim'); + +submodule('jwplayer', getTargetingInfoForBid); +setup(); +// submodule('jwplayer', getTargetingInfoForBid); From 88ec998cfc5fbd17bba7882ebdc318858f06f218 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 6 Jul 2020 18:34:45 -0400 Subject: [PATCH 03/51] implements getTargeting --- modules/jwplayer/index.js | 0 modules/jwplayerTargeting.js | 105 ++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 52 deletions(-) delete mode 100644 modules/jwplayer/index.js diff --git a/modules/jwplayer/index.js b/modules/jwplayer/index.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index b8cd99cde83..665c893bd82 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -1,81 +1,82 @@ -import { config } from '../src/config.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; +import config from '../src/config.js'; +import ajaxBuilder from '../src/ajax.js'; +import logError from '../src/utils.js'; -// const segCache = {}; -export function getTargetingInfoForBid(bid) { - -} - -let subModules = []; - -/** - * enable submodule in User ID - * @param {RtdSubmodule} submodule - */ -export function attachProvider(submodule) { - subModules.push(submodule); -} +const segCache = {}; /** * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests` * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information */ export function getTargetingForBid(bidRequest) { - /* - * */ console.log('karim getTargetingForBid'); const jwpTargeting = bidRequest.jwpTargeting; - if (jwpTargeting) { - /* - jwpTargeting.playerID - jwpTargeting.mediaID - */ + if (!jwpTargeting) { + return []; } + const { mediaID, playerID } = jwpTargeting; + let segments = segCache[mediaID]; + if (segments) { + return segments; + } + + const player = getPlayer(playerID); + if (!player) { + return []; + } + + let item = player.getPlaylist().filter(item => item.mediaid === mediaID); + if (item) { + segments = item.jwpseg; + segCache[mediaID] = segments; + return segments; + } + return player.getPlaylistItem().jwpseg; +} + +function getPlayer(playerID) { + return null; } -export function setup () { - console.log('karim setup'); +function setup () { config.getConfig('jwpTargeting', (config) => { - getTargetingForBid(); // fetch media ids - console.log('karim jwpTargeting set:', config); + const targeting = config.jwpTargeting; + if (!targeting) { + return; + } + const mediaIDs = targeting.mediaIDs; + mediaIDs.forEach(mediaID => { + console.log(mediaID); + fetchTargetingForMediaId(mediaID); + }) }); } -export function getMediaId(mediaId) { +function fetchTargetingForMediaId(mediaId) { let ajax = ajaxBuilder(1500); - ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}/`, + ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { - success: function (response, req) { - if (req.status === 200) { - try { - // const data = JSON.parse(response); - // if (data && data.p && data.kn) { - // setData({p: data.p, kn: data.kn, pmd: data.pmd}); - // } else { - // setData({}); - // } - // addBrowsiTag(data); - } catch (err) { - // utils.logError('unable to parse data'); - // setData({}) + success: function (response) { + try { + const data = JSON.parse(response); + if (!data) { + return; } - } else if (req.status === 204) { - // unrecognized site key - // setData({}); + const jwpseg = data.playlist[0].jwpseg; + if (jwpseg) { + segCache[mediaId] = jwpseg; + console.log('writing to cache: ', segCache); + } + } catch (err) { + logError('failed to parse response'); } }, error: function () { - // setData({}); - // utils.logError('unable to get prediction data'); + logError('failed to retrieve targeting information'); } } ); } -console.log('karim'); - -submodule('jwplayer', getTargetingInfoForBid); setup(); -// submodule('jwplayer', getTargetingInfoForBid); From 7fbdbeee0076b79d8d97e025ea85d1c31d2713c8 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Tue, 7 Jul 2020 13:22:31 -0400 Subject: [PATCH 04/51] implements getPlayer --- modules/jwplayerTargeting.js | 44 ++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 665c893bd82..d45ba4ad7af 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -4,6 +4,21 @@ import logError from '../src/utils.js'; const segCache = {}; +function setup () { + config.getConfig('jwpTargeting', (config) => { + // fetch media ids + const targeting = config.jwpTargeting; + if (!targeting) { + return; + } + const mediaIDs = targeting.mediaIDs; + mediaIDs.forEach(mediaID => { + console.log(mediaID); + fetchTargetingForMediaId(mediaID); + }) + }); +} + /** * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests` * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information @@ -35,26 +50,21 @@ export function getTargetingForBid(bidRequest) { } function getPlayer(playerID) { - return null; -} - -function setup () { - config.getConfig('jwpTargeting', (config) => { - // fetch media ids - const targeting = config.jwpTargeting; - if (!targeting) { - return; - } - const mediaIDs = targeting.mediaIDs; - mediaIDs.forEach(mediaID => { - console.log(mediaID); - fetchTargetingForMediaId(mediaID); - }) - }); + var jwplayer = window.jwplayer; + if (!jwplayer) { + logError('jwplayer.js was not found on page'); + return; + } + const player = jwplayer(playerID); + if (!player || !player.getPlaylist) { + logError('player ID did not match any players'); + return; + } + return player; } function fetchTargetingForMediaId(mediaId) { - let ajax = ajaxBuilder(1500); + const ajax = ajaxBuilder(1500); ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { success: function (response) { From 041c53726f7afae5bb63058d87820f5eabd4041b Mon Sep 17 00:00:00 2001 From: karimJWP Date: Tue, 7 Jul 2020 17:39:20 -0400 Subject: [PATCH 05/51] blocks bids until all targeting requests complete --- modules/jwplayerTargeting.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index d45ba4ad7af..4eb493b83ee 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -1,8 +1,12 @@ import config from '../src/config.js'; import ajaxBuilder from '../src/ajax.js'; import logError from '../src/utils.js'; +import getGlobal from '../src/prebidGlobal.js'; const segCache = {}; +let pendingRequests = 0; +let requestTimeout; +let resumeBidRequest; function setup () { config.getConfig('jwpTargeting', (config) => { @@ -12,11 +16,26 @@ function setup () { return; } const mediaIDs = targeting.mediaIDs; + pendingRequests = mediaIDs.length; mediaIDs.forEach(mediaID => { console.log(mediaID); fetchTargetingForMediaId(mediaID); }) }); + + getGlobal().requestBids.before(function(nextFn, reqBidsConfigObj) { + console.log('before requestBids'); + if (pendingRequests <= 0) { + nextFn.apply(this, [reqBidsConfigObj]); + return; + } + requestTimeout = setTimeout(() => { + console.log('Request for targeting info timed out') + nextFn.apply(this, [reqBidsConfigObj]); + }, 1500); + + resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); + }); } /** @@ -81,12 +100,29 @@ function fetchTargetingForMediaId(mediaId) { } catch (err) { logError('failed to parse response'); } + onRequestCompleted(); }, error: function () { logError('failed to retrieve targeting information'); + onRequestCompleted(); } } ); } +function onRequestCompleted() { + pendingRequests--; + if (pendingRequests > 0) { + return; + } + + if (requestTimeout) { + clearTimeout(requestTimeout); + } + + if (resumeBidRequest) { + resumeBidRequest(); + } +} + setup(); From daaa0fdb5e2f5439fdf92f2690070514559d006c Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 18:29:19 -0400 Subject: [PATCH 06/51] makes getTarget more resilient --- modules/jwplayerTargeting.js | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 4eb493b83ee..0217361163a 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -1,7 +1,7 @@ -import config from '../src/config.js'; -import ajaxBuilder from '../src/ajax.js'; -import logError from '../src/utils.js'; -import getGlobal from '../src/prebidGlobal.js'; +import { config } from '../src/config.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { logError } from '../src/utils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const segCache = {}; let pendingRequests = 0; @@ -24,16 +24,18 @@ function setup () { }); getGlobal().requestBids.before(function(nextFn, reqBidsConfigObj) { - console.log('before requestBids'); + console.error('karim before requestBids', reqBidsConfigObj); if (pendingRequests <= 0) { + console.log('karim no pending reqs'); nextFn.apply(this, [reqBidsConfigObj]); return; } requestTimeout = setTimeout(() => { - console.log('Request for targeting info timed out') + console.log('karim Request for targeting info timed out') nextFn.apply(this, [reqBidsConfigObj]); }, 1500); + console.log('karim storing req'); resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); }); } @@ -46,26 +48,40 @@ export function getTargetingForBid(bidRequest) { console.log('karim getTargetingForBid'); const jwpTargeting = bidRequest.jwpTargeting; if (!jwpTargeting) { + console.log('karim no targeting'); return []; } const { mediaID, playerID } = jwpTargeting; let segments = segCache[mediaID]; if (segments) { + console.log('karim got segs from cache'); return segments; } const player = getPlayer(playerID); if (!player) { + console.log('karim no player'); return []; } + console.log('karim playlist ? ', player.getPlaylist()); - let item = player.getPlaylist().filter(item => item.mediaid === mediaID); + const playlist = player.getPlaylist(); + let item = playlist.find(item => item.mediaid === mediaID); if (item) { segments = item.jwpseg; segCache[mediaID] = segments; - return segments; + console.log('karim mediaId: ', item); + console.log('karim got seg from playlist'); + return segments || []; + } + + item = player.getPlaylistItem(); + if (item) { + console.log('karim got seg from current item'); + console.log('karim getPlaylistItem ? ', player.getPlaylistItem()); + return item.jwpseg || []; } - return player.getPlaylistItem().jwpseg; + return []; } function getPlayer(playerID) { @@ -117,10 +133,12 @@ function onRequestCompleted() { } if (requestTimeout) { + console.log('karim clear Timeout bid req'); clearTimeout(requestTimeout); } if (resumeBidRequest) { + console.log('karim resume bid req'); resumeBidRequest(); } } From c04cb3ab280966da73ca40275602b3583b15ce0d Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 19:20:31 -0400 Subject: [PATCH 07/51] enables mdule hook --- modules/jwplayerTargeting.js | 38 +++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 0217361163a..f8b3ca0ce21 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -1,22 +1,20 @@ import { config } from '../src/config.js'; import { ajaxBuilder } from '../src/ajax.js'; -import { logError } from '../src/utils.js'; +import { logError, isPlainObject } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { module } from '../src/hook.js'; const segCache = {}; -let pendingRequests = 0; +let requestCount = 0; let requestTimeout; let resumeBidRequest; function setup () { config.getConfig('jwpTargeting', (config) => { // fetch media ids - const targeting = config.jwpTargeting; - if (!targeting) { - return; - } - const mediaIDs = targeting.mediaIDs; - pendingRequests = mediaIDs.length; + console.log('karim config'); + const mediaIDs = config.jwpTargeting.mediaIDs; + requestCount = mediaIDs.length; mediaIDs.forEach(mediaID => { console.log(mediaID); fetchTargetingForMediaId(mediaID); @@ -25,7 +23,7 @@ function setup () { getGlobal().requestBids.before(function(nextFn, reqBidsConfigObj) { console.error('karim before requestBids', reqBidsConfigObj); - if (pendingRequests <= 0) { + if (requestCount <= 0) { console.log('karim no pending reqs'); nextFn.apply(this, [reqBidsConfigObj]); return; @@ -127,8 +125,8 @@ function fetchTargetingForMediaId(mediaId) { } function onRequestCompleted() { - pendingRequests--; - if (pendingRequests > 0) { + requestCount--; + if (requestCount > 0) { return; } @@ -144,3 +142,21 @@ function onRequestCompleted() { } setup(); + +const sharedMethods = { + 'getTargetingForBid': getTargetingForBid +} +Object.freeze(sharedMethods); + +module('jwplayer', function shareJWPlayerUtilities(...args) { + if (!isPlainObject(args[0])) { + logError('JW Player module requires plain object to share methods with submodule'); + return; + } + function addMethods(object, func) { + for (let name in func) { + object[name] = func[name]; + } + } + addMethods(args[0], sharedMethods); +}); From ce8b2bc762e2fb4d4b6c1e7e3caa0889b2462001 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 22:40:14 -0400 Subject: [PATCH 08/51] replaces triple dot notation --- modules/appnexusBidAdapter.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 12bc6a8105c..8527de3054d 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -8,6 +8,10 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; +// import {getTargetingForBid} from './jwplayerTargeting.js'; +import { submodule } from '../src/hook.js'; + +export const jwplayerUtils = {}; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -247,6 +251,24 @@ export const spec = { } const request = formatRequest(payload, bidderRequest); + request.jwpTargeting = { + playerID: 'karim', + mediaID: '2XZFlRuo' + }; + const targ = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info: ', targ); + request.jwpTargeting = { + playerID: 'karim', + mediaID: 'kmoSeg' + }; + const targ2 = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info 2: ', targ2); + request.jwpTargeting = { + playerID: 'karim', + mediaID: 'kmoNoSeg' + }; + const targ3 = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info 3: ', targ3); return request; }, @@ -972,3 +994,4 @@ function parseMediaType(rtbBid) { } registerBidder(spec); +submodule('jwplayer', jwplayerUtils); From 2a72c0444e4e618039cb849b7a75aca913a2a8fe Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 23:29:22 -0400 Subject: [PATCH 09/51] Revert "replaces triple dot notation" This reverts commit 7a76ea62e1eb210c61abdc8e74da8ee68d47590c. --- modules/appnexusBidAdapter.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 8527de3054d..12bc6a8105c 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -8,10 +8,6 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; -// import {getTargetingForBid} from './jwplayerTargeting.js'; -import { submodule } from '../src/hook.js'; - -export const jwplayerUtils = {}; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -251,24 +247,6 @@ export const spec = { } const request = formatRequest(payload, bidderRequest); - request.jwpTargeting = { - playerID: 'karim', - mediaID: '2XZFlRuo' - }; - const targ = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info: ', targ); - request.jwpTargeting = { - playerID: 'karim', - mediaID: 'kmoSeg' - }; - const targ2 = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info 2: ', targ2); - request.jwpTargeting = { - playerID: 'karim', - mediaID: 'kmoNoSeg' - }; - const targ3 = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info 3: ', targ3); return request; }, @@ -994,4 +972,3 @@ function parseMediaType(rtbBid) { } registerBidder(spec); -submodule('jwplayer', jwplayerUtils); From 35c870a38c2ff518616807657cfbe9c3997a5ae2 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 23:30:31 -0400 Subject: [PATCH 10/51] Revert "Revert "replaces triple dot notation"" This reverts commit 130aa2adf5103013814537e8666e4ec49cd2d127. --- modules/appnexusBidAdapter.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 12bc6a8105c..8527de3054d 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -8,6 +8,10 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; +// import {getTargetingForBid} from './jwplayerTargeting.js'; +import { submodule } from '../src/hook.js'; + +export const jwplayerUtils = {}; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -247,6 +251,24 @@ export const spec = { } const request = formatRequest(payload, bidderRequest); + request.jwpTargeting = { + playerID: 'karim', + mediaID: '2XZFlRuo' + }; + const targ = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info: ', targ); + request.jwpTargeting = { + playerID: 'karim', + mediaID: 'kmoSeg' + }; + const targ2 = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info 2: ', targ2); + request.jwpTargeting = { + playerID: 'karim', + mediaID: 'kmoNoSeg' + }; + const targ3 = jwplayerUtils.getTargetingForBid(request); + console.log('karim target Info 3: ', targ3); return request; }, @@ -972,3 +994,4 @@ function parseMediaType(rtbBid) { } registerBidder(spec); +submodule('jwplayer', jwplayerUtils); From 3fb4ceee349a0f6e103ef1252dde888f35366220 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 8 Jul 2020 23:32:13 -0400 Subject: [PATCH 11/51] checks current item only if mediaid is missing --- modules/jwplayerTargeting.js | 58 ++++++++++++++---------------------- 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index f8b3ca0ce21..186a218b1ec 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -12,7 +12,6 @@ let resumeBidRequest; function setup () { config.getConfig('jwpTargeting', (config) => { // fetch media ids - console.log('karim config'); const mediaIDs = config.jwpTargeting.mediaIDs; requestCount = mediaIDs.length; mediaIDs.forEach(mediaID => { @@ -22,18 +21,14 @@ function setup () { }); getGlobal().requestBids.before(function(nextFn, reqBidsConfigObj) { - console.error('karim before requestBids', reqBidsConfigObj); if (requestCount <= 0) { - console.log('karim no pending reqs'); nextFn.apply(this, [reqBidsConfigObj]); return; } requestTimeout = setTimeout(() => { - console.log('karim Request for targeting info timed out') nextFn.apply(this, [reqBidsConfigObj]); }, 1500); - console.log('karim storing req'); resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); }); } @@ -43,43 +38,32 @@ function setup () { * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information */ export function getTargetingForBid(bidRequest) { - console.log('karim getTargetingForBid'); const jwpTargeting = bidRequest.jwpTargeting; if (!jwpTargeting) { - console.log('karim no targeting'); return []; } + const { mediaID, playerID } = jwpTargeting; let segments = segCache[mediaID]; if (segments) { - console.log('karim got segs from cache'); return segments; } const player = getPlayer(playerID); if (!player) { - console.log('karim no player'); return []; } - console.log('karim playlist ? ', player.getPlaylist()); - const playlist = player.getPlaylist(); - let item = playlist.find(item => item.mediaid === mediaID); + let item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); if (item) { segments = item.jwpseg; - segCache[mediaID] = segments; - console.log('karim mediaId: ', item); - console.log('karim got seg from playlist'); - return segments || []; } - item = player.getPlaylistItem(); - if (item) { - console.log('karim got seg from current item'); - console.log('karim getPlaylistItem ? ', player.getPlaylistItem()); - return item.jwpseg || []; + if (segments && mediaID) { + segCache[mediaID] = segments; } - return []; + + return segments || []; } function getPlayer(playerID) { @@ -88,6 +72,7 @@ function getPlayer(playerID) { logError('jwplayer.js was not found on page'); return; } + const player = jwplayer(playerID); if (!player || !player.getPlaylist) { logError('player ID did not match any players'); @@ -106,10 +91,15 @@ function fetchTargetingForMediaId(mediaId) { if (!data) { return; } - const jwpseg = data.playlist[0].jwpseg; + + const playlist = data.playlist; + if (!playlist || !playlist.length) { + return; + } + + const jwpseg = playlist[0].jwpseg; if (jwpseg) { segCache[mediaId] = jwpseg; - console.log('writing to cache: ', segCache); } } catch (err) { logError('failed to parse response'); @@ -131,32 +121,28 @@ function onRequestCompleted() { } if (requestTimeout) { - console.log('karim clear Timeout bid req'); clearTimeout(requestTimeout); } if (resumeBidRequest) { - console.log('karim resume bid req'); resumeBidRequest(); } } setup(); -const sharedMethods = { +const jwplayerUtilities = { 'getTargetingForBid': getTargetingForBid -} -Object.freeze(sharedMethods); +}; -module('jwplayer', function shareJWPlayerUtilities(...args) { - if (!isPlainObject(args[0])) { +module('jwplayer', function shareJWPlayerUtilities() { + const host = arguments[0]; + if (!isPlainObject(host)) { logError('JW Player module requires plain object to share methods with submodule'); return; } - function addMethods(object, func) { - for (let name in func) { - object[name] = func[name]; - } + + for (let method in jwplayerUtilities) { + host[method] = jwplayerUtilities[method]; } - addMethods(args[0], sharedMethods); }); From c94d05a4dfade01848a2bb66dd95f3afb7af66e6 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 9 Jul 2020 23:41:43 -0400 Subject: [PATCH 12/51] adds unit tests --- modules/jwplayerTargeting.js | 53 ++++-- modules/spotxBidAdapter.js | 1 - test/spec/modules/jwplayerTargeting_spec.js | 197 ++++++++++++++++++++ 3 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 test/spec/modules/jwplayerTargeting_spec.js diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 186a218b1ec..3479621968e 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -12,27 +12,32 @@ let resumeBidRequest; function setup () { config.getConfig('jwpTargeting', (config) => { // fetch media ids - const mediaIDs = config.jwpTargeting.mediaIDs; - requestCount = mediaIDs.length; - mediaIDs.forEach(mediaID => { - console.log(mediaID); - fetchTargetingForMediaId(mediaID); - }) + fetchTargetingInformation(config.jwpTargeting) }); - getGlobal().requestBids.before(function(nextFn, reqBidsConfigObj) { - if (requestCount <= 0) { - nextFn.apply(this, [reqBidsConfigObj]); - return; - } - requestTimeout = setTimeout(() => { - nextFn.apply(this, [reqBidsConfigObj]); - }, 1500); + getGlobal().requestBids.before(onFetchCompetion); +} - resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); +export function fetchTargetingInformation(jwTargeting) { + const mediaIDs = jwTargeting.mediaIDs; + requestCount = mediaIDs.length; + mediaIDs.forEach(mediaID => { + fetchTargetingForMediaId(mediaID); }); } +export function onFetchCompetion(nextFn, reqBidsConfigObj) { + if (requestCount <= 0) { + nextFn.apply(this, [reqBidsConfigObj]); + return; + } + requestTimeout = setTimeout(() => { + nextFn.apply(this, [reqBidsConfigObj]); + }, 1500); + + resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); +} + /** * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests` * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information @@ -43,7 +48,9 @@ export function getTargetingForBid(bidRequest) { return []; } - const { mediaID, playerID } = jwpTargeting; + const playerID = jwpTargeting.playerID; + let mediaID = jwpTargeting.mediaID; + // const { mediaID, playerID } = jwpTargeting; let segments = segCache[mediaID]; if (segments) { return segments; @@ -54,7 +61,14 @@ export function getTargetingForBid(bidRequest) { return []; } - let item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); + let item; + if (mediaID) { + item = player.getPlaylist().find(item => item.mediaid === mediaID); + } else { + item = player.getPlaylistItem(); + mediaID = item.mediaid; + } + if (item) { segments = item.jwpseg; } @@ -67,21 +81,24 @@ export function getTargetingForBid(bidRequest) { } function getPlayer(playerID) { + // console.log('window: ', window, this); var jwplayer = window.jwplayer; if (!jwplayer) { + console.log('karim no player.js'); logError('jwplayer.js was not found on page'); return; } const player = jwplayer(playerID); if (!player || !player.getPlaylist) { + console.log('karim no player instance'); logError('player ID did not match any players'); return; } return player; } -function fetchTargetingForMediaId(mediaId) { +export function fetchTargetingForMediaId(mediaId) { const ajax = ajaxBuilder(1500); ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index 21fee86b0a4..a8d874c57e9 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -83,7 +83,6 @@ export const spec = { const ext = { sdk_name: 'Prebid 1+', - player_vendor: 'SpotXJW', versionOrtb: ORTB_VERSION }; diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js new file mode 100644 index 00000000000..e39e8a31319 --- /dev/null +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -0,0 +1,197 @@ +import { fetchTargetingForMediaId, getTargetingForBid } from 'modules/jwplayerTargeting.js'; +import { server } from 'test/mocks/xhr.js'; + +const responseHeader = {'Content-Type': 'application/json'}; + +describe('jwplayer', function() { + const validSegments1 = ['test_seg_1', 'test_seg_2']; + + describe('Fetch targeting for mediaID tests', function () { + let request; + const testID = 'testID'; + + beforeEach(function () { + fetchTargetingForMediaId(testID); + request = server.requests[0]; + }); + + afterEach(function () { + // logErrorStub.restore(); + }); + + it('should reach out to media endpoint', function () { + expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testID}`); + }); + + it('should write to cache when successful', function () { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments1 + } + ] + }) + ); + + const targetingInfo = getTargetingForBid({ + jwpTargeting: { + mediaID: testID + } + }); + + expect(targetingInfo).to.deep.equal(validSegments1); + }); + + it('', function() { + + }); + // + // it('should fake', function () { + // let callBackSpy = sinon.spy(); + // let consentData = { + // gdprApplies: true, + // consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' + // }; + // let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; + // submoduleCallback(callBackSpy); + // let request = server.requests[0]; + // expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=1&cv=BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g'); + // request.respond( + // 200, + // responseHeader, + // JSON.stringify({}) + // ); + // expect(callBackSpy.calledOnce).to.be.true; + // }); + }); + + describe('Get targeting for bid', function() { + const validPlayerID = 'player_test_ID_valid'; + const invalidPlayerID = 'player_test_ID_invalid'; + const jwplayerMock = function(playerID) { + if (playerID === validPlayerID) { + return playerInstanceMock; + } else { + return {}; + } + }; + + const playlistItemWithSegmentMock = { + mediaid: 'media_ID_1', + jwpseg: validSegments1 + }; + + const playlistItemNoSegmentMock = { + mediaid: 'media_ID_2' + }; + + const currentItemSegments = ['test_seg_3', 'test_seg_4']; + const currentPlaylistItemMock = { + mediaid: 'media_ID_current', + jwpseg: currentItemSegments + }; + + const playerInstanceMock = { + getPlaylist: function () { + return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; + }, + + getPlaylistItem: function () { + return currentPlaylistItemMock; + } + }; + + it('returns empty array when targeting block is missing', function () { + const targeting = getTargetingForBid({}); + expect(targeting).to.deep.equal([]); + }); + + it('returns empty array when jwplayer.js is absent from page', function () { + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: invalidPlayerID, + mediaID: 'media_test_ID' + } + }); + expect(targeting).to.deep.equal([]); + }); + + it('returns empty array when player ID does not match player on page', function () { + window.jwplayer = jwplayerMock; + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: invalidPlayerID, + mediaID: 'media_test_ID' + } + }); + expect(targeting).to.deep.equal([]); + }); + + it('returns segments when media ID matches a playlist item with segments', function () { + window.jwplayer = jwplayerMock; + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: validPlayerID, + mediaID: 'media_ID_1' + } + }); + expect(targeting).to.deep.equal(validSegments1); + }); + + it('caches segments media ID matches a playist item with segments', function () { + // console.log('test window: ', window, this); + window.jwplayer = jwplayerMock; + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: validPlayerID, + mediaID: 'media_ID_1' + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwpTargeting: { + playerID: invalidPlayerID, + mediaID: 'media_ID_1' + } + }); + expect(targeting2).to.deep.equal(validSegments1); + }); + + it('returns segments of current item when media ID is missing', function () { + window.jwplayer = jwplayerMock; + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: validPlayerID + } + }); + expect(targeting).to.deep.equal(currentItemSegments); + }); + + it('caches segments from the current item', function () { + window.jwplayer = jwplayerMock; + const targeting = getTargetingForBid({ + jwpTargeting: { + playerID: validPlayerID + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwpTargeting: { + playerID: invalidPlayerID, + mediaID: 'media_ID_current' + } + }); + expect(targeting2).to.deep.equal(currentItemSegments); + }); + }); + + describe('Blocking mechanism for bid requests', function () { + + }); +}); From e954470c92bd5185e641aa510f8b0fea7d973a0f Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 02:24:04 -0400 Subject: [PATCH 13/51] completes test cases --- test/spec/modules/jwplayerTargeting_spec.js | 164 +++++++++++++++++--- 1 file changed, 140 insertions(+), 24 deletions(-) diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index e39e8a31319..bea0309c022 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -1,4 +1,5 @@ -import { fetchTargetingForMediaId, getTargetingForBid } from 'modules/jwplayerTargeting.js'; +import { fetchTargetingForMediaId, getTargetingForBid, + onFetchCompetion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; import { server } from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; @@ -9,10 +10,11 @@ describe('jwplayer', function() { describe('Fetch targeting for mediaID tests', function () { let request; const testID = 'testID'; + const testID2 = 'testID2'; beforeEach(function () { - fetchTargetingForMediaId(testID); - request = server.requests[0]; + // fetchTargetingForMediaId(testID); + // request = server.requests[0]; }); afterEach(function () { @@ -20,10 +22,14 @@ describe('jwplayer', function() { }); it('should reach out to media endpoint', function () { + fetchTargetingForMediaId(testID); + const request = server.requests[0]; expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testID}`); }); it('should write to cache when successful', function () { + fetchTargetingForMediaId(testID); + const request = server.requests[0]; request.respond( 200, responseHeader, @@ -46,27 +52,63 @@ describe('jwplayer', function() { expect(targetingInfo).to.deep.equal(validSegments1); }); - it('', function() { - - }); - // - // it('should fake', function () { - // let callBackSpy = sinon.spy(); - // let consentData = { - // gdprApplies: true, - // consentString: 'BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g' - // }; - // let submoduleCallback = identityLinkSubmodule.getId(defaultConfigParams, consentData).callback; - // submoduleCallback(callBackSpy); - // let request = server.requests[0]; - // expect(request.url).to.be.eq('https://api.rlcdn.com/api/identity/envelope?pid=14&ct=1&cv=BOkIpDSOkIpDSADABAENCc-AAAApOAFAAMAAsAMIAcAA_g'); - // request.respond( - // 200, - // responseHeader, - // JSON.stringify({}) - // ); - // expect(callBackSpy.calledOnce).to.be.true; - // }); + it('should not write to cache when response is malformed', function() { + fetchTargetingForMediaId(testID2); + const request = server.requests[0] + request.respond('{]'); + const targetingInfo = getTargetingForBid({ + jwpTargeting: { + mediaID: testID2 + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when playlist is absent', function() { + fetchTargetingForMediaId(testID2); + const request = server.requests[0] + request.respond({}); + const targetingInfo = getTargetingForBid({ + jwpTargeting: { + mediaID: testID2 + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when segments are absent', function() { + fetchTargetingForMediaId(testID2); + const request = server.requests[0] + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4' + } + ] + }) + ); + const targetingInfo = getTargetingForBid({ + jwpTargeting: { + mediaID: testID2 + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when request errors', function() { + fetchTargetingForMediaId(testID2); + const request = server.requests[0] + request.error(); + const targetingInfo = getTargetingForBid({ + jwpTargeting: { + mediaID: testID2 + } + }); + expect(targetingInfo).to.deep.equal([]); + }); }); describe('Get targeting for bid', function() { @@ -192,6 +234,80 @@ describe('jwplayer', function() { }); describe('Blocking mechanism for bid requests', function () { + const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; + + it('executes the bidRequest immediately when no requests are pending', function () { + fetchTargetingInformation({ + mediaIDs: [] + }); + let bidRequestSpy = sinon.spy(); + onFetchCompetion(bidRequestSpy, {}); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + it('executes the bidRequest after timeout if requests are still pending', function () { + let serv = sinon.createFakeServer(); + serv.respondImmediately = false; + serv.autoRespond = false; + const clock = sinon.useFakeTimers({ + toFake: ['setTimeout'] + }); + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + let bidRequestSpy = sinon.spy(); + onFetchCompetion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(1500); + expect(bidRequestSpy.calledOnce).to.be.true; + clock.restore(); + }); + + it('executes the bidRequest only once if requests succeed after timeout', function () { + let serv = sinon.createFakeServer(); + serv.respondImmediately = false; + serv.autoRespond = false; + const clock = sinon.useFakeTimers({ + toFake: ['setTimeout'] + }); + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + let bidRequestSpy = sinon.spy(); + onFetchCompetion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(1500); + expect(bidRequestSpy.calledOnce).to.be.true; + + serv.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + clock.restore(); + }); + + it('executes the bidRequest when all pending jwpseg requests are done', function () { + let serv = sinon.createFakeServer(); + serv.respondImmediately = false; + serv.autoRespond = false; + + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + let bidRequestSpy = sinon.spy(); + onFetchCompetion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + + const req1 = serv.requests[0]; + const req2 = serv.requests[1]; + const req3 = serv.requests[2]; + + req1.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req2.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req3.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); }); }); From aef48b96414b25172fcf20226437870e8f6760d0 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 13:34:08 -0400 Subject: [PATCH 14/51] stores segments for current item --- modules/jwplayerTargeting.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 3479621968e..2f4bb8512a4 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -31,11 +31,12 @@ export function onFetchCompetion(nextFn, reqBidsConfigObj) { nextFn.apply(this, [reqBidsConfigObj]); return; } - requestTimeout = setTimeout(() => { - nextFn.apply(this, [reqBidsConfigObj]); - }, 1500); - resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); + requestTimeout = setTimeout(function() { + resumeBidRequest(); + resumeBidRequest = null; + requestTimeout = null; + }, 1500); } /** @@ -50,7 +51,6 @@ export function getTargetingForBid(bidRequest) { const playerID = jwpTargeting.playerID; let mediaID = jwpTargeting.mediaID; - // const { mediaID, playerID } = jwpTargeting; let segments = segCache[mediaID]; if (segments) { return segments; @@ -61,18 +61,13 @@ export function getTargetingForBid(bidRequest) { return []; } - let item; - if (mediaID) { - item = player.getPlaylist().find(item => item.mediaid === mediaID); - } else { - item = player.getPlaylistItem(); - mediaID = item.mediaid; - } - - if (item) { - segments = item.jwpseg; + let item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); + if (!item) { + return []; } + mediaID = mediaID || item.mediaid; + segments = item.jwpseg; if (segments && mediaID) { segCache[mediaID] = segments; } @@ -81,17 +76,14 @@ export function getTargetingForBid(bidRequest) { } function getPlayer(playerID) { - // console.log('window: ', window, this); var jwplayer = window.jwplayer; if (!jwplayer) { - console.log('karim no player.js'); logError('jwplayer.js was not found on page'); return; } const player = jwplayer(playerID); if (!player || !player.getPlaylist) { - console.log('karim no player instance'); logError('player ID did not match any players'); return; } @@ -136,13 +128,14 @@ function onRequestCompleted() { if (requestCount > 0) { return; } - if (requestTimeout) { clearTimeout(requestTimeout); + requestTimeout = null; } if (resumeBidRequest) { resumeBidRequest(); + resumeBidRequest = null; } } From 38f2c503184c1c9d012ad5ff38b9b3e8bcf499ba Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 13:42:14 -0400 Subject: [PATCH 15/51] renames jwp targeting --- modules/jwplayerTargeting.js | 12 ++++----- test/spec/modules/jwplayerTargeting_spec.js | 28 ++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 2f4bb8512a4..7d08045b8b0 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -10,9 +10,9 @@ let requestTimeout; let resumeBidRequest; function setup () { - config.getConfig('jwpTargeting', (config) => { + config.getConfig('jwTargeting', (config) => { // fetch media ids - fetchTargetingInformation(config.jwpTargeting) + fetchTargetingInformation(config.jwTargeting) }); getGlobal().requestBids.before(onFetchCompetion); @@ -44,13 +44,13 @@ export function onFetchCompetion(nextFn, reqBidsConfigObj) { * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information */ export function getTargetingForBid(bidRequest) { - const jwpTargeting = bidRequest.jwpTargeting; - if (!jwpTargeting) { + const jwTargeting = bidRequest.jwTargeting; + if (!jwTargeting) { return []; } - const playerID = jwpTargeting.playerID; - let mediaID = jwpTargeting.mediaID; + const playerID = jwTargeting.playerID; + let mediaID = jwTargeting.mediaID; let segments = segCache[mediaID]; if (segments) { return segments; diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index bea0309c022..6a95f5634e2 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -44,7 +44,7 @@ describe('jwplayer', function() { ); const targetingInfo = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { mediaID: testID } }); @@ -57,7 +57,7 @@ describe('jwplayer', function() { const request = server.requests[0] request.respond('{]'); const targetingInfo = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { mediaID: testID2 } }); @@ -69,7 +69,7 @@ describe('jwplayer', function() { const request = server.requests[0] request.respond({}); const targetingInfo = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { mediaID: testID2 } }); @@ -91,7 +91,7 @@ describe('jwplayer', function() { }) ); const targetingInfo = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { mediaID: testID2 } }); @@ -103,7 +103,7 @@ describe('jwplayer', function() { const request = server.requests[0] request.error(); const targetingInfo = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { mediaID: testID2 } }); @@ -154,7 +154,7 @@ describe('jwplayer', function() { it('returns empty array when jwplayer.js is absent from page', function () { const targeting = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: invalidPlayerID, mediaID: 'media_test_ID' } @@ -165,7 +165,7 @@ describe('jwplayer', function() { it('returns empty array when player ID does not match player on page', function () { window.jwplayer = jwplayerMock; const targeting = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: invalidPlayerID, mediaID: 'media_test_ID' } @@ -176,7 +176,7 @@ describe('jwplayer', function() { it('returns segments when media ID matches a playlist item with segments', function () { window.jwplayer = jwplayerMock; const targeting = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: validPlayerID, mediaID: 'media_ID_1' } @@ -188,7 +188,7 @@ describe('jwplayer', function() { // console.log('test window: ', window, this); window.jwplayer = jwplayerMock; const targeting = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: validPlayerID, mediaID: 'media_ID_1' } @@ -196,7 +196,7 @@ describe('jwplayer', function() { window.jwplayer = null; const targeting2 = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: invalidPlayerID, mediaID: 'media_ID_1' } @@ -207,7 +207,7 @@ describe('jwplayer', function() { it('returns segments of current item when media ID is missing', function () { window.jwplayer = jwplayerMock; const targeting = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: validPlayerID } }); @@ -216,15 +216,15 @@ describe('jwplayer', function() { it('caches segments from the current item', function () { window.jwplayer = jwplayerMock; - const targeting = getTargetingForBid({ - jwpTargeting: { + getTargetingForBid({ + jwTargeting: { playerID: validPlayerID } }); window.jwplayer = null; const targeting2 = getTargetingForBid({ - jwpTargeting: { + jwTargeting: { playerID: invalidPlayerID, mediaID: 'media_ID_current' } From 3956cc67b8331274b8071f178d31fafe15c9bfc1 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 16:13:58 -0400 Subject: [PATCH 16/51] refactors fetch tests --- test/spec/modules/jwplayerTargeting_spec.js | 173 ++++++++++---------- 1 file changed, 83 insertions(+), 90 deletions(-) diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index 6a95f5634e2..2389d43754c 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -1,5 +1,5 @@ import { fetchTargetingForMediaId, getTargetingForBid, - onFetchCompetion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; + onFetchCompletion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; import { server } from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; @@ -9,105 +9,98 @@ describe('jwplayer', function() { describe('Fetch targeting for mediaID tests', function () { let request; - const testID = 'testID'; - const testID2 = 'testID2'; + const testIdForSuccess = 'test_id_for_success'; + const testIdForFailure = 'test_id_for_failure'; - beforeEach(function () { - // fetchTargetingForMediaId(testID); - // request = server.requests[0]; - }); + describe('Fetch succeeds', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForSuccess); + request = server.requests[0]; + }); - afterEach(function () { - // logErrorStub.restore(); - }); + it('should reach out to media endpoint', function () { + expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testIdForSuccess}`); + }); - it('should reach out to media endpoint', function () { - fetchTargetingForMediaId(testID); - const request = server.requests[0]; - expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testID}`); + it('should write to cache when successful', function () { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments1 + } + ] + }) + ); + + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForSuccess + } + }); + + expect(targetingInfo).to.deep.equal(validSegments1); + }); }); - it('should write to cache when successful', function () { - fetchTargetingForMediaId(testID); - const request = server.requests[0]; - request.respond( - 200, - responseHeader, - JSON.stringify({ - playlist: [ - { - file: 'test.mp4', - jwpseg: validSegments1 - } - ] - }) - ); - - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testID - } + describe('Fetch fails', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForFailure); + request = server.requests[0] }); - expect(targetingInfo).to.deep.equal(validSegments1); - }); - - it('should not write to cache when response is malformed', function() { - fetchTargetingForMediaId(testID2); - const request = server.requests[0] - request.respond('{]'); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testID2 - } + it('should not write to cache when response is malformed', function() { + request.respond('{]'); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); }); - expect(targetingInfo).to.deep.equal([]); - }); - it('should not write to cache when playlist is absent', function() { - fetchTargetingForMediaId(testID2); - const request = server.requests[0] - request.respond({}); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testID2 - } + it('should not write to cache when playlist is absent', function() { + request.respond({}); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); }); - expect(targetingInfo).to.deep.equal([]); - }); - it('should not write to cache when segments are absent', function() { - fetchTargetingForMediaId(testID2); - const request = server.requests[0] - request.respond( - 200, - responseHeader, - JSON.stringify({ - playlist: [ - { - file: 'test.mp4' - } - ] - }) - ); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testID2 - } + it('should not write to cache when segments are absent', function() { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4' + } + ] + }) + ); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); }); - expect(targetingInfo).to.deep.equal([]); - }); - it('should not write to cache when request errors', function() { - fetchTargetingForMediaId(testID2); - const request = server.requests[0] - request.error(); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testID2 - } + it('should not write to cache when request errors', function() { + request.error(); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); }); - expect(targetingInfo).to.deep.equal([]); }); }); @@ -241,7 +234,7 @@ describe('jwplayer', function() { mediaIDs: [] }); let bidRequestSpy = sinon.spy(); - onFetchCompetion(bidRequestSpy, {}); + onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.calledOnce).to.be.true; }); @@ -256,7 +249,7 @@ describe('jwplayer', function() { mediaIDs: validMediaIDs }); let bidRequestSpy = sinon.spy(); - onFetchCompetion(bidRequestSpy, {}); + onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; @@ -274,7 +267,7 @@ describe('jwplayer', function() { mediaIDs: validMediaIDs }); let bidRequestSpy = sinon.spy(); - onFetchCompetion(bidRequestSpy, {}); + onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; @@ -293,7 +286,7 @@ describe('jwplayer', function() { mediaIDs: validMediaIDs }); let bidRequestSpy = sinon.spy(); - onFetchCompetion(bidRequestSpy, {}); + onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; const req1 = serv.requests[0]; From 708a34c4ebca6a23374639e9b69c5689ec5d1f02 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 16:31:17 -0400 Subject: [PATCH 17/51] refactors get targeting tests --- modules/jwplayerTargeting.js | 4 +- test/spec/modules/jwplayerTargeting_spec.js | 180 ++++++++++---------- 2 files changed, 95 insertions(+), 89 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 7d08045b8b0..9e59222caa7 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -15,7 +15,7 @@ function setup () { fetchTargetingInformation(config.jwTargeting) }); - getGlobal().requestBids.before(onFetchCompetion); + getGlobal().requestBids.before(onFetchCompletion); } export function fetchTargetingInformation(jwTargeting) { @@ -26,7 +26,7 @@ export function fetchTargetingInformation(jwTargeting) { }); } -export function onFetchCompetion(nextFn, reqBidsConfigObj) { +export function onFetchCompletion(nextFn, reqBidsConfigObj) { if (requestCount <= 0) { nextFn.apply(this, [reqBidsConfigObj]); return; diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index 2389d43754c..49cd9eb064d 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -105,40 +105,13 @@ describe('jwplayer', function() { }); describe('Get targeting for bid', function() { + const mediaIdWithSegment = 'media_ID_1'; + const mediaIdNoSegment = 'media_ID_2'; + const mediaIdForCurrentItem = 'media_ID_current'; + const mediaIdNotCached = 'media_test_ID'; + const validPlayerID = 'player_test_ID_valid'; const invalidPlayerID = 'player_test_ID_invalid'; - const jwplayerMock = function(playerID) { - if (playerID === validPlayerID) { - return playerInstanceMock; - } else { - return {}; - } - }; - - const playlistItemWithSegmentMock = { - mediaid: 'media_ID_1', - jwpseg: validSegments1 - }; - - const playlistItemNoSegmentMock = { - mediaid: 'media_ID_2' - }; - - const currentItemSegments = ['test_seg_3', 'test_seg_4']; - const currentPlaylistItemMock = { - mediaid: 'media_ID_current', - jwpseg: currentItemSegments - }; - - const playerInstanceMock = { - getPlaylist: function () { - return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; - }, - - getPlaylistItem: function () { - return currentPlaylistItemMock; - } - }; it('returns empty array when targeting block is missing', function () { const targeting = getTargetingForBid({}); @@ -149,80 +122,113 @@ describe('jwplayer', function() { const targeting = getTargetingForBid({ jwTargeting: { playerID: invalidPlayerID, - mediaID: 'media_test_ID' + mediaID: mediaIdNotCached } }); expect(targeting).to.deep.equal([]); }); - it('returns empty array when player ID does not match player on page', function () { - window.jwplayer = jwplayerMock; - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: 'media_test_ID' + describe('When jwplayer.js is on page', function () { + const playlistItemWithSegmentMock = { + mediaid: mediaIdWithSegment, + jwpseg: validSegments1 + }; + + const playlistItemNoSegmentMock = { + mediaid: mediaIdNoSegment + }; + + const currentItemSegments = ['test_seg_3', 'test_seg_4']; + const currentPlaylistItemMock = { + mediaid: mediaIdForCurrentItem, + jwpseg: currentItemSegments + }; + + const playerInstanceMock = { + getPlaylist: function () { + return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; + }, + + getPlaylistItem: function () { + return currentPlaylistItemMock; } - }); - expect(targeting).to.deep.equal([]); - }); + }; - it('returns segments when media ID matches a playlist item with segments', function () { - window.jwplayer = jwplayerMock; - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID, - mediaID: 'media_ID_1' + const jwplayerMock = function(playerID) { + if (playerID === validPlayerID) { + return playerInstanceMock; + } else { + return {}; } + }; + + beforeEach(function () { + window.jwplayer = jwplayerMock; }); - expect(targeting).to.deep.equal(validSegments1); - }); - it('caches segments media ID matches a playist item with segments', function () { - // console.log('test window: ', window, this); - window.jwplayer = jwplayerMock; - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID, - mediaID: 'media_ID_1' - } + it('returns empty array when player ID does not match player on page', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdNotCached + } + }); + expect(targeting).to.deep.equal([]); }); - window.jwplayer = null; - const targeting2 = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: 'media_ID_1' - } + it('returns segments when media ID matches a playlist item with segments', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID, + mediaID: mediaIdWithSegment + } + }); + expect(targeting).to.deep.equal(validSegments1); }); - expect(targeting2).to.deep.equal(validSegments1); - }); - it('returns segments of current item when media ID is missing', function () { - window.jwplayer = jwplayerMock; - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID - } + it('caches segments media ID matches a playist item with segments', function () { + getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID, + mediaID: mediaIdWithSegment + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdWithSegment + } + }); + expect(targeting2).to.deep.equal(validSegments1); }); - expect(targeting).to.deep.equal(currentItemSegments); - }); - it('caches segments from the current item', function () { - window.jwplayer = jwplayerMock; - getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID - } + it('returns segments of current item when media ID is missing', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID + } + }); + expect(targeting).to.deep.equal(currentItemSegments); }); - window.jwplayer = null; - const targeting2 = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: 'media_ID_current' - } + it('caches segments from the current item', function () { + getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdForCurrentItem + } + }); + expect(targeting2).to.deep.equal(currentItemSegments); }); - expect(targeting2).to.deep.equal(currentItemSegments); }); }); From 724b03d8769880515a4125418bec4dd9df794549 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 16:52:59 -0400 Subject: [PATCH 18/51] refactors blocking tests --- test/spec/modules/jwplayerTargeting_spec.js | 61 ++++++++++----------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index 49cd9eb064d..8939f77f09e 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -5,7 +5,7 @@ import { server } from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('jwplayer', function() { - const validSegments1 = ['test_seg_1', 'test_seg_2']; + const validSegments = ['test_seg_1', 'test_seg_2']; describe('Fetch targeting for mediaID tests', function () { let request; @@ -30,7 +30,7 @@ describe('jwplayer', function() { playlist: [ { file: 'test.mp4', - jwpseg: validSegments1 + jwpseg: validSegments } ] }) @@ -42,7 +42,7 @@ describe('jwplayer', function() { } }); - expect(targetingInfo).to.deep.equal(validSegments1); + expect(targetingInfo).to.deep.equal(validSegments); }); }); @@ -131,7 +131,7 @@ describe('jwplayer', function() { describe('When jwplayer.js is on page', function () { const playlistItemWithSegmentMock = { mediaid: mediaIdWithSegment, - jwpseg: validSegments1 + jwpseg: validSegments }; const playlistItemNoSegmentMock = { @@ -183,7 +183,7 @@ describe('jwplayer', function() { mediaID: mediaIdWithSegment } }); - expect(targeting).to.deep.equal(validSegments1); + expect(targeting).to.deep.equal(validSegments); }); it('caches segments media ID matches a playist item with segments', function () { @@ -201,7 +201,7 @@ describe('jwplayer', function() { mediaID: mediaIdWithSegment } }); - expect(targeting2).to.deep.equal(validSegments1); + expect(targeting2).to.deep.equal(validSegments); }); it('returns segments of current item when media ID is missing', function () { @@ -234,70 +234,67 @@ describe('jwplayer', function() { describe('Blocking mechanism for bid requests', function () { const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; + let bidRequestSpy; + let fakeServer; + let clock; + + beforeEach(function () { + bidRequestSpy = sinon.spy(); + + fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; + + clock = sinon.useFakeTimers({ + toFake: ['setTimeout'] + }); + }); + + afterEach(function () { + clock.restore(); + }); it('executes the bidRequest immediately when no requests are pending', function () { fetchTargetingInformation({ mediaIDs: [] }); - let bidRequestSpy = sinon.spy(); onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.calledOnce).to.be.true; }); it('executes the bidRequest after timeout if requests are still pending', function () { - let serv = sinon.createFakeServer(); - serv.respondImmediately = false; - serv.autoRespond = false; - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout'] - }); fetchTargetingInformation({ mediaIDs: validMediaIDs }); - let bidRequestSpy = sinon.spy(); onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; - clock.restore(); }); it('executes the bidRequest only once if requests succeed after timeout', function () { - let serv = sinon.createFakeServer(); - serv.respondImmediately = false; - serv.autoRespond = false; - const clock = sinon.useFakeTimers({ - toFake: ['setTimeout'] - }); fetchTargetingInformation({ mediaIDs: validMediaIDs }); - let bidRequestSpy = sinon.spy(); onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; - serv.respond(); + fakeServer.respond(); expect(bidRequestSpy.calledOnce).to.be.true; - clock.restore(); }); it('executes the bidRequest when all pending jwpseg requests are done', function () { - let serv = sinon.createFakeServer(); - serv.respondImmediately = false; - serv.autoRespond = false; - fetchTargetingInformation({ mediaIDs: validMediaIDs }); - let bidRequestSpy = sinon.spy(); onFetchCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; - const req1 = serv.requests[0]; - const req2 = serv.requests[1]; - const req3 = serv.requests[2]; + const req1 = fakeServer.requests[0]; + const req2 = fakeServer.requests[1]; + const req3 = fakeServer.requests[2]; req1.respond(); expect(bidRequestSpy.notCalled).to.be.true; From a34a5dfc2916acc61dba309b3a3ffdece9f26e03 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 16:55:18 -0400 Subject: [PATCH 19/51] renames module --- modules/.submodules.json | 3 ++- modules/jwplayerTargeting.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index dd40557c35b..663e099ae3b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -19,5 +19,6 @@ ], "rtdModule": [ "browsiRtdProvider" - ] + ], + "jwplayerTargeting": [] } diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 9e59222caa7..f6eedab8a0e 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -145,7 +145,7 @@ const jwplayerUtilities = { 'getTargetingForBid': getTargetingForBid }; -module('jwplayer', function shareJWPlayerUtilities() { +module('jwplayerTargeting', function shareJWPlayerUtilities() { const host = arguments[0]; if (!isPlainObject(host)) { logError('JW Player module requires plain object to share methods with submodule'); From 9331726db8ffa451bced6fff90d7414d0638eaa5 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Fri, 10 Jul 2020 17:06:45 -0400 Subject: [PATCH 20/51] cleans changes made to app nexus --- modules/appnexusBidAdapter.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 8527de3054d..12bc6a8105c 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -8,10 +8,6 @@ import find from 'core-js-pure/features/array/find.js'; import includes from 'core-js-pure/features/array/includes.js'; import { OUTSTREAM, INSTREAM } from '../src/video.js'; import { getStorageManager } from '../src/storageManager.js'; -// import {getTargetingForBid} from './jwplayerTargeting.js'; -import { submodule } from '../src/hook.js'; - -export const jwplayerUtils = {}; const BIDDER_CODE = 'appnexus'; const URL = 'https://ib.adnxs.com/ut/v3/prebid'; @@ -251,24 +247,6 @@ export const spec = { } const request = formatRequest(payload, bidderRequest); - request.jwpTargeting = { - playerID: 'karim', - mediaID: '2XZFlRuo' - }; - const targ = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info: ', targ); - request.jwpTargeting = { - playerID: 'karim', - mediaID: 'kmoSeg' - }; - const targ2 = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info 2: ', targ2); - request.jwpTargeting = { - playerID: 'karim', - mediaID: 'kmoNoSeg' - }; - const targ3 = jwplayerUtils.getTargetingForBid(request); - console.log('karim target Info 3: ', targ3); return request; }, @@ -994,4 +972,3 @@ function parseMediaType(rtbBid) { } registerBidder(spec); -submodule('jwplayer', jwplayerUtils); From 36066510a8f89be6a5e82c40662a877f1da7b1b3 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 16:03:46 -0400 Subject: [PATCH 21/51] removes setup and player utilities --- modules/jwplayerTargeting.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index f6eedab8a0e..65261ba3835 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -9,14 +9,12 @@ let requestCount = 0; let requestTimeout; let resumeBidRequest; -function setup () { - config.getConfig('jwTargeting', (config) => { - // fetch media ids - fetchTargetingInformation(config.jwTargeting) - }); +config.getConfig('jwTargeting', (config) => { + // fetch media ids + fetchTargetingInformation(config.jwTargeting) +}); - getGlobal().requestBids.before(onFetchCompletion); -} +getGlobal().requestBids.before(onFetchCompletion); export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; @@ -139,20 +137,11 @@ function onRequestCompleted() { } } -setup(); - -const jwplayerUtilities = { - 'getTargetingForBid': getTargetingForBid -}; - module('jwplayerTargeting', function shareJWPlayerUtilities() { const host = arguments[0]; if (!isPlainObject(host)) { logError('JW Player module requires plain object to share methods with submodule'); return; } - - for (let method in jwplayerUtilities) { - host[method] = jwplayerUtilities[method]; - } + host.getTargetingForBid = getTargetingForBid; }); From 41290a151e43b4076725c7d8c02655a4c61506e7 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 16:23:48 -0400 Subject: [PATCH 22/51] renames onFetchCompletion --- modules/jwplayerTargeting.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 65261ba3835..8c43c0ba301 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -14,7 +14,7 @@ config.getConfig('jwTargeting', (config) => { fetchTargetingInformation(config.jwTargeting) }); -getGlobal().requestBids.before(onFetchCompletion); +getGlobal().requestBids.before(ensureFeedRequestCompletion); export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; @@ -24,12 +24,12 @@ export function fetchTargetingInformation(jwTargeting) { }); } -export function onFetchCompletion(nextFn, reqBidsConfigObj) { +export function ensureFeedRequestCompletion(requestBids, bidRequestConfig) { if (requestCount <= 0) { - nextFn.apply(this, [reqBidsConfigObj]); + requestBids.apply(this, [bidRequestConfig]); return; } - resumeBidRequest = nextFn.bind(this, reqBidsConfigObj); + resumeBidRequest = requestBids.bind(this, bidRequestConfig); requestTimeout = setTimeout(function() { resumeBidRequest(); resumeBidRequest = null; From e2d55fb2440420d9fbb26e62a54b4d536e2ad45d Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 17:16:37 -0400 Subject: [PATCH 23/51] renames onFetchCOmpletion in unti tests --- test/spec/modules/jwplayerTargeting_spec.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index 8939f77f09e..be0d854c948 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -1,5 +1,5 @@ import { fetchTargetingForMediaId, getTargetingForBid, - onFetchCompletion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; + ensureFeedRequestCompletion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; import { server } from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; @@ -258,7 +258,7 @@ describe('jwplayer', function() { fetchTargetingInformation({ mediaIDs: [] }); - onFetchCompletion(bidRequestSpy, {}); + ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.calledOnce).to.be.true; }); @@ -266,7 +266,7 @@ describe('jwplayer', function() { fetchTargetingInformation({ mediaIDs: validMediaIDs }); - onFetchCompletion(bidRequestSpy, {}); + ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; @@ -276,7 +276,7 @@ describe('jwplayer', function() { fetchTargetingInformation({ mediaIDs: validMediaIDs }); - onFetchCompletion(bidRequestSpy, {}); + ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; clock.tick(1500); expect(bidRequestSpy.calledOnce).to.be.true; @@ -289,7 +289,7 @@ describe('jwplayer', function() { fetchTargetingInformation({ mediaIDs: validMediaIDs }); - onFetchCompletion(bidRequestSpy, {}); + ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; const req1 = fakeServer.requests[0]; From 1ee3fdd580a6a2aa732d929629ec8c0c313998db Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 18:02:56 -0400 Subject: [PATCH 24/51] throws instead of early return --- modules/.submodules.json | 3 +-- modules/jwplayerTargeting.js | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index 663e099ae3b..dd40557c35b 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -19,6 +19,5 @@ ], "rtdModule": [ "browsiRtdProvider" - ], - "jwplayerTargeting": [] + ] } diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 8c43c0ba301..5067c55c2c5 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -59,7 +59,7 @@ export function getTargetingForBid(bidRequest) { return []; } - let item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); + const item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); if (!item) { return []; } @@ -74,7 +74,7 @@ export function getTargetingForBid(bidRequest) { } function getPlayer(playerID) { - var jwplayer = window.jwplayer; + const jwplayer = window.jwplayer; if (!jwplayer) { logError('jwplayer.js was not found on page'); return; @@ -96,12 +96,12 @@ export function fetchTargetingForMediaId(mediaId) { try { const data = JSON.parse(response); if (!data) { - return; + throw ('Empty response'); } const playlist = data.playlist; if (!playlist || !playlist.length) { - return; + throw ('Empty playlist'); } const jwpseg = playlist[0].jwpseg; @@ -109,7 +109,7 @@ export function fetchTargetingForMediaId(mediaId) { segCache[mediaId] = jwpseg; } } catch (err) { - logError('failed to parse response'); + logError(err); } onRequestCompleted(); }, From e34364234c6c5655214ff8ffe6e0c1ccf3e73f3a Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 18:48:09 -0400 Subject: [PATCH 25/51] reduces timeout and introduces override --- modules/jwplayerTargeting.js | 19 ++++++++++++++++--- test/spec/modules/jwplayerTargeting_spec.js | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 5067c55c2c5..839239fa9c3 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -9,15 +9,28 @@ let requestCount = 0; let requestTimeout; let resumeBidRequest; -config.getConfig('jwTargeting', (config) => { +/* +Prebid auctions timeout at 200ms. + */ +let feedFetchTimeout = 150; + +config.getConfig('jwTargeting', config => { // fetch media ids fetchTargetingInformation(config.jwTargeting) + + const timeout = config.bidderTimeout; + if (timeout < 200) { + // 3/4 is the ratio between 150 and 200, where 150 is our default and 200 is prebid's default auction timeout. + // Note auction will close at 200ms even if bidderTimeout is greater. + feedFetchTimeout = timeout * 3 / 4; + } }); getGlobal().requestBids.before(ensureFeedRequestCompletion); export function fetchTargetingInformation(jwTargeting) { - const mediaIDs = jwTargeting.mediaIDs; + const { mediaIDs, prefetchTimeout } = jwTargeting.mediaIDs; + feedFetchTimeout = prefetchTimeout || feedFetchTimeout; requestCount = mediaIDs.length; mediaIDs.forEach(mediaID => { fetchTargetingForMediaId(mediaID); @@ -34,7 +47,7 @@ export function ensureFeedRequestCompletion(requestBids, bidRequestConfig) { resumeBidRequest(); resumeBidRequest = null; requestTimeout = null; - }, 1500); + }, feedFetchTimeout); } /** diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js index be0d854c948..4cb195bfdfd 100644 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ b/test/spec/modules/jwplayerTargeting_spec.js @@ -268,7 +268,7 @@ describe('jwplayer', function() { }); ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(1500); + clock.tick(150); expect(bidRequestSpy.calledOnce).to.be.true; }); @@ -278,7 +278,7 @@ describe('jwplayer', function() { }); ensureFeedRequestCompletion(bidRequestSpy, {}); expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(1500); + clock.tick(150); expect(bidRequestSpy.calledOnce).to.be.true; fakeServer.respond(); From d3571c8efc86729b7371109d6a9b86d578ec13e7 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 13 Jul 2020 19:07:17 -0400 Subject: [PATCH 26/51] targeting timeout supersedes --- modules/jwplayerTargeting.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 839239fa9c3..21530dd0818 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -15,10 +15,18 @@ Prebid auctions timeout at 200ms. let feedFetchTimeout = 150; config.getConfig('jwTargeting', config => { + const targeting = config.jwTargeting; // fetch media ids - fetchTargetingInformation(config.jwTargeting) + fetchTargetingInformation(targeting); - const timeout = config.bidderTimeout; + const prefetchTimeout = targeting.prefetchTimeout; + if (prefetchTimeout) { + // prefetch timeout supersedes our default and our adjustment for bidderTimeout. + feedFetchTimeout = prefetchTimeout; + return; + } + + const timeout = config.bidderTimeout; if (timeout < 200) { // 3/4 is the ratio between 150 and 200, where 150 is our default and 200 is prebid's default auction timeout. // Note auction will close at 200ms even if bidderTimeout is greater. @@ -29,8 +37,7 @@ config.getConfig('jwTargeting', config => { getGlobal().requestBids.before(ensureFeedRequestCompletion); export function fetchTargetingInformation(jwTargeting) { - const { mediaIDs, prefetchTimeout } = jwTargeting.mediaIDs; - feedFetchTimeout = prefetchTimeout || feedFetchTimeout; + const mediaIDs = jwTargeting.mediaIDs; requestCount = mediaIDs.length; mediaIDs.forEach(mediaID => { fetchTargetingForMediaId(mediaID); From e3a3d8822d226066563234d65ac39ca9c6c69a2f Mon Sep 17 00:00:00 2001 From: karimJWP Date: Tue, 14 Jul 2020 15:31:59 -0400 Subject: [PATCH 27/51] renames feed fetch timeout --- modules/jwplayerTargeting.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 21530dd0818..a54835add6b 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -12,7 +12,7 @@ let resumeBidRequest; /* Prebid auctions timeout at 200ms. */ -let feedFetchTimeout = 150; +let bidPauseTimeout = 150; config.getConfig('jwTargeting', config => { const targeting = config.jwTargeting; @@ -22,15 +22,15 @@ config.getConfig('jwTargeting', config => { const prefetchTimeout = targeting.prefetchTimeout; if (prefetchTimeout) { // prefetch timeout supersedes our default and our adjustment for bidderTimeout. - feedFetchTimeout = prefetchTimeout; + bidPauseTimeout = prefetchTimeout; return; } - const timeout = config.bidderTimeout; + const timeout = config.bidderTimeout; if (timeout < 200) { // 3/4 is the ratio between 150 and 200, where 150 is our default and 200 is prebid's default auction timeout. // Note auction will close at 200ms even if bidderTimeout is greater. - feedFetchTimeout = timeout * 3 / 4; + bidPauseTimeout = timeout * 3 / 4; } }); @@ -54,7 +54,7 @@ export function ensureFeedRequestCompletion(requestBids, bidRequestConfig) { resumeBidRequest(); resumeBidRequest = null; requestTimeout = null; - }, feedFetchTimeout); + }, bidPauseTimeout); } /** From 570b4e7bc025dc5b72d7a183857316f27355c0b0 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 15 Jul 2020 00:21:50 -0400 Subject: [PATCH 28/51] adds inline doc --- modules/jwplayerTargeting.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index a54835add6b..8e1bd6b847a 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -1,3 +1,12 @@ +/** + * The JW Player Targeting module provides functions which allow Ad Bidders to obtain JW Player's + * Video Ad Targeting information. + * The module can be used as a submodule for prebid adapters, allowing them to use the getTargetingForBid() function. + * The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid + * requests are made while the segments are being fetched, they will be blocked until all requests complete, or the + * 150ms timeout expires. + */ + import { config } from '../src/config.js'; import { ajaxBuilder } from '../src/ajax.js'; import { logError, isPlainObject } from '../src/utils.js'; @@ -58,7 +67,9 @@ export function ensureFeedRequestCompletion(requestBids, bidRequestConfig) { } /** - * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests` + * Retrieves the targeting information pertaining to a bid request. + * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain + * a jwTargeting property. * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information */ export function getTargetingForBid(bidRequest) { From eca4eb6efcee0e6980a848cbdf616232f9a30a72 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 15 Jul 2020 14:18:17 -0400 Subject: [PATCH 29/51] uses find util --- modules/jwplayerTargeting.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js index 8e1bd6b847a..cad3b7e642c 100644 --- a/modules/jwplayerTargeting.js +++ b/modules/jwplayerTargeting.js @@ -12,6 +12,7 @@ import { ajaxBuilder } from '../src/ajax.js'; import { logError, isPlainObject } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { module } from '../src/hook.js'; +import find from 'core-js-pure/features/array/find.js'; const segCache = {}; let requestCount = 0; @@ -90,7 +91,7 @@ export function getTargetingForBid(bidRequest) { return []; } - const item = mediaID ? player.getPlaylist().find(item => item.mediaid === mediaID) : player.getPlaylistItem(); + const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); if (!item) { return []; } From 8cd1a13f0fda52ff7a95c187e0d1b9f7789b0560 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 15 Jul 2020 18:11:21 -0400 Subject: [PATCH 30/51] adds jwplayer rtd provider --- modules/jwplayerRtdProvider | 161 ++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 modules/jwplayerRtdProvider diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider new file mode 100644 index 00000000000..d9a51407730 --- /dev/null +++ b/modules/jwplayerRtdProvider @@ -0,0 +1,161 @@ +import {submodule} from '../src/hook.js'; +import { config } from '../src/config.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { logError } from '../src/utils.js'; + +const SUBMODULE_NAME = 'jwplayer'; +let requestCount = 0; +let requestTimeout = 1500; +const segCache = {}; +// const pendingMediaIReqs = {}; + +export function fetchTargetingInformation(jwTargeting) { + const mediaIDs = jwTargeting.mediaIDs; + // pendingMediaIDs = mediaIDs; + requestCount = mediaIDs.length; + mediaIDs.forEach(mediaID => { + fetchTargetingForMediaId(mediaID); + }); +} + +export function fetchTargetingForMediaId(mediaId) { + const ajax = ajaxBuilder(requestTimeout); + ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, + { + success: function (response) { + try { + const data = JSON.parse(response); + if (!data) { + throw ('Empty response'); + } + + const playlist = data.playlist; + if (!playlist || !playlist.length) { + throw ('Empty playlist'); + } + + const jwpseg = playlist[0].jwpseg; + if (jwpseg) { + segCache[mediaId] = jwpseg; + } + } catch (err) { + logError(err); + } + onRequestCompleted(); + }, + error: function () { + logError('failed to retrieve targeting information'); + onRequestCompleted(); + } + } + ); +} + +function onRequestCompleted(mediaID) { + // pendingMediaIDs.remove(mediaID); + requestCount--; + if (requestCount > 0) { + return; + } + + if (resumeBidRequest) { + resumeBidRequest(); + resumeBidRequest = null; + } +} + +function getSegments(adUnits, onDone) { + adUnits.forEach(adUnit => { + const targeting = adUnit.jwTargeting; + const seg = getTargetingForBid(targeting); + }); + let dataToReturn = adUnits.reduce((acc, adUnit) => { + const jwTargeting = adUnit.jwTargeting; + if (!jwTargeting) { + return acc; + } + const adUnitCode = adUnit.code; + acc[adUnitCode] = getTargetingForBid(jwTargeting); + return acc; + }, {}); +} + +/** + * Retrieves the targeting information pertaining to a bid request. + * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain + * a jwTargeting property. + * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information + */ +export function getTargetingForBid(bidRequest) { + const jwTargeting = bidRequest.jwTargeting; + if (!jwTargeting) { + return []; + } + + const playerID = jwTargeting.playerID; + let mediaID = jwTargeting.mediaID; + let segments = segCache[mediaID]; + if (segments) { + return segments; + } + + const player = getPlayer(playerID); + if (!player) { + return []; + } + + const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); + if (!item) { + return []; + } + + mediaID = mediaID || item.mediaid; + segments = item.jwpseg; + if (segments && mediaID) { + segCache[mediaID] = segments; + } + + return segments || []; +} + +function getPlayer(playerID) { + const jwplayer = window.jwplayer; + if (!jwplayer) { + logError('jwplayer.js was not found on page'); + return; + } + + const player = jwplayer(playerID); + if (!player || !player.getPlaylist) { + logError('player ID did not match any players'); + return; + } + return player; +} + +/** @type {RtdSubmodule} */ +export const jwplayerSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + /** + * get data and send back to realTimeData module + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ + getData: getSegments +}; + +export function init(config) { + config.getConfig('realTimeData', ({realTimeData}) => { + const params = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME)[0].params; + requestTimeout = params.auctionDelay || params.timeout || requestTimeout; + fetchTargetingInformation(params); + }); +} + +submodule('realTimeData', jwplayerSubmodule); +init(config); From 34c66eba0914835e4e4599ef657e4966b899d927 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 15 Jul 2020 23:39:44 -0400 Subject: [PATCH 31/51] implements targeting retrieval --- modules/.submodules.json | 3 ++- modules/jwplayerRtdProvider | 39 ++++++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/modules/.submodules.json b/modules/.submodules.json index dd40557c35b..af7806616e1 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -18,6 +18,7 @@ "dfpAdServerVideo" ], "rtdModule": [ - "browsiRtdProvider" + "browsiRtdProvider", + "jwplayerRtdProvider" ] } diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider index d9a51407730..12555222a72 100644 --- a/modules/jwplayerRtdProvider +++ b/modules/jwplayerRtdProvider @@ -7,11 +7,10 @@ const SUBMODULE_NAME = 'jwplayer'; let requestCount = 0; let requestTimeout = 1500; const segCache = {}; -// const pendingMediaIReqs = {}; +let resumeBidRequest; export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; - // pendingMediaIDs = mediaIDs; requestCount = mediaIDs.length; mediaIDs.forEach(mediaID => { fetchTargetingForMediaId(mediaID); @@ -51,8 +50,7 @@ export function fetchTargetingForMediaId(mediaId) { ); } -function onRequestCompleted(mediaID) { - // pendingMediaIDs.remove(mediaID); +function onRequestCompleted() { requestCount--; if (requestCount > 0) { return; @@ -65,19 +63,28 @@ function onRequestCompleted(mediaID) { } function getSegments(adUnits, onDone) { - adUnits.forEach(adUnit => { - const targeting = adUnit.jwTargeting; - const seg = getTargetingForBid(targeting); - }); - let dataToReturn = adUnits.reduce((acc, adUnit) => { - const jwTargeting = adUnit.jwTargeting; - if (!jwTargeting) { + executeAfterPrefetch(() => { + const dataToReturn = adUnits.reduce((acc, adUnit) => { + const code = adUnit.code; + if (!code) { + return acc; + } + const targetingInfo = getTargetingForBid(adUnit); + if (targetingInfo.length) { + acc[code] = targetingInfo; + } return acc; - } - const adUnitCode = adUnit.code; - acc[adUnitCode] = getTargetingForBid(jwTargeting); - return acc; - }, {}); + }, {}); + onDone(dataToReturn); + }); +} + +function executeAfterPrefetch(callback) { + if (requestCount > 0) { + resumeBidRequest = callback; + } else { + callback(); + } } /** From 1d626f538158c71c0e623f1778ade351fbc5c526 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Mon, 20 Jul 2020 13:13:09 -0400 Subject: [PATCH 32/51] ensures provider is found --- modules/jwplayerRtdProvider | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider index 12555222a72..66430e4a715 100644 --- a/modules/jwplayerRtdProvider +++ b/modules/jwplayerRtdProvider @@ -2,6 +2,7 @@ import {submodule} from '../src/hook.js'; import { config } from '../src/config.js'; import { ajaxBuilder } from '../src/ajax.js'; import { logError } from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; const SUBMODULE_NAME = 'jwplayer'; let requestCount = 0; @@ -71,7 +72,9 @@ function getSegments(adUnits, onDone) { } const targetingInfo = getTargetingForBid(adUnit); if (targetingInfo.length) { - acc[code] = targetingInfo; + acc[code] = { + jwTargeting: JSON.stringify(targetingInfo) + }; } return acc; }, {}); @@ -98,7 +101,6 @@ export function getTargetingForBid(bidRequest) { if (!jwTargeting) { return []; } - const playerID = jwTargeting.playerID; let mediaID = jwTargeting.mediaID; let segments = segCache[mediaID]; @@ -158,7 +160,12 @@ export const jwplayerSubmodule = { export function init(config) { config.getConfig('realTimeData', ({realTimeData}) => { - const params = realTimeData.dataProviders && realTimeData.dataProviders.filter(pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME)[0].params; + const providers = realTimeData.dataProviders; + const jwplayerProvider = providers && find(providers, (pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME)); + const params = jwplayerProvider && jwplayerProvider.params; + if (!params) { + return; + } requestTimeout = params.auctionDelay || params.timeout || requestTimeout; fetchTargetingInformation(params); }); From e393980f13021cfcb1b3ed973bb151fd49392874 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Tue, 21 Jul 2020 16:13:50 -0400 Subject: [PATCH 33/51] implements init --- modules/jwplayerRtdProvider | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider index 66430e4a715..be1f9e8494c 100644 --- a/modules/jwplayerRtdProvider +++ b/modules/jwplayerRtdProvider @@ -155,10 +155,11 @@ export const jwplayerSubmodule = { * @param {adUnit[]} adUnits * @param {function} onDone */ - getData: getSegments + getData: getSegments, + init }; -export function init(config) { +export function beforeInit(config) { config.getConfig('realTimeData', ({realTimeData}) => { const providers = realTimeData.dataProviders; const jwplayerProvider = providers && find(providers, (pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME)); @@ -171,5 +172,9 @@ export function init(config) { }); } +function init(config, gdpr, usp) { + return true; +} + submodule('realTimeData', jwplayerSubmodule); -init(config); +beforeInit(config); From 5d6556a19fdf8d0e431ad651f13a5ee306e6ba7b Mon Sep 17 00:00:00 2001 From: karimJWP Date: Tue, 21 Jul 2020 23:32:03 -0400 Subject: [PATCH 34/51] jwTargeting is object --- modules/jwplayerRtdProvider | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider index be1f9e8494c..678dd3f30ca 100644 --- a/modules/jwplayerRtdProvider +++ b/modules/jwplayerRtdProvider @@ -73,7 +73,9 @@ function getSegments(adUnits, onDone) { const targetingInfo = getTargetingForBid(adUnit); if (targetingInfo.length) { acc[code] = { - jwTargeting: JSON.stringify(targetingInfo) + jwTargeting: { + segments: targetingInfo + } }; } return acc; From 2c6611723a9500ece3e916e1e13347a4006530a6 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 16:49:15 -0400 Subject: [PATCH 35/51] commits test file --- test/spec/modules/jwplayerRtdProvider_spec.js | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 test/spec/modules/jwplayerRtdProvider_spec.js diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js new file mode 100644 index 00000000000..80653621c72 --- /dev/null +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -0,0 +1,308 @@ +import { fetchTargetingForMediaId, getTargetingForBid, fetchTargetingInformation } from 'modules/jwplayerRtdProvider.js'; +import * as rtdModule from 'modules/rtdModule/index.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('jwplayerRtdProvider', function() { + const validSegments = ['test_seg_1', 'test_seg_2']; + + describe('Fetch targeting for mediaID tests', function () { + let request; + const testIdForSuccess = 'test_id_for_success'; + const testIdForFailure = 'test_id_for_failure'; + + describe('Fetch succeeds', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForSuccess); + request = server.requests[0]; + rtdModule.getProviderData; + }); + + it('should reach out to media endpoint', function () { + expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testIdForSuccess}`); + }); + + it('should write to cache when successful', function () { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4', + jwpseg: validSegments + } + ] + }) + ); + + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForSuccess + } + }); + + expect(targetingInfo).to.deep.equal(validSegments); + }); + }); + + describe('Fetch fails', function () { + beforeEach(function () { + fetchTargetingForMediaId(testIdForFailure); + request = server.requests[0] + }); + + it('should not write to cache when response is malformed', function() { + request.respond('{]'); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when playlist is absent', function() { + request.respond({}); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when segments are absent', function() { + request.respond( + 200, + responseHeader, + JSON.stringify({ + playlist: [ + { + file: 'test.mp4' + } + ] + }) + ); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + + it('should not write to cache when request errors', function() { + request.error(); + const targetingInfo = getTargetingForBid({ + jwTargeting: { + mediaID: testIdForFailure + } + }); + expect(targetingInfo).to.deep.equal([]); + }); + }); + }); + + describe('Get targeting for bid', function() { + const mediaIdWithSegment = 'media_ID_1'; + const mediaIdNoSegment = 'media_ID_2'; + const mediaIdForCurrentItem = 'media_ID_current'; + const mediaIdNotCached = 'media_test_ID'; + + const validPlayerID = 'player_test_ID_valid'; + const invalidPlayerID = 'player_test_ID_invalid'; + + it('returns empty array when targeting block is missing', function () { + const targeting = getTargetingForBid({}); + expect(targeting).to.deep.equal([]); + }); + + it('returns empty array when jwplayer.js is absent from page', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdNotCached + } + }); + expect(targeting).to.deep.equal([]); + }); + + describe('When jwplayer.js is on page', function () { + const playlistItemWithSegmentMock = { + mediaid: mediaIdWithSegment, + jwpseg: validSegments + }; + + const playlistItemNoSegmentMock = { + mediaid: mediaIdNoSegment + }; + + const currentItemSegments = ['test_seg_3', 'test_seg_4']; + const currentPlaylistItemMock = { + mediaid: mediaIdForCurrentItem, + jwpseg: currentItemSegments + }; + + const playerInstanceMock = { + getPlaylist: function () { + return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; + }, + + getPlaylistItem: function () { + return currentPlaylistItemMock; + } + }; + + const jwplayerMock = function(playerID) { + if (playerID === validPlayerID) { + return playerInstanceMock; + } else { + return {}; + } + }; + + beforeEach(function () { + window.jwplayer = jwplayerMock; + }); + + it('returns empty array when player ID does not match player on page', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdNotCached + } + }); + expect(targeting).to.deep.equal([]); + }); + + it('returns segments when media ID matches a playlist item with segments', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID, + mediaID: mediaIdWithSegment + } + }); + expect(targeting).to.deep.equal(validSegments); + }); + + it('caches segments media ID matches a playist item with segments', function () { + getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID, + mediaID: mediaIdWithSegment + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdWithSegment + } + }); + expect(targeting2).to.deep.equal(validSegments); + }); + + it('returns segments of current item when media ID is missing', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID + } + }); + expect(targeting).to.deep.equal(currentItemSegments); + }); + + it('caches segments from the current item', function () { + getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID + } + }); + + window.jwplayer = null; + const targeting2 = getTargetingForBid({ + jwTargeting: { + playerID: invalidPlayerID, + mediaID: mediaIdForCurrentItem + } + }); + expect(targeting2).to.deep.equal(currentItemSegments); + }); + }); + }); + + describe('Blocking mechanism for bid requests', function () { + const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; + let bidRequestSpy; + let fakeServer; + let clock; + + beforeEach(function () { + bidRequestSpy = sinon.spy(); + + fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; + + clock = sinon.useFakeTimers({ + toFake: ['setTimeout'] + }); + }); + + afterEach(function () { + clock.restore(); + }); + + it('executes the bidRequest immediately when no requests are pending', function () { + fetchTargetingInformation({ + mediaIDs: [] + }); + // ensureFeedRequestCompletion(bidRequestSpy, {}); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('executes the bidRequest after timeout if requests are still pending', function () { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + // ensureFeedRequestCompletion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(150); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('executes the bidRequest only once if requests succeed after timeout', function () { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + // ensureFeedRequestCompletion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(150); + expect(bidRequestSpy.calledOnce).to.be.true; + + fakeServer.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + + it('executes the bidRequest when all pending jwpseg requests are done', function () { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + // ensureFeedRequestCompletion(bidRequestSpy, {}); + expect(bidRequestSpy.notCalled).to.be.true; + + const req1 = fakeServer.requests[0]; + const req2 = fakeServer.requests[1]; + const req3 = fakeServer.requests[2]; + + req1.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req2.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req3.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); + }); +}); From e091b3dcdd335d052ca746f85454304d1f5c3610 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 17:39:00 -0400 Subject: [PATCH 36/51] adds file extension --- modules/{jwplayerRtdProvider => jwplayerRtdProvider.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename modules/{jwplayerRtdProvider => jwplayerRtdProvider.js} (99%) diff --git a/modules/jwplayerRtdProvider b/modules/jwplayerRtdProvider.js similarity index 99% rename from modules/jwplayerRtdProvider rename to modules/jwplayerRtdProvider.js index 678dd3f30ca..78eb8af5947 100644 --- a/modules/jwplayerRtdProvider +++ b/modules/jwplayerRtdProvider.js @@ -1,4 +1,4 @@ -import {submodule} from '../src/hook.js'; +import { submodule } from '../src/hook.js'; import { config } from '../src/config.js'; import { ajaxBuilder } from '../src/ajax.js'; import { logError } from '../src/utils.js'; From cc5ccfcd3886b68351bc4f48578ace9f07ed12ed Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 18:37:35 -0400 Subject: [PATCH 37/51] adds tests --- modules/jwplayerRtdProvider.js | 4 +- test/spec/modules/jwplayerRtdProvider_spec.js | 128 ++++++++++-------- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 78eb8af5947..04f65760d71 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -6,7 +6,7 @@ import find from 'core-js-pure/features/array/find.js'; const SUBMODULE_NAME = 'jwplayer'; let requestCount = 0; -let requestTimeout = 1500; +let requestTimeout = 150; const segCache = {}; let resumeBidRequest; @@ -164,7 +164,7 @@ export const jwplayerSubmodule = { export function beforeInit(config) { config.getConfig('realTimeData', ({realTimeData}) => { const providers = realTimeData.dataProviders; - const jwplayerProvider = providers && find(providers, (pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME)); + const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME); const params = jwplayerProvider && jwplayerProvider.params; if (!params) { return; diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 80653621c72..74b77ce5773 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -1,9 +1,10 @@ -import { fetchTargetingForMediaId, getTargetingForBid, fetchTargetingInformation } from 'modules/jwplayerRtdProvider.js'; -import * as rtdModule from 'modules/rtdModule/index.js'; +import { fetchTargetingForMediaId, getTargetingForBid, + fetchTargetingInformation, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import { server } from 'test/mocks/xhr.js'; describe('jwplayerRtdProvider', function() { const validSegments = ['test_seg_1', 'test_seg_2']; + const responseHeader = {'Content-Type': 'application/json'}; describe('Fetch targeting for mediaID tests', function () { let request; @@ -14,7 +15,6 @@ describe('jwplayerRtdProvider', function() { beforeEach(function () { fetchTargetingForMediaId(testIdForSuccess); request = server.requests[0]; - rtdModule.getProviderData; }); it('should reach out to media endpoint', function () { @@ -231,78 +231,88 @@ describe('jwplayerRtdProvider', function() { }); }); - describe('Blocking mechanism for bid requests', function () { - const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; - let bidRequestSpy; - let fakeServer; - let clock; - - beforeEach(function () { - bidRequestSpy = sinon.spy(); + describe('jwplayerSubmodule', function () { + it('successfully instantiates', function () { + expect(jwplayerSubmodule.init()).to.equal(true); + }); - fakeServer = sinon.createFakeServer(); - fakeServer.respondImmediately = false; - fakeServer.autoRespond = false; + describe('getData', function () { + const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; + let bidRequestSpy; + let fakeServer; + let clock; - clock = sinon.useFakeTimers({ - toFake: ['setTimeout'] - }); - }); + beforeEach(function () { + bidRequestSpy = sinon.spy(); - afterEach(function () { - clock.restore(); - }); + fakeServer = sinon.createFakeServer(); + fakeServer.respondImmediately = false; + fakeServer.autoRespond = false; - it('executes the bidRequest immediately when no requests are pending', function () { - fetchTargetingInformation({ - mediaIDs: [] + clock = sinon.useFakeTimers({ + toFake: ['setTimeout'] + }); }); - // ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - it('executes the bidRequest after timeout if requests are still pending', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs + afterEach(function () { + clock.restore(); }); - // ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(150); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - it('executes the bidRequest only once if requests succeed after timeout', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs + it('executes callback immediately when no requests are pending', function () { + fetchTargetingInformation({ + mediaIDs: [] + }); + jwplayerSubmodule.getData([], bidRequestSpy); + expect(bidRequestSpy.calledOnce).to.be.true; }); - // ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(150); - expect(bidRequestSpy.calledOnce).to.be.true; - fakeServer.respond(); - expect(bidRequestSpy.calledOnce).to.be.true; - }); + it('executes callback after requests complete', function() { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + jwplayerSubmodule.getData([], bidRequestSpy); + expect(bidRequestSpy.notCalled).to.be.true; + + const req1 = fakeServer.requests[0]; + const req2 = fakeServer.requests[1]; + const req3 = fakeServer.requests[2]; + + req1.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req2.respond(); + expect(bidRequestSpy.notCalled).to.be.true; + + req3.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); - it('executes the bidRequest when all pending jwpseg requests are done', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs + it('executes callback after timeout', function () { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + jwplayerSubmodule.getData([], bidRequestSpy); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(150); + expect(bidRequestSpy.calledOnce).to.be.true; }); - // ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - const req1 = fakeServer.requests[0]; - const req2 = fakeServer.requests[1]; - const req3 = fakeServer.requests[2]; + it('executes callback only once if requests succeed after timeout', function () { + fetchTargetingInformation({ + mediaIDs: validMediaIDs + }); + jwplayerSubmodule.getData([], bidRequestSpy); + expect(bidRequestSpy.notCalled).to.be.true; + clock.tick(150); + expect(bidRequestSpy.calledOnce).to.be.true; - req1.respond(); - expect(bidRequestSpy.notCalled).to.be.true; + fakeServer.respond(); + expect(bidRequestSpy.calledOnce).to.be.true; + }); - req2.respond(); - expect(bidRequestSpy.notCalled).to.be.true; + it('returns data in proper structure', function () { - req3.respond(); - expect(bidRequestSpy.calledOnce).to.be.true; + }); }); }); }); From 8bf45fd6ab32c50dc16dfa7bda2f4a37f2bd10d8 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 19:09:10 -0400 Subject: [PATCH 38/51] fixes test for proper structure --- test/spec/modules/jwplayerRtdProvider_spec.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 74b77ce5773..2f949528323 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -4,11 +4,11 @@ import { server } from 'test/mocks/xhr.js'; describe('jwplayerRtdProvider', function() { const validSegments = ['test_seg_1', 'test_seg_2']; + const testIdForSuccess = 'test_id_for_success'; const responseHeader = {'Content-Type': 'application/json'}; describe('Fetch targeting for mediaID tests', function () { let request; - const testIdForSuccess = 'test_id_for_success'; const testIdForFailure = 'test_id_for_failure'; describe('Fetch succeeds', function () { @@ -311,7 +311,22 @@ describe('jwplayerRtdProvider', function() { }); it('returns data in proper structure', function () { - + const adUnitCode = 'test_ad_unit'; + const adUnitWithMediaId = { + code: adUnitCode, + mediaID: testIdForSuccess + }; + const adUnitEmpty = { + code: 'test_ad_unit_empty' + }; + const expectedData = {}; + expectedData[adUnitCode] = { + jwTargeting: { + segments: validSegments + } + }; + jwplayerSubmodule.getData([adUnitWithMediaId, adUnitEmpty], bidRequestSpy); + bidRequestSpy.calledOnceWithExactly(expectedData); }); }); }); From ba213851cd1e211d29ba083b646dfa253c221ead Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 19:22:13 -0400 Subject: [PATCH 39/51] uses default clock mock --- test/spec/modules/jwplayerRtdProvider_spec.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 2f949528323..0de8db7cb88 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -249,9 +249,7 @@ describe('jwplayerRtdProvider', function() { fakeServer.respondImmediately = false; fakeServer.autoRespond = false; - clock = sinon.useFakeTimers({ - toFake: ['setTimeout'] - }); + clock = sinon.useFakeTimers(); }); afterEach(function () { From cf51266fecb8a9f5068c6e05e6fc7ca46de76651 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 19:28:39 -0400 Subject: [PATCH 40/51] ends reqs before rtd module timeout --- modules/jwplayerRtdProvider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 04f65760d71..1ea6eaa981b 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -169,7 +169,8 @@ export function beforeInit(config) { if (!params) { return; } - requestTimeout = params.auctionDelay || params.timeout || requestTimeout; + const rtdModuleTimeout = params.auctionDelay || params.timeout; + requestTimeout = rtdModuleTimeout === undefined ? requestTimeout : Math.max(rtdModuleTimeout - 1, 0); fetchTargetingInformation(params); }); } From c31ca791ba671f65709e53c1472f69dd856cfb51 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 23:32:15 -0400 Subject: [PATCH 41/51] removes obsolete export --- modules/jwplayerRtdProvider.js | 146 +++++++++++++++++---------------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 1ea6eaa981b..78bec0636c8 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -1,3 +1,14 @@ +/** + * This module adds the jwplayer provider to the Real Time Data module (rtdModule) + * The {@link module:modules/realTimeData} module is required + * The module will allow Ad Bidders to obtain JW Player's Video Ad Targeting information + * The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid + * requests are made while the segments are being fetched, they will be blocked until all requests complete, or the + * timeout expires. + * @module modules/jwplayerRtdProvider + * @requires module:modules/realTimeData + */ + import { submodule } from '../src/hook.js'; import { config } from '../src/config.js'; import { ajaxBuilder } from '../src/ajax.js'; @@ -10,6 +21,41 @@ let requestTimeout = 150; const segCache = {}; let resumeBidRequest; +/** @type {RtdSubmodule} */ +export const jwplayerSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: SUBMODULE_NAME, + /** + * get data and send back to realTimeData module + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ + getData: getSegments, + init +}; + +config.getConfig('realTimeData', ({realTimeData}) => { + const providers = realTimeData.dataProviders; + const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME); + const params = jwplayerProvider && jwplayerProvider.params; + if (!params) { + return; + } + const rtdModuleTimeout = params.auctionDelay || params.timeout; + requestTimeout = rtdModuleTimeout === undefined ? requestTimeout : Math.max(rtdModuleTimeout - 1, 0); + fetchTargetingInformation(params); +}); + +submodule('realTimeData', jwplayerSubmodule); + +function init(config, gdpr, usp) { + return true; +} + export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; requestCount = mediaIDs.length; @@ -20,35 +66,33 @@ export function fetchTargetingInformation(jwTargeting) { export function fetchTargetingForMediaId(mediaId) { const ajax = ajaxBuilder(requestTimeout); - ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, - { - success: function (response) { - try { - const data = JSON.parse(response); - if (!data) { - throw ('Empty response'); - } + ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { + success: function (response) { + try { + const data = JSON.parse(response); + if (!data) { + throw ('Empty response'); + } - const playlist = data.playlist; - if (!playlist || !playlist.length) { - throw ('Empty playlist'); - } + const playlist = data.playlist; + if (!playlist || !playlist.length) { + throw ('Empty playlist'); + } - const jwpseg = playlist[0].jwpseg; - if (jwpseg) { - segCache[mediaId] = jwpseg; - } - } catch (err) { - logError(err); + const jwpseg = playlist[0].jwpseg; + if (jwpseg) { + segCache[mediaId] = jwpseg; } - onRequestCompleted(); - }, - error: function () { - logError('failed to retrieve targeting information'); - onRequestCompleted(); + } catch (err) { + logError(err); } + onRequestCompleted(); + }, + error: function () { + logError('failed to retrieve targeting information'); + onRequestCompleted(); } - ); + }); } function onRequestCompleted() { @@ -65,22 +109,22 @@ function onRequestCompleted() { function getSegments(adUnits, onDone) { executeAfterPrefetch(() => { - const dataToReturn = adUnits.reduce((acc, adUnit) => { + const realTimeData = adUnits.reduce((data, adUnit) => { const code = adUnit.code; if (!code) { - return acc; + return data; } - const targetingInfo = getTargetingForBid(adUnit); - if (targetingInfo.length) { - acc[code] = { + const segments = getTargetingForBid(adUnit); + if (segments.length) { + data[code] = { jwTargeting: { - segments: targetingInfo + segments } }; } - return acc; + return data; }, {}); - onDone(dataToReturn); + onDone(realTimeData); }); } @@ -143,41 +187,3 @@ function getPlayer(playerID) { } return player; } - -/** @type {RtdSubmodule} */ -export const jwplayerSubmodule = { - /** - * used to link submodule with realTimeData - * @type {string} - */ - name: SUBMODULE_NAME, - /** - * get data and send back to realTimeData module - * @function - * @param {adUnit[]} adUnits - * @param {function} onDone - */ - getData: getSegments, - init -}; - -export function beforeInit(config) { - config.getConfig('realTimeData', ({realTimeData}) => { - const providers = realTimeData.dataProviders; - const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME); - const params = jwplayerProvider && jwplayerProvider.params; - if (!params) { - return; - } - const rtdModuleTimeout = params.auctionDelay || params.timeout; - requestTimeout = rtdModuleTimeout === undefined ? requestTimeout : Math.max(rtdModuleTimeout - 1, 0); - fetchTargetingInformation(params); - }); -} - -function init(config, gdpr, usp) { - return true; -} - -submodule('realTimeData', jwplayerSubmodule); -beforeInit(config); From 36d47c07708f8c4d785eaea78fb4a4ad371a7e5f Mon Sep 17 00:00:00 2001 From: karimJWP Date: Wed, 22 Jul 2020 23:57:15 -0400 Subject: [PATCH 42/51] request counts updates in aggregate --- modules/jwplayerRtdProvider.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 78bec0636c8..6c90f922959 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -58,7 +58,9 @@ function init(config, gdpr, usp) { export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; - requestCount = mediaIDs.length; + console.log('karim new req count: ', requestCount); + requestCount += mediaIDs.length; + console.log('karim prev req count: ', requestCount); mediaIDs.forEach(mediaID => { fetchTargetingForMediaId(mediaID); }); @@ -96,7 +98,8 @@ export function fetchTargetingForMediaId(mediaId) { } function onRequestCompleted() { - requestCount--; + requestCount = Math.max(requestCount - 1, 0) + console.log('karim req complete: ', requestCount); if (requestCount > 0) { return; } From 18a0bf3cbab38a1fb05a382dbbe7611e34fea970 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 00:15:10 -0400 Subject: [PATCH 43/51] cleans server mock after each test --- modules/jwplayerRtdProvider.js | 10 +++++----- test/spec/modules/jwplayerRtdProvider_spec.js | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 6c90f922959..cad397f65d3 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -58,9 +58,9 @@ function init(config, gdpr, usp) { export function fetchTargetingInformation(jwTargeting) { const mediaIDs = jwTargeting.mediaIDs; - console.log('karim new req count: ', requestCount); - requestCount += mediaIDs.length; - console.log('karim prev req count: ', requestCount); + if (!mediaIDs) { + return; + } mediaIDs.forEach(mediaID => { fetchTargetingForMediaId(mediaID); }); @@ -68,6 +68,7 @@ export function fetchTargetingInformation(jwTargeting) { export function fetchTargetingForMediaId(mediaId) { const ajax = ajaxBuilder(requestTimeout); + requestCount++; ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, { success: function (response) { try { @@ -98,8 +99,7 @@ export function fetchTargetingForMediaId(mediaId) { } function onRequestCompleted() { - requestCount = Math.max(requestCount - 1, 0) - console.log('karim req complete: ', requestCount); + requestCount--; if (requestCount > 0) { return; } diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 0de8db7cb88..e1f938c589b 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -17,6 +17,10 @@ describe('jwplayerRtdProvider', function() { request = server.requests[0]; }); + afterEach(function () { + server.respond(); + }); + it('should reach out to media endpoint', function () { expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testIdForSuccess}`); }); @@ -254,6 +258,7 @@ describe('jwplayerRtdProvider', function() { afterEach(function () { clock.restore(); + fakeServer.respond(); }); it('executes callback immediately when no requests are pending', function () { From c77f1f7099aca9b0ce434686a67f353da69a0a88 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 00:18:36 -0400 Subject: [PATCH 44/51] deletes jwplayer targeting --- modules/jwplayerTargeting.js | 179 ------------ test/spec/modules/jwplayerTargeting_spec.js | 309 -------------------- 2 files changed, 488 deletions(-) delete mode 100644 modules/jwplayerTargeting.js delete mode 100644 test/spec/modules/jwplayerTargeting_spec.js diff --git a/modules/jwplayerTargeting.js b/modules/jwplayerTargeting.js deleted file mode 100644 index cad3b7e642c..00000000000 --- a/modules/jwplayerTargeting.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * The JW Player Targeting module provides functions which allow Ad Bidders to obtain JW Player's - * Video Ad Targeting information. - * The module can be used as a submodule for prebid adapters, allowing them to use the getTargetingForBid() function. - * The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid - * requests are made while the segments are being fetched, they will be blocked until all requests complete, or the - * 150ms timeout expires. - */ - -import { config } from '../src/config.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { logError, isPlainObject } from '../src/utils.js'; -import { getGlobal } from '../src/prebidGlobal.js'; -import { module } from '../src/hook.js'; -import find from 'core-js-pure/features/array/find.js'; - -const segCache = {}; -let requestCount = 0; -let requestTimeout; -let resumeBidRequest; - -/* -Prebid auctions timeout at 200ms. - */ -let bidPauseTimeout = 150; - -config.getConfig('jwTargeting', config => { - const targeting = config.jwTargeting; - // fetch media ids - fetchTargetingInformation(targeting); - - const prefetchTimeout = targeting.prefetchTimeout; - if (prefetchTimeout) { - // prefetch timeout supersedes our default and our adjustment for bidderTimeout. - bidPauseTimeout = prefetchTimeout; - return; - } - - const timeout = config.bidderTimeout; - if (timeout < 200) { - // 3/4 is the ratio between 150 and 200, where 150 is our default and 200 is prebid's default auction timeout. - // Note auction will close at 200ms even if bidderTimeout is greater. - bidPauseTimeout = timeout * 3 / 4; - } -}); - -getGlobal().requestBids.before(ensureFeedRequestCompletion); - -export function fetchTargetingInformation(jwTargeting) { - const mediaIDs = jwTargeting.mediaIDs; - requestCount = mediaIDs.length; - mediaIDs.forEach(mediaID => { - fetchTargetingForMediaId(mediaID); - }); -} - -export function ensureFeedRequestCompletion(requestBids, bidRequestConfig) { - if (requestCount <= 0) { - requestBids.apply(this, [bidRequestConfig]); - return; - } - resumeBidRequest = requestBids.bind(this, bidRequestConfig); - requestTimeout = setTimeout(function() { - resumeBidRequest(); - resumeBidRequest = null; - requestTimeout = null; - }, bidPauseTimeout); -} - -/** - * Retrieves the targeting information pertaining to a bid request. - * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain - * a jwTargeting property. - * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information - */ -export function getTargetingForBid(bidRequest) { - const jwTargeting = bidRequest.jwTargeting; - if (!jwTargeting) { - return []; - } - - const playerID = jwTargeting.playerID; - let mediaID = jwTargeting.mediaID; - let segments = segCache[mediaID]; - if (segments) { - return segments; - } - - const player = getPlayer(playerID); - if (!player) { - return []; - } - - const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); - if (!item) { - return []; - } - - mediaID = mediaID || item.mediaid; - segments = item.jwpseg; - if (segments && mediaID) { - segCache[mediaID] = segments; - } - - return segments || []; -} - -function getPlayer(playerID) { - const jwplayer = window.jwplayer; - if (!jwplayer) { - logError('jwplayer.js was not found on page'); - return; - } - - const player = jwplayer(playerID); - if (!player || !player.getPlaylist) { - logError('player ID did not match any players'); - return; - } - return player; -} - -export function fetchTargetingForMediaId(mediaId) { - const ajax = ajaxBuilder(1500); - ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, - { - success: function (response) { - try { - const data = JSON.parse(response); - if (!data) { - throw ('Empty response'); - } - - const playlist = data.playlist; - if (!playlist || !playlist.length) { - throw ('Empty playlist'); - } - - const jwpseg = playlist[0].jwpseg; - if (jwpseg) { - segCache[mediaId] = jwpseg; - } - } catch (err) { - logError(err); - } - onRequestCompleted(); - }, - error: function () { - logError('failed to retrieve targeting information'); - onRequestCompleted(); - } - } - ); -} - -function onRequestCompleted() { - requestCount--; - if (requestCount > 0) { - return; - } - if (requestTimeout) { - clearTimeout(requestTimeout); - requestTimeout = null; - } - - if (resumeBidRequest) { - resumeBidRequest(); - resumeBidRequest = null; - } -} - -module('jwplayerTargeting', function shareJWPlayerUtilities() { - const host = arguments[0]; - if (!isPlainObject(host)) { - logError('JW Player module requires plain object to share methods with submodule'); - return; - } - host.getTargetingForBid = getTargetingForBid; -}); diff --git a/test/spec/modules/jwplayerTargeting_spec.js b/test/spec/modules/jwplayerTargeting_spec.js deleted file mode 100644 index 4cb195bfdfd..00000000000 --- a/test/spec/modules/jwplayerTargeting_spec.js +++ /dev/null @@ -1,309 +0,0 @@ -import { fetchTargetingForMediaId, getTargetingForBid, - ensureFeedRequestCompletion, fetchTargetingInformation } from 'modules/jwplayerTargeting.js'; -import { server } from 'test/mocks/xhr.js'; - -const responseHeader = {'Content-Type': 'application/json'}; - -describe('jwplayer', function() { - const validSegments = ['test_seg_1', 'test_seg_2']; - - describe('Fetch targeting for mediaID tests', function () { - let request; - const testIdForSuccess = 'test_id_for_success'; - const testIdForFailure = 'test_id_for_failure'; - - describe('Fetch succeeds', function () { - beforeEach(function () { - fetchTargetingForMediaId(testIdForSuccess); - request = server.requests[0]; - }); - - it('should reach out to media endpoint', function () { - expect(request.url).to.be.eq(`https://cdn.jwplayer.com/v2/media/${testIdForSuccess}`); - }); - - it('should write to cache when successful', function () { - request.respond( - 200, - responseHeader, - JSON.stringify({ - playlist: [ - { - file: 'test.mp4', - jwpseg: validSegments - } - ] - }) - ); - - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testIdForSuccess - } - }); - - expect(targetingInfo).to.deep.equal(validSegments); - }); - }); - - describe('Fetch fails', function () { - beforeEach(function () { - fetchTargetingForMediaId(testIdForFailure); - request = server.requests[0] - }); - - it('should not write to cache when response is malformed', function() { - request.respond('{]'); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testIdForFailure - } - }); - expect(targetingInfo).to.deep.equal([]); - }); - - it('should not write to cache when playlist is absent', function() { - request.respond({}); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testIdForFailure - } - }); - expect(targetingInfo).to.deep.equal([]); - }); - - it('should not write to cache when segments are absent', function() { - request.respond( - 200, - responseHeader, - JSON.stringify({ - playlist: [ - { - file: 'test.mp4' - } - ] - }) - ); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testIdForFailure - } - }); - expect(targetingInfo).to.deep.equal([]); - }); - - it('should not write to cache when request errors', function() { - request.error(); - const targetingInfo = getTargetingForBid({ - jwTargeting: { - mediaID: testIdForFailure - } - }); - expect(targetingInfo).to.deep.equal([]); - }); - }); - }); - - describe('Get targeting for bid', function() { - const mediaIdWithSegment = 'media_ID_1'; - const mediaIdNoSegment = 'media_ID_2'; - const mediaIdForCurrentItem = 'media_ID_current'; - const mediaIdNotCached = 'media_test_ID'; - - const validPlayerID = 'player_test_ID_valid'; - const invalidPlayerID = 'player_test_ID_invalid'; - - it('returns empty array when targeting block is missing', function () { - const targeting = getTargetingForBid({}); - expect(targeting).to.deep.equal([]); - }); - - it('returns empty array when jwplayer.js is absent from page', function () { - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: mediaIdNotCached - } - }); - expect(targeting).to.deep.equal([]); - }); - - describe('When jwplayer.js is on page', function () { - const playlistItemWithSegmentMock = { - mediaid: mediaIdWithSegment, - jwpseg: validSegments - }; - - const playlistItemNoSegmentMock = { - mediaid: mediaIdNoSegment - }; - - const currentItemSegments = ['test_seg_3', 'test_seg_4']; - const currentPlaylistItemMock = { - mediaid: mediaIdForCurrentItem, - jwpseg: currentItemSegments - }; - - const playerInstanceMock = { - getPlaylist: function () { - return [playlistItemWithSegmentMock, playlistItemNoSegmentMock]; - }, - - getPlaylistItem: function () { - return currentPlaylistItemMock; - } - }; - - const jwplayerMock = function(playerID) { - if (playerID === validPlayerID) { - return playerInstanceMock; - } else { - return {}; - } - }; - - beforeEach(function () { - window.jwplayer = jwplayerMock; - }); - - it('returns empty array when player ID does not match player on page', function () { - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: mediaIdNotCached - } - }); - expect(targeting).to.deep.equal([]); - }); - - it('returns segments when media ID matches a playlist item with segments', function () { - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID, - mediaID: mediaIdWithSegment - } - }); - expect(targeting).to.deep.equal(validSegments); - }); - - it('caches segments media ID matches a playist item with segments', function () { - getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID, - mediaID: mediaIdWithSegment - } - }); - - window.jwplayer = null; - const targeting2 = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: mediaIdWithSegment - } - }); - expect(targeting2).to.deep.equal(validSegments); - }); - - it('returns segments of current item when media ID is missing', function () { - const targeting = getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID - } - }); - expect(targeting).to.deep.equal(currentItemSegments); - }); - - it('caches segments from the current item', function () { - getTargetingForBid({ - jwTargeting: { - playerID: validPlayerID - } - }); - - window.jwplayer = null; - const targeting2 = getTargetingForBid({ - jwTargeting: { - playerID: invalidPlayerID, - mediaID: mediaIdForCurrentItem - } - }); - expect(targeting2).to.deep.equal(currentItemSegments); - }); - }); - }); - - describe('Blocking mechanism for bid requests', function () { - const validMediaIDs = ['media_ID_1', 'media_ID_2', 'media_ID_3']; - let bidRequestSpy; - let fakeServer; - let clock; - - beforeEach(function () { - bidRequestSpy = sinon.spy(); - - fakeServer = sinon.createFakeServer(); - fakeServer.respondImmediately = false; - fakeServer.autoRespond = false; - - clock = sinon.useFakeTimers({ - toFake: ['setTimeout'] - }); - }); - - afterEach(function () { - clock.restore(); - }); - - it('executes the bidRequest immediately when no requests are pending', function () { - fetchTargetingInformation({ - mediaIDs: [] - }); - ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - - it('executes the bidRequest after timeout if requests are still pending', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs - }); - ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(150); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - - it('executes the bidRequest only once if requests succeed after timeout', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs - }); - ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - clock.tick(150); - expect(bidRequestSpy.calledOnce).to.be.true; - - fakeServer.respond(); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - - it('executes the bidRequest when all pending jwpseg requests are done', function () { - fetchTargetingInformation({ - mediaIDs: validMediaIDs - }); - ensureFeedRequestCompletion(bidRequestSpy, {}); - expect(bidRequestSpy.notCalled).to.be.true; - - const req1 = fakeServer.requests[0]; - const req2 = fakeServer.requests[1]; - const req3 = fakeServer.requests[2]; - - req1.respond(); - expect(bidRequestSpy.notCalled).to.be.true; - - req2.respond(); - expect(bidRequestSpy.notCalled).to.be.true; - - req3.respond(); - expect(bidRequestSpy.calledOnce).to.be.true; - }); - }); -}); From eab3a6bef61c8406fd12b0e574f6134bf7b0dd04 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 12:47:56 -0400 Subject: [PATCH 45/51] includes content id --- modules/jwplayerRtdProvider.js | 36 ++++++++----- test/spec/modules/jwplayerRtdProvider_spec.js | 52 +++++++++++++------ 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index cad397f65d3..f54bbb5272a 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -117,14 +117,20 @@ function getSegments(adUnits, onDone) { if (!code) { return data; } - const segments = getTargetingForBid(adUnit); - if (segments.length) { - data[code] = { - jwTargeting: { - segments - } - }; + const { segments, mediaID } = getTargetingForBid(adUnit); + const jwTargeting = {}; + if (segments && segments.length) { + jwTargeting.segments = segments; } + if (mediaID) { + const id = 'jw_' + mediaID; + jwTargeting.content = { + id + } + } + data[code] = { + jwTargeting + }; return data; }, {}); onDone(realTimeData); @@ -148,23 +154,26 @@ function executeAfterPrefetch(callback) { export function getTargetingForBid(bidRequest) { const jwTargeting = bidRequest.jwTargeting; if (!jwTargeting) { - return []; + return {}; } const playerID = jwTargeting.playerID; let mediaID = jwTargeting.mediaID; let segments = segCache[mediaID]; if (segments) { - return segments; + return { + segments, + mediaID + }; } const player = getPlayer(playerID); if (!player) { - return []; + return {}; } const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); if (!item) { - return []; + return {}; } mediaID = mediaID || item.mediaid; @@ -173,7 +182,10 @@ export function getTargetingForBid(bidRequest) { segCache[mediaID] = segments; } - return segments || []; + return { + segments, + mediaID + }; } function getPlayer(playerID) { diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index e1f938c589b..70bfae800ab 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -3,8 +3,8 @@ import { fetchTargetingForMediaId, getTargetingForBid, import { server } from 'test/mocks/xhr.js'; describe('jwplayerRtdProvider', function() { - const validSegments = ['test_seg_1', 'test_seg_2']; const testIdForSuccess = 'test_id_for_success'; + const validSegments = ['test_seg_1', 'test_seg_2']; const responseHeader = {'Content-Type': 'application/json'}; describe('Fetch targeting for mediaID tests', function () { @@ -45,7 +45,12 @@ describe('jwplayerRtdProvider', function() { } }); - expect(targetingInfo).to.deep.equal(validSegments); + const validTargeting = { + segments: validSegments, + mediaID: testIdForSuccess + }; + + expect(targetingInfo).to.deep.equal(validTargeting); }); }); @@ -62,7 +67,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal([]); + expect(targetingInfo).to.deep.equal({}); }); it('should not write to cache when playlist is absent', function() { @@ -72,7 +77,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal([]); + expect(targetingInfo).to.deep.equal({}); }); it('should not write to cache when segments are absent', function() { @@ -92,7 +97,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal([]); + expect(targetingInfo).to.deep.equal({}); }); it('should not write to cache when request errors', function() { @@ -102,7 +107,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal([]); + expect(targetingInfo).to.deep.equal({}); }); }); }); @@ -116,19 +121,19 @@ describe('jwplayerRtdProvider', function() { const validPlayerID = 'player_test_ID_valid'; const invalidPlayerID = 'player_test_ID_invalid'; - it('returns empty array when targeting block is missing', function () { + it('returns empty object when targeting block is missing', function () { const targeting = getTargetingForBid({}); - expect(targeting).to.deep.equal([]); + expect(targeting).to.deep.equal({}); }); - it('returns empty array when jwplayer.js is absent from page', function () { + it('returns empty object when jwplayer.js is absent from page', function () { const targeting = getTargetingForBid({ jwTargeting: { playerID: invalidPlayerID, mediaID: mediaIdNotCached } }); - expect(targeting).to.deep.equal([]); + expect(targeting).to.deep.equal({}); }); describe('When jwplayer.js is on page', function () { @@ -137,6 +142,11 @@ describe('jwplayerRtdProvider', function() { jwpseg: validSegments }; + const targetingForMediaWithSegment = { + segments: validSegments, + mediaID: mediaIdWithSegment + }; + const playlistItemNoSegmentMock = { mediaid: mediaIdNoSegment }; @@ -146,6 +156,10 @@ describe('jwplayerRtdProvider', function() { mediaid: mediaIdForCurrentItem, jwpseg: currentItemSegments }; + const targetingForCurrentItem = { + segments: currentItemSegments, + mediaID: mediaIdForCurrentItem + }; const playerInstanceMock = { getPlaylist: function () { @@ -169,14 +183,14 @@ describe('jwplayerRtdProvider', function() { window.jwplayer = jwplayerMock; }); - it('returns empty array when player ID does not match player on page', function () { + it('returns empty object when player ID does not match player on page', function () { const targeting = getTargetingForBid({ jwTargeting: { playerID: invalidPlayerID, mediaID: mediaIdNotCached } }); - expect(targeting).to.deep.equal([]); + expect(targeting).to.deep.equal({}); }); it('returns segments when media ID matches a playlist item with segments', function () { @@ -186,7 +200,7 @@ describe('jwplayerRtdProvider', function() { mediaID: mediaIdWithSegment } }); - expect(targeting).to.deep.equal(validSegments); + expect(targeting).to.deep.equal(targetingForMediaWithSegment); }); it('caches segments media ID matches a playist item with segments', function () { @@ -204,7 +218,7 @@ describe('jwplayerRtdProvider', function() { mediaID: mediaIdWithSegment } }); - expect(targeting2).to.deep.equal(validSegments); + expect(targeting2).to.deep.equal(targetingForMediaWithSegment); }); it('returns segments of current item when media ID is missing', function () { @@ -213,7 +227,7 @@ describe('jwplayerRtdProvider', function() { playerID: validPlayerID } }); - expect(targeting).to.deep.equal(currentItemSegments); + expect(targeting).to.deep.equal(targetingForCurrentItem); }); it('caches segments from the current item', function () { @@ -230,7 +244,7 @@ describe('jwplayerRtdProvider', function() { mediaID: mediaIdForCurrentItem } }); - expect(targeting2).to.deep.equal(currentItemSegments); + expect(targeting2).to.deep.equal(targetingForCurrentItem); }); }); }); @@ -323,9 +337,13 @@ describe('jwplayerRtdProvider', function() { code: 'test_ad_unit_empty' }; const expectedData = {}; + const expectedContentId = 'jw_' + testIdForSuccess; expectedData[adUnitCode] = { jwTargeting: { - segments: validSegments + segments: validSegments, + content: { + id: expectedContentId + } } }; jwplayerSubmodule.getData([adUnitWithMediaId, adUnitEmpty], bidRequestSpy); From b60dcf98e02c4c3aae92f659c87ed40b6a77827d Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 13:14:12 -0400 Subject: [PATCH 46/51] adds test for missing segment --- test/spec/modules/jwplayerRtdProvider_spec.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 70bfae800ab..4b671e429f5 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -246,6 +246,19 @@ describe('jwplayerRtdProvider', function() { }); expect(targeting2).to.deep.equal(targetingForCurrentItem); }); + + it('returns undefined segments when segments are absent', function () { + const targeting = getTargetingForBid({ + jwTargeting: { + playerID: validPlayerID, + mediaID: mediaIdNoSegment + } + }); + expect(targeting).to.deep.equal({ + mediaID: mediaIdNoSegment, + segments: undefined + }); + }); }); }); From 369ee70810464882ba3da4caeb2a4a0b0a3b7774 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 15:21:21 -0400 Subject: [PATCH 47/51] getSegments is nullable --- modules/jwplayerRtdProvider.js | 12 ++-- test/spec/modules/jwplayerRtdProvider_spec.js | 58 ++++++++++++++----- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index f54bbb5272a..5da0c7cf3fc 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -117,7 +117,11 @@ function getSegments(adUnits, onDone) { if (!code) { return data; } - const { segments, mediaID } = getTargetingForBid(adUnit); + const vat = getTargetingForBid(adUnit); + if (!vat) { + return data; + } + const { segments, mediaID } = vat; const jwTargeting = {}; if (segments && segments.length) { jwTargeting.segments = segments; @@ -154,7 +158,7 @@ function executeAfterPrefetch(callback) { export function getTargetingForBid(bidRequest) { const jwTargeting = bidRequest.jwTargeting; if (!jwTargeting) { - return {}; + return null; } const playerID = jwTargeting.playerID; let mediaID = jwTargeting.mediaID; @@ -168,12 +172,12 @@ export function getTargetingForBid(bidRequest) { const player = getPlayer(playerID); if (!player) { - return {}; + return null; } const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem(); if (!item) { - return {}; + return null; } mediaID = mediaID || item.mediaid; diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index 4b671e429f5..b5bacdc3694 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -4,12 +4,12 @@ import { server } from 'test/mocks/xhr.js'; describe('jwplayerRtdProvider', function() { const testIdForSuccess = 'test_id_for_success'; + const testIdForFailure = 'test_id_for_failure'; const validSegments = ['test_seg_1', 'test_seg_2']; const responseHeader = {'Content-Type': 'application/json'}; describe('Fetch targeting for mediaID tests', function () { let request; - const testIdForFailure = 'test_id_for_failure'; describe('Fetch succeeds', function () { beforeEach(function () { @@ -67,7 +67,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal({}); + expect(targetingInfo).to.be.null; }); it('should not write to cache when playlist is absent', function() { @@ -77,7 +77,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal({}); + expect(targetingInfo).to.be.null; }); it('should not write to cache when segments are absent', function() { @@ -97,7 +97,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal({}); + expect(targetingInfo).to.be.null; }); it('should not write to cache when request errors', function() { @@ -107,7 +107,7 @@ describe('jwplayerRtdProvider', function() { mediaID: testIdForFailure } }); - expect(targetingInfo).to.deep.equal({}); + expect(targetingInfo).to.be.null; }); }); }); @@ -121,19 +121,19 @@ describe('jwplayerRtdProvider', function() { const validPlayerID = 'player_test_ID_valid'; const invalidPlayerID = 'player_test_ID_invalid'; - it('returns empty object when targeting block is missing', function () { + it('returns null when targeting block is missing', function () { const targeting = getTargetingForBid({}); - expect(targeting).to.deep.equal({}); + expect(targeting).to.be.null; }); - it('returns empty object when jwplayer.js is absent from page', function () { + it('returns null when jwplayer.js is absent from page', function () { const targeting = getTargetingForBid({ jwTargeting: { playerID: invalidPlayerID, mediaID: mediaIdNotCached } }); - expect(targeting).to.deep.equal({}); + expect(targeting).to.be.null; }); describe('When jwplayer.js is on page', function () { @@ -183,14 +183,14 @@ describe('jwplayerRtdProvider', function() { window.jwplayer = jwplayerMock; }); - it('returns empty object when player ID does not match player on page', function () { + it('returns null when player ID does not match player on page', function () { const targeting = getTargetingForBid({ jwTargeting: { playerID: invalidPlayerID, mediaID: mediaIdNotCached } }); - expect(targeting).to.deep.equal({}); + expect(targeting).to.be.null; }); it('returns segments when media ID matches a playlist item with segments', function () { @@ -344,7 +344,9 @@ describe('jwplayerRtdProvider', function() { const adUnitCode = 'test_ad_unit'; const adUnitWithMediaId = { code: adUnitCode, - mediaID: testIdForSuccess + jwTargeting: { + mediaID: testIdForSuccess + } }; const adUnitEmpty = { code: 'test_ad_unit_empty' @@ -360,7 +362,37 @@ describe('jwplayerRtdProvider', function() { } }; jwplayerSubmodule.getData([adUnitWithMediaId, adUnitEmpty], bidRequestSpy); - bidRequestSpy.calledOnceWithExactly(expectedData); + expect(bidRequestSpy.calledOnceWithExactly(expectedData)).to.be.true; + }); + + it('returns an empty object when media id is invalid', function () { + const adUnitCode = 'test_ad_unit'; + const adUnitWithMediaId = { + code: adUnitCode, + jwTargeting: { + mediaID: testIdForFailure + } + }; + const adUnitEmpty = { + code: 'test_ad_unit_empty' + }; + + jwplayerSubmodule.getData([adUnitWithMediaId, adUnitEmpty], bidRequestSpy); + expect(bidRequestSpy.calledOnceWithExactly({})).to.be.true; + }); + + it('returns an empty object when jwTargeting block is absent', function () { + const adUnitCode = 'test_ad_unit'; + const adUnitWithMediaId = { + code: adUnitCode, + mediaID: testIdForSuccess + }; + const adUnitEmpty = { + code: 'test_ad_unit_empty' + }; + + jwplayerSubmodule.getData([adUnitWithMediaId, adUnitEmpty], bidRequestSpy); + expect(bidRequestSpy.calledOnceWithExactly({})).to.be.true; }); }); }); From e7707990a8c7e373d5059679b503c606ae177b07 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 16:01:25 -0400 Subject: [PATCH 48/51] replaces condition with guard --- modules/jwplayerRtdProvider.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 5da0c7cf3fc..732724ae0ad 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -114,24 +114,24 @@ function getSegments(adUnits, onDone) { executeAfterPrefetch(() => { const realTimeData = adUnits.reduce((data, adUnit) => { const code = adUnit.code; - if (!code) { - return data; - } - const vat = getTargetingForBid(adUnit); + const vat = code && getTargetingForBid(adUnit); if (!vat) { return data; } + const { segments, mediaID } = vat; const jwTargeting = {}; if (segments && segments.length) { jwTargeting.segments = segments; } + if (mediaID) { const id = 'jw_' + mediaID; jwTargeting.content = { id } } + data[code] = { jwTargeting }; From 97a6a9bbd5d9792b547549ba4bae004e03d818e2 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Thu, 23 Jul 2020 16:33:03 -0400 Subject: [PATCH 49/51] updates doc --- modules/jwplayerRtdProvider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 732724ae0ad..b7c8879ed8e 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -153,7 +153,8 @@ function executeAfterPrefetch(callback) { * Retrieves the targeting information pertaining to a bid request. * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain * a jwTargeting property. - * @returns {Array} - an array of jwpseg targeting segments found for the given bidRequest information + * @returns targetingInformation {object} nullable - contains the media ID as well as the jwpseg targeting segments + * found for the given bidRequest information */ export function getTargetingForBid(bidRequest) { const jwTargeting = bidRequest.jwTargeting; From b62958fb6a5f2caee0caddd9d702da3b12184320 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Sat, 25 Jul 2020 01:06:58 -0400 Subject: [PATCH 50/51] adds md file --- .../gpt/jwplayerRtdProvider_example.html | 10 ++ modules/jwplayerRtdProvider.md | 94 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 integrationExamples/gpt/jwplayerRtdProvider_example.html create mode 100644 modules/jwplayerRtdProvider.md diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html new file mode 100644 index 00000000000..e149a394349 --- /dev/null +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md new file mode 100644 index 00000000000..15854cb1513 --- /dev/null +++ b/modules/jwplayerRtdProvider.md @@ -0,0 +1,94 @@ +The purpose of this Real Time Data Provider is to allow publishers to target against their JW Player media without +having to integrate with the VPB product. This prebid module makes JW Player's video ad targeting information accessible +to Bid Adapters. + +**Usage for Publishers:** + +Compile the JW Player RTD Provider into your Prebid build: + +`gulp build --modules=jwplayerRtdProvider` + +Publishers must register JW Player as a real time data provider by setting up a Prebid Config conformant to the +following structure: + +```javascript +const jwplayerDataProvider = { + name: "jwplayer" +}; + +pbjs.setConfig({ + ..., + realTimeData: { + dataProviders: [ + jwplayerDataProvider + ] + } +}); +``` + +In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var: + +```javascript +const jwplayerDataProvider = { + name: "jwplayer", + params: { + mediaIDs: ['abc', 'def', 'ghi', 'jkl'] + } +}; +``` +Lastly, include the content's media ID and/or the player's ID in the matching AdUnit: + +```javascript +const adUnit = { + code: '/19968336/prebid_native_example_1', + ... + jwTargeting: { + playerID: 'abcd', + mediaID: '1234' + } +}; + +pbjs.que.push(function() { + pbjs.addAdUnits([adUnit]); + pbjs.requestBids({ + ... + }); +}); +``` + +**Usage for Bid Adapters:** + +Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids. +Each bid for which targeting information was found will conform to the following object structure: + +```javascript +{ + adUnitCode: 'xyz', + bidId: 'abc', + ... + realTimeData: { + ..., + jwTargeting: { + segments: ['123', '456'], + content: { + id: 'jw_abc123' + } + } + } +} +``` + +where: +- `segments` is an array of jwpseg targeting segments, of type string. +- `content` is an object containing metadata for the media. It may contain the following information: + - `id` is a unique identifier for the specific media asset. + +**Example:** + +To view an example, in your CL run: + +`gulp serve --modules=jwplayerRtdProvider` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` From 5bcb160ede026b22c8beb545c0c72f57690108b6 Mon Sep 17 00:00:00 2001 From: karimJWP Date: Sat, 25 Jul 2020 01:45:23 -0400 Subject: [PATCH 51/51] adds example page --- .../gpt/jwplayerRtdProvider_example.html | 92 ++++++++++++++++++- modules/jwplayerRtdProvider.md | 8 +- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html index e149a394349..3791ab42137 100644 --- a/integrationExamples/gpt/jwplayerRtdProvider_example.html +++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html @@ -1,10 +1,98 @@ + + - Title + JW Player RTD Provider Example + + + + + - + +
Div-1
+
+ +
diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 15854cb1513..06a7f69f497 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -85,10 +85,12 @@ where: **Example:** -To view an example, in your CL run: +To view an example: + +- in your cli run: `gulp serve --modules=jwplayerRtdProvider` -and then point your browser at: +- in your browser, navigate to: -`http://localhost:9999/integrationExamples/gpt/audigentSegments_example.html` +`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html`