Enriched ORTB2 device data
diff --git a/integrationExamples/gpt/adnuntius_multiformat_example.html b/integrationExamples/gpt/adnuntius_multiformat_example.html
new file mode 100644
index 00000000000..87b30d5887a
--- /dev/null
+++ b/integrationExamples/gpt/adnuntius_multiformat_example.html
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
Adnuntius NATIVE
+
Ad Slot 1
+
+
+
+
+
+
+
+
diff --git a/integrationExamples/gpt/precisonativeExample.html b/integrationExamples/gpt/precisonativeExample.html
new file mode 100644
index 00000000000..4e7009e2c58
--- /dev/null
+++ b/integrationExamples/gpt/precisonativeExample.html
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ad Serverless Test Page
+
+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's
+ standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make
+ a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting,
+ remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing
+ Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions
+ of Lorem Ipsum
+
+
+
+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin
+ literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney
+ College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and
+ going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum
+ comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by
+ Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance.
+ The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries/biddoInvamiaUtils/index.js b/libraries/biddoInvamiaUtils/index.js
new file mode 100644
index 00000000000..0d7c4b1b683
--- /dev/null
+++ b/libraries/biddoInvamiaUtils/index.js
@@ -0,0 +1,70 @@
+/**
+ * Helper function to build request payload for banner ads.
+ * @param {Object} bidRequest - The bid request object.
+ * @param {string} endpointUrl - The endpoint URL specific to the bidder.
+ * @returns {Array} An array of server requests.
+ */
+export function buildBannerRequests(bidRequest, endpointUrl) {
+ const serverRequests = [];
+ const sizes = bidRequest.mediaTypes.banner.sizes;
+
+ sizes.forEach(([width, height]) => {
+ bidRequest.params.requestedSizes = [width, height];
+
+ const payload = {
+ ctype: 'div',
+ pzoneid: bidRequest.params.zoneId,
+ width,
+ height,
+ };
+
+ const payloadString = Object.keys(payload)
+ .map((key) => `${key}=${encodeURIComponent(payload[key])}`)
+ .join('&');
+
+ serverRequests.push({
+ method: 'GET',
+ url: endpointUrl,
+ data: payloadString,
+ bidderRequest: bidRequest,
+ });
+ });
+
+ return serverRequests;
+}
+
+/**
+ * Helper function to interpret server response for banner ads.
+ * @param {Object} serverResponse - The server response object.
+ * @param {Object} bidderRequest - The matched bid request for this response.
+ * @returns {Array} An array of bid responses.
+ */
+export function interpretBannerResponse(serverResponse, bidderRequest) {
+ const response = serverResponse.body;
+ const bidResponses = [];
+
+ if (response && response.template && response.template.html) {
+ const { bidId } = bidderRequest;
+ const [width, height] = bidderRequest.params.requestedSizes;
+
+ const bidResponse = {
+ requestId: bidId,
+ cpm: response.hb.cpm,
+ creativeId: response.banner.hash,
+ currency: 'USD',
+ netRevenue: response.hb.netRevenue,
+ ttl: 600,
+ ad: response.template.html,
+ mediaType: 'banner',
+ meta: {
+ advertiserDomains: response.hb.adomains || [],
+ },
+ width,
+ height,
+ };
+
+ bidResponses.push(bidResponse);
+ }
+
+ return bidResponses;
+}
diff --git a/libraries/equativUtils/equativUtils.js b/libraries/equativUtils/equativUtils.js
new file mode 100644
index 00000000000..bdcbdad2f33
--- /dev/null
+++ b/libraries/equativUtils/equativUtils.js
@@ -0,0 +1,30 @@
+import { VIDEO } from '../../src/mediaTypes.js';
+import { deepAccess, isFn } from '../../src/utils.js';
+
+const DEFAULT_FLOOR = 0.0;
+
+/**
+ * Get floors from Prebid Price Floors module
+ *
+ * @param {object} bid Bid request object
+ * @param {string} currency Ad server currency
+ * @param {string} mediaType Bid media type
+ * @return {number} Floor price
+ */
+export function getBidFloor (bid, currency, mediaType) {
+ const floors = [];
+
+ if (isFn(bid.getFloor)) {
+ (deepAccess(bid, `mediaTypes.${mediaType}.${mediaType === VIDEO ? 'playerSize' : 'sizes'}`) || []).forEach(size => {
+ const floor = bid.getFloor({
+ currency: currency || 'USD',
+ mediaType,
+ size
+ }).floor;
+
+ floors.push(!isNaN(floor) ? floor : DEFAULT_FLOOR);
+ });
+ }
+
+ return floors.length ? Math.min(...floors) : DEFAULT_FLOOR;
+}
diff --git a/libraries/liveIntentId/externalIdSystem.js b/libraries/liveIntentId/externalIdSystem.js
index 5db94b90a44..9fcb9e6da1b 100644
--- a/libraries/liveIntentId/externalIdSystem.js
+++ b/libraries/liveIntentId/externalIdSystem.js
@@ -1,7 +1,7 @@
import { logError } from '../../src/utils.js';
import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js';
import { submodule } from '../../src/hook.js';
-import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeIdObject, eids, GVLID, PRIMARY_IDS } from './shared.js'
+import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeIdObject, eids, GVLID, PRIMARY_IDS, makeSourceEventToSend } from './shared.js'
// Reference to the client for the liQHub.
let cachedClientRef
@@ -97,8 +97,9 @@ function initializeClient(configParams) {
resolveSettings
})
- if (configParams.emailHash != null) {
- window.liQHub.push({ type: 'collect', clientRef, sourceEvent: { hash: configParams.emailHash } })
+ let sourceEvent = makeSourceEventToSend(configParams)
+ if (sourceEvent != null) {
+ window.liQHub.push({ type: 'collect', clientRef, sourceEvent })
}
cachedClientRef = clientRef
diff --git a/libraries/liveIntentId/idSystem.js b/libraries/liveIntentId/idSystem.js
index 2077df8d8bf..a9b8052c752 100644
--- a/libraries/liveIntentId/idSystem.js
+++ b/libraries/liveIntentId/idSystem.js
@@ -11,7 +11,7 @@ import { submodule } from '../../src/hook.js';
import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports
import { getStorageManager } from '../../src/storageManager.js';
import { MODULE_TYPE_UID } from '../../src/activities/modules.js';
-import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes } from './shared.js'
+import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes, makeSourceEventToSend } from './shared.js'
/**
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
@@ -23,7 +23,7 @@ const EVENTS_TOPIC = 'pre_lips';
export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME});
const calls = {
- ajaxGet: (url, onSuccess, onError, timeout) => {
+ ajaxGet: (url, onSuccess, onError, timeout, headers) => {
ajaxBuilder(timeout)(
url,
{
@@ -33,7 +33,8 @@ const calls = {
undefined,
{
method: 'GET',
- withCredentials: true
+ withCredentials: true,
+ customHeaders: headers
}
)
},
@@ -92,7 +93,11 @@ function initializeLiveConnect(configParams) {
const publisherId = configParams.publisherId || 'any';
const identityResolutionConfig = {
publisherId: publisherId,
- requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides)
+ requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides),
+ extraAttributes: {
+ ipv4: configParams.ipv4,
+ ipv6: configParams.ipv6
+ }
};
if (configParams.url) {
identityResolutionConfig.url = configParams.url;
@@ -136,8 +141,10 @@ function initializeLiveConnect(configParams) {
// The second param is the storage object, LS & Cookie manipulation uses PBJS.
// The third param is the ajax and pixel object, the AJAX and pixel use PBJS.
liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls);
- if (configParams.emailHash) {
- liveConnect.push({ hash: configParams.emailHash });
+
+ const sourceEvent = makeSourceEventToSend(configParams)
+ if (sourceEvent != null) {
+ liveConnect.push(sourceEvent);
}
return liveConnect;
}
diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js
index 2e831a899f4..509f91e44d9 100644
--- a/libraries/liveIntentId/shared.js
+++ b/libraries/liveIntentId/shared.js
@@ -29,6 +29,31 @@ export function parseRequestedAttributes(overrides) {
}
}
+export function makeSourceEventToSend(configParams) {
+ const sourceEvent = {}
+ let nonEmpty = false
+ if (typeof configParams.emailHash === 'string') {
+ nonEmpty = true
+ sourceEvent.emailHash = configParams.emailHash
+ }
+ if (typeof configParams.ipv4 === 'string') {
+ nonEmpty = true
+ sourceEvent.ipv4 = configParams.ipv4
+ }
+ if (typeof configParams.ipv6 === 'string') {
+ nonEmpty = true
+ sourceEvent.ipv6 = configParams.ipv6
+ }
+ if (typeof configParams.userAgent === 'string') {
+ nonEmpty = true
+ sourceEvent.userAgent = configParams.userAgent
+ }
+
+ if (nonEmpty) {
+ return sourceEvent
+ }
+}
+
export function composeIdObject(value) {
const result = {};
@@ -89,6 +114,18 @@ export function composeIdObject(value) {
delete result.lipb.thetradedesk
}
+ if (value.sharethrough) {
+ result.sharethrough = { 'id': value.sharethrough, ext: { provider: LI_PROVIDER_DOMAIN } }
+ }
+
+ if (value.sonobi) {
+ result.sonobi = { 'id': value.sonobi, ext: { provider: LI_PROVIDER_DOMAIN } }
+ }
+
+ if (value.vidazoo) {
+ result.vidazoo = { 'id': value.vidazoo, ext: { provider: LI_PROVIDER_DOMAIN } }
+ }
+
return result
}
@@ -199,5 +236,41 @@ export const eids = {
getValue: function(data) {
return data.id;
}
+ },
+ 'sharethrough': {
+ source: 'sharethrough.com',
+ atype: 3,
+ getValue: function(data) {
+ return data.id;
+ },
+ getUidExt: function(data) {
+ if (data.ext) {
+ return data.ext;
+ }
+ }
+ },
+ 'sonobi': {
+ source: 'liveintent.sonobi.com',
+ atype: 3,
+ getValue: function(data) {
+ return data.id;
+ },
+ getUidExt: function(data) {
+ if (data.ext) {
+ return data.ext;
+ }
+ }
+ },
+ 'vidazoo': {
+ source: 'liveintent.vidazoo.com',
+ atype: 3,
+ getValue: function(data) {
+ return data.id;
+ },
+ getUidExt: function(data) {
+ if (data.ext) {
+ return data.ext;
+ }
+ }
}
}
diff --git a/libraries/precisoUtils/bidNativeUtils.js b/libraries/precisoUtils/bidNativeUtils.js
new file mode 100644
index 00000000000..29b39f6d77d
--- /dev/null
+++ b/libraries/precisoUtils/bidNativeUtils.js
@@ -0,0 +1,104 @@
+import { deepAccess, logInfo } from '../../src/utils.js';
+import { NATIVE } from '../../src/mediaTypes.js';
+import { macroReplace } from './bidUtils.js';
+
+const TTL = 55;
+// Codes defined by OpenRTB Native Ads 1.1 specification
+export const OPENRTB = {
+ NATIVE: {
+ IMAGE_TYPE: {
+ ICON: 1,
+ MAIN: 3,
+ },
+ ASSET_ID: {
+ TITLE: 1,
+ IMAGE: 2,
+ ICON: 3,
+ BODY: 4,
+ SPONSORED: 5,
+ CTA: 6
+ },
+ DATA_ASSET_TYPE: {
+ SPONSORED: 1,
+ DESC: 2,
+ CTA_TEXT: 12,
+ },
+ }
+};
+
+/**
+ * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3
+ * @returns {object} Prebid native bidObject
+ */
+export function interpretNativeBid(serverBid) {
+ return {
+ requestId: serverBid.impid,
+ mediaType: NATIVE,
+ cpm: serverBid.price,
+ creativeId: serverBid.adid || serverBid.crid,
+ width: 1,
+ height: 1,
+ ttl: TTL,
+ meta: {
+ advertiserDomains: serverBid.adomain
+ },
+ netRevenue: true,
+ currency: 'USD',
+ // native: interpretNativeAd(serverBid.adm)
+ native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price))
+ }
+}
+
+/**
+ * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1
+ * @returns {object} Prebid bidObject.native
+ */
+
+export function interpretNativeAd(adm) {
+ try {
+ const native = JSON.parse(adm).native;
+ if (native) {
+ const result = {
+ clickUrl: encodeURI(native.link.url),
+ impressionTrackers: native.imptrackers || native.eventtrackers[0].url,
+ };
+ if (native.link.clicktrackers) {
+ result.clickTrackers = native.link.clicktrackers[0];
+ }
+
+ native.assets.forEach(asset => {
+ switch (asset.id) {
+ case OPENRTB.NATIVE.ASSET_ID.TITLE:
+ result.title = deepAccess(asset, 'title.text');
+ break;
+ case OPENRTB.NATIVE.ASSET_ID.IMAGE:
+ result.image = {
+ url: encodeURI(asset.img.url),
+ width: deepAccess(asset, 'img.w'),
+ height: deepAccess(asset, 'img.h')
+ };
+ break;
+ case OPENRTB.NATIVE.ASSET_ID.ICON:
+ result.icon = {
+ url: encodeURI(asset.img.url),
+ width: deepAccess(asset, 'img.w'),
+ height: deepAccess(asset, 'img.h')
+ };
+ break;
+ case OPENRTB.NATIVE.ASSET_ID.BODY:
+ result.body = deepAccess(asset, 'data.value');
+ break;
+ case OPENRTB.NATIVE.ASSET_ID.SPONSORED:
+ result.sponsoredBy = deepAccess(asset, 'data.value');
+ break;
+ case OPENRTB.NATIVE.ASSET_ID.CTA:
+ result.cta = deepAccess(asset, 'data.value');
+ break;
+ }
+ });
+ return result;
+ }
+ } catch (error) {
+ logInfo('Error in bidUtils interpretNativeAd' + error);
+ }
+}
diff --git a/libraries/precisoUtils/bidUtils.js b/libraries/precisoUtils/bidUtils.js
index 95278cd1013..8359963cd03 100644
--- a/libraries/precisoUtils/bidUtils.js
+++ b/libraries/precisoUtils/bidUtils.js
@@ -1,33 +1,16 @@
import { convertOrtbRequestToProprietaryNative } from '../../src/native.js';
-import { replaceAuctionPrice } from '../../src/utils.js';
+import { replaceAuctionPrice, deepAccess } from '../../src/utils.js';
import { ajax } from '../../src/ajax.js';
-import { consentCheck } from './bidUtilsCommon.js';
+// import { NATIVE } from '../../src/mediaTypes.js';
+import { consentCheck, getBidFloor } from './bidUtilsCommon.js';
+import { interpretNativeBid } from './bidNativeUtils.js';
export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => {
- // convert Native ORTB definition to old-style prebid native definition
validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests);
var city = Intl.DateTimeFormat().resolvedOptions().timeZone;
let req = {
- // bidRequest: bidderRequest,
id: validBidRequests[0].auctionId,
- cur: validBidRequests[0].params.currency || ['USD'],
- imp: validBidRequests.map(req => {
- const { bidId, sizes } = req
- const impValue = {
- id: bidId,
- bidfloor: req.params.bidFloor,
- bidfloorcur: req.params.currency
- }
- if (req.mediaTypes.banner) {
- impValue.banner = {
- format: (req.mediaTypes.banner.sizes || sizes).map(size => {
- return { w: size[0], h: size[1] }
- }),
-
- }
- }
- return impValue
- }),
+ imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)),
user: {
id: validBidRequests[0].userId.pubcid || '',
buyeruid: validBidRequests[0].buyerUid || '',
@@ -55,7 +38,6 @@ export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest
publisherId: validBidRequests[0].params.publisherId
};
- // req.language.indexOf('-') != -1 && (req.language = req.language.split('-')[0])
consentCheck(bidderRequest, req);
return {
method: 'POST',
@@ -96,8 +78,172 @@ export function onBidWon(bid) {
}
}
-/* replacing auction_price macro from adm */
-function macroReplace(adm, cpm) {
+export function macroReplace(adm, cpm) {
let replacedadm = replaceAuctionPrice(adm, cpm);
return replacedadm;
}
+
+function mapImpression(slot, bidderRequest) {
+ const imp = {
+ id: slot.bidId,
+ bidFloor: getBidFloor(slot),
+ };
+
+ if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) {
+ imp.native = mapNative(slot)
+ } else {
+ imp.banner = mapBanner(slot)
+ }
+ return imp
+}
+
+function mapNative(slot) {
+ if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) {
+ let request = {
+ assets: slot.nativeOrtbRequest.assets || slot.nativeParams.ortb.assets,
+ ver: '1.2'
+ };
+ return {
+ request: JSON.stringify(request)
+ }
+ }
+}
+
+function mapBanner(slot) {
+ if (slot.mediaTypes.banner) {
+ let format = (slot.mediaTypes.banner.sizes || slot.sizes).map(size => {
+ return { w: size[0], h: size[1] }
+ });
+
+ return {
+ format
+ }
+ }
+}
+
+export function buildBidResponse(serverResponse) {
+ const responseBody = serverResponse.body;
+ const bids = [];
+ responseBody.seatbid.forEach(seat => {
+ seat.bid.forEach(serverBid => {
+ if (!serverBid.price) {
+ return;
+ }
+ if (serverBid.adm.indexOf('{') === 0) {
+ let interpretedBid = interpretNativeBid(serverBid);
+ bids.push(interpretedBid
+ );
+ } else {
+ bids.push({
+ requestId: serverBid.impid,
+ cpm: serverBid.price,
+ width: serverBid.w,
+ height: serverBid.h,
+ creativeId: serverBid.crid,
+ ad: macroReplace(serverBid.adm, serverBid.price),
+ currency: 'USD',
+ netRevenue: true,
+ ttl: 300,
+ meta: {
+ advertiserDomains: serverBid.adomain || '',
+ },
+ });
+ }
+ })
+ });
+ return bids;
+}
+
+// export function interpretNativeAd(adm) {
+// try {
+// // logInfo('adm::' + adm);
+// const native = JSON.parse(adm).native;
+// if (native) {
+// const result = {
+// clickUrl: encodeURI(native.link.url),
+// impressionTrackers: native.eventtrackers[0].url,
+// };
+// if (native.link.clicktrackers[0]) {
+// result.clickTrackers = native.link.clicktrackers[0];
+// }
+
+// native.assets.forEach(asset => {
+// switch (asset.id) {
+// case OPENRTB.NATIVE.ASSET_ID.TITLE:
+// result.title = deepAccess(asset, 'title.text');
+// break;
+// case OPENRTB.NATIVE.ASSET_ID.IMAGE:
+// result.image = {
+// url: encodeURI(asset.img.url),
+// width: deepAccess(asset, 'img.w'),
+// height: deepAccess(asset, 'img.h')
+// };
+// break;
+// case OPENRTB.NATIVE.ASSET_ID.ICON:
+// result.icon = {
+// url: encodeURI(asset.img.url),
+// width: deepAccess(asset, 'img.w'),
+// height: deepAccess(asset, 'img.h')
+// };
+// break;
+// case OPENRTB.NATIVE.ASSET_ID.DATA:
+// result.body = deepAccess(asset, 'data.value');
+// break;
+// case OPENRTB.NATIVE.ASSET_ID.SPONSORED:
+// result.sponsoredBy = deepAccess(asset, 'data.value');
+// break;
+// case OPENRTB.NATIVE.ASSET_ID.CTA:
+// result.cta = deepAccess(asset, 'data.value');
+// break;
+// }
+// });
+// return result;
+// }
+// } catch (error) {
+// logInfo('Error in bidUtils interpretNativeAd' + error);
+// }
+// }
+
+// export const OPENRTB = {
+// NATIVE: {
+// IMAGE_TYPE: {
+// ICON: 1,
+// MAIN: 3,
+// },
+// ASSET_ID: {
+// TITLE: 1,
+// IMAGE: 2,
+// ICON: 3,
+// BODY: 4,
+// SPONSORED: 5,
+// CTA: 6
+// },
+// DATA_ASSET_TYPE: {
+// SPONSORED: 1,
+// DESC: 2,
+// CTA_TEXT: 12,
+// },
+// }
+// };
+
+// /**
+// * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3
+// * @returns {object} Prebid native bidObject
+// */
+// export function interpretNativeBid(serverBid) {
+// return {
+// requestId: serverBid.impid,
+// mediaType: NATIVE,
+// cpm: serverBid.price,
+// creativeId: serverBid.adid || serverBid.crid,
+// width: 1,
+// height: 1,
+// ttl: 56,
+// meta: {
+// advertiserDomains: serverBid.adomain
+// },
+// netRevenue: true,
+// currency: 'USD',
+// native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price))
+// }
+// }
diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js
index cb9699035fb..74cf00b8450 100644
--- a/libraries/precisoUtils/bidUtilsCommon.js
+++ b/libraries/precisoUtils/bidUtilsCommon.js
@@ -61,16 +61,6 @@ export const buildBidRequests = (adurl) => (validBidRequests = [], bidderRequest
placements: placements
};
consentCheck(bidderRequest, request);
-
- // if (bidderRequest) {
- // if (bidderRequest.uspConsent) {
- // request.ccpa = bidderRequest.uspConsent;
- // }
- // if (bidderRequest.gdprConsent) {
- // request.gdpr = bidderRequest.gdprConsent;
- // }
- // }
-
const len = validBidRequests.length;
for (let i = 0; i < len; i++) {
const bid = validBidRequests[i];
@@ -134,7 +124,6 @@ export const buildUserSyncs = (syncOptions, serverResponses, gdprConsent, uspCon
let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image';
const isCk2trk = syncEndpoint.includes('ck.2trk.info');
- // Base sync URL
let syncUrl = isCk2trk ? syncEndpoint : `${syncEndpoint}/${syncType}?pbjs=1`;
if (gdprConsent && gdprConsent.consentString) {
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 36daa70e75b..d998a62500a 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -36,6 +36,7 @@
"novatiqIdSystem",
"oneKeyIdSystem",
"operaadsIdSystem",
+ "permutiveIdentityManagerIdSystem",
"pubProvidedIdSystem",
"publinkIdSystem",
"quantcastIdSystem",
diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js
index 4f8024aa84f..8251fd76e28 100644
--- a/modules/51DegreesRtdProvider.js
+++ b/modules/51DegreesRtdProvider.js
@@ -4,6 +4,7 @@ import {submodule} from '../src/hook.js';
import {
deepAccess,
deepSetValue,
+ formatQS,
mergeDeep,
prefixLog,
} from '../src/utils.js';
@@ -107,15 +108,42 @@ export const extractConfig = (moduleConfig, reqBidsConfigObj) => {
* @param {Object} pathData API path data
* @param {string} [pathData.resourceKey] Resource key
* @param {string} [pathData.onPremiseJSUrl] On-premise JS URL
+ * @param {Object
} [pathData.hev] High entropy values
+ * @param {Window} [win] Window object (mainly for testing)
* @returns {string} 51Degrees JS URL
*/
-export const get51DegreesJSURL = (pathData) => {
- if (pathData.onPremiseJSUrl) {
- return pathData.onPremiseJSUrl;
- }
- return `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`;
+export const get51DegreesJSURL = (pathData, win) => {
+ const _window = win || window;
+ const baseURL = pathData.onPremiseJSUrl || `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`;
+
+ const queryPrefix = baseURL.includes('?') ? '&' : '?';
+ const qs = {};
+
+ deepSetNotEmptyValue(
+ qs,
+ '51D_GetHighEntropyValues',
+ pathData.hev && Object.keys(pathData.hev).length ? btoa(JSON.stringify(pathData.hev)) : null,
+ );
+ deepSetNotEmptyValue(qs, '51D_ScreenPixelsHeight', _window?.screen?.height);
+ deepSetNotEmptyValue(qs, '51D_ScreenPixelsWidth', _window?.screen?.width);
+ deepSetNotEmptyValue(qs, '51D_PixelRatio', _window?.devicePixelRatio);
+
+ const _qs = formatQS(qs);
+ const _qsString = _qs ? `${queryPrefix}${_qs}` : '';
+
+ return `${baseURL}${_qsString}`;
}
+/**
+ * Retrieves high entropy values from `navigator.userAgentData` if available
+ *
+ * @param {Array} hints - An array of hints indicating which high entropy values to retrieve
+ * @returns {Promise>} A promise that resolves to an object containing high entropy values if supported, or `undefined` if not
+ */
+export const getHighEntropyValues = async (hints) => {
+ return navigator?.userAgentData?.getHighEntropyValues?.(hints);
+};
+
/**
* Check if meta[http-equiv="Delegate-CH"] tag is present in the document head and points to 51Degrees cloud
*
@@ -251,10 +279,6 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
logMessage('Resource key: ', resourceKey);
logMessage('On-premise JS URL: ', onPremiseJSUrl);
- // Get 51Degrees JS URL, which is either cloud or on-premise
- const scriptURL = get51DegreesJSURL(resourceKey ? {resourceKey} : {onPremiseJSUrl});
- logMessage('URL of the script to be injected: ', scriptURL);
-
// Check if 51Degrees meta is present (cloud only)
if (resourceKey) {
logMessage('Checking if 51Degrees meta is present in the document head');
@@ -263,21 +287,27 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
}
}
- // Inject 51Degrees script, get device data and merge it into the ORTB2 object
- loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => {
- logMessage('Successfully injected 51Degrees script');
- const fod = /** @type {Object} */ (window.fod);
- // Convert and merge device data in the callback
- fod.complete((data) => {
- logMessage('51Degrees raw data: ', data);
- mergeDeep(
- reqBidsConfigObj.ortb2Fragments.global,
- convert51DegreesDataToOrtb2(data),
- );
- logMessage('reqBidsConfigObj: ', reqBidsConfigObj);
- callback();
- });
- }, document, {crossOrigin: 'anonymous'});
+ getHighEntropyValues(['model', 'platform', 'platformVersion', 'fullVersionList']).then((hev) => {
+ // Get 51Degrees JS URL, which is either cloud or on-premise
+ const scriptURL = get51DegreesJSURL({resourceKey, onPremiseJSUrl, hev});
+ logMessage('URL of the script to be injected: ', scriptURL);
+
+ // Inject 51Degrees script, get device data and merge it into the ORTB2 object
+ loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => {
+ logMessage('Successfully injected 51Degrees script');
+ const fod = /** @type {Object} */ (window.fod);
+ // Convert and merge device data in the callback
+ fod.complete((data) => {
+ logMessage('51Degrees raw data: ', data);
+ mergeDeep(
+ reqBidsConfigObj.ortb2Fragments.global,
+ convert51DegreesDataToOrtb2(data),
+ );
+ logMessage('reqBidsConfigObj: ', reqBidsConfigObj);
+ callback();
+ });
+ }, document, {crossOrigin: 'anonymous'});
+ });
} catch (error) {
// In case of an error, log it and continue
logError(error);
diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js
index 452e521c680..94319aa6108 100644
--- a/modules/adagioAnalyticsAdapter.js
+++ b/modules/adagioAnalyticsAdapter.js
@@ -68,6 +68,7 @@ const cache = {
return { auctionId: null, adUnitCode: null }
}
};
+
const enc = window.encodeURIComponent;
/**
@@ -195,7 +196,14 @@ function handlerAuctionInit(event) {
const w = getBestWindowForAdagio();
const prebidAuctionId = event.auctionId;
- const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode);
+
+ // adUnitCodes come from `event.bidderRequests` to be sure to keep the ad-units that are valid and will be effectively used during the auction.
+ // This array can be different than `event.adUnitCodes` because of the usage of conditionnal ad-units (see: https://docs.prebid.org/dev-docs/conditional-ad-units.html)
+ const adUnitCodes = new Set(
+ event.bidderRequests
+ .map(br => br.bids.map(bid => bid.adUnitCode))
+ .flat()
+ );
// Check if Adagio is on the bid requests.
const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode));
@@ -207,6 +215,7 @@ function handlerAuctionInit(event) {
adUnitCodes.forEach(adUnitCode => {
// event.adUnits are splitted by mediatypes
+ // having twin ad-unit codes is ok: https://docs.prebid.org/dev-docs/adunit-reference.html#twin-adunit-codes
const adUnits = event.adUnits.filter(adUnit => adUnit.code === adUnitCode);
// Get all bidders configured for the ad unit.
@@ -236,6 +245,7 @@ function handlerAuctionInit(event) {
const request = event.bidderRequests.find(br => br.bidderCode === bidder)
return request ? request.bids[0].src : null
}
+
const biddersSrc = sortedBidderNames.map(bidSrcMapper).join(',');
const biddersCode = sortedBidderNames.map(bidder => adapterManager.resolveAlias(bidder)).join(',');
diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js
index 74c063042ef..78e76651177 100644
--- a/modules/admaticBidAdapter.js
+++ b/modules/admaticBidAdapter.js
@@ -4,6 +4,7 @@ import { config } from '../src/config.js';
import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js';
import { Renderer } from '../src/Renderer.js';
import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js';
+import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -11,28 +12,6 @@ import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js';
* @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
*/
-export const OPENRTB = {
- N: {
- IMAGE_TYPE: {
- ICON: 1,
- MAIN: 3,
- },
- ASSET_ID: {
- TITLE: 1,
- IMAGE: 2,
- ICON: 3,
- BODY: 4,
- SPONSORED: 5,
- CTA: 6
- },
- DATA_ASSET_TYPE: {
- SPONSORED: 1,
- DESC: 2,
- CTA_TEXT: 12,
- },
- }
-};
-
let SYNC_URL = '';
const BIDDER_CODE = 'admatic';
const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js';
@@ -416,45 +395,6 @@ function concatSizes(bid) {
}
}
-function interpretNativeAd(adm) {
- const native = JSON.parse(adm).native;
- const result = {
- clickUrl: encodeURI(native.link.url),
- impressionTrackers: native.imptrackers
- };
- native.assets.forEach(asset => {
- switch (asset.id) {
- case OPENRTB.N.ASSET_ID.TITLE:
- result.title = asset.title.text;
- break;
- case OPENRTB.N.ASSET_ID.IMAGE:
- result.image = {
- url: encodeURI(asset.img.url),
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- case OPENRTB.N.ASSET_ID.ICON:
- result.icon = {
- url: encodeURI(asset.img.url),
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- case OPENRTB.N.ASSET_ID.BODY:
- result.body = asset.data.value;
- break;
- case OPENRTB.N.ASSET_ID.SPONSORED:
- result.sponsoredBy = asset.data.value;
- break;
- case OPENRTB.N.ASSET_ID.CTA:
- result.cta = asset.data.value;
- break;
- }
- });
- return result;
-}
-
function _validateId(id) {
return (parseInt(id) > 0);
}
diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js
index d017b6a8398..cce1b5332ad 100644
--- a/modules/adnuntiusBidAdapter.js
+++ b/modules/adnuntiusBidAdapter.js
@@ -1,5 +1,5 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js';
import { config } from '../src/config.js';
import { getStorageManager } from '../src/storageManager.js';
@@ -12,13 +12,20 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => {
const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i';
const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i';
const GVLID = 855;
-const DEFAULT_VAST_VERSION = 'vast4'
+const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO];
const MAXIMUM_DEALS_LIMIT = 5;
const VALID_BID_TYPES = ['netBid', 'grossBid'];
const METADATA_KEY = 'adn.metaData';
const METADATA_KEY_SEPARATOR = '@@@';
export const misc = {
+ findHighestPrice: function(arr, bidType) {
+ return arr.reduce((highest, cur) => {
+ const currentBid = cur[bidType];
+ const highestBid = highest[bidType]
+ return currentBid.currency === highestBid.currency && currentBid.amount > highestBid.amount ? cur : highest;
+ }, arr[0]);
+ }
};
const storageTool = (function () {
@@ -219,7 +226,7 @@ export const spec = {
code: BIDDER_CODE,
aliases: BIDDER_CODE_DEAL_ALIASES,
gvlid: GVLID,
- supportedMediaTypes: [BANNER, VIDEO],
+ supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
isBidRequestValid: function (bid) {
// The auId MUST be a hexadecimal string
const validAuId = AU_ID_REGEX.test(bid.params.auId);
@@ -266,10 +273,6 @@ export const spec = {
}
let network = bid.params.network || 'network';
- if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') {
- network += '_video'
- }
-
bidRequests[network] = bidRequests[network] || [];
bidRequests[network].push(bid);
@@ -291,20 +294,40 @@ export const spec = {
const bidTargeting = {...bid.params.targeting || {}};
targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest);
- const adUnit = { ...bidTargeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId };
- const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT));
- if (maxDeals > 0) {
- adUnit.maxDeals = maxDeals;
+ const mediaTypes = bid.mediaTypes || {};
+ const validMediaTypes = SUPPORTED_MEDIA_TYPES.filter(mt => {
+ return mediaTypes[mt];
+ }) || [];
+ if (validMediaTypes.length === 0) {
+ // banner ads by default if nothing specified, dimensions to be derived from the ad unit within adnuntius system
+ validMediaTypes.push(BANNER);
}
- if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes
- networks[network].adUnits.push(adUnit);
+ const isSingleFormat = validMediaTypes.length === 1;
+ validMediaTypes.forEach(mediaType => {
+ const mediaTypeData = mediaTypes[mediaType];
+ if (mediaType === VIDEO && mediaTypeData && mediaTypeData.context === 'outstream') {
+ return;
+ }
+ const targetId = (bid.params.targetId || bid.bidId) + (isSingleFormat || mediaType === BANNER ? '' : ('-' + mediaType));
+ const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId};
+ if (mediaType === VIDEO) {
+ adUnit.adType = 'VAST';
+ }
+ const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT));
+ if (maxDeals > 0) {
+ adUnit.maxDeals = maxDeals;
+ }
+ if (mediaType === BANNER && mediaTypeData && mediaTypeData.sizes) {
+ adUnit.dimensions = mediaTypeData.sizes;
+ }
+ networks[network].adUnits.push(adUnit);
+ });
}
const requests = [];
const networkKeys = Object.keys(networks);
for (let j = 0; j < networkKeys.length; j++) {
const network = networkKeys[j];
- if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) }
const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL
requests.push({
method: 'POST',
@@ -321,7 +344,7 @@ export const spec = {
if (serverResponse.body.metaData) {
storageTool.saveToStorage(serverResponse.body.metaData, serverResponse.body.network);
}
- const adUnits = serverResponse.body.adUnits;
+ const responseAdUnits = serverResponse.body.adUnits;
let validatedBidType = validateBidType(config.getConfig().bidType);
if (bidRequest.bid) {
@@ -367,6 +390,35 @@ export const spec = {
return adResponse;
}
+ const highestYieldingAdUnits = [];
+ if (responseAdUnits.length === 1) {
+ highestYieldingAdUnits.push(responseAdUnits[0]);
+ } else if (responseAdUnits.length > 1) {
+ bidRequest.bid.forEach((resp) => {
+ const multiFormatAdUnits = [];
+ SUPPORTED_MEDIA_TYPES.forEach((mediaType) => {
+ const suffix = mediaType === BANNER ? '' : '-' + mediaType;
+ const targetId = (resp?.params?.targetId || resp.bidId) + suffix;
+
+ const au = responseAdUnits.find((rAu) => {
+ return rAu.targetId === targetId && rAu.matchedAdCount > 0;
+ });
+ if (au) {
+ multiFormatAdUnits.push(au);
+ }
+ });
+ if (multiFormatAdUnits.length > 0) {
+ const highestYield = multiFormatAdUnits.length === 1 ? multiFormatAdUnits[0] : multiFormatAdUnits.reduce((highest, cur) => {
+ const highestBid = misc.findHighestPrice(highest.ads, validatedBidType)[validatedBidType];
+ const curBid = misc.findHighestPrice(cur.ads, validatedBidType)[validatedBidType];
+ return curBid.currency === highestBid.currency && curBid.amount > highestBid.amount ? cur : highest;
+ }, multiFormatAdUnits[0]);
+ highestYield.targetId = resp.bidId;
+ highestYieldingAdUnits.push(highestYield);
+ }
+ });
+ }
+
const bidsById = bidRequest.bid.reduce((response, bid) => {
return {
...response,
@@ -374,7 +426,7 @@ export const spec = {
};
}, {});
- const hasBidAdUnits = adUnits.filter((au) => {
+ const hasBidAdUnits = highestYieldingAdUnits.filter((au) => {
const bid = bidsById[au.targetId];
if (bid && bid.bidder && BIDDER_CODE_DEAL_ALIASES.indexOf(bid.bidder) < 0) {
return au.matchedAdCount > 0;
@@ -384,7 +436,7 @@ export const spec = {
return false;
}
});
- const hasDealsAdUnits = adUnits.filter((au) => {
+ const hasDealsAdUnits = highestYieldingAdUnits.filter((au) => {
return au.deals && au.deals.length > 0;
});
diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js
index 9a58308278b..e5baac1add0 100644
--- a/modules/aniviewBidAdapter.js
+++ b/modules/aniviewBidAdapter.js
@@ -31,7 +31,6 @@ const converter = ortbConverter({
imp(buildImp, bidRequest, context) {
const { mediaType } = context;
const imp = buildImp(bidRequest, context);
- const isVideo = mediaType === VIDEO;
const isBanner = mediaType === BANNER;
const { width, height } = getSize(context, bidRequest);
const floor = getFloor(bidRequest, { width, height }, mediaType);
@@ -43,14 +42,7 @@ const converter = ortbConverter({
imp.bidfloorcur = DEFAULT_CURRENCY;
}
- if (isVideo) {
- deepSetValue(imp, `ext.${BIDDER_CODE}`, {
- AV_WIDTH: width,
- AV_HEIGHT: height,
- bidWidth: width,
- bidHeight: height,
- });
- } else if (isBanner) {
+ if (isBanner) {
// TODO: remove once serving will be fixed
deepSetValue(imp, 'banner', { w: width, h: height });
}
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index cf503dfe006..4270b47d91e 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -710,6 +710,7 @@ function newBid(serverBid, rtbBid, bidderRequest) {
// Custom fields
bid[NATIVE].ext = {
+ video: nativeAd.video,
customImage1: nativeAd.image1 && {
url: nativeAd.image1.url,
height: nativeAd.image1.height,
diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js
index d94e68b7e55..eaeead6a249 100644
--- a/modules/atsAnalyticsAdapter.js
+++ b/modules/atsAnalyticsAdapter.js
@@ -325,7 +325,7 @@ atsAnalyticsAdapter.getUserAgent = function () {
atsAnalyticsAdapter.setSamplingCookie = function (samplRate) {
const now = new Date();
- now.setTime(now.getTime() + 86400000);
+ now.setTime(now.getTime() + 604800000);
storage.setCookie('_lr_sampling_rate', samplRate, now.toUTCString());
}
diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js
index 3ace8108d2b..5b138965983 100644
--- a/modules/bidResponseFilter/index.js
+++ b/modules/bidResponseFilter/index.js
@@ -7,14 +7,29 @@ export const BID_CATEGORY_REJECTION_REASON = 'Category is not allowed';
export const BID_ADV_DOMAINS_REJECTION_REASON = 'Adv domain is not allowed';
export const BID_ATTR_REJECTION_REASON = 'Attr is not allowed';
+let moduleConfig;
+let enabled = false;
+
function init() {
- getHook('addBidResponse').before(addBidResponseHook);
-};
+ config.getConfig(MODULE_NAME, (cfg) => {
+ moduleConfig = cfg[MODULE_NAME];
+ if (enabled && !moduleConfig) {
+ reset();
+ } else if (!enabled && moduleConfig) {
+ enabled = true;
+ getHook('addBidResponse').before(addBidResponseHook);
+ }
+ })
+}
+
+export function reset() {
+ enabled = false;
+ getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove();
+}
export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) {
const {bcat = [], badv = []} = index.getOrtb2(bid) || {};
const battr = index.getBidRequest(bid)?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || [];
- const moduleConfig = config.getConfig(MODULE_NAME);
const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})};
const advConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.adv || {})};
diff --git a/modules/biddoBidAdapter.js b/modules/biddoBidAdapter.js
index cf39c572629..6bfa0ac6ef8 100644
--- a/modules/biddoBidAdapter.js
+++ b/modules/biddoBidAdapter.js
@@ -1,5 +1,6 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER} from '../src/mediaTypes.js';
+import { buildBannerRequests, interpretBannerResponse } from '../libraries/biddoInvamiaUtils/index.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -12,86 +13,15 @@ const ENDPOINT_URL = 'https://ad.adopx.net/delivery/impress';
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],
- /**
- * Determines whether or not the given bid request is valid.
- *
- * @param {BidRequest} bidRequest The bid request params to validate.
- * @return boolean True if this is a valid bid request, and false otherwise.
- */
- isBidRequestValid: function(bidRequest) {
+ isBidRequestValid: function (bidRequest) {
return !!bidRequest.params.zoneId;
},
- /**
- * Make a server request from the list of BidRequests.
- *
- * @param {Array} validBidRequests an array of bid requests
- * @return ServerRequest Info describing the request to the server.
- */
- buildRequests: function(validBidRequests) {
- let serverRequests = [];
-
- validBidRequests.forEach(bidRequest => {
- const sizes = bidRequest.mediaTypes.banner.sizes;
-
- sizes.forEach(([width, height]) => {
- bidRequest.params.requestedSizes = [width, height];
-
- const payload = {
- ctype: 'div',
- pzoneid: bidRequest.params.zoneId,
- width,
- height,
- };
-
- const payloadString = Object.keys(payload).map(k => k + '=' + encodeURIComponent(payload[k])).join('&');
-
- serverRequests.push({
- method: 'GET',
- url: ENDPOINT_URL,
- data: payloadString,
- bidderRequest: bidRequest,
- });
- });
- });
-
- return serverRequests;
+ buildRequests: function (validBidRequests) {
+ return validBidRequests.flatMap((bidRequest) => buildBannerRequests(bidRequest, ENDPOINT_URL));
},
- /**
- * Unpack the response from the server into a list of bids.
- *
- * @param {ServerResponse} serverResponse A successful response from the server.
- * @param {BidRequest} bidderRequest A matched bid request for this response.
- * @return Array An array of bids which were nested inside the server.
- */
- interpretResponse: function(serverResponse, {bidderRequest}) {
- const response = serverResponse.body;
- const bidResponses = [];
-
- if (response && response.template && response.template.html) {
- const {bidId} = bidderRequest;
- const [width, height] = bidderRequest.params.requestedSizes;
-
- const bidResponse = {
- requestId: bidId,
- cpm: response.hb.cpm,
- creativeId: response.banner.hash,
- currency: 'USD',
- netRevenue: response.hb.netRevenue,
- ttl: 600,
- ad: response.template.html,
- mediaType: 'banner',
- meta: {
- advertiserDomains: response.hb.adomains || [],
- },
- width,
- height,
- };
-
- bidResponses.push(bidResponse);
- }
-
- return bidResponses;
+ interpretResponse: function (serverResponse, { bidderRequest }) {
+ return interpretBannerResponse(serverResponse, bidderRequest);
},
-}
+};
registerBidder(spec);
diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js
index 97283952888..fa441a4f4fa 100644
--- a/modules/cadentApertureMXBidAdapter.js
+++ b/modules/cadentApertureMXBidAdapter.js
@@ -230,11 +230,6 @@ export const spec = {
return false;
}
- if (bid.bidder !== BIDDER_CODE) {
- logWarn(BIDDER_CODE + ': Must use "cadent_aperture_mx" as bidder code.');
- return false;
- }
-
if (!bid.params.tagid || !isStr(bid.params.tagid)) {
logWarn(BIDDER_CODE + ': Missing tagid param or tagid present and not type String.');
return false;
diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js
index 6e453e2caa7..aeadd2d1cd9 100644
--- a/modules/connatixBidAdapter.js
+++ b/modules/connatixBidAdapter.js
@@ -6,7 +6,7 @@ import {
import { percentInView } from '../libraries/percentInView/percentInView.js';
import { config } from '../src/config.js';
-
+import { getStorageManager } from '../src/storageManager.js';
import { ajax } from '../src/ajax.js';
import {
deepAccess,
@@ -31,6 +31,12 @@ const BIDDER_CODE = 'connatix';
const AD_URL = 'https://capi.connatix.com/rtb/hba';
const DEFAULT_MAX_TTL = '3600';
const DEFAULT_CURRENCY = 'USD';
+const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids';
+const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; // 30 days
+export const storage = getStorageManager({ bidderCode: BIDDER_CODE });
+const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved';
+const IDENTITY_PROVIDER_RESOLVED_EVENT = 'cnx_identity_provider_resolved';
+let cnxIdsValues;
const EVENTS_URL = 'https://capi.connatix.com/tr/am';
@@ -182,6 +188,25 @@ function _handleEids(payload, validBidRequests) {
}
}
+export function saveOnAllStorages(name, value, expirationTimeMs) {
+ const date = new Date();
+ date.setTime(date.getTime() + expirationTimeMs);
+ const expires = `expires=${date.toUTCString()}`;
+ storage.setCookie(name, JSON.stringify(value), expires);
+ storage.setDataInLocalStorage(name, JSON.stringify(value));
+ cnxIdsValues = value;
+}
+
+export function readFromAllStorages(name) {
+ const fromCookie = storage.getCookie(name);
+ const fromLocalStorage = storage.getDataFromLocalStorage(name);
+
+ const parsedCookie = fromCookie ? JSON.parse(fromCookie) : undefined;
+ const parsedLocalStorage = fromLocalStorage ? JSON.parse(fromLocalStorage) : undefined;
+
+ return parsedCookie || parsedLocalStorage || undefined;
+}
+
export const spec = {
code: BIDDER_CODE,
gvlid: 143,
@@ -225,6 +250,12 @@ export const spec = {
*/
buildRequests: (validBidRequests = [], bidderRequest = {}) => {
const bidRequests = _getBidRequests(validBidRequests);
+ let userIds;
+ try {
+ userIds = readFromAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY) || cnxIdsValues;
+ } catch (error) {
+ userIds = cnxIdsValues;
+ }
const requestPayload = {
ortb2: bidderRequest.ortb2,
@@ -232,6 +263,7 @@ export const spec = {
uspConsent: bidderRequest.uspConsent,
gppConsent: bidderRequest.gppConsent,
refererInfo: bidderRequest.refererInfo,
+ identityProviderData: userIds,
bidRequests,
};
@@ -308,6 +340,24 @@ export const spec = {
params['us_privacy'] = encodeURIComponent(uspConsent);
}
+ window.addEventListener('message', function handler(event) {
+ if (!event.data || event.origin !== 'https://cds.connatix.com') {
+ return;
+ }
+
+ if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) {
+ this.removeEventListener('message', handler);
+ event.stopImmediatePropagation();
+ }
+
+ if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) {
+ const response = event.data;
+ if (response.data) {
+ saveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY);
+ }
+ }
+ }, true)
+
const syncUrl = serverResponses[0].body.UserSyncEndpoint;
const queryParams = Object.keys(params).length > 0 ? formatQS(params) : '';
diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js
new file mode 100644
index 00000000000..7f1a8702a3b
--- /dev/null
+++ b/modules/contxtfulBidAdapter.js
@@ -0,0 +1,217 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import { _each, buildUrl, isStr, isEmptyStr, logInfo, logError } from '../src/utils.js';
+import { sendBeacon, ajax } from '../src/ajax.js';
+import { config as pbjsConfig } from '../src/config.js';
+import {
+ isBidRequestValid,
+ interpretResponse,
+ getUserSyncs as getUserSyncsLib,
+} from '../libraries/teqblazeUtils/bidderUtils.js';
+import {ortbConverter} from '../libraries/ortbConverter/converter.js';
+
+// Constants
+const BIDDER_CODE = 'contxtful';
+const BIDDER_ENDPOINT = 'prebid.receptivity.io';
+const MONITORING_ENDPOINT = 'monitoring.receptivity.io';
+const DEFAULT_NET_REVENUE = true;
+const DEFAULT_TTL = 300;
+const PREBID_VERSION = '$prebid.version$';
+
+// ORTB conversion
+const converter = ortbConverter({
+ context: {
+ netRevenue: DEFAULT_NET_REVENUE,
+ ttl: DEFAULT_TTL
+ },
+ imp(buildImp, bidRequest, context) {
+ let imp = buildImp(bidRequest, context);
+ return imp;
+ },
+ request(buildRequest, imps, bidderRequest, context) {
+ const reqData = buildRequest(imps, bidderRequest, context);
+ return reqData;
+ },
+ bidResponse(buildBidResponse, bid, context) {
+ const bidResponse = buildBidResponse(bid, context);
+ return bidResponse;
+ }
+});
+
+// Get Bid Floor
+const _getRequestBidFloor = (mediaTypes, paramsBidFloor, bid) => {
+ const bidMediaType = Object.keys(mediaTypes)[0] || 'banner';
+ const bidFloor = { floor: 0, currency: 'USD' };
+
+ if (typeof bid.getFloor === 'function') {
+ const { currency, floor } = bid.getFloor({
+ mediaType: bidMediaType,
+ size: '*'
+ });
+ floor && (bidFloor.floor = floor);
+ currency && (bidFloor.currency = currency);
+ } else if (paramsBidFloor) {
+ bidFloor.floor = paramsBidFloor
+ }
+
+ return bidFloor;
+}
+
+// Get Parameters from the config.
+const extractParameters = (config) => {
+ const version = config?.contxtful?.version;
+ if (!isStr(version) || isEmptyStr(version)) {
+ throw Error(`contxfulBidAdapter: contxtful.version should be a non-empty string`);
+ }
+
+ const customer = config?.contxtful?.customer;
+ if (!isStr(customer) || isEmptyStr(customer)) {
+ throw Error(`contxfulBidAdapter: contxtful.customer should be a non-empty string`);
+ }
+
+ return { version, customer };
+}
+
+// Construct the Payload towards the Bidding endpoint
+const buildRequests = (validBidRequests = [], bidderRequest = {}) => {
+ const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests});
+
+ const bidRequests = [];
+ _each(validBidRequests, bidRequest => {
+ const {
+ mediaTypes = {},
+ params = {},
+ } = bidRequest;
+ bidRequest.bidFloor = _getRequestBidFloor(mediaTypes, params.bidfloor, bidRequest);
+ bidRequests.push(bidRequest)
+ });
+ const config = pbjsConfig.getConfig();
+ config.pbjsVersion = PREBID_VERSION;
+ const {version, customer} = extractParameters(config)
+ const adapterUrl = buildUrl({
+ protocol: 'https',
+ host: BIDDER_ENDPOINT,
+ pathname: `/${version}/prebid/${customer}/bid`,
+ });
+
+ // https://docs.prebid.org/dev-docs/bidder-adaptor.html
+ let req = {
+ url: adapterUrl,
+ method: 'POST',
+ data: {
+ ortb2,
+ bidRequests,
+ bidderRequest,
+ config,
+ },
+ };
+
+ return req;
+};
+
+// Prepare a sync object compatible with getUserSyncs.
+const constructUrl = (userSyncsDefault, userSyncServer) => {
+ const urlSyncServer = (userSyncServer?.url ?? '').split('?');
+ const userSyncUrl = userSyncsDefault?.url || '';
+ const baseSyncUrl = urlSyncServer[0] || '';
+
+ let url = `${baseSyncUrl}${userSyncUrl}`;
+
+ if (urlSyncServer.length > 1) {
+ const urlParams = urlSyncServer[1];
+ url += url.includes('?') ? `&${urlParams}` : `?${urlParams}`;
+ }
+
+ return {
+ ...userSyncsDefault,
+ url,
+ };
+};
+
+// Returns the list of user synchronization objects.
+const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => {
+ // Get User Sync Defaults from pbjs lib
+ const userSyncsDefaultLib = getUserSyncsLib('')(syncOptions, null, gdprConsent, uspConsent, gppConsent);
+ const userSyncsDefault = userSyncsDefaultLib?.find(item => item.url !== undefined);
+
+ // Map Server Responses to User Syncs list
+ const serverSyncsData = serverResponses?.flatMap(response => response.body || [])
+ .map(data => data.syncs)
+ .find(syncs => Array.isArray(syncs) && syncs.length > 0) || [];
+ const userSyncs = serverSyncsData
+ .map(sync => constructUrl(userSyncsDefault, sync))
+ .filter(Boolean); // Filter out nulls
+ return userSyncs;
+};
+
+// Retrieve the sampling rate for events
+const getSamplingRate = (bidderConfig, eventType) => {
+ const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase());
+ return entry ? entry[1] : 0.001;
+};
+
+// Handles the logging of events
+const logEvent = (eventType, data, options = {}) => {
+ const {
+ samplingEnabled = false,
+ } = options;
+
+ try {
+ // Log event
+ logInfo(BIDDER_CODE, `[${eventType}] ${JSON.stringify(data)}`);
+
+ // Get Config
+ const bidderConfig = pbjsConfig.getConfig();
+ const {version, customer} = extractParameters(bidderConfig);
+
+ // Sampled monitoring
+ if (samplingEnabled) {
+ const shouldSampleDice = Math.random();
+ const samplingRate = getSamplingRate(bidderConfig, eventType);
+ if (shouldSampleDice >= samplingRate) {
+ return; // Don't sample
+ }
+ }
+
+ const payload = { type: eventType, data };
+ const eventUrl = buildUrl({
+ protocol: 'https',
+ host: MONITORING_ENDPOINT,
+ pathname: `/${version}/prebid/${customer}/log/${eventType}`,
+ });
+
+ // Try sending a beacon
+ if (sendBeacon(eventUrl, JSON.stringify(payload))) {
+ logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Beacon and payload: ${JSON.stringify(data)}`);
+ } else {
+ // Fallback to using ajax
+ ajax(eventUrl, null, JSON.stringify(payload), {
+ method: 'POST',
+ contentType: 'application/json',
+ withCredentials: true,
+ });
+ logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Ajax and payload: ${JSON.stringify(data)}`);
+ }
+ } catch (error) {
+ logError(BIDDER_CODE, `Failed to log event: ${eventType}`);
+ }
+};
+
+// Bidder public specification
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
+
+ isBidRequestValid,
+ buildRequests,
+ interpretResponse,
+ getUserSyncs,
+ onBidWon: function(bid, options) { logEvent('onBidWon', bid, { samplingEnabled: false, ...options }); },
+ onBidBillable: function(bid, options) { logEvent('onBidBillable', bid, { samplingEnabled: false, ...options }); },
+ onAdRenderSucceeded: function(bid, options) { logEvent('onAdRenderSucceeded', bid, { samplingEnabled: false, ...options }); },
+ onSetTargeting: function(bid, options) { },
+ onTimeout: function(timeoutData, options) { logEvent('onTimeout', timeoutData, { samplingEnabled: true, ...options }); },
+ onBidderError: function(args, options) { logEvent('onBidderError', args, { samplingEnabled: true, ...options }); },
+};
+
+registerBidder(spec);
diff --git a/modules/contxtfulBidAdapter.md b/modules/contxtfulBidAdapter.md
new file mode 100644
index 00000000000..87a78c38a85
--- /dev/null
+++ b/modules/contxtfulBidAdapter.md
@@ -0,0 +1,98 @@
+# Overview
+
+```
+Module Name: Contxtful Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: contact@contxtful.com
+```
+
+# Description
+
+The Contxtful Bidder Adapter supports all mediatypes and connects to demand sources for bids.
+
+# Configuration
+## Global Configuration
+Contxtful uses the global configuration to store params once instead of duplicating for each ad unit.
+Also, enabling user syncing greatly increases match rates and monetization.
+Be sure to call `pbjs.setConfig()` only once.
+
+```javascript
+pbjs.setConfig({
+ debug: false,
+ contxtful: {
+ customer: '', // Required
+ version: '', // Required
+ },
+ userSync: {
+ filterSettings: {
+ iframe: {
+ bidders: ['contxtful'],
+ filter: 'include'
+ }
+ }
+ }
+ // [...]
+});
+```
+
+## Bidder Setting
+Contxtful leverages local storage for user syncing.
+
+```javascript
+pbjs.bidderSettings = {
+ contxtful: {
+ storageAllowed: true
+ }
+}
+```
+
+# Example Ad-units configuration
+```javascript
+var adUnits = [
+ {
+ code: 'adunit1',
+ mediaTypes: {
+ banner: {
+ sizes: [ [300, 250], [320, 50] ],
+ }
+ },
+ bids: [{
+ bidder: 'contxtful',
+ }]
+ },
+ {
+ code: 'addunit2',
+ mediaTypes: {
+ video: {
+ playerSize: [ [640, 480] ],
+ context: 'instream',
+ minduration: 5,
+ maxduration: 60,
+ }
+ },
+ bids: [{
+ bidder: 'contxtful',
+ }]
+ },
+ {
+ code: 'addunit3',
+ mediaTypes: {
+ native: {
+ title: {
+ required: true
+ },
+ body: {
+ required: true
+ },
+ icon: {
+ required: true,
+ size: [64, 64]
+ }
+ }
+ },
+ bids: [{
+ bidder: 'contxtful',
+ }]
+ }
+];
+```
diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js
new file mode 100644
index 00000000000..c7cb304d22b
--- /dev/null
+++ b/modules/equativBidAdapter.js
@@ -0,0 +1,146 @@
+import { BANNER } from '../src/mediaTypes.js';
+import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getStorageManager } from '../src/storageManager.js';
+import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js';
+
+/**
+ * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
+ * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
+ */
+
+const BIDDER_CODE = 'equativ';
+const COOKIE_SYNC_ORIGIN = 'https://apps.smartadserver.com';
+const COOKIE_SYNC_URL = `${COOKIE_SYNC_ORIGIN}/diff/templates/asset/csync.html`;
+const PID_COOKIE_NAME = 'eqt_pid';
+
+export const storage = getStorageManager({ bidderCode: BIDDER_CODE });
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: 45,
+ supportedMediaTypes: [BANNER],
+
+ /**
+ * @param bidRequests
+ * @param bidderRequest
+ * @returns {ServerRequest[]}
+ */
+ buildRequests: (bidRequests, bidderRequest) => {
+ return {
+ data: converter.toORTB({ bidderRequest, bidRequests }),
+ method: 'POST',
+ url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169'
+ };
+ },
+
+ /**
+ * @param serverResponse
+ * @param bidRequest
+ * @returns {Bid[]}
+ */
+ interpretResponse: (serverResponse, bidRequest) =>
+ converter.fromORTB({
+ request: bidRequest.data,
+ response: serverResponse.body,
+ }),
+
+ /**
+ * @param bidRequest
+ * @returns {boolean}
+ */
+ isBidRequestValid: (bidRequest) => {
+ return !!(
+ deepAccess(bidRequest, 'params.networkId') ||
+ deepAccess(bidRequest, 'ortb2.site.publisher.id') ||
+ deepAccess(bidRequest, 'ortb2.app.publisher.id') ||
+ deepAccess(bidRequest, 'ortb2.dooh.publisher.id')
+ );
+ },
+
+ /**
+ * @param syncOptions
+ * @returns {{type: string, url: string}[]}
+ */
+ getUserSyncs: (syncOptions) => {
+ if (syncOptions.iframeEnabled) {
+ window.addEventListener('message', function handler(event) {
+ if (event.origin === COOKIE_SYNC_ORIGIN && event.data.pid) {
+ const exp = new Date();
+ exp.setTime(Date.now() + 31536000000); // in a year
+ storage.setCookie(PID_COOKIE_NAME, event.data.pid, exp.toUTCString());
+ this.removeEventListener('message', handler);
+ }
+ });
+
+ return [{ type: 'iframe', url: COOKIE_SYNC_URL }];
+ }
+
+ return [];
+ }
+};
+
+export const converter = ortbConverter({
+ context: {
+ netRevenue: true,
+ ttl: 300,
+ },
+
+ imp(buildImp, bidRequest, context) {
+ const imp = buildImp(bidRequest, context);
+ const { siteId, pageId, formatId } = bidRequest.params;
+
+ delete imp.dt;
+
+ imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest);
+ imp.secure = 1;
+ imp.tagid = bidRequest.adUnitCode;
+
+ if (siteId || pageId || formatId) {
+ const bidder = {};
+
+ if (siteId) {
+ bidder.siteId = siteId;
+ }
+
+ if (pageId) {
+ bidder.pageId = pageId;
+ }
+
+ if (formatId) {
+ bidder.formatId = formatId;
+ }
+
+ mergeDeep(imp, {
+ ext: { bidder },
+ });
+ }
+
+ return imp;
+ },
+
+ request(buildRequest, imps, bidderRequest, context) {
+ const bid = context.bidRequests[0];
+ const req = buildRequest(imps, bidderRequest, context);
+
+ if (deepAccess(bid, 'ortb2.site.publisher')) {
+ deepSetValue(req, 'site.publisher.id', bid.ortb2.site.publisher.id || bid.params.networkId);
+ } else if (deepAccess(bid, 'ortb2.app.publisher')) {
+ deepSetValue(req, 'app.publisher.id', bid.ortb2.app.publisher.id || bid.params.networkId);
+ } else if (deepAccess(bid, 'ortb2.dooh.publisher')) {
+ deepSetValue(req, 'dooh.publisher.id', bid.ortb2.dooh.publisher.id || bid.params.networkId);
+ } else {
+ deepSetValue(req, 'site.publisher.id', bid.params.networkId);
+ }
+
+ const pid = storage.getCookie(PID_COOKIE_NAME);
+ if (pid) {
+ deepSetValue(req, 'user.buyeruid', pid);
+ }
+
+ return req;
+ },
+});
+
+registerBidder(spec);
diff --git a/modules/equativBidAdapter.md b/modules/equativBidAdapter.md
new file mode 100644
index 00000000000..ceee6d19bdc
--- /dev/null
+++ b/modules/equativBidAdapter.md
@@ -0,0 +1,40 @@
+# Overview
+
+```
+Module Name: Equativ Bidder Adapter (beta)
+Module Type: Bidder Adapter
+Maintainer: support@equativ.com
+```
+
+# Description
+
+Connect to Equativ for bids.
+
+The Equativ adapter requires setup and approval from the Equativ team. Please reach out to your technical account manager for more information.
+
+# Test Parameters
+
+## Web or In-app
+```javascript
+var adUnits = [
+ {
+ code: '/589236/banner_1',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'equativ',
+ params: {
+ networkId: 13, // mandatory if no ortb2.(site or app).publisher.id set
+ siteId: 20743, // optional
+ pageId: 89653, // optional
+ formatId: 291, // optional
+ }
+ }
+ ]
+ }
+];
+```
\ No newline at end of file
diff --git a/modules/gameraRtdProvider.js b/modules/gameraRtdProvider.js
new file mode 100644
index 00000000000..96c4bac5f87
--- /dev/null
+++ b/modules/gameraRtdProvider.js
@@ -0,0 +1,107 @@
+import { submodule } from '../src/hook.js';
+import { getGlobal } from '../src/prebidGlobal.js';
+import {
+ isPlainObject,
+ logError,
+ mergeDeep,
+ deepClone,
+} from '../src/utils.js';
+
+const MODULE_NAME = 'gamera';
+const MODULE = `${MODULE_NAME}RtdProvider`;
+
+/**
+ * Initialize the Gamera RTD Module.
+ * @param {Object} config
+ * @param {Object} userConsent
+ * @returns {boolean}
+ */
+function init(config, userConsent) {
+ return true;
+}
+
+/**
+ * Modify bid request data before auction
+ * @param {Object} reqBidsConfigObj - The bid request config object
+ * @param {function} callback - Callback function to execute after data handling
+ * @param {Object} config - Module configuration
+ * @param {Object} userConsent - User consent data
+ */
+function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) {
+ // Check if window.gamera.getPrebidSegments is available
+ if (typeof window.gamera?.getPrebidSegments !== 'function') {
+ window.gamera = window.gamera || {};
+ window.gamera.cmd = window.gamera.cmd || [];
+ window.gamera.cmd.push(function () {
+ enrichAuction(reqBidsConfigObj, callback, config, userConsent);
+ });
+ return;
+ }
+
+ enrichAuction(reqBidsConfigObj, callback, config, userConsent);
+}
+
+/**
+ * Enriches the auction with user and content segments from Gamera's on-page script
+ * @param {Object} reqBidsConfigObj - The bid request config object
+ * @param {Function} callback - Callback function to execute after data handling
+ * @param {Object} config - Module configuration
+ * @param {Object} userConsent - User consent data
+ */
+function enrichAuction(reqBidsConfigObj, callback, config, userConsent) {
+ try {
+ /**
+ * @function external:"window.gamera".getPrebidSegments
+ * @description Retrieves user and content segments from Gamera's on-page script
+ * @param {Function|null} onSegmentsUpdateCallback - Callback for segment updates (not used here)
+ * @param {Object} config - Module configuration
+ * @param {Object} userConsent - User consent data
+ * @returns {Object|undefined} segments - The targeting segments object containing:
+ * @property {Object} [user] - User-level attributes to merge into ortb2.user
+ * @property {Object} [site] - Site-level attributes to merge into ortb2.site
+ * @property {Object.} [adUnits] - Ad unit specific attributes, keyed by adUnitCode,
+ * to merge into each ad unit's ortb2Imp
+ */
+ const segments = window.gamera.getPrebidSegments(null, deepClone(config || {}), deepClone(userConsent || {})) || {};
+
+ // Initialize ortb2Fragments and its nested objects
+ reqBidsConfigObj.ortb2Fragments = reqBidsConfigObj.ortb2Fragments || {};
+ reqBidsConfigObj.ortb2Fragments.global = reqBidsConfigObj.ortb2Fragments.global || {};
+
+ // Add user-level data
+ if (segments.user && isPlainObject(segments.user)) {
+ reqBidsConfigObj.ortb2Fragments.global.user = reqBidsConfigObj.ortb2Fragments.global.user || {};
+ mergeDeep(reqBidsConfigObj.ortb2Fragments.global.user, segments.user);
+ }
+
+ // Add site-level data
+ if (segments.site && isPlainObject(segments.site)) {
+ reqBidsConfigObj.ortb2Fragments.global.site = reqBidsConfigObj.ortb2Fragments.global.site || {};
+ mergeDeep(reqBidsConfigObj.ortb2Fragments.global.site, segments.site);
+ }
+
+ // Add adUnit-level data
+ const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits || [];
+ adUnits.forEach(adUnit => {
+ const gameraData = segments.adUnits && segments.adUnits[adUnit.code];
+ if (!gameraData || !isPlainObject(gameraData)) {
+ return;
+ }
+
+ adUnit.ortb2Imp = adUnit.ortb2Imp || {};
+ mergeDeep(adUnit.ortb2Imp, gameraData);
+ });
+ } catch (error) {
+ logError(MODULE, 'Error getting segments:', error);
+ }
+
+ callback();
+}
+
+export const subModuleObj = {
+ name: MODULE_NAME,
+ init: init,
+ getBidRequestData: getBidRequestData,
+};
+
+submodule('realTimeData', subModuleObj);
diff --git a/modules/gameraRtdProvider.md b/modules/gameraRtdProvider.md
new file mode 100644
index 00000000000..44260b88d1f
--- /dev/null
+++ b/modules/gameraRtdProvider.md
@@ -0,0 +1,51 @@
+# Overview
+
+Module Name: Gamera Rtd Provider
+Module Type: Rtd Provider
+Maintainer: aleksa@gamera.ai
+
+# Description
+
+RTD provider for Gamera.ai that enriches bid requests with real-time data, by populating the [First Party Data](https://docs.prebid.org/features/firstPartyData.html) attributes.
+The module integrates with Gamera's AI-powered audience segmentation system to provide enhanced bidding capabilities.
+The Gamera RTD Provider works in conjunction with the Gamera script, which must be available on the page for the module to enrich bid requests. To learn more about the Gamera script, please visit the [Gamera website](https://gamera.ai/).
+
+ORTB2 enrichments that gameraRtdProvider can provide:
+ * `ortb2.site`
+ * `ortb2.user`
+ * `AdUnit.ortb2Imp`
+
+# Integration
+
+## Build
+
+Include the Gamera RTD module in your Prebid.js build:
+
+```bash
+gulp build --modules=rtdModule,gameraRtdProvider
+```
+
+## Configuration
+
+Configure the module in your Prebid.js configuration:
+
+```javascript
+pbjs.setConfig({
+ realTimeData: {
+ dataProviders: [{
+ name: 'gamera',
+ params: {
+ // Optional configuration parameters
+ }
+ }]
+ }
+});
+```
+
+### Configuration Parameters
+
+The module currently supports basic initialization without required parameters. Future versions may include additional configuration options.
+
+## Support
+
+For more information or support, please contact gareth@gamera.ai.
diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js
index bdb8e634de6..ccd63bc0184 100644
--- a/modules/hadronIdSystem.js
+++ b/modules/hadronIdSystem.js
@@ -19,9 +19,9 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM
* @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
*/
-const LOG_PREFIX = '[hadronIdSystem]';
-const HADRONID_LOCAL_NAME = 'auHadronId';
-const MODULE_NAME = 'hadronId';
+export const MODULE_NAME = 'hadronId';
+const LOG_PREFIX = `[${MODULE_NAME}System]`;
+export const LS_TAM_KEY = 'auHadronId';
const AU_GVLID = 561;
const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid';
@@ -68,11 +68,9 @@ export const hadronIdSubmodule = {
* @returns {Object}
*/
decode(value) {
- const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME);
- if (isStr(hadronId)) {
- return {hadronId: hadronId};
+ return {
+ hadronId: isStr(value) ? value : value.hasOwnProperty('id') ? value.id[MODULE_NAME] : value[MODULE_NAME]
}
- return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined;
},
/**
* performs action to obtain id and return a value in the callback's response argument
@@ -81,14 +79,19 @@ export const hadronIdSubmodule = {
* @returns {IdResponse|undefined}
*/
getId(config) {
+ logInfo(LOG_PREFIX, `getId is called`, config);
if (!isPlainObject(config.params)) {
config.params = {};
}
- const partnerId = config.params.partnerId | 0;
- let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME);
- if (isStr(hadronId)) {
- return {id: {hadronId}};
+ let hadronId = '';
+ // at this point hadronId was not found by prebid, let check if it is in the webpage by other ways
+ hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY);
+ if (isStr(hadronId) && hadronId.length > 0) {
+ logInfo(LOG_PREFIX, `${LS_TAM_KEY} found in localStorage = ${hadronId}`)
+ // return {callback: function(cb) { cb(hadronId) }};
+ return {id: hadronId}
}
+ const partnerId = config.params.partnerId | 0;
const resp = function (callback) {
let responseObj = {};
const callbacks = {
@@ -98,11 +101,13 @@ export const hadronIdSubmodule = {
responseObj = JSON.parse(response);
} catch (error) {
logError(error);
+ callback();
}
logInfo(LOG_PREFIX, `Response from backend is ${response}`, responseObj);
- hadronId = responseObj['hadronId'];
- storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId);
- responseObj = {id: {hadronId}};
+ if (isPlainObject(responseObj) && responseObj.hasOwnProperty(MODULE_NAME)) {
+ hadronId = responseObj[MODULE_NAME];
+ }
+ responseObj = hadronId; // {id: {hadronId: hadronId}};
}
callback(responseObj);
},
@@ -137,7 +142,7 @@ export const hadronIdSubmodule = {
url += `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`;
}
- logInfo(LOG_PREFIX, `hadronId not found in storage, calling home (${url})`);
+ logInfo(LOG_PREFIX, `${MODULE_NAME} not found, calling home (${url})`);
ajax(url, callbacks, undefined, {method: 'GET'});
};
diff --git a/modules/hadronIdSystem.md b/modules/hadronIdSystem.md
index 212030cbcd9..f58cd46ef61 100644
--- a/modules/hadronIdSystem.md
+++ b/modules/hadronIdSystem.md
@@ -12,7 +12,7 @@ pbjs.setConfig({
userIds: [{
name: 'hadronId',
params: {
- partnerId: 1234 // change it to the Partner ID you'll get from Audigent
+ partnerId: 1234 // change it to the Partner ID you got from Audigent
},
storage: {
name: 'hadronId',
@@ -25,14 +25,13 @@ pbjs.setConfig({
## Parameter Descriptions for the `usersync` Configuration Section
The below parameters apply only to the HadronID User ID Module integration.
-| Param under usersync.userIds[] | Scope | Type | Description | Example |
-|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
-| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` |
-| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | |
-| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` |
-| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"hadronid"` |
-| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` |
-| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` |
+| Param under usersync.userIds[] | Scope | Type | Description | Example |
+|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|
+| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` |
+| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | |
+| storage.type | Required | String | This is where the the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` |
+| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. The recommended value is `hadronId`. | `"auHadronId"` |
+| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. The recommended value is 14 days. | `14` |
+| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"hadronId": "0aRSTUAackg79ijgd8e8j6kah9ed9j6hdfgb6cl00volopxo00npzjmmb"}` |
| params | Optional | Object | Used to store params for the id system |
-| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` |
- |
+| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` |
diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js
index f9a2eaed9c9..d85f049c2de 100644
--- a/modules/hadronRtdProvider.js
+++ b/modules/hadronRtdProvider.js
@@ -10,7 +10,7 @@ import {config} from '../src/config.js';
import {getGlobal} from '../src/prebidGlobal.js';
import {getStorageManager} from '../src/storageManager.js';
import {submodule} from '../src/hook.js';
-import {isFn, isStr, isArray, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js';
+import {isFn, isStr, isArray, isEmpty, deepEqual, isPlainObject, logError, logInfo} from '../src/utils.js';
import {loadExternalScript} from '../src/adloader.js';
import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
@@ -18,14 +18,14 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
* @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
*/
-const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: ';
+const LOG_PREFIX = '[HadronRtdProvider] ';
const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'hadron';
const AU_GVLID = 561;
const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid';
-const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd';
-export const HADRONID_LOCAL_NAME = 'auHadronId';
-export const RTD_LOCAL_NAME = 'auHadronRtd';
+const HADRON_SEGMENT_URL = 'https://prebid-rtd.audigent.workers.dev'; // https://id.hadron.ad.gt/api/v1/rtd';
+const LS_TAM_KEY = 'auHadronId';
+const RTD_LOCAL_NAME = 'auHadronRtd';
export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME});
/**
@@ -166,14 +166,29 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
const userIds = {};
- let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME);
- if (isStr(hadronId)) {
- if (typeof getGlobal().refreshUserIds === 'function') {
- (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'});
+ const allUserIds = getGlobal().getUserIds();
+ if (allUserIds.hasOwnProperty('hadronId')) {
+ userIds['hadronId'] = allUserIds.hadronId;
+ logInfo(LOG_PREFIX, 'hadronId user module found', allUserIds.hadronId);
+ } else {
+ let hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY);
+ if (isStr(hadronId) && hadronId.length > 0) {
+ userIds['hadronId'] = hadronId;
+ logInfo(LOG_PREFIX, 'hadronId TAM found', hadronId);
}
- userIds.hadronId = hadronId;
+ }
+ if (!isEmpty(userIds)) {
+ // if (typeof getGlobal().refreshUserIds === 'function') {
+ // (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'});
+ // }
+ // userIds.hadronId = hadronId;
getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds);
} else {
+ // the hadronId was not found, reasons can be:
+ // 1) prebid wasn't compiled with hadronIdSystem
+ // 2) prebid wasn't configured to use hadronId user module
+ // 3) all previous and no other hadronId snippet configured in the page
+ // then need to load hadron.js from the CDN
window.pubHadronCb = (hadronId) => {
userIds.hadronId = hadronId;
getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds);
@@ -184,8 +199,8 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds),
`partner_id=${partnerId}&_it=prebid`
);
- loadExternalScript(scriptUrl, MODULE_TYPE_RTD, 'hadron', () => {
- logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl);
+ loadExternalScript(scriptUrl, SUBMODULE_NAME, () => {
+ logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl);
})
}
}
@@ -198,7 +213,7 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) {
* @param {Object} userConsent
* @param {Object} userIds
*/
-export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) {
+function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds) {
let reqParams = {};
if (isPlainObject(rtdConfig)) {
@@ -223,7 +238,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent,
onDone();
}
} catch (err) {
- logError('unable to parse audigent segment data');
+ logError(LOG_PREFIX, 'unable to parse audigent segment data');
onDone();
}
} else if (req.status === 204) {
@@ -233,7 +248,7 @@ export function getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent,
},
error: function () {
onDone();
- logError('unable to get audigent segment data');
+ logError(LOG_PREFIX, 'unable to get audigent segment data');
}
},
JSON.stringify({'userIds': userIds, 'config': reqParams}),
diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js
index c72d21d08b4..90bc0c78212 100644
--- a/modules/holidBidAdapter.js
+++ b/modules/holidBidAdapter.js
@@ -93,13 +93,13 @@ export const spec = {
}
const bidders = getBidders(serverResponse)
-
- if (optionsType.iframeEnabled && bidders) {
+ // note this only does the iframe sync when gdpr consent object exists to match previous behavior (generate error on gdprconsent not existing)
+ if (optionsType.iframeEnabled && bidders && gdprConsent) {
const queryParams = []
queryParams.push('bidders=' + bidders)
- queryParams.push('gdpr=' + +gdprConsent.gdprApplies)
- queryParams.push('gdpr_consent=' + gdprConsent.consentString)
+ queryParams.push('gdpr=' + +gdprConsent?.gdprApplies)
+ queryParams.push('gdpr_consent=' + gdprConsent?.consentString)
queryParams.push('usp_consent=' + (uspConsent || ''))
let strQueryParams = queryParams.join('&')
diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js
index 5d78f8eff6c..79522f4a089 100644
--- a/modules/impactifyBidAdapter.js
+++ b/modules/impactifyBidAdapter.js
@@ -168,11 +168,6 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) {
}
deepSetValue(request, 'regs.ext.gdpr', gdprApplies);
- if (bidderRequest.uspConsent) {
- deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent);
- this.syncStore.uspConsent = bidderRequest.uspConsent;
- }
-
if (GET_CONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1);
if (bidderRequest.uspConsent) {
diff --git a/modules/invamiaBidAdapter.js b/modules/invamiaBidAdapter.js
index 96af163ca4f..7f9a40e1473 100644
--- a/modules/invamiaBidAdapter.js
+++ b/modules/invamiaBidAdapter.js
@@ -1,5 +1,6 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {BANNER} from '../src/mediaTypes.js';
+import { buildBannerRequests, interpretBannerResponse } from '../libraries/biddoInvamiaUtils/index.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -12,86 +13,15 @@ const ENDPOINT_URL = 'https://ad.invamia.com/delivery/impress';
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER],
- /**
- * Determines whether or not the given bid request is valid.
- *
- * @param {BidRequest} bidRequest The bid request params to validate.
- * @return boolean True if this is a valid bid request, and false otherwise.
- */
- isBidRequestValid: function(bidRequest) {
+ isBidRequestValid: function (bidRequest) {
return !!bidRequest.params.zoneId;
},
- /**
- * Make a server request from the list of BidRequests.
- *
- * @param {Array} validBidRequests an array of bid requests
- * @return ServerRequest Info describing the request to the server.
- */
- buildRequests: function(validBidRequests) {
- let serverRequests = [];
-
- validBidRequests.forEach(bidRequest => {
- const sizes = bidRequest.mediaTypes.banner.sizes;
-
- sizes.forEach(([width, height]) => {
- bidRequest.params.requestedSizes = [width, height];
-
- const payload = {
- ctype: 'div',
- pzoneid: bidRequest.params.zoneId,
- width,
- height,
- };
-
- const payloadString = Object.keys(payload).map(k => k + '=' + encodeURIComponent(payload[k])).join('&');
-
- serverRequests.push({
- method: 'GET',
- url: ENDPOINT_URL,
- data: payloadString,
- bidderRequest: bidRequest,
- });
- });
- });
-
- return serverRequests;
+ buildRequests: function (validBidRequests) {
+ return validBidRequests.flatMap((bidRequest) => buildBannerRequests(bidRequest, ENDPOINT_URL));
},
- /**
- * Unpack the response from the server into a list of bids.
- *
- * @param {ServerResponse} serverResponse A successful response from the server.
- * @param {BidRequest} bidderRequest A matched bid request for this response.
- * @return Array An array of bids which were nested inside the server.
- */
- interpretResponse: function(serverResponse, {bidderRequest}) {
- const response = serverResponse.body;
- const bidResponses = [];
-
- if (response && response.template && response.template.html) {
- const {bidId} = bidderRequest;
- const [width, height] = bidderRequest.params.requestedSizes;
-
- const bidResponse = {
- requestId: bidId,
- cpm: response.hb.cpm,
- creativeId: response.banner.hash,
- currency: 'USD',
- netRevenue: response.hb.netRevenue,
- ttl: 600,
- ad: response.template.html,
- mediaType: 'banner',
- meta: {
- advertiserDomains: response.hb.adomains || [],
- },
- width,
- height,
- };
-
- bidResponses.push(bidResponse);
- }
-
- return bidResponses;
+ interpretResponse: function (serverResponse, { bidderRequest }) {
+ return interpretBannerResponse(serverResponse, bidderRequest);
},
-}
+};
registerBidder(spec);
diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js
index 8429228d7af..46ad7dfec71 100644
--- a/modules/kargoBidAdapter.js
+++ b/modules/kargoBidAdapter.js
@@ -1,4 +1,4 @@
-import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel, logError } from '../src/utils.js';
+import { _each, isEmpty, buildUrl, deepAccess, pick, logError } from '../src/utils.js';
import { config } from '../src/config.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
@@ -452,21 +452,20 @@ function getRequestCount() {
}
function sendTimeoutData(auctionId, auctionTimeout) {
- let params = {
- aid: auctionId,
- ato: auctionTimeout
- };
-
- try {
- let timeoutRequestUrl = buildUrl({
- protocol: 'https',
- hostname: BIDDER.HOST,
- pathname: BIDDER.TIMEOUT_ENDPOINT,
- search: params
- });
+ const params = { aid: auctionId, ato: auctionTimeout };
+ const timeoutRequestUrl = buildUrl({
+ protocol: 'https',
+ hostname: BIDDER.HOST,
+ pathname: BIDDER.TIMEOUT_ENDPOINT,
+ search: params,
+ });
- triggerPixel(timeoutRequestUrl);
- } catch (e) {}
+ fetch(timeoutRequestUrl, {
+ method: 'GET',
+ keepalive: true,
+ }).catch((e) => {
+ logError('Kargo: sendTimeoutData/fetch threw an error: ', e);
+ });
}
function getImpression(bid) {
diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js
index bba8f988be6..0069bf052ba 100644
--- a/modules/missenaBidAdapter.js
+++ b/modules/missenaBidAdapter.js
@@ -11,6 +11,7 @@ import { config } from '../src/config.js';
import { BANNER } from '../src/mediaTypes.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
+import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -70,18 +71,8 @@ function toPayload(bidRequest, bidderRequest) {
}
const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL;
- if (bidRequest.params.test) {
- payload.test = bidRequest.params.test;
- }
- if (bidRequest.params.placement) {
- payload.placement = bidRequest.params.placement;
- }
- if (bidRequest.params.formats) {
- payload.formats = bidRequest.params.formats;
- }
- if (bidRequest.params.isInternal) {
- payload.is_internal = bidRequest.params.isInternal;
- }
+ payload.params = bidRequest.params;
+
if (bidRequest.ortb2?.device?.ext?.cdep) {
payload.cdep = bidRequest.ortb2?.device?.ext?.cdep;
}
@@ -91,8 +82,10 @@ function toPayload(bidRequest, bidderRequest) {
const bidFloor = getFloor(bidRequest);
payload.floor = bidFloor?.floor;
payload.floor_currency = bidFloor?.currency;
- payload.currency = config.getConfig('currency.adServerCurrency') || 'EUR';
+ payload.currency = config.getConfig('currency.adServerCurrency');
payload.schain = bidRequest.schain;
+ payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0;
+ payload.autoplay = isAutoplayEnabled() === true ? 1 : 0;
return {
method: 'POST',
diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js
index c9da876b292..c945619c667 100644
--- a/modules/nativoBidAdapter.js
+++ b/modules/nativoBidAdapter.js
@@ -1,6 +1,6 @@
import { deepAccess, isEmpty } from '../src/utils.js'
import { registerBidder } from '../src/adapters/bidderFactory.js'
-import { BANNER } from '../src/mediaTypes.js'
+import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'
import { getGlobal } from '../src/prebidGlobal.js'
import { ortbConverter } from '../libraries/ortbConverter/converter.js'
@@ -8,14 +8,16 @@ const converter = ortbConverter({
context: {
// `netRevenue` and `ttl` are required properties of bid responses - provide a default for them
netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false
- ttl: 30 // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp)
+ ttl: 30, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp)
},
imp(buildImp, bidRequest, context) {
- const imp = buildImp(bidRequest, context);
+ const imp = buildImp(bidRequest, context)
imp.tagid = bidRequest.adUnitCode
- return imp;
- }
-});
+ if (imp.ext) imp.ext.placementId = bidRequest.params.placementId
+
+ return imp
+ },
+})
const BIDDER_CODE = 'nativo'
const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid'
@@ -24,12 +26,22 @@ const GVLID = 263
const TIME_TO_LIVE = 360
-const SUPPORTED_AD_TYPES = [BANNER]
+const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]
const FLOOR_PRICE_CURRENCY = 'USD'
const PRICE_FLOOR_WILDCARD = '*'
const localPbjsRef = getGlobal()
+function getMediaType(accessObj) {
+ if (deepAccess(accessObj, 'mediaTypes.video')) {
+ return VIDEO
+ } else if (deepAccess(accessObj, 'mediaTypes.native')) {
+ return NATIVE
+ } else {
+ return BANNER
+ }
+}
+
/**
* Keep track of bid data by keys
* @returns {Object} - Map of bid data that can be referenced by multiple keys
@@ -122,8 +134,7 @@ export const spec = {
*/
isBidRequestValid: function (bid) {
// We don't need any specific parameters to make a bid request
- // If not parameters are supplied just verify it's the correct bidder code
- if (!bid.params) return bid.bidder === BIDDER_CODE
+ if (!bid.params) return true
// Check if any supplied parameters are invalid
const hasInvalidParameters = Object.keys(bid.params).some((key) => {
@@ -150,7 +161,10 @@ export const spec = {
*/
buildRequests: function (validBidRequests, bidderRequest) {
// Get OpenRTB Data
- const openRTBData = converter.toORTB({bidRequests: validBidRequests, bidderRequest})
+ const openRTBData = converter.toORTB({
+ bidRequests: validBidRequests,
+ bidderRequest,
+ })
const openRTBDataString = JSON.stringify(openRTBData)
const requestData = new RequestData()
@@ -201,7 +215,8 @@ export const spec = {
let params = [
// Prebid version
{
- key: 'ntv_pbv', value: localPbjsRef.version
+ key: 'ntv_pbv',
+ value: localPbjsRef.version,
},
// Prebid request id
{ key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId },
@@ -278,19 +293,31 @@ export const spec = {
})
}
+ // Add GPP params
+ if (bidderRequest.gppConsent) {
+ params.unshift({
+ key: 'ntv_gpp_consent',
+ value: bidderRequest.gppConsent.gppString,
+ })
+ }
+
// Add USP params
if (bidderRequest.uspConsent) {
// Put on the beginning of the qs param array
params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent })
}
- const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)]
+ const qsParamStrings = [
+ requestData.getRequestDataQueryString(),
+ arrayToQS(params),
+ ]
const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings)
let serverRequest = {
method: 'POST',
url: requestUrl,
data: openRTBDataString,
+ bidderRequest: bidderRequest,
}
return serverRequest
@@ -320,9 +347,10 @@ export const spec = {
// Step through and grab pertinent data
let bidResponse, adUnit
- seatbids.forEach((seatbid) => {
+ seatbids.forEach((seatbid, i) => {
seatbid.bid.forEach((bid) => {
adUnit = this.getAdUnitData(body.id, bid)
+
bidResponse = {
requestId: adUnit.bidId,
cpm: bid.price,
@@ -337,10 +365,18 @@ export const spec = {
meta: {
advertiserDomains: bid.adomain,
},
+ mediaType: getMediaType(request.bidderRequest.bids[i]),
}
if (bid.ext) extData[bid.id] = bid.ext
-
+ if (bidResponse.mediaType === VIDEO) {
+ bidResponse.vastUrl = bid.adm
+ }
+ if (bidResponse.mediaType === NATIVE) {
+ bidResponse.native = {
+ ortb: JSON.parse(bidResponse.ad),
+ }
+ }
bidResponses.push(bidResponse)
})
})
@@ -414,23 +450,27 @@ export const spec = {
typeof response.body === 'string'
? JSON.parse(response.body)
: response.body
- } catch (err) { return }
+ } catch (err) {
+ return
+ }
// Make sure we have valid content
if (!body || !body.seatbid || body.seatbid.length === 0) return
body.seatbid.forEach((seatbid) => {
// Grab the syncs for each seatbid
- seatbid.syncUrls.forEach((sync) => {
- if (types[sync.type]) {
- if (sync.url.trim() !== '') {
- syncs.push({
- type: sync.type,
- url: sync.url.replace('{GDPR_params}', params),
- })
+ if (seatbid.syncUrls) {
+ seatbid.syncUrls.forEach((sync) => {
+ if (types[sync.type]) {
+ if (sync.url.trim() !== '') {
+ syncs.push({
+ type: sync.type,
+ url: sync.url.replace('{GDPR_params}', params),
+ })
+ }
}
- }
- })
+ })
+ }
})
})
@@ -491,7 +531,9 @@ export class RequestData {
getRequestDataQueryString() {
if (this.bidRequestDataSources.length == 0) return
- const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '')
+ const queryParams = this.bidRequestDataSources
+ .map((dataSource) => dataSource.getRequestQueryString())
+ .filter((queryString) => queryString !== '')
return queryParams.join('&')
}
}
@@ -500,8 +542,10 @@ export class BidRequestDataSource {
constructor() {
this.type = 'BidRequestDataSource'
}
- processBidRequestData(bidRequest, bidderRequest) { }
- getRequestQueryString() { return '' }
+ processBidRequestData(bidRequest, bidderRequest) {}
+ getRequestQueryString() {
+ return ''
+ }
}
export class UserEIDs extends BidRequestDataSource {
@@ -540,7 +584,7 @@ QueryStringParam.prototype.toString = function () {
export function encodeToBase64(value) {
try {
return btoa(JSON.stringify(value))
- } catch (err) { }
+ } catch (err) {}
}
export function parseFloorPriceData(bidRequest) {
@@ -708,9 +752,13 @@ function getLargestSize(sizes, method = area) {
* Build the final request url
*/
export function buildRequestUrl(baseUrl, qsParamStringArray = []) {
- if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl
+ if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) {
+ return baseUrl
+ }
- const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '')
+ const nonEmptyQSParamStrings = qsParamStringArray.filter(
+ (qsParamString) => qsParamString.trim() !== ''
+ )
if (nonEmptyQSParamStrings.length === 0) return baseUrl
@@ -752,7 +800,7 @@ export function getPageUrlFromBidRequest(bidRequest) {
try {
const url = new URL(paramPageUrl)
return url.href
- } catch (err) { }
+ } catch (err) {}
}
export function hasProtocol(url) {
diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md
index f83fb45b52e..515d87af28e 100644
--- a/modules/nativoBidAdapter.md
+++ b/modules/nativoBidAdapter.md
@@ -16,24 +16,91 @@ gulp serve --modules=nativoBidAdapter
# Test Parameters
+## Banner
+
+```js
+var adUnits = [
+ {
+ code: 'div-gpt-ad-1460505748561-0',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600],
+ ],
+ },
+ },
+ // Replace this object to test a new Adapter!
+ bids: [
+ {
+ bidder: 'nativo',
+ params: {
+ url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
+ },
+ },
+ ],
+ },
+]
```
+
+## Video
+
+```js
var adUnits = [
- {
- code: 'div-gpt-ad-1460505748561-0',
- mediaTypes: {
- banner: {
- sizes: [[300, 250], [300,600]],
- }
- },
- // Replace this object to test a new Adapter!
- bids: [{
- bidder: 'nativo',
- params: {
- url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html'
- }
- }]
-
- }
- ];
+ {
+ code: 'ntvPlaceholder-1',
+ mediaTypes: {
+ video: {
+ mimes: ['video/mp4'],
+ protocols: [2, 3, 5, 6],
+ playbackmethod: [1, 2],
+ skip: 1,
+ skipafter: 5,
+ },
+ },
+ video: {
+ divId: 'player',
+ },
+ bids: [
+ {
+ bidder: 'nativo',
+ params: {
+ url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
+ },
+ },
+ ],
+ },
+]
+```
+
+## Native
+```js
+var adUnits = [
+ {
+ code: '/416881364/prebid-native-test-unit',
+ sizes: [[300, 250]],
+ mediaTypes: {
+ native: {
+ title: {
+ required: true,
+ },
+ image: {
+ required: true,
+ },
+ sponsoredBy: {
+ required: true,
+ },
+ },
+ },
+ bids: [
+ {
+ bidder: 'nativo',
+ params: {
+ url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html',
+ },
+ },
+ ],
+ },
+]
```
diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js
index 1d37c237b70..8c794fdaf03 100644
--- a/modules/nextMillenniumBidAdapter.js
+++ b/modules/nextMillenniumBidAdapter.js
@@ -11,6 +11,7 @@ import {
parseUrl,
triggerPixel,
} from '../src/utils.js';
+
import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js';
import { EVENTS } from '../src/constants.js';
@@ -20,7 +21,7 @@ import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {getRefererInfo} from '../src/refererDetection.js';
-const NM_VERSION = '4.0.1';
+const NM_VERSION = '4.2.0';
const PBJS_VERSION = 'v$prebid.version$';
const GVLID = 1060;
const BIDDER_CODE = 'nextMillennium';
@@ -30,6 +31,7 @@ const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdp
const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric';
const TIME_TO_LIVE = 360;
const DEFAULT_CURRENCY = 'USD';
+const DEFAULT_TMAX = 1500;
const VIDEO_PARAMS_DEFAULT = {
api: undefined,
@@ -65,6 +67,10 @@ const ALLOWED_ORTB2_PARAMETERS = [
'site.keywords',
'site.content.keywords',
'user.keywords',
+ 'bcat',
+ 'badv',
+ 'wlang',
+ 'wlangb',
];
export const spec = {
@@ -74,7 +80,8 @@ export const spec = {
isBidRequestValid: function(bid) {
return !!(
- (bid.params.placement_id && isStr(bid.params.placement_id)) || (bid.params.group_id && isStr(bid.params.group_id))
+ (bid.params.placement_id && isStr(bid.params.placement_id)) ||
+ (bid.params.group_id && isStr(bid.params.group_id))
);
},
@@ -84,15 +91,19 @@ export const spec = {
window.nmmRefreshCounts = window.nmmRefreshCounts || {};
const site = getSiteObj();
const device = getDeviceObj();
+ const source = getSourceObj(validBidRequests, bidderRequest);
+ const tmax = deepAccess(bidderRequest, 'timeout') || DEFAULT_TMAX;
const postBody = {
id: bidderRequest?.bidderRequestId,
+ tmax,
ext: {
next_mil_imps: [],
},
device,
site,
+ source,
imp: [],
};
@@ -334,10 +345,10 @@ export function setConsentStrings(postBody = {}, bidderRequest) {
if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) gppConsent = bidderRequest?.ortb2?.regs;
if (gdprConsent || uspConsent || gppConsent) {
- postBody.regs = { ext: {} };
+ postBody.regs = {};
if (uspConsent) {
- postBody.regs.ext.us_privacy = uspConsent;
+ postBody.regs.us_privacy = uspConsent;
};
if (gppConsent) {
@@ -347,15 +358,19 @@ export function setConsentStrings(postBody = {}, bidderRequest) {
if (gdprConsent) {
if (typeof gdprConsent.gdprApplies !== 'undefined') {
- postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0;
+ postBody.regs.gdpr = gdprConsent.gdprApplies ? 1 : 0;
};
if (typeof gdprConsent.consentString !== 'undefined') {
postBody.user = {
- ext: { consent: gdprConsent.consentString },
+ consent: gdprConsent.consentString,
};
};
};
+
+ if (typeof bidderRequest?.ortb2?.regs?.coppa === 'number') {
+ postBody.regs.coppa = bidderRequest?.ortb2?.regs?.coppa;
+ };
};
};
@@ -364,6 +379,8 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) {
const value = deepAccess(ortb2, parameter);
if (value) deepSetValue(postBody, parameter, value);
}
+
+ if (postBody.wlang) delete postBody.wlangb
}
export function setEids(postBody = {}, bids = []) {
@@ -481,6 +498,19 @@ function getDeviceObj() {
};
}
+export function getSourceObj(validBidRequests, bidderRequest) {
+ const schain = validBidRequests?.[0]?.schain ||
+ (bidderRequest?.ortb2?.source && (bidderRequest?.ortb2?.source?.schain || bidderRequest?.ortb2?.source?.ext?.schain));
+
+ if (!schain) return;
+
+ const source = {
+ schain,
+ };
+
+ return source;
+}
+
function getSua() {
let {brands, mobile, platform} = (window?.navigator?.userAgentData || {});
if (!(brands && platform)) return undefined;
diff --git a/modules/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js
index 69032fb151b..9a76b24f223 100644
--- a/modules/nexx360BidAdapter.js
+++ b/modules/nexx360BidAdapter.js
@@ -34,6 +34,7 @@ const ALIASES = [
{ code: 'prjads' },
{ code: 'pubtech' },
{ code: '1accord', gvlid: 965 },
+ { code: 'easybid', gvlid: 1068 },
];
export const storage = getStorageManager({
diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md
index 532d48418b6..5935b568a13 100644
--- a/modules/nexx360BidAdapter.md
+++ b/modules/nexx360BidAdapter.md
@@ -30,8 +30,7 @@ var adUnits = [
bids: [{
bidder: 'nexx360',
params: {
- account: '1067',
- tagId: 'luvxjvgn'
+ tagId: 'testnexx'
}
}]
},
@@ -51,8 +50,7 @@ var adUnits = [
bids: [{
bidder: 'nexx360',
params: {
- account: '1067',
- tagId: 'luvxjvgn'
+ tagId: 'testnexx'
}
}]
};
diff --git a/modules/permutiveIdentityManagerIdSystem.js b/modules/permutiveIdentityManagerIdSystem.js
new file mode 100644
index 00000000000..5dc12d44edb
--- /dev/null
+++ b/modules/permutiveIdentityManagerIdSystem.js
@@ -0,0 +1,151 @@
+import {MODULE_TYPE_UID} from '../src/activities/modules.js'
+import {submodule} from '../src/hook.js'
+import {getStorageManager} from '../src/storageManager.js'
+import {prefixLog, safeJSONParse} from '../src/utils.js'
+/**
+ * @typedef {import('../modules/userId/index.js').Submodule} Submodule
+ * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig
+ * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData
+ * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse
+ */
+
+const MODULE_NAME = 'permutiveIdentityManagerId'
+const PERMUTIVE_ID_DATA_STORAGE_KEY = 'permutive-prebid-id'
+
+const ID5_DOMAIN = 'id5-sync.com'
+const LIVERAMP_DOMAIN = 'liveramp.com'
+const UID_DOMAIN = 'uidapi.com'
+
+const PRIMARY_IDS = ['id5id', 'idl_env', 'uid2']
+
+export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME})
+
+const logger = prefixLog('[PermutiveID]')
+
+const readFromSdkLocalStorage = () => {
+ const data = safeJSONParse(storage.getDataFromLocalStorage(PERMUTIVE_ID_DATA_STORAGE_KEY))
+ const id = {}
+ if (data && typeof data === 'object' && 'providers' in data && typeof data.providers === 'object') {
+ const now = Date.now()
+ for (const [idName, value] of Object.entries(data.providers)) {
+ if (PRIMARY_IDS.includes(idName) && value.userId) {
+ if (!value.expiryTime || value.expiryTime > now) {
+ id[idName] = value.userId
+ }
+ }
+ }
+ }
+ return id
+}
+
+/**
+ * Catch and log errors
+ * @param {function} fn - Function to safely evaluate
+ */
+function makeSafe (fn) {
+ try {
+ return fn()
+ } catch (e) {
+ logger.logError(e)
+ }
+}
+
+const waitAndRetrieveFromSdk = (timeoutMs) =>
+ new Promise(
+ resolve => {
+ const fallback = setTimeout(() => {
+ logger.logInfo('timeout expired waiting for SDK - attempting read from local storage again')
+ resolve(readFromSdkLocalStorage())
+ }, timeoutMs)
+ return window?.permutive?.ready(() => makeSafe(() => {
+ logger.logInfo('Permutive SDK is ready')
+ const onReady = makeSafe(() => window.permutive.addons.identity_manager.prebid.onReady)
+ if (typeof onReady === 'function') {
+ onReady((ids) => {
+ logger.logInfo('Permutive SDK has provided ids')
+ resolve(ids)
+ clearTimeout(fallback)
+ })
+ } else {
+ logger.logError('Permutive SDK initialised but identity manager prebid api not present')
+ }
+ }))
+ }
+ )
+
+/** @type {Submodule} */
+export const permutiveIdentityManagerIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function decode
+ * @param {(Object|string)} value
+ * @param {SubmoduleConfig|undefined} config
+ * @returns {(Object|undefined)}
+ */
+ decode(value, config) {
+ return value
+ },
+
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function getId
+ * @param {SubmoduleConfig} submoduleConfig
+ * @param {ConsentData} consentData
+ * @param {(Object|undefined)} cacheIdObj
+ * @returns {IdResponse|undefined}
+ */
+ getId(submoduleConfig, consentData, cacheIdObj) {
+ const id = readFromSdkLocalStorage()
+ if (Object.entries(id).length > 0) {
+ logger.logInfo('found id in sdk storage')
+ return { id }
+ } else if ('params' in submoduleConfig && submoduleConfig.params.ajaxTimeout) {
+ logger.logInfo('failed to find id in sdk storage - waiting for sdk')
+ // Is ajaxTimeout an appropriate timeout to use here?
+ return { callback: (done) => waitAndRetrieveFromSdk(submoduleConfig.params.ajaxTimeout).then(done) }
+ } else {
+ logger.logInfo('failed to find id in sdk storage and no wait time specified')
+ }
+ },
+
+ primaryIds: PRIMARY_IDS,
+
+ eids: {
+ 'id5id': {
+ getValue: function (data) {
+ return data.uid
+ },
+ source: ID5_DOMAIN,
+ atype: 1,
+ getUidExt: function (data) {
+ if (data.ext) {
+ return data.ext
+ }
+ }
+ },
+ 'idl_env': {
+ source: LIVERAMP_DOMAIN,
+ atype: 3,
+ },
+ 'uid2': {
+ source: UID_DOMAIN,
+ atype: 3,
+ getValue: function(data) {
+ return data.id
+ },
+ getUidExt: function(data) {
+ if (data.ext) {
+ return data.ext
+ }
+ }
+ }
+ }
+}
+
+submodule('userId', permutiveIdentityManagerIdSubmodule)
diff --git a/modules/permutiveIdentityManagerIdSystem.md b/modules/permutiveIdentityManagerIdSystem.md
new file mode 100644
index 00000000000..ae249803d11
--- /dev/null
+++ b/modules/permutiveIdentityManagerIdSystem.md
@@ -0,0 +1,58 @@
+# Permutive Identity Manager
+
+This module supports [Permutive](https://permutive.com/) customers in using Permutive's Identity Manager functionality.
+
+To use this Prebid.js module it is assumed that the site includes Permutive's SDK, with Identity Manager configuration
+enabled. See Permutive's user documentation for more information on Identity Manager.
+
+## Building Prebid.js with Permutive Identity Manager Support
+
+Prebid.js must be built with the `permutiveIdentityManagerIdSystem` module in order for Permutive's Identity Manager to be able to
+activate relevant user identities to Prebid.
+
+To build Prebid.js with the `permutiveIdentityManagerIdSystem` module included:
+
+```
+gulp build --modules=userId,permutiveIdentityManagerIdSystem
+```
+
+## Prebid configuration
+
+There is minimal configuration required to be set on Prebid.js, since the bulk of the behaviour is managed through
+Permutive's dashboard and SDK.
+
+It is recommended to keep the Prebid.js caching for this module short, since the mechanism by which Permutive's SDK
+communicates with Prebid.js is effectively a local cache anyway.
+
+```
+pbjs.setConfig({
+ ...
+ userSync: {
+ userIds: [
+ {
+ name: 'permutiveIdentityManagerId',
+ params: {
+ ajaxTimeout: 90
+ },
+ storage: {
+ type: 'html5',
+ name: 'permutiveIdentityManagerId',
+ refreshInSeconds: 5
+ }
+ }
+ ],
+ auctionDelay: 100
+ },
+ ...
+});
+```
+
+### ajaxTimeout
+
+By default this module will read IDs provided by the Permutive SDK from local storage when requested by prebid, and if
+nothing is found, will not provide any identities. If a timeout is provided via the `ajaxTimeout` parameter, it will
+instead wait for up to the specified number of milliseconds for Permutive's SDK to become available, and will retrieve
+identities from the SDK directly if/when this happens.
+
+This value should be set to a value smaller than the `auctionDelay` set on the `userSync` configuration object, since
+there is no point waiting longer than this as the auction will already have been triggered.
\ No newline at end of file
diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js
index 6f6ab82eca2..8c244dca040 100644
--- a/modules/precisoBidAdapter.js
+++ b/modules/precisoBidAdapter.js
@@ -1,15 +1,14 @@
import { logInfo } from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { BANNER } from '../src/mediaTypes.js';
+import { BANNER, NATIVE } from '../src/mediaTypes.js';
import { getStorageManager } from '../src/storageManager.js';
import { MODULE_TYPE_UID } from '../src/activities/modules.js';
-import { buildRequests, interpretResponse, onBidWon } from '../libraries/precisoUtils/bidUtils.js';
+import { buildBidResponse, buildRequests, onBidWon } from '../libraries/precisoUtils/bidUtils.js';
import { buildUserSyncs } from '../libraries/precisoUtils/bidUtilsCommon.js';
const BIDDER__CODE = 'preciso';
export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE });
-// export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE });
-const SUPPORTED_MEDIA_TYPES = [BANNER];
+const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE];
const GVLID = 874;
let precisoId = 'NA';
let sharedId = 'NA';
@@ -37,17 +36,10 @@ export const spec = {
return Boolean(bid.bidId && bid.params && bid.params.publisherId && precisoBid);
},
buildRequests: buildRequests(endpoint),
- interpretResponse,
+ interpretResponse: buildBidResponse,
onBidWon,
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
- const isSpec = syncOptions.spec;
- if (!Object.is(isSpec, true)) {
- let syncId = storage.getCookie('_sharedid');
- syncEndpoint = syncEndpoint + 'id=' + syncId;
- } else {
- syncEndpoint = syncEndpoint + 'id=NA';
- }
-
+ syncEndpoint = syncEndpoint + 'id=' + sharedId;
return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint);
}
};
@@ -56,17 +48,16 @@ registerBidder(spec);
async function getapi(url) {
try {
- // Storing response
const response = await fetch(url);
-
- // Storing data in form of JSON
var data = await response.json();
const dataMap = new Map(Object.entries(data));
const uuidValue = dataMap.get('UUID');
if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) {
- storage.setDataInLocalStorage('_pre|id', uuidValue);
+ if (storage.localStorageIsEnabled()) {
+ storage.setDataInLocalStorage('_pre|id', uuidValue);
+ }
}
return data;
} catch (error) {
diff --git a/modules/precisoBidAdapter.md b/modules/precisoBidAdapter.md
index b1fb0d062da..97521f195d8 100644
--- a/modules/precisoBidAdapter.md
+++ b/modules/precisoBidAdapter.md
@@ -14,9 +14,9 @@ Module that connects to preciso' demand sources
| Name | Scope | Description | Example |
| :------------ | :------- | :------------------------ | :------------------- |
-| `region` | required (for prebid.js) | region | "prebid-eu" |
-| `publisherId` | required (for prebid-server) | partner ID | "1901" |
-| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" |
+| `region` | optional (for prebid.js) | 3 letter country code | "USA" |
+| `publisherId` | required (for prebid-server) | partner ID provided by preciso | PreTest_0001 |
+| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native | "banner" |
# Test Parameters
```
@@ -25,7 +25,35 @@ Module that connects to preciso' demand sources
{
code: 'placementId_0',
mediaTypes: {
- native: {}
+ native: {
+ ortb: {
+ assets: [
+ {
+ id: 3,
+ required: 1,
+ img: {
+ type: 3,
+ w: 300,
+ h: 250
+ }
+ },
+ {
+ id: 1,
+ required: 1,
+ title: {
+ len: 800
+ }
+ },
+ {
+ id: 4,
+ required: 0,
+ data: {
+ type: 1
+ }
+ }
+ ]
+ }
+ }
},
bids: [
{
@@ -33,7 +61,7 @@ Module that connects to preciso' demand sources
params: {
host: 'prebid',
publisherId: '0',
- region: 'prebid-eu',
+ region: 'USA',
traffic: 'native'
}
}
@@ -53,32 +81,11 @@ Module that connects to preciso' demand sources
params: {
host: 'prebid',
publisherId: '0',
- region: 'prebid-eu',
+ region: 'USA',
traffic: 'banner'
}
}
]
- },
- // Will return test vast xml. All video params are stored under placement in publishers UI
- {
- code: 'placementId_0',
- mediaTypes: {
- video: {
- playerSize: [640, 480],
- context: 'instream'
- }
- },
- bids: [
- {
- bidder: 'preciso',
- params: {
- host: 'prebid',
- publisherId: '0',
- region: 'prebid-eu',
- traffic: 'video'
- }
- }
- ]
}
];
```
\ No newline at end of file
diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index 405e419dac8..a03091f1f4a 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -1297,6 +1297,11 @@ export const spec = {
}
}
+ // if present, merge device object from ortb2 into `payload.device`
+ if (bidderRequest?.ortb2?.device) {
+ mergeDeep(payload.device, bidderRequest.ortb2.device);
+ }
+
if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) {
const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat;
_allowedIabCategoriesValidation(payload, acatParams);
diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js
index aaf9b983c78..3505f64789a 100644
--- a/modules/qortexRtdProvider.js
+++ b/modules/qortexRtdProvider.js
@@ -6,10 +6,9 @@ import * as events from '../src/events.js';
import { EVENTS } from '../src/constants.js';
import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
-let requestUrl;
-let bidderArray;
-let impressionIds;
-let currentSiteContext;
+const DEFAULT_API_URL = 'https://demand.qortex.ai';
+
+const qortexSessionInfo = {}
/**
* Init if module configuration is valid
@@ -22,11 +21,34 @@ function init (config) {
return false;
} else {
initializeModuleData(config);
+ if (config?.params?.enableBidEnrichment) {
+ logMessage('Requesting Qortex group configuration')
+ getGroupConfig()
+ .then(groupConfig => {
+ logMessage(['Received response for qortex group config', groupConfig])
+ if (groupConfig?.active === true && groupConfig?.prebidBidEnrichment === true) {
+ setGroupConfigData(groupConfig);
+ initializeBidEnrichment();
+ } else {
+ logWarn('Group config is not configured for qortex bid enrichment')
+ setGroupConfigData(groupConfig);
+ }
+ })
+ .catch((e) => {
+ const errorStatus = e.message;
+ logWarn('Returned error status code: ' + errorStatus);
+ if (errorStatus == 404) {
+ logWarn('No Group Config found');
+ }
+ });
+ } else {
+ logWarn('Bid Enrichment Function has been disabled in module configuration')
+ }
+ if (config?.params?.tagConfig) {
+ loadScriptTag(config)
+ }
+ return true;
}
- if (config?.params?.tagConfig) {
- loadScriptTag(config)
- }
- return true;
}
/**
@@ -35,64 +57,167 @@ function init (config) {
* @param {Function} callback Called on completion
*/
function getBidRequestData (reqBidsConfig, callback) {
- if (reqBidsConfig?.adUnits?.length > 0) {
+ if (reqBidsConfig?.adUnits?.length > 0 && shouldAllowBidEnrichment()) {
getContext()
.then(contextData => {
setContextData(contextData)
addContextToRequests(reqBidsConfig)
callback();
})
- .catch((e) => {
- logWarn(e?.message);
+ .catch(e => {
+ logWarn('Returned error status code: ' + e.message);
callback();
});
} else {
- logWarn('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfig))
+ logWarn('Module function is paused due to configuration \n Module Config: ' + JSON.stringify(reqBidsConfig) + `\n Group Config: ${JSON.stringify(qortexSessionInfo.groupConfig) ?? 'NO GROUP CONFIG'}`)
callback();
}
}
+/**
+ * Processess auction end events for Qortex reporting
+ * @param {Object} data Auction end object
+ */
+function onAuctionEndEvent (data, config, t) {
+ if (shouldAllowBidEnrichment()) {
+ sendAnalyticsEvent('AUCTION', 'AUCTION_END', attachContextAnalytics(data))
+ .then(result => {
+ logMessage('Qortex analytics event sent')
+ })
+ .catch(e => logWarn(e.message))
+ }
+}
+
/**
* determines whether to send a request to context api and does so if necessary
* @returns {Promise} ortb Content object
*/
export function getContext () {
- if (!currentSiteContext) {
+ if (!qortexSessionInfo.currentSiteContext) {
+ const pageUrlObject = { pageUrl: qortexSessionInfo.indexData?.pageUrl ?? '' }
logMessage('Requesting new context data');
return new Promise((resolve, reject) => {
const callbacks = {
success(text, data) {
- const result = data.status === 200 ? JSON.parse(data.response)?.content : null;
+ const responseStatus = data.status;
+ let result = null;
+ if (responseStatus === 200) {
+ qortexSessionInfo.pageAnalysisData.contextRetrieved = true
+ result = JSON.parse(data.response)?.content;
+ }
resolve(result);
},
- error(error) {
- reject(new Error(error));
+ error(e, x) {
+ const responseStatus = x.status;
+ reject(new Error(responseStatus));
}
}
- ajax(requestUrl, callbacks)
+ ajax(qortexSessionInfo.contextUrl, callbacks, JSON.stringify(pageUrlObject), {contentType: 'application/json'})
})
} else {
logMessage('Adding Content object from existing context data');
- return new Promise(resolve => resolve(currentSiteContext));
+ return new Promise((resolve, reject) => resolve(qortexSessionInfo.currentSiteContext));
+ }
+}
+
+/**
+ * Requests Qortex group configuration using group id
+ * @returns {Promise} Qortex group configuration
+ */
+export function getGroupConfig () {
+ return new Promise((resolve, reject) => {
+ const callbacks = {
+ success(text, data) {
+ const result = data.status === 200 ? JSON.parse(data.response) : null;
+ resolve(result);
+ },
+ error(e, x) {
+ reject(new Error(x.status));
+ }
+ }
+ ajax(qortexSessionInfo.groupConfigUrl, callbacks)
+ })
+}
+
+/**
+ * Sends analytics events to Qortex
+ * @returns {Promise}
+ */
+export function sendAnalyticsEvent(eventType, subType, data) {
+ if (qortexSessionInfo.analyticsUrl !== null) {
+ if (shouldSendAnalytics(data)) {
+ const analtyicsEventObject = generateAnalyticsEventObject(eventType, subType, data)
+ logMessage('Sending qortex analytics event');
+ return new Promise((resolve, reject) => {
+ const callbacks = {
+ success() {
+ resolve();
+ },
+ error(e, x) {
+ reject(new Error('Returned error status code: ' + x.status));
+ }
+ }
+ ajax(qortexSessionInfo.analyticsUrl, callbacks, JSON.stringify(analtyicsEventObject), {contentType: 'application/json'})
+ })
+ } else {
+ return new Promise((resolve, reject) => reject(new Error('Current request did not meet analytics percentage threshold, cancelling sending event')));
+ }
+ } else {
+ return new Promise((resolve, reject) => reject(new Error('Analytics host not initialized')));
+ }
+}
+
+/**
+ * Creates analytics object for Qortex
+ * @returns {Object} analytics object
+ */
+export function generateAnalyticsEventObject(eventType, subType, data) {
+ return {
+ sessionId: qortexSessionInfo.sessionId,
+ groupId: qortexSessionInfo.groupId,
+ eventType: eventType,
+ subType: subType,
+ eventOriginSource: 'RTD',
+ data: data
+ }
+}
+
+/**
+ * Determines API host for Qortex
+ * @param qortexUrlBase api url from config or default
+ * @returns {string} Qortex analytics host url
+ */
+export function generateAnalyticsHostUrl(qortexUrlBase) {
+ if (qortexUrlBase === DEFAULT_API_URL) {
+ return 'https://events.qortex.ai/api/v1/player-event';
+ } else if (qortexUrlBase.includes('stg-demand')) {
+ return 'https://stg-events.qortex.ai/api/v1/player-event';
+ } else {
+ return 'https://dev-events.qortex.ai/api/v1/player-event';
}
}
/**
* Updates bidder configs with the response from Qortex context services
* @param {Object} reqBidsConfig Bid request configuration object
- * @param {string[]} bidders Bidders specified in module's configuration
*/
export function addContextToRequests (reqBidsConfig) {
- if (currentSiteContext === null) {
+ if (qortexSessionInfo.currentSiteContext === null) {
logWarn('No context data received at this time');
} else {
- const fragment = { site: {content: currentSiteContext} }
- if (bidderArray?.length > 0) {
- bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment}))
- } else if (!bidderArray) {
- mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment);
+ if (checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidBidEnrichmentPercentage)) {
+ const fragment = { site: {content: qortexSessionInfo.currentSiteContext} }
+ if (qortexSessionInfo.bidderArray?.length > 0) {
+ qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment}))
+ saveContextAdded(reqBidsConfig);
+ } else if (!qortexSessionInfo.bidderArray) {
+ mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment);
+ saveContextAdded(reqBidsConfig);
+ } else {
+ logWarn('Config contains an empty bidders array, unable to determine which bids to enrich');
+ }
} else {
- logWarn('Config contains an empty bidders array, unable to determine which bids to enrich');
+ saveContextAdded(reqBidsConfig, true);
}
}
}
@@ -122,45 +247,134 @@ export function loadScriptTag(config) {
switch (e?.detail?.type) {
case 'qx-impression':
const {uid} = e.detail;
- if (!uid || impressionIds.has(uid)) {
- logWarn(`received invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`)
+ if (!uid || qortexSessionInfo.impressionIds.has(uid)) {
+ logWarn(`Received invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`)
return;
} else {
- logMessage('received billable event: qx-impression')
- impressionIds.add(uid)
+ logMessage('Received billable event: qx-impression')
+ qortexSessionInfo.impressionIds.add(uid)
billableEvent.transactionId = e.detail.uid;
events.emit(EVENTS.BILLABLE_EVENT, billableEvent);
break;
}
default:
- logWarn(`received invalid billable event: ${e.detail?.type}`)
+ logWarn(`Received invalid billable event: ${e.detail?.type}`)
}
})
loadExternalScript(src, MODULE_TYPE_RTD, code, undefined, undefined, attr);
}
+export function initializeBidEnrichment() {
+ if (shouldAllowBidEnrichment()) {
+ getContext()
+ .then(contextData => {
+ if (qortexSessionInfo.pageAnalysisData.contextRetrieved) {
+ logMessage('Contextual record Received from Qortex API')
+ setContextData(contextData)
+ } else {
+ logWarn('Contexual record is not yet complete at this time')
+ }
+ })
+ .catch((e) => {
+ logWarn('Returned error status code: ' + e.message)
+ })
+ }
+}
/**
* Helper function to set initial values when they are obtained by init
* @param {Object} config module config obtained during init
*/
export function initializeModuleData(config) {
- const DEFAULT_API_URL = 'https://demand.qortex.ai';
- const {apiUrl, groupId, bidders} = config.params;
- requestUrl = `${apiUrl || DEFAULT_API_URL}/api/v1/analyze/${groupId}/prebid`;
- bidderArray = bidders;
- impressionIds = new Set();
- currentSiteContext = null;
+ const {apiUrl, groupId, bidders, enableBidEnrichment} = config.params;
+ const qortexUrlBase = apiUrl || DEFAULT_API_URL;
+ const windowUrl = window.top.location.host;
+ qortexSessionInfo.bidEnrichmentDisabled = enableBidEnrichment !== null ? !enableBidEnrichment : true;
+ qortexSessionInfo.bidderArray = bidders;
+ qortexSessionInfo.impressionIds = new Set();
+ qortexSessionInfo.currentSiteContext = null;
+ qortexSessionInfo.pageAnalysisData = {
+ contextRetrieved: false,
+ contextAdded: {}
+ };
+ qortexSessionInfo.sessionId = generateSessionId();
+ qortexSessionInfo.groupId = groupId;
+ qortexSessionInfo.groupConfigUrl = `${qortexUrlBase}/api/v1/prebid/group/configs/${groupId}/${windowUrl}`;
+ qortexSessionInfo.contextUrl = `${qortexUrlBase}/api/v1/prebid/${groupId}/page/lookup`;
+ qortexSessionInfo.analyticsUrl = generateAnalyticsHostUrl(qortexUrlBase);
+ return qortexSessionInfo;
+}
+
+export function saveContextAdded(reqBids, skipped = false) {
+ const id = reqBids.auctionId;
+ const contextBidders = qortexSessionInfo.bidderArray ?? Array.from(new Set(reqBids.adUnits.flatMap(adunit => adunit.bids.map(bid => bid.bidder))))
+ qortexSessionInfo.pageAnalysisData.contextAdded[id] = {
+ bidders: contextBidders,
+ contextSkipped: skipped
+ };
}
export function setContextData(value) {
- currentSiteContext = value
+ qortexSessionInfo.currentSiteContext = value
+}
+
+export function setGroupConfigData(value) {
+ qortexSessionInfo.groupConfig = value
+}
+
+export function getContextAddedEntry (id) {
+ return qortexSessionInfo?.pageAnalysisData?.contextAdded[id]
+}
+
+function generateSessionId() {
+ const randomInt = window.crypto.getRandomValues(new Uint32Array(1));
+ const currentDateTime = Math.floor(Date.now() / 1000);
+ return 'QX' + randomInt.toString() + 'X' + currentDateTime.toString()
+}
+
+function attachContextAnalytics (data) {
+ const contextAddedEntry = getContextAddedEntry(data.auctionId);
+ if (contextAddedEntry) {
+ data.qortexContext = qortexSessionInfo.currentSiteContext ?? {};
+ data.qortexContextBidders = contextAddedEntry?.bidders;
+ data.qortexContextSkipped = contextAddedEntry?.contextSkipped;
+ return data;
+ } else {
+ logMessage(`Auction ${data.auctionId} did not interact with qortex bid enrichment`)
+ return null;
+ }
+}
+
+function checkPercentageOutcome(percentageValue) {
+ const analyticsPercentage = percentageValue ?? 0;
+ const randomInt = Math.random().toFixed(5) * 100;
+ return analyticsPercentage > randomInt;
+}
+
+function shouldSendAnalytics(data) {
+ if (data) {
+ return checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidReportingPercentage)
+ } else {
+ return false;
+ }
+}
+
+function shouldAllowBidEnrichment() {
+ if (qortexSessionInfo.bidEnrichmentDisabled) {
+ logWarn('Bid enrichment disabled at prebid config')
+ return false;
+ } else if (!qortexSessionInfo.groupConfig?.prebidBidEnrichment) {
+ logWarn('Bid enrichment disabled at group config')
+ return false;
+ }
+ return true
}
export const qortexSubmodule = {
name: 'qortex',
init,
- getBidRequestData
+ getBidRequestData,
+ onAuctionEndEvent
}
submodule('realTimeData', qortexSubmodule);
diff --git a/modules/qortexRtdProvider.md b/modules/qortexRtdProvider.md
index 312696068cd..b9a08eb817a 100644
--- a/modules/qortexRtdProvider.md
+++ b/modules/qortexRtdProvider.md
@@ -12,7 +12,7 @@ Maintainer: mannese@qortex.ai
The Qortex RTD module appends contextual segments to the bidding object based on the content of a page using the Qortex API.
-Upon load, the Qortex context API will analyze the bidder page (video, text, image, etc.) and will return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26). The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data.
+If the `Qortex Group Id` and module parameters provided during configuration is active, the Qortex context API will attempt to generate and return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26) using indexed data from provided page content. The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data.
## Build
@@ -40,13 +40,8 @@ pbjs.setConfig({
params: {
groupId: 'ABC123', //required
bidders: ['qortex', 'adapter2'], //optional (see below)
- tagConfig: { // optional, please reach out to your account manager for configuration reccommendation
- videoContainer: 'string',
- htmlContainer: 'string',
- attachToTop: 'string',
- esm6Mod: 'string',
- continuousLoad: 'string'
- }
+ enableBidEnrichment: true, //optional (see below)
+ tagConfig: { } // optional, please reach out to your account manager for configuration reccommendation
}
}]
}
@@ -63,7 +58,11 @@ pbjs.setConfig({
- If this parameter is omitted, the RTD module will default to updating `ortb2.site.content` on *all* bid adapters being used on the page
+#### `enableBidEnrichment` - optional
+- This optional parameter allows a publisher to opt-in to the features of the RTD module that use our API to enrich bids with first party data for contextuality. Enabling this feature will allow this module to interact with the Qortex AI contextuality server for indexing and analysis. Please use caution when adding this module to pages that may contain personal user data or proprietary information.
+
#### `tagConfig` - optional
- This optional parameter is an object containing the config settings that could be usedto initialize the Qortex integration on your page. A preconfigured object for this step will be provided to you by the Qortex team.
-- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal.
\ No newline at end of file
+- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal.
+
\ No newline at end of file
diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js
index 4f8c8c1c550..bcc076f1781 100644
--- a/modules/rtbhouseBidAdapter.js
+++ b/modules/rtbhouseBidAdapter.js
@@ -5,6 +5,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
import {includes} from '../src/polyfill.js';
import {convertOrtbRequestToProprietaryNative} from '../src/native.js';
import {config} from '../src/config.js';
+import { interpretNativeBid, OPENRTB } from '../libraries/precisoUtils/bidNativeUtils.js';
const BIDDER_CODE = 'rtbhouse';
const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia'];
@@ -24,29 +25,6 @@ const DSA_ATTRIBUTES = [
{ name: 'datatopub', 'min': 0, 'max': 2 }
];
-// Codes defined by OpenRTB Native Ads 1.1 specification
-export const OPENRTB = {
- NATIVE: {
- IMAGE_TYPE: {
- ICON: 1,
- MAIN: 3,
- },
- ASSET_ID: {
- TITLE: 1,
- IMAGE: 2,
- ICON: 3,
- BODY: 4,
- SPONSORED: 5,
- CTA: 6
- },
- DATA_ASSET_TYPE: {
- SPONSORED: 1,
- DESC: 2,
- CTA_TEXT: 12,
- },
- }
-};
-
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
@@ -489,71 +467,6 @@ function interpretBannerBid(serverBid) {
}
}
-/**
- * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3
- * @returns {object} Prebid native bidObject
- */
-function interpretNativeBid(serverBid) {
- return {
- requestId: serverBid.impid,
- mediaType: NATIVE,
- cpm: serverBid.price,
- creativeId: serverBid.adid,
- width: 1,
- height: 1,
- ttl: TTL,
- meta: {
- advertiserDomains: serverBid.adomain
- },
- netRevenue: true,
- currency: 'USD',
- native: interpretNativeAd(serverBid.adm),
- }
-}
-
-/**
- * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1
- * @returns {object} Prebid bidObject.native
- */
-function interpretNativeAd(adm) {
- const native = JSON.parse(adm).native;
- const result = {
- clickUrl: encodeURI(native.link.url),
- impressionTrackers: native.imptrackers
- };
- native.assets.forEach(asset => {
- switch (asset.id) {
- case OPENRTB.NATIVE.ASSET_ID.TITLE:
- result.title = asset.title.text;
- break;
- case OPENRTB.NATIVE.ASSET_ID.IMAGE:
- result.image = {
- url: encodeURI(asset.img.url),
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- case OPENRTB.NATIVE.ASSET_ID.ICON:
- result.icon = {
- url: encodeURI(asset.img.url),
- width: asset.img.w,
- height: asset.img.h
- };
- break;
- case OPENRTB.NATIVE.ASSET_ID.BODY:
- result.body = asset.data.value;
- break;
- case OPENRTB.NATIVE.ASSET_ID.SPONSORED:
- result.sponsoredBy = asset.data.value;
- break;
- case OPENRTB.NATIVE.ASSET_ID.CTA:
- result.cta = asset.data.value;
- break;
- }
- });
- return result;
-}
-
/**
* https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md
*
diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index 16239b6d1c3..c62876b9605 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -42,6 +42,8 @@ config.getConfig('rubicon', config => {
const GVLID = 52;
+let impIdMap = {};
+
var sizeMap = {
1: '468x60',
2: '728x90',
@@ -148,7 +150,13 @@ var sizeMap = {
580: '505x656',
622: '192x160',
632: '1200x450',
- 634: '340x450'
+ 634: '340x450',
+ 680: '970x570',
+ 682: '300x240',
+ 684: '970x550',
+ 686: '300x210',
+ 688: '300x220',
+ 690: '970x170'
};
_each(sizeMap, (item, key) => sizeMap[item] = key);
@@ -212,6 +220,9 @@ export const converter = ortbConverter({
setBidFloors(bidRequest, imp);
+ // ensure unique imp IDs for twin adunits
+ imp.id = impIdMap[imp.id] ? imp.id + impIdMap[imp.id]++ : (impIdMap[imp.id] = 2, imp.id);
+
return imp;
},
bidResponse(buildBidResponse, bid, context) {
@@ -302,6 +313,7 @@ export const spec = {
if (filteredRequests && filteredRequests.length) {
const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest});
+ resetImpIdMap();
filteredHttpRequest.push({
method: 'POST',
@@ -705,6 +717,10 @@ export const spec = {
bid.meta.advertiserDomains = Array.isArray(ad.adomain) ? ad.adomain : [ad.adomain];
}
+ if (ad.emulated_format) {
+ bid.meta.mediaType = ad.emulated_format;
+ }
+
if (ad.creative_type === VIDEO) {
bid.width = associatedBidRequest.params.video.playerWidth;
bid.height = associatedBidRequest.params.video.playerHeight;
@@ -1152,6 +1168,7 @@ function bidType(bid, log = false) {
}
export const resetRubiConf = () => rubiConf = {};
+export const resetImpIdMap = () => impIdMap = {};
export function masSizeOrdering(sizes) {
const MAS_SIZE_PRIORITY = [15, 2, 9];
diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js
index 026f89c5faa..afac0a88567 100644
--- a/modules/showheroes-bsBidAdapter.js
+++ b/modules/showheroes-bsBidAdapter.js
@@ -1,200 +1,114 @@
import {
deepAccess,
- getWindowTop,
+ deepSetValue,
triggerPixel,
- logInfo,
- logError, getBidIdParameter
+ isFn,
+ logInfo
} from '../src/utils.js';
-import { config } from '../src/config.js';
import { Renderer } from '../src/Renderer.js';
+import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { VIDEO, BANNER } from '../src/mediaTypes.js';
-/**
- * See https://github.com/prebid/Prebid.js/pull/4222 for details on linting exception
- * ShowHeroes only imports after winning a bid
- * Also see https://github.com/prebid/Prebid.js/issues/11656
- */
-// eslint-disable-next-line no-restricted-imports
-import { loadExternalScript } from '../src/adloader.js';
-import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js';
+import { VIDEO } from '../src/mediaTypes.js';
-const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid';
-const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid';
-const VIRALIZE_ENDPOINT = 'https://ads.viralize.tv/prebid-sh/';
-const PROD_PUBLISHER_TAG = 'https://static.showheroes.com/publishertag.js';
-const STAGE_PUBLISHER_TAG = 'https://pubtag.stage.showheroes.com/publishertag.js';
-const PROD_VL = 'https://video-library.showheroes.com';
-const STAGE_VL = 'https://video-library.stage.showheroes.com';
+const ENDPOINT = 'https://ads.viralize.tv/openrtb2/auction/';
const BIDDER_CODE = 'showheroes-bs';
const TTL = 300;
-function getEnvURLs(isStage) {
- return {
- pubTag: isStage ? STAGE_PUBLISHER_TAG : PROD_PUBLISHER_TAG,
- vlHost: isStage ? STAGE_VL : PROD_VL
- }
-}
-
-const GVLID = 111;
-
-export const spec = {
- code: BIDDER_CODE,
- gvlid: GVLID,
- aliases: ['showheroesBs'],
- supportedMediaTypes: [VIDEO, BANNER],
- isBidRequestValid: function(bid) {
- return !!bid.params.playerId || !!bid.params.unitId;
+const converter = ortbConverter({
+ context: {
+ netRevenue: true,
+ ttl: TTL,
+ currency: 'EUR',
+ mediaType: VIDEO,
},
- buildRequests: function(validBidRequests, bidderRequest) {
- let adUnits = [];
- const pageURL = validBidRequests[0].params.contentPageUrl ||
- bidderRequest.refererInfo.canonicalUrl ||
- deepAccess(window, 'location.href');
- const isStage = !!validBidRequests[0].params.stage;
- const isViralize = !!validBidRequests[0].params.unitId;
- const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream';
- const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender');
- const isNodeRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe');
- const isNativeRender = deepAccess(validBidRequests[0], 'renderer');
- const outstreamOptions = deepAccess(validBidRequests[0], 'params.outstreamOptions');
- const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender));
- const defaultSchain = validBidRequests[0].schain || {};
-
- const consentData = bidderRequest.gdprConsent || {};
- const uspConsent = bidderRequest.uspConsent || '';
- const gdprConsent = {
- apiVersion: consentData.apiVersion || 2,
- gdprApplies: consentData.gdprApplies || 0,
- consentString: consentData.consentString || '',
+ imp(buildImp, bidRequest, context) {
+ const imp = buildImp(bidRequest, context);
+ const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context');
+ deepSetValue(imp, 'video.ext.context', videoContext);
+ imp.ext = imp.ext || {};
+ imp.ext.params = bidRequest.params;
+ imp.ext.adUnitCode = bidRequest.adUnitCode;
+
+ if (!imp.displaymanager) {
+ imp.displaymanager = 'Prebid.js';
+ imp.displaymanagerver = '$prebid.version$'; // prebid version
}
- validBidRequests.forEach((bid) => {
- const videoSizes = getVideoSizes(bid);
- const bannerSizes = getBannerSizes(bid);
- const vpaidMode = getBidIdParameter('vpaidMode', bid.params);
-
- const makeBids = (type, size, isViralize) => {
- let context = '';
- let streamType = 2;
-
- if (type === BANNER) {
- streamType = 5;
- } else {
- context = deepAccess(bid, 'mediaTypes.video.context');
- if (vpaidMode && context === 'instream') {
- streamType = 1;
- }
- if (context === 'outstream') {
- streamType = 5;
- }
- }
+ if (!isFn(bidRequest.getFloor)) {
+ return imp
+ }
- let rBid = {
- type: streamType,
- adUnitCode: bid.adUnitCode,
- bidId: bid.bidId,
- context: context,
- // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781
- auctionId: bidderRequest.auctionId,
- start: +new Date(),
- timeout: 3000,
- params: bid.params,
- schain: bid.schain || defaultSchain
- };
+ let floor = bidRequest.getFloor({
+ currency: 'EUR',
+ mediaType: '*',
+ size: '*',
+ });
+ if (!isNaN(floor?.floor) && floor?.currency === 'EUR') {
+ imp.bidfloor = floor.floor;
+ imp.bidfloorcur = 'EUR';
+ }
+ return imp;
+ },
- if (isViralize) {
- rBid.unitId = getBidIdParameter('unitId', bid.params);
- rBid.sizes = size;
- rBid.mediaTypes = {
- [type]: {'context': context}
- };
- } else {
- rBid.playerId = getBidIdParameter('playerId', bid.params);
- rBid.mediaType = type;
- rBid.size = {
- width: size[0],
- height: size[1]
- };
- rBid.gdprConsent = gdprConsent;
- rBid.uspConsent = uspConsent;
- }
+ bidResponse(buildBidResponse, bid, context) {
+ const bidResponse = buildBidResponse(bid, context);
- return rBid;
+ if (context.imp?.video?.ext?.context === 'outstream') {
+ const renderConfig = {
+ rendererUrl: bid.ext?.rendererConfig?.rendererUrl,
+ renderFunc: bid.ext?.rendererConfig?.renderFunc,
+ renderOptions: bid.ext?.rendererConfig?.renderOptions,
};
-
- if (isViralize) {
- if (videoSizes && videoSizes[0]) {
- adUnits.push(makeBids(VIDEO, videoSizes, isViralize));
- }
- if (bannerSizes && bannerSizes[0]) {
- adUnits.push(makeBids(BANNER, bannerSizes, isViralize));
- }
- } else {
- videoSizes.forEach((size) => {
- adUnits.push(makeBids(VIDEO, size));
- });
-
- bannerSizes.forEach((size) => {
- adUnits.push(makeBids(BANNER, size));
- });
+ if (renderConfig.renderFunc && renderConfig.rendererUrl) {
+ bidResponse.renderer = createRenderer(bidResponse, renderConfig);
}
- });
-
- let endpointUrl;
- let data;
+ }
+ bidResponse.callbacks = bid.ext?.callbacks;
+ bidResponse.extra = bid.ext?.extra;
+ return bidResponse;
+ },
+})
- const QA = validBidRequests[0].params.qa || {};
+const GVLID = 111;
- if (isViralize) {
- endpointUrl = VIRALIZE_ENDPOINT;
- data = {
- 'bidRequests': adUnits,
- 'context': {
- 'gdprConsent': gdprConsent,
- 'uspConsent': uspConsent,
- 'schain': defaultSchain,
- 'pageURL': QA.pageURL || encodeURIComponent(pageURL)
- }
- }
- } else {
- endpointUrl = isStage ? STAGE_ENDPOINT : PROD_ENDPOINT;
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: GVLID,
+ aliases: ['showheroesBs'],
+ supportedMediaTypes: [VIDEO],
+ isBidRequestValid: (bid) => {
+ return !!bid.params.unitId;
+ },
+ buildRequests: (bidRequests, bidderRequest) => {
+ const QA = bidRequests[0].params.qa;
- data = {
- 'user': [],
- 'meta': {
- 'adapterVersion': 2,
- 'pageURL': QA.pageURL || encodeURIComponent(pageURL),
- 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false,
- 'isDesktop': getWindowTop().document.documentElement.clientWidth > 700,
- 'xmlAndTag': !!(isOutstream && isCustomRender) || false,
- 'stage': isStage || undefined
- },
- 'requests': adUnits,
- 'debug': validBidRequests[0].params.debug || false,
- }
- }
+ const ortbData = converter.toORTB({ bidRequests, bidderRequest })
return {
- url: QA.endpoint || endpointUrl,
+ url: QA?.endpoint || ENDPOINT,
method: 'POST',
- options: {contentType: 'application/json', accept: 'application/json'},
- data: data
+ data: ortbData,
};
},
- interpretResponse: function(response, request) {
- return createBids(response.body, request.data);
+ interpretResponse: (response, request) => {
+ if (!response.body) {
+ return [];
+ }
+
+ const bids = converter.fromORTB({response: response.body, request: request.data}).bids;
+ return bids;
},
- getUserSyncs: function(syncOptions, serverResponses) {
+ getUserSyncs: (syncOptions, serverResponses) => {
const syncs = [];
- if (!serverResponses.length || !serverResponses[0].body.userSync) {
+ if (!serverResponses.length || !serverResponses[0].body?.ext?.userSync) {
return syncs;
}
- const userSync = serverResponses[0].body.userSync;
+ const userSync = serverResponses[0].body.ext.userSync;
- if (syncOptions.iframeEnabled) {
- (userSync.iframes || []).forEach(url => {
+ if (syncOptions.iframeEnabled && userSync?.iframes?.length) {
+ userSync.iframes.forEach(url => {
syncs.push({
type: 'iframe',
url
@@ -210,6 +124,7 @@ export const spec = {
});
});
}
+
return syncs;
},
@@ -223,177 +138,33 @@ export const spec = {
},
};
-function createBids(bidRes, reqData) {
- if (!bidRes) {
- return [];
- }
- const responseBids = bidRes.bids || bidRes.bidResponses;
- if (!Array.isArray(responseBids) || responseBids.length < 1) {
- return [];
- }
-
- const bids = [];
- const bidMap = {};
- (reqData.requests || reqData.bidRequests || []).forEach((bid) => {
- bidMap[bid.bidId] = bid;
- });
-
- responseBids.forEach(function (bid) {
- const requestId = bid.bidId || bid.requestId;
- const reqBid = bidMap[requestId];
- const currentBidParams = reqBid.params;
- const isViralize = !!reqBid.params.unitId;
- const size = {
- width: bid.width || bid.size.width,
- height: bid.height || bid.size.height
- };
-
- let bidUnit = {};
- bidUnit.cpm = bid.cpm;
- bidUnit.requestId = requestId;
- bidUnit.adUnitCode = reqBid.adUnitCode;
- bidUnit.currency = bid.currency;
- bidUnit.mediaType = bid.mediaType || VIDEO;
- bidUnit.ttl = TTL;
- bidUnit.creativeId = 'c_' + requestId;
- bidUnit.netRevenue = true;
- bidUnit.width = size.width;
- bidUnit.height = size.height;
- bidUnit.meta = {
- advertiserDomains: bid.adomain || []
- };
- if (bid.vastXml) {
- bidUnit.vastXml = bid.vastXml;
- bidUnit.adResponse = {
- content: bid.vastXml,
- };
- }
- if (bid.vastTag || bid.vastUrl) {
- bidUnit.vastUrl = bid.vastTag || bid.vastUrl;
+function outstreamRender(response, renderConfig) {
+ response.renderer.push(() => {
+ const func = deepAccess(window, renderConfig.renderFunc);
+ if (!isFn(func)) {
+ return;
}
- if (bid.mediaType === BANNER) {
- bidUnit.ad = getBannerHtml(bid, reqBid, reqData);
- } else if (bid.context === 'outstream') {
- const renderer = Renderer.install({
- id: requestId,
- url: 'https://static.showheroes.com/renderer.js',
- adUnitCode: reqBid.adUnitCode,
- config: {
- playerId: reqBid.playerId,
- width: size.width,
- height: size.height,
- vastUrl: bid.vastTag,
- vastXml: bid.vastXml,
- ad: bid.ad,
- debug: reqData.debug,
- isStage: reqData.meta && !!reqData.meta.stage,
- isViralize: isViralize,
- customRender: getBidIdParameter('customRender', currentBidParams.outstreamOptions),
- slot: getBidIdParameter('slot', currentBidParams.outstreamOptions),
- iframe: getBidIdParameter('iframe', currentBidParams.outstreamOptions),
- }
- });
- renderer.setRender(outstreamRender);
- bidUnit.renderer = renderer;
+ const renderPayload = { ...renderConfig.renderOptions };
+ if (response.vastXml) {
+ renderPayload.adResponse = {
+ content: response.vastXml,
+ };
}
- bids.push(bidUnit);
+ func(renderPayload);
});
-
- return bids;
}
-function outstreamRender(bid) {
- let embedCode;
- if (bid.renderer.config.isViralize) {
- embedCode = createOutstreamEmbedCodeV2(bid);
- } else {
- embedCode = createOutstreamEmbedCode(bid);
- }
- if (typeof bid.renderer.config.customRender === 'function') {
- bid.renderer.config.customRender(bid, embedCode);
- } else {
- try {
- const inIframe = getBidIdParameter('iframe', bid.renderer.config);
- if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') {
- const iframe = window.document.getElementById(inIframe);
- let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
- framedoc.body.appendChild(embedCode);
- return;
- }
-
- const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode;
- if (slot && window.document.getElementById(slot)) {
- window.document.getElementById(slot).appendChild(embedCode);
- } else if (slot) {
- logError('[ShowHeroes][renderer] Error: spot not found');
- }
- } catch (err) {
- logError('[ShowHeroes][renderer] Error:' + err.message);
- }
- }
-}
-
-function createOutstreamEmbedCode(bid) {
- const isStage = getBidIdParameter('isStage', bid.renderer.config);
- const urls = getEnvURLs(isStage);
-
- const fragment = window.document.createDocumentFragment();
-
- let script = loadExternalScript(urls.pubTag, MODULE_TYPE_BIDDER, 'showheroes-bs', function () {
- window.ShowheroesTag = this;
+function createRenderer(bid, renderConfig) {
+ const renderer = Renderer.install({
+ id: bid.id,
+ url: renderConfig.rendererUrl,
+ loaded: false,
+ adUnitCode: bid.adUnitCode,
});
- script.setAttribute('data-player-host', urls.vlHost);
-
- const spot = window.document.createElement('div');
- spot.setAttribute('class', 'showheroes-spot');
- spot.setAttribute('data-player', getBidIdParameter('playerId', bid.renderer.config));
- spot.setAttribute('data-debug', getBidIdParameter('debug', bid.renderer.config));
- spot.setAttribute('data-ad-vast-tag', getBidIdParameter('vastUrl', bid.renderer.config));
- spot.setAttribute('data-stream-type', 'outstream');
-
- fragment.appendChild(spot);
- fragment.appendChild(script);
- return fragment;
-}
-
-function createOutstreamEmbedCodeV2(bid) {
- const range = document.createRange();
- range.selectNode(document.getElementsByTagName('body')[0]);
- return range.createContextualFragment(getBidIdParameter('ad', bid.renderer.config));
-}
-
-function getBannerHtml (bid, reqBid, reqData) {
- const isStage = !!reqData.meta.stage;
- const urls = getEnvURLs(isStage);
- return `
-
-
-
-
-
- `;
-}
-
-function getVideoSizes(bidRequest) {
- return formatSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []);
-}
-
-function getBannerSizes(bidRequest) {
- return formatSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []);
-}
-
-function formatSizes(sizes) {
- if (!sizes || !sizes.length) {
- return []
- }
- return Array.isArray(sizes[0]) ? sizes : [sizes];
+ renderer.setRender((render) => {
+ return outstreamRender(render, renderConfig);
+ });
+ return renderer;
}
registerBidder(spec);
diff --git a/modules/showheroes-bsBidAdapter.md b/modules/showheroes-bsBidAdapter.md
index a32a77a2525..f306593a783 100644
--- a/modules/showheroes-bsBidAdapter.md
+++ b/modules/showheroes-bsBidAdapter.md
@@ -1,16 +1,14 @@
# Overview
+```
Module Name: ShowHeroes Bidder Adapter
-
Module Type: Bidder Adapter
-
Alias: showheroesBs
-
Maintainer: tech@showheroes.com
-
+```
# Description
-Module that connects to ShowHeroes demand source to fetch bids.
+A module that connects to ShowHeroes demand source to fetch bids.
# Test Parameters
```
@@ -27,122 +25,7 @@ Module that connects to ShowHeroes demand source to fetch bids.
{
bidder: "showheroes-bs",
params: {
- playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05',
- vpaidMode: true // by default is 'false'
- }
- }
- ]
- },
- {
- // if you have adSlot renderer or oustream should be returned as banner
- code: 'video',
- mediaTypes: {
- video: {
- playerSize: [640, 480],
- context: 'outstream',
- }
- },
- bids: [
- {
- bidder: "showheroes-bs",
- params: {
- playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05',
- }
- }
- ]
- },
- {
- code: 'video',
- mediaTypes: {
- video: {
- playerSize: [640, 480],
- context: 'outstream',
- }
- },
- bids: [
- {
- bidder: "showheroes-bs",
- params: {
- playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05',
-
- outstreamOptions: {
- // Required for the outstream renderer to exact node, one of
- iframe: 'iframe_id',
- // or
- slot: 'slot_id'
- }
- }
- }
- ]
- },
- {
- code: 'video',
- mediaTypes: {
- video: {
- playerSize: [640, 480],
- context: 'outstream',
- }
- },
- bids: [
- {
- bidder: "showheroes-bs",
- params: {
- playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05',
-
- outstreamOptions: {
- // Custom outstream rendering function
- customRender: function(bid, embedCode) {
- // Example with embedCode
- someContainer.appendChild(embedCode);
-
- // bid config data
- var vastUrl = bid.renderer.config.vastUrl;
- var vastXML = bid.renderer.config.vastXML;
- var videoWidth = bid.renderer.config.width;
- var videoHeight = bid.renderer.config.height;
- var playerId = bid.renderer.config.playerId;
- },
- }
- }
- }
- ]
- },
- {
- code: 'banner',
- mediaTypes: {
- banner: {
- sizes: [[640, 480]],
- }
- },
- bids: [
- {
- bidder: "showheroes-bs",
- params: {
- playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05',
- }
- }
- ]
- }
- ];
-```
-
-# Test Parameters (V2)
-```
- var adUnits = [
- {
- code: 'video',
- mediaTypes: {
- video: {
- playerSize: [640, 480],
- context: 'instream',
- }
- },
- bids: [
- {
- bidder: "showheroes-bs",
- params: {
- unitId: 'AACBWAcof-611K4U',
- vpaidMode: true // by default is 'false'
+ unitId: '1234abcd-5678efgh',
}
}
]
@@ -159,18 +42,10 @@ Module that connects to ShowHeroes demand source to fetch bids.
{
bidder: "showheroes-bs",
params: {
- unitId: 'AACBTwsZVANd9NlB',
-
- outstreamOptions: {
- // Required for the outstream renderer to exact node, one of
- iframe: 'iframe_id',
- // or
- slot: 'slot_id'
- }
+ unitId: '1234abcd-5678efgh',
}
}
]
}
];
```
-
diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js
index 840ba8a82aa..e0ddf0a66e6 100644
--- a/modules/smartadserverBidAdapter.js
+++ b/modules/smartadserverBidAdapter.js
@@ -4,24 +4,23 @@ import {
isArray,
isArrayOfNums,
isEmpty,
- isFn,
isInteger,
- isPlainObject,
logError
} from '../src/utils.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { config } from '../src/config.js';
+import { getBidFloor } from '../libraries/equativUtils/equativUtils.js'
import { registerBidder } from '../src/adapters/bidderFactory.js';
/**
* @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
* @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid
* @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest
+ * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync
*/
const BIDDER_CODE = 'smartadserver';
const GVL_ID = 45;
-const DEFAULT_FLOOR = 0.0;
export const spec = {
code: BIDDER_CODE,
@@ -176,7 +175,7 @@ export const spec = {
* Makes server requests from the list of BidRequests.
*
* @param {BidRequest[]} validBidRequests an array of bids
- * @param {BidderRequest} bidderRequest bidder request object
+ * @param {BidRequest} bidderRequest bidder request object
* @return {ServerRequest[]} Info describing the request to the server.
*/
buildRequests: function (validBidRequests, bidderRequest) {
@@ -257,7 +256,7 @@ export const spec = {
if (isSupportedVideoContext) {
let videoPayload = deepClone(payload);
spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video);
- videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO);
+ videoPayload.bidfloor = bid.params.bidfloor || getBidFloor(bid, adServerCurrency, VIDEO);
bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain));
}
} else {
@@ -265,7 +264,7 @@ export const spec = {
spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video);
}
- payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type);
+ payload.bidfloor = bid.params.bidfloor || getBidFloor(bid, adServerCurrency, type);
bidRequests.push(spec.createServerRequest(payload, bid.params.domain));
} else {
bidRequests.push({});
@@ -324,34 +323,12 @@ export const spec = {
return bidResponses;
},
- /**
- * Get floors from Prebid Price Floors module
- *
- * @param {object} bid Bid request object
- * @param {string} currency Ad server currency
- * @param {string} mediaType Bid media type
- * @return {number} Floor price
- */
- getBidFloor: function (bid, currency, mediaType) {
- if (!isFn(bid.getFloor)) {
- return DEFAULT_FLOOR;
- }
-
- const floor = bid.getFloor({
- currency: currency || 'USD',
- mediaType,
- size: '*'
- });
-
- return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR;
- },
-
/**
* User syncs.
*
* @param {*} syncOptions Publisher prebid configuration.
* @param {*} serverResponses A successful response from the server.
- * @return {syncs[]} An array of syncs that should be executed.
+ * @return {UserSync[]} An array of syncs that should be executed.
*/
getUserSyncs: function (syncOptions, serverResponses) {
const syncs = [];
diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js
index 483a7a86d73..1442199cd6d 100644
--- a/modules/smartxBidAdapter.js
+++ b/modules/smartxBidAdapter.js
@@ -422,6 +422,12 @@ function createOutstreamConfig(bid) {
var playerListener = function callback(event) {
switch (event) {
+ case 'AdError':
+ try {
+ window.sc_smartIntxtError();
+ } catch (f) {}
+ break;
+
case 'AdSlotStarted':
try {
window.sc_smartIntxtStart();
diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js
index 4e0de53ca0d..13731e2b7e1 100644
--- a/modules/snigelBidAdapter.js
+++ b/modules/snigelBidAdapter.js
@@ -16,6 +16,7 @@ const SESSION_ID_KEY = '_sn_session_pba';
const getConfig = config.getConfig;
const storageManager = getStorageManager({bidderCode: BIDDER_CODE});
const refreshes = {};
+const placementCounters = {};
const pageViewId = generateUUID();
const pageViewStart = new Date().getTime();
let auctionCounter = 0;
@@ -46,6 +47,7 @@ export const spec = {
cur: getCurrencies(),
test: getTestFlag(),
version: 'v' + '$prebid.version$',
+ adapterVersion: '2.0',
gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp'),
gpp_sid:
deepAccess(bidderRequest, 'gppConsent.applicableSections') || deepAccess(bidderRequest, 'ortb2.regs.gpp_sid'),
@@ -71,6 +73,7 @@ export const spec = {
gpid: deepAccess(r, 'ortb2Imp.ext.gpid'),
pbadslot: deepAccess(r, 'ortb2Imp.ext.data.pbadslot') || deepAccess(r, 'ortb2Imp.ext.gpid'),
name: r.params.placement,
+ counter: getPlacementCounter(r.params.placement),
sizes: r.sizes,
floor: getPriceFloor(r, BANNER, FLOOR_MATCH_ALL_SIZES),
refresh: getRefreshInformation(r.adUnitCode),
@@ -182,6 +185,17 @@ function getRefreshInformation(adUnitCode) {
};
}
+function getPlacementCounter(placement) {
+ const counter = placementCounters[placement];
+ if (counter === undefined) {
+ placementCounters[placement] = 0;
+ return 0;
+ }
+
+ placementCounters[placement]++;
+ return placementCounters[placement];
+}
+
function mapIdToRequestId(id, bidRequest) {
return bidRequest.bidderRequest.bids.filter((bid) => bid.adUnitCode === id)[0].bidId;
}
diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js
index 5649afd3946..2bb08707c85 100644
--- a/modules/sparteoBidAdapter.js
+++ b/modules/sparteoBidAdapter.js
@@ -24,12 +24,14 @@ const converter = ortbConverter({
request(buildRequest, imps, bidderRequest, context) {
const request = buildRequest(imps, bidderRequest, context);
+ deepSetValue(request, 'site.publisher.ext.params.pbjsVersion', '$prebid.version$');
+
if (bidderRequest.bids[0].params.networkId) {
- deepSetValue(request, 'site.publisher.ext.params.networkId', bidderRequest.bids[0].params.networkId);
+ request.site.publisher.ext.params.networkId = bidderRequest.bids[0].params.networkId;
}
if (bidderRequest.bids[0].params.publisherId) {
- deepSetValue(request, 'site.publisher.ext.params.publisherId', bidderRequest.bids[0].params.publisherId);
+ request.site.publisher.ext.params.publisherId = bidderRequest.bids[0].params.publisherId;
}
return request;
diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js
index dc4cd13d4a1..ed15842f96b 100644
--- a/modules/ssp_genieeBidAdapter.js
+++ b/modules/ssp_genieeBidAdapter.js
@@ -213,7 +213,7 @@ function makeCommonRequestData(bid, geparameter, refererInfo) {
}
// imuid
- const imuidQuery = getImuidAsQueryParameter();
+ const imuidQuery = getImuidAsQueryParameter(bid);
if (imuidQuery) data.extuid = imuidQuery;
// makeUAQuery
@@ -308,21 +308,11 @@ function makeBidResponseAd(innerHTML) {
return '' + innerHTML + '';
}
-/**
- * add imuid script tag
- */
-function appendImuidScript() {
- const scriptEl = document.createElement('script');
- scriptEl.src = '//dmp.im-apps.net/scripts/im-uid-hook.js?cid=3929';
- scriptEl.async = true;
- document.body.appendChild(scriptEl);
-}
-
/**
* return imuid strings as query parameters
*/
-function getImuidAsQueryParameter() {
- const imuid = storage.getCookie('_im_uid.3929');
+function getImuidAsQueryParameter(bid) {
+ const imuid = utils.deepAccess(bid, 'userId.imuid');
return imuid ? 'im:' + imuid : ''; // To avoid double encoding, not using encodeURIComponent here
}
@@ -404,8 +394,6 @@ export const spec = {
return bidResponses;
}
- appendImuidScript();
-
const zoneId = bidderRequest.bid.params.zoneId;
let successBid;
successBid = serverResponse.body || {};
diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js
index 072b72a6724..fedebc72053 100644
--- a/modules/stroeerCoreBidAdapter.js
+++ b/modules/stroeerCoreBidAdapter.js
@@ -75,7 +75,7 @@ export const spec = {
};
}
- const ORTB2_KEYS = ['regs.ext.dsa', 'device.ext.cdep'];
+ const ORTB2_KEYS = ['regs.ext.dsa', 'device.ext.cdep', 'site.ext'];
ORTB2_KEYS.forEach(key => {
const value = deepAccess(bidderRequest.ortb2, key);
if (value !== undefined) {
diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js
index 8afe7298c13..6c44bea7cd2 100644
--- a/modules/yandexAnalyticsAdapter.js
+++ b/modules/yandexAnalyticsAdapter.js
@@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js';
import { logError, logInfo } from '../src/utils.js';
import { EVENTS } from '../src/constants.js';
import * as events from '../src/events.js';
+import { getGlobal } from '../src/prebidGlobal.js';
const timeoutIds = {};
const tryUntil = (operationId, conditionCb, cb) => {
@@ -23,6 +24,7 @@ const clearTryUntilTimeouts = (timeouts) => {
});
};
+export const PBJS_INIT_EVENT_NAME = 'pbjsInit';
const SEND_EVENTS_BUNDLE_TIMEOUT = 1500;
const {
BID_REQUESTED,
@@ -122,6 +124,9 @@ const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' }
logError('Aborting yandex analytics provider initialization.');
}, 25000);
+ yandexAnalytics.onEvent(PBJS_INIT_EVENT_NAME, {
+ 'version': getGlobal().version,
+ });
events.getEvents().forEach((event) => {
if (event && EVENTS_TO_TRACK.indexOf(event.eventType) >= 0) {
yandexAnalytics.onEvent(event.eventType, event);
diff --git a/package-lock.json b/package-lock.json
index 0091ce17f24..50319bec3c7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "prebid.js",
- "version": "9.17.0-pre",
+ "version": "9.20.0-pre",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "prebid.js",
- "version": "9.17.0-pre",
+ "version": "9.20.0-pre",
"license": "Apache-2.0",
"dependencies": {
"@babel/core": "^7.25.2",
@@ -22,7 +22,7 @@
"fun-hooks": "^0.9.9",
"gulp-wrap": "^0.15.0",
"klona": "^2.0.6",
- "live-connect-js": "^6.7.3"
+ "live-connect-js": "^7.1.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.5",
@@ -18197,23 +18197,23 @@
"dev": true
},
"node_modules/live-connect-common": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz",
- "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-4.1.0.tgz",
+ "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==",
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/live-connect-js": {
- "version": "6.7.3",
- "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz",
- "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz",
+ "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==",
"dependencies": {
- "live-connect-common": "^v3.1.4",
+ "live-connect-common": "^v4.1.0",
"tiny-hashes": "1.0.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/livereload-js": {
@@ -41721,16 +41721,16 @@
"dev": true
},
"live-connect-common": {
- "version": "3.1.4",
- "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz",
- "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ=="
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-4.1.0.tgz",
+ "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw=="
},
"live-connect-js": {
- "version": "6.7.3",
- "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz",
- "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz",
+ "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==",
"requires": {
- "live-connect-common": "^v3.1.4",
+ "live-connect-common": "^v4.1.0",
"tiny-hashes": "1.0.1"
}
},
diff --git a/package.json b/package.json
index 47ec8b7f81b..151f324f5da 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "prebid.js",
- "version": "9.17.0-pre",
+ "version": "9.20.0-pre",
"description": "Header Bidding Management Library",
"main": "src/prebid.public.js",
"exports": {
@@ -143,7 +143,7 @@
"fun-hooks": "^0.9.9",
"gulp-wrap": "^0.15.0",
"klona": "^2.0.6",
- "live-connect-js": "^6.7.3"
+ "live-connect-js": "^7.1.0"
},
"optionalDependencies": {
"fsevents": "^2.3.2"
diff --git a/src/adRendering.js b/src/adRendering.js
index 47f335664e5..4b1f2960428 100644
--- a/src/adRendering.js
+++ b/src/adRendering.js
@@ -137,11 +137,12 @@ export const getRenderingData = hook('sync', function (bidResponse, options) {
};
})
-export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options}) {
- if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) {
+export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options, doc, isMainDocument = doc === document && !inIframe()}) {
+ const videoBid = (FEATURES.VIDEO && bidResponse.mediaType === VIDEO)
+ if (isMainDocument || videoBid) {
emitAdRenderFail({
reason: AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT,
- message: 'Cannot render video ad',
+ message: videoBid ? 'Cannot render video ad without a renderer' : `renderAd was prevented from writing to the main document.`,
bid: bidResponse,
id: bidResponse.adId
});
@@ -277,14 +278,10 @@ export function renderAdDirect(doc, adId, options) {
if (!adId || !doc) {
fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`);
} else {
- if ((doc === document && !inIframe())) {
- fail(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`);
- } else {
- getBidToRender(adId).then(bidResponse => {
- bid = bidResponse;
- handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc});
- });
- }
+ getBidToRender(adId).then(bidResponse => {
+ bid = bidResponse;
+ handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc});
+ });
}
} catch (e) {
fail(EXCEPTION, e.message);
diff --git a/src/auction.js b/src/auction.js
index f122840affc..759397275d5 100644
--- a/src/auction.js
+++ b/src/auction.js
@@ -635,15 +635,15 @@ function getPreparedBidForAuction(bid, {index = auctionManager.index} = {}) {
var renderer = null;
// the renderer for the mediaType takes precendence
- if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) {
+ if (mediaTypeRenderer && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) {
renderer = mediaTypeRenderer;
- } else if (bidRenderer && bidRenderer.url && bidRenderer.render && !(bidRenderer.backupOnly === true && bid.renderer)) {
+ } else if (bidRenderer && bidRenderer.render && !(bidRenderer.backupOnly === true && bid.renderer)) {
renderer = bidRenderer;
}
if (renderer) {
// be aware, an adapter could already have installed the bidder, in which case this overwrite's the existing adapter
- bid.renderer = Renderer.install({ url: renderer.url, config: renderer.options });// rename options to config, to make it consistent?
+ bid.renderer = Renderer.install({ url: renderer.url, config: renderer.options, renderNow: renderer.url == null });// rename options to config, to make it consistent?
bid.renderer.setRender(renderer.render);
}
diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js
index 4c3fd4b6c07..ec5047238a2 100644
--- a/src/fpd/enrichment.js
+++ b/src/fpd/enrichment.js
@@ -129,7 +129,7 @@ const ENRICHMENTS = {
regs() {
const regs = {};
if (winFallback((win) => win.navigator.globalPrivacyControl)) {
- deepSetValue(regs, 'ext.gpc', 1);
+ deepSetValue(regs, 'ext.gpc', '1');
}
const coppa = config.getConfig('coppa');
if (typeof coppa === 'boolean') {
diff --git a/test/spec/auctionmanager_spec.js b/test/spec/auctionmanager_spec.js
index 6a8a826bec5..afc90fae342 100644
--- a/test/spec/auctionmanager_spec.js
+++ b/test/spec/auctionmanager_spec.js
@@ -1044,26 +1044,38 @@ describe('auctionmanager.js', function () {
Object.entries({
'on adUnit': () => adUnits[0],
'on bid': () => bidderRequests[0].bids[0],
+ 'on mediatype': () => bidderRequests[0].bids[0].mediaTypes.banner,
}).forEach(([t, getObj]) => {
- it(t, () => {
- let renderer = {
+ let renderer, bid;
+ beforeEach(() => {
+ renderer = {
url: 'renderer.js',
render: (bid) => bid
};
+ })
- let bids1 = Object.assign({},
+ function getBid() {
+ let bid = Object.assign({},
bids[0],
{
bidderCode: BIDDER_CODE,
- mediaType: 'video-outstream',
+ mediaType: 'banner',
}
);
Object.assign(getObj(), {renderer});
- spec.interpretResponse.returns(bids1);
+ spec.interpretResponse.returns(bid);
auction.callBids();
- const addedBid = auction.getBidsReceived().pop();
- assert.equal(addedBid.renderer.url, 'renderer.js');
- })
+ return auction.getBidsReceived().pop();
+ }
+
+ it(t, () => {
+ expect(getBid().renderer.url).to.eql('renderer.js');
+ });
+
+ it('allows renderers without URL', () => {
+ delete renderer.url;
+ expect(getBid().renderer.renderNow).to.be.true;
+ });
})
})
diff --git a/test/spec/fpd/enrichment_spec.js b/test/spec/fpd/enrichment_spec.js
index cec6597f2d4..5bf1dbc22a4 100644
--- a/test/spec/fpd/enrichment_spec.js
+++ b/test/spec/fpd/enrichment_spec.js
@@ -222,7 +222,7 @@ describe('FPD enrichment', () => {
it('is set if globalPrivacyControl is set', () => {
win.navigator.globalPrivacyControl = true;
return fpd().then(ortb2 => {
- expect(ortb2.regs.ext.gpc).to.eql(1);
+ expect(ortb2.regs.ext.gpc).to.eql('1');
});
});
diff --git a/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js b/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js
new file mode 100644
index 00000000000..edbc078a740
--- /dev/null
+++ b/test/spec/libraries/precisoUtils/bidNativeUtils_spec.js
@@ -0,0 +1,78 @@
+import {expect} from 'chai';
+import { NATIVE } from '../../../../src/mediaTypes';
+import { interpretNativeBid, OPENRTB } from '../../../../libraries/precisoUtils/bidNativeUtils';
+
+const DEFAULT_PRICE = 1
+const DEFAULT_BANNER_WIDTH = 300
+const DEFAULT_BANNER_HEIGHT = 250
+const BIDDER_CODE = 'test';
+
+describe('bidNativeUtils', function () {
+ describe('interpretNativeBid', function () {
+ it('should get correct native bid response', function () {
+ const adm = {
+ native: {
+ ver: 1.2,
+ link: {
+ url: 'https://example.com',
+ clicktrackers: 'https://example.com/clktracker'
+ },
+ eventtrackers: [
+ {
+ url: 'https://example.com/imptracker'
+ }
+ ],
+ imptrackers: [
+ 'https://example.com/imptracker'
+ ],
+ assets: [{
+ id: OPENRTB.NATIVE.ASSET_ID.IMAGE,
+ required: 1,
+ img: {
+ url: 'https://example.com/image.jpg',
+ w: 150,
+ h: 50
+ }
+ }],
+ }
+ }
+ let bid = {
+ id: '123',
+ impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22',
+ price: DEFAULT_PRICE,
+ adm: JSON.stringify(adm),
+ cid: 'test_cid',
+ crid: 'test_banner_crid',
+ w: DEFAULT_BANNER_WIDTH,
+ h: DEFAULT_BANNER_HEIGHT,
+ adomain: [],
+ }
+
+ let expectedResponse = {
+ requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22',
+ mediaType: NATIVE,
+ cpm: DEFAULT_PRICE,
+ creativeId: 'test_banner_crid',
+ width: 1,
+ height: 1,
+ ttl: 300,
+ netRevenue: true,
+ currency: 'USD',
+ meta: { advertiserDomains: [] },
+ native: {
+ clickUrl: encodeURI('https://example.com'),
+ impressionTrackers: ['https://example.com/imptracker'],
+ image: {
+ url: encodeURI('https://example.com/image.jpg'),
+ width: 150,
+ height: 50
+ },
+ }
+ }
+
+ let result = interpretNativeBid(bid);
+
+ expect(Object.keys(result)).to.have.members(Object.keys(expectedResponse));
+ })
+ });
+});
diff --git a/test/spec/libraries/precisoUtils/bidUtils_spec.js b/test/spec/libraries/precisoUtils/bidUtils_spec.js
index eecfea4b70e..05c480f41bc 100644
--- a/test/spec/libraries/precisoUtils/bidUtils_spec.js
+++ b/test/spec/libraries/precisoUtils/bidUtils_spec.js
@@ -10,7 +10,7 @@ const BIDDER_CODE = 'preciso';
const TESTDOMAIN = 'test.org'
const bidEndPoint = `https://${TESTDOMAIN}/bid_request/openrtb`;
-describe('bidderOperations', function () {
+describe('bidUtils', function () {
let bid = {
bidId: '23fhj33i987f',
bidder: BIDDER_CODE,
diff --git a/test/spec/modules/51DegreesRtdProvider_spec.js b/test/spec/modules/51DegreesRtdProvider_spec.js
index 4847587c7d3..7b60a08b906 100644
--- a/test/spec/modules/51DegreesRtdProvider_spec.js
+++ b/test/spec/modules/51DegreesRtdProvider_spec.js
@@ -160,16 +160,114 @@ describe('51DegreesRtdProvider', function() {
});
describe('get51DegreesJSURL', function() {
+ const hev = {
+ 'brands': [
+ {
+ 'brand': 'Chromium',
+ 'version': '130'
+ },
+ {
+ 'brand': 'Google Chrome',
+ 'version': '130'
+ },
+ {
+ 'brand': 'Not?A_Brand',
+ 'version': '99'
+ }
+ ],
+ 'fullVersionList': [
+ {
+ 'brand': 'Chromium',
+ 'version': '130.0.6723.92'
+ },
+ {
+ 'brand': 'Google Chrome',
+ 'version': '130.0.6723.92'
+ },
+ {
+ 'brand': 'Not?A_Brand',
+ 'version': '99.0.0.0'
+ }
+ ],
+ 'mobile': false,
+ 'model': '',
+ 'platform': 'macOS',
+ 'platformVersion': '14.6.1'
+ };
+ const mockWindow = {
+ ...window,
+ screen: {
+ height: 1117,
+ width: 1728,
+ },
+ devicePixelRatio: 2,
+ };
+
it('returns the cloud URL if the resourceKey is provided', function() {
const config = {resourceKey: 'TEST_RESOURCE_KEY'};
- expect(get51DegreesJSURL(config)).to.equal(
- 'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js'
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal(
+ 'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js?' +
+ `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
+ `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
+ `51D_PixelRatio=${mockWindow.devicePixelRatio}`
+ );
+ });
+
+ it('returns the on-premise URL if the onPremiseJSUrl is provided', function () {
+ const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'};
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal(
+ `https://example.com/51Degrees.core.js?` +
+ `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
+ `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
+ `51D_PixelRatio=${mockWindow.devicePixelRatio}`
+ );
+ });
+
+ it('doesn\'t override static query string parameters', function () {
+ const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js?test=1'};
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal(
+ `https://example.com/51Degrees.core.js?test=1&` +
+ `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
+ `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
+ `51D_PixelRatio=${mockWindow.devicePixelRatio}`
+ );
+ });
+
+ it('adds high entropy values to the query string, if available', async function () {
+ const config = {
+ onPremiseJSUrl: 'https://example.com/51Degrees.core.js',
+ hev,
+ };
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal(
+ `https://example.com/51Degrees.core.js?` +
+ `51D_GetHighEntropyValues=${btoa(JSON.stringify(hev))}&` +
+ `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
+ `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
+ `51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});
- it('returns the on-premise URL if the onPremiseJSUrl is provided', function() {
+ it('doesn\'t add high entropy values to the query string if object is empty', function () {
+ const config = {
+ onPremiseJSUrl: 'https://example.com/51Degrees.core.js',
+ hev: {},
+ };
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal(
+ `https://example.com/51Degrees.core.js?` +
+ `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
+ `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
+ `51D_PixelRatio=${mockWindow.devicePixelRatio}`
+ );
+ });
+
+ it('keeps the original URL if none of the additional parameters are available', function () {
+ // delete screen and devicePixelRatio properties to test the case when they are not available
+ delete mockWindow.screen;
+ delete mockWindow.devicePixelRatio;
+
const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'};
- expect(get51DegreesJSURL(config)).to.equal('https://example.com/51Degrees.core.js');
+ expect(get51DegreesJSURL(config, mockWindow)).to.equal('https://example.com/51Degrees.core.js');
+ expect(get51DegreesJSURL(config, window)).to.not.equal('https://example.com/51Degrees.core.js');
});
});
diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js
index 329aa980caf..d1058170f44 100644
--- a/test/spec/modules/adagioAnalyticsAdapter_spec.js
+++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js
@@ -287,141 +287,192 @@ const AUCTION_INIT_ANOTHER = {
},
} ],
'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'],
- 'bidderRequests': [ {
- 'bidderCode': 'another',
- 'auctionId': AUCTION_ID,
- 'bidderRequestId': '1be65d7958826a',
- 'bids': [ {
- 'bidder': 'another',
- 'params': {
- 'publisherId': '1001',
- },
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 480]]
- }
- },
- 'adUnitCode': '/19968336/header-bid-tag-1',
- 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
- 'sizes': [[640, 480]],
- 'bidId': '2ecff0db240757',
- 'bidderRequestId': '1be65d7958826a',
+ 'bidderRequests': [
+ {
+ 'bidderCode': 'another',
'auctionId': AUCTION_ID,
- 'src': 'client',
- 'bidRequestsCount': 1
- }, {
- 'bidder': 'another',
- 'params': {
- 'publisherId': '1001'
+ 'bidderRequestId': '1be65d7958826a',
+ 'bids': [
+ {
+ 'bidder': 'another',
+ 'params': {
+ 'publisherId': '1001',
+ },
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [[640, 480]]
+ }
+ },
+ 'adUnitCode': '/19968336/header-bid-tag-1',
+ 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
+ 'sizes': [[640, 480]],
+ 'bidId': '2ecff0db240757',
+ 'bidderRequestId': '1be65d7958826a',
+ 'auctionId': AUCTION_ID,
+ 'src': 'client',
+ 'bidRequestsCount': 1
+ },
+ {
+ 'bidder': 'another',
+ 'params': {
+ 'publisherId': '1001'
+ },
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [[640, 480]]
+ }
+ },
+ 'adUnitCode': '/19968336/footer-bid-tag-1',
+ 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
+ 'sizes': [[640, 480]],
+ 'bidId': '2ecff0db240757',
+ 'bidderRequestId': '1be65d7958826a',
+ 'auctionId': AUCTION_ID,
+ 'src': 'client',
+ 'bidRequestsCount': 1
+ },
+ ],
+ 'timeout': 3000,
+ 'refererInfo': {
+ 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 480]]
+ 'ortb2': {
+ 'site': {
+ 'ext': {
+ 'data': {
+ 'adg_rtd': {
+ ...ADG_RTD
+ },
+ ...ORTB_DATA
+ }
+ }
}
- },
- 'adUnitCode': '/19968336/footer-bid-tag-1',
- 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
- 'sizes': [[640, 480]],
- 'bidId': '2ecff0db240757',
- 'bidderRequestId': '1be65d7958826a',
+ }
+ },
+ {
+ 'bidderCode': 'nobid',
'auctionId': AUCTION_ID,
- 'src': 'client',
- 'bidRequestsCount': 1
- }, {
- 'bidder': 'nobid',
- 'params': {
- 'publisherId': '1001'
+ 'bidderRequestId': '1be65d7958826a',
+ 'bids': [{
+ 'bidder': 'nobid',
+ 'params': {
+ 'publisherId': '1001'
+ },
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [[640, 480]]
+ }
+ },
+ 'adUnitCode': '/19968336/header-bid-tag-1',
+ 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
+ 'sizes': [[640, 480]],
+ 'bidId': '2ecff0db240757',
+ 'bidderRequestId': '1be65d7958826a',
+ 'auctionId': AUCTION_ID,
+ 'src': 'client',
+ 'bidRequestsCount': 1
+ }
+ ],
+ 'timeout': 3000,
+ 'refererInfo': {
+ 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 480]]
+ 'ortb2': {
+ 'site': {
+ 'ext': {
+ 'data': {
+ 'adg_rtd': {
+ ...ADG_RTD
+ },
+ ...ORTB_DATA
+ }
+ }
}
- },
- 'adUnitCode': '/19968336/footer-bid-tag-1',
- 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
- 'sizes': [[640, 480]],
- 'bidId': '2ecff0db240757',
- 'bidderRequestId': '1be65d7958826a',
+ }
+ },
+ {
+ bidderCode: 'anotherWithAlias',
'auctionId': AUCTION_ID,
- 'src': 'client',
- 'bidRequestsCount': 1
- }, {
- 'bidder': 'anotherWithAlias',
- 'params': {
- 'publisherId': '1001',
+ 'bidderRequestId': '1be65d7958826a',
+ 'bids': [
+ {
+ 'bidder': 'anotherWithAlias',
+ 'params': {
+ 'publisherId': '1001',
+ },
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [[640, 480]]
+ }
+ },
+ 'adUnitCode': '/19968336/header-bid-tag-1',
+ 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
+ 'sizes': [[640, 480]],
+ 'bidId': '2ecff0db240757',
+ 'bidderRequestId': '1be65d7958826a',
+ 'auctionId': AUCTION_ID,
+ 'src': 'client',
+ 'bidRequestsCount': 1
+ },
+ ],
+ 'timeout': 3000,
+ 'refererInfo': {
+ 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 480]]
+ 'ortb2': {
+ 'site': {
+ 'ext': {
+ 'data': {
+ 'adg_rtd': {
+ ...ADG_RTD
+ },
+ ...ORTB_DATA
+ }
+ }
}
- },
- 'adUnitCode': '/19968336/header-bid-tag-1',
- 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
- 'sizes': [[640, 480]],
- 'bidId': '2ecff0db240757',
- 'bidderRequestId': '1be65d7958826a',
- 'auctionId': AUCTION_ID,
- 'src': 'client',
- 'bidRequestsCount': 1
- },
- ],
- 'timeout': 3000,
- 'refererInfo': {
- 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
+ }
},
- 'ortb2': {
- 'site': {
- 'ext': {
- 'data': {
- 'adg_rtd': {
- ...ADG_RTD
- },
- ...ORTB_DATA
+ {
+ 'bidderCode': 'adagio',
+ 'auctionId': AUCTION_ID,
+ 'bidderRequestId': '1be65d7958826a',
+ 'bids': [ {
+ 'bidder': 'adagio',
+ 'params': {
+ ...PARAMS_ADG
+ },
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [[640, 480]]
}
- }
+ },
+ 'adUnitCode': '/19968336/header-bid-tag-1',
+ 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
+ 'sizes': [[640, 480]],
+ 'bidId': '2ecff0db240757',
+ 'bidderRequestId': '1be65d7958826a',
+ 'auctionId': AUCTION_ID,
+ 'src': 'client',
+ 'bidRequestsCount': 1
}
- }
- }, {
- 'bidderCode': 'adagio',
- 'auctionId': AUCTION_ID,
- 'bidderRequestId': '1be65d7958826a',
- 'bids': [ {
- 'bidder': 'adagio',
- 'params': {
- ...PARAMS_ADG
+ ],
+ 'timeout': 3000,
+ 'refererInfo': {
+ 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 480]]
- }
- },
- 'adUnitCode': '/19968336/header-bid-tag-1',
- 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014',
- 'sizes': [[640, 480]],
- 'bidId': '2ecff0db240757',
- 'bidderRequestId': '1be65d7958826a',
- 'auctionId': AUCTION_ID,
- 'src': 'client',
- 'bidRequestsCount': 1
- }
- ],
- 'timeout': 3000,
- 'refererInfo': {
- 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html']
- },
- 'ortb2': {
- 'site': {
- 'ext': {
- 'data': {
- 'adg_rtd': {
- ...ADG_RTD
- },
- ...ORTB_DATA
+ 'ortb2': {
+ 'site': {
+ 'ext': {
+ 'data': {
+ 'adg_rtd': {
+ ...ADG_RTD
+ },
+ ...ORTB_DATA
+ }
}
}
}
}
- }
],
'bidsReceived': [],
'winningBids': [],
diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js
index d4802ffd4c0..a0846a829a8 100644
--- a/test/spec/modules/adnuntiusBidAdapter_spec.js
+++ b/test/spec/modules/adnuntiusBidAdapter_spec.js
@@ -50,7 +50,6 @@ describe('adnuntiusBidAdapter', function () {
const tzo = new Date().getTimezoneOffset();
const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`;
const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`;
- const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`;
const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`;
const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`;
const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`;
@@ -102,7 +101,66 @@ describe('adnuntiusBidAdapter', function () {
}
},
}
- ]
+ ];
+
+ const multiBidderInResponse = {
+ bid: [{
+ bidder: 'adnuntius',
+ bidId: '3a602680158a85',
+ params: {
+ auId: '381535',
+ network: '1287',
+ bidType: 'netBid',
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[200, 200]]
+ },
+ video: {
+ playerSize: [200, 200],
+ context: 'instream'
+ }
+ }
+ },
+ {
+ bidder: 'adnuntius',
+ params: {
+ auId: '381535',
+ network: '1287',
+ bidType: 'netBid',
+ targetId: 'fred',
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [[200, 200]]
+ },
+ video: {
+ playerSize: [200, 200],
+ context: 'instream'
+ }
+ }
+ }]
+ };
+
+ const multiBidderRequest = [
+ {
+ bidId: 'adn-0000000000000551',
+ bidder: 'adnuntius',
+ params: {
+ auId: '0000000000000551',
+ network: 'adnuntius',
+ },
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480],
+ context: 'instream'
+ },
+ banner: {
+ sizes: [[1640, 1480], [1600, 1400]],
+ }
+ },
+ }
+ ];
const singleBidRequest = {
bid: [
@@ -184,6 +242,131 @@ describe('adnuntiusBidAdapter', function () {
}
];
+ const multiFormatServerResponse = {
+ body: {
+ 'adUnits': [
+ {
+ 'auId': '0000000000381535',
+ 'targetId': '3a602680158a85-video',
+ 'vastXml': '\n',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp-453419729',
+ 'ads': [
+ {
+ 'cpm': {
+ 'amount': 1500.0,
+ 'currency': 'NOK'
+ },
+ 'bid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'adId': 'adn-id-615465411',
+ 'vastXml': ''
+ }
+ ]
+ },
+ {
+ 'auId': '0000000000381535',
+ 'targetId': 'fred-video',
+ 'vastXml': '',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp--1809523040',
+ 'ads': [
+ {
+ 'cpm': {
+ 'amount': 1500.0,
+ 'currency': 'NOK'
+ },
+ 'bid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.5,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'adId': 'adn-id-344789675',
+ 'selectedColumn': '0',
+ 'selectedColumnPosition': '0',
+ 'vastXml': '\n',
+ }
+ ]
+ },
+ {
+ 'auId': '0000000000381535',
+ 'targetId': '3a602680158a85',
+ 'html': '\u003C!DOCTYPE html\u003E\n\n\u003C/html\u003E',
+ 'matchedAdCount': 0,
+ 'responseId': '',
+ 'ads': []
+ },
+ {
+ 'auId': '0000000000381535',
+ 'renderOption': 'DIV',
+ 'targetId': 'fred',
+ 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E',
+ 'matchedAdCount': 1,
+ 'responseId': 'adn-rsp-1620340740',
+ 'ads': [
+ {
+ 'destinationUrlEsc': '',
+ 'cpm': {
+ 'amount': 1250.0,
+ 'currency': 'NOK'
+ },
+ creativeWidth: 200,
+ creativeHeight: 240,
+ 'bid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'grossBid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'netBid': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'cost': {
+ 'amount': 1.75,
+ 'currency': 'NOK'
+ },
+ 'html': '\u003Ca \'\u003E\u003C/script\u003E',
+ }
+ ]
+ }
+ ],
+ 'network': '1287',
+ 'keywords': []
+ }
+ };
+
const serverResponse = {
body: {
'adUnits': [
@@ -565,11 +748,34 @@ describe('adnuntiusBidAdapter', function () {
it('Test Video requests', function () {
const request = spec.buildRequests(videoBidderRequest, {});
expect(request.length).to.equal(1);
+
+ const data = JSON.parse(request[0].data);
+ expect(data.adUnits.length).to.equal(1);
+ expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551');
+ expect(data.adUnits[0].adType).to.equal('VAST');
+
expect(request[0]).to.have.property('bid');
const bid = request[0].bid[0]
expect(bid).to.have.property('bidId');
expect(request[0]).to.have.property('url');
- expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO);
+ expect(request[0].url).to.equal(ENDPOINT_URL);
+ });
+
+ it('Test multiformat requests', function () {
+ const request = spec.buildRequests(multiBidderRequest, {});
+ expect(request.length).to.equal(1);
+ expect(request.data)
+ const data = JSON.parse(request[0].data);
+ expect(data.adUnits.length).to.equal(2);
+ expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551');
+ expect(data.adUnits[0]).not.to.have.property('adType');
+ expect(data.adUnits[1].targetId).to.equal('adn-0000000000000551-video');
+ expect(data.adUnits[1].adType).to.equal('VAST');
+ expect(request[0]).to.have.property('bid');
+ const bid = request[0].bid[0]
+ expect(bid).to.have.property('bidId');
+ expect(request[0]).to.have.property('url');
+ expect(request[0].url).to.equal(ENDPOINT_URL);
});
it('should pass segments if available in config and merge from targeting', function () {
@@ -960,7 +1166,7 @@ describe('adnuntiusBidAdapter', function () {
expect(data.adUnits.length).to.equal(1);
expect(data.adUnits[0].maxDeals).to.equal(5);
});
- it('Should allow a minumum of 0 deals.', function () {
+ it('Should allow a minimum of 0 deals.', function () {
config.setBidderConfig({
bidders: ['adnuntius'],
});
@@ -1102,6 +1308,41 @@ describe('adnuntiusBidAdapter', function () {
expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90));
});
+ it('should return valid response when passed valid multiformat server response', function () {
+ config.setBidderConfig({
+ bidders: ['adnuntius'],
+ config: {
+ bidType: 'netBid',
+ maxDeals: 0
+ }
+ });
+
+ const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse));
+ expect(interpretedResponse).to.have.lengthOf(2);
+
+ let ad = multiFormatServerResponse.body.adUnits[0].ads[0];
+ expect(interpretedResponse[0].bidderCode).to.equal('adnuntius');
+ expect(interpretedResponse[0].cpm).to.equal(ad.netBid.amount * 1000);
+ expect(interpretedResponse[0].width).to.equal(Number(ad.creativeWidth));
+ expect(interpretedResponse[0].height).to.equal(Number(ad.creativeHeight));
+ expect(interpretedResponse[0].creativeId).to.equal(ad.creativeId);
+ expect(interpretedResponse[0].currency).to.equal(ad.bid.currency);
+ expect(interpretedResponse[0].netRevenue).to.equal(false);
+ expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html);
+ expect(interpretedResponse[0].ttl).to.equal(360);
+
+ ad = multiFormatServerResponse.body.adUnits[3].ads[0];
+ expect(interpretedResponse[1].bidderCode).to.equal('adnuntius');
+ expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000);
+ expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth));
+ expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight));
+ expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId);
+ expect(interpretedResponse[1].currency).to.equal(ad.bid.currency);
+ expect(interpretedResponse[1].netRevenue).to.equal(false);
+ expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html);
+ expect(interpretedResponse[1].ttl).to.equal(360);
+ });
+
it('should not process valid response when passed alt bidder that is an adndeal', function () {
const altBidder = {
bid: [
diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js
index d87c84a05c5..726bccaa027 100644
--- a/test/spec/modules/aniviewBidAdapter_spec.js
+++ b/test/spec/modules/aniviewBidAdapter_spec.js
@@ -188,7 +188,6 @@ describe('Aniview Bid Adapter', function () {
expect(url).equal('https://rtb.aniview.com/sspRTB2');
expect(method).equal('POST');
expect(imp[0].tagid).equal(CHANNEL_ID_1);
- expect(imp[0].ext.aniview.AV_HEIGHT).equal(VIDEO_SIZE.height);
expect(imp[0].id).equal(videoBidRequest.bids[0].bidId);
expect(ext.aniview.pbjs).equal(1);
});
diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js
index 05c0982c80e..b2bdfbcdf5a 100644
--- a/test/spec/modules/appnexusBidAdapter_spec.js
+++ b/test/spec/modules/appnexusBidAdapter_spec.js
@@ -2085,6 +2085,8 @@ describe('AppNexusAdapter', function () {
expect(result[0].native.body2).to.equal('Additional body text');
expect(result[0].native.cta).to.equal('Do it');
expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png');
+ // Video is technically not a base Prebid native field, so it should be included as part of the ext
+ // But it's also included here for backwards compatibility if people read the bid directly
expect(result[0].native.video.content).to.equal('');
});
@@ -2093,6 +2095,7 @@ describe('AppNexusAdapter', function () {
response1.tags[0].ads[0].ad_type = 'native';
response1.tags[0].ads[0].rtb.native = {
...BASE_NATIVE,
+ // 'video' is included in base native
'title1': 'Custom Title 1',
'title2': 'Custom Title 2',
'title3': 'Custom Title 3',
@@ -2204,6 +2207,9 @@ describe('AppNexusAdapter', function () {
let result = spec.interpretResponse({ body: response1 }, { bidderRequest });
expect(result[0].native.ext).to.deep.equal({
+ 'video': {
+ 'content': ''
+ },
'customTitle1': 'Custom Title 1',
'customTitle2': 'Custom Title 2',
'customTitle3': 'Custom Title 3',
diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js
index 3990cd3feb3..c4b4a776243 100644
--- a/test/spec/modules/bidResponseFilter_spec.js
+++ b/test/spec/modules/bidResponseFilter_spec.js
@@ -1,15 +1,53 @@
-import { BID_ADV_DOMAINS_REJECTION_REASON, BID_ATTR_REJECTION_REASON, BID_CATEGORY_REJECTION_REASON, MODULE_NAME, PUBLISHER_FILTER_REJECTION_REASON, addBidResponseHook } from '../../../modules/bidResponseFilter';
-import { config } from '../../../src/config';
+import {
+ addBidResponseHook,
+ BID_ADV_DOMAINS_REJECTION_REASON,
+ BID_ATTR_REJECTION_REASON,
+ BID_CATEGORY_REJECTION_REASON,
+ init,
+ MODULE_NAME
+ , reset} from '../../../modules/bidResponseFilter';
+import {config} from '../../../src/config';
+import {addBidResponse} from '../../../src/auction.js';
describe('bidResponseFilter', () => {
let mockAuctionIndex
beforeEach(() => {
- config.resetConfig();
mockAuctionIndex = {
- getBidRequest: () => {},
- getAdUnit: () => {}
+ getBidRequest: () => {
+ },
+ getAdUnit: () => {
+ }
};
});
+ afterEach(() => {
+ config.resetConfig();
+ reset();
+ })
+
+ describe('enable/disable', () => {
+ let reject, dispatch;
+
+ beforeEach(() => {
+ reject = sinon.stub();
+ dispatch = sinon.stub();
+ });
+
+ it('should not run if not configured', () => {
+ reset();
+ addBidResponse.call({dispatch}, 'au', {}, reject);
+ sinon.assert.notCalled(reject);
+ sinon.assert.called(dispatch);
+ });
+
+ it('should run if configured', () => {
+ config.setConfig({
+ bidResponseFilter: {}
+ });
+ addBidResponse.call({dispatch}, 'au', {}, reject);
+ sinon.assert.called(reject);
+ sinon.assert.notCalled(dispatch);
+ })
+ });
it('should pass the bid after successful ortb2 rules validation', () => {
const call = sinon.stub();
@@ -26,7 +64,8 @@ describe('bidResponseFilter', () => {
}
};
- addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex);
+ addBidResponseHook(call, 'adcode', bid, () => {
+ }, mockAuctionIndex);
sinon.assert.calledOnce(call);
});
@@ -109,7 +148,8 @@ describe('bidResponseFilter', () => {
config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}});
- addBidResponseHook(call, 'adcode', bid, () => {}, mockAuctionIndex);
+ addBidResponseHook(call, 'adcode', bid, () => {
+ }, mockAuctionIndex);
sinon.assert.calledOnce(call);
});
@@ -129,7 +169,8 @@ describe('bidResponseFilter', () => {
config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}});
- addBidResponseHook(call, 'adcode', bid, () => {});
+ addBidResponseHook(call, 'adcode', bid, () => {
+ });
sinon.assert.calledOnce(call);
});
})
diff --git a/test/spec/modules/cadentApertureMXBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js
index 4f0f2cf8f20..d8686e3c667 100644
--- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js
+++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js
@@ -48,49 +48,11 @@ describe('cadent_aperture_mx Adapter', function () {
'auctionId': '1d1a01234a475'
};
let noBid = {};
- let otherBid = {
- 'bidder': 'emxdigital',
- 'params': {
- 'tagid': '25251'
- },
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[300, 250]]
- }
- },
- 'adUnitCode': 'adunit-code',
- 'sizes': [
- [300, 250],
- [300, 600]
- ],
- 'bidId': '30b31c2501de1e',
- 'bidderRequestId': '22edbae3120bf6',
- 'auctionId': '1d1a01234a475'
- };
- let noMediaSizeBid = {
- 'bidder': 'emxdigital',
- 'params': {
- 'tagid': '25251'
- },
- 'mediaTypes': {
- 'banner': {}
- },
- 'adUnitCode': 'adunit-code',
- 'sizes': [
- [300, 250],
- [300, 600]
- ],
- 'bidId': '30b31c2501de1e',
- 'bidderRequestId': '22edbae3120bf6',
- 'auctionId': '1d1a01234a475'
- };
it('should return true when required params found', function () {
expect(spec.isBidRequestValid(bid)).to.equal(true);
expect(spec.isBidRequestValid(badBid)).to.equal(false);
expect(spec.isBidRequestValid(noBid)).to.equal(false);
- expect(spec.isBidRequestValid(otherBid)).to.equal(false);
- expect(spec.isBidRequestValid(noMediaSizeBid)).to.equal(false);
});
});
diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js
index 5c01e23b027..af56b937f58 100644
--- a/test/spec/modules/connatixBidAdapter_spec.js
+++ b/test/spec/modules/connatixBidAdapter_spec.js
@@ -8,6 +8,9 @@ import {
_getMinSize as connatixGetMinSize,
_getViewability as connatixGetViewability,
_isViewabilityMeasurable as connatixIsViewabilityMeasurable,
+ saveOnAllStorages as connatixSaveOnAllStorages,
+ readFromAllStorages as connatixReadFromAllStorages,
+ storage,
spec
} from '../../../modules/connatixBidAdapter.js';
import adapterManager from '../../../src/adapterManager.js';
@@ -564,6 +567,7 @@ describe('connatixBidAdapter', function () {
describe('buildRequests', function () {
let serverRequest;
+ let setCookieStub, setDataInLocalStorageStub;
let bidderRequest = {
refererInfo: {
canonicalUrl: '',
@@ -592,10 +596,21 @@ describe('connatixBidAdapter', function () {
};
this.beforeEach(function () {
+ const mockIdentityProviderData = { mockKey: 'mockValue' };
+ const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000;
+ setCookieStub = sinon.stub(storage, 'setCookie');
+ setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage');
+ connatixSaveOnAllStorages('test_ids_cnx', mockIdentityProviderData, CNX_IDS_EXPIRY);
+
bid = mockBidRequest();
serverRequest = spec.buildRequests([bid], bidderRequest);
})
+ this.afterEach(function() {
+ setCookieStub.restore();
+ setDataInLocalStorageStub.restore();
+ });
+
it('Creates a ServerRequest object with method, URL and data', function () {
expect(serverRequest).to.exist;
expect(serverRequest.method).to.exist;
@@ -622,6 +637,7 @@ describe('connatixBidAdapter', function () {
expect(serverRequest.data.uspConsent).to.equal(bidderRequest.uspConsent);
expect(serverRequest.data.gppConsent).to.equal(bidderRequest.gppConsent);
expect(serverRequest.data.ortb2).to.equal(bidderRequest.ortb2);
+ expect(serverRequest.data.identityProviderData).to.deep.equal({ mockKey: 'mockValue' });
});
});
@@ -935,4 +951,102 @@ describe('connatixBidAdapter', function () {
expect(floor).to.equal(0);
});
});
+ describe('getUserSyncs with message event listener', function() {
+ const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000;
+ const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids';
+ const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved';
+
+ const mockData = {
+ providerName: 'nonId',
+ data: {
+ supplementalEids: [{ provider: 2, group: 1, eidsList: ['123', '456'] }]
+ }
+ };
+
+ function messageHandler(event) {
+ if (!event.data || event.origin !== 'https://cds.connatix.com') {
+ return;
+ }
+
+ if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) {
+ window.removeEventListener('message', messageHandler);
+ event.stopImmediatePropagation();
+ }
+
+ if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) {
+ const response = event.data;
+ if (response.data) {
+ connatixSaveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY);
+ }
+ }
+ }
+
+ let sandbox;
+
+ beforeEach(() => {
+ sandbox = sinon.createSandbox();
+ sandbox.stub(storage, 'setCookie');
+ sandbox.stub(storage, 'setDataInLocalStorage');
+ sandbox.stub(window, 'removeEventListener');
+ sandbox.stub(storage, 'cookiesAreEnabled').returns(true);
+ sandbox.stub(storage, 'localStorageIsEnabled').returns(true);
+ sandbox.stub(storage, 'getCookie');
+ sandbox.stub(storage, 'getDataFromLocalStorage');
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ it('Should set a cookie and save to local storage when a valid message is received', () => {
+ const fakeEvent = {
+ data: { type: 'cnx_all_identity_providers_resolved', data: mockData },
+ origin: 'https://cds.connatix.com',
+ stopImmediatePropagation: sinon.spy()
+ };
+
+ messageHandler(fakeEvent);
+
+ expect(fakeEvent.stopImmediatePropagation.calledOnce).to.be.true;
+ expect(window.removeEventListener.calledWith('message', messageHandler)).to.be.true;
+ expect(storage.setCookie.calledWith(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, JSON.stringify(mockData), sinon.match.string)).to.be.true;
+ expect(storage.setDataInLocalStorage.calledWith(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, JSON.stringify(mockData))).to.be.true;
+
+ storage.getCookie.returns(JSON.stringify(mockData));
+ storage.getDataFromLocalStorage.returns(JSON.stringify(mockData));
+
+ const retrievedData = connatixReadFromAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY);
+ expect(retrievedData).to.deep.equal(mockData);
+ });
+
+ it('Should should not do anything when there is no data in the payload', () => {
+ const fakeEvent = {
+ data: null,
+ origin: 'https://cds.connatix.com',
+ stopImmediatePropagation: sinon.spy()
+ };
+
+ messageHandler(fakeEvent);
+
+ expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true;
+ expect(window.removeEventListener.notCalled).to.be.true;
+ expect(storage.setCookie.notCalled).to.be.true;
+ expect(storage.setDataInLocalStorage.notCalled).to.be.true;
+ });
+
+ it('Should should not do anything when the origin is invalid', () => {
+ const fakeEvent = {
+ data: { type: 'cnx_all_identity_providers_resolved', data: mockData },
+ origin: 'https://notConnatix.com',
+ stopImmediatePropagation: sinon.spy()
+ };
+
+ messageHandler(fakeEvent);
+
+ expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true;
+ expect(window.removeEventListener.notCalled).to.be.true;
+ expect(storage.setCookie.notCalled).to.be.true;
+ expect(storage.setDataInLocalStorage.notCalled).to.be.true;
+ });
+ });
});
diff --git a/test/spec/modules/contxtfulBidAdapter_spec.js b/test/spec/modules/contxtfulBidAdapter_spec.js
new file mode 100644
index 00000000000..02cb3ccef8a
--- /dev/null
+++ b/test/spec/modules/contxtfulBidAdapter_spec.js
@@ -0,0 +1,496 @@
+import { spec } from 'modules/contxtfulBidAdapter.js';
+import { newBidder } from 'src/adapters/bidderFactory.js';
+import { config } from 'src/config.js';
+import * as ajax from 'src/ajax.js';
+const VERSION = 'v1';
+const CUSTOMER = 'CUSTOMER';
+const BIDDER_ENDPOINT = 'prebid.receptivity.io';
+const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' };
+
+describe('contxtful bid adapter', function () {
+ const adapter = newBidder(spec);
+ let sandbox;
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create();
+ });
+
+ afterEach(function () {
+ sandbox.restore();
+ });
+
+ describe('is a functions', function () {
+ it('exists and is a function', function () {
+ expect(adapter.callBids).to.exist.and.to.be.a('function');
+ });
+ });
+
+ describe('valid code', function () {
+ it('should return the bidder code of contxtful', function () {
+ expect(spec.code).to.eql('contxtful');
+ });
+ });
+
+ let bidRequests =
+ [
+ {
+ bidder: 'contxtful',
+ bidId: 'bId1',
+ custom_param_1: 'value_1',
+ transactionId: 'tId1',
+ params: {
+ bcat: ['cat1', 'cat2'],
+ badv: ['adv1', 'adv2'],
+ },
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600]
+ ]
+ },
+ },
+ ortb2Imp: {
+ ext: {
+ tid: 't-id-test-1',
+ gpid: 'gpid-id-unitest-1'
+ },
+ },
+ schain: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [
+ {
+ asi: 'schain-seller-1.com',
+ sid: '00001',
+ hp: 1,
+ },
+ ],
+ },
+ getFloor: () => ({ currency: 'CAD', floor: 10 }),
+ }
+ ];
+
+ let expectedReceptivityData = {
+ rx: RX_FROM_API,
+ params: {
+ ev: VERSION,
+ ci: CUSTOMER,
+ },
+ };
+
+ let bidderRequest = {
+ refererInfo: {
+ ref: 'https://my-referer-custom.com',
+ },
+ ortb2: {
+ source: {
+ tid: 'auction-id',
+ },
+ property_1: 'string_val_1',
+ regs: {
+ coppa: 1,
+ ext: {
+ us_privacy: '12345'
+ }
+ },
+ user: {
+ data: [
+ {
+ name: 'contxtful',
+ ext: expectedReceptivityData
+ }
+ ],
+ ext: {
+ eids: [
+ {
+ source: 'id5-sync.com',
+ uids: [
+ {
+ atype: 1,
+ id: 'fake-id5id',
+ },
+ ]
+ }
+ ]
+ }
+ }
+
+ },
+ timeout: 1234,
+ uspConsent: '12345'
+ };
+
+ describe('valid configuration', function() {
+ const theories = [
+ [
+ null,
+ 'contxfulBidAdapter: contxtful.version should be a non-empty string',
+ 'null object for config',
+ ],
+ [
+ {},
+ 'contxfulBidAdapter: contxtful.version should be a non-empty string',
+ 'empty object for config',
+ ],
+ [
+ { customer: CUSTOMER },
+ 'contxfulBidAdapter: contxtful.version should be a non-empty string',
+ 'customer only in config',
+ ],
+ [
+ { version: VERSION },
+ 'contxfulBidAdapter: contxtful.customer should be a non-empty string',
+ 'version only in config',
+ ],
+ [
+ { customer: CUSTOMER, version: '' },
+ 'contxfulBidAdapter: contxtful.version should be a non-empty string',
+ 'empty string for version',
+ ],
+ [
+ { customer: '', version: VERSION },
+ 'contxfulBidAdapter: contxtful.customer should be a non-empty string',
+ 'empty string for customer',
+ ],
+ [
+ { customer: '', version: '' },
+ 'contxfulBidAdapter: contxtful.version should be a non-empty string',
+ 'empty string for version & customer',
+ ],
+ ];
+
+ theories.forEach(([params, expectedErrorMessage, description]) => {
+ it('detects invalid configuration and throws the expected error (' + description + ')', () => {
+ config.setConfig({
+ contxtful: params
+ });
+ expect(() => spec.buildRequests(bidRequests, {
+ auctionId: 'new-auction-id'
+ })).to.throw(
+ expectedErrorMessage
+ );
+ });
+ });
+
+ it('uses a valid configuration and returns the right url', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION}
+ });
+ const bidRequest = spec.buildRequests(bidRequests);
+ expect(bidRequest.url).to.eq('https://' + BIDDER_ENDPOINT + `/${VERSION}/prebid/${CUSTOMER}/bid`)
+ });
+
+ it('will take specific ortb2 configuration parameters and returns it in ortb2 object', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+ const bidRequest = spec.buildRequests(bidRequests, bidderRequest);
+ expect(bidRequest.data.ortb2.property_1).to.equal('string_val_1');
+ });
+ });
+
+ describe('valid bid request', function () {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+ const bidRequest = spec.buildRequests(bidRequests, bidderRequest);
+
+ it('will return a data property containing properties ortb2, bidRequests, bidderRequest and config', () => {
+ expect(bidRequest.data).not.to.be.undefined;
+ expect(bidRequest.data.ortb2).not.to.be.undefined;
+ expect(bidRequest.data.bidRequests).not.to.be.undefined;
+ expect(bidRequest.data.bidderRequest).not.to.be.undefined;
+ expect(bidRequest.data.config).not.to.be.undefined;
+ });
+
+ it('will take custom parameters in the bid request and within the bidRequests array', () => {
+ expect(bidRequest.data.bidRequests[0].custom_param_1).to.equal('value_1')
+ });
+
+ it('will return any supply chain parameters within the bidRequests array', () => {
+ expect(bidRequest.data.bidRequests[0].schain.ver).to.equal('1.0');
+ });
+
+ it('will return floor request within the bidFloor parameter in the bidRequests array', () => {
+ expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('CAD');
+ expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(10);
+ });
+
+ it('will return the usp string in the uspConsent parameter within the bidderRequest property', () => {
+ expect(bidRequest.data.bidderRequest.uspConsent).to.equal('12345');
+ });
+
+ it('will contains impressions array on ortb2.imp object for all ad units', () => {
+ expect(bidRequest.data.ortb2.imp.length).to.equal(1);
+ expect(bidRequest.data.ortb2.imp[0].id).to.equal('bId1');
+ });
+
+ it('will contains the registration on ortb2.regs object', () => {
+ expect(bidRequest.data.ortb2.regs).not.to.be.undefined;
+ expect(bidRequest.data.ortb2.regs.coppa).to.equal(1);
+ expect(bidRequest.data.ortb2.regs.ext.us_privacy).to.equal('12345')
+ })
+
+ it('will contains the eids modules within the ortb2.user.ext.eids', () => {
+ expect(bidRequest.data.ortb2.user.ext.eids).not.to.be.undefined;
+ expect(bidRequest.data.ortb2.user.ext.eids[0].source).to.equal('id5-sync.com');
+ expect(bidRequest.data.ortb2.user.ext.eids[0].uids[0].id).to.equal('fake-id5id');
+ });
+
+ it('will contains the receptivity value within the ortb2.user.data with contxtful name', () => {
+ let obtained_receptivity_data = bidRequest.data.ortb2.user.data.filter(function(userData) {
+ return userData.name == 'contxtful';
+ });
+ expect(obtained_receptivity_data.length).to.equal(1);
+ expect(obtained_receptivity_data[0].ext).to.deep.equal(expectedReceptivityData);
+ });
+
+ it('will contains ortb2Imp of the bid request within the ortb2.imp.ext', () => {
+ let first_imp = bidRequest.data.ortb2.imp[0];
+ expect(first_imp.ext).not.to.be.undefined;
+ expect(first_imp.ext.tid).to.equal('t-id-test-1');
+ expect(first_imp.ext.gpid).to.equal('gpid-id-unitest-1');
+ });
+ });
+
+ describe('valid bid request with no floor module', () => {
+ let noFloorsBidRequests =
+ [
+ {
+ bidder: 'contxtful',
+ bidId: 'bId1',
+ transactionId: 'tId1',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600]
+ ]
+ },
+ },
+ },
+ {
+ bidder: 'contxtful',
+ bidId: 'bId2',
+ transactionId: 'tId2',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600]
+ ]
+ },
+ },
+ params: {
+ bidfloor: 54
+ }
+ },
+ ];
+
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+
+ const bidRequest = spec.buildRequests(noFloorsBidRequests, bidderRequest);
+ it('will contains default value of floor if the bid request do not contains floor function', () => {
+ expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('USD');
+ expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(0);
+ });
+
+ it('will take the param.bidfloor as floor value if possible', () => {
+ expect(bidRequest.data.bidRequests[1].bidFloor.currency).to.equal('USD');
+ expect(bidRequest.data.bidRequests[1].bidFloor.floor).to.equal(54);
+ });
+ });
+
+ describe('valid bid response', () => {
+ const bidResponse = [
+ {
+ 'requestId': 'arequestId',
+ 'originalCpm': 1.5,
+ 'cpm': 1.35,
+ 'currency': 'CAD',
+ 'width': 300,
+ 'height': 600,
+ 'creativeId': 'creativeid',
+ 'netRevenue': true,
+ 'ttl': 300,
+ 'ad': '',
+ 'mediaType': 'banner',
+ 'syncs': [
+ {
+ 'url': 'mysyncurl.com?qparam1=qparamv1&qparam2=qparamv2'
+ }
+ ]
+ }
+ ];
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+
+ const bidRequest = spec.buildRequests(bidRequests, bidderRequest);
+
+ it('will interpret response correcly', () => {
+ const bids = spec.interpretResponse({ body: bidResponse }, bidRequest);
+ expect(bids).not.to.be.undefined;
+ expect(bids).to.have.lengthOf(1);
+ expect(bids).to.deep.equal(bidResponse);
+ });
+
+ it('will return empty response if bid response is empty', () => {
+ const bids = spec.interpretResponse({ body: [] }, bidRequest);
+ expect(bids).to.have.lengthOf(0);
+ })
+
+ it('will trigger user sync if enable pixel mode', () => {
+ const syncOptions = {
+ pixelEnabled: true
+ };
+
+ const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]);
+ expect(userSyncs).to.deep.equal([
+ {
+ 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2',
+ 'type': 'image'
+ }
+ ]);
+ });
+
+ it('will trigger user sync if enable iframe mode', () => {
+ const syncOptions = {
+ iframeEnabled: true
+ };
+
+ const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]);
+ expect(userSyncs).to.deep.equal([
+ {
+ 'url': 'mysyncurl.com/iframe?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2',
+ 'type': 'iframe'
+ }
+ ]);
+ });
+
+ describe('no sync option', () => {
+ it('will return image sync if no sync options', () => {
+ const userSyncs = spec.getUserSyncs({}, [{ body: bidResponse }]);
+ expect(userSyncs).to.deep.equal([
+ {
+ 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2',
+ 'type': 'image'
+ }
+ ]);
+ });
+ it('will return empty value if no server response', () => {
+ const userSyncs = spec.getUserSyncs({}, []);
+ expect(userSyncs).to.have.lengthOf(0);
+ const userSyncs2 = spec.getUserSyncs({}, null);
+ expect(userSyncs2).to.have.lengthOf(0);
+ });
+ });
+
+ it('will return empty value if no server response', () => {
+ const syncOptions = {
+ iframeEnabled: true
+ };
+
+ const userSyncs = spec.getUserSyncs(syncOptions, []);
+ expect(userSyncs).to.have.lengthOf(0);
+ const userSyncs2 = spec.getUserSyncs(syncOptions, null);
+ expect(userSyncs2).to.have.lengthOf(0);
+ });
+
+ describe('on timeout callback', () => {
+ it('will never call server if sampling is 0 with sendBeacon available', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 0.0}},
+ });
+
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true);
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw;
+ expect(beaconStub.called).to.be.false;
+ expect(ajaxStub.called).to.be.false;
+ });
+
+ it('will always call server if sampling is 1 with sendBeacon available', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}},
+ });
+
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true);
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw;
+ expect(beaconStub.called).to.be.true;
+ expect(ajaxStub.called).to.be.false;
+ });
+
+ it('will always call server if sampling is 1 with sendBeacon not available', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onTimeout': 1.0}},
+ });
+
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
+ expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw;
+ expect(beaconStub.called).to.be.true;
+ expect(beaconStub.returned(false)).to.be.true;
+ expect(ajaxStub.calledOnce).to.be.true;
+ });
+ });
+
+ describe('on onBidderError callback', () => {
+ it('will always call server if sampling is 1', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION, 'sampling': {'onBidderError': 1.0}},
+ });
+
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
+ spec.onBidderError({'customData': 'customvalue'});
+ expect(ajaxStub.calledOnce).to.be.true;
+ expect(beaconStub.returned(false)).to.be.true;
+ });
+ });
+
+ describe('on onBidWon callback', () => {
+ it('will always call server', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
+ spec.onBidWon({'customData': 'customvalue'});
+ expect(ajaxStub.calledOnce).to.be.true;
+ expect(beaconStub.returned(false)).to.be.true;
+ });
+ });
+
+ describe('on onBidBillable callback', () => {
+ it('will always call server', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
+ spec.onBidBillable({'customData': 'customvalue'});
+ expect(ajaxStub.calledOnce).to.be.true;
+ expect(beaconStub.returned(false)).to.be.true;
+ });
+ });
+
+ describe('on onAdRenderSucceeded callback', () => {
+ it('will always call server', () => {
+ config.setConfig({
+ contxtful: {customer: CUSTOMER, version: VERSION},
+ });
+ const ajaxStub = sandbox.stub(ajax, 'ajax');
+ const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false);
+ spec.onAdRenderSucceeded({'customData': 'customvalue'});
+ expect(ajaxStub.calledOnce).to.be.true;
+ expect(beaconStub.returned(false)).to.be.true;
+ });
+ });
+ });
+});
diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js
new file mode 100644
index 00000000000..c52507a000b
--- /dev/null
+++ b/test/spec/modules/equativBidAdapter_spec.js
@@ -0,0 +1,536 @@
+import { BANNER } from 'src/mediaTypes.js';
+import { getBidFloor } from 'libraries/equativUtils/equativUtils.js'
+import { converter, spec, storage } from 'modules/equativBidAdapter.js';
+
+describe('Equativ bid adapter tests', () => {
+ const DEFAULT_BID_REQUESTS = [
+ {
+ adUnitCode: 'eqtv_42',
+ bidId: 'abcd1234',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [300, 250],
+ [300, 600],
+ ],
+ },
+ },
+ bidder: 'equativ',
+ params: {
+ networkId: 111,
+ },
+ requestId: 'efgh5678',
+ ortb2Imp: {
+ ext: {
+ tid: 'zsfgzzg',
+ },
+ },
+ },
+ ];
+
+ const DEFAULT_BIDDER_REQUEST = {
+ bidderCode: 'equativ',
+ bids: DEFAULT_BID_REQUESTS,
+ };
+
+ const SAMPLE_RESPONSE = {
+ body: {
+ id: '12h712u7-k22g-8124-ab7a-h268s22dy271',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '1bh7jku7-ko2g-8654-ab72-h268shvwy271',
+ impid: 'r12gwgf231',
+ price: 0.6565,
+ adm: 'AD
',
+ adomain: ['abc.com'],
+ cid: '1242512',
+ crid: '535231',
+ w: 300,
+ h: 600,
+ mtype: 1,
+ cat: ['IAB19', 'IAB19-1'],
+ cattax: 1,
+ },
+ ],
+ seat: '4212',
+ },
+ ],
+ cur: 'USD',
+ statuscode: 0,
+ },
+ };
+
+ // const RESPONSE_WITH_DSP_PIXELS = {
+ // ...SAMPLE_RESPONSE,
+ // body: {
+ // dspPixels: ['1st-pixel', '2nd-pixel', '3rd-pixel']
+ // }
+ // };
+
+ describe('buildRequests', () => {
+ it('should build correct request using ORTB converter', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ const dataFromConverter = converter.toORTB({
+ bidderRequest: DEFAULT_BIDDER_REQUEST,
+ bidRequests: DEFAULT_BID_REQUESTS,
+ });
+ expect(request).to.deep.equal({
+ data: { ...dataFromConverter, id: request.data.id },
+ method: 'POST',
+ url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169',
+ });
+ });
+
+ it('should add ext.bidder to imp object when siteId is defined', () => {
+ const bidRequests = [
+ { ...DEFAULT_BID_REQUESTS[0], params: { siteId: 123 } },
+ ];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.imp[0].ext.bidder).to.deep.equal({
+ siteId: 123,
+ });
+ });
+
+ it('should add ext.bidder to imp object when pageId is defined', () => {
+ const bidRequests = [
+ { ...DEFAULT_BID_REQUESTS[0], params: { pageId: 123 } },
+ ];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.imp[0].ext.bidder).to.deep.equal({
+ pageId: 123,
+ });
+ });
+
+ it('should add ext.bidder to imp object when formatId is defined', () => {
+ const bidRequests = [
+ { ...DEFAULT_BID_REQUESTS[0], params: { formatId: 123 } },
+ ];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.imp[0].ext.bidder).to.deep.equal({
+ formatId: 123,
+ });
+ });
+
+ it('should not add ext.bidder to imp object when siteId, pageId, formatId are not defined', () => {
+ const bidRequests = [{ ...DEFAULT_BID_REQUESTS[0], params: {} }];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.imp[0].ext.bidder).to.be.undefined;
+ });
+
+ it('should add site.publisher.id param', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ expect(request.data.site.publisher.id).to.equal(111);
+ });
+
+ it('should pass ortb2.site.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ site: {
+ publisher: {
+ id: 98,
+ }
+ }
+ }
+ }];
+ delete bidRequests[0].params;
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.site.publisher.id).to.equal(98);
+ });
+
+ it('should pass networkId as site.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ site: {
+ publisher: {}
+ }
+ }
+ }];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.site.publisher.id).to.equal(111);
+ });
+
+ it('should pass ortb2.app.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ app: {
+ publisher: {
+ id: 27,
+ }
+ }
+ }
+ }];
+ delete bidRequests[0].params;
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.app.publisher.id).to.equal(27);
+ });
+
+ it('should pass networkId as app.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ app: {
+ publisher: {}
+ }
+ }
+ }];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.app.publisher.id).to.equal(111);
+ });
+
+ it('should pass ortb2.dooh.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ dooh: {
+ publisher: {
+ id: 35,
+ }
+ }
+ }
+ }];
+ delete bidRequests[0].params;
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.dooh.publisher.id).to.equal(35);
+ });
+
+ it('should pass networkId as dooh.publisher.id', () => {
+ const bidRequests = [{
+ ...DEFAULT_BID_REQUESTS[0],
+ ortb2: {
+ dooh: {
+ publisher: {}
+ }
+ }
+ }];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.dooh.publisher.id).to.equal(111);
+ });
+
+ it('should send default floor of 0.0', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ expect(request.data.imp[0]).to.have.property('bidfloor').that.eq(0.0);
+ });
+
+ it('should send secure connection', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ expect(request.data.imp[0]).to.have.property('secure').that.eq(1);
+ });
+
+ it('should have tagid', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BID_REQUESTS[0].adUnitCode);
+ });
+
+ it('should remove dt', () => {
+ const bidRequests = [
+ { ...DEFAULT_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } }
+ ];
+ const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests };
+ const request = spec.buildRequests(bidRequests, bidderRequest);
+ expect(request.data.imp[0]).to.not.have.property('dt');
+ });
+
+ it('should read and send pid as buyeruid', () => {
+ const cookieData = {
+ 'eqt_pid': '7789746781'
+ };
+ const getCookieStub = sinon.stub(storage, 'getCookie');
+ getCookieStub.callsFake(cookieName => cookieData[cookieName]);
+
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+
+ expect(request.data.user).to.have.property('buyeruid').that.eq(cookieData['eqt_pid']);
+
+ getCookieStub.restore();
+ });
+
+ it('should not send buyeruid', () => {
+ const getCookieStub = sinon.stub(storage, 'getCookie');
+ getCookieStub.callsFake(() => null);
+
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+
+ expect(request.data).to.not.have.property('user');
+
+ getCookieStub.restore();
+ });
+
+ it('should pass buyeruid defined in config', () => {
+ const getCookieStub = sinon.stub(storage, 'getCookie');
+ getCookieStub.callsFake(() => undefined);
+
+ const bidRequest = {
+ ...DEFAULT_BIDDER_REQUEST,
+ ortb2: {
+ user: {
+ buyeruid: 'buyeruid-provided-by-publisher'
+ }
+ }
+ };
+ const request = spec.buildRequests([ DEFAULT_BID_REQUESTS[0] ], bidRequest);
+
+ expect(request.data.user.buyeruid).to.deep.eq(bidRequest.ortb2.user.buyeruid);
+
+ getCookieStub.restore();
+ });
+ });
+
+ describe('getBidFloor', () => {
+ it('should return floor of 0.0 if floor module not available', () => {
+ const bid = {
+ ...DEFAULT_BID_REQUESTS[0],
+ getFloor: false,
+ };
+ expect(getBidFloor(bid)).to.deep.eq(0.0);
+ });
+
+ it('should return floor of 0.0 if mediaTypes not defined', () => {
+ const bid = {
+ getFloor: () => ({})
+ };
+ expect(bid.mediaTypes).to.be.undefined;
+ expect(getBidFloor(bid)).to.deep.eq(0.0);
+ });
+
+ it('should return proper min floor', () => {
+ const bid = {
+ ...DEFAULT_BID_REQUESTS[0],
+ getFloor: data => {
+ if (data.size[0] === 300 && data.size[1] === 250) {
+ return { floor: 1.13 };
+ } else if (data.size[0] === 300 && data.size[1] === 600) {
+ return { floor: 1.39 };
+ } else {
+ return { floor: 0.52 };
+ }
+ }
+ };
+ expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(1.13);
+ });
+
+ it('should return global media type floor if no rule for size', () => {
+ const bid = {
+ ...DEFAULT_BID_REQUESTS[0],
+ getFloor: data => {
+ if (data.size[0] === 728 && data.size[1] === 90) {
+ return { floor: 1.13 };
+ } else if (data.size[0] === 300 && data.size[1] === 600) {
+ return { floor: 1.36 };
+ } else {
+ return { floor: 0.34 };
+ }
+ }
+ };
+ expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(0.34);
+ });
+
+ it('should return floor of 0 if no rule for size', () => {
+ const bid = {
+ ...DEFAULT_BID_REQUESTS[0],
+ getFloor: data => {
+ if (data.size[0] === 728 && data.size[1] === 90) {
+ return { floor: 1.13 };
+ } else if (data.size[0] === 300 && data.size[1] === 600) {
+ return { floor: 1.36 };
+ } else {
+ return {};
+ }
+ }
+ };
+ expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(0.0);
+ });
+ });
+
+ describe('getUserSyncs', () => {
+ let setCookieStub;
+
+ beforeEach(() => setCookieStub = sinon.stub(storage, 'setCookie'));
+
+ afterEach(() => setCookieStub.restore());
+
+ it('should return empty array if iframe sync not enabled', () => {
+ const syncs = spec.getUserSyncs({}, SAMPLE_RESPONSE);
+ expect(syncs).to.deep.equal([]);
+ });
+
+ it('should retrieve and save user pid', (done) => {
+ const userSyncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ SAMPLE_RESPONSE
+ );
+
+ window.dispatchEvent(new MessageEvent('message', {
+ data: {
+ pid: '7767825890726'
+ },
+ origin: 'https://apps.smartadserver.com'
+ }));
+
+ const exp = new Date();
+ exp.setTime(Date.now() + 31536000000);
+
+ setTimeout(() => {
+ expect(setCookieStub.calledOnce).to.be.true;
+ expect(setCookieStub.calledWith('eqt_pid', '7767825890726', exp.toUTCString())).to.be.true;
+ done();
+ });
+ });
+
+ it('should not save user pid coming from not origin', (done) => {
+ const userSyncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ SAMPLE_RESPONSE
+ );
+
+ window.dispatchEvent(new MessageEvent('message', {
+ data: {
+ pid: '7767825890726'
+ },
+ origin: 'https://another-origin.com'
+ }));
+
+ setTimeout(() => {
+ expect(setCookieStub.notCalled).to.be.true;
+ done();
+ });
+ });
+
+ it('should not save empty pid', (done) => {
+ const userSyncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ SAMPLE_RESPONSE
+ );
+
+ window.dispatchEvent(new MessageEvent('message', {
+ data: {
+ pid: ''
+ },
+ origin: 'https://apps.smartadserver.com'
+ }));
+
+ setTimeout(() => {
+ expect(setCookieStub.notCalled).to.be.true;
+ done();
+ });
+ });
+
+ it('should return array including iframe cookie sync object', () => {
+ const syncs = spec.getUserSyncs(
+ { iframeEnabled: true },
+ SAMPLE_RESPONSE
+ );
+ expect(syncs).to.have.lengthOf(1);
+ expect(syncs[0]).to.deep.equal({
+ type: 'iframe',
+ url: 'https://apps.smartadserver.com/diff/templates/asset/csync.html'
+ });
+ });
+ });
+
+ describe('interpretResponse', () => {
+ it('should return data returned by ORTB converter', () => {
+ const request = spec.buildRequests(
+ DEFAULT_BID_REQUESTS,
+ DEFAULT_BIDDER_REQUEST
+ );
+ const bids = spec.interpretResponse(SAMPLE_RESPONSE, request);
+ expect(bids).to.deep.equal(
+ converter.fromORTB({
+ request: request.data,
+ response: SAMPLE_RESPONSE.body,
+ })
+ );
+ });
+ });
+
+ describe('isBidRequestValid', () => {
+ it('should return true if params.networkId is set', () => {
+ const bidRequest = {
+ params: {
+ networkId: 123,
+ },
+ };
+ expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
+ });
+
+ it('should return true if ortb2.site.publisher.id is set', () => {
+ const bidRequest = {
+ ortb2: {
+ site: {
+ publisher: {
+ id: 123,
+ },
+ },
+ },
+ };
+ expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
+ });
+
+ it('should return true if ortb2.app.publisher.id is set', () => {
+ const bidRequest = {
+ ortb2: {
+ app: {
+ publisher: {
+ id: 123,
+ },
+ },
+ },
+ };
+ expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
+ });
+
+ it('should return true if ortb2.dooh.publisher.id is set', () => {
+ const bidRequest = {
+ ortb2: {
+ dooh: {
+ publisher: {
+ id: 123,
+ },
+ },
+ },
+ };
+ expect(spec.isBidRequestValid(bidRequest)).to.equal(true);
+ });
+
+ it('should return false if networkId is not set', () => {
+ const bidRequest = {};
+ expect(spec.isBidRequestValid(bidRequest)).to.equal(false);
+ });
+ });
+});
diff --git a/test/spec/modules/gameraRtdProvider_spec.js b/test/spec/modules/gameraRtdProvider_spec.js
new file mode 100644
index 00000000000..63029d85545
--- /dev/null
+++ b/test/spec/modules/gameraRtdProvider_spec.js
@@ -0,0 +1,223 @@
+import { submodule } from 'src/hook.js';
+import { getGlobal } from 'src/prebidGlobal.js';
+import * as utils from 'src/utils.js';
+import { subModuleObj } from 'modules/gameraRtdProvider.js';
+
+describe('gameraRtdProvider', function () {
+ let logErrorSpy;
+
+ beforeEach(function () {
+ logErrorSpy = sinon.spy(utils, 'logError');
+ });
+
+ afterEach(function () {
+ logErrorSpy.restore();
+ });
+
+ describe('subModuleObj', function () {
+ it('should have the correct module name', function () {
+ expect(subModuleObj.name).to.equal('gamera');
+ });
+
+ it('successfully instantiates and returns true', function () {
+ expect(subModuleObj.init()).to.equal(true);
+ });
+ });
+
+ describe('getBidRequestData', function () {
+ const reqBidsConfigObj = {
+ adUnits: [{
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ ortb2Imp: {
+ ext: {
+ data: {
+ pbadslot: 'homepage-top-rect',
+ adUnitSpecificAttribute: '123',
+ }
+ }
+ },
+ bids: [{ bidder: 'test' }]
+ }],
+ ortb2Fragments: {
+ global: {
+ site: {
+ name: 'example',
+ domain: 'page.example.com',
+ // OpenRTB 2.5 spec / Content Taxonomy
+ cat: ['IAB2'],
+ sectioncat: ['IAB2-2'],
+ pagecat: ['IAB2-2'],
+
+ page: 'https://page.example.com/here.html',
+ ref: 'https://ref.example.com',
+ keywords: 'power tools, drills',
+ search: 'drill',
+ content: {
+ userrating: '4',
+ data: [{
+ name: 'www.dataprovider1.com', // who resolved the segments
+ ext: {
+ segtax: 7, // taxonomy used to encode the segments
+ cids: ['iris_c73g5jq96mwso4d8']
+ },
+ // the bare minimum are the IDs. These IDs are the ones from the new IAB Content Taxonomy v3
+ segment: [{ id: '687' }, { id: '123' }]
+ }]
+ },
+ ext: {
+ data: { // fields that aren't part of openrtb 2.6
+ pageType: 'article',
+ category: 'repair'
+ }
+ }
+ },
+ // this is where the user data is placed
+ user: {
+ keywords: 'a,b',
+ data: [{
+ name: 'dataprovider.com',
+ ext: {
+ segtax: 4
+ },
+ segment: [{
+ id: '1'
+ }]
+ }],
+ ext: {
+ data: {
+ registered: true,
+ interests: ['cars']
+ }
+ }
+ }
+ }
+ }
+ };
+
+ let callback;
+
+ beforeEach(function () {
+ callback = sinon.spy();
+ window.gamera = undefined;
+ });
+
+ it('should queue command when gamera.getPrebidSegments is not available', function () {
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(window.gamera).to.exist;
+ expect(window.gamera.cmd).to.be.an('array');
+ expect(window.gamera.cmd.length).to.equal(1);
+ expect(callback.called).to.be.false;
+
+ // our callback should be executed if command queue is flushed
+ window.gamera.cmd.forEach(command => command());
+ expect(callback.calledOnce).to.be.true;
+ });
+
+ it('should call enrichAuction directly when gamera.getPrebidSegments is available', function () {
+ window.gamera = {
+ getPrebidSegments: () => ({})
+ };
+
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(callback.calledOnce).to.be.true;
+ });
+
+ it('should handle errors gracefully', function () {
+ window.gamera = {
+ getPrebidSegments: () => {
+ throw new Error('Test error');
+ }
+ };
+
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(logErrorSpy.calledWith('gameraRtdProvider', 'Error getting segments:')).to.be.true;
+ expect(callback.calledOnce).to.be.true;
+ });
+
+ describe('segment enrichment', function () {
+ const mockSegments = {
+ user: {
+ data: [{
+ name: 'gamera.ai',
+ ext: {
+ segtax: 4,
+ },
+ segment: [{ id: 'user-1' }]
+ }]
+ },
+ site: {
+ keywords: 'gamera,article,keywords',
+ content: {
+ data: [{
+ name: 'gamera.ai',
+ ext: {
+ segtax: 7,
+ },
+ segment: [{ id: 'site-1' }]
+ }]
+ }
+ },
+ adUnits: {
+ 'test-div': {
+ key: 'value',
+ ext: {
+ data: {
+ gameraSegment: 'ad-1',
+ }
+ }
+ }
+ }
+ };
+
+ beforeEach(function () {
+ window.gamera = {
+ getPrebidSegments: () => mockSegments
+ };
+ });
+
+ it('should enrich ortb2Fragments with user data', function () {
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(reqBidsConfigObj.ortb2Fragments.global.user.data).to.deep.include(mockSegments.user.data[0]);
+
+ // check if existing attributes are not overwritten
+ expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4);
+ expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].segment[0].id).to.equal('1');
+ expect(reqBidsConfigObj.ortb2Fragments.global.user.keywords).to.equal('a,b');
+ expect(reqBidsConfigObj.ortb2Fragments.global.user.ext.data.registered).to.equal(true);
+ });
+
+ it('should enrich ortb2Fragments with site data', function () {
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data).to.deep.include(mockSegments.site.content.data[0]);
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal('gamera,article,keywords');
+
+ // check if existing attributes are not overwritten
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(7);
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment[0].id).to.equal('687');
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.ext.data.category).to.equal('repair');
+ expect(reqBidsConfigObj.ortb2Fragments.global.site.content.userrating).to.equal('4');
+ });
+
+ it('should enrich adUnits with segment data', function () {
+ subModuleObj.getBidRequestData(reqBidsConfigObj, callback);
+
+ expect(reqBidsConfigObj.adUnits[0].ortb2Imp.key).to.equal('value');
+ expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.gameraSegment).to.equal('ad-1');
+
+ // check if existing attributes are not overwritten
+ expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.adUnitSpecificAttribute).to.equal('123');
+ expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.pbadslot).to.equal('homepage-top-rect');
+ });
+ });
+ });
+});
diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js
index 899dc640dc1..70aaf06bcc8 100644
--- a/test/spec/modules/hadronIdSystem_spec.js
+++ b/test/spec/modules/hadronIdSystem_spec.js
@@ -1,15 +1,15 @@
-import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.js';
-import { server } from 'test/mocks/xhr.js';
-import * as utils from 'src/utils.js';
+import {hadronIdSubmodule, storage, LS_TAM_KEY} from 'modules/hadronIdSystem.js';
+import {server} from 'test/mocks/xhr.js';
import {attachIdSystem} from '../../../modules/userId/index.js';
import {createEidsArray} from '../../../modules/userId/eids.js';
import {expect} from 'chai/index.mjs';
describe('HadronIdSystem', function () {
- describe('getId', function() {
+ const HADRON_TEST = 'tstCachedHadronId1';
+ describe('getId', function () {
let getDataFromLocalStorageStub;
- beforeEach(function() {
+ beforeEach(function () {
getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage');
});
@@ -17,42 +17,27 @@ describe('HadronIdSystem', function () {
getDataFromLocalStorageStub.restore();
});
- it('gets a hadronId', function() {
+ it('gets a cached hadronid', function () {
const config = {
params: {}
};
- const callbackSpy = sinon.spy();
- const callback = hadronIdSubmodule.getId(config).callback;
- callback(callbackSpy);
- const request = server.requests[0];
- expect(request.url).to.match(/^https:\/\/id\.hadron\.ad\.gt\/api\/v1\/pbhid/);
- request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' }));
- expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } });
- });
-
- it('gets a cached hadronid', function() {
- const config = {
- params: {}
- };
- getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1');
-
+ getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(HADRON_TEST);
const result = hadronIdSubmodule.getId(config);
- expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } });
+ expect(result).to.deep.equal({id: HADRON_TEST});
});
- it('allows configurable id url', function() {
+ it('allows configurable id url', function () {
const config = {
params: {
url: 'https://hadronid.publync.com'
}
};
+ getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(null);
const callbackSpy = sinon.spy();
const callback = hadronIdSubmodule.getId(config).callback;
callback(callbackSpy);
const request = server.requests[0];
expect(request.url).to.match(/^https:\/\/hadronid\.publync\.com\//);
- request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' }));
- expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } });
});
});
@@ -60,7 +45,7 @@ describe('HadronIdSystem', function () {
before(() => {
attachIdSystem(hadronIdSubmodule);
});
- it('hadronId', function() {
+ it('hadronId', function () {
const userId = {
hadronId: 'some-random-id-value'
};
diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js
index 140855194c5..46877f246b5 100644
--- a/test/spec/modules/hadronRtdProvider_spec.js
+++ b/test/spec/modules/hadronRtdProvider_spec.js
@@ -1,13 +1,20 @@
import {config} from 'src/config.js';
-import {HADRONID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js';
+import {
+ HADRONID_LOCAL_NAME,
+ RTD_LOCAL_NAME,
+ addRealTimeData,
+ getRealTimeData,
+ hadronSubmodule,
+ storage
+} from 'modules/hadronRtdProvider.js';
import {server} from 'test/mocks/xhr.js';
const responseHeader = {'Content-Type': 'application/json'};
-describe('hadronRtdProvider', function() {
+describe('hadronRtdProvider', function () {
let getDataFromLocalStorageStub;
- beforeEach(function() {
+ beforeEach(function () {
config.resetConfig();
getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage');
});
@@ -16,19 +23,19 @@ describe('hadronRtdProvider', function() {
getDataFromLocalStorageStub.restore();
});
- describe('hadronSubmodule', function() {
+ describe('hadronSubmodule', function () {
it('successfully instantiates', function () {
- expect(hadronSubmodule.init()).to.equal(true);
+ expect(hadronSubmodule.init()).to.equal(true);
});
});
- describe('Add Real-Time Data', function() {
- it('merges ortb2 data', function() {
+ describe('Add Real-Time Data', function () {
+ it('merges ortb2 data', function () {
let rtdConfig = {};
const setConfigUserObj1 = {
name: 'www.dataprovider1.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
+ ext: {taxonomyname: 'iab_audience_taxonomy'},
segment: [{
id: '1776'
}]
@@ -36,7 +43,7 @@ describe('hadronRtdProvider', function() {
const setConfigUserObj2 = {
name: 'www.dataprovider2.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
+ ext: {taxonomyname: 'iab_audience_taxonomy'},
segment: [{
id: '1914'
}]
@@ -123,12 +130,12 @@ describe('hadronRtdProvider', function() {
expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]);
});
- it('merges ortb2 data without duplication', function() {
+ it('merges ortb2 data without duplication', function () {
let rtdConfig = {};
const userObj1 = {
name: 'www.dataprovider1.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
+ ext: {taxonomyname: 'iab_audience_taxonomy'},
segment: [{
id: '1776'
}]
@@ -136,7 +143,7 @@ describe('hadronRtdProvider', function() {
const userObj2 = {
name: 'www.dataprovider2.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
+ ext: {taxonomyname: 'iab_audience_taxonomy'},
segment: [{
id: '1914'
}]
@@ -195,12 +202,12 @@ describe('hadronRtdProvider', function() {
expect(ortb2Config.site.content.data).to.have.lengthOf(1);
});
- it('merges bidder-specific ortb2 data', function() {
+ it('merges bidder-specific ortb2 data', function () {
let rtdConfig = {};
const configUserObj1 = {
name: 'www.dataprovider1.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '1776'
}]
@@ -208,7 +215,7 @@ describe('hadronRtdProvider', function() {
const configUserObj2 = {
name: 'www.dataprovider2.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '1914'
}]
@@ -216,7 +223,7 @@ describe('hadronRtdProvider', function() {
const configUserObj3 = {
name: 'www.dataprovider1.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '2003'
}]
@@ -372,12 +379,12 @@ describe('hadronRtdProvider', function() {
expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]);
});
- it('merges bidder-specific ortb2 data without duplication', function() {
+ it('merges bidder-specific ortb2 data without duplication', function () {
let rtdConfig = {};
const userObj1 = {
name: 'www.dataprovider1.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '1776'
}]
@@ -385,7 +392,7 @@ describe('hadronRtdProvider', function() {
const userObj2 = {
name: 'www.dataprovider2.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '1914'
}]
@@ -393,7 +400,7 @@ describe('hadronRtdProvider', function() {
const userObj3 = {
name: 'www.dataprovider1.com',
- ext: { segtax: 3 },
+ ext: {segtax: 3},
segment: [{
id: '2003'
}]
@@ -501,10 +508,10 @@ describe('hadronRtdProvider', function() {
expect(ortb2Config.site.content.data).to.have.lengthOf(2);
});
- it('allows publisher defined rtd ortb2 logic', function() {
+ it('allows publisher defined rtd ortb2 logic', function () {
const rtdConfig = {
params: {
- handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) {
+ handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) {
if (rtd.ortb2.user.data[0].segment[0].id == '1776') {
pbConfig.setConfig({ortb2: rtd.ortb2});
} else {
@@ -518,7 +525,7 @@ describe('hadronRtdProvider', function() {
const rtdUserObj1 = {
name: 'www.dataprovider.com',
- ext: { taxonomyname: 'iab_audience_taxonomy' },
+ ext: {taxonomyname: 'iab_audience_taxonomy'},
segment: [{
id: '1776'
}]
@@ -564,10 +571,10 @@ describe('hadronRtdProvider', function() {
expect(config.getConfig().ortb2).to.deep.equal({});
});
- it('allows publisher defined adunit logic', function() {
+ it('allows publisher defined adunit logic', function () {
const rtdConfig = {
params: {
- handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) {
+ handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) {
var adUnits = bidConfig.adUnits;
for (var i = 0; i < adUnits.length; i++) {
var adUnit = adUnits[i];
@@ -629,8 +636,8 @@ describe('hadronRtdProvider', function() {
});
});
- describe('Get Real-Time Data', function() {
- it('gets rtd from local storage cache', function() {
+ describe('Get Real-Time Data', function () {
+ it('gets rtd from local storage cache', function () {
const rtdConfig = {
params: {
segmentCache: true
@@ -665,12 +672,12 @@ describe('hadronRtdProvider', function() {
};
getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd));
-
- getRealTimeData(bidConfig, () => {}, rtdConfig, {});
- expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]);
+ getRealTimeData(bidConfig, () => {
+ expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]);
+ }, rtdConfig, {});
});
- it('gets real-time data via async request', function() {
+ it('gets real-time data via async request', function () {
const setConfigSiteObj1 = {
name: 'www.audigent.com',
ext: {
@@ -736,16 +743,14 @@ describe('hadronRtdProvider', function() {
};
getDataFromLocalStorageStub.withArgs(HADRONID_LOCAL_NAME).returns('testHadronId1');
- getRealTimeData(bidConfig, () => {}, rtdConfig, {});
-
- let request = server.requests[0];
- let postData = JSON.parse(request.requestBody);
- expect(postData.config).to.have.deep.property('publisherId', 'testPub1');
- expect(postData.userIds).to.have.deep.property('hadronId', 'testHadronId1');
-
- request.respond(200, responseHeader, JSON.stringify(data));
-
- expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]);
+ getRealTimeData(bidConfig, () => {
+ let request = server.requests[0];
+ let postData = JSON.parse(request.requestBody);
+ expect(postData.config).to.have.deep.property('publisherId', 'testPub1');
+ expect(postData.userIds).to.have.deep.property('hadronId', 'testHadronId1');
+ request.respond(200, responseHeader, JSON.stringify(data));
+ expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]);
+ }, rtdConfig, {});
});
});
});
diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js
index e24c34dd1ab..a9121a7a59f 100644
--- a/test/spec/modules/kargoBidAdapter_spec.js
+++ b/test/spec/modules/kargoBidAdapter_spec.js
@@ -2030,31 +2030,49 @@ describe('kargo adapter tests', function() {
});
});
- describe('onTimeout', function() {
+ describe('onTimeout', function () {
+ let fetchStub;
+
beforeEach(function () {
- sinon.stub(utils, 'triggerPixel');
+ fetchStub = sinon.stub(global, 'fetch').resolves(); // Stub fetch globally
});
afterEach(function () {
- utils.triggerPixel.restore();
+ fetchStub.restore(); // Restore the original fetch function
});
- it('does not call triggerPixel if timeout data is not provided', function() {
+ it('does not call fetch if timeout data is not provided', function () {
spec.onTimeout(null);
- expect(utils.triggerPixel.callCount).to.equal(0);
+ expect(fetchStub.callCount).to.equal(0);
});
- it('calls triggerPixel if any timeout data is provided', function() {
+ it('calls fetch with the correct URLs if timeout data is provided', function () {
spec.onTimeout([
- {auctionId: 'test-auction-id', timeout: 400},
- {auctionId: 'test-auction-id-2', timeout: 100},
- {auctionId: 'test-auction-id-3', timeout: 450},
- {auctionId: 'test-auction-id-4', timeout: 500},
+ { auctionId: 'test-auction-id', timeout: 400 },
+ { auctionId: 'test-auction-id-2', timeout: 100 },
+ { auctionId: 'test-auction-id-3', timeout: 450 },
+ { auctionId: 'test-auction-id-4', timeout: 500 },
]);
- expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400')).to.be.true;
- expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100')).to.be.true;
- expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450')).to.be.true;
- expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500')).to.be.true;
+
+ expect(fetchStub.calledWith(
+ 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400',
+ { method: 'GET', keepalive: true }
+ )).to.be.true;
+
+ expect(fetchStub.calledWith(
+ 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100',
+ { method: 'GET', keepalive: true }
+ )).to.be.true;
+
+ expect(fetchStub.calledWith(
+ 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450',
+ { method: 'GET', keepalive: true }
+ )).to.be.true;
+
+ expect(fetchStub.calledWith(
+ 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500',
+ { method: 'GET', keepalive: true }
+ )).to.be.true;
});
});
});
diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js
index 1b5eeffecd2..3e49eb5fc4b 100644
--- a/test/spec/modules/liveIntentExternalIdSystem_spec.js
+++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js
@@ -54,7 +54,7 @@ describe('LiveIntentExternalId', function() {
},
{
clientRef: {},
- sourceEvent: { hash: '123' },
+ sourceEvent: { emailHash: '123' },
type: 'collect'
}])
});
@@ -120,7 +120,7 @@ describe('LiveIntentExternalId', function() {
expect(window.liQHub[1]).to.eql({
clientRef: {},
- sourceEvent: { hash: '58131bc547fb87af94cebdaf3102321f' },
+ sourceEvent: { emailHash: '58131bc547fb87af94cebdaf3102321f' },
type: 'collect'
})
@@ -179,7 +179,7 @@ describe('LiveIntentExternalId', function() {
},
{
clientRef: {},
- sourceEvent: { hash: '123' },
+ sourceEvent: { emailHash: '123' },
type: 'collect'
}])
});
@@ -205,7 +205,36 @@ describe('LiveIntentExternalId', function() {
},
{
clientRef: {},
- sourceEvent: { hash: '123' },
+ sourceEvent: { emailHash: '123' },
+ type: 'collect'
+ }])
+ });
+
+ it('should include the identifier data if it is present in config', function() {
+ const configParams = {
+ params: {
+ ...defaultConfigParams.params,
+ distributorId: 'did-1111',
+ emailHash: '123',
+ ipv4: 'foov4',
+ ipv6: 'foov6',
+ userAgent: 'bar'
+ }
+ }
+ liveIntentExternalIdSubmodule.decode({}, configParams);
+ expect(window.liQHub).to.eql([{
+ clientDetails: { name: 'prebid', version: '$prebid.version$' },
+ clientRef: {},
+ collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT },
+ consent: {},
+ integration: { distributorId: 'did-1111', publisherId: defaultConfigParams.params.publisherId, type: 'custom' },
+ partnerCookies: new Set(),
+ resolveSettings: { identityPartner: 'did-1111', timeout: DEFAULT_AJAX_TIMEOUT },
+ type: 'register_client'
+ },
+ {
+ clientRef: {},
+ sourceEvent: { emailHash: '123', ipv4: 'foov4', ipv6: 'foov6', userAgent: 'bar' },
type: 'collect'
}])
});
@@ -416,4 +445,19 @@ describe('LiveIntentExternalId', function() {
type: 'resolve'
})
});
+
+ it('should decode a sharethrough id to a separate object when present', function() {
+ const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, defaultConfigParams);
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a sonobi id to a separate object when present', function() {
+ const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, defaultConfigParams);
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a vidazoo id to a separate object when present', function() {
+ const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams);
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
});
diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js
index 1c9c19d51d7..a859b3e7995 100644
--- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js
+++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js
@@ -313,4 +313,19 @@ describe('LiveIntentMinimalId', function() {
);
expect(callBackSpy.calledOnce).to.be.true;
});
+
+ it('should decode a sharethrough id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a sonobi id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a vidazoo id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
});
diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js
index a630c73eb2b..50f51bd3dc8 100644
--- a/test/spec/modules/liveIntentIdSystem_spec.js
+++ b/test/spec/modules/liveIntentIdSystem_spec.js
@@ -369,6 +369,21 @@ describe('LiveIntentId', function() {
expect(callBackSpy.calledOnce).to.be.true;
});
+ it('should include ip4,ip6,userAgent if it\'s present', function(done) {
+ liveIntentIdSubmodule.getId({ params: {
+ ...defaultConfigParams,
+ ipv4: 'foov4',
+ ipv6: 'foov6',
+ userAgent: 'boo'
+ }});
+ setTimeout(() => {
+ let request = rpRequests()[0];
+ expect(request.url).to.match(/^https:\/\/rp\.liadm\.com\/j?.*pip=.*&pip6=.*$/)
+ expect(request.requestHeaders['X-LI-Provided-User-Agent']).to.be.eq('boo')
+ done();
+ }, 300);
+ });
+
it('should send an error when the cookie jar throws an unexpected error', function() {
getCookieStub.throws('CookieError', 'A message');
liveIntentIdSubmodule.getId({ params: defaultConfigParams });
@@ -511,6 +526,21 @@ describe('LiveIntentId', function() {
});
});
+ it('should decode a sharethrough id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, { params: defaultConfigParams });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a sonobi id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, { params: defaultConfigParams });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
+ it('should decode a vidazoo id to a separate object when present', function() {
+ const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams });
+ expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}});
+ });
+
describe('eid', () => {
before(() => {
attachIdSystem(liveIntentIdSubmodule);
@@ -785,5 +815,104 @@ describe('LiveIntentId', function() {
uids: [{id: 'some-random-id-value', atype: 3}]
});
});
+
+ it('sharethrough', function () {
+ const userId = {
+ sharethrough: { 'id': 'sample_id' }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'sharethrough.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3
+ }]
+ });
+ });
+
+ it('sharethrough with ext', function () {
+ const userId = {
+ sharethrough: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'sharethrough.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3,
+ ext: {
+ provider: 'some.provider.com'
+ }
+ }]
+ });
+ });
+
+ it('sonobi', function () {
+ const userId = {
+ sonobi: { 'id': 'sample_id' }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'liveintent.sonobi.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3
+ }]
+ });
+ });
+
+ it('sonobi with ext', function () {
+ const userId = {
+ sonobi: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'liveintent.sonobi.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3,
+ ext: {
+ provider: 'some.provider.com'
+ }
+ }]
+ });
+ });
+
+ it('vidazoo', function () {
+ const userId = {
+ vidazoo: { 'id': 'sample_id' }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'liveintent.vidazoo.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3
+ }]
+ });
+ });
+
+ it('vidazoo with ext', function () {
+ const userId = {
+ vidazoo: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } }
+ };
+ const newEids = createEidsArray(userId);
+ expect(newEids.length).to.equal(1);
+ expect(newEids[0]).to.deep.equal({
+ source: 'liveintent.vidazoo.com',
+ uids: [{
+ id: 'sample_id',
+ atype: 3,
+ ext: {
+ provider: 'some.provider.com'
+ }
+ }]
+ });
+ });
})
})
diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js
index 2b05a1f0830..6a143fa49ef 100644
--- a/test/spec/modules/missenaBidAdapter_spec.js
+++ b/test/spec/modules/missenaBidAdapter_spec.js
@@ -1,6 +1,8 @@
import { expect } from 'chai';
import { spec, storage } from 'modules/missenaBidAdapter.js';
import { BANNER } from '../../../src/mediaTypes.js';
+import { config } from 'src/config.js';
+import * as autoplay from 'libraries/autoplayDetection/autoplay.js';
const REFERRER = 'https://referer';
const REFERRER2 = 'https://referer2';
@@ -12,6 +14,9 @@ describe('Missena Adapter', function () {
storageAllowed: true,
},
};
+ let sandbox = sinon.sandbox.create();
+ sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true);
+ sandbox.stub(autoplay, 'isAutoplayEnabled').returns(false);
const bidId = 'abc';
const bid = {
@@ -69,6 +74,7 @@ describe('Missena Adapter', function () {
topmostLocation: REFERRER,
canonicalUrl: 'https://canonical',
},
+ ortb2: { regs: { coppa: 1 } },
};
const bids = [bid, bidWithoutFloor];
@@ -107,6 +113,15 @@ describe('Missena Adapter', function () {
const payload = JSON.parse(request.data);
const payloadNoFloor = JSON.parse(requests[1].data);
+ it('should send disabled autoplay', function () {
+ expect(payload.autoplay).to.equal(0);
+ });
+
+ it('should contain coppa', function () {
+ expect(payload.coppa).to.equal(1);
+ });
+ sandbox.restore();
+
it('should contain uspConsent', function () {
expect(payload.us_privacy).to.equal('IDO');
});
@@ -128,11 +143,11 @@ describe('Missena Adapter', function () {
});
it('should send placement', function () {
- expect(payload.placement).to.equal('sticky');
+ expect(payload.params.placement).to.equal('sticky');
});
it('should send formats', function () {
- expect(payload.formats).to.eql(['sticky-banner']);
+ expect(payload.params.formats).to.eql(['sticky-banner']);
});
it('should send referer information to the request', function () {
diff --git a/test/spec/modules/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js
index 75fb357b196..349051cb48e 100644
--- a/test/spec/modules/nativoBidAdapter_spec.js
+++ b/test/spec/modules/nativoBidAdapter_spec.js
@@ -221,6 +221,7 @@ describe('interpretResponse', function () {
meta: {
advertiserDomains: ['test.com'],
},
+ mediaType: 'banner',
},
]
@@ -681,16 +682,24 @@ describe('hasProtocol', () => {
describe('addProtocol', () => {
it('www.testpage.com', () => {
- expect(addProtocol('www.testpage.com')).to.be.equal('https://www.testpage.com')
+ expect(addProtocol('www.testpage.com')).to.be.equal(
+ 'https://www.testpage.com'
+ )
})
it('//www.testpage.com', () => {
- expect(addProtocol('//www.testpage.com')).to.be.equal('https://www.testpage.com')
+ expect(addProtocol('//www.testpage.com')).to.be.equal(
+ 'https://www.testpage.com'
+ )
})
it('http://www.testpage.com', () => {
- expect(addProtocol('http://www.testpage.com')).to.be.equal('http://www.testpage.com')
+ expect(addProtocol('http://www.testpage.com')).to.be.equal(
+ 'http://www.testpage.com'
+ )
})
it('https://www.testpage.com', () => {
- expect(addProtocol('https://www.testpage.com')).to.be.equal('https://www.testpage.com')
+ expect(addProtocol('https://www.testpage.com')).to.be.equal(
+ 'https://www.testpage.com'
+ )
})
})
@@ -786,7 +795,7 @@ describe('RequestData', () => {
describe('UserEIDs', () => {
const userEids = new UserEIDs()
- const eids = [{ 'testId': 1111 }]
+ const eids = [{ testId: 1111 }]
describe('processBidRequestData', () => {
it('Processes bid request without eids', () => {
@@ -810,7 +819,7 @@ describe('UserEIDs', () => {
expect(qs).to.include('ntv_pb_eid=')
try {
expect(JSON.parse(value)).to.be.equal(eids)
- } catch (err) { }
+ } catch (err) {}
})
})
})
@@ -828,12 +837,83 @@ describe('buildRequestUrl', () => {
})
it('Returns baseUrl + QS params if QS strings passed', () => {
- const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar'])
- expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`)
+ const url = buildRequestUrl(baseUrl, [
+ 'ntv_ptd=123456&ntv_test=true',
+ 'ntv_foo=bar',
+ ])
+ expect(url).to.be.equal(
+ `${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`
+ )
})
it('Returns baseUrl + QS params if mixed QS strings passed', () => {
- const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar'])
- expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`)
+ const url = buildRequestUrl(baseUrl, [
+ 'ntv_ptd=123456&ntv_test=true',
+ '',
+ '',
+ 'ntv_foo=bar',
+ ])
+ expect(url).to.be.equal(
+ `${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`
+ )
+ })
+})
+
+describe('Prebid Video', function () {
+ it('should handle video bid requests', function () {
+ const videoBidRequest = {
+ bidder: 'nativo',
+ params: {
+ video: {
+ mimes: ['video/mp4'],
+ protocols: [2, 3, 5, 6],
+ playbackmethod: [1, 2],
+ skip: 1,
+ skipafter: 5,
+ },
+ },
+ }
+
+ const isValid = spec.isBidRequestValid(videoBidRequest)
+ expect(isValid).to.be.true
+ })
+})
+
+describe('Prebid Native', function () {
+ it('should handle native bid requests', function () {
+ const nativeBidRequest = {
+ bidder: 'nativo',
+ params: {
+ native: {
+ title: {
+ required: true,
+ len: 80,
+ },
+ image: {
+ required: true,
+ sizes: [150, 50],
+ },
+ sponsoredBy: {
+ required: true,
+ },
+ clickUrl: {
+ required: true,
+ },
+ privacyLink: {
+ required: false,
+ },
+ body: {
+ required: true,
+ },
+ icon: {
+ required: true,
+ sizes: [50, 50],
+ },
+ },
+ },
+ }
+
+ const isValid = spec.isBidRequestValid(nativeBidRequest)
+ expect(isValid).to.be.true
})
})
diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js
index 6b2e7ef9ba0..5cf51af9948 100644
--- a/test/spec/modules/nextMillenniumBidAdapter_spec.js
+++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js
@@ -1,6 +1,7 @@
import { expect } from 'chai';
import {
getImp,
+ getSourceObj,
replaceUsersyncMacros,
setConsentStrings,
setOrtb2Parameters,
@@ -108,6 +109,112 @@ describe('nextMillenniumBidAdapterTests', () => {
}
});
+ describe('function getSourceObj', () => {
+ const dataTests = [
+ {
+ title: 'schain is empty',
+ validBidRequests: [{}],
+ bidderRequest: {},
+ expected: undefined,
+ },
+
+ {
+ title: 'schain is validBidReequest',
+ bidderRequest: {},
+ validBidRequests: [{
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ }],
+
+ expected: {
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ },
+ },
+
+ {
+ title: 'schain is bidderReequest.ortb2.source.schain',
+ bidderRequest: {
+ ortb2: {
+ source: {
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ },
+ },
+ },
+
+ validBidRequests: [{}],
+ expected: {
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ },
+ },
+
+ {
+ title: 'schain is bidderReequest.ortb2.source.ext.schain',
+ bidderRequest: {
+ ortb2: {
+ source: {
+ ext: {
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ },
+ },
+ },
+ },
+
+ validBidRequests: [{}],
+ expected: {
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [{asi: 'test.test', sid: '00001', hp: 1}],
+ },
+ },
+ },
+ },
+ ];
+
+ for (let {title, validBidRequests, bidderRequest, expected} of dataTests) {
+ it(title, () => {
+ const source = getSourceObj(validBidRequests, bidderRequest);
+ expect(source).to.deep.equal(expected);
+ });
+ }
+ });
+
describe('function setConsentStrings', () => {
const dataTests = [
{
@@ -118,16 +225,18 @@ describe('nextMillenniumBidAdapterTests', () => {
uspConsent: '1---',
gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]},
gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true},
- ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}},
+ ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10], coppa: 1}},
},
},
expected: {
- user: {ext: {consent: 'kjfdniwjnifwenrif3'}},
+ user: {consent: 'kjfdniwjnifwenrif3'},
regs: {
gpp: 'DBACNYA~CPXxRfAPXxR',
gpp_sid: [7],
- ext: {gdpr: 1, us_privacy: '1---'},
+ gdpr: 1,
+ us_privacy: '1---',
+ coppa: 1
},
},
},
@@ -138,16 +247,17 @@ describe('nextMillenniumBidAdapterTests', () => {
postBody: {},
bidderRequest: {
gdprConsent: {consentString: 'ewtewbefbawyadexv', gdprApplies: false},
- ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}},
+ ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10], coppa: 0}},
},
},
expected: {
- user: {ext: {consent: 'ewtewbefbawyadexv'}},
+ user: {consent: 'ewtewbefbawyadexv'},
regs: {
gpp: 'DSFHFHWEUYVDC',
gpp_sid: [8, 9, 10],
- ext: {gdpr: 0},
+ gdpr: 0,
+ coppa: 0,
},
},
},
@@ -160,7 +270,7 @@ describe('nextMillenniumBidAdapterTests', () => {
},
expected: {
- regs: {ext: {gdpr: 0}},
+ regs: {gdpr: 0},
},
},
@@ -414,16 +524,27 @@ describe('nextMillenniumBidAdapterTests', () => {
title: 'site.pagecat, site.content.cat and site.content.language',
data: {
postBody: {},
- ortb2: {site: {
+ ortb2: {
+ bcat: ['IAB1-3', 'IAB1-4'],
+ badv: ['domain1.com', 'domain2.com'],
+ wlang: ['en', 'fr', 'de'],
+ wlangb: ['en', 'fr', 'de'],
+ site: {
+ pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'],
+ content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'},
+ }
+ },
+ },
+
+ expected: {
+ bcat: ['IAB1-3', 'IAB1-4'],
+ badv: ['domain1.com', 'domain2.com'],
+ wlang: ['en', 'fr', 'de'],
+ site: {
pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'],
content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'},
- }},
+ }
},
-
- expected: {site: {
- pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'],
- content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'},
- }},
},
{
@@ -431,6 +552,7 @@ describe('nextMillenniumBidAdapterTests', () => {
data: {
postBody: {},
ortb2: {
+ wlangb: ['en', 'fr', 'de'],
user: {keywords: 'key7,key8,key9'},
site: {
keywords: 'key1,key2,key3',
@@ -440,6 +562,7 @@ describe('nextMillenniumBidAdapterTests', () => {
},
expected: {
+ wlangb: ['en', 'fr', 'de'],
user: {keywords: 'key7,key8,key9'},
site: {
keywords: 'key1,key2,key3',
@@ -873,7 +996,7 @@ describe('nextMillenniumBidAdapterTests', () => {
const tests = [
{
title: 'test - 1',
- bidderRequest: {bidderRequestId: 'mock-uuid'},
+ bidderRequest: {bidderRequestId: 'mock-uuid', timeout: 1200},
bidRequests: [
{
adUnitCode: 'test-div',
@@ -936,6 +1059,7 @@ describe('nextMillenniumBidAdapterTests', () => {
impSize: 2,
requestSize: 1,
domain: 'example.com',
+ tmax: 1200,
},
},
];
@@ -948,6 +1072,7 @@ describe('nextMillenniumBidAdapterTests', () => {
const requestData = JSON.parse(request[0].data);
expect(requestData.id).to.equal(expected.id);
+ expect(requestData.tmax).to.equal(expected.tmax);
expect(requestData?.imp?.length).to.equal(expected.impSize);
});
};
diff --git a/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js
new file mode 100644
index 00000000000..96c581844c1
--- /dev/null
+++ b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js
@@ -0,0 +1,126 @@
+import { permutiveIdentityManagerIdSubmodule, storage } from 'modules/permutiveIdentityManagerIdSystem'
+import { deepSetValue } from 'src/utils.js'
+
+const STORAGE_KEY = 'permutive-prebid-id'
+
+describe('permutiveIdentityManagerIdSystem', () => {
+ afterEach(() => {
+ storage.removeDataFromLocalStorage(STORAGE_KEY)
+ })
+
+ describe('decode', () => {
+ it('returns the input unchanged', () => {
+ const input = {
+ id5id: {
+ uid: '0',
+ ext: {
+ abTestingControlGroup: false,
+ linkType: 2,
+ pba: 'somepba'
+ }
+ }
+ }
+ const result = permutiveIdentityManagerIdSubmodule.decode(input)
+ expect(result).to.be.equal(input)
+ })
+ })
+
+ describe('getId', () => {
+ it('returns relevant IDs from localStorage and does not return unexpected IDs', () => {
+ const data = getUserIdData()
+ storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(data))
+ const result = permutiveIdentityManagerIdSubmodule.getId({})
+ const expected = {
+ 'id': {
+ 'id5id': {
+ 'uid': '0',
+ 'linkType': 0,
+ 'ext': {
+ 'abTestingControlGroup': false,
+ 'linkType': 0,
+ 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q=='
+ }
+ }
+ }
+ }
+ expect(result).to.deep.equal(expected)
+ })
+
+ it('returns undefined if no relevant IDs are found in localStorage', () => {
+ storage.setDataInLocalStorage(STORAGE_KEY, '{}')
+ const result = permutiveIdentityManagerIdSubmodule.getId({})
+ expect(result).to.be.undefined
+ })
+
+ it('will optionally wait for Permutive SDK if no identities are in local storage already', async () => {
+ const cleanup = setWindowPermutive()
+ const result = permutiveIdentityManagerIdSubmodule.getId({params: {ajaxTimeout: 50}})
+ expect(result).not.to.be.undefined
+ expect(result.id).to.be.undefined
+ expect(result.callback).not.to.be.undefined
+ const expected = {
+ 'id5id': {
+ 'uid': '0',
+ 'linkType': 0,
+ 'ext': {
+ 'abTestingControlGroup': false,
+ 'linkType': 0,
+ 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q=='
+ }
+ }
+ }
+ const r = await new Promise(result.callback)
+ expect(r).to.deep.equal(expected)
+ cleanup()
+ })
+ })
+})
+
+const setWindowPermutive = () => {
+ // Read from Permutive
+ const backup = window.permutive
+
+ deepSetValue(window, 'permutive.ready', (f) => {
+ setTimeout(() => f(), 5)
+ })
+
+ deepSetValue(window, 'permutive.addons.identity_manager.prebid.onReady', (f) => {
+ setTimeout(() => f(sdkUserIdData()), 5)
+ })
+
+ // Cleanup
+ return () => window.permutive = backup
+}
+
+const sdkUserIdData = () => ({
+ 'id5id': {
+ 'uid': '0',
+ 'linkType': 0,
+ 'ext': {
+ 'abTestingControlGroup': false,
+ 'linkType': 0,
+ 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q=='
+ }
+ },
+})
+
+const getUserIdData = () => ({
+ 'providers': {
+ 'id5id': {
+ 'userId': {
+ 'uid': '0',
+ 'linkType': 0,
+ 'ext': {
+ 'abTestingControlGroup': false,
+ 'linkType': 0,
+ 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q=='
+ }
+ }
+ },
+ 'fooid': {
+ 'userId': {
+ 'id': '1'
+ }
+ }
+ }
+})
diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js
index 60cdd43504a..9a5ce1ae330 100644
--- a/test/spec/modules/precisoBidAdapter_spec.js
+++ b/test/spec/modules/precisoBidAdapter_spec.js
@@ -1,5 +1,8 @@
import { expect } from 'chai';
import { spec } from '../../../modules/precisoBidAdapter.js';
+import { NATIVE } from '../../../src/mediaTypes.js';
+import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils.js';
+
// simport { config } from '../../../src/config.js';
const DEFAULT_PRICE = 1
@@ -53,6 +56,74 @@ describe('PrecisoAdapter', function () {
};
+ let nativeBid = {
+
+ precisoBid: true,
+ bidId: '23fhj33i987f',
+ bidder: 'precisonat',
+ buyerUid: 'testuid',
+ params: {
+ host: 'prebid',
+ sourceid: '0',
+ publisherId: '0',
+ mediaType: 'native',
+ region: 'IND'
+
+ },
+ userId: {
+ pubcid: '12355454test'
+
+ },
+ user: {
+ geo: {
+ region: 'IND',
+ }
+ },
+ ortb2: {
+ device: {
+ w: 1920,
+ h: 166,
+ dnt: 0,
+ },
+ site: {
+ domain: 'localHost'
+ },
+ source: {
+ tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3'
+ }
+
+ },
+ mediaType: 'native',
+ nativeOrtbRequest: {
+ assets: [
+ {
+ id: 2,
+ required: 1,
+ img: {
+ type: 3,
+ w: 300,
+ h: 250
+ }
+ }
+ ]
+ },
+ nativeParams: {
+ ortb: {
+ assets: [
+ {
+ id: 2,
+ required: 1,
+ img: {
+ type: 3,
+ w: 300,
+ h: 250
+ }
+ }
+ ]
+ }
+ }
+ };
+
describe('isBidRequestValid', function () {
it('Should return true if there are bidId, params and sourceid parameters present', function () {
expect(spec.isBidRequestValid(bid)).to.be.true;
@@ -61,6 +132,14 @@ describe('PrecisoAdapter', function () {
delete bid.params.publisherId;
expect(spec.isBidRequestValid(bid)).to.be.false;
});
+ it('Should return true if there are bidId, params and sourceid parameters present native Bid', function () {
+ expect(spec.isBidRequestValid(nativeBid)).to.be.true;
+ });
+
+ it('Should return false if at least one of parameters is not present in native bid', function () {
+ delete nativeBid.params.publisherId;
+ expect(spec.isBidRequestValid(nativeBid)).to.be.false;
+ });
});
describe('buildRequests', function () {
@@ -91,6 +170,31 @@ describe('PrecisoAdapter', function () {
let data = serverRequest.data;
expect(data.device).to.be.undefined;
});
+
+ let ServeNativeRequest = spec.buildRequests([nativeBid]);
+ it('Creates a valid nativeServerRequest object ', function () {
+ expect(ServeNativeRequest).to.exist;
+ expect(ServeNativeRequest.method).to.exist;
+ expect(ServeNativeRequest.url).to.exist;
+ expect(ServeNativeRequest.data).to.exist;
+ expect(ServeNativeRequest.method).to.equal('POST');
+ expect(ServeNativeRequest.url).to.equal('https://ssp-bidder.mndtrk.com/bid_request/openrtb');
+ });
+
+ it('should extract the native params', function () {
+ let nativeData = ServeNativeRequest.data;
+ const asset = JSON.parse(nativeData.imp[0].native.request).assets[0]
+ expect(asset).to.deep.equal({
+ id: OPENRTB.NATIVE.ASSET_ID.IMAGE,
+ required: 1,
+ img: {
+ w: 300,
+ h: 250,
+ type: OPENRTB.NATIVE.IMAGE_TYPE.MAIN,
+ }
+ }
+ )
+ });
});
describe('interpretResponse', function () {
@@ -137,7 +241,87 @@ describe('PrecisoAdapter', function () {
expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0]))
})
+
+ it('should get correct native bid response', function () {
+ const adm = {
+ native: {
+ ver: 1.2,
+ link: {
+ url: 'https://example.com',
+ clicktrackers: 'https://example.com/clktracker'
+ },
+ eventtrackers: [
+ {
+ url: 'https://example.com/imptracker'
+ }
+ ],
+ imptrackers: [
+ 'https://example.com/imptracker'
+ ],
+ assets: [{
+ id: OPENRTB.NATIVE.ASSET_ID.IMAGE,
+ required: 1,
+ img: {
+ url: 'https://example.com/image.jpg',
+ w: 150,
+ h: 50
+ }
+ }],
+ }
+ }
+ let nativeResponse = {
+ bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a',
+ seatbid: [
+ {
+ bid: [
+ {
+ id: '123',
+ impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22',
+ price: DEFAULT_PRICE,
+ adm: JSON.stringify(adm),
+ cid: 'test_cid',
+ crid: 'test_banner_crid',
+ w: DEFAULT_BANNER_WIDTH,
+ h: DEFAULT_BANNER_HEIGHT,
+ adomain: [],
+ }
+ ],
+ seat: BIDDER_CODE
+ }
+ ],
+ }
+
+ let expectedNativeResponse = [
+ {
+ requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22',
+ mediaType: NATIVE,
+ cpm: DEFAULT_PRICE,
+ creativeId: 'test_banner_crid',
+ width: 1,
+ height: 1,
+ ttl: 300,
+ meta: {
+ advertiserDomains: []
+ },
+ netRevenue: true,
+ currency: 'USD',
+ // meta: { advertiserDomains: [] },
+ native: {
+ clickUrl: encodeURI('https://example.com'),
+ impressionTrackers: ['https://example.com/imptracker'],
+ image: {
+ url: encodeURI('https://example.com/image.jpg'),
+ width: 150,
+ height: 50
+ },
+ }
+ }
+ ]
+ let result = spec.interpretResponse({ body: nativeResponse });
+ expect(Object.keys(result[0])).to.have.members(Object.keys(expectedNativeResponse[0]));
+ })
})
+
describe('getUserSyncs', function () {
const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=NA&gdpr=0&gdpr_consent=&us_privacy=&t=4';
const syncOptions = {
diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js
index 48f7a19e7d0..71b22e25272 100644
--- a/test/spec/modules/pubmaticBidAdapter_spec.js
+++ b/test/spec/modules/pubmaticBidAdapter_spec.js
@@ -2378,6 +2378,40 @@ describe('PubMatic adapter', function () {
expect(data.device.ext).to.deep.equal(cdepObj);
});
+ it('should pass enriched device data from ortb2 object if present in bidderRequest fpd', function () {
+ const fpdBidderRequest = {
+ auctionId: 'new-auction-id',
+ ortb2: {
+ device: {
+ w: 980,
+ h: 1720,
+ dnt: 0,
+ ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1',
+ language: 'en',
+ devicetype: 1,
+ make: 'Apple',
+ model: 'iPhone 12 Pro Max',
+ os: 'iOS',
+ osv: '17.4',
+ }
+ },
+ };
+
+ const request = spec.buildRequests(multipleMediaRequests, fpdBidderRequest);
+ const data = JSON.parse(request.data);
+
+ expect(data.device.w).to.equal(fpdBidderRequest.ortb2.device.w);
+ expect(data.device.h).to.equal(fpdBidderRequest.ortb2.device.h);
+ expect(data.device.dnt).to.equal(fpdBidderRequest.ortb2.device.dnt);
+ expect(data.device.ua).to.equal(fpdBidderRequest.ortb2.device.ua);
+ expect(data.device.language).to.equal(fpdBidderRequest.ortb2.device.language);
+ expect(data.device.devicetype).to.equal(fpdBidderRequest.ortb2.device.devicetype);
+ expect(data.device.make).to.equal(fpdBidderRequest.ortb2.device.make);
+ expect(data.device.model).to.equal(fpdBidderRequest.ortb2.device.model);
+ expect(data.device.os).to.equal(fpdBidderRequest.ortb2.device.os);
+ expect(data.device.osv).to.equal(fpdBidderRequest.ortb2.device.osv);
+ });
+
it('Request params should have valid native bid request for all valid params', function () {
let request = spec.buildRequests(nativeBidRequests, {
auctionId: 'new-auction-id'
diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js
index c9f92e8af67..c1ae25e6104 100644
--- a/test/spec/modules/qortexRtdProvider_spec.js
+++ b/test/spec/modules/qortexRtdProvider_spec.js
@@ -1,21 +1,28 @@
-import * as utils from 'src/utils';
-import * as ajax from 'src/ajax.js';
+import * as utils from 'src/utils.js';
import * as events from 'src/events.js';
import { EVENTS } from '../../../src/constants.js';
import {loadExternalScript} from 'src/adloader.js';
import {
qortexSubmodule as module,
getContext,
+ getGroupConfig,
+ generateAnalyticsEventObject,
+ generateAnalyticsHostUrl,
addContextToRequests,
setContextData,
+ loadScriptTag,
initializeModuleData,
- loadScriptTag
+ setGroupConfigData,
+ saveContextAdded,
+ initializeBidEnrichment,
+ getContextAddedEntry
} from '../../../modules/qortexRtdProvider';
import {server} from '../../mocks/xhr.js';
import { cloneDeep } from 'lodash';
describe('qortexRtdProvider', () => {
let logWarnSpy;
+ let logMessageSpy;
let ortb2Stub;
const defaultApiHost = 'https://demand.qortex.ai';
@@ -27,45 +34,60 @@ describe('qortexRtdProvider', () => {
}
const validModuleConfig = {
- params: {
- groupId: defaultGroupId,
- apiUrl: defaultApiHost,
- bidders: validBidderArray
- }
- },
- emptyModuleConfig = {
- params: {}
+ params: {
+ groupId: defaultGroupId,
+ apiUrl: defaultApiHost,
+ bidders: validBidderArray,
+ enableBidEnrichment: true
+ }
+ }
+ const bidEnrichmentDisabledModuleConfig = {
+ params: {
+ groupId: defaultGroupId,
+ apiUrl: defaultApiHost,
+ bidders: validBidderArray
+ }
+ }
+ const invalidApiUrlModuleConfig = {
+ params: {
+ groupId: defaultGroupId,
+ apiUrl: 'test123',
+ bidders: validBidderArray
}
+ }
+ const emptyModuleConfig = {
+ params: {}
+ }
const validImpressionEvent = {
- detail: {
- uid: 'uid123',
- type: 'qx-impression'
- }
- },
- validImpressionEvent2 = {
- detail: {
- uid: 'uid1234',
- type: 'qx-impression'
- }
- },
- missingIdImpressionEvent = {
- detail: {
- type: 'qx-impression'
- }
- },
- invalidTypeQortexEvent = {
- detail: {
- type: 'invalid-type'
- }
+ detail: {
+ uid: 'uid123',
+ type: 'qx-impression'
+ }
+ }
+ const validImpressionEvent2 = {
+ detail: {
+ uid: 'uid1234',
+ type: 'qx-impression'
+ }
+ }
+ const missingIdImpressionEvent = {
+ detail: {
+ type: 'qx-impression'
+ }
+ }
+ const invalidTypeQortexEvent = {
+ detail: {
+ type: 'invalid-type'
}
+ }
const responseHeaders = {
'content-type': 'application/json',
'access-control-allow-origin': '*'
};
- const responseObj = {
+ const contextResponseObj = {
content: {
id: '123456',
episode: 15,
@@ -74,11 +96,36 @@ describe('qortexRtdProvider', () => {
season: '1',
url: 'https://example.com/file.mp4'
}
- };
+ }
+ const contextResponse = JSON.stringify(contextResponseObj);
+
+ const validGroupConfigResponseObj = {
+ groupId: defaultGroupId,
+ active: true,
+ prebidBidEnrichment: true,
+ prebidBidEnrichmentPercentage: 100,
+ prebidReportingPercentage: 100
+ }
+ const validGroupConfigResponse = JSON.stringify(validGroupConfigResponseObj);
- const apiResponse = JSON.stringify(responseObj);
+ const inactiveGroupConfigResponseObj = {
+ groupId: defaultGroupId,
+ active: false,
+ PrebidBidEnrichment: true,
+ PrebidReportingPercentage: 100
+ }
+ const inactiveGroupConfigResponse = JSON.stringify(inactiveGroupConfigResponseObj);
+
+ const noEnrichmentGroupConfigResponseObj = {
+ groupId: defaultGroupId,
+ active: true,
+ prebidBidEnrichment: true,
+ prebidBidEnrichmentPercentage: 0,
+ prebidReportingPercentage: 100
+ }
const reqBidsConfig = {
+ auctionId: '1234',
adUnits: [{
bids: [
{ bidder: 'qortex' }
@@ -93,17 +140,51 @@ describe('qortexRtdProvider', () => {
beforeEach(() => {
ortb2Stub = sinon.stub(reqBidsConfig, 'ortb2Fragments').value({bidder: {}, global: {}})
logWarnSpy = sinon.spy(utils, 'logWarn');
+ logMessageSpy = sinon.spy(utils, 'logMessage');
})
afterEach(() => {
logWarnSpy.restore();
+ logMessageSpy.restore();
ortb2Stub.restore();
setContextData(null);
})
describe('init', () => {
- it('returns true for valid config object', () => {
- expect(module.init(validModuleConfig)).to.be.true;
+ it('returns true for valid config object', (done) => {
+ const result = module.init(validModuleConfig);
+ expect(server.requests.length).to.be.eql(1)
+ const groupConfigReq = server.requests[0];
+ groupConfigReq.respond(200, responseHeaders, validGroupConfigResponse);
+ setTimeout(() => {
+ expect(result).to.be.true;
+ done()
+ }, 500)
+ })
+
+ it('logs warning when group config does not pass setup conditions', (done) => {
+ const result = module.init(validModuleConfig);
+ expect(server.requests.length).to.be.eql(1)
+ const groupConfigReq = server.requests[0];
+ groupConfigReq.respond(200, responseHeaders, inactiveGroupConfigResponse);
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('Group config is not configured for qortex bid enrichment')).to.be.true;
+ done()
+ }, 500)
+ })
+
+ it('logs warning when group config request errors', (done) => {
+ const result = module.init(validModuleConfig);
+ server.requests[0].respond(404, responseHeaders, inactiveGroupConfigResponse);
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('No Group Config found')).to.be.true;
+ done()
+ }, 500)
+ })
+
+ it('will not initialize bid enrichment if it is disabled', () => {
+ module.init(bidEnrichmentDisabledModuleConfig);
+ expect(logWarnSpy.calledWith('Bid Enrichment Function has been disabled in module configuration')).to.be.true;
})
it('returns false and logs error for missing groupId', () => {
@@ -168,21 +249,21 @@ describe('qortexRtdProvider', () => {
dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent));
dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent));
expect(billableEvents.length).to.be.equal(1);
- expect(logWarnSpy.calledWith('received invalid billable event due to duplicate uid: qx-impression')).to.be.ok;
+ expect(logWarnSpy.calledWith('Received invalid billable event due to duplicate uid: qx-impression')).to.be.ok;
})
it('will not allow events with missing uid', () => {
loadScriptTag(config);
dispatchEvent(new CustomEvent('qortex-rtd', missingIdImpressionEvent));
expect(billableEvents.length).to.be.equal(0);
- expect(logWarnSpy.calledWith('received invalid billable event due to missing uid: qx-impression')).to.be.ok;
+ expect(logWarnSpy.calledWith('Received invalid billable event due to missing uid: qx-impression')).to.be.ok;
})
it('will not allow events with unavailable type', () => {
loadScriptTag(config);
dispatchEvent(new CustomEvent('qortex-rtd', invalidTypeQortexEvent));
expect(billableEvents.length).to.be.equal(0);
- expect(logWarnSpy.calledWith('received invalid billable event: invalid-type')).to.be.ok;
+ expect(logWarnSpy.calledWith('Received invalid billable event: invalid-type')).to.be.ok;
})
})
@@ -191,7 +272,9 @@ describe('qortexRtdProvider', () => {
beforeEach(() => {
initializeModuleData(validModuleConfig);
+ setGroupConfigData(validGroupConfigResponseObj);
callbackSpy = sinon.spy();
+ server.reset();
})
afterEach(() => {
@@ -203,26 +286,107 @@ describe('qortexRtdProvider', () => {
const reqBidsConfigNoBids = { adUnits: [] };
module.getBidRequestData(reqBidsConfigNoBids, callbackSpy);
expect(callbackSpy.calledOnce).to.be.true;
- expect(logWarnSpy.calledWith('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfigNoBids))).to.be.ok;
+ expect(logWarnSpy.calledOnce).to.be.true;
})
- it('will call callback if getContext does not throw', () => {
+ it('will call callback if getContext does not throw', (done) => {
const cb = function () {
expect(logWarnSpy.calledOnce).to.be.false;
done();
}
module.getBidRequestData(reqBidsConfig, cb);
- server.requests[0].respond(200, responseHeaders, apiResponse);
+ server.requests[0].respond(200, responseHeaders, contextResponse);
+ })
+
+ it('will log message call callback if context data has already been collected', (done) => {
+ setContextData(contextResponseObj);
+ module.getBidRequestData(reqBidsConfig, callbackSpy);
+ setTimeout(() => {
+ expect(server.requests.length).to.be.eql(0);
+ expect(logMessageSpy.calledWith('Adding Content object from existing context data')).to.be.true;
+ done();
+ }, 250)
})
it('will catch and log error and fire callback', (done) => {
- const a = sinon.stub(ajax, 'ajax').throws(new Error('test'));
+ module.getBidRequestData(reqBidsConfig, callbackSpy);
+ server.requests[0].respond(404, responseHeaders, JSON.stringify({}));
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('Returned error status code: 404')).to.be.eql(true);
+ expect(callbackSpy.calledOnce).to.be.true;
+ done();
+ }, 250)
+ })
+
+ it('will not request context if group config toggle is false', (done) => {
+ setGroupConfigData(inactiveGroupConfigResponseObj);
+ const cb = function () {
+ expect(server.requests.length).to.be.eql(0);
+ expect(logWarnSpy.called).to.be.true;
+ expect(logWarnSpy.calledWith('Bid enrichment disabled at group config')).to.be.true;
+ done();
+ }
+ module.getBidRequestData(reqBidsConfig, cb);
+ })
+
+ it('Logs warning for network error', (done) => {
+ saveContextAdded(reqBidsConfig);
+ const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
+ module.onAuctionEndEvent(testData);
+ server.requests[0].respond(500, responseHeaders, JSON.stringify({}));
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('Returned error status code: 500')).to.be.eql(true);
+ done();
+ }, 200)
+ })
+
+ it('will not request context if prebid disable toggle is true', (done) => {
+ initializeModuleData(bidEnrichmentDisabledModuleConfig);
const cb = function () {
- expect(logWarnSpy.calledWith('test')).to.be.eql(true);
+ expect(server.requests.length).to.be.eql(0);
+ expect(logWarnSpy.called).to.be.true;
+ expect(logWarnSpy.calledWith('Bid enrichment disabled at prebid config')).to.be.true;
done();
}
module.getBidRequestData(reqBidsConfig, cb);
- a.restore();
+ })
+ })
+
+ describe('onAuctionEndEvent', () => {
+ beforeEach(() => {
+ initializeModuleData(validModuleConfig);
+ setGroupConfigData(validGroupConfigResponseObj);
+ })
+
+ afterEach(() => {
+ initializeModuleData(emptyModuleConfig);
+ setGroupConfigData(null);
+ })
+
+ it('Properly sends analytics event with valid config', (done) => {
+ saveContextAdded(reqBidsConfig);
+ const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'};
+ module.onAuctionEndEvent(testData);
+ const request = server.requests[0];
+ expect(request.url).to.be.eql('https://events.qortex.ai/api/v1/player-event');
+ server.requests[0].respond(200, responseHeaders, JSON.stringify({}));
+ setTimeout(() => {
+ expect(logMessageSpy.calledWith('Qortex analytics event sent')).to.be.true
+ done();
+ }, 200)
+ })
+
+ it('Logs warning for rejected analytics request', (done) => {
+ const invalidPercentageConfig = cloneDeep(validGroupConfigResponseObj);
+ invalidPercentageConfig.prebidReportingPercentage = -1;
+ setGroupConfigData(invalidPercentageConfig);
+ const testData = {data: 'data'};
+ module.onAuctionEndEvent(testData);
+ expect(server.requests.length).to.be.eql(0);
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('Current request did not meet analytics percentage threshold, cancelling sending event')).to.be.true
+ done();
+ }, 200)
})
})
@@ -235,38 +399,27 @@ describe('qortexRtdProvider', () => {
initializeModuleData(emptyModuleConfig);
})
- it('returns a promise', (done) => {
+ it('returns a promise', () => {
const result = getContext();
expect(result).to.be.a('promise');
- done();
})
it('uses request url generated from initialize function in config and resolves to content object data', (done) => {
- let requestUrl = `${validModuleConfig.params.apiUrl}/api/v1/analyze/${validModuleConfig.params.groupId}/prebid`;
+ let requestUrl = `${validModuleConfig.params.apiUrl}/api/v1/prebid/${validModuleConfig.params.groupId}/page/lookup`;
const ctx = getContext()
- expect(server.requests.length).to.be.eql(1);
- expect(server.requests[0].url).to.be.eql(requestUrl);
- server.requests[0].respond(200, responseHeaders, apiResponse);
+ const request = server.requests[0]
+ request.respond(200, responseHeaders, contextResponse);
ctx.then(response => {
- expect(response).to.be.eql(responseObj.content);
- done();
- });
- })
-
- it('will return existing context data instead of ajax call if the source was not updated', (done) => {
- setContextData(responseObj.content);
- const ctx = getContext();
- expect(server.requests.length).to.be.eql(0);
- ctx.then(response => {
- expect(response).to.be.eql(responseObj.content);
+ expect(server.requests.length).to.be.eql(1);
+ expect(request.url).to.be.eql(requestUrl);
+ expect(response).to.be.eql(contextResponseObj.content);
done();
});
})
- it('returns null for non erroring api responses other than 200', (done) => {
- const nullContentResponse = { content: null }
+ it('returns null when necessary', (done) => {
const ctx = getContext()
- server.requests[0].respond(200, responseHeaders, JSON.stringify(nullContentResponse))
+ server.requests[0].respond(202, responseHeaders, JSON.stringify({}))
ctx.then(response => {
expect(response).to.be.null;
expect(server.requests.length).to.be.eql(1);
@@ -276,7 +429,28 @@ describe('qortexRtdProvider', () => {
})
})
- describe(' addContextToRequests', () => {
+ describe('addContextToRequests', () => {
+ let testReqBids;
+ beforeEach(() => {
+ setGroupConfigData(validGroupConfigResponseObj);
+ testReqBids = {
+ auctionId: '1234',
+ adUnits: [{
+ bids: [
+ { bidder: 'qortex' }
+ ]
+ }],
+ ortb2Fragments: {
+ bidder: {},
+ global: {}
+ }
+ }
+ })
+
+ afterEach(() => {
+ setGroupConfigData(null);
+ })
+
it('logs error if no data was retrieved from get context call', () => {
initializeModuleData(validModuleConfig);
addContextToRequests(reqBidsConfig);
@@ -286,34 +460,44 @@ describe('qortexRtdProvider', () => {
expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({});
})
+ it('saves context added entry with skipped flag if valid request does not meet threshold', () => {
+ initializeModuleData(validModuleConfig);
+ setContextData(contextResponseObj.content);
+ setGroupConfigData(noEnrichmentGroupConfigResponseObj);
+ addContextToRequests(reqBidsConfig);
+ const contextAdded = getContextAddedEntry(reqBidsConfig.auctionId);
+ expect(contextAdded).to.not.be.null;
+ expect(contextAdded.contextSkipped).to.eql(true);
+ })
+
it('adds site.content only to global ortb2 when bidders array is omitted', () => {
const omittedBidderArrayConfig = cloneDeep(validModuleConfig);
delete omittedBidderArrayConfig.params.bidders;
initializeModuleData(omittedBidderArrayConfig);
- setContextData(responseObj.content);
+ setContextData(contextResponseObj.content);
addContextToRequests(reqBidsConfig);
expect(reqBidsConfig.ortb2Fragments.global).to.have.property('site');
expect(reqBidsConfig.ortb2Fragments.global.site).to.have.property('content');
- expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(responseObj.content);
+ expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(contextResponseObj.content);
expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({});
})
it('adds site.content only to bidder ortb2 when bidders array is included', () => {
initializeModuleData(validModuleConfig);
- setContextData(responseObj.content);
+ setContextData(contextResponseObj.content);
addContextToRequests(reqBidsConfig);
const qortexOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['qortex']
expect(qortexOrtb2Fragment).to.not.be.null;
expect(qortexOrtb2Fragment).to.have.property('site');
expect(qortexOrtb2Fragment.site).to.have.property('content');
- expect(qortexOrtb2Fragment.site.content).to.be.eql(responseObj.content);
+ expect(qortexOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content);
const testOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['test']
expect(testOrtb2Fragment).to.not.be.null;
expect(testOrtb2Fragment).to.have.property('site');
expect(testOrtb2Fragment.site).to.have.property('content');
- expect(testOrtb2Fragment.site.content).to.be.eql(responseObj.content);
+ expect(testOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content);
expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({});
})
@@ -322,7 +506,7 @@ describe('qortexRtdProvider', () => {
const invalidBidderArrayConfig = cloneDeep(validModuleConfig);
invalidBidderArrayConfig.params.bidders = [];
initializeModuleData(invalidBidderArrayConfig);
- setContextData(responseObj.content)
+ setContextData(contextResponseObj.content)
addContextToRequests(reqBidsConfig);
expect(logWarnSpy.calledWith('Config contains an empty bidders array, unable to determine which bids to enrich')).to.be.ok;
@@ -330,4 +514,117 @@ describe('qortexRtdProvider', () => {
expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({});
})
})
+
+ describe('generateAnalyticsEventObject', () => {
+ let qortexSessionInfo;
+ beforeEach(() => {
+ qortexSessionInfo = initializeModuleData(validModuleConfig);
+ setGroupConfigData(validGroupConfigResponseObj);
+ })
+
+ afterEach(() => {
+ initializeModuleData(emptyModuleConfig);
+ setGroupConfigData(null);
+ })
+
+ it('returns expected object', () => {
+ const testEventType = 'TEST';
+ const testSubType = 'TEST_SUBTYPE';
+ const testData = {data: 'data'};
+
+ const result = generateAnalyticsEventObject(testEventType, testSubType, testData);
+
+ expect(result.sessionId).to.be.eql(qortexSessionInfo.sessionId);
+ expect(result.groupId).to.be.eql(qortexSessionInfo.groupId);
+ expect(result.eventType).to.be.eql(testEventType);
+ expect(result.subType).to.be.eql(testSubType);
+ expect(result.eventOriginSource).to.be.eql('RTD');
+ expect(result.data).to.be.eql(testData);
+ })
+ })
+
+ describe('generateAnalyticsHostUrl', () => {
+ it('will use qortex analytics host when appropriate', () => {
+ const hostUrl = generateAnalyticsHostUrl(defaultApiHost);
+ expect(hostUrl).to.be.eql('https://events.qortex.ai/api/v1/player-event');
+ })
+
+ it('will use qortex stage analytics host when appropriate', () => {
+ const hostUrl = generateAnalyticsHostUrl('https://stg-demand.qortex.ai');
+ expect(hostUrl).to.be.eql('https://stg-events.qortex.ai/api/v1/player-event');
+ })
+
+ it('will default to dev analytics host when appropriate', () => {
+ const hostUrl = generateAnalyticsHostUrl('https://dev-demand.qortex.ai');
+ expect(hostUrl).to.be.eql('https://dev-events.qortex.ai/api/v1/player-event');
+ })
+ })
+
+ describe('getGroupConfig', () => {
+ let sessionInfo;
+
+ beforeEach(() => {
+ sessionInfo = initializeModuleData(validModuleConfig);
+ })
+
+ afterEach(() => {
+ initializeModuleData(emptyModuleConfig);
+ setGroupConfigData(null);
+ setContextData(null);
+ server.reset();
+ })
+
+ it('returns a promise', () => {
+ const result = getGroupConfig();
+ expect(result).to.be.a('promise');
+ })
+
+ it('processes group config response in valid conditions', (done) => {
+ const result = getGroupConfig();
+ const request = server.requests[0]
+ request.respond(200, responseHeaders, validGroupConfigResponse);
+ result.then(response => {
+ expect(request.url).to.be.eql(sessionInfo.groupConfigUrl);
+ expect(response.groupId).to.be.eql(validGroupConfigResponseObj.groupId);
+ expect(response.active).to.be.eql(validGroupConfigResponseObj.active);
+ expect(response.prebidBidEnrichment).to.be.eql(validGroupConfigResponseObj.prebidBidEnrichment);
+ expect(response.prebidReportingPercentage).to.be.eql(validGroupConfigResponseObj.prebidReportingPercentage);
+ done();
+ })
+ })
+ })
+
+ describe('initializeBidEnrichment', () => {
+ beforeEach(() => {
+ initializeModuleData(validModuleConfig);
+ setGroupConfigData(validGroupConfigResponseObj);
+ setContextData(null);
+ server.reset();
+ })
+
+ afterEach(() => {
+ initializeModuleData(emptyModuleConfig);
+ setGroupConfigData(null);
+ setContextData(null);
+ server.reset();
+ })
+
+ it('sets context data if applicable', (done) => {
+ initializeBidEnrichment();
+ server.requests[0].respond(200, responseHeaders, contextResponse);
+ setTimeout(() => {
+ expect(logMessageSpy.calledWith('Contextual record Received from Qortex API')).to.be.true;
+ done()
+ }, 250)
+ })
+
+ it('logs warning if no record has been made', (done) => {
+ initializeBidEnrichment();
+ server.requests[0].respond(202, responseHeaders, JSON.stringify({}));
+ setTimeout(() => {
+ expect(logWarnSpy.calledWith('Contexual record is not yet complete at this time')).to.be.true;
+ done();
+ }, 250)
+ })
+ })
})
diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js
index f3f9ce8616f..dded1fe15a0 100644
--- a/test/spec/modules/rtbhouseBidAdapter_spec.js
+++ b/test/spec/modules/rtbhouseBidAdapter_spec.js
@@ -1,8 +1,9 @@
import { expect } from 'chai';
-import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js';
+import { spec } from 'modules/rtbhouseBidAdapter.js';
import { newBidder } from 'src/adapters/bidderFactory.js';
import { config } from 'src/config.js';
import { mergeDeep } from '../../../src/utils';
+import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils';
describe('RTBHouseAdapter', () => {
const adapter = newBidder(spec);
diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js
index 2ce16ab8101..5be3040ddd4 100644
--- a/test/spec/modules/rubiconBidAdapter_spec.js
+++ b/test/spec/modules/rubiconBidAdapter_spec.js
@@ -6,6 +6,7 @@ import {
resetUserSync,
classifiedAsVideo,
resetRubiConf,
+ resetImpIdMap,
converter
} from 'modules/rubiconBidAdapter.js';
import {config} from 'src/config.js';
@@ -485,6 +486,7 @@ describe('the rubicon adapter', function () {
utils.logError.restore();
config.resetConfig();
resetRubiConf();
+ resetImpIdMap();
delete $$PREBID_GLOBAL$$.installedModules;
});
@@ -3060,6 +3062,21 @@ describe('the rubicon adapter', function () {
expect(other).to.be.empty;
});
});
+
+ describe('with duplicate adUnitCodes', () => {
+ it('should increment PBS request imp[].id starting at 2', () => {
+ const nativeBidderRequest = addNativeToBidRequest(bidderRequest, {twin: true});
+ const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids});
+ for (let i = 0; i < nativeBidderRequest.bids.length; i++) {
+ var adUnitCode = nativeBidderRequest.bids[i].adUnitCode;
+ if (i === 0) {
+ expect(request.imp[i].id).to.equal(adUnitCode);
+ } else {
+ expect(request.imp[i].id).to.equal(adUnitCode + (i + 1));
+ }
+ }
+ });
+ });
});
}
});
@@ -3767,6 +3784,71 @@ describe('the rubicon adapter', function () {
expect(bids[0].cpm).to.be.equal(0);
});
+ it('should use ads.emulated_format if defined for bid.meta.mediaType', function () {
+ let response = {
+ 'status': 'ok',
+ 'account_id': 14062,
+ 'site_id': 70608,
+ 'zone_id': 530022,
+ 'size_id': 15,
+ 'alt_size_ids': [
+ 43
+ ],
+ 'tracking': '',
+ 'inventory': {},
+ 'ads': [
+ {
+ 'status': 'ok',
+ 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c',
+ 'size_id': '15',
+ 'ad_id': '6',
+ 'advertiser': 7,
+ 'network': 8,
+ 'creative_id': 'crid-9',
+ 'type': 'script',
+ 'script': 'alert(\'foo\')',
+ 'campaign_id': 10,
+ 'cpm': 0.811,
+ 'emulated_format': 'video',
+ 'targeting': [
+ {
+ 'key': 'rpfl_14062',
+ 'values': [
+ '15_tier_all_test'
+ ]
+ }
+ ]
+ },
+ {
+ 'status': 'ok',
+ 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d',
+ 'size_id': '43',
+ 'ad_id': '7',
+ 'advertiser': 7,
+ 'network': 8,
+ 'creative_id': 'crid-9',
+ 'type': 'script',
+ 'script': 'alert(\'foo\')',
+ 'campaign_id': 10,
+ 'cpm': 0.911,
+ 'targeting': [
+ {
+ 'key': 'rpfl_14062',
+ 'values': [
+ '43_tier_all_test'
+ ]
+ }
+ ]
+ }
+ ]
+ };
+ let bids = spec.interpretResponse({body: response}, {
+ bidRequest: bidderRequest.bids[0]
+ });
+ expect(bids[0].meta.mediaType).to.equal('banner');
+ expect(bids[1].meta.mediaType).to.equal('video');
+ });
+
describe('singleRequest enabled', function () {
it('handles bidRequest of type Array and returns associated adUnits', function () {
const overrideMap = [];
@@ -3997,7 +4079,7 @@ describe('the rubicon adapter', function () {
let bids = spec.interpretResponse({body: response}, {data: request});
expect(bids[0].width).to.equal(0);
expect(bids[0].height).to.equal(0);
- })
+ });
});
}
@@ -4545,7 +4627,7 @@ describe('the rubicon adapter', function () {
});
});
-function addNativeToBidRequest(bidderRequest) {
+function addNativeToBidRequest(bidderRequest, options = {twin: false}) {
const nativeOrtbRequest = {
assets: [{
id: 0,
@@ -4574,27 +4656,30 @@ function addNativeToBidRequest(bidderRequest) {
bidderRequest.refererInfo = {
page: 'localhost'
}
- bidderRequest.bids[0] = {
- bidder: 'rubicon',
- params: {
- accountId: '14062',
- siteId: '70608',
- zoneId: '335918',
- },
- adUnitCode: '/19968336/header-bid-tag-0',
- code: 'div-1',
- bidId: '2ffb201a808da7',
- bidderRequestId: '178e34bad3658f',
- auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a',
- transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b',
- mediaTypes: {
- native: {
- ortb: {
- ...nativeOrtbRequest
+ const numBids = !options.twin ? 1 : 2;
+ for (let i = 0; i < numBids; i++) {
+ bidderRequest.bids[i] = {
+ bidder: 'rubicon',
+ params: {
+ accountId: '14062',
+ siteId: '70608',
+ zoneId: '335918',
+ },
+ adUnitCode: '/19968336/header-bid-tag-0',
+ code: 'div-1',
+ bidId: '2ffb201a808da7',
+ bidderRequestId: '178e34bad3658f',
+ auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a',
+ transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b',
+ mediaTypes: {
+ native: {
+ ortb: {
+ ...nativeOrtbRequest
+ }
}
- }
- },
- nativeOrtbRequest
+ },
+ nativeOrtbRequest
+ }
}
return bidderRequest;
}
diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js
index 30e95b04ccf..b03f6eb9a8a 100644
--- a/test/spec/modules/showheroes-bsBidAdapter_spec.js
+++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js
@@ -1,98 +1,67 @@
-import {expect} from 'chai'
-import {spec} from 'modules/showheroes-bsBidAdapter.js'
-import {newBidder} from 'src/adapters/bidderFactory.js'
-import {VIDEO, BANNER} from 'src/mediaTypes.js'
+import { expect } from 'chai'
+import { spec } from 'modules/showheroes-bsBidAdapter.js'
+import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js';
+import 'modules/priceFloors.js';
+import 'modules/consentManagementTcf.js';
+import 'modules/consentManagementUsp.js';
+import 'modules/schain.js';
+import { VIDEO } from 'src/mediaTypes.js'
const bidderRequest = {
refererInfo: {
- canonicalUrl: 'https://example.com'
+ page: 'https://example.com/home',
+ ref: 'https://referrer'
}
}
const adomain = ['showheroes.com'];
const gdpr = {
- 'gdprConsent': {
- 'apiVersion': 2,
- 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA',
- 'gdprApplies': true
+ gdprConsent: {
+ apiVersion: 2,
+ consentString: 'CONSENT',
+ vendorData: { purpose: { consents: { 1: true } } },
+ gdprApplies: true,
}
}
const uspConsent = '1---';
const schain = {
- 'schain': {
- 'validation': 'strict',
- 'config': {
- 'ver': '1.0',
- 'complete': 1,
- 'nodes': [
+ schain: {
+ validation: 'strict',
+ config: {
+ ver: '1.0',
+ complete: 1,
+ nodes: [
{
- 'asi': 'some.com',
- 'sid': '00001',
- 'hp': 1
+ asi: 'some.com',
+ sid: '00001',
+ hp: 1
}
]
}
}
}
-const bidRequestCommonParams = {
- 'bidder': 'showheroes-bs',
- 'params': {
- 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd',
- },
- 'adUnitCode': 'adunit-code-1',
- 'sizes': [[640, 480]],
- 'bidId': '38b373e1e31c18',
- 'bidderRequestId': '12e3ade2543ba6',
- 'auctionId': '43aa080090a47f',
-}
-
const bidRequestCommonParamsV2 = {
- 'bidder': 'showheroes-bs',
- 'params': {
- 'unitId': 'AACBWAcof-611K4U',
+ bidder: 'showheroes-bs',
+ params: {
+ unitId: 'AACBWAcof-611K4U',
},
- 'adUnitCode': 'adunit-code-1',
- 'sizes': [[640, 480]],
- 'bidId': '38b373e1e31c18',
- 'bidderRequestId': '12e3ade2543ba6',
- 'auctionId': '43aa080090a47f',
-}
-
-const bidRequestVideo = {
- ...bidRequestCommonParams,
- ...{
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480],
- 'context': 'instream',
- }
- }
- }
-}
-
-const bidRequestOutstream = {
- ...bidRequestCommonParams,
- ...{
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480],
- 'context': 'outstream',
- }
- }
- }
+ adUnitCode: 'adunit-code-1',
+ bidId: '38b373e1e31c18',
+ bidderRequestId: '12e3ade2543ba6',
+ auctionId: '43aa080090a47f',
}
const bidRequestVideoV2 = {
...bidRequestCommonParamsV2,
...{
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480],
- 'context': 'instream',
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480],
+ context: 'instream',
}
}
}
@@ -101,541 +70,188 @@ const bidRequestVideoV2 = {
const bidRequestOutstreamV2 = {
...bidRequestCommonParamsV2,
...{
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480],
- 'context': 'outstream'
- }
- }
- }
-}
-
-const bidRequestVideoVpaid = {
- ...bidRequestCommonParams,
- ...{
- 'params': {
- 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd',
- 'vpaidMode': true,
- },
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480],
- 'context': 'instream',
- }
- }
- }
-}
-
-const bidRequestBanner = {
- ...bidRequestCommonParams,
- ...{
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 360]]
- }
- }
- }
-}
-
-const bidRequestBannerMultiSizes = {
- ...bidRequestCommonParams,
- ...{
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[640, 360], [480, 320]]
- }
+ mediaTypes: {
+ video: {
+ playerSize: [640, 480],
+ context: 'outstream'
+ },
}
}
}
-const bidRequestVideoAndBanner = {
- ...bidRequestCommonParams,
- 'mediaTypes': {
- ...bidRequestBanner.mediaTypes,
- ...bidRequestVideo.mediaTypes
- }
-}
-
-describe('shBidAdapter', function () {
- const adapter = newBidder(spec)
-
- describe('inherited functions', function () {
- it('exists and is a function', function () {
- expect(adapter.callBids).to.exist.and.to.be.a('function')
- })
- })
-
- describe('isBidRequestValid', function () {
- it('should return true when required params found', function () {
- const requestV1 = {
- 'params': {
- 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd',
- }
- }
- expect(spec.isBidRequestValid(requestV1)).to.equal(true)
-
- const requestV2 = {
- 'params': {
- 'unitId': 'AACBTwsZVANd9NlB',
- }
- }
- expect(spec.isBidRequestValid(requestV2)).to.equal(true)
- })
-
- it('should return false when required params are not passed', function () {
- const request = {
- 'params': {}
- }
- expect(spec.isBidRequestValid(request)).to.equal(false)
- })
- })
-
- describe('buildRequests', function () {
- it('sends bid request to ENDPOINT via POST', function () {
- const request = spec.buildRequests([bidRequestVideo], bidderRequest)
- expect(request.method).to.equal('POST')
-
- const requestV2 = spec.buildRequests([bidRequestVideoV2], bidderRequest)
- expect(requestV2.method).to.equal('POST')
- })
-
- it('check sizes formats', function () {
- const request = spec.buildRequests([{
- 'params': {},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[320, 240]]
- }
- },
- }], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload.size).to.have.property('width', 320);
- expect(payload.size).to.have.property('height', 240);
-
- const request2 = spec.buildRequests([{
- 'params': {},
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 360]
- }
- },
- }], bidderRequest)
- const payload2 = request2.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload2.size).to.have.property('width', 640);
- expect(payload2.size).to.have.property('height', 360);
- })
-
- it('should get size from mediaTypes when sizes property is empty', function () {
- const request = spec.buildRequests([{
- 'params': {},
- 'mediaTypes': {
- 'video': {
- 'playerSize': [640, 480]
- }
- },
- 'sizes': [],
- }], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload.size).to.have.property('width', 640);
- expect(payload.size).to.have.property('height', 480);
-
- const request2 = spec.buildRequests([{
- 'params': {},
- 'mediaTypes': {
- 'banner': {
- 'sizes': [[320, 240]]
- }
- },
- 'sizes': [],
- }], bidderRequest)
- const payload2 = request2.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload2.size).to.have.property('width', 320);
- expect(payload2.size).to.have.property('height', 240);
- })
-
- it('should attach valid params to the payload when type is video', function () {
- const request = spec.buildRequests([bidRequestVideo], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload).to.have.property('mediaType', VIDEO);
- expect(payload).to.have.property('type', 2);
- })
-
- it('should attach valid params to the payload when type is video & vpaid mode on', function () {
- const request = spec.buildRequests([bidRequestVideoVpaid], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload).to.have.property('mediaType', VIDEO);
- expect(payload).to.have.property('type', 1);
- })
-
- it('should attach valid params to the payload when type is banner', function () {
- const request = spec.buildRequests([bidRequestBanner], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload).to.have.property('mediaType', BANNER);
- expect(payload).to.have.property('type', 5);
- })
-
- it('should attach valid params to the payload when type is banner (multi sizes)', function () {
- const request = spec.buildRequests([bidRequestBannerMultiSizes], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload).to.have.property('mediaType', BANNER);
- expect(payload).to.have.property('type', 5);
- expect(payload).to.have.nested.property('size.width', 640);
- expect(payload).to.have.nested.property('size.height', 360);
- const payload2 = request.data.requests[1];
- expect(payload2).to.be.an('object');
- expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload2).to.have.property('mediaType', BANNER);
- expect(payload2).to.have.property('type', 5);
- expect(payload2).to.have.nested.property('size.width', 480);
- expect(payload2).to.have.nested.property('size.height', 320);
- })
-
- it('should attach valid params to the payload when type is banner and video', function () {
- const request = spec.buildRequests([bidRequestVideoAndBanner], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload).to.have.property('mediaType', VIDEO);
- expect(payload).to.have.property('type', 2);
- const payload2 = request.data.requests[1];
- expect(payload2).to.be.an('object');
- expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd');
- expect(payload2).to.have.property('mediaType', BANNER);
- expect(payload2).to.have.property('type', 5);
- })
-
- it('should attach valid params to the payload when type is video (instream V2)', function () {
- const request = spec.buildRequests([bidRequestVideoV2], bidderRequest)
- const payload = request.data.bidRequests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U');
- expect(payload.mediaTypes).to.eql({
- [VIDEO]: {
- 'context': 'instream'
- }
- });
- })
-
- it('should attach valid params to the payload when type is video (outstream V2)', function () {
- const request = spec.buildRequests([bidRequestOutstreamV2], bidderRequest)
- const payload = request.data.bidRequests[0];
- expect(payload).to.be.an('object');
- expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U');
- expect(payload.mediaTypes).to.eql({
- [VIDEO]: {
- 'context': 'outstream'
- }
- });
- })
-
- it('passes gdpr & uspConsent if present', function () {
- const request = spec.buildRequests([bidRequestVideo], {
- ...bidderRequest,
- ...gdpr,
- uspConsent,
- })
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload.gdprConsent).to.eql(gdpr.gdprConsent)
- expect(payload.uspConsent).to.eql(uspConsent)
- })
-
- it('passes gdpr & usp if present (V2)', function () {
- const request = spec.buildRequests([bidRequestVideoV2], {
- ...bidderRequest,
- ...gdpr,
- uspConsent,
- })
- const context = request.data.context;
- expect(context).to.be.an('object');
- expect(context.gdprConsent).to.eql(gdpr.gdprConsent)
- expect(context.uspConsent).to.eql(uspConsent)
- })
-
- it('passes schain object if present', function() {
- const request = spec.buildRequests([{
- ...bidRequestVideo,
- ...schain
- }], bidderRequest)
- const payload = request.data.requests[0];
- expect(payload).to.be.an('object');
- expect(payload.schain).to.eql(schain.schain);
- })
-
- it('passes schain object if present (V2)', function() {
- const request = spec.buildRequests([{
- ...bidRequestVideoV2,
- ...schain
- }], bidderRequest)
- const context = request.data.context;
- expect(context).to.be.an('object');
- expect(context.schain).to.eql(schain.schain);
- })
- })
-
- describe('interpretResponse', function () {
- it('handles nobid responses', function () {
- expect(spec.interpretResponse({body: {}}, {data: {meta: {}}}).length).to.equal(0)
- expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0)
- })
-
- const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6'
- const vastXml = ''
-
- const basicResponse = {
- 'cpm': 5,
- 'currency': 'EUR',
- 'mediaType': VIDEO,
- 'context': 'instream',
- 'bidId': '38b373e1e31c18',
- 'size': {'width': 640, 'height': 480},
- 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6',
- 'vastXml': vastXml,
- 'adomain': adomain,
- };
-
- const responseVideo = {
- 'bids': [{
- ...basicResponse,
- }],
+describe('shBidAdapter', () => {
+ it('validates request', () => {
+ const bid = {
+ params: {
+ testKey: 'testValue',
+ },
};
-
- const responseVideoOutstream = {
- 'bids': [{
- ...basicResponse,
- 'context': 'outstream',
- }],
+ expect(spec.isBidRequestValid(bid)).to.eql(false);
+ bid.params = {
+ unitId: 'test_unit',
};
+ expect(spec.isBidRequestValid(bid)).to.eql(true);
+ });
- const responseBanner = {
- 'bids': [{
- ...basicResponse,
- 'mediaType': BANNER,
- }],
+ it('passes gdpr, usp, schain, floor in ortb request', () => {
+ const bidRequest = Object.assign({}, bidRequestVideoV2)
+ const fullRequest = {
+ bids: [bidRequestVideoV2],
+ ...bidderRequest,
+ ...gdpr,
+ ...schain,
+ ...{ uspConsent: uspConsent },
};
+ bidRequest.schain = schain.schain.config;
+ const getFloorResponse = { currency: 'EUR', floor: 3 };
+ bidRequest.getFloor = () => getFloorResponse;
+ const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(fullRequest));
+ const payload = request.data;
+ expect(payload.regs.ext.gdpr).to.eql(Number(gdpr.gdprConsent.gdprApplies));
+ expect(payload.regs.ext.us_privacy).to.eql(uspConsent);
+ expect(payload.user.ext.consent).to.eql(gdpr.gdprConsent.consentString);
+ expect(payload.source.ext.schain).to.eql(bidRequest.schain);
+ expect(payload.test).to.eql(0);
+ expect(payload.imp[0].bidfloor).eql(3);
+ expect(payload.imp[0].bidfloorcur).eql('EUR');
+ expect(payload.imp[0].displaymanager).eql('Prebid.js');
+ expect(payload.site.page).to.eql('https://example.com/home');
+ });
- const basicResponseV2 = {
- 'requestId': '38b373e1e31c18',
- 'adUnitCode': 'adunit-code-1',
- 'cpm': 1,
- 'currency': 'EUR',
- 'width': 640,
- 'height': 480,
- 'advertiserDomain': [],
- 'callbacks': {
- 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok']
- },
- 'mediaType': 'video',
- 'adomain': adomain,
- };
+ describe('interpretResponse', function () {
+ const vastXml = ''
+ const callback_won = 'https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'
+ const basicResponse = {
+ cur: 'EUR',
+ seatbid: [{
+ bid: [{
+ price: 1,
+ w: 640,
+ h: 480,
+ adm: vastXml,
+ impid: '38b373e1e31c18',
+ crid: 'c_38b373e1e31c18',
+ adomain: adomain,
+ ext: {
+ callbacks: {
+ won: [callback_won],
+ },
+ extra: 'test',
+ },
+ }],
+ seat: 'showheroes',
+ }]
+ }
const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1'
- const responseVideoV2 = {
- 'bidResponses': [{
- ...basicResponseV2,
- 'context': 'instream',
- 'vastUrl': vastUrl,
- }],
- };
-
- const responseVideoOutstreamV2 = {
- 'bidResponses': [{
- ...basicResponseV2,
- 'context': 'outstream',
- 'ad': '',
- }],
- };
-
- it('should get correct bid response when type is video', function () {
- const request = spec.buildRequests([bidRequestVideo], bidderRequest)
- const expectedResponse = [
- {
- 'cpm': 5,
- 'creativeId': 'c_38b373e1e31c18',
- 'adUnitCode': 'adunit-code-1',
- 'currency': 'EUR',
- 'width': 640,
- 'height': 480,
- 'mediaType': 'video',
- 'netRevenue': true,
- 'vastUrl': vastTag,
- 'vastXml': vastXml,
- 'requestId': '38b373e1e31c18',
- 'ttl': 300,
- 'adResponse': {
- 'content': vastXml
- },
- 'meta': {
- 'advertiserDomains': adomain
+ if (FEATURES.VIDEO) {
+ it('should get correct bid response when type is video (V2)', function () {
+ const request = spec.buildRequests([bidRequestVideoV2], bidderRequest)
+ const expectedResponse = [
+ {
+ cpm: 1,
+ creativeId: 'c_38b373e1e31c18',
+ creative_id: 'c_38b373e1e31c18',
+ currency: 'EUR',
+ width: 640,
+ height: 480,
+ playerHeight: 480,
+ playerWidth: 640,
+ mediaType: 'video',
+ netRevenue: true,
+ requestId: '38b373e1e31c18',
+ ttl: 300,
+ meta: {
+ advertiserDomains: adomain
+ },
+ vastXml: vastXml,
+ callbacks: {
+ won: [callback_won],
+ },
+ extra: 'test',
}
- }
- ]
+ ]
- const result = spec.interpretResponse({'body': responseVideo}, request)
- expect(result).to.deep.equal(expectedResponse)
- })
+ const result = spec.interpretResponse({ 'body': basicResponse }, request)
+ expect(result).to.deep.equal(expectedResponse)
+ })
- it('should get correct bid response when type is video (V2)', function () {
- const request = spec.buildRequests([bidRequestVideoV2], bidderRequest)
- const expectedResponse = [
- {
- 'cpm': 1,
- 'creativeId': 'c_38b373e1e31c18',
- 'adUnitCode': 'adunit-code-1',
- 'currency': 'EUR',
- 'width': 640,
- 'height': 480,
- 'mediaType': 'video',
- 'netRevenue': true,
- 'vastUrl': vastUrl,
- 'requestId': '38b373e1e31c18',
- 'ttl': 300,
- 'meta': {
- 'advertiserDomains': adomain
+ it('should get correct bid response when type is outstream (slot V2)', function () {
+ window.myRenderer = {
+ renderAd: function() {
+ return null;
}
}
- ]
-
- const result = spec.interpretResponse({'body': responseVideoV2}, request)
- expect(result).to.deep.equal(expectedResponse)
- })
-
- it('should get correct bid response when type is banner', function () {
- const request = spec.buildRequests([bidRequestBanner], bidderRequest)
-
- const result = spec.interpretResponse({'body': responseBanner}, request)
- expect(result[0]).to.have.property('mediaType', BANNER);
- expect(result[0].ad).to.include('