diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 016f4055216..962e057fbc5 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,6 +6,8 @@ master branch.
Pull requests must have 80% code coverage before beign considered for merge.
Additional details about the process can be found [here](./PR_REVIEW.md).
+There are more details available if you'd like to contribute a [bid adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html) or [analytics adapter](https://docs.prebid.org/dev-docs/integrate-with-the-prebid-analytics-api.html).
+
## Issues
[prebid.org](http://prebid.org/) contains documentation that may help answer questions you have about using Prebid.js.
If you can't find the answer there, try searching for a similar issue on the [issues page](https://github.com/prebid/Prebid.js/issues).
diff --git a/PR_REVIEW.md b/PR_REVIEW.md
index 6402fcbbbaa..9a57539a0cd 100644
--- a/PR_REVIEW.md
+++ b/PR_REVIEW.md
@@ -14,7 +14,7 @@ For modules and core platform updates, the initial reviewer should request an ad
- Review for obvious errors or bad coding practice / use best judgement here.
- If the change is a new feature / change to core prebid.js - review the change with a Tech Lead on the project and make sure they agree with the nature of change.
- If the change results in needing updates to docs (such as public API change, module interface etc), add a label for "needs docs" and inform the submitter they must submit a docs PR to update the appropriate area of Prebid.org **before the PR can merge**. Help them with finding where the docs are located on prebid.org if needed.
- - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/bidder.md file):
+ - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file):
- If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true`
- If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true`
- If they support the US Privacy consentManagementUsp module, add `usp_supported: true`
@@ -23,7 +23,7 @@ For modules and core platform updates, the initial reviewer should request an ad
- If they support COPPA, add `coppa_supported: true`
- If they support SChain, add `schain_supported: true`
- If their bidder doesn't work well with safeframed creatives, add `safeframes_ok: false`. This will alert publishers to not use safeframed creatives when creating the ad server entries for their bidder.
- - If they're a member of Prebid.org, add `prebid_member: true`
+ - If they're setting a deal ID in some scenarios, add `bidder_supports_deals: true`
- If all above is good, add a `LGTM` comment and request 1 additional core member to review.
- Once there is 2 `LGTM` on the PR, merge to master
- Ask the submitter to add a PR for documentation if applicable.
@@ -34,18 +34,17 @@ For modules and core platform updates, the initial reviewer should request an ad
- Follow steps above for general review process. In addition, please verify the following:
- Verify that bidder has submitted valid bid params and that bids are being received.
- Verify that bidder is not manipulating the prebid.js auction in any way or doing things that go against the principles of the project. If unsure check with the Tech Lead.
-- Verify that the bidder is being as efficient as possible, ideally not loading an external library, however if they do load a library it should be cached.
- Verify that code re-use is being done properly and that changes introduced by a bidder don't impact other bidders.
- If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed.
-- If the adapter is triggering any user syncs make sure they are using the user sync module in the Prebid.js core.
-- Requests to the bidder should support HTTPS
-- Responses from the bidder should be compressed (such as gzip, compress, deflate)
-- Bid responses may not use JSONP: All requests must be AJAX with JSON responses
-- Video openrtb params should be read from the ad unit when available; however bidder config can override the ad unit.
-- All user-sync (aka pixel) activity must be registered via the provided functions
-- Adapters may not use the $$PREBID_GLOBAL$$ variable
-- All adapters must support the creation of multiple concurrent instances. This means, for example, that adapters cannot rely on mutable global variables.
-- Adapters may not globally override or default the standard ad server targeting values: hb_adid, hb_bidder, hb_pb, hb_deal, or hb_size, hb_source, hb_format.
+- All required global and bidder-adapter rules defined in the [Module Rules](https://docs.prebid.org/dev-docs/module-rules.html) must be followed. Please review these rules often - we depend on reviewers to enforce them.
+- All bidder parameter conventions must be followed:
+ - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit.
+ - First party data must be read from [`fpd.context` and `fpd.user`](https://docs.prebid.org/dev-docs/publisher-api-reference.html#setConfig-fpd).
+ - Adapters that accept a floor parameter must also support the [floors module](https://docs.prebid.org/dev-docs/modules/floors.html) -- look for a call to the `getFloors()` function.
+ - Adapters cannot accept an schain parameter. Rather, they must look for the schain parameter at bidRequest.schain.
+ - The bidRequest page referrer must checked in addition to any bidder-specific parameter.
+ - If they're getting the COPPA flag, it must come from config.getConfig('coppa');
+
- After a new adapter is approved, let the submitter know they may open a PR in the [headerbid-expert repository](https://github.com/prebid/headerbid-expert) to have their adapter recognized by the [Headerbid Expert extension](https://chrome.google.com/webstore/detail/headerbid-expert/cgfkddgbnfplidghapbbnngaogeldmop). The PR should be to the [bidder patterns file](https://github.com/prebid/headerbid-expert/blob/master/bidderPatterns.js), adding an entry with their adapter's name and the url the adapter uses to send and receive bid responses.
## Ticket Coordinator
diff --git a/README.md b/README.md
index b3f33b27aa5..44882570d89 100644
--- a/README.md
+++ b/README.md
@@ -268,7 +268,7 @@ As you make code changes, the bundles will be rebuilt and the page reloaded auto
## Contribute
-Many SSPs, bidders, and publishers have contributed to this project. [60+ Bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js.
+Many SSPs, bidders, and publishers have contributed to this project. [Hundreds of bidders](https://github.com/prebid/Prebid.js/tree/master/src/adapters) are supported by Prebid.js.
For guidelines, see [Contributing](./CONTRIBUTING.md).
@@ -276,9 +276,7 @@ Our PR review process can be found [here](https://github.com/prebid/Prebid.js/tr
### Add a Bidder Adapter
-To add a bidder adapter module, see the instructions in [How to add a bidder adaptor](http://prebid.org/dev-docs/bidder-adaptor.html).
-
-Please **do NOT load Prebid.js inside your adapter**. If you do this, we will reject or remove your adapter as appropriate.
+To add a bidder adapter module, see the instructions in [How to add a bidder adapter](https://docs.prebid.org/dev-docs/bidder-adaptor.html).
### Code Quality
diff --git a/integrationExamples/gpt/jwplayerRtdProvider_example.html b/integrationExamples/gpt/jwplayerRtdProvider_example.html
new file mode 100644
index 00000000000..3791ab42137
--- /dev/null
+++ b/integrationExamples/gpt/jwplayerRtdProvider_example.html
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+ JW Player RTD Provider Example
+
+
+
+
+
+
+
+
+Div-1
+
+
+
+
+
diff --git a/integrationExamples/gpt/userId_example.html b/integrationExamples/gpt/userId_example.html
index 4acbe0aae31..9307bc91456 100644
--- a/integrationExamples/gpt/userId_example.html
+++ b/integrationExamples/gpt/userId_example.html
@@ -115,7 +115,7 @@
consentManagement: {
cmpApi: 'iab',
timeout: 1000,
- allowAuctionWithoutConsent: true
+ defaultGdprScope: true
},
// consentManagement: {
// cmpApi: 'static',
@@ -128,7 +128,7 @@
// }
// }
// },
- usersync: {
+ userSync: {
userIds: [{
name: "unifiedId",
params: {
@@ -164,6 +164,18 @@
},
}, {
+ name: "merkleId",
+ params: {
+ ptk: '12345678-aaaa-bbbb-cccc-123456789abc', //Set your real merkle partner key here
+ pubid: 'EXAMPLE' //Set your real merkle publisher id here
+ },
+ storage: {
+ type: "html5",
+ name: "merkleId",
+ expires: 30
+ },
+
+ },{
name: "parrableId",
params: {
// change to Parrable Partner Client ID(s) you received from the Parrable Partners you are using
diff --git a/karma.conf.maker.js b/karma.conf.maker.js
index 712ef14caa1..8af216d6262 100644
--- a/karma.conf.maker.js
+++ b/karma.conf.maker.js
@@ -170,6 +170,16 @@ module.exports = function(codeCoverage, browserstack, watchMode, file) {
plugins: plugins
}
+
+ // To ensure that, we are able to run single spec file
+ // here we are adding preprocessors, when file is passed
+ if (file) {
+ config.files.forEach((file) => {
+ config.preprocessors[file] = ['webpack', 'sourcemap'];
+ });
+ delete config.preprocessors['test/test_index.js'];
+ }
+
setReporters(config, codeCoverage, browserstack);
setBrowsers(config, browserstack);
return config;
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 58ab8798fa5..50d17fc5f6c 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -7,16 +7,19 @@
"britepoolIdSystem",
"liveIntentIdSystem",
"lotamePanoramaId",
+ "merkleIdSystem",
"criteoIdSystem",
"netIdSystem",
"identityLinkIdSystem",
- "sharedIdSystem"
+ "sharedIdSystem",
+ "intentIqIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
"dfpAdServerVideo"
],
"rtdModule": [
- "browsiRtdProvider"
+ "browsiRtdProvider",
+ "jwplayerRtdProvider"
]
}
diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js
index 06c2eea5d67..10edd8ae3e3 100644
--- a/modules/adbutlerBidAdapter.js
+++ b/modules/adbutlerBidAdapter.js
@@ -9,7 +9,7 @@ const BIDDER_CODE = 'adbutler';
export const spec = {
code: BIDDER_CODE,
pageID: Math.floor(Math.random() * 10e6),
- aliases: ['divreach'],
+ aliases: ['divreach', 'doceree'],
isBidRequestValid: function (bid) {
return !!(bid.params.accountID && bid.params.zoneID);
diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js
index cd5a44f34d8..606e56c13d5 100644
--- a/modules/adheseBidAdapter.js
+++ b/modules/adheseBidAdapter.js
@@ -4,10 +4,12 @@ import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
const BIDDER_CODE = 'adhese';
+const GVLID = 553;
const USER_SYNC_BASE_URL = 'https://user-sync.adhese.com/iframe/user_sync.html';
export const spec = {
code: BIDDER_CODE,
+ gvlid: GVLID,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid: function(bid) {
@@ -112,12 +114,15 @@ function mergeTargets(targets, target) {
if (target) {
Object.keys(target).forEach(function (key) {
const val = target[key];
- const values = Array.isArray(val) ? val : [val];
- if (targets[key]) {
- const distinctValues = values.filter(v => targets[key].indexOf(v) < 0);
- targets[key].push.apply(targets[key], distinctValues);
- } else {
- targets[key] = values;
+ const dirtyValues = Array.isArray(val) ? val : [val];
+ const values = dirtyValues.filter(v => v === 0 || v);
+ if (values.length > 0) {
+ if (targets[key]) {
+ const distinctValues = values.filter(v => targets[key].indexOf(v) < 0);
+ targets[key].push.apply(targets[key], distinctValues);
+ } else {
+ targets[key] = values;
+ }
}
});
}
diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js
index f0fab83aeea..6b83c40897e 100644
--- a/modules/aniviewBidAdapter.js
+++ b/modules/aniviewBidAdapter.js
@@ -3,6 +3,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js';
import { Renderer } from '../src/Renderer.js';
const BIDDER_CODE = 'aniview';
+const GVLID = 780;
const TTL = 600;
function avRenderer(bid) {
@@ -24,10 +25,28 @@ function avRenderer(bid) {
}
function newRenderer(bidRequest) {
- let playerDomain = bidRequest && bidRequest.bidRequest && bidRequest.bidRequest.params && bidRequest.bidRequest.params.playerDomain ? bidRequest.bidRequest.params.playerDomain : 'player.aniview.com';
+ let playerDomain = 'player.aniview.com';
+ const config = {};
+
+ if (bidRequest && bidRequest.bidRequest && bidRequest.bidRequest.params) {
+ const params = bidRequest.bidRequest.params
+
+ if (params.playerDomain) {
+ playerDomain = params.playerDomain;
+ }
+
+ if (params.AV_PUBLISHERID) {
+ config.AV_PUBLISHERID = params.AV_PUBLISHERID;
+ }
+
+ if (params.AV_CHANNELID) {
+ config.AV_CHANNELID = params.AV_CHANNELID;
+ }
+ }
+
const renderer = Renderer.install({
url: 'https://' + playerDomain + '/script/6.1/prebidRenderer.js',
- config: {},
+ config: config,
loaded: false,
});
@@ -252,7 +271,8 @@ function getUserSyncs(syncOptions, serverResponses) {
export const spec = {
code: BIDDER_CODE,
- aliases: ['selectmediavideo'],
+ gvlid: GVLID,
+ aliases: ['avantisvideo', 'selectmediavideo'],
supportedMediaTypes: [VIDEO],
isBidRequestValid,
buildRequests,
diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js
new file mode 100644
index 00000000000..5a285be71c0
--- /dev/null
+++ b/modules/brightMountainMediaBidAdapter.js
@@ -0,0 +1,89 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
+import * as utils from '../src/utils.js';
+
+const BIDDER_CODE = 'brightmountainmedia';
+const AD_URL = 'https://console.brightmountainmedia.com/hb/bid';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
+
+ isBidRequestValid: (bid) => {
+ return Boolean(bid.bidId && bid.params && bid.params.placement_id);
+ },
+
+ buildRequests: (validBidRequests, bidderRequest) => {
+ let winTop = window;
+ let location;
+ try {
+ location = new URL(bidderRequest.refererInfo.referer)
+ winTop = window.top;
+ } catch (e) {
+ location = winTop.location;
+ utils.logMessage(e);
+ };
+ let placements = [];
+ let request = {
+ 'deviceWidth': winTop.screen.width,
+ 'deviceHeight': winTop.screen.height,
+ 'language': (navigator && navigator.language) ? navigator.language : '',
+ 'secure': 1,
+ 'host': location.host,
+ 'page': location.pathname,
+ 'placements': placements
+ };
+ if (bidderRequest) {
+ if (bidderRequest.gdprConsent) {
+ request.gdpr_consent = bidderRequest.gdprConsent.consentString || 'ALL'
+ request.gdpr_require = bidderRequest.gdprConsent.gdprApplies ? 1 : 0
+ }
+ }
+ for (let i = 0; i < validBidRequests.length; i++) {
+ let bid = validBidRequests[i];
+ let traff = bid.params.traffic || BANNER
+ let placement = {
+ placementId: bid.params.placement_id,
+ bidId: bid.bidId,
+ sizes: bid.mediaTypes[traff].sizes,
+ traffic: traff
+ };
+ if (bid.schain) {
+ placement.schain = bid.schain;
+ }
+ placements.push(placement);
+ }
+ return {
+ method: 'POST',
+ url: AD_URL,
+ data: request
+ };
+ },
+
+ interpretResponse: (serverResponse) => {
+ let response = [];
+ try {
+ serverResponse = serverResponse.body;
+ for (let i = 0; i < serverResponse.length; i++) {
+ let resItem = serverResponse[i];
+
+ response.push(resItem);
+ }
+ } catch (e) {
+ utils.logMessage(e);
+ };
+ return response;
+ },
+
+ getUserSyncs: (syncOptions) => {
+ if (syncOptions.iframeEnabled) {
+ return [{
+ type: 'iframe',
+ url: 'https://console.brightmountainmedia.com:4444/cookieSync'
+ }];
+ }
+ },
+
+};
+
+registerBidder(spec);
diff --git a/modules/brightMountainMediaBidAdapter.md b/modules/brightMountainMediaBidAdapter.md
new file mode 100644
index 00000000000..a9900b5264d
--- /dev/null
+++ b/modules/brightMountainMediaBidAdapter.md
@@ -0,0 +1,34 @@
+# Overview
+
+```
+Module Name: Bright Mountain Media Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: dev@brightmountainmedia.com
+```
+
+# Description
+
+Connects to Bright Mountain Media exchange for bids.
+
+Bright Mountain Media bid adapter currently supports Banner.
+
+# Test Parameters
+```
+ var adUnits = [
+ code: 'placementid_0',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'brightmountainmedia',
+ params: {
+ placement_id: '5f21784949be82079d08c',
+ traffic: 'banner'
+ }
+ }
+ ]
+ ];
+```
\ No newline at end of file
diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js
index 9317786fb8d..3aff3c6aac6 100644
--- a/modules/browsiRtdProvider.js
+++ b/modules/browsiRtdProvider.js
@@ -37,6 +37,8 @@ let _moduleParams = {};
let _data = null;
/** @type {null | function} */
let _dataReadyCallback = null;
+/** @type {string} */
+const DEF_KEYNAME = 'browsiViewability';
/**
* add browsi script to page
@@ -117,16 +119,14 @@ function waitForData(callback) {
function sendDataToModule(adUnits, onDone) {
try {
waitForData(_predictionsData => {
- const _predictions = _predictionsData.p;
- if (!_predictions || !Object.keys(_predictions).length) {
- return onDone({});
- }
+ const _predictions = _predictionsData.p || {};
let dataToReturn = adUnits.reduce((rp, cau) => {
const adUnitCode = cau && cau.code;
if (!adUnitCode) { return rp }
const adSlot = getSlotByCode(adUnitCode);
const identifier = adSlot ? getMacroId(_predictionsData.pmd, adSlot) : adUnitCode;
const predictionData = _predictions[identifier];
+ rp[adUnitCode] = getKVObject(-1, _predictionsData.kn);
if (!predictionData) { return rp }
if (predictionData.p) {
@@ -160,7 +160,7 @@ function getAllSlots() {
function getKVObject(p, keyName) {
const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2);
let prObject = {};
- prObject[((_moduleParams['keyName'] || keyName).toString())] = prValue.toString();
+ prObject[((_moduleParams['keyName'] || keyName || DEF_KEYNAME).toString())] = prValue.toString();
return prObject;
}
/**
@@ -231,7 +231,7 @@ function evaluate(macro, divId, adUnit, replacer) {
* @param {string} url server url with query params
*/
function getPredictionsFromServer(url) {
- let ajax = ajaxBuilder(_moduleParams.auctionDelay || _moduleParams.timeout || DEF_TIMEOUT);
+ let ajax = ajaxBuilder(_moduleParams.auctionDelay || _moduleParams.timeout);
ajax(url,
{
@@ -286,21 +286,26 @@ export const browsiSubmodule = {
* @param {adUnit[]} adUnits
* @param {function} onDone
*/
- getData: sendDataToModule
+ getData: sendDataToModule,
+ init: init
};
-export function init(config) {
+function init(config, gdpr, usp) {
+ return true;
+}
+
+export function beforeInit(config) {
const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => {
try {
_moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter(
pr => pr.name && pr.name.toLowerCase() === 'browsi')[0].params;
+ confListener();
_moduleParams.auctionDelay = realTimeData.auctionDelay;
- _moduleParams.timeout = realTimeData.timeout;
+ _moduleParams.timeout = realTimeData.timeout || DEF_TIMEOUT;
} catch (e) {
_moduleParams = {};
}
if (_moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) {
- confListener();
collectData();
} else {
utils.logError('missing params for Browsi provider');
@@ -308,5 +313,8 @@ export function init(config) {
});
}
-submodule('realTimeData', browsiSubmodule);
-init(config);
+function registerSubModule() {
+ submodule('realTimeData', browsiSubmodule);
+}
+registerSubModule();
+beforeInit(config);
diff --git a/modules/concertAnalyticsAdapter.js b/modules/concertAnalyticsAdapter.js
new file mode 100644
index 00000000000..a81d07e63b5
--- /dev/null
+++ b/modules/concertAnalyticsAdapter.js
@@ -0,0 +1,120 @@
+import {ajax} from '../src/ajax.js';
+import adapter from '../src/AnalyticsAdapter.js';
+import CONSTANTS from '../src/constants.json';
+import adapterManager from '../src/adapterManager.js';
+import * as utils from '../src/utils.js';
+
+const analyticsType = 'endpoint';
+
+// We only want to send about 1% of events for sampling purposes
+const SAMPLE_RATE_PERCENTAGE = 1 / 100;
+const pageIncludedInSample = sampleAnalytics();
+
+const url = 'https://bids.concert.io/analytics';
+
+const {
+ EVENTS: {
+ BID_RESPONSE,
+ BID_WON,
+ AUCTION_END
+ }
+} = CONSTANTS;
+
+let queue = [];
+
+let concertAnalytics = Object.assign(adapter({url, analyticsType}), {
+ track({ eventType, args }) {
+ switch (eventType) {
+ case BID_RESPONSE:
+ if (args.bidder !== 'concert') break;
+ queue.push(mapBidEvent(eventType, args));
+ break;
+
+ case BID_WON:
+ if (args.bidder !== 'concert') break;
+ queue.push(mapBidEvent(eventType, args));
+ break;
+
+ case AUCTION_END:
+ // Set a delay, as BID_WON events will come after AUCTION_END events
+ setTimeout(() => sendEvents(), 3000);
+ break;
+
+ default:
+ break;
+ }
+ }
+});
+
+function mapBidEvent(eventType, args) {
+ const { adId, auctionId, cpm, creativeId, width, height, timeToRespond } = args;
+ const [gamCreativeId, concertRequestId] = getConcertRequestId(creativeId);
+
+ const payload = {
+ event: eventType,
+ concert_rid: concertRequestId,
+ adId,
+ auctionId,
+ creativeId: gamCreativeId,
+ position: args.adUnitCode,
+ url: window.location.href,
+ cpm,
+ width,
+ height,
+ timeToRespond
+ }
+
+ return payload;
+}
+
+/**
+ * In order to pass back the concert_rid from CBS, it is tucked into the `creativeId`
+ * slot in the bid response and combined with a pipe `|`. This method splits the creative ID
+ * and the concert_rid.
+ *
+ * @param {string} creativeId
+ */
+function getConcertRequestId(creativeId) {
+ if (!creativeId || creativeId.indexOf('|') < 0) return [null, null];
+
+ return creativeId.split('|');
+}
+
+function sampleAnalytics() {
+ return Math.random() <= SAMPLE_RATE_PERCENTAGE;
+}
+
+function sendEvents() {
+ concertAnalytics.eventsStorage = queue;
+
+ if (!queue.length) return;
+
+ if (!pageIncludedInSample) {
+ utils.logMessage('Page not included in sample for Concert Analytics');
+ return;
+ }
+
+ try {
+ const body = JSON.stringify(queue);
+ ajax(url, () => queue = [], body, {
+ contentType: 'application/json',
+ method: 'POST'
+ });
+ } catch (err) { utils.logMessage('Concert Analytics error') }
+}
+
+// save the base class function
+concertAnalytics.originEnableAnalytics = concertAnalytics.enableAnalytics;
+concertAnalytics.eventsStorage = [];
+
+// override enableAnalytics so we can get access to the config passed in from the page
+concertAnalytics.enableAnalytics = function (config) {
+ concertAnalytics.originEnableAnalytics(config);
+};
+
+adapterManager.registerAnalyticsAdapter({
+ adapter: concertAnalytics,
+ code: 'concert'
+});
+
+export default concertAnalytics;
diff --git a/modules/concertAnalyticsAdapter.md b/modules/concertAnalyticsAdapter.md
new file mode 100644
index 00000000000..7c9b6d22703
--- /dev/null
+++ b/modules/concertAnalyticsAdapter.md
@@ -0,0 +1,11 @@
+# Overview
+
+```
+Module Name: Concert Analytics Adapter
+Module Type: Analytics Adapter
+Maintainer: support@concert.io
+```
+
+# Description
+
+Analytics adapter for concert.
\ No newline at end of file
diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js
new file mode 100644
index 00000000000..d153ddf9ee2
--- /dev/null
+++ b/modules/concertBidAdapter.js
@@ -0,0 +1,208 @@
+
+import * as utils from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getStorageManager } from '../src/storageManager.js'
+
+const BIDDER_CODE = 'concert';
+const CONCERT_ENDPOINT = 'https://bids.concert.io';
+const USER_SYNC_URL = 'https://cdn.concert.io/lib/bids/sync.html';
+
+export const spec = {
+ code: BIDDER_CODE,
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ if (!bid.params.partnerId) {
+ utils.logWarn('Missing partnerId bid parameter');
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @param {bidderRequest} -
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(validBidRequests, bidderRequest) {
+ utils.logMessage(validBidRequests);
+ utils.logMessage(bidderRequest);
+ let payload = {
+ meta: {
+ prebidVersion: '$prebid.version$',
+ pageUrl: bidderRequest.refererInfo.referer,
+ screen: [window.screen.width, window.screen.height].join('x'),
+ debug: utils.debugTurnedOn(),
+ uid: getUid(bidderRequest),
+ optedOut: hasOptedOutOfPersonalization(),
+ adapterVersion: '1.1.0',
+ uspConsent: bidderRequest.uspConsent,
+ gdprConsent: bidderRequest.gdprConsent
+ }
+ }
+
+ payload.slots = validBidRequests.map(bidRequest => {
+ let slot = {
+ name: bidRequest.adUnitCode,
+ bidId: bidRequest.bidId,
+ transactionId: bidRequest.transactionId,
+ sizes: bidRequest.sizes,
+ partnerId: bidRequest.params.partnerId,
+ slotType: bidRequest.params.slotType
+ }
+
+ return slot;
+ });
+
+ utils.logMessage(payload);
+
+ return {
+ method: 'POST',
+ url: `${CONCERT_ENDPOINT}/bids/prebid`,
+ data: JSON.stringify(payload)
+ }
+ },
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse, bidRequest) {
+ utils.logMessage(serverResponse);
+ utils.logMessage(bidRequest);
+
+ const serverBody = serverResponse.body;
+
+ if (!serverBody || typeof serverBody !== 'object') {
+ return [];
+ }
+
+ let bidResponses = [];
+
+ bidResponses = serverBody.bids.map(bid => {
+ return {
+ requestId: bid.bidId,
+ cpm: bid.cpm,
+ width: bid.width,
+ height: bid.height,
+ ad: bid.ad,
+ ttl: bid.ttl,
+ creativeId: bid.creativeId,
+ netRevenue: bid.netRevenue,
+ currency: bid.currency
+ }
+ });
+
+ if (utils.debugTurnedOn() && serverBody.debug) {
+ utils.logMessage(`CONCERT`, serverBody.debug);
+ }
+
+ utils.logMessage(bidResponses);
+ return bidResponses;
+ },
+
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @param {ServerResponse[]} serverResponses List of server's responses.
+ * @param {gdprConsent} object GDPR consent object.
+ * @param {uspConsent} string US Privacy String.
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) {
+ const syncs = []
+ if (syncOptions.iframeEnabled && !hasOptedOutOfPersonalization()) {
+ let params = [];
+
+ if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) {
+ params.push(`gdpr_applies=${gdprConsent.gdprApplies ? '1' : '0'}`);
+ }
+ if (gdprConsent && (typeof gdprConsent.consentString === 'string')) {
+ params.push(`gdpr_consent=${gdprConsent.consentString}`);
+ }
+ if (uspConsent && (typeof uspConsent === 'string')) {
+ params.push(`usp_consent=${uspConsent}`);
+ }
+
+ syncs.push({
+ type: 'iframe',
+ url: USER_SYNC_URL + (params.length > 0 ? `?${params.join('&')}` : '')
+ });
+ }
+ return syncs;
+ },
+
+ /**
+ * Register bidder specific code, which will execute if bidder timed out after an auction
+ * @param {data} Containing timeout specific data
+ */
+ onTimeout: function(data) {
+ utils.logMessage('concert bidder timed out');
+ utils.logMessage(data);
+ },
+
+ /**
+ * Register bidder specific code, which will execute if a bid from this bidder won the auction
+ * @param {Bid} The bid that won the auction
+ */
+ onBidWon: function(bid) {
+ utils.logMessage('concert bidder won bid');
+ utils.logMessage(bid);
+ }
+
+}
+
+registerBidder(spec);
+
+const storage = getStorageManager();
+
+/**
+ * Check or generate a UID for the current user.
+ */
+function getUid(bidderRequest) {
+ if (hasOptedOutOfPersonalization() || !consentAllowsPpid(bidderRequest)) {
+ return false;
+ }
+
+ const CONCERT_UID_KEY = 'c_uid';
+
+ let uid = storage.getDataFromLocalStorage(CONCERT_UID_KEY);
+
+ if (!uid) {
+ uid = utils.generateUUID();
+ storage.setDataInLocalStorage(CONCERT_UID_KEY, uid);
+ }
+
+ return uid;
+}
+
+/**
+ * Whether the user has opted out of personalization.
+ */
+function hasOptedOutOfPersonalization() {
+ const CONCERT_NO_PERSONALIZATION_KEY = 'c_nap';
+
+ return storage.getDataFromLocalStorage(CONCERT_NO_PERSONALIZATION_KEY) === 'true';
+}
+
+/**
+ * Whether the privacy consent strings allow personalization.
+ *
+ * @param {BidderRequest} bidderRequest Object which contains any data consent signals
+ */
+function consentAllowsPpid(bidderRequest) {
+ /* NOTE: We cannot easily test GDPR consent, without the
+ * `consent-string` npm module; so will have to rely on that
+ * happening on the bid-server. */
+ return !(bidderRequest.uspConsent === 'string' &&
+ bidderRequest.uspConsent.toUpperCase().substring(0, 2) === '1YY')
+}
diff --git a/modules/concertBidAdapter.md b/modules/concertBidAdapter.md
new file mode 100644
index 00000000000..faf774946d1
--- /dev/null
+++ b/modules/concertBidAdapter.md
@@ -0,0 +1,33 @@
+# Overview
+
+```
+Module Name: Concert Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: support@concert.io
+```
+
+# Description
+
+Module that connects to Concert demand sources
+
+# Test Paramters
+```
+ var adUnits = [
+ {
+ code: 'desktop_leaderboard_variable',
+ mediaTypes: {
+ banner: {
+ sizes: [[1030, 590]]
+ }
+ }
+ bids: [
+ {
+ bidder: "concert",
+ params: {
+ partnerId: 'test_partner'
+ }
+ }
+ ]
+ }
+ ];
+```
\ No newline at end of file
diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js
index 8b34df563ec..04459ec1f09 100644
--- a/modules/connectadBidAdapter.js
+++ b/modules/connectadBidAdapter.js
@@ -2,6 +2,7 @@ import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER } from '../src/mediaTypes.js'
import {config} from '../src/config.js';
+import {createEidsArray} from './userId/eids.js';
const BIDDER_CODE = 'connectad';
const BIDDER_CODE_ALIAS = 'connectadrealtime';
@@ -18,8 +19,6 @@ export const spec = {
},
buildRequests: function(validBidRequests, bidderRequest) {
- let digitrust;
-
let ret = {
method: 'POST',
url: '',
@@ -41,7 +40,8 @@ export const spec = {
screensize: getScreenSize(),
dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0,
language: navigator.language,
- ua: navigator.userAgent
+ ua: navigator.userAgent,
+ pversion: '$prebid.version$'
});
// coppa compliance
@@ -69,80 +69,19 @@ export const spec = {
utils.deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent);
}
- // Digitrust Support
- const bidRequestDigitrust = utils.deepAccess(validBidRequests[0], 'userId.digitrustid.data');
- if (bidRequestDigitrust && (!bidRequestDigitrust.privacy || !bidRequestDigitrust.privacy.optout)) {
- digitrust = {
- id: bidRequestDigitrust.id,
- keyv: bidRequestDigitrust.keyv
- }
- }
-
- if (digitrust) {
- utils.deepSetValue(data, 'user.ext.digitrust', {
- id: digitrust.id,
- keyv: digitrust.keyv
- })
- }
-
- if (validBidRequests[0].userId && typeof validBidRequests[0].userId === 'object' && (validBidRequests[0].userId.tdid || validBidRequests[0].userId.pubcid || validBidRequests[0].userId.lipb || validBidRequests[0].userId.id5id || validBidRequests[0].userId.parrableId)) {
- utils.deepSetValue(data, 'user.ext.eids', []);
-
- if (validBidRequests[0].userId.tdid) {
- data.user.ext.eids.push({
- source: 'adserver.org',
- uids: [{
- id: validBidRequests[0].userId.tdid,
- ext: {
- rtiPartner: 'TDID'
- }
- }]
- });
- }
-
- if (validBidRequests[0].userId.pubcid) {
- data.user.ext.eids.push({
- source: 'pubcommon',
- uids: [{
- id: validBidRequests[0].userId.pubcid,
- }]
- });
- }
-
- if (validBidRequests[0].userId.id5id) {
- data.user.ext.eids.push({
- source: 'id5-sync.com',
- uids: [{
- id: validBidRequests[0].userId.id5id,
- }]
- });
- }
-
- if (validBidRequests[0].userId.parrableId) {
- data.user.ext.eids.push({
- source: 'parrable.com',
- uids: [{
- id: validBidRequests[0].userId.parrableId.eid,
- }]
- });
- }
-
- if (validBidRequests[0].userId.lipb && validBidRequests[0].userId.lipb.lipbid) {
- data.user.ext.eids.push({
- source: 'liveintent.com',
- uids: [{
- id: validBidRequests[0].userId.lipb.lipbid
- }]
- });
- }
+ // EIDS Support
+ if (validBidRequests[0].userId) {
+ data.user.ext.eids = createEidsArray(validBidRequests[0].userId);
}
validBidRequests.map(bid => {
const placement = Object.assign({
id: bid.transactionId,
divName: bid.bidId,
+ pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0],
sizes: bid.mediaTypes.banner.sizes,
- adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes)
+ adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes),
+ floor: getBidFloor(bid)
}, bid.params);
if (placement.networkId && placement.siteId) {
@@ -277,6 +216,22 @@ sizeMap[331] = '320x250';
sizeMap[3301] = '320x267';
sizeMap[2730] = '728x250';
+function getBidFloor(bidRequest) {
+ let floorInfo = {};
+
+ if (typeof bidRequest.getFloor === 'function') {
+ floorInfo = bidRequest.getFloor({
+ currency: 'USD',
+ mediaType: 'banner',
+ size: '*'
+ });
+ }
+
+ let floor = floorInfo.floor || 0;
+
+ return floor;
+}
+
function getSize(sizes) {
const result = [];
sizes.forEach(function(size) {
diff --git a/modules/consentManagement.js b/modules/consentManagement.js
index 0c48d8c854c..90fe24735db 100644
--- a/modules/consentManagement.js
+++ b/modules/consentManagement.js
@@ -371,7 +371,7 @@ function cmpFailed(errMsg, hookConfig, extraArgs) {
}
/**
- * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction
+ * Stores CMP data locally in module and then invokes gdprDataHandler.setConsentData() to make information available in adaptermanager.js for later in the auction
* @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only)
*/
function storeConsentData(cmpConsentObject) {
@@ -387,6 +387,9 @@ function storeConsentData(cmpConsentObject) {
vendorData: (cmpConsentObject) || undefined,
gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope
};
+ if (cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) {
+ consentData.addtlConsent = cmpConsentObject.addtlConsent;
+ };
}
consentData.apiVersion = cmpVersion;
gdprDataHandler.setConsentData(consentData);
diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js
old mode 100755
new mode 100644
index b416c00c2d0..61ccc0e786b
--- a/modules/cpmstarBidAdapter.js
+++ b/modules/cpmstarBidAdapter.js
@@ -13,6 +13,12 @@ const ENDPOINT_PRODUCTION = 'https://server.cpmstar.com/view.aspx';
const DEFAULT_TTL = 300;
const DEFAULT_CURRENCY = 'USD';
+function fixedEncodeURIComponent(str) {
+ return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
+ return '%' + c.charCodeAt(0).toString(16);
+ });
+}
+
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
@@ -47,13 +53,29 @@ export const spec = {
var mediaType = spec.getMediaType(bidRequest);
var playerSize = spec.getPlayerSize(bidRequest);
var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : '');
-
var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') +
'&json=c_b&mv=1&poolid=' + utils.getBidIdParameter('placementId', bidRequest.params) +
'&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) +
'&requestid=' + bidRequest.bidId +
'&referer=' + encodeURIComponent(referer);
+ if (bidRequest.schain && bidRequest.schain.nodes) {
+ var schain = bidRequest.schain;
+ var schainString = '';
+ schainString += schain.ver + ',' + schain.complete;
+ for (var i2 = 0; i2 < schain.nodes.length; i2++) {
+ var node = schain.nodes[i2];
+ schainString += '!' +
+ fixedEncodeURIComponent(node.asi || '') + ',' +
+ fixedEncodeURIComponent(node.sid || '') + ',' +
+ fixedEncodeURIComponent(node.hp || '') + ',' +
+ fixedEncodeURIComponent(node.rid || '') + ',' +
+ fixedEncodeURIComponent(node.name || '') + ',' +
+ fixedEncodeURIComponent(node.domain || '');
+ }
+ url += '&schain=' + schainString
+ }
+
if (bidderRequest.gdprConsent) {
if (bidderRequest.gdprConsent.consentString != null) {
url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString;
@@ -138,6 +160,21 @@ export const spec = {
}
return bidResponses;
+ },
+
+ getUserSyncs: function (syncOptions, serverResponses) {
+ const syncs = [];
+ if (serverResponses.length == 0 || !serverResponses[0].body) return syncs;
+ var usersyncs = serverResponses[0].body[0].syncs;
+ if (!usersyncs || usersyncs.length < 0) return syncs;
+ for (var i = 0; i < usersyncs.length; i++) {
+ var us = usersyncs[i];
+ if ((us.type === 'image' && syncOptions.pixelEnabled) || (us.type == 'iframe' && syncOptions.iframeEnabled)) {
+ syncs.push(us);
+ }
+ }
+ return syncs;
}
+
};
registerBidder(spec);
diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js
index 1d999fdbacc..9fe8c26e4f6 100644
--- a/modules/dfpAdServerVideo.js
+++ b/modules/dfpAdServerVideo.js
@@ -42,7 +42,7 @@ import { auctionManager } from '../src/auctionManager.js';
const defaultParamConstants = {
env: 'vp',
gdfp_req: 1,
- output: 'xml_vast3',
+ output: 'vast',
unviewed_position_start: 1,
};
diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js
index 03dca0b607d..21f3b7b3586 100644
--- a/modules/districtmDMXBidAdapter.js
+++ b/modules/districtmDMXBidAdapter.js
@@ -1,14 +1,26 @@
import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
const BIDDER_CODE = 'districtmDMX';
const DMXURI = 'https://dmx.districtm.io/b/v1';
+const VIDEO_MAPPING = {
+ playback_method: {
+ 'auto_play_sound_on': 1,
+ 'auto_play_sound_off': 2,
+ 'click_to_play': 3,
+ 'mouse_over': 4,
+ 'viewport_sound_on': 5,
+ 'viewport_sound_off': 6
+ }
+};
export const spec = {
code: BIDDER_CODE,
- supportedFormat: ['banner'],
+ supportedFormat: [BANNER, VIDEO],
+ supportedMediaTypes: [VIDEO, BANNER],
isBidRequestValid(bid) {
return !!(bid.params.dmxid && bid.params.memberid);
},
@@ -27,14 +39,23 @@ export const spec = {
nBid.requestId = nBid.impid;
nBid.width = nBid.w || width;
nBid.height = nBid.h || height;
+ nBid.mediaType = bid.mediaTypes && bid.mediaTypes.video ? 'video' : null;
+ if (nBid.mediaType) {
+ nBid.vastXml = cleanVast(nBid.adm);
+ }
if (nBid.dealid) {
nBid.dealId = nBid.dealid;
}
+ nBid.uuid = nBid.bidId;
nBid.ad = nBid.adm;
nBid.netRevenue = true;
nBid.creativeId = nBid.crid;
nBid.currency = 'USD';
nBid.ttl = 60;
+ nBid.meta = nBid.meta || {};
+ if (nBid.adomain && nBid.adomain.length > 0) {
+ nBid.meta.advertiserDomains = nBid.adomain;
+ }
return nBid;
} else {
oBid.cpm = oBid.price;
@@ -83,12 +104,19 @@ export const spec = {
}
let eids = [];
- if (bidRequest && bidRequest.userId) {
- bindUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1);
- bindUserId(eids, utils.deepAccess(bidRequest, `userId.digitrustid.data.id`), 'digitru.st', 1);
- bindUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1);
- bindUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1);
- bindUserId(eids, utils.deepAccess(bidRequest, `userId.tdid`), 'adserver.org', 1);
+ if (bidRequest[0] && bidRequest[0].userId) {
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.idl_env`), 'liveramp.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.id5id`), 'id5-sync.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.pubcid`), 'pubcid.org', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.tdid`), 'adserver.org', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.criteoId`), 'criteo.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.britepoolid`), 'britepool.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.lipb.lipbid`), 'liveintent.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.intentiqid`), 'intentiq.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.lotamePanoramaId`), 'lotame.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.parrableId`), 'parrable.com', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.netId`), 'netid.de', 1);
+ bindUserId(eids, utils.deepAccess(bidRequest[0], `userId.sharedid`), 'sharedid.org', 1);
dmxRequest.user = dmxRequest.user || {};
dmxRequest.user.ext = dmxRequest.user.ext || {};
dmxRequest.user.ext.eids = eids;
@@ -122,14 +150,34 @@ export const spec = {
obj.id = dmx.bidId;
obj.tagid = String(dmx.params.dmxid);
obj.secure = 1;
- obj.banner = {
- topframe: 1,
- w: cleanSizes(dmx.sizes, 'w'),
- h: cleanSizes(dmx.sizes, 'h'),
- format: cleanSizes(dmx.sizes).map(s => {
- return {w: s[0], h: s[1]};
- }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number')
- };
+ obj.bidfloor = getFloor(dmx);
+ if (dmx.mediaTypes && dmx.mediaTypes.video) {
+ obj.video = {
+ topframe: 1,
+ skip: dmx.mediaTypes.video.skippable || 0,
+ linearity: dmx.mediaTypes.video.linearity || 1,
+ minduration: dmx.mediaTypes.video.minduration || 5,
+ maxduration: dmx.mediaTypes.video.maxduration || 60,
+ playbackmethod: getPlaybackmethod(dmx.mediaTypes.video.playback_method),
+ api: getApi(dmx.mediaTypes.video),
+ mimes: dmx.mediaTypes.video.mimes || ['video/mp4'],
+ protocols: getProtocols(dmx.mediaTypes.video),
+ w: dmx.mediaTypes.video.playerSize[0][0],
+ h: dmx.mediaTypes.video.playerSize[0][1],
+ format: dmx.mediaTypes.video.playerSize.map(s => {
+ return {w: s[0], h: s[1]};
+ }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number')
+ };
+ } else {
+ obj.banner = {
+ topframe: 1,
+ w: cleanSizes(dmx.sizes, 'w'),
+ h: cleanSizes(dmx.sizes, 'h'),
+ format: cleanSizes(dmx.sizes).map(s => {
+ return {w: s[0], h: s[1]};
+ }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number')
+ };
+ }
return obj;
});
@@ -169,6 +217,27 @@ export const spec = {
}
}
+export function getFloor (bid) {
+ let floor = null;
+ if (typeof bid.getFloor === 'function') {
+ const floorInfo = bid.getFloor({
+ currency: 'USD',
+ mediaType: bid.mediaTypes.video ? 'video' : 'banner',
+ size: bid.sizes.map(size => {
+ return {
+ w: size[0],
+ h: size[1]
+ }
+ })
+ });
+ if (typeof floorInfo === 'object' &&
+ floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) {
+ floor = parseFloat(floorInfo.floor);
+ }
+ }
+ return floor !== null ? floor : bid.params.floor;
+}
+
export function cleanSizes(sizes, value) {
const supportedSize = [
{
@@ -310,4 +379,65 @@ export function bindUserId(eids, value, source, atype) {
})
}
}
+
+export function getApi({protocols}) {
+ let defaultValue = [2];
+ let listProtocols = [
+ {key: 'VPAID_1_0', value: 1},
+ {key: 'VPAID_2_0', value: 2},
+ {key: 'MRAID_1', value: 3},
+ {key: 'ORMMA', value: 4},
+ {key: 'MRAID_2', value: 5},
+ {key: 'MRAID_3', value: 6},
+ ];
+ if (protocols) {
+ return listProtocols.filter(p => {
+ return protocols.indexOf(p.key) !== -1;
+ }).map(p => p.value)
+ } else {
+ return defaultValue;
+ }
+}
+export function getPlaybackmethod(playback) {
+ if (Array.isArray(playback) && playback.length > 0) {
+ return playback.map(label => {
+ return VIDEO_MAPPING.playback_method[label]
+ })
+ }
+ return [2]
+}
+
+export function getProtocols({protocols}) {
+ let defaultValue = [2, 3, 5, 6, 7, 8];
+ let listProtocols = [
+ {key: 'VAST_1_0', value: 1},
+ {key: 'VAST_2_0', value: 2},
+ {key: 'VAST_3_0', value: 3},
+ {key: 'VAST_1_0_WRAPPER', value: 4},
+ {key: 'VAST_2_0_WRAPPER', value: 5},
+ {key: 'VAST_3_0_WRAPPER', value: 6},
+ {key: 'VAST_4_0', value: 7},
+ {key: 'VAST_4_0_WRAPPER', value: 8}
+ ];
+ if (protocols) {
+ return listProtocols.filter(p => {
+ return protocols.indexOf(p.key) !== -1
+ }).map(p => p.value);
+ } else {
+ return defaultValue;
+ }
+}
+
+export function cleanVast(str) {
+ const toberemove = /]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/
+ const [img, url] = str.match(toberemove)
+ str = str.replace(toberemove, '')
+ if (img) {
+ if (url) {
+ const insrt = ``
+ str = str.replace('', `${insrt}`)
+ }
+ }
+ return str;
+}
registerBidder(spec);
diff --git a/modules/districtmDmxBidAdapter.md b/modules/districtmDmxBidAdapter.md
index 792cf2e7305..5d5dd2affe6 100644
--- a/modules/districtmDmxBidAdapter.md
+++ b/modules/districtmDmxBidAdapter.md
@@ -20,13 +20,14 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman
## Media Types
* Banner
-
+* Video
## Bidder Parameters
| Key | Scope | Type | Description
| --- | --- | --- | ---
| `dmxid` | Mandatory | Integer | Unique identifier of the placement, dmxid can be obtained in the district m Boost platform.
| `memberid` | Mandatory | Integer | Unique identifier for your account, memberid can be obtained in the district m Boost platform.
+| `floor` | Optional | float | Most placement can have floor set in our platform, but this can now be set on the request too.
# Ad Unit Configuration Example
@@ -48,6 +49,35 @@ The `districtmDmxAdapter` module allows publishers to include DMX Exchange deman
}];
```
+# Ad Unit Configuration Example for video request
+
+```javascript
+ var videoAdUnit = {
+ code: 'video1',
+ sizes: [640,480],
+ mediaTypes: { video: {context: 'instream', //or 'outstream'
+ playerSize: [[640, 480]],
+ skipppable: true,
+ minduration: 5,
+ maxduration: 45,
+ playback_method: ['auto_play_sound_off', 'viewport_sound_off'],
+ mimes: ["application/javascript",
+ "video/mp4"],
+
+ } },
+ bids: [
+ {
+ bidder: 'districtmDMX',
+ params: {
+ dmxid: '100001',
+ memberid: '100003',
+ }
+ }
+
+ ]
+ };
+```
+
# Ad Unit Configuration when COPPA is needed
@@ -117,6 +147,35 @@ Our demand and adapter supports multiple sizes per placement, as such a single d
}];
```
+Our bidder only supports instream context at the moment and we strongly like to put the media types and setting in the ad unit settings.
+If no value is set the default value will be applied.
+
+```javascript
+ var videoAdUnit = {
+ code: 'video1',
+ sizes: [640,480],
+ mediaTypes: { video: {context: 'instream', //or 'outstream'
+ playerSize: [[640, 480]],
+ skipppable: true,
+ minduration: 5,
+ maxduration: 45,
+ playback_method: ['auto_play_sound_off', 'viewport_sound_off'],
+ mimes: ["application/javascript",
+ "video/mp4"],
+
+ } },
+ bids: [
+ {
+ bidder: 'districtmDMX',
+ params: {
+ dmxid: '250258',
+ memberid: '100600',
+ }
+ }
+ ]
+ };
+```
+
###### 4. Implementation Checking
Once the bidder is live in your Prebid configuration you may confirm it is making requests to our end point by looking for requests to `https://dmx.districtm.io/b/v1`.
diff --git a/modules/docereeBidAdapter.md b/modules/docereeBidAdapter.md
new file mode 100644
index 00000000000..9e3d4bbe1b8
--- /dev/null
+++ b/modules/docereeBidAdapter.md
@@ -0,0 +1,32 @@
+# Overview
+
+Module Name: Doceree Bidder Adapter
+Module Type: Bidder Adapter
+
+# Description
+
+Connects to Doceree demand source to fetch bids.
+Please use ```doceree``` as the bidder code.
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ code: 'desktop-banner-ad-div',
+ sizes: [[300, 250]],
+ bids: [
+ {
+ bidder: "doceree",
+ params: {
+ accountID: '167283',
+ zoneID: '445501',
+ domain: 'adbserver.doceree.com',
+ extra: {
+ tuid: '1234-abcd'
+ }
+ }
+ }
+ ]
+ },
+ ];
+```
diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js
index 6688d15d8e9..fa58481548a 100644
--- a/modules/emx_digitalBidAdapter.js
+++ b/modules/emx_digitalBidAdapter.js
@@ -162,6 +162,7 @@ export const emxAdapter = {
export const spec = {
code: BIDDER_CODE,
+ gvlid: 183,
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid: function (bid) {
if (!bid || !bid.params) {
@@ -279,12 +280,21 @@ export const spec = {
}
return emxBidResponses;
},
- getUserSyncs: function (syncOptions) {
+ getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) {
const syncs = [];
if (syncOptions.iframeEnabled) {
+ let url = 'https://biddr.brealtime.com/check.html';
+ if (gdprConsent && typeof gdprConsent.consentString === 'string') {
+ // add 'gdpr' only if 'gdprApplies' is defined
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ url += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`;
+ } else {
+ url += `?gdpr_consent=${gdprConsent.consentString}`;
+ }
+ }
syncs.push({
type: 'iframe',
- url: 'https://biddr.brealtime.com/check.html'
+ url: url
});
}
return syncs;
diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js
index c516c06d11a..14c33329b2d 100644
--- a/modules/identityLinkIdSystem.js
+++ b/modules/identityLinkIdSystem.js
@@ -6,8 +6,8 @@
*/
import * as utils from '../src/utils.js'
-import {ajax} from '../src/ajax.js';
-import {submodule} from '../src/hook.js';
+import { ajax } from '../src/ajax.js';
+import { submodule } from '../src/hook.js';
/** @type {Submodule} */
export const identityLinkSubmodule = {
@@ -16,6 +16,11 @@ export const identityLinkSubmodule = {
* @type {string}
*/
name: 'identityLink',
+ /**
+ * used to specify vendor id
+ * @type {number}
+ */
+ gvlid: 97,
/**
* decode the stored id value for passing to bid requests
* @function
@@ -39,10 +44,15 @@ export const identityLinkSubmodule = {
}
const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0;
const gdprConsentString = hasGdpr ? consentData.consentString : '';
+ const tcfPolicyV2 = utils.deepAccess(consentData, 'vendorData.tcfPolicyVersion') === 2;
// use protocol relative urls for http or https
- const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? '&ct=1&cv=' + gdprConsentString : ''}`;
+ if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) {
+ utils.logInfo('Consent string is required to call envelope API.');
+ return;
+ }
+ const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? (tcfPolicyV2 ? '&ct=4&cv=' : '&ct=1&cv=') + gdprConsentString : ''}`;
let resp;
- resp = function(callback) {
+ resp = function (callback) {
// Check ats during callback so it has a chance to initialise.
// If ats library is available, use it to retrieve envelope. If not use standard third party endpoint
if (window.ats) {
@@ -60,7 +70,7 @@ export const identityLinkSubmodule = {
}
};
- return {callback: resp};
+ return { callback: resp };
}
};
// return envelope from third party endpoint
@@ -83,7 +93,7 @@ function getEnvelope(url, callback) {
callback();
}
};
- ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true});
+ ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true });
}
submodule('userId', identityLinkSubmodule);
diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js
index 220aed47e15..50ed7d5f57c 100644
--- a/modules/invibesBidAdapter.js
+++ b/modules/invibesBidAdapter.js
@@ -8,7 +8,7 @@ const CONSTANTS = {
SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync',
TIME_TO_LIVE: 300,
DEFAULT_CURRENCY: 'EUR',
- PREBID_VERSION: 2,
+ PREBID_VERSION: 4,
METHOD: 'GET',
INVIBES_VENDOR_ID: 436
};
@@ -39,9 +39,7 @@ export const spec = {
},
getUserSyncs: function (syncOptions) {
if (syncOptions.iframeEnabled) {
- handlePostMessage();
const syncUrl = buildSyncUrl();
-
return {
type: 'iframe',
url: syncUrl
@@ -55,6 +53,7 @@ registerBidder(spec);
// some state info is required: cookie info, unique user visit id
const topWin = getTopMostWindow();
let invibes = topWin.invibes = topWin.invibes || {};
+invibes.purposes = invibes.purposes || [false, false, false, false, false, false, false, false, false, false];
let _customUserSync;
function isBidRequestValid(bid) {
@@ -89,13 +88,11 @@ function buildRequest(bidRequests, bidderRequest) {
_customUserSync = _customUserSync || bidRequest.params.customUserSync;
});
- invibes.visitId = invibes.visitId || generateRandomId();
+ invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent);
- cookieDomain = detectTopmostCookieDomain();
+ invibes.visitId = invibes.visitId || generateRandomId();
invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie');
- invibes.optIn = invibes.optIn || invibes.getCookie('ivOptIn') || readGdprConsent(bidderRequest.gdprConsent);
-
- initDomainId(invibes.domainOptions);
+ let lid = initDomainId(invibes.domainOptions);
const currentQueryStringParams = parseQueryStringParams();
@@ -117,14 +114,16 @@ function buildRequest(bidRequests, bidderRequest) {
width: topWin.innerWidth,
height: topWin.innerHeight,
- noc: !cookieDomain,
oi: invibes.optIn,
- kw: keywords
+ kw: keywords,
+ purposes: invibes.purposes.toString(),
+
+ tc: invibes.gdpr_consent
};
- if (invibes.dom.id) {
- data.lId = invibes.dom.id;
+ if (lid) {
+ data.lId = lid;
}
const parametersToPassForward = 'videoaddebug,advs,bvci,bvid,istop,trybvid,trybvci'.split(',');
@@ -278,6 +277,8 @@ function renderCreative(bidModel) {
function getCappedCampaignsAsString() {
const key = 'ivvcap';
+ if (!invibes.optIn || !invibes.purposes[0]) { return ''; }
+
let loadData = function () {
try {
return JSON.parse(storage.getDataFromLocalStorage(key)) || {};
@@ -348,49 +349,48 @@ function buildSyncUrl() {
return syncUrl;
}
-function handlePostMessage() {
- try {
- if (window.addEventListener) {
- window.addEventListener('message', acceptPostMessage);
- }
- } catch (e) { }
-}
-
-function acceptPostMessage(e) {
- let msg = e.data || {};
- if (msg.ivbscd === 1) {
- invibes.setCookie(msg.name, msg.value, msg.exdays, msg.domain);
- } else if (msg.ivbscd === 2) {
- invibes.dom.graduate();
- }
-}
-
function readGdprConsent(gdprConsent) {
if (gdprConsent && gdprConsent.vendorData) {
+ invibes.gdpr_consent = getVendorConsentData(gdprConsent.vendorData);
+
if (!gdprConsent.vendorData.gdprApplies || gdprConsent.vendorData.hasGlobalConsent) {
+ var index;
+ for (index = 0; index < invibes.purposes; ++index) {
+ invibes.purposes[index] = true;
+ }
return 2;
}
let purposeConsents = getPurposeConsents(gdprConsent.vendorData);
if (purposeConsents == null) { return 0; }
- let properties = Object.keys(purposeConsents);
- let purposeConsentsCounter = getPurposeConsentsCounter(gdprConsent.vendorData);
+ let purposesLength = getPurposeConsentsCounter(gdprConsent.vendorData);
- if (properties.length < purposeConsentsCounter) {
+ if (purposeConsents instanceof Array) {
+ for (let i = 0; i < purposesLength && i < purposeConsents.length; i++) {
+ invibes.purposes[i] = !((purposeConsents[i] === false || purposeConsents[i] === 'false' || purposeConsents[i] == null));
+ }
+ } else if (typeof purposeConsents === 'object' && purposeConsents !== null) {
+ let i = 0;
+ for (let prop in purposeConsents) {
+ if (i === purposesLength) {
+ break;
+ }
+
+ if (purposeConsents.hasOwnProperty(prop)) {
+ invibes.purposes[i] = !((purposeConsents[prop] === false || purposeConsents[prop] === 'false' || purposeConsents[prop] == null));
+ i++;
+ }
+ }
+ } else {
return 0;
}
- for (let i = 0; i < purposeConsentsCounter; i++) {
- if (!purposeConsents[properties[i]] || purposeConsents[properties[i]] === 'false') { return 0; }
- }
-
+ let invibesVendorId = CONSTANTS.INVIBES_VENDOR_ID.toString(10);
let vendorConsents = getVendorConsents(gdprConsent.vendorData);
- if (vendorConsents == null || vendorConsents[CONSTANTS.INVIBES_VENDOR_ID.toString(10)] == null) {
- return 4;
- }
+ if (vendorConsents == null || vendorConsents[invibesVendorId] == null) { return 4; }
- if (vendorConsents[CONSTANTS.INVIBES_VENDOR_ID.toString(10)] === false) { return 0; }
+ if (vendorConsents[invibesVendorId] === false) { return 0; }
return 2;
}
@@ -418,6 +418,13 @@ function getPurposeConsents(vendorData) {
return null;
}
+function getVendorConsentData(vendorData) {
+ if (vendorData.purpose && vendorData.purpose.consents) {
+ if (vendorData.tcString != null) { return vendorData.tcString; }
+ }
+ return vendorData.consentData;
+};
+
function getVendorConsents(vendorData) {
if (vendorData.vendor && vendorData.vendor.consents) {
return vendorData.vendor.consents;
@@ -443,55 +450,15 @@ invibes.Uid = {
}
};
-let cookieDomain;
invibes.getCookie = function (name) {
if (!storage.cookiesAreEnabled()) { return; }
- let i, x, y;
- let cookies = document.cookie.split(';');
- for (i = 0; i < cookies.length; i++) {
- x = cookies[i].substr(0, cookies[i].indexOf('='));
- y = cookies[i].substr(cookies[i].indexOf('=') + 1);
- x = x.replace(/^\s+|\s+$/g, '');
- if (x === name) {
- return unescape(y);
- }
- }
-};
-invibes.setCookie = function (name, value, exdays, domain) {
- if (!storage.cookiesAreEnabled()) { return; }
- let whiteListed = name == 'ivNoCookie' || name == 'IvbsCampIdsLocal';
- if (invibes.noCookies && !whiteListed && (exdays || 0) >= 0) { return; }
- if (exdays > 365) { exdays = 365; }
- domain = domain || cookieDomain;
- let exdate = new Date();
- let exms = exdays * 24 * 60 * 60 * 1000;
- exdate.setTime(exdate.getTime() + exms);
- storage.setCookie(name, value, exdate.toUTCString(), undefined, domain);
-};
+ if (!invibes.optIn || !invibes.purposes[0]) { return; }
-let detectTopmostCookieDomain = function () {
- let testCookie = invibes.Uid.generate();
- let hostParts = location.hostname.split('.');
- if (hostParts.length === 1) {
- return location.hostname;
- }
- for (let i = hostParts.length - 1; i >= 0; i--) {
- let domain = '.' + hostParts.slice(i).join('.');
- invibes.setCookie(testCookie, testCookie, 1, domain);
- let val = invibes.getCookie(testCookie);
- if (val === testCookie) {
- invibes.setCookie(testCookie, testCookie, -1, domain);
- return domain;
- }
- }
+ return storage.getCookie(name);
};
let initDomainId = function (options) {
- if (invibes.dom) { return; }
-
- options = options || {};
-
let cookiePersistence = {
cname: 'ivbsdid',
load: function () {
@@ -499,75 +466,16 @@ let initDomainId = function (options) {
try {
return JSON.parse(str);
} catch (e) { }
- },
- save: function (obj) {
- invibes.setCookie(this.cname, JSON.stringify(obj), 365);
- }
- };
-
- let persistence = options.persistence || cookiePersistence;
- let state;
- let minHC = 2;
-
- let validGradTime = function (state) {
- if (!state.cr) { return false; }
- let min = 151 * 10e9;
- if (state.cr < min) {
- return false;
}
- let now = new Date().getTime();
- let age = now - state.cr;
- let minAge = 24 * 60 * 60 * 1000;
- return age > minAge;
- };
-
- state = persistence.load() || {
- id: invibes.Uid.generate(),
- cr: new Date().getTime(),
- hc: 1,
};
- if (state.id.match(/\./)) {
- state.id = invibes.Uid.generate();
- }
-
- let graduate = function () {
- if (!state.cr) { return; }
- delete state.cr;
- delete state.hc;
- persistence.save(state);
- setId();
- };
+ options = options || {};
- let regenerateId = function () {
- state.id = invibes.Uid.generate();
- persistence.save(state);
- };
+ var persistence = options.persistence || cookiePersistence;
- let setId = function () {
- invibes.dom = {
- get id() {
- return (!state.cr && invibes.optIn > 0) ? state.id : undefined;
- },
- get tempId() {
- return (invibes.optIn > 0) ? state.id : undefined;
- },
- graduate: graduate,
- regen: regenerateId
- };
- };
+ let state = persistence.load();
- if (state.cr && !options.noVisit) {
- if (state.hc < minHC) {
- state.hc++;
- }
- if ((state.hc >= minHC && validGradTime(state)) || options.skipGraduation) {
- graduate();
- }
- }
- persistence.save(state);
- setId();
- ivLogger.info('Did=' + invibes.dom.id);
+ return state ? (state.id || state.tempId) : undefined;
};
let keywords = (function () {
diff --git a/modules/ironsourceBidAdapter.js b/modules/ironsourceBidAdapter.js
new file mode 100644
index 00000000000..34650d46a0f
--- /dev/null
+++ b/modules/ironsourceBidAdapter.js
@@ -0,0 +1,234 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import * as utils from '../src/utils.js';
+import {VIDEO} from '../src/mediaTypes.js';
+import {config} from '../src/config.js';
+
+const SUPPORTED_AD_TYPES = [VIDEO];
+const BIDDER_CODE = 'ironsource';
+const BIDDER_VERSION = '4.0.0';
+const TTL = 360;
+const SELLER_ENDPOINT = 'https://hb.yellowblue.io/hb';
+const SUPPORTED_SYNC_METHODS = {
+ IFRAME: 'iframe',
+ PIXEL: 'pixel'
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ version: BIDDER_VERSION,
+ supportedMediaTypes: SUPPORTED_AD_TYPES,
+ isBidRequestValid: function(bidRequest) {
+ return !!(bidRequest.params.isOrg);
+ },
+ buildRequests: function (bidRequests, bidderRequest) {
+ if (bidRequests.length === 0) {
+ return [];
+ }
+
+ const requests = [];
+
+ bidRequests.forEach(bid => {
+ requests.push(buildVideoRequest(bid, bidderRequest));
+ });
+
+ return requests;
+ },
+ interpretResponse: function({body}) {
+ const bidResponses = [];
+
+ const bidResponse = {
+ requestId: body.requestId,
+ cpm: body.cpm,
+ width: body.width,
+ height: body.height,
+ creativeId: body.requestId,
+ currency: body.currency,
+ netRevenue: body.netRevenue,
+ ttl: TTL,
+ vastXml: body.vastXml,
+ mediaType: VIDEO
+ };
+
+ bidResponses.push(bidResponse);
+
+ return bidResponses;
+ },
+ getUserSyncs: function(syncOptions, serverResponses) {
+ const syncs = [];
+ for (const response of serverResponses) {
+ if (syncOptions.iframeEnabled && response.body.userSyncURL) {
+ syncs.push({
+ type: 'iframe',
+ url: response.body.userSyncURL
+ });
+ }
+ if (syncOptions.pixelEnabled && utils.isArray(response.body.userSyncPixels)) {
+ const pixels = response.body.userSyncPixels.map(pixel => {
+ return {
+ type: 'image',
+ url: pixel
+ }
+ })
+ syncs.push(...pixels)
+ }
+ }
+ return syncs;
+ }
+};
+
+registerBidder(spec);
+
+/**
+ * Build the video request
+ * @param bid {bid}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object}
+ */
+function buildVideoRequest(bid, bidderRequest) {
+ const sellerParams = generateParameters(bid, bidderRequest);
+ return {
+ method: 'GET',
+ url: SELLER_ENDPOINT,
+ data: sellerParams
+ };
+}
+
+/**
+ * Get the the ad size from the bid
+ * @param bid {bid}
+ * @returns {Array}
+ */
+function getSizes(bid) {
+ if (utils.deepAccess(bid, 'mediaTypes.video.sizes')) {
+ return bid.mediaTypes.video.sizes[0];
+ } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) {
+ return bid.sizes[0];
+ }
+ return [];
+}
+
+/**
+ * Get schain string value
+ * @param schainObject {Object}
+ * @returns {string}
+ */
+function getSupplyChain(schainObject) {
+ if (utils.isEmpty(schainObject)) {
+ return '';
+ }
+ let scStr = `${schainObject.ver},${schainObject.complete}`;
+ schainObject.nodes.forEach((node) => {
+ scStr += '!';
+ scStr += `${getEncodedValIfNotEmpty(node.asi)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.sid)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.hp)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.rid)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.name)},`;
+ scStr += `${getEncodedValIfNotEmpty(node.domain)}`;
+ });
+ return scStr;
+}
+
+/**
+ * Get encoded node value
+ * @param val {string}
+ * @returns {string}
+ */
+function getEncodedValIfNotEmpty(val) {
+ return !utils.isEmpty(val) ? encodeURIComponent(val) : '';
+}
+
+/**
+ * Get preferred user-sync method based on publisher configuration
+ * @param bidderCode {string}
+ * @returns {string}
+ */
+function getAllowedSyncMethod(filterSettings, bidderCode) {
+ const iframeConfigsToCheck = ['all', 'iframe'];
+ const pixelConfigToCheck = 'image';
+ if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) {
+ return SUPPORTED_SYNC_METHODS.IFRAME;
+ }
+ if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) {
+ return SUPPORTED_SYNC_METHODS.PIXEL;
+ }
+}
+
+/**
+ * Check if sync rule is supported
+ * @param syncRule {Object}
+ * @param bidderCode {string}
+ * @returns {boolean}
+ */
+function isSyncMethodAllowed(syncRule, bidderCode) {
+ if (!syncRule) {
+ return false;
+ }
+ const isInclude = syncRule.filter === 'include';
+ const bidders = utils.isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode];
+ return isInclude && utils.contains(bidders, bidderCode);
+}
+
+/**
+ * Generate query parameters for the request
+ * @param bid {bid}
+ * @param bidderRequest {bidderRequest}
+ * @returns {Object}
+ */
+function generateParameters(bid, bidderRequest) {
+ const timeout = config.getConfig('bidderTimeout');
+ const { syncEnabled, filterSettings } = config.getConfig('userSync');
+ const [ width, height ] = getSizes(bid);
+ const { params } = bid;
+ const { bidderCode } = bidderRequest;
+ const domain = window.location.hostname;
+
+ const requestParams = {
+ auction_start: utils.timestamp(),
+ ad_unit_code: utils.getBidIdParameter('adUnitCode', bid),
+ tmax: timeout,
+ width: width,
+ height: height,
+ publisher_id: params.isOrg,
+ floor_price: params.floorPrice,
+ ua: navigator.userAgent,
+ bid_id: utils.getBidIdParameter('bidId', bid),
+ bidder_request_id: utils.getBidIdParameter('bidderRequestId', bid),
+ transaction_id: utils.getBidIdParameter('transactionId', bid),
+ session_id: utils.getBidIdParameter('auctionId', bid),
+ publisher_name: domain,
+ site_domain: domain,
+ bidder_version: BIDDER_VERSION
+ };
+
+ if (syncEnabled) {
+ const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode);
+ if (allowedSyncMethod) {
+ requestParams.cs_method = allowedSyncMethod;
+ }
+ }
+
+ if (bidderRequest.uspConsent) {
+ requestParams.us_privacy = bidderRequest.uspConsent;
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) {
+ requestParams.gdpr = bidderRequest.gdprConsent.gdprApplies;
+ requestParams.gdpr_consent = bidderRequest.gdprConsent.consentString;
+ }
+
+ if (params.ifa) {
+ requestParams.ifa = params.ifa;
+ }
+
+ if (bid.schain) {
+ requestParams.schain = getSupplyChain(bid.schain);
+ }
+
+ if (bidderRequest && bidderRequest.refererInfo) {
+ requestParams.referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer');
+ requestParams.page_url = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href');
+ }
+
+ return requestParams;
+}
diff --git a/modules/ironsourceBidAdapter.md b/modules/ironsourceBidAdapter.md
new file mode 100644
index 00000000000..2f9e38b69e8
--- /dev/null
+++ b/modules/ironsourceBidAdapter.md
@@ -0,0 +1,49 @@
+#Overview
+
+Module Name: IronSource Bidder Adapter
+
+Module Type: Bidder Adapter
+
+Maintainer: prebid-digital-brands@ironsrc.com
+
+
+# Description
+
+Module that connects to IronSource's demand sources.
+
+The IronSource adapter requires setup and approval from the IronSource. Please reach out to prebid-digital-brands@ironsrc.com to create an IronSource account.
+
+The adapter supports Video(instream). For the integration, IronSource returns content as vastXML and requires the publisher to define the cache url in config passed to Prebid for it to be valid in the auction.
+
+# Bid Parameters
+## Video
+
+| Name | Scope | Type | Description | Example
+| ---- | ----- | ---- | ----------- | -------
+| `isOrg` | required | String | IronSource publisher Id provided by your IronSource representative | "56f91cd4d3e3660002000033"
+| `floorPrice` | optional | Number | Minimum price in USD. Misuse of this parameter can impact revenue | 2.00
+| `ifa` | optional | String | The ID for advertisers (also referred to as "IDFA") | "XXX-XXX"
+
+# Test Parameters
+```javascript
+var adUnits = [
+ {
+ code: 'dfp-video-div',
+ sizes: [[640, 480]],
+ mediaTypes: {
+ video: {
+ playerSize: [[640, 480]],
+ context: 'instream'
+ }
+ },
+ bids: [{
+ bidder: 'ironsource',
+ params: {
+ isOrg: '56f91cd4d3e3660002000033', // Required
+ floorPrice: 2.00, // Optional
+ ifa: 'XXX-XXX', // Optional
+ }
+ }]
+ }
+ ];
+```
diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js
new file mode 100644
index 00000000000..b7c8879ed8e
--- /dev/null
+++ b/modules/jwplayerRtdProvider.js
@@ -0,0 +1,209 @@
+/**
+ * This module adds the jwplayer provider to the Real Time Data module (rtdModule)
+ * The {@link module:modules/realTimeData} module is required
+ * The module will allow Ad Bidders to obtain JW Player's Video Ad Targeting information
+ * The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid
+ * requests are made while the segments are being fetched, they will be blocked until all requests complete, or the
+ * timeout expires.
+ * @module modules/jwplayerRtdProvider
+ * @requires module:modules/realTimeData
+ */
+
+import { submodule } from '../src/hook.js';
+import { config } from '../src/config.js';
+import { ajaxBuilder } from '../src/ajax.js';
+import { logError } from '../src/utils.js';
+import find from 'core-js-pure/features/array/find.js';
+
+const SUBMODULE_NAME = 'jwplayer';
+let requestCount = 0;
+let requestTimeout = 150;
+const segCache = {};
+let resumeBidRequest;
+
+/** @type {RtdSubmodule} */
+export const jwplayerSubmodule = {
+ /**
+ * used to link submodule with realTimeData
+ * @type {string}
+ */
+ name: SUBMODULE_NAME,
+ /**
+ * get data and send back to realTimeData module
+ * @function
+ * @param {adUnit[]} adUnits
+ * @param {function} onDone
+ */
+ getData: getSegments,
+ init
+};
+
+config.getConfig('realTimeData', ({realTimeData}) => {
+ const providers = realTimeData.dataProviders;
+ const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME);
+ const params = jwplayerProvider && jwplayerProvider.params;
+ if (!params) {
+ return;
+ }
+ const rtdModuleTimeout = params.auctionDelay || params.timeout;
+ requestTimeout = rtdModuleTimeout === undefined ? requestTimeout : Math.max(rtdModuleTimeout - 1, 0);
+ fetchTargetingInformation(params);
+});
+
+submodule('realTimeData', jwplayerSubmodule);
+
+function init(config, gdpr, usp) {
+ return true;
+}
+
+export function fetchTargetingInformation(jwTargeting) {
+ const mediaIDs = jwTargeting.mediaIDs;
+ if (!mediaIDs) {
+ return;
+ }
+ mediaIDs.forEach(mediaID => {
+ fetchTargetingForMediaId(mediaID);
+ });
+}
+
+export function fetchTargetingForMediaId(mediaId) {
+ const ajax = ajaxBuilder(requestTimeout);
+ requestCount++;
+ ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, {
+ success: function (response) {
+ try {
+ const data = JSON.parse(response);
+ if (!data) {
+ throw ('Empty response');
+ }
+
+ const playlist = data.playlist;
+ if (!playlist || !playlist.length) {
+ throw ('Empty playlist');
+ }
+
+ const jwpseg = playlist[0].jwpseg;
+ if (jwpseg) {
+ segCache[mediaId] = jwpseg;
+ }
+ } catch (err) {
+ logError(err);
+ }
+ onRequestCompleted();
+ },
+ error: function () {
+ logError('failed to retrieve targeting information');
+ onRequestCompleted();
+ }
+ });
+}
+
+function onRequestCompleted() {
+ requestCount--;
+ if (requestCount > 0) {
+ return;
+ }
+
+ if (resumeBidRequest) {
+ resumeBidRequest();
+ resumeBidRequest = null;
+ }
+}
+
+function getSegments(adUnits, onDone) {
+ executeAfterPrefetch(() => {
+ const realTimeData = adUnits.reduce((data, adUnit) => {
+ const code = adUnit.code;
+ const vat = code && getTargetingForBid(adUnit);
+ if (!vat) {
+ return data;
+ }
+
+ const { segments, mediaID } = vat;
+ const jwTargeting = {};
+ if (segments && segments.length) {
+ jwTargeting.segments = segments;
+ }
+
+ if (mediaID) {
+ const id = 'jw_' + mediaID;
+ jwTargeting.content = {
+ id
+ }
+ }
+
+ data[code] = {
+ jwTargeting
+ };
+ return data;
+ }, {});
+ onDone(realTimeData);
+ });
+}
+
+function executeAfterPrefetch(callback) {
+ if (requestCount > 0) {
+ resumeBidRequest = callback;
+ } else {
+ callback();
+ }
+}
+
+/**
+ * Retrieves the targeting information pertaining to a bid request.
+ * @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain
+ * a jwTargeting property.
+ * @returns targetingInformation {object} nullable - contains the media ID as well as the jwpseg targeting segments
+ * found for the given bidRequest information
+ */
+export function getTargetingForBid(bidRequest) {
+ const jwTargeting = bidRequest.jwTargeting;
+ if (!jwTargeting) {
+ return null;
+ }
+ const playerID = jwTargeting.playerID;
+ let mediaID = jwTargeting.mediaID;
+ let segments = segCache[mediaID];
+ if (segments) {
+ return {
+ segments,
+ mediaID
+ };
+ }
+
+ const player = getPlayer(playerID);
+ if (!player) {
+ return null;
+ }
+
+ const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem();
+ if (!item) {
+ return null;
+ }
+
+ mediaID = mediaID || item.mediaid;
+ segments = item.jwpseg;
+ if (segments && mediaID) {
+ segCache[mediaID] = segments;
+ }
+
+ return {
+ segments,
+ mediaID
+ };
+}
+
+function getPlayer(playerID) {
+ const jwplayer = window.jwplayer;
+ if (!jwplayer) {
+ logError('jwplayer.js was not found on page');
+ return;
+ }
+
+ const player = jwplayer(playerID);
+ if (!player || !player.getPlaylist) {
+ logError('player ID did not match any players');
+ return;
+ }
+ return player;
+}
diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md
new file mode 100644
index 00000000000..06a7f69f497
--- /dev/null
+++ b/modules/jwplayerRtdProvider.md
@@ -0,0 +1,96 @@
+The purpose of this Real Time Data Provider is to allow publishers to target against their JW Player media without
+having to integrate with the VPB product. This prebid module makes JW Player's video ad targeting information accessible
+to Bid Adapters.
+
+**Usage for Publishers:**
+
+Compile the JW Player RTD Provider into your Prebid build:
+
+`gulp build --modules=jwplayerRtdProvider`
+
+Publishers must register JW Player as a real time data provider by setting up a Prebid Config conformant to the
+following structure:
+
+```javascript
+const jwplayerDataProvider = {
+ name: "jwplayer"
+};
+
+pbjs.setConfig({
+ ...,
+ realTimeData: {
+ dataProviders: [
+ jwplayerDataProvider
+ ]
+ }
+});
+```
+
+In order to prefetch targeting information for certain media, include the media IDs in the `jwplayerDataProvider` var:
+
+```javascript
+const jwplayerDataProvider = {
+ name: "jwplayer",
+ params: {
+ mediaIDs: ['abc', 'def', 'ghi', 'jkl']
+ }
+};
+```
+Lastly, include the content's media ID and/or the player's ID in the matching AdUnit:
+
+```javascript
+const adUnit = {
+ code: '/19968336/prebid_native_example_1',
+ ...
+ jwTargeting: {
+ playerID: 'abcd',
+ mediaID: '1234'
+ }
+};
+
+pbjs.que.push(function() {
+ pbjs.addAdUnits([adUnit]);
+ pbjs.requestBids({
+ ...
+ });
+});
+```
+
+**Usage for Bid Adapters:**
+
+Implement the `buildRequests` function. When it is called, the `bidRequests` param will be an array of bids.
+Each bid for which targeting information was found will conform to the following object structure:
+
+```javascript
+{
+ adUnitCode: 'xyz',
+ bidId: 'abc',
+ ...
+ realTimeData: {
+ ...,
+ jwTargeting: {
+ segments: ['123', '456'],
+ content: {
+ id: 'jw_abc123'
+ }
+ }
+ }
+}
+```
+
+where:
+- `segments` is an array of jwpseg targeting segments, of type string.
+- `content` is an object containing metadata for the media. It may contain the following information:
+ - `id` is a unique identifier for the specific media asset.
+
+**Example:**
+
+To view an example:
+
+- in your cli run:
+
+`gulp serve --modules=jwplayerRtdProvider`
+
+- in your browser, navigate to:
+
+`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html`
diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js
index 31c35f4afe3..03767efc135 100644
--- a/modules/kargoBidAdapter.js
+++ b/modules/kargoBidAdapter.js
@@ -65,7 +65,8 @@ export const spec = {
let meta;
if (adUnit.metadata && adUnit.metadata.landingPageDomain) {
meta = {
- clickUrl: adUnit.metadata.landingPageDomain
+ clickUrl: adUnit.metadata.landingPageDomain,
+ advertiserDomains: [adUnit.metadata.landingPageDomain]
};
}
bidResponses.push({
diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js
new file mode 100644
index 00000000000..68eb95ddae3
--- /dev/null
+++ b/modules/mediagoBidAdapter.js
@@ -0,0 +1,362 @@
+/**
+ * gulp serve --modules=mediagoBidAdapter --nolint --notest
+ */
+
+import * as utils from '../src/utils.js';
+import { getStorageManager } from '../src/storageManager.js';
+import {
+ registerBidder
+} from '../src/adapters/bidderFactory.js';
+
+const BIDDER_CODE = 'mediago';
+// const PROTOCOL = window.document.location.protocol;
+const ENDPOINT_URL =
+ // ((PROTOCOL === 'https:') ? 'https' : 'http') +
+ 'https://rtb-us.mediago.io/api/bid?tn=';
+const TIME_TO_LIVE = 500;
+// const ENDPOINT_URL = '/api/bid?tn=';
+const storage = getStorageManager();
+let globals = {};
+let itemMaps = {};
+
+/**
+ * 获取随机id
+ * @param {number} a random number from 0 to 15
+ * @return {string} random number or random string
+ */
+function getRandomId(
+ a // placeholder
+) {
+ return a // if the placeholder was passed, return
+ ? ( // a random number from 0 to 15
+ a ^ // unless b is 8,
+ Math.random() * // in which case
+ 16 >> // a random number from
+ a / 4 // 8 to 11
+ ).toString(16) // in hexadecimal
+ : ( // or otherwise a concatenated string:
+ [1e7] + // 10000000 +
+ 1e3 + // -1000 +
+ 4e3 + // -4000 +
+ 8e3 + // -80000000 +
+ 1e11 // -100000000000,
+ ).replace( // replacing
+ /[018]/g, // zeroes, ones, and eights with
+ getRandomId // random hex digits
+ );
+}
+
+/* ----- mguid:start ------ */
+const COOKIE_KEY_MGUID = '__mguid_';
+
+/**
+ * 获取用户id
+ * @return {string}
+ */
+const getUserID = () => {
+ const i = storage.getCookie(COOKIE_KEY_MGUID);
+
+ if (i === null) {
+ const uuid = utils.generateUUID();
+ storage.setCookie(COOKIE_KEY_MGUID, uuid);
+ return uuid;
+ }
+ return i;
+};
+
+/* ----- mguid:end ------ */
+
+/**
+ * 获取一个对象的某个值,如果没有则返回空字符串
+ * @param {Object} obj 对象
+ * @param {...string} keys 键名
+ * @return {any}
+ */
+function getProperty(obj, ...keys) {
+ let o = obj;
+
+ for (let key of keys) {
+ // console.log(key, o);
+ if (o && o[key]) {
+ o = o[key];
+ } else {
+ return '';
+ }
+ }
+ return o;
+}
+
+/**
+ * 是不是移动设备或者平板
+ * @return {boolean}
+ */
+function isMobileAndTablet() {
+ let check = false;
+ (function(a) {
+ let reg1 = new RegExp(['(android|bb\d+|meego)',
+ '.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)',
+ '|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone',
+ '|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap',
+ '|windows ce|xda|xiino|android|ipad|playbook|silk'
+ ].join(''), 'i');
+ let reg2 = new RegExp(['1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)',
+ '|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )',
+ '|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell',
+ '|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)',
+ '|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene',
+ '|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c',
+ '|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom',
+ '|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)',
+ '|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)',
+ '|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]',
+ '|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)',
+ '|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio',
+ '|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms',
+ '|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al',
+ '|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)',
+ '|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|',
+ 'v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)',
+ '|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-',
+ '|your|zeto|zte\-'
+ ].join(''), 'i');
+ if (reg1.test(a) ||
+ reg2.test(a.substr(0, 4))) {
+ check = true;
+ }
+ })(navigator.userAgent || navigator.vendor || window.opera);
+ return check;
+}
+
+/**
+ * 将尺寸转为RTB识别的尺寸
+ *
+ * @param {Array|Object} requestSizes 配置尺寸
+ * @return {Object}
+ */
+function transformSizes(requestSizes) {
+ let sizes = [];
+ let sizeObj = {};
+
+ if (utils.isArray(requestSizes) && requestSizes.length === 2 &&
+ !utils.isArray(requestSizes[0])) {
+ sizeObj.width = parseInt(requestSizes[0], 10);
+ sizeObj.height = parseInt(requestSizes[1], 10);
+ sizes.push(sizeObj);
+ } else if (typeof requestSizes === 'object') {
+ for (let i = 0; i < requestSizes.length; i++) {
+ let size = requestSizes[i];
+ sizeObj = {};
+ sizeObj.width = parseInt(size[0], 10);
+ sizeObj.height = parseInt(size[1], 10);
+ sizes.push(sizeObj);
+ }
+ }
+
+ return sizes;
+}
+
+/**
+ * 获取广告位配置
+ * @param {Array} validBidRequests an an array of bids
+ * @param {Object} bidderRequest The master bidRequest object
+ * @return {Object}
+ */
+function getItems(validBidRequests, bidderRequest) {
+ let items = [];
+ items = validBidRequests.map((req, i) => {
+ let ret = {};
+ let mediaTypes = getProperty(req, 'mediaTypes');
+
+ let sizes = transformSizes(getProperty(req, 'sizes'));
+ let matchSize;
+
+ // 确认尺寸是否符合我们要求
+ for (let size of sizes) {
+ if (size.width === 300 && size.height === 250) {
+ matchSize = size;
+ break;
+ }
+ }
+
+ // if (mediaTypes.native) {}
+ // banner广告类型
+ if (mediaTypes.banner) {
+ let id = '' + (i + 1);
+ ret = {
+ id: id,
+ // bidFloor: 0, // todo
+ banner: {
+ h: matchSize.height,
+ w: matchSize.width,
+ pos: 1,
+ }
+ };
+ itemMaps[id] = {
+ req,
+ ret
+ };
+ }
+
+ return ret;
+ });
+ return items;
+}
+
+/**
+ * 获取rtb请求参数
+ *
+ * @param {Array} validBidRequests an an array of bids
+ * @param {Object} bidderRequest The master bidRequest object
+ * @return {Object}
+ */
+function getParam(validBidRequests, bidderRequest) {
+ // console.log(validBidRequests, bidderRequest);
+ let isMobile = isMobileAndTablet() ? 1 : 0;
+ let isTest = 0;
+ let auctionId = getProperty(bidderRequest, 'auctionId') || getRandomId();
+ let items = getItems(validBidRequests, bidderRequest);
+
+ const domain = document.domain;
+ const location = utils.deepAccess(bidderRequest, 'refererInfo.referer');
+
+ if (items && items.length) {
+ let c = {
+ 'id': 'mgprebidjs_' + auctionId,
+ 'test': +isTest,
+ 'at': 1,
+ 'cur': ['USD'],
+ 'device': {
+ // 'dnt':0,
+ // 'devicetype':2,
+ 'js': 1,
+ 'os': navigator.platform || '',
+ 'ua': navigator.userAgent,
+ 'language': /en/.test(navigator.language) ? 'en' : navigator.language,
+ // 'geo':{
+ // 'country':'USA'
+ // }
+ },
+ 'user': {
+ 'id': getUserID() // todo
+ },
+ 'site': {
+ 'name': domain,
+ 'domain': domain,
+ 'page': location,
+ 'ref': location,
+ 'mobile': isMobile,
+ 'cat': [], // todo
+ 'publisher': { // todo
+ 'id': domain,
+ 'name': domain
+ }
+ },
+ 'imp': items
+ };
+ return c;
+ } else {
+ return null;
+ }
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ // aliases: ['ex'], // short code
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {BidRequest} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function(bid) {
+ // console.log('mediago', {
+ // bid
+ // });
+ if (bid.params.token) {
+ globals['token'] = bid.params.token;
+ }
+ return !!(bid.params.token);
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {Array} validBidRequests an an array of bids
+ * @param {Object} bidderRequest The master bidRequest object
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function(validBidRequests, bidderRequest) {
+ let payload = getParam(validBidRequests, bidderRequest);
+
+ const payloadString = JSON.stringify(payload);
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL + globals['token'],
+ data: payloadString,
+ };
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse, bidRequest) {
+ const bids = getProperty(serverResponse, 'body', 'seatbid', 0, 'bid');
+ const cur = getProperty(serverResponse, 'body', 'cur');
+
+ const bidResponses = [];
+
+ for (let bid of bids) {
+ let impid = getProperty(bid, 'impid');
+ if (itemMaps[impid]) {
+ let bidId = getProperty(itemMaps[impid], 'req', 'bidId');
+ const bidResponse = {
+ requestId: bidId,
+ cpm: getProperty(bid, 'price'),
+ width: getProperty(bid, 'w'),
+ height: getProperty(bid, 'h'),
+ creativeId: getProperty(bid, 'crid'),
+ dealId: '',
+ currency: cur,
+ netRevenue: true,
+ ttl: TIME_TO_LIVE,
+ // referrer: REFERER,
+ ad: getProperty(bid, 'adm')
+ };
+ bidResponses.push(bidResponse);
+ }
+ }
+
+ return bidResponses;
+ },
+
+ /**
+ * Register bidder specific code, which will execute if bidder timed out after an auction
+ * @param {data} Containing timeout specific data
+ */
+ onTimeout: function(data) {
+ // console.log('onTimeout', data);
+ // Bidder specifc code
+ },
+
+ /**
+ * Register bidder specific code, which will execute if a bid from this bidder won the auction
+ * @param {Bid} The bid that won the auction
+ */
+ onBidWon: function(bid) {
+ // console.log('onBidWon', bid);
+ // Bidder specific code
+ },
+
+ /**
+ * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder
+ * @param {Bid} The bid of which the targeting has been set
+ */
+ onSetTargeting: function(bid) {
+ // console.log('onSetTargeting', bid);
+ // Bidder specific code
+ }
+};
+registerBidder(spec);
diff --git a/modules/mediagoBidAdapter.md b/modules/mediagoBidAdapter.md
new file mode 100644
index 00000000000..87c38f662a3
--- /dev/null
+++ b/modules/mediagoBidAdapter.md
@@ -0,0 +1,33 @@
+# Overview
+
+```
+Module Name: MediaGo Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: fangsimin@baidu.com
+```
+
+# Description
+
+Module that connects to MediaGo's demand sources
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]],
+ }
+ },
+ bids: [
+ {
+ bidder: "mediago",
+ params: {
+ token: '' // required, send email to ext_mediago_am@baidu.com to get the corresponding token
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/modules/merkleIdSystem.js b/modules/merkleIdSystem.js
new file mode 100644
index 00000000000..d6bf96618df
--- /dev/null
+++ b/modules/merkleIdSystem.js
@@ -0,0 +1,80 @@
+/**
+ * This module adds merkleId to the User ID module
+ * The {@link module:modules/userId} module is required
+ * @module modules/merkleIdSystem
+ * @requires module:modules/userId
+ */
+
+import * as utils from '../src/utils.js'
+import {ajax} from '../src/ajax.js';
+import {submodule} from '../src/hook.js'
+
+const MODULE_NAME = 'merkleId';
+
+/** @type {Submodule} */
+export const merkleIdSubmodule = {
+ /**
+ * used to link submodule with config
+ * @type {string}
+ */
+ name: MODULE_NAME,
+ /**
+ * decode the stored id value for passing to bid requests
+ * @function
+ * @param {string} value
+ * @returns {{merkleId:string}}
+ */
+ decode(value) {
+ const id = (value && value.ppid && typeof value.ppid.id === 'string') ? value.ppid.id : undefined;
+ return id ? { 'merkleId': id } : undefined;
+ },
+ /**
+ * performs action to obtain id and return a value in the callback's response argument
+ * @function
+ * @param {SubmoduleParams} [configParams]
+ * @param {ConsentData} [consentData]
+ * @returns {IdResponse|undefined}
+ */
+ getId(configParams, consentData) {
+ if (!configParams || typeof configParams.pubid !== 'string') {
+ utils.logError('User ID - merkleId submodule requires a valid pubid to be defined');
+ return;
+ }
+
+ if (typeof configParams.ptk !== 'string') {
+ utils.logError('User ID - merkleId submodule requires a valid ptk string to be defined');
+ return;
+ }
+
+ if (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) {
+ utils.logError('User ID - merkleId submodule does not currently handle consent strings');
+ return;
+ }
+
+ const url = `https://mid.rkdms.com/idsv2?ptk=${configParams.ptk}&pubid=${configParams.pubid}`;
+
+ const resp = function (callback) {
+ const callbacks = {
+ success: response => {
+ let responseObj;
+ if (response) {
+ try {
+ responseObj = JSON.parse(response);
+ } catch (error) {
+ utils.logError(error);
+ }
+ }
+ callback(responseObj);
+ },
+ error: error => {
+ utils.logError(`${MODULE_NAME}: merkleId fetch encountered an error`, error);
+ callback();
+ }
+ };
+ ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true});
+ };
+ return {callback: resp};
+ }
+};
+
+submodule('userId', merkleIdSubmodule);
diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js
index 26b0a7dccc2..c5bc054ac04 100644
--- a/modules/oneVideoBidAdapter.js
+++ b/modules/oneVideoBidAdapter.js
@@ -4,12 +4,11 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
const BIDDER_CODE = 'oneVideo';
export const spec = {
code: 'oneVideo',
- VERSION: '3.0.3',
+ VERSION: '3.0.4',
ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=',
E2ETESTENDPOINT: 'https://ads-wc.v.ssp.yahoo.com/rtb/openrtb?ext_id=',
- SYNC_ENDPOINT1: 'https://cm.g.doubleclick.net/pixel?google_nid=adaptv_dbm&google_cm&google_sc',
- SYNC_ENDPOINT2: 'https://pr-bh.ybp.yahoo.com/sync/adaptv_ortb/{combo_uid}',
- SYNC_ENDPOINT3: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1',
+ SYNC_ENDPOINT1: 'https://pixel.advertising.com/ups/57304/sync?gdpr=&gdpr_consent=&_origin=0&redir=true',
+ SYNC_ENDPOINT2: 'https://match.adsrvr.org/track/cmf/generic?ttd_pid=adaptv&ttd_tpi=1',
supportedMediaTypes: ['video', 'banner'],
/**
* Determines whether or not the given bid request is valid.
@@ -136,17 +135,13 @@ export const spec = {
type: 'image',
url: spec.SYNC_ENDPOINT1
},
- {
- type: 'image',
- url: spec.SYNC_ENDPOINT2
- },
{
type: 'image',
url: `https://sync-tm.everesttech.net/upi/pid/m7y5t93k?gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&redir=https%3A%2F%2Fpixel.advertising.com%2Fups%2F55986%2Fsync%3Fuid%3D%24%7BUSER_ID%7D%26_origin%3D0` + encodeURI(`&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}`)
},
{
type: 'image',
- url: spec.SYNC_ENDPOINT3
+ url: spec.SYNC_ENDPOINT2
}];
}
}
diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js
index 1007305b324..b3d559d956f 100644
--- a/modules/prebidServerBidAdapter/index.js
+++ b/modules/prebidServerBidAdapter/index.js
@@ -641,6 +641,9 @@ const OPEN_RTB_PROTOCOL = {
}
utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies);
utils.deepSetValue(request, 'user.ext.consent', firstBidRequest.gdprConsent.consentString);
+ if (firstBidRequest.gdprConsent.addtlConsent && typeof firstBidRequest.gdprConsent.addtlConsent === 'string') {
+ utils.deepSetValue(request, 'user.ext.ConsentedProvidersSettings.consented_providers', firstBidRequest.gdprConsent.addtlConsent);
+ }
}
// US Privacy (CCPA) support
diff --git a/modules/priceFloors.js b/modules/priceFloors.js
index eb1f3aed84c..2acd21918ef 100644
--- a/modules/priceFloors.js
+++ b/modules/priceFloors.js
@@ -138,10 +138,10 @@ function generatePossibleEnumerations(arrayOfFields, delimiter) {
/**
* @summary If a the input bidder has a registered cpmadjustment it returns the input CPM after being adjusted
*/
-export function getBiddersCpmAdjustment(bidderName, inputCpm) {
+export function getBiddersCpmAdjustment(bidderName, inputCpm, bid = {}) {
const adjustmentFunction = utils.deepAccess(getGlobal(), `bidderSettings.${bidderName}.bidCpmAdjustment`);
if (adjustmentFunction) {
- return parseFloat(adjustmentFunction(inputCpm));
+ return parseFloat(adjustmentFunction(inputCpm, {...bid, cpm: inputCpm}));
}
return parseFloat(inputCpm);
}
@@ -682,7 +682,7 @@ export function addBidResponseHook(fn, adUnitCode, bid) {
}
// ok we got the bid response cpm in our desired currency. Now we need to run the bidders CPMAdjustment function if it exists
- adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm);
+ adjustedCpm = getBiddersCpmAdjustment(bid.bidderCode, adjustedCpm, bid);
// add necessary data information for analytics adapters / floor providers would possibly need
addFloorDataToBid(floorData, floorInfo, bid, adjustedCpm);
diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index 2e52fd27cf1..d21854a57c4 100644
--- a/modules/pubmaticBidAdapter.js
+++ b/modules/pubmaticBidAdapter.js
@@ -2,6 +2,7 @@ import * as utils from '../src/utils.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js';
import {config} from '../src/config.js';
+import { Renderer } from '../src/Renderer.js';
const BIDDER_CODE = 'pubmatic';
const LOG_WARN_PREFIX = 'PubMatic: ';
@@ -14,6 +15,8 @@ const UNDEFINED = undefined;
const DEFAULT_WIDTH = 0;
const DEFAULT_HEIGHT = 0;
const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html';
+const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com)
+const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application
const CUSTOM_PARAMS = {
'kadpageurl': '', // Custom page url
'gender': '', // User gender
@@ -104,6 +107,60 @@ const dealChannelValues = {
5: 'PREF',
6: 'PMPG'
};
+// BB stands for Blue BillyWig
+const BB_RENDERER = {
+ bootstrapPlayer: function(bid) {
+ const config = {
+ code: bid.adUnitCode,
+ };
+
+ if (bid.vastXml) config.vastXml = bid.vastXml;
+ else if (bid.vastUrl) config.vastUrl = bid.vastUrl;
+
+ if (!bid.vastXml && !bid.vastUrl) {
+ utils.logWarn(`${LOG_WARN_PREFIX}: No vastXml or vastUrl on bid, bailing...`);
+ return;
+ }
+
+ const rendererId = BB_RENDERER.getRendererId(PUBLICATION, bid.rendererCode);
+
+ const ele = document.getElementById(bid.adUnitCode); // NB convention
+
+ let renderer;
+
+ for (let rendererIndex = 0; rendererIndex < window.bluebillywig.renderers.length; rendererIndex++) {
+ if (window.bluebillywig.renderers[rendererIndex]._id === rendererId) {
+ renderer = window.bluebillywig.renderers[rendererIndex];
+ break;
+ }
+ }
+
+ if (renderer) renderer.bootstrap(config, ele);
+ else utils.logWarn(`${LOG_WARN_PREFIX}: Couldn't find a renderer with ${rendererId}`);
+ },
+ newRenderer: function(rendererCode, adUnitCode) {
+ var rendererUrl = RENDERER_URL.replace('$RENDERER', rendererCode);
+ const renderer = Renderer.install({
+ url: rendererUrl,
+ loaded: false,
+ adUnitCode
+ });
+
+ try {
+ renderer.setRender(BB_RENDERER.outstreamRender);
+ } catch (err) {
+ utils.logWarn(`${LOG_WARN_PREFIX}: Error tying to setRender on renderer`, err);
+ }
+
+ return renderer;
+ },
+ outstreamRender: function(bid) {
+ bid.renderer.push(function() { BB_RENDERER.bootstrapPlayer(bid) });
+ },
+ getRendererId: function(pub, renderer) {
+ return `${pub}-${renderer}`; // NB convention!
+ }
+};
let publisherId = 0;
let isInvalidNativeRequest = false;
@@ -760,6 +817,23 @@ function _handleDealCustomTargetings(payload, dctrArr, validBidRequests) {
}
}
+function _assignRenderer(newBid, request) {
+ let bidParams, context, adUnitCode;
+ if (request.bidderRequest && request.bidderRequest.bids) {
+ for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) {
+ if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === newBid.requestId) {
+ bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params;
+ context = request.bidderRequest.bids[bidderRequestBidsIndex].mediaTypes[VIDEO].context;
+ adUnitCode = request.bidderRequest.bids[bidderRequestBidsIndex].adUnitCode;
+ }
+ }
+ if (context && context === 'outstream' && bidParams && bidParams.outstreamAU && adUnitCode) {
+ newBid.rendererCode = bidParams.outstreamAU;
+ newBid.renderer = BB_RENDERER.newRenderer(newBid.rendererCode, adUnitCode);
+ }
+ }
+};
+
export const spec = {
code: BIDDER_CODE,
gvlid: 76,
@@ -782,6 +856,19 @@ export const spec = {
utils.logWarn(LOG_WARN_PREFIX + 'Error: For video ads, mimes is mandatory and must specify atlease 1 mime value. Call to OpenBid will not be sent for ad unit:' + JSON.stringify(bid));
return false;
}
+ if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) {
+ if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) {
+ utils.logError(`${LOG_WARN_PREFIX}: no context specified in bid. Rejecting bid: `, bid);
+ return false;
+ }
+ if (bid.mediaTypes[VIDEO].context === 'outstream' && !utils.isStr(bid.params.outstreamAU)) {
+ utils.logError(`${LOG_WARN_PREFIX}: for "outstream" bids outstreamAU is required. Rejecting bid: `, bid);
+ return false;
+ }
+ } else {
+ utils.logError(`${LOG_WARN_PREFIX}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid);
+ return false;
+ }
}
return true;
}
@@ -865,6 +952,11 @@ export const spec = {
payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim();
payload.site.domain = _getDomainFromURL(payload.site.page);
+ // add the content object from config in request
+ if (typeof config.getConfig('content') === 'object') {
+ payload.site.content = config.getConfig('content');
+ }
+
// merge the device from config.getConfig('device')
if (typeof config.getConfig('device') === 'object') {
payload.device = Object.assign(payload.device, config.getConfig('device'));
@@ -910,13 +1002,19 @@ export const spec = {
// not copying domain from site as it is a derived value from page
payload.app.publisher = payload.site.publisher;
payload.app.ext = payload.site.ext || UNDEFINED;
+ // We will also need to pass content object in app.content if app object is also set into the config;
+ // BUT do not use content object from config if content object is present in app as app.content
+ if (typeof payload.app.content !== 'object') {
+ payload.app.content = payload.site.content || UNDEFINED;
+ }
delete payload.site;
}
return {
method: 'POST',
url: ENDPOINT,
- data: JSON.stringify(payload)
+ data: JSON.stringify(payload),
+ bidderRequest: bidderRequest
};
},
@@ -966,6 +1064,7 @@ export const spec = {
newBid.width = bid.hasOwnProperty('w') ? bid.w : req.video.w;
newBid.height = bid.hasOwnProperty('h') ? bid.h : req.video.h;
newBid.vastXml = bid.adm;
+ _assignRenderer(newBid, request);
break;
case NATIVE:
_parseNativeResponse(bid, newBid);
diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md
index a045bed3e2b..cd9398477f4 100644
--- a/modules/pubmaticBidAdapter.md
+++ b/modules/pubmaticBidAdapter.md
@@ -25,6 +25,7 @@ var adUnits = [
bidder: 'pubmatic',
params: {
publisherId: '156209', // required
+ oustreamAU: 'renderer_test_pubmatic', // required if mediaTypes-> video-> context is 'outstream'. This value can be get by BlueBillyWig Team.
adSlot: 'pubmatic_test2', // optional
pmzoneid: 'zone1, zone11', // optional
lat: '40.712775', // optional
diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js
index 91022d70df9..894bb991a71 100644
--- a/modules/quantcastBidAdapter.js
+++ b/modules/quantcastBidAdapter.js
@@ -85,11 +85,6 @@ function checkTCFv1(vendorData) {
}
function checkTCFv2(tcData) {
- if (tcData.purposeOneTreatment && tcData.publisherCC === 'DE') {
- // special purpose 1 treatment for Germany
- return true;
- }
-
let restrictions = tcData.publisher ? tcData.publisher.restrictions : {};
let qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT]
? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID]
diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js
index 3aa7753d204..9acd484cec8 100644
--- a/modules/rtdModule/index.js
+++ b/modules/rtdModule/index.js
@@ -23,14 +23,56 @@
*/
/**
- * @interface ModuleConfig
+ * @property
+ * @summary used to link submodule with config
+ * @name RtdSubmodule#config
+ * @type {Object}
*/
/**
- * @property
- * @summary sub module name
- * @name ModuleConfig#name
- * @type {string}
+ * @function
+ * @summary init sub module
+ * @name RtdSubmodule#init
+ * @param {Object} config
+ * @param {Object} gdpr settings
+ * @param {Object} usp settings
+ * @return {boolean} false to remove sub module
+ */
+
+/**
+ * @function?
+ * @summary on auction init event
+ * @name RtdSubmodule#auctionInit
+ * @param {Object} data
+ * @param {SubmoduleConfig} config
+ */
+
+/**
+ * @function?
+ * @summary on auction end event
+ * @name RtdSubmodule#auctionEnd
+ * @param {Object} data
+ * @param {SubmoduleConfig} config
+ */
+
+/**
+ * @function?
+ * @summary on bid request event
+ * @name RtdSubmodule#updateBidRequest
+ * @param {Object} data
+ * @param {SubmoduleConfig} config
+ */
+
+/**
+ * @function?
+ * @summary on bid response event
+ * @name RtdSubmodule#updateBidResponse
+ * @param {Object} data
+ * @param {SubmoduleConfig} config
+ */
+
+/**
+ * @interface ModuleConfig
*/
/**
@@ -40,18 +82,43 @@
* @type {number}
*/
+/**
+ * @property
+ * @summary timeout (if no auction dealy)
+ * @name ModuleConfig#timeout
+ * @type {number}
+ */
+
+/**
+ * @property
+ * @summary list of sub modules
+ * @name ModuleConfig#dataProviders
+ * @type {SubmoduleConfig[]}
+ */
+
+/**
+ * @interface SubModuleConfig
+ */
+
/**
* @property
* @summary params for provide (sub module)
- * @name ModuleConfig#params
+ * @name SubModuleConfig#params
* @type {Object}
*/
/**
* @property
- * @summary timeout (if no auction dealy)
- * @name ModuleConfig#timeout
- * @type {number}
+ * @summary name
+ * @name ModuleConfig#name
+ * @type {string}
+ */
+
+/**
+ * @property
+ * @summary delay auction for this sub module
+ * @name ModuleConfig#waitForIt
+ * @type {boolean}
*/
import {getGlobal} from '../../src/prebidGlobal.js';
@@ -59,15 +126,21 @@ import {config} from '../../src/config.js';
import {targeting} from '../../src/targeting.js';
import {getHook, module} from '../../src/hook.js';
import * as utils from '../../src/utils.js';
+import events from '../../src/events.js';
+import CONSTANTS from '../../src/constants.json';
+import {gdprDataHandler, uspDataHandler} from '../../src/adapterManager.js';
+import find from 'core-js-pure/features/array/find.js';
/** @type {string} */
const MODULE_NAME = 'realTimeData';
/** @type {number} */
const DEF_TIMEOUT = 1000;
/** @type {RtdSubmodule[]} */
-let subModules = [];
+export let subModules = [];
/** @type {ModuleConfig} */
let _moduleConfig;
+/** @type {SubmoduleConfig[]} */
+let _dataProviders = [];
/**
* enable submodule in User ID
@@ -85,6 +158,9 @@ export function init(config) {
}
confListener(); // unsubscribe config listener
_moduleConfig = realTimeData;
+ _dataProviders = realTimeData.dataProviders;
+ getHook('makeBidRequests').before(initSubModules);
+ setEventsListeners();
if (typeof (_moduleConfig.auctionDelay) === 'undefined') {
_moduleConfig.auctionDelay = 0;
}
@@ -97,61 +173,82 @@ export function init(config) {
});
}
+/**
+ * call each sub module init function by config order
+ * if no init function / init return failure / module not configured - remove it from submodules list
+ */
+export function initSubModules(next, adUnits, auctionStart, auctionId, cbTimeout, labels) {
+ let subModulesByOrder = [];
+ _dataProviders.forEach(provider => {
+ const sm = find(subModules, s => s.name === provider.name);
+ const initResponse = sm && sm.init && sm.init(provider, gdprDataHandler.getConsentData(), uspDataHandler.getConsentData());
+ if (initResponse) {
+ subModulesByOrder.push(Object.assign(sm, {config: provider}));
+ }
+ });
+ subModules = subModulesByOrder;
+ next(adUnits, auctionStart, auctionId, cbTimeout, labels)
+}
+
+/**
+ * call each sub module event function by config order
+ */
+function setEventsListeners() {
+ events.on(CONSTANTS.EVENTS.AUCTION_INIT, (args) => {
+ subModules.forEach(sm => { sm.auctionInit && sm.auctionInit(args, sm.config) })
+ });
+ events.on(CONSTANTS.EVENTS.AUCTION_END, (args) => {
+ subModules.forEach(sm => { sm.auctionEnd && sm.auctionEnd(args, sm.config) })
+ });
+ events.on(CONSTANTS.EVENTS.BEFORE_REQUEST_BIDS, (args) => {
+ subModules.forEach(sm => { sm.updateBidRequest && sm.updateBidRequest(args, sm.config) })
+ });
+ events.on(CONSTANTS.EVENTS.BID_RESPONSE, (args) => {
+ subModules.forEach(sm => { sm.updateBidResponse && sm.updateBidResponse(args, sm.config) })
+ });
+}
+
/**
* get data from sub module
* @param {AdUnit[]} adUnits received from auction
* @param {function} callback callback function on data received
*/
-function getProviderData(adUnits, callback) {
- const callbackExpected = subModules.length;
- let dataReceived = [];
- let processDone = false;
- const dataWaitTimeout = setTimeout(() => {
- processDone = true;
- callback(dataReceived);
- }, _moduleConfig.auctionDelay || _moduleConfig.timeout || DEF_TIMEOUT);
+export function getProviderData(adUnits, callback) {
+ /**
+ * invoke callback if one of the conditions met:
+ * timeout reached
+ * all submodules answered
+ * all sub modules configured "waitForIt:true" answered (as long as there is at least one configured)
+ */
+ const waitForSubModulesLength = subModules.filter(sm => sm.config && sm.config.waitForIt).length;
+ let callbacksExpected = waitForSubModulesLength || subModules.length;
+ const shouldWaitForAllSubModules = waitForSubModulesLength === 0;
+ let dataReceived = {};
+ let processDone = false;
+ const dataWaitTimeout = setTimeout(done, _moduleConfig.auctionDelay || _moduleConfig.timeout || DEF_TIMEOUT);
subModules.forEach(sm => {
- sm.getData(adUnits, onDataReceived);
+ sm.getData(adUnits, onDataReceived.bind(sm));
});
function onDataReceived(data) {
if (processDone) {
return
}
- dataReceived.push(data);
- if (dataReceived.length === callbackExpected) {
- processDone = true;
+ dataReceived[this.name] = data;
+ if (shouldWaitForAllSubModules || (this.config && this.config.waitForIt)) {
+ callbacksExpected--
+ }
+ if (callbacksExpected <= 0) {
clearTimeout(dataWaitTimeout);
- callback(dataReceived);
+ done();
}
}
-}
-/**
- * delete invalid data received from provider
- * this is to ensure working flow for GPT
- * @param {Object} data received from provider
- * @return {Object} valid data for GPT targeting
- */
-export function validateProviderDataForGPT(data) {
- // data must be an object, contains object with string as value
- if (typeof data !== 'object') {
- return {};
- }
- for (let key in data) {
- if (data.hasOwnProperty(key)) {
- for (let innerKey in data[key]) {
- if (data[key].hasOwnProperty(innerKey)) {
- if (typeof data[key][innerKey] !== 'string') {
- utils.logWarn(`removing ${key}: {${innerKey}:${data[key][innerKey]} } from GPT targeting because of invalid type (must be string)`);
- delete data[key][innerKey];
- }
- }
- }
- }
+ function done() {
+ processDone = true;
+ callback(dataReceived);
}
- return data;
}
/**
@@ -163,7 +260,7 @@ export function validateProviderDataForGPT(data) {
export function setTargetsAfterRequestBids(next, adUnits) {
getProviderData(adUnits, (data) => {
if (data && Object.keys(data).length) {
- const _mergedData = deepMerge(data);
+ const _mergedData = deepMerge(setDataOrderByProvider(subModules, data));
if (Object.keys(_mergedData).length) {
setDataForPrimaryAdServer(_mergedData);
}
@@ -172,6 +269,22 @@ export function setTargetsAfterRequestBids(next, adUnits) {
});
}
+/**
+ * return an array providers data in reverse order,so the data merge will be according to correct config order
+ * @param {Submodule[]} modules
+ * @param {Object} data - data retrieved from providers
+ * @return {array} reversed order ready for merge
+ */
+function setDataOrderByProvider(modules, data) {
+ let rd = [];
+ for (let i = modules.length; i--; i > 0) {
+ if (data[modules[i].name]) {
+ rd.push(data[modules[i].name])
+ }
+ }
+ return rd;
+}
+
/**
* deep merge array of objects
* @param {array} arr - objects array
@@ -207,7 +320,7 @@ export function deepMerge(arr) {
export function requestBidsHook(fn, reqBidsConfigObj) {
getProviderData(reqBidsConfigObj.adUnits || getGlobal().adUnits, (data) => {
if (data && Object.keys(data).length) {
- const _mergedData = deepMerge(data);
+ const _mergedData = deepMerge(setDataOrderByProvider(subModules, data));
if (Object.keys(_mergedData).length) {
setDataForPrimaryAdServer(_mergedData);
addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, _mergedData);
@@ -222,7 +335,6 @@ export function requestBidsHook(fn, reqBidsConfigObj) {
* @param {Object} data - key values to set
*/
function setDataForPrimaryAdServer(data) {
- data = validateProviderDataForGPT(data);
if (utils.isGptPubadsDefined()) {
targeting.setTargetingForGPT(data, null)
} else {
@@ -247,5 +359,5 @@ function addIdDataToAdUnitBids(adUnits, data) {
});
}
-init(config);
module('realTimeData', attachRealTimeDataProvider);
+init(config);
diff --git a/modules/rubiconAnalyticsAdapter.js b/modules/rubiconAnalyticsAdapter.js
index 7a1bdce84d5..0bee4d926fc 100644
--- a/modules/rubiconAnalyticsAdapter.js
+++ b/modules/rubiconAnalyticsAdapter.js
@@ -40,8 +40,6 @@ const cache = {
timeouts: {},
};
-const validFloorProviders = ['rubicon', 'rubi', 'magnite', 'mgni'];
-
export function getHostNameFromReferer(referer) {
try {
rubiconAdapter.referrerHostname = utils.parseUrl(referer, {noDecodeWholeURL: true}).hostname;
@@ -200,17 +198,19 @@ function sendMessage(auctionId, bidWonId) {
if (auctionCache.floorData.location === 'noData') {
auction.floors = utils.pick(auctionCache.floorData, [
'location',
- 'fetchStatus'
+ 'fetchStatus',
+ 'floorProvider as provider'
]);
} else {
auction.floors = utils.pick(auctionCache.floorData, [
'location',
- 'modelName', () => auctionCache.floorData.modelVersion,
+ 'modelVersion as modelName',
'skipped',
'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'),
'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'),
'skipRate',
- 'fetchStatus'
+ 'fetchStatus',
+ 'floorProvider as provider'
]);
}
}
@@ -285,7 +285,7 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) {
if (previousBidResponse && previousBidResponse.bidPriceUSD > responsePrice) {
return previousBidResponse;
}
- let bidResponse = utils.pick(bid, [
+ return utils.pick(bid, [
'bidPriceUSD', () => responsePrice,
'dealId',
'status',
@@ -295,12 +295,9 @@ export function parseBidResponse(bid, previousBidResponse, auctionFloorData) {
'height'
]),
'seatBidId',
+ 'floorValue', () => utils.deepAccess(bid, 'floorData.floorValue'),
+ 'floorRule', () => utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined
]);
- if (auctionFloorData) {
- bidResponse.floorValue = utils.deepAccess(bid, 'floorData.floorValue');
- bidResponse.floorRule = utils.debugTurnedOn() ? utils.deepAccess(bid, 'floorData.floorRule') : undefined
- }
- return bidResponse;
}
let samplingFactor = 1;
@@ -380,9 +377,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
cacheEntry.bids = {};
cacheEntry.bidsWon = {};
cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer;
- const floorProvider = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData.floorProvider');
- if (validFloorProviders.indexOf(floorProvider) !== -1) {
- cacheEntry.floorData = {...args.bidderRequests[0].bids[0].floorData};
+ const floorData = utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData');
+ if (floorData) {
+ cacheEntry.floorData = {...floorData};
}
cache.auctions[args.auctionId] = cacheEntry;
break;
@@ -468,7 +465,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
bid.adUnit.adSlot = args.floorData.matchedFields.gptSlot;
}
// if we have not set enforcements yet set it
- if (auctionEntry.floorData && !auctionEntry.floorData.enforcements && utils.deepAccess(args, 'floorData.enforcements')) {
+ if (!utils.deepAccess(auctionEntry, 'floorData.enforcements') && utils.deepAccess(args, 'floorData.enforcements')) {
auctionEntry.floorData.enforcements = {...args.floorData.enforcements};
}
if (!bid) {
@@ -492,7 +489,7 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
};
}
bid.clientLatencyMillis = Date.now() - cache.auctions[args.auctionId].timestamp;
- bid.bidResponse = parseBidResponse(args, bid.bidResponse, auctionEntry.floorData);
+ bid.bidResponse = parseBidResponse(args, bid.bidResponse);
break;
case BIDDER_DONE:
args.bids.forEach(bid => {
diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index 0c563987331..4a0dd10281b 100644
--- a/modules/rubiconBidAdapter.js
+++ b/modules/rubiconBidAdapter.js
@@ -2,6 +2,7 @@ import * as utils from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {config} from '../src/config.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import find from 'core-js-pure/features/array/find.js';
const DEFAULT_INTEGRATION = 'pbjs_lite';
const DEFAULT_PBS_INTEGRATION = 'pbjs';
@@ -203,11 +204,16 @@ export const spec = {
let bidFloor;
if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) {
- let floorInfo = bidRequest.getFloor({
- currency: 'USD',
- mediaType: 'video',
- size: parseSizes(bidRequest, 'video')
- });
+ let floorInfo;
+ try {
+ floorInfo = bidRequest.getFloor({
+ currency: 'USD',
+ mediaType: 'video',
+ size: parseSizes(bidRequest, 'video')
+ });
+ } catch (e) {
+ utils.logError('Rubicon: getFloor threw an error: ', e);
+ }
bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined;
} else {
bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor'));
@@ -242,59 +248,25 @@ export const spec = {
utils.deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent);
}
- if (bidRequest.userId && typeof bidRequest.userId === 'object' &&
- (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb || bidRequest.userId.idl_env)) {
- utils.deepSetValue(data, 'user.ext.eids', []);
-
- if (bidRequest.userId.tdid) {
- data.user.ext.eids.push({
- source: 'adserver.org',
- uids: [{
- id: bidRequest.userId.tdid,
- ext: {
- rtiPartner: 'TDID'
- }
- }]
- });
- }
-
- if (bidRequest.userId.pubcid) {
- data.user.ext.eids.push({
- source: 'pubcommon',
- uids: [{
- id: bidRequest.userId.pubcid,
- }]
- });
- }
-
- // support liveintent ID
- if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) {
- data.user.ext.eids.push({
- source: 'liveintent.com',
- uids: [{
- id: bidRequest.userId.lipb.lipbid
- }]
- });
-
- data.user.ext.tpid = {
- source: 'liveintent.com',
- uid: bidRequest.userId.lipb.lipbid
- };
-
- if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) {
- utils.deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments);
+ const eids = utils.deepAccess(bidderRequest, 'bids.0.userIdAsEids');
+ if (eids && eids.length) {
+ // filter out unsupported id systems
+ utils.deepSetValue(data, 'user.ext.eids', eids.filter(eid => ['adserver.org', 'pubcid.org', 'liveintent.com', 'liveramp.com', 'sharedid.org'].indexOf(eid.source) !== -1));
+
+ // liveintent requires additional props to be set
+ const liveIntentEid = find(data.user.ext.eids, eid => eid.source === 'liveintent.com');
+ if (liveIntentEid) {
+ utils.deepSetValue(data, 'user.ext.tpid', { source: liveIntentEid.source, uid: liveIntentEid.uids[0].id });
+ if (liveIntentEid.ext && liveIntentEid.ext.segments) {
+ utils.deepSetValue(data, 'rp.target.LIseg', liveIntentEid.ext.segments);
}
}
+ }
- // support identityLink (aka LiveRamp)
- if (bidRequest.userId.idl_env) {
- data.user.ext.eids.push({
- source: 'liveramp.com',
- uids: [{
- id: bidRequest.userId.idl_env
- }]
- });
- }
+ // set user.id value from config value
+ const configUserId = config.getConfig('user.id');
+ if (configUserId) {
+ utils.deepSetValue(data, 'user.id', configUserId);
}
if (config.getConfig('coppa') === true) {
@@ -527,11 +499,16 @@ export const spec = {
// If floors module is enabled and we get USD floor back, send it in rp_hard_floor else undfined
if (typeof bidRequest.getFloor === 'function' && !config.getConfig('rubicon.disableFloors')) {
- let floorInfo = bidRequest.getFloor({
- currency: 'USD',
- mediaType: 'banner',
- size: '*'
- });
+ let floorInfo;
+ try {
+ floorInfo = bidRequest.getFloor({
+ currency: 'USD',
+ mediaType: 'banner',
+ size: '*'
+ });
+ } catch (e) {
+ utils.logError('Rubicon: getFloor threw an error: ', e);
+ }
data['rp_hard_floor'] = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined;
}
@@ -554,10 +531,21 @@ export const spec = {
// support identityLink (aka LiveRamp)
if (bidRequest.userId.idl_env) {
- data['tpid_liveramp.com'] = bidRequest.userId.idl_env;
+ data['x_liverampidl'] = bidRequest.userId.idl_env;
+ }
+
+ // support shared id
+ if (bidRequest.userId.sharedid) {
+ data['eid_sharedid.org'] = `${bidRequest.userId.sharedid.id}^3^${bidRequest.userId.sharedid.third}`;
}
}
+ // set ppuid value from config value
+ const configUserId = config.getConfig('user.id');
+ if (configUserId) {
+ data['ppuid'] = configUserId;
+ }
+
if (bidderRequest.gdprConsent) {
// add 'gdpr' only if 'gdprApplies' is defined
if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
@@ -1166,7 +1154,7 @@ export function hasValidSupplyChainParams(schain) {
if (!schain.nodes) return isValid;
isValid = schain.nodes.reduce((status, node) => {
if (!status) return status;
- return requiredFields.every(field => node[field]);
+ return requiredFields.every(field => node.hasOwnProperty(field));
}, true);
if (!isValid) utils.logError('Rubicon: required schain params missing');
return isValid;
diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js
new file mode 100644
index 00000000000..a745c54e39c
--- /dev/null
+++ b/modules/smartxBidAdapter.js
@@ -0,0 +1,400 @@
+import * as utils from '../src/utils.js';
+import {
+ Renderer
+} from '../src/Renderer.js';
+import {
+ registerBidder
+} from '../src/adapters/bidderFactory.js';
+import {
+ VIDEO
+} from '../src/mediaTypes.js';
+const BIDDER_CODE = 'smartx';
+const URL = 'https://bid.sxp.smartclip.net/bid/1000';
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [VIDEO],
+ /**
+ * Determines whether or not the given bid request is valid.
+ * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid).
+ *
+ * @param {object} bid The bid to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid: function (bid) {
+ if (bid && typeof bid.params !== 'object') {
+ utils.logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.');
+ return false;
+ }
+ if (!utils.deepAccess(bid, 'mediaTypes.video')) {
+ utils.logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.');
+ return false;
+ }
+ const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize');
+ if (!playerSize || !utils.isArray(playerSize)) {
+ utils.logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.');
+ return false;
+ }
+ if (!utils.getBidIdParameter('tagId', bid.params)) {
+ utils.logError(BIDDER_CODE + ': tagId is not present in bidder params');
+ return false;
+ }
+ if (!utils.getBidIdParameter('publisherId', bid.params)) {
+ utils.logError(BIDDER_CODE + ': publisherId is not present in bidder params');
+ return false;
+ }
+ if (!utils.getBidIdParameter('siteId', bid.params)) {
+ utils.logError(BIDDER_CODE + ': siteId is not present in bidder params');
+ return false;
+ }
+ if (!utils.getBidIdParameter('bidfloor', bid.params)) {
+ utils.logError(BIDDER_CODE + ': bidfloor is not present in bidder params');
+ return false;
+ }
+ if (!utils.getBidIdParameter('bidfloorcur', bid.params)) {
+ utils.logError(BIDDER_CODE + ': bidfloorcur is not present in bidder params');
+ return false;
+ }
+ if (utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') {
+ if (!utils.getBidIdParameter('outstream_options', bid.params)) {
+ utils.logError(BIDDER_CODE + ': outstream_options parameter is not defined');
+ return false;
+ }
+ if (!utils.getBidIdParameter('slot', bid.params.outstream_options)) {
+ utils.logError(BIDDER_CODE + ': slot parameter is not defined in outstream_options object in the configuration');
+ return false;
+ }
+ if (!utils.getBidIdParameter('outstream_function', bid.params)) {
+ utils.logMessage(BIDDER_CODE + ': outstream_function parameter is not defined. The default outstream renderer will be injected in the header. You can override the default SmartX outstream rendering by defining your own Outstream function using field outstream_function.');
+ return true;
+ }
+ }
+
+ return true;
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test.
+ *
+ * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server.
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (bidRequests, bidderRequest) {
+ const page = bidderRequest.refererInfo.referer;
+ const isPageSecure = !!page.match(/^https:/)
+
+ const smartxRequests = bidRequests.map(function (bid) {
+ const tagId = utils.getBidIdParameter('tagId', bid.params);
+ const publisherId = utils.getBidIdParameter('publisherId', bid.params);
+ const bidfloor = utils.getBidIdParameter('bidfloor', bid.params);
+ const bidfloorcur = utils.getBidIdParameter('bidfloorcur', bid.params);
+ const siteId = utils.getBidIdParameter('siteId', bid.params);
+ const domain = utils.getBidIdParameter('domain', bid.params);
+ const cat = utils.getBidIdParameter('cat', bid.params);
+ let pubcid = null;
+ const playerSize = utils.deepAccess(bid, 'mediaTypes.video.playerSize');
+ const contentWidth = playerSize[0][0];
+ const contentHeight = playerSize[0][1];
+ const secure = +(isPageSecure || (utils.getBidIdParameter('secure', bid.params) ? 1 : 0));
+ const ext = {
+ sdk_name: 'Prebid 1+'
+ };
+ const mimes = utils.getBidIdParameter('mimes', bid.params) || ['application/javascript', 'video/mp4', 'video/webm'];
+ const linearity = utils.getBidIdParameter('linearity', bid.params) || 1;
+ const minduration = utils.getBidIdParameter('minduration', bid.params) || 0;
+ const maxduration = utils.getBidIdParameter('maxduration', bid.params) || 500;
+ const startdelay = utils.getBidIdParameter('startdelay', bid.params) || 0;
+ const minbitrate = utils.getBidIdParameter('minbitrate', bid.params) || 0;
+ const maxbitrate = utils.getBidIdParameter('maxbitrate', bid.params) || 3500;
+ const delivery = utils.getBidIdParameter('delivery', bid.params) || [2];
+ const pos = utils.getBidIdParameter('pos', bid.params) || 1;
+ const api = utils.getBidIdParameter('api', bid.params) || [2];
+ const protocols = utils.getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6];
+ var contextcustom = utils.deepAccess(bid, 'mediaTypes.video.context');
+ var placement = 1;
+
+ if (contextcustom === 'outstream') {
+ placement = 3;
+ }
+
+ let smartxReq = {
+ id: bid.bidId,
+ secure: secure,
+ bidfloor: bidfloor,
+ bidfloorcur: bidfloorcur,
+ video: {
+ w: contentWidth,
+ h: contentHeight,
+ mimes: mimes,
+ linearity: linearity,
+ minduration: minduration,
+ maxduration: maxduration,
+ startdelay: startdelay,
+ protocols: protocols,
+ minbitrate: minbitrate,
+ maxbitrate: maxbitrate,
+ delivery: delivery,
+ pos: pos,
+ placement: placement,
+ api: api,
+ ext: ext
+ },
+ tagid: tagId,
+ ext: {
+ 'smart.bidpricetype': 1
+ }
+ };
+
+ if (bid.crumbs && bid.crumbs.pubcid) {
+ pubcid = bid.crumbs.pubcid;
+ }
+
+ const language = navigator.language ? 'language' : 'userLanguage';
+ const device = {
+ h: screen.height,
+ w: screen.width,
+ dnt: utils.getDNT() ? 1 : 0,
+ language: navigator[language].split('-')[0],
+ make: navigator.vendor ? navigator.vendor : '',
+ ua: navigator.userAgent
+ };
+ const at = utils.getBidIdParameter('at', bid.params) || 2;
+ const cur = utils.getBidIdParameter('cur', bid.params) || ['EUR'];
+ const requestPayload = {
+ id: utils.generateUUID(),
+ imp: smartxReq,
+ site: {
+ id: siteId,
+ page: page,
+ cat: cat,
+ content: 'content',
+ domain: domain,
+ publisher: {
+ id: publisherId
+ }
+ },
+ device: device,
+ at: at,
+ cur: cur
+ };
+ const userExt = {};
+
+ // Add GDPR flag and consent string
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ userExt.consent = bidderRequest.gdprConsent.consentString;
+ if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') {
+ requestPayload.regs = {
+ ext: {
+ gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)
+ }
+ };
+ }
+ }
+
+ // Add common id if available
+ if (pubcid) {
+ userExt.fpc = pubcid;
+ }
+
+ // Only add the user object if it's not empty
+ if (!utils.isEmpty(userExt)) {
+ requestPayload.user = {
+ ext: userExt
+ };
+ }
+
+ // Targeting
+ if (utils.getBidIdParameter('data', bid.params.user)) {
+ var targetingarr = [];
+ for (var i = 0; i < bid.params.user.data.length; i++) {
+ var isemq = (bid.params.user.data[i].name) || 'empty';
+ if (isemq !== 'empty') {
+ var provider = bid.params.user.data[i].name;
+ var targetingstring = (bid.params.user.data[i].segment[0].value) || 'empty';
+ targetingarr.push({
+ id: provider,
+ name: provider,
+ segment: {
+ name: provider,
+ value: targetingstring,
+ }
+ })
+ }
+ }
+
+ requestPayload.user = {
+ ext: userExt,
+ data: targetingarr
+ }
+ }
+
+ return {
+ method: 'POST',
+ url: URL,
+ data: requestPayload,
+ bidRequest: bidderRequest,
+ options: {
+ contentType: 'application/json',
+ customHeaders: {
+ 'x-openrtb-version': '2.3'
+ }
+ }
+ };
+ });
+
+ return smartxRequests;
+ },
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {*} serverResponse A successful response from the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function (serverResponse, bidderRequest) {
+ const bidResponses = [];
+ const serverResponseBody = serverResponse.body;
+ if (serverResponseBody && utils.isArray(serverResponseBody.seatbid)) {
+ utils._each(serverResponseBody.seatbid, function (bids) {
+ utils._each(bids.bid, function (smartxBid) {
+ let currentBidRequest = {};
+ for (let i in bidderRequest.bidRequest.bids) {
+ if (smartxBid.impid == bidderRequest.bidRequest.bids[i].bidId) {
+ currentBidRequest = bidderRequest.bidRequest.bids[i];
+ }
+ }
+ /**
+ * Make sure currency and price are the right ones
+ * TODO: what about the pre_market_bid partners sizes?
+ */
+ utils._each(currentBidRequest.params.pre_market_bids, function (pmb) {
+ if (pmb.deal_id == smartxBid.id) {
+ smartxBid.price = pmb.price;
+ serverResponseBody.cur = pmb.currency;
+ }
+ });
+ const bid = {
+ requestId: currentBidRequest.bidId,
+ currency: serverResponseBody.cur || 'USD',
+ cpm: smartxBid.price,
+ creativeId: smartxBid.crid || '',
+ ttl: 360,
+ netRevenue: true,
+ vastContent: smartxBid.adm,
+ vastXml: smartxBid.adm,
+ mediaType: VIDEO,
+ width: smartxBid.w,
+ height: smartxBid.h
+ };
+ const context = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context');
+ if (context === 'outstream') {
+ const playersize = utils.deepAccess(currentBidRequest, 'mediaTypes.video.playerSize');
+ const renderer = Renderer.install({
+ id: 0,
+ url: '//',
+ config: {
+ adText: 'SmartX Outstream Video Ad via Prebid.js',
+ player_width: playersize[0][0],
+ player_height: playersize[0][1],
+ content_page_url: utils.deepAccess(bidderRequest, 'data.site.page'),
+ ad_mute: +!!utils.deepAccess(currentBidRequest, 'params.ad_mute'),
+ hide_skin: +!!utils.deepAccess(currentBidRequest, 'params.hide_skin'),
+ outstream_options: utils.deepAccess(currentBidRequest, 'params.outstream_options'),
+ outstream_function: utils.deepAccess(currentBidRequest, 'params.outstream_function')
+ }
+ });
+ try {
+ renderer.setRender(outstreamRender);
+ renderer.setEventHandlers({
+ impression: function impression() {
+ return utils.logMessage('SmartX outstream video impression event');
+ },
+ loaded: function loaded() {
+ return utils.logMessage('SmartX outstream video loaded event');
+ },
+ ended: function ended() {
+ utils.logMessage('SmartX outstream renderer video event');
+ }
+ });
+ } catch (err) {
+ utils.logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err);
+ }
+ bid.renderer = renderer;
+ }
+ bidResponses.push(bid);
+ })
+ });
+ }
+ return bidResponses;
+ }
+}
+
+function createOutstreamScript(bid) {
+ // const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options);
+ utils.logMessage('[SMARTX][renderer] Handle SmartX outstream renderer');
+ const elementId = bid.adUnitCode;
+ // eslint-disable-next-line camelcase
+ var sc_smartIntxtStart;
+ // eslint-disable-next-line camelcase
+ var sc_smartIntxtNoad;
+ // eslint-disable-next-line camelcase
+ var sc_smartIntxtEnd;
+ var SmartPlay;
+ let smartPlayObj = {
+ minAdWidth: 290,
+ maxAdWidth: 900,
+ elementLocator: {
+ allowInViewport: false,
+ minimumElementWidth: 290,
+ scanPixelsBelowViewport: 800
+ },
+ onStartCallback: function (m, n) {
+ try {
+ sc_smartIntxtStart(n);
+ } catch (f) {}
+ },
+ onCappedCallback: function (m, n) {
+ try {
+ sc_smartIntxtNoad(n);
+ } catch (f) {}
+ },
+ onEndCallback: function (m, n) {
+ try {
+ sc_smartIntxtEnd(n);
+ } catch (f) {}
+ },
+ debug: true
+ };
+ smartPlayObj.adResponse = bid.vastContent;
+ const script = window.document.createElement('script');
+ script.type = 'text/javascript';
+ script.async = 'true';
+ script.src = 'https://dco.smartclip.net/?plc=7777777';
+ script.onload = script.onreadystatechange = function () {
+ var rs = this.readyState;
+ if (rs && rs != 'complete' && rs != 'loaded') return;
+ try {
+ SmartPlay(elementId, smartPlayObj);
+ } catch (e) {
+ utils.logError('error caught : ' + e);
+ }
+ };
+ return script;
+}
+
+function outstreamRender(bid) {
+ const script = createOutstreamScript(bid);
+ if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') {
+ bid.renderer.config.outstream_function(bid, script);
+ } else {
+ try {
+ const slot = utils.getBidIdParameter('slot', bid.renderer.config.outstream_options);
+ if (slot && window.document.getElementById(slot)) {
+ window.document.getElementById(slot).appendChild(script);
+ } else {
+ window.document.getElementsByTagName('head')[0].appendChild(script);
+ }
+ } catch (err) {
+ utils.logError('[SMARTX][renderer] Error:' + err.message)
+ }
+ }
+}
+registerBidder(spec);
diff --git a/modules/smartxBidAdapter.md b/modules/smartxBidAdapter.md
new file mode 100644
index 00000000000..a53af839e2b
--- /dev/null
+++ b/modules/smartxBidAdapter.md
@@ -0,0 +1,159 @@
+# Overview
+
+```
+Module Name: smartclip Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: adtech@smartclip.tv
+```
+
+# Description
+
+Connect to smartx for bids.
+
+This adapter requires setup and approval from the smartclip team.
+
+# Test Parameters - Use case #1 - Out-Stream example and default rendering options
+```
+ var adUnits = [{
+ code: 'video1',
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 360]
+ }
+ },
+ bids: [{
+ bidder: 'smartx',
+ params: {
+ tagId: 'Nu68JuOWAvrbzoyrOR9a7A',
+ publisherId: '11986',
+ siteId: '22860',
+ bidfloor: 0.3,
+ bidfloorcur: "EUR",
+ at: 2,
+ cur: ["EUR"],
+ outstream_options: {
+ slot: 'video1'
+ },
+ }
+ }],
+ }];
+```
+
+# Test Parameters - Use case #2 - Out-Stream with targeting example and default rendering options
+```
+ var adUnits = [{
+ code: 'video1',
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [640, 360]
+ }
+ },
+ bids: [{
+ bidder: 'smartx',
+ params: {
+ tagId: 'Nu68JuOWAvrbzoyrOR9a7A',
+ publisherId: '11986',
+ siteId: '22860',
+ bidfloor: 0.3,
+ bidfloorcur: "EUR",
+ at: 2,
+ cur: ["EUR"],
+ outstream_options: {
+ slot: 'video1'
+ },
+ user: {
+ data: [{
+ id: 'emq',
+ name: 'emq',
+ segment: [{
+ id: 'emq',
+ name: 'emq',
+ value: 'e0:k14:e24'
+ }]
+ }, {
+ id: 'gs',
+ name: 'gs',
+ segment: [{
+ id: 'gs',
+ name: 'gs',
+ value: 'tone_of_voice_dislike:tone_of_voice_negative:gs_health'
+ }]
+ }]
+ }
+ }
+ }]
+ }];
+```
+
+# Test Parameters - Use case #3 - In-Stream example and default rendering options
+```
+ var adUnits = [{
+ code: 'video1',
+ mediaTypes: {
+ video: {
+ context: 'instream',
+ playerSize: [640, 360]
+ }
+ },
+ bids: [{
+ bidder: 'smartx',
+ params: {
+ tagId: 'Nu68JuOWAvrbzoyrOR9a7A',
+ publisherId: '11986',
+ siteId: '22860',
+ bidfloor: 0.3,
+ bidfloorcur: "EUR",
+ at: 2,
+ cur: ["EUR"]
+ }
+ }],
+ }];
+```
+
+# Test Parameters - Use case #4 - In-Stream with targeting example and default rendering options
+```
+ var adUnits = [{
+ code: 'video1',
+ mediaTypes: {
+ video: {
+ context: 'instream',
+ playerSize: [640, 360]
+ }
+ },
+ bids: [{
+ bidder: 'smartx',
+ params: {
+ tagId: 'Nu68JuOWAvrbzoyrOR9a7A',
+ publisherId: '11986',
+ siteId: '22860',
+ bidfloor: 0.3,
+ bidfloorcur: "EUR",
+ at: 2,
+ cur: ["EUR"],
+ user: {
+ data: [{
+ id: 'emq',
+ name: 'emq',
+ segment: [{
+ id: 'emq',
+ name: 'emq',
+ value: 'e0:k14:e24'
+ }]
+ },
+ {
+ id: 'gs',
+ name: 'gs',
+ segment: [{
+ id: 'gs',
+ name: 'gs',
+ value: 'tone_of_voice_dislike:tone_of_voice_negative:gs_health'
+ }]
+ }
+ ]
+ }
+ }
+ }],
+ }];
+```
\ No newline at end of file
diff --git a/modules/sspBCAdapter.js b/modules/sspBCAdapter.js
new file mode 100644
index 00000000000..ef89fb08449
--- /dev/null
+++ b/modules/sspBCAdapter.js
@@ -0,0 +1,319 @@
+import * as utils from '../src/utils.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { BANNER } from '../src/mediaTypes.js';
+
+const BIDDER_CODE = 'sspBC';
+const BIDDER_URL = 'https://ssp.wp.pl/bidder/';
+const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync';
+const TMAX = 450;
+const BIDDER_VERSION = '4.5';
+const W = window;
+const { navigator } = W;
+
+const cookieSupport = () => {
+ const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent);
+ const useCookies = navigator.cookieEnabled || !!document.cookie.length;
+
+ return !isSafari && useCookies;
+};
+
+const applyClientHints = ortbRequest => {
+ const connection = navigator.connection || false;
+ const viewport = W.visualViewport || false;
+ const segments = [];
+ const hints = {
+ 'CH-Ect': connection.effectiveType,
+ 'CH-Rtt': connection.rtt,
+ 'CH-SaveData': connection.saveData,
+ 'CH-Downlink': connection.downlink,
+ 'CH-DeviceMemory': navigator.deviceMemory,
+ 'CH-Dpr': W.devicePixelRatio,
+ 'CH-ViewportWidth': viewport.width,
+ };
+
+ Object.keys(hints).forEach(key => {
+ const hint = hints[key];
+
+ if (hint) {
+ segments.push({
+ name: key,
+ value: hint.toString(),
+ });
+ }
+ });
+ const data = [
+ {
+ id: '12',
+ name: 'NetInfo',
+ segment: segments,
+ }];
+
+ ortbRequest.user = Object.assign(ortbRequest.user, { data });
+};
+
+function applyGdpr(bidderRequest, ortbRequest) {
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ ortbRequest.regs = Object.assign(ortbRequest.regs, { '[ortb_extensions.gdpr]': bidderRequest.gdprConsent.gdprApplies ? 1 : 0 });
+ ortbRequest.user = Object.assign(ortbRequest.user, { '[ortb_extensions.consent]': bidderRequest.gdprConsent.consentString });
+ }
+}
+
+function setOnAny(collection, key) {
+ for (let i = 0, result; i < collection.length; i++) {
+ result = utils.deepAccess(collection[i], key);
+
+ if (result) {
+ return result;
+ }
+ }
+}
+
+/**
+ * @param {object} slot Ad Unit Params by Prebid
+ * @returns {object} Banner by OpenRTB 2.5 §3.2.6
+ */
+function mapBanner(slot) {
+ if (slot.mediaType === 'banner' ||
+ utils.deepAccess(slot, 'mediaTypes.banner') ||
+ (!slot.mediaType && !slot.mediaTypes)) {
+ const format = slot.sizes.map(size => ({
+ w: size[0],
+ h: size[1],
+ }));
+
+ // override - tylko 1szy wymiar
+ // format = format.slice(0, 1);
+ return {
+ format,
+ id: slot.bidId,
+ };
+ }
+}
+
+function mapImpression(slot) {
+ const imp = {
+ id: slot.params.id,
+ banner: mapBanner(slot),
+ /* native: mapNative(slot), */
+ tagid: slot.params.id,
+ };
+
+ const bidfloor = parseFloat(slot.params.bidfloor);
+
+ if (bidfloor) {
+ imp.bidfloor = bidfloor;
+ }
+
+ return imp;
+}
+
+function renderCreative(site, auctionId, bid, seat, request) {
+ let gam;
+
+ const mcad = {
+ id: auctionId,
+ seat,
+ seatbid: [{
+ bid: [bid],
+ }],
+ };
+
+ const mcbase = btoa(encodeURI(JSON.stringify(mcad)));
+
+ if (bid.adm) {
+ // parse adm for gam config
+ try {
+ gam = JSON.parse(bid.adm).gam;
+
+ if (!gam || !Object.keys(gam).length) {
+ gam = undefined;
+ } else {
+ gam.namedSizes = ['fluid'];
+ gam.div = 'div-gpt-ad-x01';
+ gam.targeting = Object.assign(gam.targeting || {}, {
+ OAS_retarg: '0',
+ PREBID_ON: '1',
+ emptygaf: '0',
+ });
+ }
+
+ if (gam && !gam.targeting) {
+ gam.targeting = {};
+ }
+ } catch (err) {
+ utils.logWarn('Could not parse adm data', bid.adm);
+ }
+ }
+
+ let adcode = `
+
+
+
+
+
+
+
+
+
+
+