diff --git a/integrationExamples/noadserver/jwplayerBidAdapter_sample.html b/integrationExamples/noadserver/jwplayerBidAdapter_sample.html
new file mode 100644
index 00000000000..f8b15af64a2
--- /dev/null
+++ b/integrationExamples/noadserver/jwplayerBidAdapter_sample.html
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html
index 4f29ef1c406..f3f0c64fb1a 100644
--- a/integrationExamples/realTimeData/jwplayerRtdProvider_example.html
+++ b/integrationExamples/realTimeData/jwplayerRtdProvider_example.html
@@ -35,6 +35,13 @@
},
// Replace this object to test a new Adapter!
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html
index 80ea81d09b6..d0b261043e4 100644
--- a/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html
+++ b/integrationExamples/videoModule/jwplayer/bidMarkedAsUsed.html
@@ -20,6 +20,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html
index 663765317b0..c40af32cac2 100644
--- a/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html
+++ b/integrationExamples/videoModule/jwplayer/bidRequestScheduling.html
@@ -50,12 +50,17 @@
mediationLayerAdServer: "dfp",
bidTimeout: 2000
},
- bidders: [
- {
+ bidders: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
name: "ix",
siteId: "300"
- }
- ]
+ }]
}
}
}
diff --git a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html
index 66eaff26090..75a72ba3501 100644
--- a/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html
+++ b/integrationExamples/videoModule/jwplayer/bidsBackHandlerOverride.html
@@ -19,6 +19,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/eventListeners.html b/integrationExamples/videoModule/jwplayer/eventListeners.html
index 39acb086107..6f04f37264b 100644
--- a/integrationExamples/videoModule/jwplayer/eventListeners.html
+++ b/integrationExamples/videoModule/jwplayer/eventListeners.html
@@ -23,6 +23,13 @@
// Replace this object to test a new Adapter!
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/eventsUI.html b/integrationExamples/videoModule/jwplayer/eventsUI.html
index 78d72a6153d..cfd1efe7624 100644
--- a/integrationExamples/videoModule/jwplayer/eventsUI.html
+++ b/integrationExamples/videoModule/jwplayer/eventsUI.html
@@ -22,6 +22,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html
index 018c8eba8b2..1f4331785ea 100644
--- a/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html
+++ b/integrationExamples/videoModule/jwplayer/gamAdServerMediation.html
@@ -19,6 +19,13 @@
divId: 'player', // required to indicate which player is being used to render this ad unit.
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/mediaMetadata.html b/integrationExamples/videoModule/jwplayer/mediaMetadata.html
index 7581af571d3..63e62aa4b82 100644
--- a/integrationExamples/videoModule/jwplayer/mediaMetadata.html
+++ b/integrationExamples/videoModule/jwplayer/mediaMetadata.html
@@ -20,6 +20,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/jwplayer/playlist.html b/integrationExamples/videoModule/jwplayer/playlist.html
index 89efaea3d5c..9e89f606f23 100644
--- a/integrationExamples/videoModule/jwplayer/playlist.html
+++ b/integrationExamples/videoModule/jwplayer/playlist.html
@@ -20,6 +20,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html
index d6656bc0c93..35745ab281f 100644
--- a/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html
+++ b/integrationExamples/videoModule/videojs/bidMarkedAsUsed.html
@@ -35,6 +35,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/bidRequestScheduling.html b/integrationExamples/videoModule/videojs/bidRequestScheduling.html
index eb10fda89a2..da6499ca4cc 100644
--- a/integrationExamples/videoModule/videojs/bidRequestScheduling.html
+++ b/integrationExamples/videoModule/videojs/bidRequestScheduling.html
@@ -37,6 +37,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html
index ac8f4163e76..74217ecee2c 100644
--- a/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html
+++ b/integrationExamples/videoModule/videojs/bidsBackHandlerOverride.html
@@ -34,6 +34,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/eventListeners.html b/integrationExamples/videoModule/videojs/eventListeners.html
index 1966f134e02..3fc2ef7137c 100644
--- a/integrationExamples/videoModule/videojs/eventListeners.html
+++ b/integrationExamples/videoModule/videojs/eventListeners.html
@@ -35,6 +35,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/eventsUI.html b/integrationExamples/videoModule/videojs/eventsUI.html
index 9eba09f7a52..215b2de4d25 100644
--- a/integrationExamples/videoModule/videojs/eventsUI.html
+++ b/integrationExamples/videoModule/videojs/eventsUI.html
@@ -37,6 +37,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/gamAdServerMediation.html b/integrationExamples/videoModule/videojs/gamAdServerMediation.html
index 6ffc1a67c03..d6603abbf8f 100644
--- a/integrationExamples/videoModule/videojs/gamAdServerMediation.html
+++ b/integrationExamples/videoModule/videojs/gamAdServerMediation.html
@@ -34,6 +34,13 @@
divId: 'player', // required to indicate which player is being used to render this ad unit.
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/mediaMetadata.html b/integrationExamples/videoModule/videojs/mediaMetadata.html
index ede076fd814..084c597cddd 100644
--- a/integrationExamples/videoModule/videojs/mediaMetadata.html
+++ b/integrationExamples/videoModule/videojs/mediaMetadata.html
@@ -35,6 +35,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/integrationExamples/videoModule/videojs/playlist.html b/integrationExamples/videoModule/videojs/playlist.html
index eb813f095f7..2563717df41 100644
--- a/integrationExamples/videoModule/videojs/playlist.html
+++ b/integrationExamples/videoModule/videojs/playlist.html
@@ -36,6 +36,13 @@
},
bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }, {
bidder: 'ix',
params: {
siteId: '300',
diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js
index 51c93b652ef..2d0136c84b2 100644
--- a/libraries/ortbConverter/processors/banner.js
+++ b/libraries/ortbConverter/processors/banner.js
@@ -1,4 +1,4 @@
-import {createTrackPixelHtml, deepAccess, inIframe, mergeDeep} from '../../../src/utils.js';
+import {createTrackPixelHtml, deepAccess, encodeMacroURI, inIframe, mergeDeep} from '../../../src/utils.js';
import {BANNER} from '../../../src/mediaTypes.js';
import {sizesToFormat} from '../lib/sizes.js';
@@ -24,7 +24,7 @@ export function fillBannerImp(imp, bidRequest, context) {
}
}
-export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url))} = {}) {
+export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url), encodeMacroURI)} = {}) {
return function fillBannerResponse(bidResponse, bid) {
if (bidResponse.mediaType === BANNER) {
if (bid.adm && bid.nurl) {
diff --git a/modules/adgenerationBidAdapter.js b/modules/adgenerationBidAdapter.js
index e0538fe2815..16375d92194 100644
--- a/modules/adgenerationBidAdapter.js
+++ b/modules/adgenerationBidAdapter.js
@@ -38,7 +38,7 @@ export const spec = {
buildRequests: function (validBidRequests, bidderRequest) {
// convert Native ORTB definition to old-style prebid native definition
validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
- const ADGENE_PREBID_VERSION = '1.6.2';
+ const ADGENE_PREBID_VERSION = '1.6.3';
let serverRequests = [];
for (let i = 0, len = validBidRequests.length; i < len; i++) {
const validReq = validBidRequests[i];
@@ -80,12 +80,12 @@ export const spec = {
}
data = tryAppendQueryString(data, 'tp', bidderRequest.refererInfo.page);
- if (isIos()) {
- const hyperId = getHyperId(validReq);
- if (hyperId != null) {
- data = tryAppendQueryString(data, 'hyper_id', hyperId);
- }
+
+ const hyperId = getHyperId(validReq);
+ if (hyperId != null) {
+ data = tryAppendQueryString(data, 'hyper_id', hyperId);
}
+
// remove the trailing "&"
if (data.lastIndexOf('&') === data.length - 1) {
data = data.substring(0, data.length - 1);
@@ -337,8 +337,4 @@ function getHyperId(validReq) {
return null;
}
-function isIos() {
- return (/(ios|ipod|ipad|iphone)/i).test(window.navigator.userAgent);
-}
-
registerBidder(spec);
diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js
index a66e825e5df..2d2fadfe4ca 100644
--- a/modules/engageyaBidAdapter.js
+++ b/modules/engageyaBidAdapter.js
@@ -81,7 +81,7 @@ function parseBannerResponse(rec, response) {
const title = rec.title && rec.title.trim() ? `${rec.title}
` : '';
const displayName = rec.displayName && title ? `${rec.displayName}
` : '';
const trackers = getImpressionTrackers(rec, response)
- .map(createTrackPixelHtml)
+ .map((url) => createTrackPixelHtml(url))
.join('');
return `${style}`;
}
diff --git a/modules/jwplayerBidAdapter.js b/modules/jwplayerBidAdapter.js
new file mode 100644
index 00000000000..151d08bf3a6
--- /dev/null
+++ b/modules/jwplayerBidAdapter.js
@@ -0,0 +1,412 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { VIDEO } from '../src/mediaTypes.js';
+import { isArray, isFn, deepAccess, deepSetValue, getDNT, logError, logWarn } from '../src/utils.js';
+import { config } from '../src/config.js';
+import { hasPurpose1Consent } from '../src/utils/gpdr.js';
+
+const BIDDER_CODE = 'jwplayer';
+const BASE_URL = 'https://vpb-server.jwplayer.com/';
+const AUCTION_URL = BASE_URL + 'openrtb2/auction';
+const USER_SYNC_URL = BASE_URL + 'setuid';
+const GVLID = 1046;
+const SUPPORTED_AD_TYPES = [VIDEO];
+
+const VIDEO_ORTB_PARAMS = [
+ 'pos',
+ 'w',
+ 'h',
+ 'playbackend',
+ 'mimes',
+ 'minduration',
+ 'maxduration',
+ 'protocols',
+ 'startdelay',
+ 'placement',
+ 'plcmt',
+ 'skip',
+ 'skipafter',
+ 'minbitrate',
+ 'maxbitrate',
+ 'delivery',
+ 'playbackmethod',
+ 'api',
+ 'linearity'
+];
+
+function getBidAdapter() {
+ function isBidRequestValid(bid) {
+ const params = bid && bid.params;
+ if (!params) {
+ return false;
+ }
+
+ return !!params.placementId && !!params.publisherId && !!params.siteId;
+ }
+
+ function buildRequests(bidRequests, bidderRequest) {
+ if (!bidRequests) {
+ return;
+ }
+
+ if (!hasContentUrl(bidderRequest.ortb2)) {
+ logError(`${BIDDER_CODE}: cannot bid without a valid Content URL. Please populate ortb2.site.content.url`);
+ return;
+ }
+
+ const warnings = getWarnings(bidderRequest);
+ warnings.forEach(warning => {
+ logWarn(`${BIDDER_CODE}: ${warning}`);
+ });
+
+ return bidRequests.map(bidRequest => {
+ const payload = buildRequest(bidRequest, bidderRequest);
+
+ return {
+ method: 'POST',
+ url: AUCTION_URL,
+ data: payload
+ }
+ });
+ }
+
+ function interpretResponse(serverResponse) {
+ const outgoingBidResponses = [];
+ const serverResponseBody = serverResponse.body;
+
+ logResponseWarnings(serverResponseBody);
+
+ const seatBids = serverResponseBody && serverResponseBody.seatbid;
+ if (!isArray(seatBids)) {
+ return outgoingBidResponses;
+ }
+
+ const cur = serverResponseBody.cur;
+
+ seatBids.forEach(seatBid => {
+ seatBid.bid.forEach(bid => {
+ const bidResponse = {
+ requestId: serverResponseBody.id,
+ cpm: bid.price,
+ currency: cur,
+ width: bid.w,
+ height: bid.h,
+ ad: bid.adm,
+ vastXml: bid.adm,
+ ttl: bid.ttl || 3600,
+ netRevenue: false,
+ creativeId: bid.adid,
+ dealId: bid.dealid,
+ meta: {
+ advertiserDomains: bid.adomain,
+ mediaType: VIDEO,
+ primaryCatId: bid.cat,
+ }
+ };
+
+ outgoingBidResponses.push(bidResponse);
+ });
+ });
+
+ return outgoingBidResponses;
+ }
+
+ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ if (!hasPurpose1Consent(gdprConsent)) {
+ return [];
+ }
+
+ const userSyncs = [];
+ const consentQueryParams = getUserSyncConsentQueryParams(gdprConsent);
+ const url = `https://ib.adnxs.com/getuid?${USER_SYNC_URL}?bidder=jwplayer&uid=$UID&f=i` + consentQueryParams
+
+ if (syncOptions.iframeEnabled) {
+ userSyncs.push({
+ type: 'iframe',
+ url
+ });
+ }
+
+ if (syncOptions.pixelEnabled) {
+ userSyncs.push({
+ type: 'image',
+ url
+ });
+ }
+
+ return userSyncs;
+ }
+
+ return {
+ code: BIDDER_CODE,
+ gvlid: GVLID,
+ supportedMediaTypes: SUPPORTED_AD_TYPES,
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ getUserSyncs
+ }
+
+ function getUserSyncConsentQueryParams(gdprConsent) {
+ if (!gdprConsent) {
+ return '';
+ }
+
+ const consentString = gdprConsent.consentString;
+ if (!consentString) {
+ return '';
+ }
+
+ let gdpr = 0;
+ const gdprApplies = gdprConsent.gdprApplies;
+ if (typeof gdprApplies === 'boolean') {
+ gdpr = Number(gdprApplies)
+ }
+
+ return `&gdpr=${gdpr}&gdpr_consent=${consentString}`;
+ }
+
+ function buildRequest(bidRequest, bidderRequest) {
+ const openrtbRequest = {
+ id: bidRequest.bidId,
+ imp: getRequestImpressions(bidRequest, bidderRequest),
+ site: getRequestSite(bidRequest, bidderRequest),
+ device: getRequestDevice(bidderRequest.ortb2),
+ user: getRequestUser(bidderRequest.ortb2),
+ };
+
+ // GDPR Consent Params
+ if (bidderRequest.gdprConsent) {
+ deepSetValue(openrtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
+ deepSetValue(openrtbRequest, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0));
+ }
+
+ // CCPA
+ if (bidderRequest.uspConsent) {
+ deepSetValue(openrtbRequest, 'regs.ext.us_privacy', bidderRequest.uspConsent);
+ }
+
+ if (bidRequest.schain) {
+ deepSetValue(openrtbRequest, 'source.schain', bidRequest.schain);
+ }
+
+ openrtbRequest.tmax = bidderRequest.timeout || 200;
+
+ return JSON.stringify(openrtbRequest);
+ }
+
+ function getRequestImpressions(bidRequest) {
+ const impressionObject = {
+ id: bidRequest.adUnitCode,
+ };
+
+ impressionObject.video = getImpressionVideo(bidRequest);
+
+ const bidFloorData = getBidFloorData(bidRequest);
+ if (bidFloorData) {
+ impressionObject.bidfloor = bidFloorData.floor;
+ impressionObject.bidfloorcur = bidFloorData.currency;
+ }
+
+ impressionObject.ext = getImpressionExtension(bidRequest);
+
+ return [impressionObject];
+ }
+
+ function getImpressionVideo(bidRequest) {
+ const videoParams = deepAccess(bidRequest, 'mediaTypes.video', {});
+
+ const video = {};
+
+ VIDEO_ORTB_PARAMS.forEach((param) => {
+ if (videoParams.hasOwnProperty(param)) {
+ video[param] = videoParams[param];
+ }
+ });
+
+ setPlayerSize(video, videoParams);
+
+ if (!videoParams.plcmt) {
+ logWarn(`${BIDDER_CODE}: Please set a value to mediaTypes.video.plcmt`);
+ }
+
+ return video;
+ }
+
+ function getImpressionExtension(bidRequest) {
+ return {
+ prebid: {
+ bidder: {
+ jwplayer: {
+ placementId: bidRequest.params.placementId
+ }
+ }
+ }
+ };
+ }
+
+ function setPlayerSize(videoImp, videoParams) {
+ if (videoImp.w !== undefined && videoImp.h !== undefined) {
+ return;
+ }
+
+ const playerSize = getNormalizedPlayerSize(videoParams.playerSize);
+ if (!playerSize.length) {
+ logWarn(logWarn(`${BIDDER_CODE}: Video size has not been set. Please set values in video.h and video.w`));
+ return;
+ }
+
+ if (videoImp.w === undefined) {
+ videoImp.w = playerSize[0];
+ }
+
+ if (videoImp.h === undefined) {
+ videoImp.h = playerSize[1];
+ }
+ }
+
+ function getNormalizedPlayerSize(playerSize) {
+ if (!Array.isArray(playerSize)) {
+ return [];
+ }
+
+ if (Array.isArray(playerSize[0])) {
+ playerSize = playerSize[0];
+ }
+
+ if (playerSize.length < 2) {
+ return [];
+ }
+
+ return playerSize;
+ }
+
+ function getBidFloorData(bidRequest) {
+ const { params } = bidRequest;
+ const currency = params.currency || 'USD';
+
+ let floorData;
+ if (isFn(bidRequest.getFloor)) {
+ const bidFloorRequest = {
+ currency: currency,
+ mediaType: VIDEO,
+ size: '*'
+ };
+ floorData = bidRequest.getFloor(bidFloorRequest);
+ } else if (params.bidFloor) {
+ floorData = { floor: params.bidFloor, currency: currency };
+ }
+
+ return floorData;
+ }
+
+ function getRequestSite(bidRequest, bidderRequest) {
+ const site = bidderRequest.ortb2.site || {};
+
+ site.domain = site.domain || config.publisherDomain || window.location.hostname;
+ site.page = site.page || config.pageUrl || window.location.href;
+
+ const referer = bidderRequest.refererInfo && bidderRequest.refererInfo.referer;
+ if (!site.ref && referer) {
+ site.ref = referer;
+ }
+
+ const jwplayerPublisherExtChain = 'publisher.ext.jwplayer.';
+
+ deepSetValue(site, jwplayerPublisherExtChain + 'publisherId', bidRequest.params.publisherId);
+ deepSetValue(site, jwplayerPublisherExtChain + 'siteId', bidRequest.params.siteId);
+
+ return site;
+ }
+
+ function getRequestDevice(ortb2) {
+ const device = Object.assign({
+ h: screen.height,
+ w: screen.width,
+ ua: navigator.userAgent,
+ dnt: getDNT() ? 1 : 0,
+ js: 1
+ }, ortb2.device || {})
+
+ const language = getLanguage();
+ if (!device.language && language) {
+ device.language = language;
+ }
+
+ return device;
+ }
+
+ function getLanguage() {
+ const navigatorLanguage = navigator.language;
+ if (!navigatorLanguage) {
+ return;
+ }
+
+ const languageCodeSegments = navigatorLanguage.split('-');
+ if (!languageCodeSegments.length) {
+ return;
+ }
+
+ return languageCodeSegments[0];
+ }
+
+ function getRequestUser(ortb2) {
+ const user = ortb2.user || {};
+ if (config.getConfig('coppa') === true) {
+ user.coppa = true;
+ }
+
+ return user;
+ }
+
+ function hasContentUrl(ortb2) {
+ const site = ortb2.site;
+ const content = site && site.content;
+ return !!(content && content.url);
+ }
+
+ function getWarnings(bidderRequest) {
+ const content = bidderRequest.ortb2.site.content;
+ const contentChain = 'ortb2.site.content.';
+ const warnings = [];
+ if (!content.id) {
+ warnings.push(getMissingFieldMessage(contentChain + 'id'));
+ }
+
+ if (!content.title) {
+ warnings.push(getMissingFieldMessage(contentChain + 'title'));
+ }
+
+ if (!content.ext || !content.ext.description) {
+ warnings.push(getMissingFieldMessage(contentChain + 'ext.description'));
+ }
+
+ return warnings;
+ }
+
+ function getMissingFieldMessage(fieldName) {
+ return `Optional field ${fieldName} is not populated; we recommend populating for maximum performance.`
+ }
+
+ function logResponseWarnings(serverResponseBody) {
+ const warningPayload = deepAccess(serverResponseBody, 'ext.warnings');
+ if (!warningPayload) {
+ return;
+ }
+
+ const warningCategories = Object.keys(warningPayload);
+ warningCategories.forEach(category => {
+ const warnings = warningPayload[category];
+ if (!isArray(warnings)) {
+ return;
+ }
+
+ warnings.forEach(warning => {
+ logWarn(`${BIDDER_CODE}: [Bid Response][Warning Code: ${warning.code}] ${warning.message}`);
+ });
+ });
+ }
+}
+
+export const spec = getBidAdapter();
+
+registerBidder(spec);
diff --git a/modules/jwplayerBidAdapter.md b/modules/jwplayerBidAdapter.md
new file mode 100644
index 00000000000..620f8657e50
--- /dev/null
+++ b/modules/jwplayerBidAdapter.md
@@ -0,0 +1,72 @@
+# Overview
+
+```
+Module Name: JWPlayer Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: boost-engineering@jwplayer.com
+```
+
+# Description
+
+Connects to JWPlayer's demand sources.
+
+JWPlayer bid adapter supports Video (instream and outstream).
+
+# Sample Ad Unit
+
+```markdown
+const adUnit = {
+ code: 'test-ad-unit',
+ mediaTypes: {
+ video: {
+ pos: 0,
+ w: 640,
+ h: 480,
+ mimes : ['video/x-ms-wmv', 'video/mp4'],
+ minduration : 0,
+ maxduration: 60,
+ protocols : [2,3,7,5,6,8],
+ startdelay: 0,
+ placement: 1,
+ plcmt: 1,
+ skip: 1,
+ skipafter: 10,
+ playbackmethod: [3],
+ api: [2],
+ linearity: 1
+ }
+ },
+ bids: [{
+ bidder: 'jwplayer',
+ params: {
+ publisherId: 'test-publisher-id',
+ siteId: 'test-site-id',
+ placementId: 'test-placement-id'
+ }
+ }]
+};
+```
+
+# Sample ortb2 config
+
+```markdown
+pbjs.setConfig({
+ ortb2: {
+ site: {
+ publisher: {
+ id: 'test-publisher-id'
+ },
+ content: {
+ id: 'test-media-id',
+ url: 'test.mp4',
+ title: 'title of my media',
+ ext: {
+ description: 'description of my media'
+ }
+ },
+ domain: 'test-domain.com',
+ page: 'https://www.test-domain.com/test.html',
+ }
+ }
+}
+```
diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js
index 5150d5d7dca..8afe7298c13 100644
--- a/modules/yandexAnalyticsAdapter.js
+++ b/modules/yandexAnalyticsAdapter.js
@@ -32,6 +32,9 @@ const {
BIDDER_DONE,
AUCTION_END,
BID_TIMEOUT,
+ AD_RENDER_FAILED,
+ AD_RENDER_SUCCEEDED,
+ BIDDER_ERROR,
} = EVENTS;
export const EVENTS_TO_TRACK = [
@@ -42,6 +45,9 @@ export const EVENTS_TO_TRACK = [
BIDDER_DONE,
AUCTION_END,
BID_TIMEOUT,
+ AD_RENDER_FAILED,
+ AD_RENDER_SUCCEEDED,
+ BIDDER_ERROR,
];
const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' }), {
diff --git a/src/utils.js b/src/utils.js
index 71cc78090a6..abb69209020 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -489,19 +489,32 @@ export function insertUserSyncIframe(url, done, timeout) {
/**
* Creates a snippet of HTML that retrieves the specified `url`
* @param {string} url URL to be requested
+ * @param encode
* @return {string} HTML snippet that contains the img src = set to `url`
*/
-export function createTrackPixelHtml(url) {
+export function createTrackPixelHtml(url, encode = encodeURI) {
if (!url) {
return '';
}
- let escapedUrl = encodeURI(url);
+ let escapedUrl = encode(url);
let img = '';
img += '
';
return img;
};
+/**
+ * encodeURI, but preserves macros of the form '${MACRO}' (e.g. '${AUCTION_PRICE}')
+ * @param url
+ * @return {string}
+ */
+export function encodeMacroURI(url) {
+ const macros = Array.from(url.matchAll(/\$({[^}]+})/g)).map(match => match[1]);
+ return macros.reduce((str, macro) => {
+ return str.replace('$' + encodeURIComponent(macro), '$' + macro)
+ }, encodeURI(url))
+}
+
/**
* Creates a snippet of Iframe HTML that retrieves the specified `url`
* @param {string} url plain URL to be requested
diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js
index adfd38d22cc..9a3bf61fe23 100644
--- a/test/spec/modules/adgenerationBidAdapter_spec.js
+++ b/test/spec/modules/adgenerationBidAdapter_spec.js
@@ -184,12 +184,12 @@ describe('AdgenerationAdapter', function () {
}
};
const data = {
- banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&imark=1&tp=https%3A%2F%2Fexample.com`,
- bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&imark=1&tp=https%3A%2F%2Fexample.com`,
- native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&tp=https%3A%2F%2Fexample.com`,
- bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`,
- bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`,
- bannerWithAdgextIds: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.2&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&adgext_imuid=i.KrAH6ZAZTJOnH5S4N2sogA&adgext_uid2=AgAAAAVacu1uAxgAxH%2BHJ8%2BnWlS2H4uVqr6i%2BHBDCNREHD8WKsio%2Fx7D8xXFuq1cJycUU86yXfTH9Xe%2F4C8KkH%2B7UCiU7uQxhyD7Qxnv251pEs6K8oK%2BBPLYR%2B8BLY%2FsJKesa%2FkoKwx1FHgUzIBum582tSy2Oo%2B7C6wYUaaV4QcLr%2F4LPA%3D&gpid=%2F1111%2Fhomepage%23300x250&uach=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22macOS%22%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Not%3AA-Brand%22%2C%22version%22%3A%5B%2299%22%5D%7D%5D%2C%22mobile%22%3A0%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22indirectseller.com%22%2C%22sid%22%3A%2200001%22%2C%22hp%22%3A1%7D%5D%7D&imark=1&tp=https%3A%2F%2Fexample.com`,
+ banner: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com`,
+ bannerUSD: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=300x250%2C320x100¤cy=USD&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com`,
+ native: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=1x1¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&tp=https%3A%2F%2Fexample.com`,
+ bannerWithHyperId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&imark=1&tp=https%3A%2F%2Fexample.com&hyper_id=novatiqId`,
+ bannerWithAdgextCriteoId: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&adgext_criteo_id=criteo-id-test-1234567890&imark=1&tp=https%3A%2F%2Fexample.com`,
+ bannerWithAdgextIds: `posall=SSPLOC&id=58278&sdktype=0&hb=true&t=json3&sizes=320x100¤cy=JPY&pbver=${prebid.version}&sdkname=prebidjs&adapterver=1.6.3&adgext_id5_id=id5-id-test-1234567890&adgext_id5_id_link_type=2&adgext_imuid=i.KrAH6ZAZTJOnH5S4N2sogA&adgext_uid2=AgAAAAVacu1uAxgAxH%2BHJ8%2BnWlS2H4uVqr6i%2BHBDCNREHD8WKsio%2Fx7D8xXFuq1cJycUU86yXfTH9Xe%2F4C8KkH%2B7UCiU7uQxhyD7Qxnv251pEs6K8oK%2BBPLYR%2B8BLY%2FsJKesa%2FkoKwx1FHgUzIBum582tSy2Oo%2B7C6wYUaaV4QcLr%2F4LPA%3D&gpid=%2F1111%2Fhomepage%23300x250&uach=%7B%22source%22%3A2%2C%22platform%22%3A%7B%22brand%22%3A%22macOS%22%7D%2C%22browsers%22%3A%5B%7B%22brand%22%3A%22Chromium%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Google%20Chrome%22%2C%22version%22%3A%5B%22112%22%5D%7D%2C%7B%22brand%22%3A%22Not%3AA-Brand%22%2C%22version%22%3A%5B%2299%22%5D%7D%5D%2C%22mobile%22%3A0%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22indirectseller.com%22%2C%22sid%22%3A%2200001%22%2C%22hp%22%3A1%7D%5D%7D&imark=1&tp=https%3A%2F%2Fexample.com`,
};
it('sends bid request to ENDPOINT via GET', function () {
const request = spec.buildRequests(bidRequests, bidderRequest)[0];
diff --git a/test/spec/modules/jwplayerBidAdapter_spec.js b/test/spec/modules/jwplayerBidAdapter_spec.js
new file mode 100644
index 00000000000..e19790a9670
--- /dev/null
+++ b/test/spec/modules/jwplayerBidAdapter_spec.js
@@ -0,0 +1,412 @@
+import { expect, assert } from 'chai';
+import { spec } from 'modules/jwplayerBidAdapter.js';
+
+describe('jwplayerBidAdapter', function() {
+ beforeEach(function() {
+ this.defaultBidderRequest = {
+ gdprConsent: {
+ consentString: 'testConsentString',
+ gdprApplies: true
+ },
+ uspConsent: 'testCCPA',
+ refererInfo: {
+ referer: 'https://example.com'
+ },
+ ortb2: {
+ site: {
+ domain: 'page.example.com',
+ page: 'https://examplepage.com',
+ content: {
+ url: 'media.mp4',
+ id: 'testMediaId',
+ title: 'testTile',
+ data: [{
+ name: 'jwplayer.com',
+ segment: [{
+ id: '00000000'
+ }, {
+ id: '88888888'
+ }, {
+ id: '80808080'
+ }],
+ ext: {
+ segtax: 502,
+ cids: ['testMediaId', 'externalTestId'],
+ }
+ }],
+ ext: {
+ description: 'testDescription'
+ }
+ }
+ }
+ },
+ timeout: 1000
+ }
+ });
+
+ it('should use jwplayer bidder code', function () {
+ expect(spec.code).to.equal('jwplayer');
+ });
+
+ it('should use jwplayer GVLID code', function () {
+ expect(spec.gvlid).to.equal(1046);
+ });
+
+ it('should support VIDEO media type only', function () {
+ expect(spec.supportedMediaTypes).to.have.length(1);
+ expect(spec.supportedMediaTypes[0]).to.equal('video');
+ });
+
+ describe('isBidRequestValid', function() {
+ it('should be invalid when bidRequest is undefined', function() {
+ assert(spec.isBidRequestValid() === false);
+ });
+
+ it('should be invalid when bidRequest is null', function() {
+ assert(spec.isBidRequestValid(null) === false);
+ });
+
+ it('should be invalid when the bidRequest has no params', function() {
+ assert(spec.isBidRequestValid({}) === false);
+ });
+
+ it('should be invalid when the bid request only includes a publisher ID', function() {
+ assert(spec.isBidRequestValid({params: {publisherId: 'foo'}}) === false);
+ });
+
+ it('should be invalid when the bid request only includes a placement ID', function() {
+ assert(spec.isBidRequestValid({params: {placementId: 'foo'}}) === false);
+ });
+
+ it('should be invalid when the bid request only includes a site ID', function() {
+ assert(spec.isBidRequestValid({params: {siteId: 'foo'}}) === false);
+ });
+
+ it('should be valid when the bid includes a placement ID, a publisher ID and a site ID', function() {
+ assert(spec.isBidRequestValid({params: {placementId: 'foo', publisherId: 'bar', siteId: 'siteId '}}) === true);
+ });
+ });
+
+ describe('buildRequests', function() {
+ it('should return undefined when bidRequests is undefined', function () {
+ expect(spec.buildRequests()).to.be.undefined;
+ });
+
+ it('should return undefined when bidRequests is null', function () {
+ expect(spec.buildRequests(null)).to.be.undefined;
+ });
+
+ it('should return undefined when ortb site.content.url is absent', function () {
+ const request = spec.buildRequests({}, {
+ ortb2: {
+ site: {
+ content: {
+ url: null,
+ }
+ }
+ }
+ });
+
+ expect(request).to.be.undefined;
+ });
+
+ it('should build a valid request when bid request is complete', function() {
+ const incomingBidRequests = [
+ {
+ bidder: 'jwplayer',
+ params: {
+ placementId: 'testPlacementId',
+ publisherId: 'testPublisherId',
+ siteId: 'testSiteId',
+ bidFloor: 10,
+ currency: 'EUR',
+ },
+ mediaTypes: {
+ video: {
+ pos: 3,
+ w: 640,
+ h: 480,
+ context: 'instream',
+ mimes: [
+ 'video/mp4',
+ 'application/javascript'
+ ],
+ protocols: [2, 3, 5, 6],
+ maxduration: 60,
+ minduration: 3,
+ startdelay: 0,
+ linearity: 1,
+ placement: 1,
+ plcmt: 1,
+ skip: 1,
+ skipafter: 4,
+ minbitrate: 500,
+ maxbitrate: 1000,
+ api: [2],
+ delivery: [2],
+ playbackmethod: [1],
+ playbackend: 2
+ }
+ },
+ schain: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [
+ {
+ asi: 'publisher.com',
+ sid: '00001',
+ hp: 1
+ }
+ ]
+ },
+ bidRequestsCount: 1,
+ adUnitCode: 'testAdUnitCode',
+ bidId: 'testBidId'
+ }
+ ];
+
+ const outgoingBidRequests = spec.buildRequests(incomingBidRequests, this.defaultBidderRequest);
+
+ outgoingBidRequests.forEach(serverRequest => {
+ expect(serverRequest.url).to.equal('https://vpb-server.jwplayer.com/openrtb2/auction');
+ expect(serverRequest.method).to.equal('POST');
+
+ const openrtbRequest = JSON.parse(serverRequest.data);
+
+ expect(openrtbRequest.id).to.equal('testBidId');
+
+ expect(openrtbRequest.imp[0].id).to.equal('testAdUnitCode');
+ expect(openrtbRequest.imp[0].video.w).to.equal(640);
+ expect(openrtbRequest.imp[0].video.h).to.equal(480);
+ expect(openrtbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'application/javascript']);
+ expect(openrtbRequest.imp[0].video.protocols).to.deep.equal([2, 3, 5, 6]);
+ expect(openrtbRequest.imp[0].video.api).to.deep.equal([2]);
+ expect(openrtbRequest.imp[0].video.startdelay).to.equal(0);
+ expect(openrtbRequest.imp[0].video.placement).to.equal(1);
+ expect(openrtbRequest.imp[0].video.plcmt).to.equal(1);
+ expect(openrtbRequest.imp[0].video.pos).to.equal(3);
+ expect(openrtbRequest.imp[0].video.minduration).to.equal(3);
+ expect(openrtbRequest.imp[0].video.maxduration).to.equal(60);
+ expect(openrtbRequest.imp[0].video.skip).to.equal(1);
+ expect(openrtbRequest.imp[0].video.skipafter).to.equal(4);
+ expect(openrtbRequest.imp[0].video.minbitrate).to.equal(500);
+ expect(openrtbRequest.imp[0].video.maxbitrate).to.equal(1000);
+ expect(openrtbRequest.imp[0].video.delivery).to.deep.equal([2]);
+ expect(openrtbRequest.imp[0].video.playbackmethod).to.deep.equal([1]);
+ expect(openrtbRequest.imp[0].video.playbackend).to.equal(2);
+ expect(openrtbRequest.imp[0].video.linearity).to.equal(1);
+
+ expect(openrtbRequest.imp[0].bidfloor).to.equal(10);
+ expect(openrtbRequest.imp[0].bidfloorcur).to.equal('EUR');
+
+ expect(openrtbRequest.imp[0].ext.prebid.bidder.jwplayer.placementId).to.equal('testPlacementId');
+
+ expect(openrtbRequest.site.domain).to.equal('page.example.com');
+ expect(openrtbRequest.site.page).to.equal('https://examplepage.com');
+ expect(openrtbRequest.site.ref).to.equal('https://example.com');
+
+ expect(openrtbRequest.site.publisher.ext.jwplayer.publisherId).to.equal('testPublisherId');
+ expect(openrtbRequest.site.publisher.ext.jwplayer.siteId).to.equal('testSiteId');
+
+ expect(openrtbRequest.site.content.url).to.equal('media.mp4');
+ expect(openrtbRequest.site.content.id).to.equal('testMediaId');
+ expect(openrtbRequest.site.content.title).to.equal('testTile');
+ expect(openrtbRequest.site.content.ext.description).to.equal('testDescription');
+ expect(openrtbRequest.site.content.data.length).to.equal(1);
+ const datum = openrtbRequest.site.content.data[0];
+ expect(datum.name).to.equal('jwplayer.com');
+ expect(datum.segment).to.deep.equal([{
+ id: '00000000'
+ }, {
+ id: '88888888'
+ }, {
+ id: '80808080'
+ }]);
+ expect(datum.ext.segtax).to.equal(502);
+ expect(datum.ext.cids).to.deep.equal(['testMediaId', 'externalTestId']);
+
+ expect(openrtbRequest.device.ua).to.equal(navigator.userAgent);
+ expect(openrtbRequest.device.w).to.not.be.undefined;
+ expect(openrtbRequest.device.h).to.not.be.undefined;
+ expect(openrtbRequest.device.dnt).to.not.be.undefined;
+ expect(openrtbRequest.device.js).to.equal(1);
+ expect(openrtbRequest.device.language).to.not.be.undefined;
+
+ expect(openrtbRequest.user.ext.consent).to.equal('testConsentString');
+
+ expect(openrtbRequest.regs.ext.gdpr).to.equal(1);
+ expect(openrtbRequest.regs.ext.us_privacy).to.equal('testCCPA');
+
+ expect(openrtbRequest.source.schain).to.deep.equal({
+ ver: '1.0',
+ complete: 1,
+ nodes: [
+ {
+ asi: 'publisher.com',
+ sid: '00001',
+ hp: 1
+ }
+ ]
+ });
+
+ expect(openrtbRequest.tmax).to.equal(1000);
+ });
+ });
+ });
+
+ describe('interpretResponse', function() {
+ const serverResponse = {
+ body: {
+ id: 'test-request-id',
+ cur: 'USD',
+ seatbid: [{
+ bid: [{
+ id: 'testId',
+ impid: 'test-imp-id',
+ price: 5.000,
+ adid: 'test-creative-id',
+ adm: 'test-ad-xml',
+ adomain: ['prebid.com'],
+ cat: ['test-iab-category'],
+ w: 200,
+ h: 150,
+ dealid: 'test-deal-id'
+ }],
+ seat: 1000
+ }]
+ }
+ };
+
+ const bidResponses = spec.interpretResponse(serverResponse);
+
+ expect(bidResponses).to.have.length(1);
+ const bid = bidResponses[0];
+ expect(bid.requestId).to.equal('test-request-id');
+ expect(bid.cpm).to.equal(5);
+ expect(bid.currency).to.equal('USD');
+ expect(bid.width).to.equal(200);
+ expect(bid.height).to.equal(150);
+ expect(bid.creativeId).to.equal('test-creative-id');
+ expect(bid.vastXml).to.equal('test-ad-xml');
+ expect(bid.netRevenue).to.equal(false);
+ expect(bid.ttl).to.equal(3600);
+ expect(bid.ad).to.equal('test-ad-xml');
+ expect(bid.dealId).to.equal('test-deal-id');
+
+ expect(bid.meta).to.not.be.undefined;
+
+ expect(bid.meta.advertiserDomains).to.have.length(1);
+ expect(bid.meta.advertiserDomains[0]).to.equal('prebid.com');
+
+ expect(bid.meta.mediaType).to.equal('video');
+
+ expect(bid.meta.primaryCatId).to.have.length(1);
+ expect(bid.meta.primaryCatId[0]).to.equal('test-iab-category');
+ });
+
+ describe('getUserSyncs', function() {
+ const consentString = 'test_consent_string';
+ const baseGdprConsent = {
+ gdprApplies: true,
+ vendorData: {
+ purpose: {
+ consents: {
+ 1: true
+ }
+ }
+ }
+ };
+
+ const expectedBaseUrl = 'https://ib.adnxs.com/getuid?https://vpb-server.jwplayer.com/setuid?bidder=jwplayer&uid=$UID&f=i';
+
+ it('should return empty when Purpose 1 consent is not granted', function() {
+ expect(spec.getUserSyncs({}, {})).to.be.empty;
+ expect(spec.getUserSyncs({}, {}, {})).to.be.empty;
+ expect(spec.getUserSyncs({}, {}, { gdprApplies: false })).to.be.empty;
+ expect(spec.getUserSyncs({}, {}, {
+ gdprApplies: true,
+ vendorData: {
+ purpose: {
+ consents: {
+ 1: false
+ }
+ }
+ }
+ })).to.be.empty;
+ });
+
+ it('should return iframe when enabled', function () {
+ const userSyncs = spec.getUserSyncs({ iframeEnabled: true }, {}, baseGdprConsent);
+ expect(userSyncs.length).to.equal(1);
+ const sync = userSyncs[0];
+ expect(sync.type).to.equal('iframe');
+ expect(sync.url).to.equal(expectedBaseUrl);
+ });
+
+ it('should return image when enabled', function () {
+ const userSyncs = spec.getUserSyncs({ pixelEnabled: true }, {}, baseGdprConsent);
+ expect(userSyncs.length).to.equal(1);
+ const sync = userSyncs[0];
+ expect(sync.type).to.equal('image');
+ expect(sync.url).to.equal(expectedBaseUrl);
+ });
+
+ it('should return both iframe and image when enabled', function () {
+ const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, baseGdprConsent);
+ expect(userSyncs.length).to.equal(2);
+
+ const iframeSync = userSyncs[0];
+ expect(iframeSync.type).to.equal('iframe');
+ expect(iframeSync.url).to.equal(expectedBaseUrl);
+
+ const imageSync = userSyncs[1];
+ expect(imageSync.type).to.equal('image');
+ expect(imageSync.url).to.equal(expectedBaseUrl);
+ });
+
+ it('should include gdpr consent query params in sync redirect url', function () {
+ const expectedUrl = expectedBaseUrl + '&gdpr=1&gdpr_consent=' + consentString;
+ const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString });
+ const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent);
+ expect(userSyncs.length).to.equal(2);
+
+ const iframeSync = userSyncs[0];
+ expect(iframeSync.type).to.equal('iframe');
+ expect(iframeSync.url).to.equal(expectedUrl);
+
+ const imageSync = userSyncs[1];
+ expect(imageSync.type).to.equal('image');
+ expect(imageSync.url).to.equal(expectedUrl);
+ });
+
+ it('should include gdpr 0 in consent query params when gdprApplies is false', function () {
+ const expectedUrl = expectedBaseUrl + '&gdpr=0&gdpr_consent=' + consentString;
+ const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString, gdprApplies: false });
+ const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent);
+ expect(userSyncs.length).to.equal(2);
+
+ const iframeSync = userSyncs[0];
+ expect(iframeSync.type).to.equal('iframe');
+ expect(iframeSync.url).to.equal(expectedUrl);
+
+ const imageSync = userSyncs[1];
+ expect(imageSync.type).to.equal('image');
+ expect(imageSync.url).to.equal(expectedUrl);
+ });
+
+ it('should include gdpr 0 in consent query params when gdprApplies is not a bool', function () {
+ const expectedUrl = expectedBaseUrl + '&gdpr=0&gdpr_consent=' + consentString;
+ const gdprConsent = Object.assign({ }, baseGdprConsent, { consentString, gdprApplies: 1 });
+ const userSyncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, {}, gdprConsent);
+ expect(userSyncs.length).to.equal(2);
+
+ const iframeSync = userSyncs[0];
+ expect(iframeSync.type).to.equal('iframe');
+ expect(iframeSync.url).to.equal(expectedUrl);
+
+ const imageSync = userSyncs[1];
+ expect(imageSync.type).to.equal('image');
+ expect(imageSync.url).to.equal(expectedUrl);
+ });
+ });
+});
diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js
index c6108b49715..084b4de212a 100644
--- a/test/spec/modules/liveIntentIdSystem_spec.js
+++ b/test/spec/modules/liveIntentIdSystem_spec.js
@@ -85,28 +85,26 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/);
done();
- }, 200);
+ }, 300);
});
it('should fire an event when getId and a hash is provided', function(done) {
liveIntentIdSubmodule.getId({ params: {
- ...defaultConfigParams,
+ ...defaultConfigParams.params,
emailHash: '58131bc547fb87af94cebdaf3102321f'
}});
setTimeout(() => {
expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/)
done();
- }, 200);
+ }, 300);
});
it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) {
- liveIntentIdSubmodule.decode({}, { params: {
- ...defaultConfigParams
- }});
+ liveIntentIdSubmodule.decode({}, defaultConfigParams);
setTimeout(() => {
expect(server.requests[0].url).to.contain('tv=$prebid.version$')
done();
- }, 200);
+ }, 300);
});
it('should initialize LiveConnect with the config params when decode and emit an event', function (done) {
@@ -123,7 +121,7 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/);
done();
- }, 200);
+ }, 300);
});
it('should fire an event with the provided distributorId', function (done) {
@@ -131,7 +129,7 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/);
done();
- }, 200);
+ }, 300);
});
it('should fire an event without the provided distributorId when appId is provided', function (done) {
@@ -140,7 +138,7 @@ describe('LiveIntentId', function() {
expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/);
expect(server.requests[0].url).to.not.match(/.*did=*/);
done();
- }, 200);
+ }, 300);
});
it('should initialize LiveConnect and emit an event with a privacy string when decode', function(done) {
@@ -157,7 +155,7 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/);
done();
- }, 200);
+ }, 300);
});
it('should fire an event when decode and a hash is provided', function(done) {
@@ -168,11 +166,11 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/);
done();
- }, 200);
+ }, 300);
});
it('should not return a decoded identifier when the unifiedId is not present in the value', function() {
- const result = liveIntentIdSubmodule.decode({ additionalData: 'data' });
+ const result = liveIntentIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' });
expect(result).to.be.eql({});
});
@@ -181,7 +179,7 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests[0].url).to.be.not.null
done();
- }, 200);
+ }, 300);
});
it('should initialize LiveConnect and send data only once', function(done) {
@@ -192,7 +190,7 @@ describe('LiveIntentId', function() {
setTimeout(() => {
expect(server.requests.length).to.be.eq(1);
done();
- }, 200);
+ }, 300);
});
it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() {
diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js
index e0a114d8cf6..8ea68aadecc 100644
--- a/test/spec/utils_spec.js
+++ b/test/spec/utils_spec.js
@@ -3,7 +3,7 @@ import {expect} from 'chai';
import { TARGETING_KEYS } from 'src/constants.js';
import * as utils from 'src/utils.js';
import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js';
-import {binarySearch, deepEqual, memoize, waitForElementToLoad} from 'src/utils.js';
+import {binarySearch, deepEqual, encodeMacroURI, memoize, waitForElementToLoad} from 'src/utils.js';
import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js';
var assert = require('assert');
@@ -778,6 +778,22 @@ describe('Utils', function () {
expect(parsed.search).to.equal('?search=test&foo=bar&bar=foo&foo=xxx');
});
});
+
+ describe('encodeMacroURI', () => {
+ [
+ ['https://www.example.com', 'https://www.example.com'],
+ ['https://www.example/${MACRO}', 'https://www.example/${MACRO}'],
+ ['http://www.example/è', `http://www.example/${encodeURIComponent('è')}`],
+ ['https://www.${MACRO_1}/${MACRO_1}/${MACRO_2}è', 'https://www.${MACRO_1}/${MACRO_1}/${MACRO_2}' + encodeURIComponent('è')],
+ ['http://${MACRO}${MACRO}/${MACRO}', 'http://${MACRO}${MACRO}/${MACRO}'],
+ ['{MACRO}${MACRO}', `${encodeURIComponent('{MACRO}')}\${MACRO}`],
+ ['https://www.example.com?p=${AUCTION_PRICE}', 'https://www.example.com?p=${AUCTION_PRICE}']
+ ].forEach(([input, expected]) => {
+ it(`can encode ${input} -> ${expected}`, () => {
+ expect(encodeMacroURI(input)).to.eql(expected);
+ })
+ })
+ })
});
describe('insertElement', function () {