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 dac50593d6e..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,17 +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
-- 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 0dd5b25a50f..44882570d89 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ This README is for developers who want to contribute to Prebid.js.
Additional documentation can be found at [the Prebid homepage](http://prebid.org).
Working examples can be found in [the developer docs](http://prebid.org/dev-docs/getting-started.html).
+Prebid.js is open source software that is offered for free as a convenience. While it is designed to help companies address legal requirements associated with header bidding, we cannot and do not warrant that your use of Prebid.js will satisfy legal requirements. You are solely responsible for ensuring that your use of Prebid.js complies with all applicable laws. We strongly encourage you to obtain legal advice when using Prebid.js to ensure your implementation complies with all laws where you operate.
+
**Table of Contents**
- [Usage](#Usage)
@@ -266,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).
@@ -274,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/allowedModules.js b/allowedModules.js
index 2a521f781f9..d8e8b69f593 100644
--- a/allowedModules.js
+++ b/allowedModules.js
@@ -21,7 +21,6 @@ module.exports = {
'fun-hooks/no-eval',
'just-clone',
'dlv',
- 'dset',
- 'deep-equal'
+ 'dset'
]
};
diff --git a/gulpfile.js b/gulpfile.js
index 64152baa7ba..879e34ae588 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -110,6 +110,7 @@ function watch(done) {
connect.server({
https: argv.https,
port: port,
+ host: FAKE_SERVER_HOST,
root: './',
livereload: true
});
diff --git a/integrationExamples/gpt/digitrust_Full.html b/integrationExamples/gpt/digitrust_Full.html
deleted file mode 100644
index fc7704776f4..00000000000
--- a/integrationExamples/gpt/digitrust_Full.html
+++ /dev/null
@@ -1,222 +0,0 @@
-
-
- Full DigiTrust Prebid Sample
-
-
-
-
-
-
-
-
-
-
-
-
-
- DigiTrust Prebid Full Sample
-
-
-
- This sample shows the simplest integration path for using DigiTrust ID with Prebid.
- You can use DigiTrust ID without integrating the entire DigiTrust suite.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/integrationExamples/gpt/digitrust_Simple.html b/integrationExamples/gpt/digitrust_Simple.html
deleted file mode 100644
index 2581c6ce7cc..00000000000
--- a/integrationExamples/gpt/digitrust_Simple.html
+++ /dev/null
@@ -1,230 +0,0 @@
-
-
- Simple DigiTrust Prebid - No Framework
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- DigiTrust Prebid Sample - No Framework
-
-
- This sample shows the simplest integration path for using DigiTrust ID with Prebid.
- You can use DigiTrust ID without integrating the entire DigiTrust suite.
-
-
-
-
-
-
-
-
diff --git a/integrationExamples/gpt/digitrust_cmp_test.html b/integrationExamples/gpt/digitrust_cmp_test.html
deleted file mode 100644
index 6f0a70188f3..00000000000
--- a/integrationExamples/gpt/digitrust_cmp_test.html
+++ /dev/null
@@ -1,192 +0,0 @@
-
-
- CMP Simple DigiTrust Prebid - No Framework
-
-
-
-
-
-
-
-
-
-
-
-
-
- DigiTrust Prebid Sample - No Framework
-
-
- This sample tests cmp behavior with simple integration path for using DigiTrust ID with Prebid.
- You can use DigiTrust ID without integrating the entire DigiTrust suite.
-
-
-
-
-
-
-
-
-
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..8115e60fcd1 100644
--- a/integrationExamples/gpt/userId_example.html
+++ b/integrationExamples/gpt/userId_example.html
@@ -84,6 +84,9 @@
{
code: 'test-div',
sizes: [[300,250],[300,600],[728,90]],
+ mediaTypes: {
+ banner: {}
+ },
bids: [
{
bidder: 'rubicon',
@@ -115,7 +118,7 @@
consentManagement: {
cmpApi: 'iab',
timeout: 1000,
- allowAuctionWithoutConsent: true
+ defaultGdprScope: true
},
// consentManagement: {
// cmpApi: 'static',
@@ -128,7 +131,7 @@
// }
// }
// },
- usersync: {
+ userSync: {
userIds: [{
name: "unifiedId",
params: {
@@ -164,6 +167,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 ba8af4e6550..50d17fc5f6c 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -2,22 +2,24 @@
"userId": [
"unifiedIdSystem",
"pubCommonIdSystem",
- "digiTrustIdSystem",
"id5IdSystem",
"parrableIdSystem",
"britepoolIdSystem",
"liveIntentIdSystem",
"lotamePanoramaId",
+ "merkleIdSystem",
"criteoIdSystem",
"netIdSystem",
"identityLinkIdSystem",
- "sharedIdSystem"
+ "sharedIdSystem",
+ "intentIqIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
"dfpAdServerVideo"
],
"rtdModule": [
- "browsiRtdProvider"
+ "browsiRtdProvider",
+ "jwplayerRtdProvider"
]
}
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index 5df2af7f32e..798b6450946 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -167,7 +167,7 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl
// Allow the ability to configure the HB endpoint for testing purposes.
const ttxSettings = config.getConfig('ttxSettings');
- const url = (ttxSettings && ttxSettings.url) || END_POINT;
+ const url = (ttxSettings && ttxSettings.url) || `${END_POINT}?guid=${params.siteId}`;
// Return the server request
return {
diff --git a/modules/a4gBidAdapter.js b/modules/a4gBidAdapter.js
new file mode 100644
index 00000000000..b7d8722e9f9
--- /dev/null
+++ b/modules/a4gBidAdapter.js
@@ -0,0 +1,90 @@
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import * as utils from '../src/utils.js';
+
+const A4G_BIDDER_CODE = 'a4g';
+const A4G_CURRENCY = 'USD';
+const A4G_DEFAULT_BID_URL = 'https://ads.ad4game.com/v1/bid';
+const A4G_TTL = 120;
+
+const LOCATION_PARAM_NAME = 'siteurl';
+const ID_PARAM_NAME = 'id';
+const IFRAME_PARAM_NAME = 'if';
+const ZONE_ID_PARAM_NAME = 'zoneId';
+const SIZE_PARAM_NAME = 'size';
+
+const ARRAY_PARAM_SEPARATOR = ';';
+const ARRAY_SIZE_SEPARATOR = ',';
+const SIZE_SEPARATOR = 'x';
+
+export const spec = {
+ code: A4G_BIDDER_CODE,
+ isBidRequestValid: function(bid) {
+ return bid.params && !!bid.params.zoneId;
+ },
+
+ buildRequests: function(validBidRequests, bidderRequest) {
+ let deliveryUrl = '';
+ const idParams = [];
+ const sizeParams = [];
+ const zoneIds = [];
+
+ utils._each(validBidRequests, function(bid) {
+ if (!deliveryUrl && typeof bid.params.deliveryUrl === 'string') {
+ deliveryUrl = bid.params.deliveryUrl;
+ }
+ idParams.push(bid.bidId);
+ let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes;
+ sizeParams.push(bidSizes.map(size => size.join(SIZE_SEPARATOR)).join(ARRAY_SIZE_SEPARATOR));
+ zoneIds.push(bid.params.zoneId);
+ });
+
+ if (!deliveryUrl) {
+ deliveryUrl = A4G_DEFAULT_BID_URL;
+ }
+
+ let data = {
+ [IFRAME_PARAM_NAME]: 0,
+ [LOCATION_PARAM_NAME]: (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : window.location.href,
+ [SIZE_PARAM_NAME]: sizeParams.join(ARRAY_PARAM_SEPARATOR),
+ [ID_PARAM_NAME]: idParams.join(ARRAY_PARAM_SEPARATOR),
+ [ZONE_ID_PARAM_NAME]: zoneIds.join(ARRAY_PARAM_SEPARATOR)
+ };
+
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ data.gdpr = {
+ applies: bidderRequest.gdprConsent.gdprApplies,
+ consent: bidderRequest.gdprConsent.consentString
+ };
+ }
+
+ return {
+ method: 'GET',
+ url: deliveryUrl,
+ data: data
+ };
+ },
+
+ interpretResponse: function(serverResponses, request) {
+ const bidResponses = [];
+ utils._each(serverResponses.body, function(response) {
+ if (response.cpm > 0) {
+ const bidResponse = {
+ requestId: response.id,
+ creativeId: response.id,
+ adId: response.id,
+ cpm: response.cpm,
+ width: response.width,
+ height: response.height,
+ currency: A4G_CURRENCY,
+ netRevenue: true,
+ ttl: A4G_TTL,
+ ad: response.ad
+ };
+ bidResponses.push(bidResponse);
+ }
+ });
+ return bidResponses;
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/a4gBidAdapter.md b/modules/a4gBidAdapter.md
index dcab312ed29..70f110724b0 100644
--- a/modules/a4gBidAdapter.md
+++ b/modules/a4gBidAdapter.md
@@ -6,32 +6,40 @@ Maintainer: devops@ad4game.com
# Description
-Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com).
+Ad4Game Bidder Adapter for Prebid.js. It should be tested on real domain. `localhost` should be rewritten (ex. example.com).
# Test Parameters
```
var adUnits = [
{
code: 'test-div',
- sizes: [[300, 250]], // a display size
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]], // a display size
+ }
+ },
bids: [
{
bidder: 'a4g',
params: {
zoneId: 59304,
- deliveryUrl: 'http://dev01.ad4game.com/v1/bid'
+ deliveryUrl: 'https://dev01.ad4game.com/v1/bid'
}
}
]
},{
code: 'test-div',
- sizes: [[300, 50]], // a mobile size
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 50]], // a mobile size
+ }
+ },
bids: [
{
bidder: 'a4g',
params: {
zoneId: 59354,
- deliveryUrl: 'http://dev01.ad4game.com/v1/bid'
+ deliveryUrl: 'https://dev01.ad4game.com/v1/bid'
}
}
]
diff --git a/modules/ablidaBidAdapter.js b/modules/ablidaBidAdapter.js
index 9bd22ef1f0d..470a845cd20 100644
--- a/modules/ablidaBidAdapter.js
+++ b/modules/ablidaBidAdapter.js
@@ -1,13 +1,14 @@
import * as utils from '../src/utils.js';
import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {ajax} from '../src/ajax.js';
+import { BANNER, NATIVE } from '../src/mediaTypes.js';
const BIDDER_CODE = 'ablida';
const ENDPOINT_URL = 'https://bidder.ablida.net/prebid';
export const spec = {
code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, NATIVE],
/**
* Determines whether or not the given bid request is valid.
@@ -31,20 +32,22 @@ export const spec = {
return [];
}
return validBidRequests.map(bidRequest => {
- const sizes = utils.parseSizesInput(bidRequest.sizes)[0];
- const size = sizes.split('x');
+ let sizes = []
+ if (bidRequest.mediaTypes && bidRequest.mediaTypes[BANNER] && bidRequest.mediaTypes[BANNER].sizes) {
+ sizes = bidRequest.mediaTypes[BANNER].sizes;
+ }
const jaySupported = 'atob' in window && 'currentScript' in document;
const device = getDevice();
const payload = {
placementId: bidRequest.params.placementId,
- width: size[0],
- height: size[1],
+ sizes: sizes,
bidId: bidRequest.bidId,
categories: bidRequest.params.categories,
referer: bidderRequest.refererInfo.referer,
jaySupported: jaySupported,
device: device,
- adapterVersion: 2
+ adapterVersion: 3,
+ mediaTypes: bidRequest.mediaTypes
};
return {
method: 'POST',
@@ -72,9 +75,8 @@ export const spec = {
return bidResponses;
},
onBidWon: function (bid) {
- if (!bid['nurl']) { return false; }
- ajax(bid['nurl'], null);
- return true;
+ if (!bid['nurl']) { return; }
+ utils.triggerPixel(bid['nurl']);
}
};
diff --git a/modules/ablidaBidAdapter.md b/modules/ablidaBidAdapter.md
index 70e6576cd30..001bee4f35c 100644
--- a/modules/ablidaBidAdapter.md
+++ b/modules/ablidaBidAdapter.md
@@ -27,6 +27,30 @@ Module that connects to Ablida's bidder for bids.
}
}
]
+ }, {
+ code: 'native-ad-div',
+ mediaTypes: {
+ native: {
+ image: {
+ sendId: true,
+ required: true
+ },
+ title: {
+ required: true
+ },
+ body: {
+ required: true
+ }
+ }
+ },
+ bids: [
+ {
+ bidder: 'ablida',
+ params: {
+ placementId: 'native-demo'
+ }
+ }
+ ]
}
- ];
+ ];
```
diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js
index b2ef5dafb41..b4c2a6ac0d6 100644
--- a/modules/adagioBidAdapter.js
+++ b/modules/adagioBidAdapter.js
@@ -5,16 +5,18 @@ import { loadExternalScript } from '../src/adloader.js'
import JSEncrypt from 'jsencrypt/bin/jsencrypt.js';
import sha256 from 'crypto-js/sha256.js';
import { getStorageManager } from '../src/storageManager.js';
-
-const BIDDER_CODE = 'adagio';
-const VERSION = '2.2.2';
-const FEATURES_VERSION = '1';
-const ENDPOINT = 'https://mp.4dex.io/prebid';
-const SUPPORTED_MEDIA_TYPES = ['banner'];
-const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js';
-const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';
-const GVLID = 617;
-const storage = getStorageManager(GVLID, 'adagio');
+import { getRefererInfo } from '../src/refererDetection.js';
+
+export const BIDDER_CODE = 'adagio';
+export const LOG_PREFIX = 'Adagio:';
+export const VERSION = '2.3.0';
+export const FEATURES_VERSION = '1';
+export const ENDPOINT = 'https://mp.4dex.io/prebid';
+export const SUPPORTED_MEDIA_TYPES = ['banner'];
+export const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js';
+export const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';
+export const GVLID = 617;
+export const storage = getStorageManager(GVLID, 'adagio');
export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0
@@ -23,17 +25,19 @@ t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj
pV6EP3MTLosuUEpLaQIDAQAB
-----END PUBLIC KEY-----`;
+let currentWindow;
+
export function adagioScriptFromLocalStorageCb(ls) {
try {
if (!ls) {
- utils.logWarn('Adagio Script not found');
+ utils.logWarn(`${LOG_PREFIX} script not found.`);
return;
}
const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/;
if (!hashRgx.test(ls)) {
- utils.logWarn('No hash found in Adagio script');
+ utils.logWarn(`${LOG_PREFIX} no hash found.`);
storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY);
} else {
const r = ls.match(hashRgx);
@@ -44,21 +48,32 @@ export function adagioScriptFromLocalStorageCb(ls) {
jsEncrypt.setPublicKey(ADAGIO_PUBKEY);
if (jsEncrypt.verify(content, hash, sha256)) {
- utils.logInfo('Start Adagio script');
+ utils.logInfo(`${LOG_PREFIX} start script.`);
Function(ls)(); // eslint-disable-line no-new-func
} else {
- utils.logWarn('Invalid Adagio script found');
+ utils.logWarn(`${LOG_PREFIX} invalid script found.`);
storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY);
}
}
} catch (err) {
- //
+ utils.logError(LOG_PREFIX, err);
}
}
export function getAdagioScript() {
storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => {
- adagioScriptFromLocalStorageCb(ls)
+ internal.adagioScriptFromLocalStorageCb(ls)
+ });
+
+ storage.localStorageIsEnabled(isValid => {
+ if (isValid) {
+ loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE);
+ } else {
+ // ensure adagio removing for next time.
+ // It's an antipattern regarding the TCF2 enforcement logic
+ // but it's the only way to respect the user choice update.
+ window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY);
+ }
});
}
@@ -72,113 +87,197 @@ function canAccessTopWindow() {
}
}
+function getCurrentWindow() {
+ return currentWindow || utils.getWindowSelf();
+}
+
+function isSafeFrameWindow() {
+ const ws = utils.getWindowSelf();
+ return !!(ws.$sf && ws.$sf.ext);
+}
+
function initAdagio() {
- const w = utils.getWindowTop();
+ if (canAccessTopWindow()) {
+ currentWindow = (canAccessTopWindow()) ? utils.getWindowTop() : utils.getWindowSelf();
+ }
+
+ const w = internal.getCurrentWindow();
w.ADAGIO = w.ADAGIO || {};
+ w.ADAGIO.adUnits = w.ADAGIO.adUnits || {};
+ w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || [];
w.ADAGIO.queue = w.ADAGIO.queue || [];
w.ADAGIO.versions = w.ADAGIO.versions || {};
w.ADAGIO.versions.adagioBidderAdapter = VERSION;
+ w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow();
getAdagioScript();
-
- loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE)
-}
-
-if (canAccessTopWindow()) {
- initAdagio();
}
export const _features = {
- getPrintNumber: function (adUnitCode) {
- const adagioAdUnit = _getOrAddAdagioAdUnit(adUnitCode);
+ getPrintNumber(adUnitCode) {
+ const adagioAdUnit = internal.getOrAddAdagioAdUnit(adUnitCode);
return adagioAdUnit.printNumber || 1;
},
- getPageDimensions: function () {
- const viewportDims = _features.getViewPortDimensions().split('x');
- const w = utils.getWindowTop();
- const body = w.document.body;
+ getPageDimensions() {
+ if (isSafeFrameWindow() || !canAccessTopWindow()) {
+ return '';
+ }
+
+ // the page dimension can be computed on window.top only.
+ const wt = utils.getWindowTop();
+ const body = wt.document.querySelector('body');
+
if (!body) {
- return ''
+ return '';
}
- const html = w.document.documentElement;
+ const html = wt.document.documentElement;
+ const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth);
const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
- return viewportDims[0] + 'x' + pageHeight;
+ return `${pageWidth}x${pageHeight}`;
},
- getViewPortDimensions: function () {
- let viewPortWidth;
- let viewPortHeight;
- const w = utils.getWindowTop();
- const d = w.document;
+ getViewPortDimensions() {
+ if (!isSafeFrameWindow() && !canAccessTopWindow()) {
+ return '';
+ }
+
+ const viewportDims = { w: 0, h: 0 };
- if (w.innerWidth) {
- viewPortWidth = w.innerWidth;
- viewPortHeight = w.innerHeight;
+ if (isSafeFrameWindow()) {
+ const ws = utils.getWindowSelf();
+
+ if (typeof ws.$sf.ext.geom !== 'function') {
+ utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`);
+ return '';
+ }
+
+ const sfGeom = ws.$sf.ext.geom().win;
+ viewportDims.w = Math.round(sfGeom.w);
+ viewportDims.h = Math.round(sfGeom.h);
} else {
- viewPortWidth = d.getElementsByTagName('body')[0].clientWidth;
- viewPortHeight = d.getElementsByTagName('body')[0].clientHeight;
+ // window.top based computing
+ const wt = utils.getWindowTop();
+
+ if (wt.innerWidth) {
+ viewportDims.w = wt.innerWidth;
+ viewportDims.h = wt.innerHeight;
+ } else {
+ const d = wt.document;
+ const body = d.querySelector('body');
+
+ if (!body) {
+ return '';
+ }
+
+ viewportDims.w = d.querySelector('body').clientWidth;
+ viewportDims.h = d.querySelector('body').clientHeight;
+ }
}
- return viewPortWidth + 'x' + viewPortHeight;
+ return `${viewportDims.w}x${viewportDims.h}`;
},
- isDomLoading: function () {
- const w = utils.getWindowTop();
- let performance = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance;
- let domLoading = -1;
+ /**
+ * domLoading feature is computed on window.top if reachable.
+ */
+ getDomLoadingDuration() {
+ let domLoadingDuration = -1;
+ let performance;
+
+ performance = (canAccessTopWindow()) ? utils.getWindowTop().performance : utils.getWindowSelf().performance;
if (performance && performance.timing && performance.timing.navigationStart > 0) {
const val = performance.timing.domLoading - performance.timing.navigationStart;
- if (val > 0) domLoading = val;
+ if (val > 0) {
+ domLoadingDuration = val;
+ }
}
- return domLoading;
+
+ return domLoadingDuration;
},
- getSlotPosition: function (element) {
- if (!element) return '';
-
- const w = utils.getWindowTop();
- const d = w.document;
- const el = element;
-
- let box = el.getBoundingClientRect();
- const docEl = d.documentElement;
- const body = d.body;
- const clientTop = d.clientTop || body.clientTop || 0;
- const clientLeft = d.clientLeft || body.clientLeft || 0;
- const scrollTop = w.pageYOffset || docEl.scrollTop || body.scrollTop;
- const scrollLeft = w.pageXOffset || docEl.scrollLeft || body.scrollLeft;
-
- const elComputedStyle = w.getComputedStyle(el, null);
- const elComputedDisplay = elComputedStyle.display || 'block';
- const mustDisplayElement = elComputedDisplay === 'none';
-
- if (mustDisplayElement) {
- el.style = el.style || {};
- el.style.display = 'block';
- box = el.getBoundingClientRect();
- el.style.display = elComputedDisplay;
+ getSlotPosition(params) {
+ const { adUnitElementId, postBid } = params;
+
+ if (!adUnitElementId) {
+ return '';
}
- const position = {
- x: Math.round(box.left + scrollLeft - clientLeft),
- y: Math.round(box.top + scrollTop - clientTop)
- };
+ if (!isSafeFrameWindow() && !canAccessTopWindow()) {
+ return '';
+ }
+
+ const position = { x: 0, y: 0 };
+
+ if (isSafeFrameWindow()) {
+ const ws = utils.getWindowSelf();
+
+ if (typeof ws.$sf.ext.geom !== 'function') {
+ utils.logWarn(`${LOG_PREFIX} cannot use the $sf.ext.geom() safeFrame API method`);
+ return '';
+ }
- return position.x + 'x' + position.y;
+ const sfGeom = ws.$sf.ext.geom().self;
+ position.x = Math.round(sfGeom.t);
+ position.y = Math.round(sfGeom.l);
+ } else if (canAccessTopWindow()) {
+ // window.top based computing
+ const wt = utils.getWindowTop();
+ const d = wt.document;
+
+ let domElement;
+
+ if (postBid === true) {
+ const ws = utils.getWindowSelf();
+ const currentElement = ws.document.getElementById(adUnitElementId);
+ domElement = internal.getElementFromTopWindow(currentElement, ws);
+ } else {
+ domElement = wt.document.getElementById(adUnitElementId);
+ }
+
+ if (!domElement) {
+ return '';
+ }
+
+ let box = domElement.getBoundingClientRect();
+
+ const docEl = d.documentElement;
+ const body = d.body;
+ const clientTop = d.clientTop || body.clientTop || 0;
+ const clientLeft = d.clientLeft || body.clientLeft || 0;
+ const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop;
+ const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft;
+
+ const elComputedStyle = wt.getComputedStyle(domElement, null);
+ const elComputedDisplay = elComputedStyle.display || 'block';
+ const mustDisplayElement = elComputedDisplay === 'none';
+
+ if (mustDisplayElement) {
+ domElement.style = domElement.style || {};
+ domElement.style.display = 'block';
+ box = domElement.getBoundingClientRect();
+ domElement.style.display = elComputedDisplay;
+ }
+ position.x = Math.round(box.left + scrollLeft - clientLeft);
+ position.y = Math.round(box.top + scrollTop - clientTop);
+ } else {
+ return '';
+ }
+
+ return `${position.x}x${position.y}`;
},
- getTimestamp: function () {
+ getTimestampUTC() {
+ // timestamp returned in seconds
return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60;
},
- getDevice: function () {
- if (!canAccessTopWindow()) return false;
- const w = utils.getWindowTop();
- const ua = w.navigator.userAgent;
+ getDevice() {
+ const ws = utils.getWindowSelf();
+ const ua = ws.navigator.userAgent;
if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) {
return 5; // "tablet"
@@ -189,52 +288,83 @@ export const _features = {
return 2; // personal computers
},
- getBrowser: function () {
- const w = utils.getWindowTop();
- const ua = w.navigator.userAgent;
+ getBrowser() {
+ const ws = utils.getWindowSelf();
+ const ua = ws.navigator.userAgent;
const uaLowerCase = ua.toLowerCase();
- return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || w.MSStream ? 'ie' : 'unknow';
+ return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || ws.MSStream ? 'ie' : 'unknow';
},
- getOS: function () {
- const w = window.top;
- const ua = w.navigator.userAgent;
+ getOS() {
+ const ws = utils.getWindowSelf();
+ const ua = ws.navigator.userAgent;
const uaLowerCase = ua.toLowerCase();
return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : '';
+ },
+
+ getUrl(refererInfo) {
+ // top has not been reached, it means we are not sure
+ // to get the proper page url.
+ if (!refererInfo.reachedTop) {
+ return;
+ }
+ return refererInfo.referer;
+ },
+
+ getUrlFromParams(params) {
+ const { postBidOptions } = params;
+ if (postBidOptions && postBidOptions.url) {
+ return postBidOptions.url;
+ }
}
-}
+};
-function _pushInAdagioQueue(ob) {
- try {
- if (!canAccessTopWindow()) return;
- const w = utils.getWindowTop();
- w.ADAGIO.queue.push(ob);
- } catch (e) {}
+function enqueue(ob) {
+ const w = internal.getCurrentWindow();
+
+ w.ADAGIO = w.ADAGIO || {};
+ w.ADAGIO.queue = w.ADAGIO.queue || [];
+ w.ADAGIO.queue.push(ob);
};
-function _getOrAddAdagioAdUnit(adUnitCode) {
- const w = utils.getWindowTop();
+function getOrAddAdagioAdUnit(adUnitCode) {
+ const w = internal.getCurrentWindow();
+
+ w.ADAGIO = w.ADAGIO || {};
+
if (w.ADAGIO.adUnits[adUnitCode]) {
return w.ADAGIO.adUnits[adUnitCode]
}
+
return w.ADAGIO.adUnits[adUnitCode] = {};
-}
+};
+
+function getPageviewId() {
+ const w = internal.getCurrentWindow();
+
+ w.ADAGIO = w.ADAGIO || {};
+ w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID();
+
+ return w.ADAGIO.pageviewId;
+};
-function _computePrintNumber(adUnitCode) {
+function computePrintNumber(adUnitCode) {
let printNumber = 1;
- const w = utils.getWindowTop();
+ const w = internal.getCurrentWindow();
+
if (
w.ADAGIO &&
w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] &&
- w.ADAGIO.adUnits[adUnitCode].pageviewId === _getPageviewId() &&
+ w.ADAGIO.adUnits[adUnitCode].pageviewId === internal.getPageviewId() &&
w.ADAGIO.adUnits[adUnitCode].printNumber
) {
printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1;
}
+
return printNumber;
-}
+};
-function _getDevice() {
+function getDevice() {
const language = navigator.language ? 'language' : 'userLanguage';
return {
userAgent: navigator.userAgent,
@@ -246,35 +376,63 @@ function _getDevice() {
};
};
-function _getSite() {
- const w = utils.getWindowTop();
+function getSite(bidderRequest) {
+ let domain = '';
+ let page = '';
+ let referrer = '';
+
+ const { refererInfo } = bidderRequest;
+
+ if (canAccessTopWindow()) {
+ const wt = utils.getWindowTop();
+ domain = wt.location.hostname;
+ page = wt.location.href;
+ referrer = wt.document.referrer || '';
+ } else if (refererInfo.reachedTop) {
+ const url = utils.parseUrl(refererInfo.referer);
+ domain = url.hostname;
+ page = refererInfo.referer;
+ } else if (refererInfo.stack && refererInfo.stack.length && refererInfo.stack[0]) {
+ // important note check if refererInfo.stack[0] is 'thruly' because a `null` value
+ // will be considered as "localhost" by the parseUrl function.
+ // As the isBidRequestValid returns false when it does not reach the referer
+ // this should never called.
+ const url = utils.parseUrl(refererInfo.stack[0]);
+ domain = url.hostname;
+ }
+
return {
- domain: w.location.hostname,
- page: w.location.href,
- referrer: w.document.referrer || ''
+ domain,
+ page,
+ referrer
};
};
-function _getPageviewId() {
- if (!canAccessTopWindow()) return false;
- const w = utils.getWindowTop();
- w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID();
- return w.ADAGIO.pageviewId;
-};
+function getElementFromTopWindow(element, currentWindow) {
+ try {
+ if (utils.getWindowTop() === currentWindow) {
+ if (!element.getAttribute('id')) {
+ element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`);
+ }
+ return element;
+ } else {
+ const frame = currentWindow.frameElement;
+ const frameClientRect = frame.getBoundingClientRect();
+ const elementClientRect = element.getBoundingClientRect();
+
+ if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) {
+ return false;
+ }
-function _getElementFromTopWindow(element, currentWindow) {
- if (utils.getWindowTop() === currentWindow) {
- if (!element.getAttribute('id')) {
- element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`);
+ return getElementFromTopWindow(frame, currentWindow.parent);
}
- return element;
- } else {
- const frame = currentWindow.frameElement;
- return _getElementFromTopWindow(frame, currentWindow.parent);
+ } catch (err) {
+ utils.logWarn(`${LOG_PREFIX}`, err);
+ return false;
}
-}
+};
-export function _autoDetectAdUnitElementId(adUnitCode) {
+function autoDetectAdUnitElementId(adUnitCode) {
const autoDetectedAdUnit = utils.getGptSlotInfoForAdUnitCode(adUnitCode)
let adUnitElementId = null;
@@ -283,9 +441,9 @@ export function _autoDetectAdUnitElementId(adUnitCode) {
}
return adUnitElementId;
-}
+};
-function _autoDetectEnvironment() {
+function autoDetectEnvironment() {
const device = _features.getDevice();
let environment;
switch (device) {
@@ -300,53 +458,37 @@ function _autoDetectEnvironment() {
break;
};
return environment
-}
+};
-/**
- * Returns all features for a specific adUnit element
- *
- * @param {Object} bidRequest
- * @returns {Object} features for an element (see specs)
- */
-function _getFeatures(bidRequest) {
- if (!canAccessTopWindow()) return;
- const w = utils.getWindowTop();
- const adUnitCode = bidRequest.adUnitCode;
- const adUnitElementId = bidRequest.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode);
- let element;
+function getFeatures(bidRequest, bidderRequest) {
+ const { adUnitCode, params } = bidRequest;
+ const { adUnitElementId } = params;
+ const { refererInfo } = bidderRequest;
if (!adUnitElementId) {
- utils.logWarn('Unable to detect adUnitElementId. Adagio measures won\'t start');
- } else {
- if (bidRequest.params.postBid === true) {
- window.document.getElementById(adUnitElementId);
- element = _getElementFromTopWindow(element, window);
- w.ADAGIO.pbjsAdUnits.map((adUnit) => {
- if (adUnit.code === adUnitCode) {
- const outerElementId = element.getAttribute('id');
- adUnit.outerAdUnitElementId = outerElementId;
- bidRequest.params.outerAdUnitElementId = outerElementId;
- }
- });
- } else {
- element = w.document.getElementById(adUnitElementId);
- }
+ utils.logWarn(`${LOG_PREFIX} unable to get params.adUnitElementId. Continue without tiv.`);
}
const features = {
- print_number: _features.getPrintNumber(bidRequest.adUnitCode).toString(),
+ print_number: _features.getPrintNumber(adUnitCode).toString(),
page_dimensions: _features.getPageDimensions().toString(),
viewport_dimensions: _features.getViewPortDimensions().toString(),
- dom_loading: _features.isDomLoading().toString(),
+ dom_loading: _features.getDomLoadingDuration().toString(),
// layout: features.getLayout().toString(),
- adunit_position: _features.getSlotPosition(element).toString(),
- user_timestamp: _features.getTimestamp().toString(),
+ adunit_position: _features.getSlotPosition(params).toString(),
+ user_timestamp: _features.getTimestampUTC().toString(),
device: _features.getDevice().toString(),
- url: w.location.origin + w.location.pathname,
+ url: _features.getUrl(refererInfo) || _features.getUrlFromParams(params) || '',
browser: _features.getBrowser(),
os: _features.getOS()
};
+ Object.keys(features).forEach((prop) => {
+ if (features[prop] === '') {
+ delete features[prop];
+ }
+ });
+
const adUnitFeature = {};
adUnitFeature[adUnitElementId] = {
@@ -354,7 +496,7 @@ function _getFeatures(bidRequest) {
version: FEATURES_VERSION
};
- _pushInAdagioQueue({
+ internal.enqueue({
action: 'features',
ts: Date.now(),
data: adUnitFeature
@@ -363,22 +505,53 @@ function _getFeatures(bidRequest) {
return features;
};
+export const internal = {
+ enqueue,
+ getOrAddAdagioAdUnit,
+ getPageviewId,
+ computePrintNumber,
+ getDevice,
+ getSite,
+ getElementFromTopWindow,
+ autoDetectAdUnitElementId,
+ autoDetectEnvironment,
+ getFeatures,
+ getRefererInfo,
+ adagioScriptFromLocalStorageCb,
+ getCurrentWindow,
+ canAccessTopWindow
+};
+
function _getGdprConsent(bidderRequest) {
+ if (!utils.deepAccess(bidderRequest, 'gdprConsent')) {
+ return false;
+ }
+
+ const {
+ apiVersion,
+ gdprApplies,
+ consentString,
+ allowAuctionWithoutConsent
+ } = bidderRequest.gdprConsent;
+
const consent = {};
- if (utils.deepAccess(bidderRequest, 'gdprConsent')) {
- if (bidderRequest.gdprConsent.consentString !== undefined) {
- consent.consentString = bidderRequest.gdprConsent.consentString;
- }
- if (bidderRequest.gdprConsent.gdprApplies !== undefined) {
- consent.consentRequired = bidderRequest.gdprConsent.gdprApplies ? 1 : 0;
- }
- if (bidderRequest.gdprConsent.allowAuctionWithoutConsent !== undefined) {
- consent.allowAuctionWithoutConsent = bidderRequest.gdprConsent.allowAuctionWithoutConsent ? 1 : 0;
- }
- if (bidderRequest.gdprConsent.apiVersion !== undefined) {
- consent.apiVersion = bidderRequest.gdprConsent.apiVersion;
- }
+
+ if (apiVersion !== undefined) {
+ consent.apiVersion = apiVersion
+ }
+
+ if (consentString !== undefined) {
+ consent.consentString = consentString;
+ }
+
+ if (gdprApplies !== undefined) {
+ consent.consentRequired = (gdprApplies) ? 1 : 0;
}
+
+ if (allowAuctionWithoutConsent !== undefined) {
+ consent.allowAuctionWithoutConsent = allowAuctionWithoutConsent ? 1 : 0;
+ }
+
return consent;
}
@@ -391,84 +564,103 @@ function _getSchain(bidRequest) {
export const spec = {
code: BIDDER_CODE,
gvlid: GVLID,
- supportedMediaType: SUPPORTED_MEDIA_TYPES,
+ supportedMediaTypes: SUPPORTED_MEDIA_TYPES,
- isBidRequestValid: function (bid) {
+ isBidRequestValid(bid) {
const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid;
- const { organizationId, site, placement } = bid.params;
- const adUnitElementId = bid.params.adUnitElementId || _autoDetectAdUnitElementId(adUnitCode);
- const environment = bid.params.environment || _autoDetectEnvironment();
- let isValid = false;
+ if (!params) {
+ utils.logWarn(`${LOG_PREFIX} the "params" property is missing.`);
+ return false;
+ }
- utils.logInfo('adUnitElementId', adUnitElementId)
+ const { organizationId, site, placement } = params;
+ const adUnitElementId = params.adUnitElementId || internal.autoDetectAdUnitElementId(adUnitCode);
+ const environment = params.environment || internal.autoDetectEnvironment();
- try {
- if (canAccessTopWindow()) {
- const w = utils.getWindowTop();
- w.ADAGIO = w.ADAGIO || {};
- w.ADAGIO.adUnits = w.ADAGIO.adUnits || {};
- w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || [];
- isValid = !!(organizationId && site && placement);
-
- const tempAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode);
-
- bid.params = {
- ...bid.params,
- adUnitElementId,
- environment
- }
+ // insure auto-detected params are kept in `bid` object.
+ bid.params = {
+ ...params,
+ adUnitElementId,
+ environment
+ }
- tempAdUnits.push({
- code: adUnitCode,
- sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes,
- bids: [{
- bidder,
- params
- }]
- });
- w.ADAGIO.pbjsAdUnits = tempAdUnits;
-
- if (isValid === true) {
- let printNumber = _computePrintNumber(adUnitCode);
- w.ADAGIO.adUnits[adUnitCode] = {
- auctionId: auctionId,
- pageviewId: _getPageviewId(),
- printNumber
- };
- }
+ const debugData = () => ({
+ action: 'pb-dbg',
+ ts: Date.now(),
+ data: {
+ bid
}
- } catch (e) {
- return isValid;
+ });
+
+ const refererInfo = internal.getRefererInfo();
+
+ if (!refererInfo.reachedTop) {
+ utils.logWarn(`${LOG_PREFIX} the main page url is unreachabled.`);
+ internal.enqueue(debugData());
+
+ return false;
+ } else if (!(organizationId && site && placement)) {
+ utils.logWarn(`${LOG_PREFIX} at least one required param is missing.`);
+ internal.enqueue(debugData());
+
+ return false;
}
- return isValid;
- },
- buildRequests: function (validBidRequests, bidderRequest) {
- // AdagioBidAdapter works when window.top can be reached only
- if (!bidderRequest.refererInfo.reachedTop) return [];
+ const w = internal.getCurrentWindow();
+ const pageviewId = internal.getPageviewId();
+ const printNumber = internal.computePrintNumber(adUnitCode);
+
+ // Store adUnits config.
+ // If an adUnitCode has already been stored, it will be replaced.
+ w.ADAGIO = w.ADAGIO || {};
+ w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode)
+ w.ADAGIO.pbjsAdUnits.push({
+ code: adUnitCode,
+ mediaTypes: mediaTypes || {},
+ sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes,
+ bids: [{
+ bidder,
+ params: bid.params // use the updated bid.params object with auto-detected params
+ }],
+ auctionId,
+ pageviewId,
+ printNumber
+ });
+
+ // (legacy) Store internal adUnit information
+ w.ADAGIO.adUnits[adUnitCode] = {
+ auctionId,
+ pageviewId,
+ printNumber,
+ };
+
+ return true;
+ },
+ buildRequests(validBidRequests, bidderRequest) {
const secure = (location.protocol === 'https:') ? 1 : 0;
- const device = _getDevice();
- const site = _getSite();
- const pageviewId = _getPageviewId();
- const gdprConsent = _getGdprConsent(bidderRequest);
+ const device = internal.getDevice();
+ const site = internal.getSite(bidderRequest);
+ const pageviewId = internal.getPageviewId();
+ const gdprConsent = _getGdprConsent(bidderRequest) || {};
const schain = _getSchain(validBidRequests[0]);
const adUnits = utils._map(validBidRequests, (bidRequest) => {
- bidRequest.features = _getFeatures(bidRequest);
+ bidRequest.features = internal.getFeatures(bidRequest, bidderRequest);
return bidRequest;
});
- // Regroug ad units by siteId
+ // Group ad units by organizationId
const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => {
- if (adUnit.params && adUnit.params.organizationId) {
- adUnit.params.organizationId = adUnit.params.organizationId.toString();
- }
- (groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []).push(adUnit);
+ adUnit.params.organizationId = adUnit.params.organizationId.toString();
+
+ groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []
+ groupedAdUnits[adUnit.params.organizationId].push(adUnit);
+
return groupedAdUnits;
}, {});
- // Build one request per siteId
- const requests = utils._map(Object.keys(groupedAdUnits), (organizationId) => {
+ // Build one request per organizationId
+ const requests = utils._map(Object.keys(groupedAdUnits), organizationId => {
return {
method: 'POST',
url: ENDPOINT,
@@ -495,13 +687,13 @@ export const spec = {
return requests;
},
- interpretResponse: function (serverResponse, bidRequest) {
+ interpretResponse(serverResponse, bidRequest) {
let bidResponses = [];
try {
const response = serverResponse.body;
if (response) {
if (response.data) {
- _pushInAdagioQueue({
+ internal.enqueue({
action: 'ssp-data',
ts: Date.now(),
data: response.data
@@ -528,18 +720,20 @@ export const spec = {
return bidResponses;
},
- getUserSyncs: function (syncOptions, serverResponses) {
+ getUserSyncs(syncOptions, serverResponses) {
if (!serverResponses.length || serverResponses[0].body === '' || !serverResponses[0].body.userSyncs) {
return false;
}
- const syncs = serverResponses[0].body.userSyncs.map((sync) => {
- return {
- type: sync.t === 'p' ? 'image' : 'iframe',
- url: sync.u
- }
- })
+
+ const syncs = serverResponses[0].body.userSyncs.map(sync => ({
+ type: sync.t === 'p' ? 'image' : 'iframe',
+ url: sync.u
+ }));
+
return syncs;
- }
-}
+ },
+};
+
+initAdagio();
registerBidder(spec);
diff --git a/modules/adbutlerBidAdapter.js b/modules/adbutlerBidAdapter.js
index 47162aa2445..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);
@@ -25,6 +25,7 @@ export const spec = {
let requestURI;
let serverRequests = [];
let zoneCounters = {};
+ let extraParams = {};
for (i = 0; i < validBidRequests.length; i++) {
bidRequest = validBidRequests[i];
@@ -32,6 +33,7 @@ export const spec = {
accountID = utils.getBidIdParameter('accountID', bidRequest.params);
keyword = utils.getBidIdParameter('keyword', bidRequest.params);
domain = utils.getBidIdParameter('domain', bidRequest.params);
+ extraParams = utils.getBidIdParameter('extra', bidRequest.params);
if (!(zoneID in zoneCounters)) {
zoneCounters[zoneID] = 0;
@@ -52,6 +54,13 @@ export const spec = {
requestURI += 'kw=' + encodeURIComponent(keyword) + ';';
}
+ for (let key in extraParams) {
+ if (extraParams.hasOwnProperty(key)) {
+ let val = encodeURIComponent(extraParams[key]);
+ requestURI += `${key}=${val};`;
+ }
+ }
+
zoneCounters[zoneID]++;
serverRequests.push({
method: 'GET',
diff --git a/modules/adbutlerBidAdapter.md b/modules/adbutlerBidAdapter.md
index 5905074270a..1921cc4046e 100644
--- a/modules/adbutlerBidAdapter.md
+++ b/modules/adbutlerBidAdapter.md
@@ -23,9 +23,12 @@ Module that connects to an AdButler zone to fetch bids.
keyword: 'red', //optional
minCPM: '1.00', //optional
maxCPM: '5.00' //optional
+ extra: { // optional
+ foo: "bar"
+ }
}
}
]
}
];
-```
\ No newline at end of file
+```
diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js
index 3c129d1bee1..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) {
@@ -20,21 +22,28 @@ export const spec = {
}
const { gdprConsent, refererInfo } = bidderRequest;
- const account = getAccount(validBidRequests);
const targets = validBidRequests.map(bid => bid.params.data).reduce(mergeTargets, {});
- const gdprParams = (gdprConsent && gdprConsent.consentString) ? [`xt${gdprConsent.consentString}`] : [];
- const refererParams = (refererInfo && refererInfo.referer) ? [`xf${base64urlEncode(refererInfo.referer)}`] : [];
- const id5Params = (getId5Id(validBidRequests)) ? [`x5${getId5Id(validBidRequests)}`] : [];
- const targetsParams = Object.keys(targets).map(targetCode => targetCode + targets[targetCode].join(';'));
- const slotsParams = validBidRequests.map(bid => 'sl' + bidToSlotName(bid));
- const params = [...slotsParams, ...targetsParams, ...gdprParams, ...refererParams, ...id5Params].map(s => `/${s}`).join('');
- const cacheBuster = '?t=' + new Date().getTime();
- const uri = 'https://ads-' + account + '.adhese.com/json' + params + cacheBuster;
+ const gdprParams = (gdprConsent && gdprConsent.consentString) ? { xt: [gdprConsent.consentString] } : {};
+ const refererParams = (refererInfo && refererInfo.referer) ? { xf: [base64urlEncode(refererInfo.referer)] } : {};
+ const id5Params = (getId5Id(validBidRequests)) ? { x5: [getId5Id(validBidRequests)] } : {};
+ const slots = validBidRequests.map(bid => ({ slotname: bidToSlotName(bid) }));
+
+ const payload = {
+ slots: slots,
+ parameters: { ...targets, ...gdprParams, ...refererParams, ...id5Params }
+ }
+
+ const account = getAccount(validBidRequests);
+ const uri = 'https://ads-' + account + '.adhese.com/json';
return {
- method: 'GET',
+ method: 'POST',
url: uri,
- bids: validBidRequests
+ data: JSON.stringify(payload),
+ bids: validBidRequests,
+ options: {
+ contentType: 'application/json'
+ }
};
},
@@ -105,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;
+ }
}
});
}
@@ -148,7 +160,7 @@ function getbaseAdResponse(response) {
}
function isAdheseAd(ad) {
- return !ad.origin || ad.origin === 'JERLICIA' || ad.origin === 'DALE';
+ return !ad.origin || ad.origin === 'JERLICIA';
}
function getMediaType(markup) {
diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js
index d069af7d56e..972dd696bf6 100644
--- a/modules/adkernelBidAdapter.js
+++ b/modules/adkernelBidAdapter.js
@@ -78,10 +78,11 @@ export const spec = {
*/
buildRequests: function (bidRequests, bidderRequest) {
let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo);
- const requests = [];
+ let requests = [];
+ let schain = bidRequests[0].schain;
Object.keys(impDispatch).forEach(host => {
Object.keys(impDispatch[host]).forEach(zoneId => {
- const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest);
+ const request = buildRtbRequest(impDispatch[host][zoneId], bidderRequest, schain);
requests.push({
method: 'POST',
url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`,
@@ -119,6 +120,9 @@ export const spec = {
ttl: 360,
netRevenue: true
};
+ if (rtbBid.dealid !== undefined) {
+ prBid.dealId = rtbBid.dealid;
+ }
if ('banner' in imp) {
prBid.mediaType = BANNER;
prBid.width = rtbBid.w;
@@ -293,9 +297,10 @@ function getAllowedSyncMethod(bidderCode) {
* Builds complete rtb request
* @param imps {Object} Collection of rtb impressions
* @param bidderRequest {BidderRequest}
+ * @param schain {Object=} Supply chain config
* @return {Object} Complete rtb request
*/
-function buildRtbRequest(imps, bidderRequest) {
+function buildRtbRequest(imps, bidderRequest, schain) {
let {bidderCode, gdprConsent, auctionId, refererInfo, timeout, uspConsent} = bidderRequest;
let req = {
@@ -305,6 +310,7 @@ function buildRtbRequest(imps, bidderRequest) {
'at': 1,
'device': {
'ip': 'caller',
+ 'ipv6': 'caller',
'ua': 'caller',
'js': 1,
'language': getLanguage()
@@ -329,6 +335,9 @@ function buildRtbRequest(imps, bidderRequest) {
if (syncMethod) {
utils.deepSetValue(req, 'ext.adk_usersync', syncMethod);
}
+ if (schain) {
+ utils.deepSetValue(req, 'source.ext.schain', schain);
+ }
return req;
}
diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js
index 306ab76f512..50303b82979 100644
--- a/modules/adprimeBidAdapter.js
+++ b/modules/adprimeBidAdapter.js
@@ -63,6 +63,7 @@ export const spec = {
for (let i = 0; i < len; i++) {
let bid = validBidRequests[i];
let sizes
+ let identeties = {}
if (bid.mediaTypes) {
if (bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) {
sizes = bid.mediaTypes[BANNER].sizes
@@ -70,6 +71,9 @@ export const spec = {
sizes = bid.mediaTypes[VIDEO].playerSize
}
}
+ if (bid.userId && bid.userId.idl_env) {
+ identeties.identityLink = bid.userId.idl_env
+ }
placements.push({
placementId: bid.params.placementId,
@@ -79,7 +83,8 @@ export const spec = {
hPlayer: sizes ? sizes[1] : 0,
traffic: bid.params.traffic || BANNER,
schain: bid.schain || {},
- keywords: bid.params.keywords || []
+ keywords: bid.params.keywords || [],
+ identeties
});
}
return {
diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js
index 90fcf878d62..2e9529b633c 100644
--- a/modules/amxBidAdapter.js
+++ b/modules/amxBidAdapter.js
@@ -1,14 +1,18 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js';
+import { config } from '../src/config.js';
+import { getStorageManager } from '../src/storageManager.js';
const BIDDER_CODE = 'amx';
+const storage = getStorageManager(737, BIDDER_CODE);
const SIMPLE_TLD_TEST = /\.co\.\w{2,4}$/;
const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c';
-const VERSION = 'pba1.0';
+const VERSION = 'pba1.2';
const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/;
const VAST_RXP = /^\s*<\??(?:vast|xml)/i;
const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/';
+const AMUID_KEY = '__amuidpb';
const getLocation = (request) =>
parseUrl(deepAccess(request, 'refererInfo.canonicalUrl', location.href))
@@ -47,6 +51,22 @@ function getID(loc) {
const enc = encodeURIComponent;
+function getUIDSafe() {
+ try {
+ return storage.getDataFromLocalStorage(AMUID_KEY)
+ } catch (e) {
+ return null
+ }
+}
+
+function setUIDSafe(uid) {
+ try {
+ storage.setDataInLocalStorage(AMUID_KEY, uid)
+ } catch (e) {
+ // do nothing
+ }
+}
+
function nestedQs (qsData) {
const out = [];
Object.keys(qsData || {}).forEach((key) => {
@@ -77,9 +97,20 @@ function convertRequest(bid) {
const av = isVideoBid || size[1] > 100;
const tid = deepAccess(bid, 'params.tagId')
+ const au = bid.params != null && typeof bid.params.adUnitId === 'string'
+ ? bid.params.adUnitId : bid.adUnitCode;
+
+ const multiSizes = [
+ bid.sizes,
+ deepAccess(bid, `mediaTypes.${BANNER}.sizes`, []) || [],
+ deepAccess(bid, `mediaTypes.${VIDEO}.sizes`, []) || [],
+ ]
+
const params = {
+ au,
av,
vr: isVideoBid,
+ ms: multiSizes,
aw: size[0],
ah: size[1],
tf: 0,
@@ -159,6 +190,16 @@ function resolveSize(bid, request, bidId) {
return [bidRequest.aw, bidRequest.ah];
}
+function values(source) {
+ if (Object.values != null) {
+ return Object.values(source)
+ }
+
+ return Object.keys(source).map((key) => {
+ return source[key]
+ });
+}
+
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
@@ -173,11 +214,19 @@ export const spec = {
const loc = getLocation(bidderRequest);
const tagId = deepAccess(bidRequests[0], 'params.tagId', null);
const testMode = deepAccess(bidRequests[0], 'params.testMode', 0);
+ const fbid = bidRequests[0] != null ? bidRequests[0] : {
+ bidderRequestsCount: 0,
+ bidderWinsCount: 0,
+ bidRequestsCount: 0
+ }
const payload = {
a: bidderRequest.auctionId,
B: 0,
b: loc.host,
+ brc: fbid.bidderRequestsCount || 0,
+ bwc: fbid.bidderWinsCount || 0,
+ trc: fbid.bidRequestsCount || 0,
tm: testMode,
V: '$prebid.version$',
i: (testMode && tagId != null) ? tagId : getID(loc),
@@ -187,15 +236,32 @@ export const spec = {
st: 'prebid',
h: screen.height,
w: screen.width,
- gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', '0'),
+ gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', ''),
gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''),
u: deepAccess(bidderRequest, 'refererInfo.canonicalUrl', loc.href),
do: loc.host,
re: deepAccess(bidderRequest, 'refererInfo.referer'),
+ am: getUIDSafe(),
usp: bidderRequest.uspConsent || '1---',
smt: 1,
d: '',
m: createBidMap(bidRequests),
+ cpp: config.getConfig('coppa') ? 1 : 0,
+ fpd: config.getConfig('fpd'),
+ eids: values(bidRequests.reduce((all, bid) => {
+ // we only want unique ones in here
+ if (bid == null || bid.userIdAsEids == null) {
+ return all
+ }
+
+ _each(bid.userIdAsEids, (value) => {
+ if (value == null) {
+ return;
+ }
+ all[value.source] = value
+ });
+ return all;
+ }, {})),
};
return {
@@ -234,6 +300,10 @@ export const spec = {
return [];
}
+ if (response.am && typeof response.am === 'string') {
+ setUIDSafe(response.am);
+ }
+
return flatMap(Object.keys(response.r), (bidID) => {
return flatMap(response.r[bidID], (siteBid) =>
siteBid.b.map((bid) => {
diff --git a/modules/amxBidAdapter.md b/modules/amxBidAdapter.md
index 4577f5f4f7c..2c38955028f 100644
--- a/modules/amxBidAdapter.md
+++ b/modules/amxBidAdapter.md
@@ -18,6 +18,7 @@ This module connects web publishers to AMX RTB video and display demand.
| --- | -------- | ------- | ----------- |
| `testMode` | no | `true` | this will activate test mode / 100% fill with sample ads |
| `tagId` | no | `"cHJlYmlkLm9yZw"` | can be used for more specific targeting of inventory. Your account manager will provide this ID if needed |
+| `adUnitId` | no | `"sticky_banner"` | optional. To override the bid.adUnitCode provided by prebid. For use in ad-unit level reporting |
# Test Parameters
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/appnexusAnalyticsAdapter.js b/modules/appnexusAnalyticsAdapter.js
index d697d31cdd3..868b317d7d4 100644
--- a/modules/appnexusAnalyticsAdapter.js
+++ b/modules/appnexusAnalyticsAdapter.js
@@ -13,7 +13,8 @@ var appnexusAdapter = adapter({
adapterManager.registerAnalyticsAdapter({
adapter: appnexusAdapter,
- code: 'appnexus'
+ code: 'appnexus',
+ gvlid: 32
});
export default appnexusAdapter;
diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js
index df0b60cb7d7..12bc6a8105c 100644
--- a/modules/appnexusBidAdapter.js
+++ b/modules/appnexusBidAdapter.js
@@ -63,7 +63,20 @@ const storage = getStorageManager(GVLID, BIDDER_CODE);
export const spec = {
code: BIDDER_CODE,
gvlid: GVLID,
- aliases: ['appnexusAst', 'brealtime', 'emxdigital', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia', 'districtm', 'adasta', 'beintoo'],
+ aliases: [
+ { code: 'appnexusAst', gvlid: 32 },
+ { code: 'brealtime' },
+ { code: 'emxdigital', gvlid: 183 },
+ { code: 'pagescience' },
+ { code: 'defymedia' },
+ { code: 'gourmetads' },
+ { code: 'matomy' },
+ { code: 'featureforward' },
+ { code: 'oftmedia' },
+ { code: 'districtm', gvlid: 144 },
+ { code: 'adasta' },
+ { code: 'beintoo', gvlid: 618 },
+ ],
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
/**
diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js
new file mode 100644
index 00000000000..4fb89b9c720
--- /dev/null
+++ b/modules/apstreamBidAdapter.js
@@ -0,0 +1,490 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import * as utils from '../src/utils.js';
+import { getStorageManager } from '../src/storageManager.js';
+
+const CONSTANTS = {
+ DSU_KEY: 'apr_dsu',
+ BIDDER_CODE: 'apstream',
+ GVLID: 394
+};
+const storage = getStorageManager(CONSTANTS.GVLID, CONSTANTS.BIDDER_CODE);
+
+var dsuModule = (function() {
+ 'use strict';
+
+ var DSU_KEY = 'apr_dsu';
+ var DSU_VERSION_NUMBER = '1';
+ var SIGNATURE_SALT = 'YicAu6ZpNG';
+ var DSU_CREATOR = {'USERREPORT': '1'};
+
+ function stringToU8(str) {
+ if (typeof TextEncoder === 'function') {
+ return new TextEncoder().encode(str);
+ }
+ str = unescape(encodeURIComponent(str));
+ var bytes = new Uint8Array(str.length);
+ for (var i = 0, j = str.length; i < j; i++) {
+ bytes[i] = str.charCodeAt(i);
+ }
+ return bytes;
+ }
+
+ function _add(a, b) {
+ var rl = a.l + b.l;
+ var a2 = {
+ h: a.h + b.h + (rl / 2 >>> 31) >>> 0,
+ l: rl >>> 0
+ };
+ a.h = a2.h;
+ a.l = a2.l;
+ }
+ function _xor(a, b) {
+ a.h ^= b.h;
+ a.h >>>= 0;
+ a.l ^= b.l;
+ a.l >>>= 0;
+ }
+ function _rotl(a, n) {
+ var a2 = {
+ h: a.h << n | a.l >>> (32 - n),
+ l: a.l << n | a.h >>> (32 - n)
+ };
+ a.h = a2.h;
+ a.l = a2.l;
+ }
+ function _rotl32(a) {
+ var al = a.l;
+ a.l = a.h;
+ a.h = al;
+ }
+
+ function _compress(v0, v1, v2, v3) {
+ _add(v0, v1);
+ _add(v2, v3);
+ _rotl(v1, 13);
+ _rotl(v3, 16);
+ _xor(v1, v0);
+ _xor(v3, v2);
+ _rotl32(v0);
+ _add(v2, v1);
+ _add(v0, v3);
+ _rotl(v1, 17);
+ _rotl(v3, 21);
+ _xor(v1, v2);
+ _xor(v3, v0);
+ _rotl32(v2);
+ }
+ function _getInt(a, offset) {
+ return a[offset + 3] << 24 |
+ a[offset + 2] << 16 |
+ a[offset + 1] << 8 |
+ a[offset];
+ }
+
+ function hash(key, m) {
+ if (typeof m === 'string') {
+ m = stringToU8(m);
+ }
+ var k0 = {
+ h: key[1] >>> 0,
+ l: key[0] >>> 0
+ };
+ var k1 = {
+ h: key[3] >>> 0,
+ l: key[2] >>> 0
+ };
+ var v0 = {
+ h: k0.h,
+ l: k0.l
+ };
+ var v2 = k0;
+ var v1 = {
+ h: k1.h,
+ l: k1.l
+ };
+ var v3 = k1;
+ var ml = m.length;
+ var ml7 = ml - 7;
+ var buf = new Uint8Array(new ArrayBuffer(8));
+ _xor(v0, {
+ h: 0x736f6d65,
+ l: 0x70736575
+ });
+ _xor(v1, {
+ h: 0x646f7261,
+ l: 0x6e646f6d
+ });
+ _xor(v2, {
+ h: 0x6c796765,
+ l: 0x6e657261
+ });
+ _xor(v3, {
+ h: 0x74656462,
+ l: 0x79746573
+ });
+ var mp = 0;
+ while (mp < ml7) {
+ var mi = {
+ h: _getInt(m, mp + 4),
+ l: _getInt(m, mp)
+ };
+ _xor(v3, mi);
+ _compress(v0, v1, v2, v3);
+ _compress(v0, v1, v2, v3);
+ _xor(v0, mi);
+ mp += 8;
+ }
+ buf[7] = ml;
+ var ic = 0;
+ while (mp < ml) {
+ buf[ic++] = m[mp++];
+ }
+ while (ic < 7) {
+ buf[ic++] = 0;
+ }
+ var mil = {
+ h: buf[7] << 24 | buf[6] << 16 | buf[5] << 8 | buf[4],
+ l: buf[3] << 24 | buf[2] << 16 | buf[1] << 8 | buf[0]
+ };
+ _xor(v3, mil);
+ _compress(v0, v1, v2, v3);
+ _compress(v0, v1, v2, v3);
+ _xor(v0, mil);
+ _xor(v2, {
+ h: 0,
+ l: 0xff
+ });
+ _compress(v0, v1, v2, v3);
+ _compress(v0, v1, v2, v3);
+ _compress(v0, v1, v2, v3);
+ _compress(v0, v1, v2, v3);
+ var h = v0;
+ _xor(h, v1);
+ _xor(h, v2);
+ _xor(h, v3);
+ return h;
+ }
+
+ function hashHex(key, m) {
+ var r = hash(key, m);
+ return ('0000000' + r.h.toString(16)).substr(-8) +
+ ('0000000' + r.l.toString(16)).substr(-8);
+ }
+
+ var SIPHASH_KEY = [0x86395a57, 0x6b5ba7f7, 0x69732c07, 0x2a6ef48d];
+ var hashWithKey = hashHex.bind(null, SIPHASH_KEY);
+
+ var parseUrlRegex = new RegExp('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?');
+ var overwrite = null;
+ var cache = {};
+ function parseUrl(url) {
+ var addscheme =
+ url.indexOf('/') !== 0 &&
+ url.indexOf('/') !== -1 &&
+ (url.indexOf(':') === -1 || url.indexOf(':') > url.indexOf('/'));
+
+ var match = parseUrlRegex.exec(addscheme ? 'noscheme://' + url : url);
+ var res = {
+ scheme: addscheme ? '' : match[2] || '',
+ host: match[4] || '',
+ hostname: match[4] ? match[4].split(':')[0] : '',
+ pathname: match[5] || '',
+ search: match[7] || '',
+ hash: match[9] || '',
+ toString: function () {
+ return url;
+ }
+ };
+
+ res.origin = res.scheme + '://' + res.host;
+ return res;
+ }
+
+ function location() {
+ var url = overwrite || window.location.toString();
+ url = url.replace(/\.demo\.audienceproject\.com\//, '/');
+
+ if (cache.url === url) {
+ return cache.parsed;
+ }
+ var parsed = parseUrl(url);
+ cache.url = url;
+ cache.parsed = parsed;
+ return parsed;
+ }
+
+ function getDaysSinceApEpoch() {
+ var timeDiff = (new Date()).getTime() - (new Date(2019, 0, 1)).getTime();
+ var daysSinceApEpoch = Math.floor(timeDiff / (1000 * 3600 * 24));
+ return daysSinceApEpoch;
+ }
+
+ function generateDsu() {
+ var dsuId = utils.generateUUID();
+ var loc = location();
+
+ var dsuIdSuffix = hashWithKey(dsuId + loc.toString());
+ var suffix4 = dsuIdSuffix.substr(0, 4);
+ var suffix8 = dsuIdSuffix.substr(4);
+
+ dsuId = dsuId.substr(0, 19) + suffix4 + '-' + suffix8;
+
+ var daysSinceApEpoch = getDaysSinceApEpoch();
+ var originHash = hashWithKey(loc.origin);
+
+ var metadata = [
+ DSU_CREATOR.USERREPORT,
+ daysSinceApEpoch,
+ originHash
+ ].join('.');
+ var signature = hashWithKey(dsuId + metadata + SIGNATURE_SALT);
+
+ return [DSU_VERSION_NUMBER, signature, dsuId, metadata].join('.');
+ }
+
+ function readOrCreateDsu() {
+ var dsu;
+ try {
+ dsu = storage.getDataFromLocalStorage(DSU_KEY);
+ } catch (err) {
+ return null;
+ }
+
+ if (!dsu) {
+ dsu = generateDsu();
+ }
+
+ try {
+ storage.setDataInLocalStorage(DSU_KEY, dsu);
+ } catch (err) {
+ return null;
+ }
+
+ return dsu;
+ }
+
+ return {
+ readOrCreateDsu: readOrCreateDsu
+ }
+})();
+
+function serializeSizes(sizes) {
+ if (Array.isArray(sizes[0]) === false) {
+ sizes = [sizes];
+ }
+
+ return sizes.map(s => s[0] + 'x' + s[1]).join('_');
+}
+
+function getRawConsentString(gdprConsentConfig) {
+ if (!gdprConsentConfig || gdprConsentConfig.gdprApplies === false) {
+ return null;
+ }
+
+ return gdprConsentConfig.consentString;
+}
+
+function getConsentStringFromPrebid(gdprConsentConfig) {
+ const consentString = getRawConsentString(gdprConsentConfig);
+ if (!consentString) {
+ return null;
+ }
+
+ let isIab = config.getConfig('consentManagement.cmpApi') != 'static';
+ let vendorConsents = (
+ gdprConsentConfig.vendorData.vendorConsents ||
+ (gdprConsentConfig.vendorData.vendor || {}).consents ||
+ {}
+ );
+ let isConsentGiven = !!vendorConsents[CONSTANTS.GVLID.toString(10)];
+
+ return isIab && isConsentGiven ? consentString : null;
+}
+
+function getIabConsentString(bidderRequest) {
+ if (utils.deepAccess(bidderRequest, 'gdprConsent')) {
+ return getConsentStringFromPrebid(bidderRequest.gdprConsent);
+ }
+
+ return 'disabled';
+}
+
+function injectPixels(ad, pixels, scripts) {
+ if (!pixels && !scripts) {
+ return ad;
+ }
+
+ let trackedAd = ad;
+ if (pixels) {
+ pixels.forEach(pixel => {
+ const tracker = utils.createTrackPixelHtml(pixel);
+ trackedAd += tracker;
+ });
+ }
+
+ if (scripts) {
+ scripts.forEach(script => {
+ const tracker = ``;
+ trackedAd += tracker;
+ });
+ }
+
+ return trackedAd;
+}
+
+function getScreenParams() {
+ return `${window.screen.width}x${window.screen.height}@${window.devicePixelRatio}`;
+}
+
+function getBids(bids) {
+ const bidArr = bids.map(bid => {
+ const bidId = bid.bidId;
+
+ let mediaType = '';
+ const mediaTypes = Object.keys(bid.mediaTypes)
+ switch (mediaTypes[0]) {
+ case 'video':
+ mediaType = 'v';
+ break;
+
+ case 'native':
+ mediaType = 'n';
+ break;
+
+ case 'audio':
+ mediaType = 'a';
+ break;
+
+ default:
+ mediaType = 'b';
+ break;
+ }
+
+ let adUnitCode = `,c=${bid.adUnitCode}`;
+ if (bid.params.code) {
+ adUnitCode = `,c=${encodeURIComponent(bid.params.code)}`;
+ }
+ if (bid.params.adunitId) {
+ adUnitCode = `,u=${encodeURIComponent(bid.params.adunitId)}`;
+ }
+
+ return `${bidId}:t=${mediaType},s=${serializeSizes(bid.sizes)}${adUnitCode}`;
+ });
+
+ return bidArr.join(';');
+};
+
+function getEndpointsGroups(bidRequests) {
+ let endpoints = [];
+ const getEndpoint = bid => {
+ const publisherId = bid.params.publisherId || config.getConfig('apstream.publisherId');
+ const isTestConfig = bid.params.test || config.getConfig('apstream.test');
+
+ if (isTestConfig) {
+ return `https://mock-bapi.userreport.com/v2/${publisherId}/bid`;
+ }
+
+ if (bid.params.endpoint) {
+ return `${bid.params.endpoint}${publisherId}/bid`;
+ }
+
+ return `https://bapi.userreport.com/v2/${publisherId}/bid`;
+ }
+ bidRequests.forEach(bid => {
+ const endpoint = getEndpoint(bid);
+ const exist = endpoints.filter(item => item.endpoint.indexOf(endpoint) > -1)[0];
+ if (exist) {
+ exist.bids.push(bid);
+ } else {
+ endpoints.push({
+ endpoint: endpoint,
+ bids: [bid]
+ });
+ }
+ });
+
+ return endpoints;
+}
+
+function isBidRequestValid(bid) {
+ const publisherId = config.getConfig('apstream.publisherId');
+ const isPublisherIdExist = !!(publisherId || bid.params.publisherId);
+ const isOneMediaType = Object.keys(bid.mediaTypes).length === 1;
+
+ return isPublisherIdExist && isOneMediaType;
+}
+
+function buildRequests(bidRequests, bidderRequest) {
+ const data = {
+ med: encodeURIComponent(window.location.href),
+ auid: bidderRequest.auctionId,
+ ref: document.referrer,
+ dnt: utils.getDNT() ? 1 : 0,
+ sr: getScreenParams()
+ };
+
+ const consentData = getRawConsentString(bidderRequest.gdprConsent);
+ data.iab_consent = consentData;
+
+ const options = {
+ withCredentials: true
+ };
+
+ const isConsent = getIabConsentString(bidderRequest);
+ const noDsu = config.getConfig('apstream.noDsu');
+ if (!isConsent || noDsu) {
+ data.dsu = '';
+ } else {
+ data.dsu = dsuModule.readOrCreateDsu();
+ }
+
+ if (!isConsent || isConsent === 'disabled') {
+ options.withCredentials = false;
+ }
+
+ const endpoints = getEndpointsGroups(bidRequests);
+ const serverRequests = endpoints.map(item => ({
+ method: 'GET',
+ url: item.endpoint,
+ data: {
+ ...data,
+ bids: getBids(item.bids),
+ rnd: Math.random()
+ },
+ options: options
+ }));
+
+ return serverRequests;
+}
+
+function interpretResponse(serverResponse) {
+ let bidResponses = serverResponse && serverResponse.body;
+
+ if (!bidResponses || !bidResponses.length) {
+ return [];
+ }
+
+ return bidResponses.map(x => ({
+ requestId: x.bidId,
+ cpm: x.bidDetails.cpm,
+ width: x.bidDetails.width,
+ height: x.bidDetails.height,
+ creativeId: x.bidDetails.creativeId,
+ currency: x.bidDetails.currency || 'USD',
+ netRevenue: x.bidDetails.netRevenue,
+ dealId: x.bidDetails.dealId,
+ ad: injectPixels(x.bidDetails.ad, x.bidDetails.noticeUrls, x.bidDetails.impressionScripts),
+ ttl: x.bidDetails.ttl,
+ }));
+}
+
+export const spec = {
+ code: CONSTANTS.BIDDER_CODE,
+ gvlid: CONSTANTS.GVLID,
+ isBidRequestValid: isBidRequestValid,
+ buildRequests: buildRequests,
+ interpretResponse: interpretResponse
+}
+
+registerBidder(spec);
diff --git a/modules/apstreamBidAdapter.md b/modules/apstreamBidAdapter.md
new file mode 100644
index 00000000000..6b87b33489a
--- /dev/null
+++ b/modules/apstreamBidAdapter.md
@@ -0,0 +1,111 @@
+# Overview
+
+```
+Module Name: AP Stream Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: tech@audienceproject.com
+gdpr_supported: true
+tcf2_supported: true
+```
+
+# Description
+
+Module that connects to AP Stream source
+
+# Inherit from prebid.js
+```
+ var adUnits = [
+ {
+ code: '/19968336/header-bid-tag-1',
+ mediaTypes: { // mandatory and should be only one
+ banner: {
+ sizes: [[920,180], [920, 130]]
+ }
+ },
+ bids: [{
+ bidder: 'apstream',
+ params: {
+ publisherId: STREAM_PIBLISHER_ID // mandatory
+ }
+ }]
+ }
+ ];
+```
+
+# Explicit ad-unit code
+```
+ var website = null;
+ switch (location.hostname) {
+ case "site1.com":
+ website = "S1";
+ break;
+ case "site2.com":
+ website = "S2";
+ break;
+ }
+
+ var adUnits = [
+ {
+ code: '/19968336/header-bid-tag-1',
+ mediaTypes: { // mandatory and should be only one
+ banner: {
+ sizes: [[920,180], [920, 130]]
+ }
+ },
+ bids: [{
+ bidder: 'apstream',
+ params: {
+ publisherId: STREAM_PIBLISHER_ID, // mandatory
+ code: website + '_Leaderboard'
+ }
+ }]
+ }
+ ];
+```
+
+# Explicit ad-unit ID
+```
+ var adUnits = [
+ {
+ code: '/19968336/header-bid-tag-1',
+ mediaTypes: { // mandatory and should be only one
+ banner: {
+ sizes: [[920,180], [920, 130]]
+ }
+ },
+ bids: [{
+ bidder: 'apstream',
+ params: {
+ publisherId: STREAM_PIBLISHER_ID, // mandatory
+ adunitId: 1234
+ }
+ }]
+ }
+ ];
+```
+
+# DSU
+
+To disable DSU use config option:
+
+```
+ pbjs.setConfig({
+ apstream: {
+ noDsu: true
+ }
+ });
+```
+
+To set `test` and `publisherId` parameters globally use config options (it can be overrided if set in specific bid):
+
+```
+pbjs.setBidderConfig({
+ bidders: ["apstream"],
+ config: {
+ appstream: {
+ publisherId: '1234
+ test: true
+ }
+ }
+});
+```
diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js
index ad488aa50d9..9811c306738 100644
--- a/modules/atsAnalyticsAdapter.js
+++ b/modules/atsAnalyticsAdapter.js
@@ -165,7 +165,8 @@ atsAnalyticsAdapter.enableAnalytics = function (config) {
adaptermanager.registerAnalyticsAdapter({
adapter: atsAnalyticsAdapter,
- code: 'atsAnalytics'
+ code: 'atsAnalytics',
+ gvlid: 97
});
export default atsAnalyticsAdapter;
diff --git a/modules/audienceNetworkBidAdapter.js b/modules/audienceNetworkBidAdapter.js
deleted file mode 100644
index 816a6abd0e8..00000000000
--- a/modules/audienceNetworkBidAdapter.js
+++ /dev/null
@@ -1,308 +0,0 @@
-/**
- * @file AudienceNetwork adapter.
- */
-import { registerBidder } from '../src/adapters/bidderFactory.js';
-import { generateUUID, deepAccess, convertTypes, formatQS } from '../src/utils.js';
-import findIndex from 'core-js-pure/features/array/find-index.js';
-import includes from 'core-js-pure/features/array/includes.js';
-
-const code = 'audienceNetwork';
-const currency = 'USD';
-const method = 'GET';
-const url = 'https://an.facebook.com/v2/placementbid.json';
-const supportedMediaTypes = ['banner', 'video'];
-const netRevenue = true;
-const hbBidder = 'fan';
-const ttl = 600;
-const videoTtl = 3600;
-const platver = '$prebid.version$';
-const platform = '241394079772386';
-const adapterver = '1.3.0';
-
-/**
- * Does this bid request contain valid parameters?
- * @param {Object} bid
- * @returns {Boolean}
- */
-const isBidRequestValid = bid =>
- typeof bid.params === 'object' &&
- typeof bid.params.placementId === 'string' &&
- bid.params.placementId.length > 0 &&
- Array.isArray(bid.sizes) && bid.sizes.length > 0 &&
- (isFullWidth(bid.params.format) ? bid.sizes.map(flattenSize).some(size => size === '300x250') : true) &&
- (isValidNonSizedFormat(bid.params.format) || bid.sizes.map(flattenSize).some(isValidSize));
-
-/**
- * Flattens a 2-element [W, H] array as a 'WxH' string,
- * otherwise passes value through.
- * @param {Array|String} size
- * @returns {String}
- */
-const flattenSize = size =>
- (Array.isArray(size) && size.length === 2) ? `${size[0]}x${size[1]}` : size;
-
-/**
- * Expands a 'WxH' string as a 2-element [W, H] array
- * @param {String} size
- * @returns {Array}
- */
-const expandSize = size => size.split('x').map(Number);
-
-/**
- * Is this a valid slot size?
- * @param {String} size
- * @returns {Boolean}
- */
-const isValidSize = size => includes(['300x250', '320x50'], size);
-
-/**
- * Is this a valid, non-sized format?
- * @param {String} size
- * @returns {Boolean}
- */
-const isValidNonSizedFormat = format => includes(['video', 'native'], format);
-
-/**
- * Is this a valid size and format?
- * @param {String} size
- * @returns {Boolean}
- */
-const isValidSizeAndFormat = (size, format) =>
- (isFullWidth(format) && flattenSize(size) === '300x250') ||
- isValidNonSizedFormat(format) ||
- isValidSize(flattenSize(size));
-
-/**
- * Find a preferred entry, if any, from an array of valid sizes.
- * @param {Array} acc
- * @param {String} cur
- */
-const sortByPreferredSize = (acc, cur) =>
- (cur === '300x250') ? [cur, ...acc] : [...acc, cur];
-
-/**
- * Map any deprecated size/formats to new values.
- * @param {String} size
- * @param {String} format
- */
-const mapDeprecatedSizeAndFormat = (size, format) =>
- isFullWidth(format) ? ['300x250', null] : [size, format];
-
-/**
- * Is this a video format?
- * @param {String} format
- * @returns {Boolean}
- */
-const isVideo = format => format === 'video';
-
-/**
- * Is this a fullwidth format?
- * @param {String} format
- * @returns {Boolean}
- */
-const isFullWidth = format => format === 'fullwidth';
-
-/**
- * Which SDK version should be used for this format?
- * @param {String} format
- * @returns {String}
- */
-const sdkVersion = format => isVideo(format) ? '' : '6.0.web';
-
-/**
- * Which platform identifier should be used?
- * @param {Array} platforms Possible platform identifiers
- * @returns {String} First valid platform found, or default if none found
- */
-const findPlatform = platforms => [...platforms.filter(Boolean), platform][0];
-
-/**
- * Does the search part of the URL contain "anhb_testmode"
- * and therefore indicate testmode should be used?
- * @returns {String} "true" or "false"
- */
-const isTestmode = () => Boolean(
- window && window.location &&
- typeof window.location.search === 'string' &&
- window.location.search.indexOf('anhb_testmode') !== -1
-).toString();
-
-/**
- * Generate ad HTML for injection into an iframe
- * @param {String} placementId
- * @param {String} format
- * @param {String} bidId
- * @returns {String} HTML
- */
-const createAdHtml = (placementId, format, bidId) => {
- const nativeStyle = format === 'native' ? '' : '';
- const nativeContainer = format === 'native' ? '' : '';
- return `
- ${nativeStyle}
-
-
-
-
- ${nativeContainer}
-
-
-`;
-};
-
-/**
- * Convert each bid request to a single URL to fetch those bids.
- * @param {Array} bids - list of bids
- * @param {String} bids[].placementCode - Prebid placement identifier
- * @param {Object} bids[].params
- * @param {String} bids[].params.placementId - Audience Network placement identifier
- * @param {String} bids[].params.platform - Audience Network platform identifier (optional)
- * @param {String} bids[].params.format - Optional format, one of 'video' or 'native' if set
- * @param {Array} bids[].sizes - list of desired advert sizes
- * @param {Array} bids[].sizes[] - Size arrays [h,w]: should include one of [300, 250], [320, 50]
- * @returns {Array} List of URLs to fetch, plus formats and sizes for later use with interpretResponse
- */
-const buildRequests = (bids, bidderRequest) => {
- // Build lists of placementids, adformats, sizes and SDK versions
- const placementids = [];
- const adformats = [];
- const sizes = [];
- const sdk = [];
- const platforms = [];
- const requestIds = [];
-
- bids.forEach(bid => bid.sizes
- .map(flattenSize)
- .filter(size => isValidSizeAndFormat(size, bid.params.format))
- .reduce(sortByPreferredSize, [])
- .slice(0, 1)
- .forEach(preferredSize => {
- const [size, format] = mapDeprecatedSizeAndFormat(preferredSize, bid.params.format);
- placementids.push(bid.params.placementId);
- adformats.push(format || size);
- sizes.push(size);
- sdk.push(sdkVersion(format));
- platforms.push(bid.params.platform);
- requestIds.push(bid.bidId);
- })
- );
- // Build URL
- const testmode = isTestmode();
- const pageurl = encodeURIComponent(deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || deepAccess(bidderRequest, 'refererInfo.referer'));
- const platform = findPlatform(platforms);
- const cb = generateUUID();
- const search = {
- placementids,
- adformats,
- testmode,
- pageurl,
- sdk,
- adapterver,
- platform,
- platver,
- cb
- };
- const video = findIndex(adformats, isVideo);
- if (video !== -1) {
- [search.playerwidth, search.playerheight] = expandSize(sizes[video]);
- }
- const data = formatQS(search);
-
- return [{ adformats, data, method, requestIds, sizes, url, pageurl }];
-};
-
-/**
- * Convert a server response to a bid response.
- * @param {Object} response - object representing the response
- * @param {Object} response.body - response body, already converted from JSON
- * @param {Object} bidRequests - the original bid requests
- * @param {Array} bidRequest.adformats - list of formats for the original bid requests
- * @param {Array} bidRequest.sizes - list of sizes fot the original bid requests
- * @returns {Array} A list of bid response objects
- */
-const interpretResponse = ({ body }, { adformats, requestIds, sizes, pageurl }) => {
- const { bids = {} } = body;
- return Object.keys(bids)
- // extract Array of bid responses
- .map(placementId => bids[placementId])
- // flatten
- .reduce((a, b) => a.concat(b), [])
- // transform to bidResponse
- .map((bid, i) => {
- const {
- bid_id: fbBidid,
- placement_id: creativeId,
- bid_price_cents: cpm
- } = bid;
-
- const format = adformats[i];
- const [width, height] = expandSize(flattenSize(sizes[i]));
- const ad = createAdHtml(creativeId, format, fbBidid);
- const requestId = requestIds[i];
-
- const bidResponse = {
- // Prebid attributes
- requestId,
- cpm: cpm / 100,
- width,
- height,
- ad,
- ttl,
- creativeId,
- netRevenue,
- currency,
- // Audience Network attributes
- hb_bidder: hbBidder,
- fb_bidid: fbBidid,
- fb_format: format,
- fb_placementid: creativeId
- };
- // Video attributes
- if (isVideo(format)) {
- bidResponse.mediaType = 'video';
- bidResponse.vastUrl = `https://an.facebook.com/v1/instream/vast.xml?placementid=${creativeId}&pageurl=${pageurl}&playerwidth=${width}&playerheight=${height}&bidid=${fbBidid}`;
- bidResponse.ttl = videoTtl;
- }
- return bidResponse;
- });
-};
-
-/**
- * Covert bid param types for S2S
- * @param {Object} params bid params
- * @param {Boolean} isOpenRtb boolean to check openrtb2 protocol
- * @return {Object} params bid params
- */
-const transformBidParams = (params, isOpenRtb) => {
- return convertTypes({
- 'placementId': 'string'
- }, params);
-}
-
-export const spec = {
- code,
- supportedMediaTypes,
- isBidRequestValid,
- buildRequests,
- interpretResponse,
- transformBidParams
-};
-
-registerBidder(spec);
diff --git a/modules/audienceNetworkBidAdapter.md b/modules/audienceNetworkBidAdapter.md
deleted file mode 100644
index 6147191f4b7..00000000000
--- a/modules/audienceNetworkBidAdapter.md
+++ /dev/null
@@ -1,49 +0,0 @@
-# Overview
-
-Module Name: Audience Network Bid Adapter
-
-Module Type: Bidder Adapter
-
-Maintainer: Lovell Fuller
-
-# Parameters
-
-| Name | Scope | Description | Example |
-| :------------ | :------- | :---------------------------------------------- | :--------------------------------- |
-| `placementId` | required | The Placement ID from Audience Network | "555555555555555\_555555555555555" |
-| `format` | optional | Format, one of "native" or "video" | "native" |
-
-# Example ad units
-
-```javascript
-const adUnits = [{
- code: "test-iab",
- sizes: [[300, 250]],
- bids: [{
- bidder: "audienceNetwork",
- params: {
- placementId: "555555555555555_555555555555555"
- }
- }]
-}, {
- code: "test-native",
- sizes: [[300, 250]],
- bids: [{
- bidder: "audienceNetwork",
- params: {
- format: "native",
- placementId: "555555555555555_555555555555555"
- }
- }]
-}, {
- code: "test-video",
- sizes: [[640, 360]],
- bids: [{
- bidder: "audienceNetwork",
- params: {
- format: "video",
- placementId: "555555555555555_555555555555555"
- }
- }]
-}];
-```
diff --git a/modules/automatadBidAdapter.js b/modules/automatadBidAdapter.js
index 95d225cb5f7..e1a69a37513 100644
--- a/modules/automatadBidAdapter.js
+++ b/modules/automatadBidAdapter.js
@@ -27,17 +27,20 @@ export const spec = {
}
const siteId = validBidRequests[0].params.siteId
- const placementId = validBidRequests[0].params.placementId
- const impressions = validBidRequests.map(bidRequest => ({
- id: bidRequest.bidId,
- banner: {
- format: bidRequest.sizes.map(sizeArr => ({
- w: sizeArr[0],
- h: sizeArr[1],
- }))
- },
- }))
+ const impressions = validBidRequests.map(bidRequest => {
+ return {
+ id: bidRequest.bidId,
+ adUnitCode: bidRequest.adUnitCode,
+ placement: bidRequest.params.placementId,
+ banner: {
+ format: bidRequest.sizes.map(sizeArr => ({
+ w: sizeArr[0],
+ h: sizeArr[1],
+ }))
+ },
+ }
+ })
// params from bid request
const openrtbRequest = {
@@ -45,7 +48,6 @@ export const spec = {
imp: impressions,
site: {
id: siteId,
- placement: placementId,
domain: window.location.hostname,
page: window.location.href,
ref: bidderRequest.refererInfo ? bidderRequest.refererInfo.referer || null : null,
diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js
index 4d40d931e1d..afacc48aa8e 100644
--- a/modules/bluebillywigBidAdapter.js
+++ b/modules/bluebillywigBidAdapter.js
@@ -1,4 +1,5 @@
import * as utils from '../src/utils.js';
+import find from 'core-js-pure/features/array/find.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { VIDEO } from '../src/mediaTypes.js';
import { config } from '../src/config.js';
@@ -17,7 +18,10 @@ const BB_CONSTANTS = {
DEFAULT_TTL: 300,
DEFAULT_WIDTH: 768,
DEFAULT_HEIGHT: 432,
- DEFAULT_NET_REVENUE: true
+ DEFAULT_NET_REVENUE: true,
+ VIDEO_PARAMS: ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin',
+ 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad',
+ 'api', 'companiontype', 'ext']
};
// Aliasing
@@ -26,8 +30,6 @@ const getConfig = config.getConfig;
// Helper Functions
export const BB_HELPERS = {
addSiteAppDevice: function(request, pageUrl) {
- if (!request) return;
-
if (typeof getConfig('app') === 'object') request.app = getConfig('app');
else {
request.site = {};
@@ -41,21 +43,15 @@ export const BB_HELPERS = {
if (!request.device.h) request.device.h = window.innerHeight;
},
addSchain: function(request, validBidRequests) {
- if (!request) return;
-
const schain = utils.deepAccess(validBidRequests, '0.schain');
if (schain) request.source.ext = { schain: schain };
},
addCurrency: function(request) {
- if (!request) return;
-
const adServerCur = getConfig('currency.adServerCurrency');
if (adServerCur && typeof adServerCur === 'string') request.cur = [adServerCur];
else if (Array.isArray(adServerCur) && adServerCur.length) request.cur = [adServerCur[0]];
},
addUserIds: function(request, validBidRequests) {
- if (!request) return;
-
const bidUserId = utils.deepAccess(validBidRequests, '0.userId');
const eids = createEidsArray(bidUserId);
@@ -63,10 +59,6 @@ export const BB_HELPERS = {
utils.deepSetValue(request, 'user.ext.eids', eids);
}
},
- addDigiTrust: function(request, bidRequests) {
- const digiTrust = BB_HELPERS.getDigiTrustParams(bidRequests && bidRequests[0]);
- if (digiTrust) utils.deepSetValue(request, 'user.ext.digitrust', digiTrust);
- },
substituteUrl: function (url, publication, renderer) {
return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer);
},
@@ -79,41 +71,59 @@ export const BB_HELPERS = {
getRendererUrl: function(publication, renderer) {
return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer);
},
- getDigiTrustParams: function(bidRequest) {
- const digiTrustId = BB_HELPERS.getDigiTrustId(bidRequest);
+ transformVideoParams: function(videoParams, videoParamsExt) {
+ videoParams = utils.deepClone(videoParams);
- if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) return null;
- return {
- id: digiTrustId.id,
- keyv: digiTrustId.keyv
- }
- },
- getDigiTrustId: function(bidRequest) {
- const bidRequestDigiTrust = utils.deepAccess(bidRequest, 'userId.digitrustid.data');
- if (bidRequestDigiTrust) return bidRequestDigiTrust;
+ let playerSize = videoParams.playerSize || [BB_CONSTANTS.DEFAULT_WIDTH, BB_CONSTANTS.DEFAULT_HEIGHT];
+ if (Array.isArray(playerSize[0])) playerSize = playerSize[0];
+
+ videoParams.w = playerSize[0];
+ videoParams.h = playerSize[1];
+ videoParams.placement = 3;
+
+ if (videoParamsExt) videoParams = Object.assign(videoParams, videoParamsExt);
+
+ const videoParamsProperties = Object.keys(videoParams);
- const digiTrustUser = getConfig('digiTrustId');
- return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null;
+ videoParamsProperties.forEach(property => {
+ if (BB_CONSTANTS.VIDEO_PARAMS.indexOf(property) === -1) delete videoParams[property];
+ });
+
+ return videoParams;
},
transformRTBToPrebidProps: function(bid, serverResponse) {
- bid.cpm = bid.price; delete bid.price;
- bid.bidId = bid.impid;
- bid.requestId = bid.impid; delete bid.impid;
- bid.width = bid.w || BB_CONSTANTS.DEFAULT_WIDTH;
- bid.height = bid.h || BB_CONSTANTS.DEFAULT_HEIGHT;
+ const bidObject = {
+ cpm: bid.price,
+ currency: serverResponse.cur,
+ netRevenue: BB_CONSTANTS.DEFAULT_NET_REVENUE,
+ bidId: bid.impid,
+ requestId: bid.impid,
+ creativeId: bid.crid,
+ mediaType: VIDEO,
+ width: bid.w || BB_CONSTANTS.DEFAULT_WIDTH,
+ height: bid.h || BB_CONSTANTS.DEFAULT_HEIGHT,
+ ttl: BB_CONSTANTS.DEFAULT_TTL
+ };
+
+ const extPrebidTargeting = utils.deepAccess(bid, 'ext.prebid.targeting');
+ const extPrebidCache = utils.deepAccess(bid, 'ext.prebid.cache');
+
+ if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) {
+ bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId;
+ bidObject.vastUrl = extPrebidCache.vastXml.url;
+ } else if (extPrebidTargeting && extPrebidTargeting.hb_uuid && extPrebidTargeting.hb_cache_host && extPrebidTargeting.hb_cache_path) {
+ bidObject.videoCacheKey = extPrebidTargeting.hb_uuid;
+ bidObject.vastUrl = `https://${extPrebidTargeting.hb_cache_host}${extPrebidTargeting.hb_cache_path}?uuid=${extPrebidTargeting.hb_uuid}`;
+ }
if (bid.adm) {
- bid.ad = bid.adm;
- bid.vastXml = bid.adm;
- delete bid.adm;
+ bidObject.ad = bid.adm;
+ bidObject.vastXml = bid.adm;
}
- if (bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5
- bid.vastUrl = bid.nurl;
- delete bid.nurl;
+ if (!bidObject.vastUrl && bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5
+ bidObject.vastUrl = bid.nurl;
}
- bid.netRevenue = BB_CONSTANTS.DEFAULT_NET_REVENUE;
- bid.creativeId = bid.crid; delete bid.crid;
- bid.currency = serverResponse.cur;
- bid.ttl = BB_CONSTANTS.DEFAULT_TTL;
+
+ return bidObject;
},
};
@@ -132,18 +142,14 @@ const BB_RENDERER = {
return;
}
- const rendererId = BB_RENDERER.getRendererId(bid.publicationName, bid.rendererCode);
+ if (!(window.bluebillywig && window.bluebillywig.renderers)) {
+ utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: renderer code failed to initialize...`);
+ return;
+ }
+ const rendererId = BB_RENDERER.getRendererId(bid.publicationName, 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;
- }
- }
+ const renderer = find(window.bluebillywig.renderers, r => r._id === rendererId);
if (renderer) renderer.bootstrap(config, ele);
else utils.logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`);
@@ -212,9 +218,8 @@ export const spec = {
utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid);
return false;
} else {
- for (let connectionIndex = 0; connectionIndex < bid.params.connections.length; connectionIndex++) {
- const connection = bid.params.connections[connectionIndex];
- if (!bid.params.hasOwnProperty(connection)) {
+ for (let i = 0; i < bid.params.connections.length; i++) {
+ if (!bid.params.hasOwnProperty(bid.params.connections[i])) {
utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid);
return false;
}
@@ -225,6 +230,11 @@ export const spec = {
return false;
}
+ if (bid.params.hasOwnProperty('video') && (bid.params.video === null || typeof bid.params.video !== 'object')) {
+ utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: params.video must be of type object. Rejecting bid: `, bid);
+ return false;
+ }
+
if (bid.hasOwnProperty('mediaTypes') && bid.mediaTypes.hasOwnProperty(VIDEO)) {
if (!bid.mediaTypes[VIDEO].hasOwnProperty('context')) {
utils.logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid);
@@ -245,20 +255,21 @@ export const spec = {
buildRequests(validBidRequests, bidderRequest) {
const imps = [];
- for (let validBidRequestIndex = 0; validBidRequestIndex < validBidRequests.length; validBidRequestIndex++) {
- const validBidRequest = validBidRequests[validBidRequestIndex];
- const _this = this;
+ validBidRequests.forEach(validBidRequest => {
+ if (!this.syncStore.publicationName) this.syncStore.publicationName = validBidRequest.params.publicationName;
+ if (!this.syncStore.accountId) this.syncStore.accountId = validBidRequest.params.accountId;
- const ext = validBidRequest.params.connections.reduce(function(extBuilder, connection) {
+ const ext = validBidRequest.params.connections.reduce((extBuilder, connection) => {
extBuilder[connection] = validBidRequest.params[connection];
- if (_this.syncStore.bidders.indexOf(connection) === -1) _this.syncStore.bidders.push(connection);
+ if (this.syncStore.bidders.indexOf(connection) === -1) this.syncStore.bidders.push(connection);
return extBuilder;
}, {});
- imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: utils.deepAccess(validBidRequest, 'mediaTypes.video') });
- }
+ const videoParams = BB_HELPERS.transformVideoParams(utils.deepAccess(validBidRequest, 'mediaTypes.video'), utils.deepAccess(validBidRequest, 'params.video'));
+ imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams });
+ });
const request = {
id: bidderRequest.auctionId,
@@ -293,7 +304,6 @@ export const spec = {
BB_HELPERS.addSchain(request, validBidRequests);
BB_HELPERS.addCurrency(request);
BB_HELPERS.addUserIds(request, validBidRequests);
- BB_HELPERS.addDigiTrust(request, validBidRequests);
return {
method: 'POST',
@@ -311,74 +321,43 @@ export const spec = {
const bids = [];
- for (let seatbidIndex = 0; seatbidIndex < serverResponse.seatbid.length; seatbidIndex++) {
- const seatbid = serverResponse.seatbid[seatbidIndex];
- if (!seatbid.bid || !Array.isArray(seatbid.bid)) continue;
- for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) {
- const bid = seatbid.bid[bidIndex];
- BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse);
-
- let bidParams;
- for (let bidderRequestBidsIndex = 0; bidderRequestBidsIndex < request.bidderRequest.bids.length; bidderRequestBidsIndex++) {
- if (request.bidderRequest.bids[bidderRequestBidsIndex].bidId === bid.bidId) {
- bidParams = request.bidderRequest.bids[bidderRequestBidsIndex].params;
- }
- }
+ serverResponse.seatbid.forEach(seatbid => {
+ if (!seatbid.bid || !Array.isArray(seatbid.bid)) return;
+ seatbid.bid.forEach(bid => {
+ bid = BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse);
- if (bidParams) {
- bid.publicationName = bidParams.publicationName;
- bid.rendererCode = bidParams.rendererCode;
- bid.accountId = bidParams.accountId;
- }
+ const bidParams = find(request.bidderRequest.bids, bidderRequestBid => bidderRequestBid.bidId === bid.bidId).params;
+ bid.publicationName = bidParams.publicationName;
+ bid.rendererCode = bidParams.rendererCode;
+ bid.accountId = bidParams.accountId;
const rendererUrl = BB_HELPERS.getRendererUrl(bid.publicationName, bid.rendererCode);
-
bid.renderer = BB_RENDERER.newRenderer(rendererUrl, bid.adUnitCode);
bids.push(bid);
- }
- }
+ });
+ });
return bids;
},
getUserSyncs(syncOptions, serverResponses, gdpr) {
- if (!serverResponses || !serverResponses.length) return [];
if (!syncOptions.iframeEnabled) return [];
const queryString = [];
- let accountId;
- let publication;
-
- const serverResponse = serverResponses[0];
- if (!serverResponse.body || !serverResponse.body.seatbid) return [];
-
- for (let seatbidIndex = 0; seatbidIndex < serverResponse.body.seatbid.length; seatbidIndex++) {
- const seatbid = serverResponse.body.seatbid[seatbidIndex];
- for (let bidIndex = 0; bidIndex < seatbid.bid.length; bidIndex++) {
- const bid = seatbid.bid[bidIndex];
- accountId = bid.accountId || null;
- publication = bid.publicationName || null;
-
- if (publication && accountId) break;
- }
- if (publication && accountId) break;
- }
-
- if (!publication || !accountId) return [];
if (gdpr.gdprApplies) queryString.push(`gdpr=${gdpr.gdprApplies ? 1 : 0}`);
if (gdpr.gdprApplies && gdpr.consentString) queryString.push(`gdpr_consent=${gdpr.consentString}`);
if (this.syncStore.uspConsent) queryString.push(`usp_consent=${this.syncStore.uspConsent}`);
- queryString.push(`accountId=${accountId}`);
+ queryString.push(`accountId=${this.syncStore.accountId}`);
queryString.push(`bidders=${btoa(JSON.stringify(this.syncStore.bidders))}`);
queryString.push(`cb=${Date.now()}-${Math.random().toString().replace('.', '')}`);
if (DEV_MODE) queryString.push('bbpbs_debug=true');
// NB syncUrl by default starts with ?pub=$$PUBLICATION
- const syncUrl = `${BB_HELPERS.getSyncUrl(publication)}&${queryString.join('&')}`;
+ const syncUrl = `${BB_HELPERS.getSyncUrl(this.syncStore.publicationName)}&${queryString.join('&')}`;
return [{
type: 'iframe',
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/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js
index a4b013a2fe2..2aad211b186 100644
--- a/modules/brightcomBidAdapter.js
+++ b/modules/brightcomBidAdapter.js
@@ -70,6 +70,11 @@ function buildRequests(bidReqs, bidderRequest) {
tmax: config.getConfig('bidderTimeout')
};
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ utils.deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies);
+ utils.deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
+ }
+
return {
method: 'POST',
url: URL,
diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md
index 89287aed7ca..a15f601aee3 100644
--- a/modules/britepoolIdSystem.md
+++ b/modules/britepoolIdSystem.md
@@ -7,7 +7,7 @@ BritePool User ID Module. For assistance setting up your module please contact u
Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params.
```
pbjs.setConfig({
- usersync: {
+ userSync: {
userIds: [{
name: 'britepoolId',
storage: {
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/cointrafficBidAdapter.js b/modules/cointrafficBidAdapter.js
new file mode 100644
index 00000000000..aa6860d1fc6
--- /dev/null
+++ b/modules/cointrafficBidAdapter.js
@@ -0,0 +1,81 @@
+import * as utils from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER} from '../src/mediaTypes.js'
+
+const BIDDER_CODE = 'cointraffic';
+const ENDPOINT_URL = 'https://appspb.cointraffic.io/pb/tmp';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER],
+
+ /**
+ * 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) {
+ return !!(bid.params.placementId);
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param validBidRequests
+ * @param bidderRequest
+ * @return Array Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ return validBidRequests.map(bidRequest => {
+ const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes);
+
+ const payload = {
+ placementId: bidRequest.params.placementId,
+ sizes: sizes,
+ bidId: bidRequest.bidId,
+ referer: bidderRequest.refererInfo.referer,
+ };
+
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: payload
+ };
+ });
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @param bidRequest
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function (serverResponse, bidRequest) {
+ const bidResponses = [];
+ const response = serverResponse.body;
+
+ if (utils.isEmpty(response)) {
+ return bidResponses;
+ }
+
+ const bidResponse = {
+ requestId: response.requestId,
+ cpm: response.cpm,
+ currency: response.currency,
+ netRevenue: response.netRevenue,
+ width: response.width,
+ height: response.height,
+ creativeId: response.creativeId,
+ ttl: response.ttl,
+ ad: response.ad
+ };
+
+ bidResponses.push(bidResponse);
+
+ return bidResponses;
+ }
+};
+
+registerBidder(spec);
diff --git a/modules/cointrafficBidAdapter.md b/modules/cointrafficBidAdapter.md
new file mode 100644
index 00000000000..ad608a1319e
--- /dev/null
+++ b/modules/cointrafficBidAdapter.md
@@ -0,0 +1,28 @@
+# Overview
+
+```
+Module Name: Cointraffic Bidder Adapter
+Module Type: Cointraffic Adapter
+Maintainer: tech@cointraffic.io
+```
+
+# Description
+The Cointraffic client module makes it easy to implement Cointraffic directly into your website. To get started, simply replace the ``placementId`` with your assigned tracker key. This is dependent on the size required by your account dashboard. For additional information on this module, please contact us at ``support@cointraffic.io``.
+
+# Test Parameters
+```
+ var adUnits = [{
+ code: 'test-ad-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]],
+ }
+ },
+ bids: [{
+ bidder: 'cointraffic',
+ params: {
+ placementId: 'testPlacementId'
+ }
+ }]
+ }];
+```
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 3dcb8da9838..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,
- }]
- });
- }
-
- 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 53e97006bd1..f44fde0554d 100644
--- a/modules/consentManagement.js
+++ b/modules/consentManagement.js
@@ -14,9 +14,12 @@ const DEFAULT_CMP = 'iab';
const DEFAULT_CONSENT_TIMEOUT = 10000;
const DEFAULT_ALLOW_AUCTION_WO_CONSENT = true;
+export const allowAuction = {
+ value: DEFAULT_ALLOW_AUCTION_WO_CONSENT,
+ definedInConfig: false
+}
export let userCMP;
export let consentTimeout;
-export let allowAuction;
export let gdprScope;
export let staticConsentData;
@@ -97,9 +100,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
function v2CmpResponseCallback(tcfData, success) {
utils.logInfo('Received a response from CMP', tcfData);
if (success) {
- if (tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') {
- cmpSuccess(tcfData, hookConfig);
- } else if (tcfData.eventStatus === 'cmpuishown' && tcfData.tcString && tcfData.purposeOneTreatment === true) {
+ if (tcfData.gdprApplies === false || tcfData.eventStatus === 'tcloaded' || tcfData.eventStatus === 'useractioncomplete') {
cmpSuccess(tcfData, hookConfig);
}
} else {
@@ -220,7 +221,7 @@ function lookupIabConsent(cmpSuccess, cmpError, hookConfig) {
window.addEventListener('message', readPostMessageResponse, false);
// call CMP
- window[apiName](commandName, null, moduleCallback);
+ window[apiName](commandName, undefined, moduleCallback);
function readPostMessageResponse(event) {
let cmpDataPkgName = `${apiName}Return`;
@@ -322,6 +323,13 @@ function processCmpData(consentObject, hookConfig) {
// determine which set of checks to run based on cmpVersion
let checkFn = (cmpVersion === 1) ? checkV1Data : (cmpVersion === 2) ? checkV2Data : null;
+ // Raise deprecation warning if 'allowAuctionWithoutConsent' is used with TCF 2.
+ if (allowAuction.definedInConfig && cmpVersion === 2) {
+ utils.logWarn(`'allowAuctionWithoutConsent' ignored for TCF 2`);
+ } else if (!allowAuction.definedInConfig && cmpVersion === 1) {
+ utils.logInfo(`'allowAuctionWithoutConsent' using system default: (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`);
+ }
+
if (utils.isFn(checkFn)) {
if (checkFn(consentObject)) {
cmpFailed(`CMP returned unexpected value during lookup process.`, hookConfig, consentObject);
@@ -352,14 +360,14 @@ function cmpFailed(errMsg, hookConfig, extraArgs) {
clearTimeout(hookConfig.timer);
// still set the consentData to undefined when there is a problem as per config options
- if (allowAuction) {
+ if (allowAuction.value && cmpVersion === 1) {
storeConsentData(undefined);
}
exitModule(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) {
@@ -375,6 +383,9 @@ function storeConsentData(cmpConsentObject) {
vendorData: (cmpConsentObject) || undefined,
gdprApplies: cmpConsentObject && typeof cmpConsentObject.gdprApplies === 'boolean' ? cmpConsentObject.gdprApplies : gdprScope
};
+ if (cmpConsentObject && cmpConsentObject.addtlConsent && utils.isStr(cmpConsentObject.addtlConsent)) {
+ consentData.addtlConsent = cmpConsentObject.addtlConsent;
+ };
}
consentData.apiVersion = cmpVersion;
gdprDataHandler.setConsentData(consentData);
@@ -406,8 +417,8 @@ function exitModule(errMsg, hookConfig, extraArgs) {
let nextFn = hookConfig.nextFn;
if (errMsg) {
- if (allowAuction) {
- utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs);
+ if (allowAuction.value && cmpVersion === 1) {
+ utils.logWarn(errMsg + ` 'allowAuctionWithoutConsent' activated.`, extraArgs);
nextFn.apply(context, args);
} else {
utils.logError(errMsg + ' Canceling auction as per consentManagement config.', extraArgs);
@@ -460,10 +471,8 @@ export function setConsentConfig(config) {
}
if (typeof config.allowAuctionWithoutConsent === 'boolean') {
- allowAuction = config.allowAuctionWithoutConsent;
- } else {
- allowAuction = DEFAULT_ALLOW_AUCTION_WO_CONSENT;
- utils.logInfo(`consentManagement config did not specify allowAuctionWithoutConsent. Using system default setting (${DEFAULT_ALLOW_AUCTION_WO_CONSENT}).`);
+ allowAuction.value = config.allowAuctionWithoutConsent;
+ allowAuction.definedInConfig = true;
}
// if true, then gdprApplies should be set to true
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/decenteradsBidAdapter.js b/modules/decenteradsBidAdapter.js
new file mode 100644
index 00000000000..823a59a3768
--- /dev/null
+++ b/modules/decenteradsBidAdapter.js
@@ -0,0 +1,90 @@
+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 = 'decenterads'
+const URL = 'https://supply.decenterads.com/?c=o&m=multi'
+const URL_SYNC = 'https://supply.decenterads.com/?c=o&m=cookie'
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO, NATIVE],
+
+ isBidRequestValid: function (opts) {
+ return Boolean(opts.bidId && opts.params && !isNaN(opts.params.placementId))
+ },
+
+ buildRequests: function (validBidRequests) {
+ validBidRequests = validBidRequests || []
+ let winTop = window
+ try {
+ window.top.location.toString()
+ winTop = window.top
+ } catch (e) { utils.logMessage(e) }
+
+ const location = utils.getWindowLocation()
+ const placements = []
+
+ for (let i = 0; i < validBidRequests.length; i++) {
+ const p = validBidRequests[i]
+
+ placements.push({
+ placementId: p.params.placementId,
+ bidId: p.bidId,
+ traffic: p.params.traffic || BANNER
+ })
+ }
+
+ return {
+ method: 'POST',
+ url: URL,
+ data: {
+ deviceWidth: winTop.screen.width,
+ deviceHeight: winTop.screen.height,
+ language: (navigator && navigator.language) ? navigator.language : '',
+ secure: +(location.protocol === 'https:'),
+ host: location.hostname,
+ page: location.pathname,
+ placements: placements
+ }
+ }
+ },
+
+ interpretResponse: function (opts) {
+ const body = opts.body
+ const response = []
+
+ for (let i = 0; i < body.length; i++) {
+ const item = body[i]
+ if (isBidResponseValid(item)) {
+ delete item.mediaType
+ response.push(item)
+ }
+ }
+
+ return response
+ },
+
+ getUserSyncs: function (syncOptions, serverResponses) {
+ return [{ type: 'image', url: URL_SYNC }]
+ }
+}
+
+registerBidder(spec)
+
+function isBidResponseValid (bid) {
+ if (!bid.requestId || !bid.cpm || !bid.creativeId ||
+ !bid.ttl || !bid.currency) {
+ return false
+ }
+ switch (bid['mediaType']) {
+ case BANNER:
+ return Boolean(bid.width && bid.height && bid.ad)
+ case VIDEO:
+ return Boolean(bid.vastUrl)
+ case NATIVE:
+ return Boolean(bid.title && bid.image && bid.impressionTrackers)
+ default:
+ return false
+ }
+}
diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js
index 71a8471d554..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,
};
@@ -82,7 +82,7 @@ export function buildDfpVideoUrl(options) {
const derivedParams = {
correlator: Date.now(),
- sz: parseSizesInput(adUnit.sizes).join('|'),
+ sz: parseSizesInput(deepAccess(adUnit, 'mediaTypes.video.playerSize')).join('|'),
url: encodeURIComponent(location.href),
};
const encodedCustomParams = getCustParams(bid, options);
diff --git a/modules/digiTrustIdSystem.js b/modules/digiTrustIdSystem.js
deleted file mode 100644
index d8aa8be9376..00000000000
--- a/modules/digiTrustIdSystem.js
+++ /dev/null
@@ -1,460 +0,0 @@
-/**
- * This module adds DigiTrust ID support to the User ID module
- * The {@link module:modules/userId} module is required
- * If the full DigiTrust Id library is included the standard functions
- * will be invoked to obtain the user's DigiTrust Id.
- * When the full library is not included this will fall back to the
- * DigiTrust Identity API and generate a mock DigiTrust object.
- * @module modules/digiTrustIdSystem
- * @requires module:modules/userId
- */
-
-import * as utils from '../src/utils.js'
-import { ajax } from '../src/ajax.js';
-import { submodule } from '../src/hook.js';
-import { getStorageManager } from '../src/storageManager.js';
-
-const DT_VENDOR_ID = 64; // cmp gvlVendorId
-const storage = getStorageManager(DT_VENDOR_ID);
-
-var fallbackTimeout = 1550; // timeout value that allows userId system to execute first
-var fallbackTimer = 0; // timer Id for fallback init so we don't double call
-
-/**
- * Checks to see if the DigiTrust framework is initialized.
- * @function
- */
-function isInitialized() {
- if (window.DigiTrust == null) {
- return false;
- }
- // eslint-disable-next-line no-undef
- return DigiTrust.isClient; // this is set to true after init
-}
-
-/**
- * Tests for presence of the DigiTrust object
- * */
-function isPresent() {
- return (window.DigiTrust != null);
-}
-
-var noop = function () {
-};
-
-const MAX_RETRIES = 2;
-const DT_ID_SVC = 'https://prebid.digitru.st/id/v1';
-
-var isFunc = function (fn) {
- return typeof (fn) === 'function';
-}
-
-var _savedId = null; // closure variable for storing Id to avoid additional requests
-
-function callApi(options) {
- var ajaxOptions = {
- method: 'GET',
- withCredentials: true
- };
-
- ajax(
- DT_ID_SVC,
- {
- success: options.success,
- error: options.fail
- },
- null,
- ajaxOptions
- );
-}
-
-/**
- * Encode the Id per DigiTrust lib
- * @param {any} id
- */
-function encId(id) {
- try {
- if (typeof (id) !== 'string') {
- id = JSON.stringify(id);
- }
- return btoa(id);
- } catch (ex) {
- return id;
- }
-}
-
-/**
- * Writes the Identity into the expected DigiTrust cookie
- * @param {any} id
- */
-function writeDigiId(id) {
- var key = 'DigiTrust.v1.identity';
- var date = new Date();
- date.setTime(date.getTime() + 604800000);
- storage.setCookie(key, encId(id), date.toUTCString(), 'none');
-}
-
-/**
- * Tests to see if the current browser is FireFox
- */
-function isFirefoxBrowser(ua) {
- ua = ua || navigator.userAgent;
- ua = ua.toLowerCase();
- if (ua.indexOf('firefox') !== -1) {
- return true;
- }
- return false;
-}
-
-/**
- * Test to see if the user has a browser that is disallowed for making AJAX
- * requests due to the behavior not supported DigiTrust ID Cookie.
- */
-function isDisallowedBrowserForApiCall() {
- if (utils.isSafariBrowser()) {
- return true;
- } else if (isFirefoxBrowser()) {
- return true;
- }
- return false;
-}
-
-/**
- * Set up a DigiTrust facade object to mimic the API
- *
- */
-function initDigitrustFacade(config) {
- clearTimeout(fallbackTimer);
- fallbackTimer = 0;
-
- var facade = {
- isClient: true,
- isMock: true,
- _internals: {
- callCount: 0,
- initCallback: null
- },
- getUser: function (obj, callback) {
- var isAsync = !!isFunc(callback);
- var cb = isAsync ? callback : noop;
- var errResp = { success: false };
- var inter = facade._internals;
- inter.callCount++;
-
- // wrap the initializer callback, if present
- var checkAndCallInitializeCb = function (idResponse) {
- if (inter.callCount <= 1 && isFunc(inter.initCallback)) {
- try {
- inter.initCallback(idResponse);
- } catch (ex) {
- utils.logError('Exception in passed DigiTrust init callback', ex);
- }
- }
- }
-
- if (!isMemberIdValid(obj.member)) {
- if (!isAsync) {
- return errResp
- } else {
- cb(errResp);
- return;
- }
- }
-
- if (_savedId != null) {
- if (isAsync) {
- checkAndCallInitializeCb(_savedId);
- // cb(_savedId);
- return;
- } else {
- return _savedId;
- }
- }
-
- var opts = {
- success: function (respText, result) {
- var idResult = {
- success: true
- }
- try {
- idResult.identity = JSON.parse(respText);
- _savedId = idResult; // Save result to the cache variable
- writeDigiId(respText);
- } catch (ex) {
- idResult.success = false;
- delete idResult.identity;
- }
- checkAndCallInitializeCb(idResult);
- },
- fail: function (statusErr, result) {
- utils.logError('DigiTrustId API error: ' + statusErr);
- }
- }
-
- // check gdpr vendor here. Full DigiTrust library has vendor check built in
- gdprConsent.hasConsent(null, function (hasConsent) {
- if (hasConsent) {
- if (isDisallowedBrowserForApiCall()) {
- let resultObj = {
- success: false,
- err: 'Your browser does not support DigiTrust Identity'
- }
- checkAndCallInitializeCb(resultObj);
- return;
- }
- callApi(opts);
- }
- })
-
- if (!isAsync) {
- return errResp; // even if it will be successful later, without a callback we report a "failure in this moment"
- }
- }
- }
-
- if (config && isFunc(config.callback)) {
- facade._internals.initCallback = config.callback;
- }
-
- if (window && window.DigiTrust == null) {
- window.DigiTrust = facade;
- }
-}
-
-/**
- * Tests to see if a member ID is valid within facade
- * @param {any} memberId
- */
-var isMemberIdValid = function (memberId) {
- if (memberId && memberId.length > 0) {
- return true;
- } else {
- utils.logError('[DigiTrust Prebid Client Error] Missing member ID, add the member ID to the function call options');
- return false;
- }
-};
-
-/**
- * DigiTrust consent handler for GDPR and __cmp.
- * */
-var gdprConsent = {
- hasConsent: function (options, consentCb) {
- options = options || { consentTimeout: 1500 };
- var stopTimer;
- var processed = false;
- var consentAnswer = false;
- if (typeof (window.__cmp) !== 'undefined') {
- stopTimer = setTimeout(function () {
- consentAnswer = false;
- processed = true;
- consentCb(consentAnswer);
- }, options.consentTimeout);
-
- window.__cmp('ping', null, function(pingAnswer) {
- if (pingAnswer.gdprAppliesGlobally) {
- window.__cmp('getVendorConsents', [DT_VENDOR_ID], function (result) {
- if (processed) { return; } // timeout before cmp answer, cancel
- clearTimeout(stopTimer);
- var myconsent = result.vendorConsents[DT_VENDOR_ID];
- consentCb(myconsent);
- });
- } else {
- if (processed) { return; } // timeout before cmp answer, cancel
- clearTimeout(stopTimer);
- consentAnswer = true;
- consentCb(consentAnswer);
- }
- });
- } else {
- // __cmp library is not preset.
- // ignore this check and rely on id system GDPR consent management
- consentAnswer = true;
- consentCb(consentAnswer);
- }
- }
-}
-
-/**
- * Encapsulation of needed info for the callback return.
- *
- * @param {any} opts
- */
-var ResultWrapper = function (opts) {
- var me = this;
- this.idObj = null;
-
- var idSystemFn = null;
-
- /**
- * Callback method that is passed back to the userId module.
- *
- * @param {function} callback
- */
- this.userIdCallback = function (callback) {
- idSystemFn = callback;
- if (me.idObj == null) {
- me.idObj = _savedId;
- }
-
- if (me.idObj != null && isFunc(callback)) {
- callback(wrapIdResult());
- }
- }
-
- /**
- * Return a wrapped result formatted for userId system
- */
- function wrapIdResult() {
- if (me.idObj == null) {
- me.idObj = _savedId;
- }
-
- if (me.idObj == null) {
- return null;
- }
-
- var cp = me.configParams;
- var exp = (cp && cp.storage && cp.storage.expires) || 60;
-
- var rslt = {
- data: null,
- expires: exp
- };
- if (me.idObj && me.idObj.success && me.idObj.identity) {
- rslt.data = me.idObj.identity;
- } else {
- rslt.err = 'Failure getting id';
- }
-
- return rslt;
- }
-
- this.retries = 0;
- this.retryId = 0;
-
- this.executeIdRequest = function (configParams) {
- // eslint-disable-next-line no-undef
- DigiTrust.getUser({ member: 'prebid' }, function (idResult) {
- me.idObj = idResult;
- var cb = function () {
- if (isFunc(idSystemFn)) {
- idSystemFn(wrapIdResult());
- }
- }
-
- cb();
- if (configParams && configParams.callback && isFunc(configParams.callback)) {
- try {
- configParams.callback(idResult);
- } catch (ex) {
- utils.logError('Failure in DigiTrust executeIdRequest', ex);
- }
- }
- });
- }
-}
-
-// An instance of the result wrapper object.
-var resultHandler = new ResultWrapper();
-
-/*
- * Internal implementation to get the Id and trigger callback
- */
-function getDigiTrustId(configParams) {
- if (resultHandler.configParams == null) {
- resultHandler.configParams = configParams;
- }
-
- // First see if we should initialize DigiTrust framework
- if (isPresent() && !isInitialized()) {
- initializeDigiTrust(configParams);
- resultHandler.retryId = setTimeout(function () {
- getDigiTrustId(configParams);
- }, 100 * (1 + resultHandler.retries++));
- return resultHandler.userIdCallback;
- } else if (!isInitialized()) { // Second see if we should build a facade object
- if (resultHandler.retries >= MAX_RETRIES) {
- initDigitrustFacade(configParams); // initialize a facade object that relies on the AJAX call
- resultHandler.executeIdRequest(configParams);
- } else {
- // use expanding envelope
- if (resultHandler.retryId != 0) {
- clearTimeout(resultHandler.retryId);
- }
- resultHandler.retryId = setTimeout(function () {
- getDigiTrustId(configParams);
- }, 100 * (1 + resultHandler.retries++));
- }
- return resultHandler.userIdCallback;
- } else { // Third get the ID
- resultHandler.executeIdRequest(configParams);
- return resultHandler.userIdCallback;
- }
-}
-
-function initializeDigiTrust(config) {
- utils.logInfo('Digitrust Init');
- var dt = window.DigiTrust;
- if (dt && !dt.isClient && config != null) {
- dt.initialize(config.init, config.callback);
- } else if (dt == null) {
- // Assume we are already on a delay and DigiTrust is not on page
- initDigitrustFacade(config);
- }
-}
-
-var testHook = {};
-
-/**
- * Exposes the test hook object by attaching to the digitrustIdModule.
- * This method is called in the unit tests to surface internals.
- */
-export function surfaceTestHook() {
- digiTrustIdSubmodule['_testHook'] = testHook;
- return testHook;
-}
-
-testHook.initDigitrustFacade = initDigitrustFacade; // expose for unit tests
-testHook.gdpr = gdprConsent;
-
-/** @type {Submodule} */
-export const digiTrustIdSubmodule = {
- /**
- * used to link submodule with config
- * @type {string}
- */
- name: 'digitrust',
- /**
- * decode the stored id value for passing to bid requests
- * @function
- * @param {string} value
- * @returns {{pubcid:string}}
- */
- decode: function (idData) {
- try {
- return { 'digitrustid': idData };
- } catch (e) {
- utils.logError('DigiTrust ID submodule decode error');
- }
- },
- getId: function (configParams) {
- return {callback: getDigiTrustId(configParams)};
- },
- _testInit: surfaceTestHook
-};
-
-// check for fallback init of DigiTrust
-function fallbackInit() {
- if (resultHandler.retryId == 0 && !isInitialized()) {
- // this triggers an init
- var conf = {
- member: 'fallback',
- callback: noop
- };
- getDigiTrustId(conf);
- }
-}
-
-fallbackTimer = setTimeout(fallbackInit, fallbackTimeout);
-
-submodule('userId', digiTrustIdSubmodule);
diff --git a/modules/digiTrustIdSystem.md b/modules/digiTrustIdSystem.md
deleted file mode 100644
index c0b274d3292..00000000000
--- a/modules/digiTrustIdSystem.md
+++ /dev/null
@@ -1,156 +0,0 @@
-## DigiTrust Universal Id Integration
-
-Setup
------
-The DigiTrust Id integration for Prebid may be used with or without the full
-DigiTrust library. This is an optional module that must be used in conjunction
-with the userId module.
-
-See the [Prebid Integration Guide for DigiTrust](https://github.com/digi-trust/dt-cdn/wiki/Prebid-Integration-for-DigiTrust-Id)
-and the [DigiTrust wiki](https://github.com/digi-trust/dt-cdn/wiki)
-for further instructions.
-
-
-## Example Prebid Configuration for Digitrust Id
-```
- pbjs.que.push(function() {
- pbjs.setConfig({
- usersync: {
- userIds: [{
- name: "digitrust",
- params: {
- init: {
- member: 'example_member_id',
- site: 'example_site_id'
- },
- callback: function (digiTrustResult) {
- // This callback method is optional and used for error handling
- // in many if not most cases.
- /*
- if (digiTrustResult.success) {
- // Success in Digitrust init;
- // 'DigiTrust Id (encrypted): ' + digiTrustResult.identity.id;
- }
- else {
- // Digitrust init failed
- }
- */
- }
- },
- storage: {
- type: "html5",
- name: "pbjsdigitrust",
- expires: 60
- }
- }]
- }
- });
- pbjs.addAdUnits(adUnits);
- pbjs.requestBids({
- bidsBackHandler: sendAdserverRequest
- });
- });
-
-```
-
-
-## Building Prebid with DigiTrust Support
-Your Prebid build must include the modules for both **userId** and **digitrustIdLoader**. Follow the build instructions for Prebid as
-explained in the top level README.md file of the Prebid source tree.
-
-ex: $ gulp build --modules=userId,digitrustIdLoader
-
-### Step by step Prebid build instructions for DigiTrust
-
-1. Download the Prebid source from [Prebid Git Repo](https://github.com/prebid/Prebid.js)
-2. Set up your environment as outlined in the [Readme File](https://github.com/prebid/Prebid.js/blob/master/README.md#Build)
-3. Execute the build command either with all modules or with the `userId` and `digitrustIdLoader` modules.
- ```
- $ gulp build --modules=userId,digitrustIdLoader
- ```
-4. (Optional) Concatenate the DigiTrust source code to the end of your `prebid.js` file for a single source distribution.
-5. Upload the resulting source file to your CDN.
-
-
-## Deploying Prebid with DigiTrust ID support
-**Precondition:** You must be a DigiTrust member and have registered through the [DigiTrust Signup Process](http://www.digitru.st/signup/).
-Your assigned publisher ID will be required in the configuration settings for all deployment scenarios.
-
-There are three supported approaches to deploying the Prebid-integrated DigiTrust package:
-
-* "Bare bones" deployment using only the integrated DigiTrust module code.
-* Full DigiTrust with CDN referenced DigiTrust.js library.
-* Full DigiTrust packaged with Prebid or site js.
-
-### Bare Bones Deployment
-
-This deployment results in the smallest Javascript package and is the simplest deployment.
-It is appropriate for testing or deployments where simplicity is key. This approach
-utilizes the REST API for ID generation. While there is less Javascript in use,
-the user may experience more network requests than the scenarios that include the full
-DigiTrust library.
-
-1. Build your Prebid package as above, skipping step 4.
-2. Add the DigiTrust initializer section to your Prebid initialization object as below,
- using your Member ID and Site ID.
-3. Add a reference to your Prebid package and the initialization code on all pages you wish
- to utilize Prebid with integrated DigiTrust ID.
-
-
-
-
-### Full DigiTrust with CDN referenced DigiTrust library
-
-Both "Full DigiTrust" deployments will result in a larger initial Javascript payload.
-The end user may experience fewer overall network requests as the encrypted and anonymous
-DigiTrust ID can often be generated fully in client-side code. Utilizing the CDN reference
-to the official DigiTrust distribution insures you will be running the latest version of the library.
-
-The Full DigiTrust deployment is designed to work with both new DigiTrust with Prebid deployments, and with
-Prebid deployments by existing DigiTrust members. This allows you to migrate your code more slowly
-without losing DigiTrust support in the process.
-
-1. Deploy your built copy of `prebid.js` to your CDN.
-2. On each page reference both your `prebid.js` and a copy of the **DigiTrust** library.
- This may either be a copy downloaded from the [DigiTrust CDN](https://cdn.digitru.st/prod/1/digitrust.min.js) to your CDN,
- or directly referenced from the URL https://cdn.digitru.st/prod/1/digitrust.min.js. These may be added to the page in any order.
-3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings.
-
-### Full DigiTrust packaged with Prebid
-
-
-1. Deploy your built copy of `prebid.js` to your CDN. Be sure to perform *Step 4* of the build to concatenate or
- integrate the full DigiTrust library code with your Prebid package.
-2. On each page reference your `prebid.js`
-3. Add a configuration section for Prebid that includes the `usersync` settings and the `digitrust` settings.
- This code may also be appended to your Prebid package or placed in other initialization methods.
-
-
-
-## Parameter Descriptions for the `usersync` Configuration Section
-The below parameters apply only to the DigiTrust ID integration.
-
-| Param under usersync.userIds[] | Scope | Type | Description | Example |
-| --- | --- | --- | --- | --- |
-| name | Required | String | ID value for the DigiTrust module - `"digitrust"` | `"digitrust"` |
-| params | Required | Object | Details for DigiTrust initialization. | |
-| params.init | Required | Object | Initialization parameters, including the DigiTrust Publisher ID and Site ID. | |
-| params.init.member | Required | String | DigiTrust Publisher Id | "A897dTzB" |
-| params.init.site | Required | String | DigiTrust Site Id | "MM2123" |
-| params.callback | Optional | Function | Callback method to fire after initialization of the DigiTrust framework. The argument indicates failure and success and the identity object upon success. | |
-| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | |
-| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` |
-| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"pbjsdigitrust"` |
-| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. Default is 30 for UnifiedId and 1825 for PubCommonID | `365` |
-| value | Optional | Object | Used only if the page has a separate mechanism for storing the Unified ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"tdid": "D6885E90-2A7A-4E0F-87CB-7734ED1B99A3"}` |
-
-
-
-## Further Reading
-
-+ [DigiTrust Home Page](http://digitru.st)
-
-+ [DigiTrust integration guide](https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide)
-
-+ [DigiTrust ID Encryption](https://github.com/digi-trust/dt-cdn/wiki/ID-encryption)
-
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/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js
index 5e3717ce1fe..dce678362cb 100644
--- a/modules/freewheel-sspBidAdapter.js
+++ b/modules/freewheel-sspBidAdapter.js
@@ -72,7 +72,7 @@ function getPricing(xmlNode) {
price: priceNode.textContent || priceNode.innerText
};
} else {
- utils.logWarn('PREBID - ' + BIDDER_CODE + ': Can\'t get pricing data. Is price awareness enabled?');
+ utils.logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.');
}
return princingData;
diff --git a/modules/gdprEnforcement.js b/modules/gdprEnforcement.js
index 6a3fbdce1f2..adbccd8666d 100644
--- a/modules/gdprEnforcement.js
+++ b/modules/gdprEnforcement.js
@@ -11,39 +11,151 @@ import includes from 'core-js-pure/features/array/includes.js';
import { registerSyncInner } from '../src/adapters/bidderFactory.js';
import { getHook } from '../src/hook.js';
import { validateStorageEnforcement } from '../src/storageManager.js';
+import events from '../src/events.js';
+import { EVENTS } from '../src/constants.json';
-const purpose1 = 'storage';
+const TCF2 = {
+ 'purpose1': { id: 1, name: 'storage' },
+ 'purpose2': { id: 2, name: 'basicAds' },
+ 'purpose7': { id: 7, name: 'measurement' }
+}
+
+/*
+ These rules would be used if `consentManagement.gdpr.rules` is undefined by the publisher.
+*/
+const DEFAULT_RULES = [{
+ purpose: 'storage',
+ enforcePurpose: true,
+ enforceVendor: true,
+ vendorExceptions: []
+}, {
+ purpose: 'basicAds',
+ enforcePurpose: true,
+ enforceVendor: true,
+ vendorExceptions: []
+}];
+
+export let purpose1Rule;
+export let purpose2Rule;
+export let purpose7Rule;
+
+export let enforcementRules;
+
+const storageBlocked = [];
+const biddersBlocked = [];
+const analyticsBlocked = [];
let addedDeviceAccessHook = false;
-let enforcementRules;
-function getGvlid() {
- let gvlid;
- const bidderCode = config.getCurrentBidder();
+// Helps in stubbing these functions in unit tests.
+export const internal = {
+ getGvlidForBidAdapter,
+ getGvlidForUserIdModule,
+ getGvlidForAnalyticsAdapter
+};
+
+/**
+ * Returns GVL ID for a Bid adapter / an USERID submodule / an Analytics adapter.
+ * If modules of different types have the same moduleCode: For example, 'appnexus' is the code for both Bid adapter and Analytics adapter,
+ * then, we assume that their GVL IDs are same. This function first checks if GVL ID is defined for a Bid adapter, if not found, tries to find User ID
+ * submodule's GVL ID, if not found, tries to find Analytics adapter's GVL ID. In this process, as soon as it finds a GVL ID, it returns it
+ * without going to the next check.
+ * @param {{string|Object}} - module
+ * @return {number} - GVL ID
+ */
+export function getGvlid(module) {
+ let gvlid = null;
+ if (module) {
+ // Check user defined GVL Mapping in pbjs.setConfig()
+ const gvlMapping = config.getConfig('gvlMapping');
+
+ // For USER ID Module, we pass the submodule object itself as the "module" parameter, this check is required to grab the module code
+ const moduleCode = typeof module === 'string' ? module : module.name;
+
+ // Return GVL ID from user defined gvlMapping
+ if (gvlMapping && gvlMapping[moduleCode]) {
+ gvlid = gvlMapping[moduleCode];
+ return gvlid;
+ }
+
+ gvlid = internal.getGvlidForBidAdapter(moduleCode) || internal.getGvlidForUserIdModule(module) || internal.getGvlidForAnalyticsAdapter(moduleCode);
+ }
+ return gvlid;
+}
+
+/**
+ * Returns GVL ID for a bid adapter. If the adapter does not have an associated GVL ID, it returns 'null'.
+ * @param {string=} bidderCode - The 'code' property of the Bidder spec.
+ * @return {number} GVL ID
+ */
+function getGvlidForBidAdapter(bidderCode) {
+ let gvlid = null;
+ bidderCode = bidderCode || config.getCurrentBidder();
if (bidderCode) {
const bidder = adapterManager.getBidAdapter(bidderCode);
- gvlid = bidder.getSpec().gvlid;
- } else {
- utils.logWarn('Current module not found');
+ if (bidder && bidder.getSpec) {
+ gvlid = bidder.getSpec().gvlid;
+ }
}
return gvlid;
}
/**
- * This function takes in rules and consentData as input and validates against the consentData provided. If it returns true Prebid will allow the next call else it will log a warning
- * @param {Object} rules enforcement rules set in config
- * @param {Object} consentData gdpr consent data
+ * Returns GVL ID for an userId submodule. If an userId submodules does not have an associated GVL ID, it returns 'null'.
+ * @param {Object} userIdModule
+ * @return {number} GVL ID
+ */
+function getGvlidForUserIdModule(userIdModule) {
+ return (typeof userIdModule === 'object' ? userIdModule.gvlid : null);
+}
+
+/**
+ * Returns GVL ID for an analytics adapter. If an analytics adapter does not have an associated GVL ID, it returns 'null'.
+ * @param {string} code - 'provider' property on the analytics adapter config
+ * @return {number} GVL ID
+ */
+function getGvlidForAnalyticsAdapter(code) {
+ return adapterManager.getAnalyticsAdapter(code) && (adapterManager.getAnalyticsAdapter(code).gvlid || null);
+}
+
+/**
+ * This function takes in a rule and consentData and validates against the consentData provided. Depending on what it returns,
+ * the caller may decide to suppress a TCF-sensitive activity.
+ * @param {Object} rule - enforcement rules set in config
+ * @param {Object} consentData - gdpr consent data
+ * @param {string=} currentModule - Bidder code of the current module
+ * @param {number=} gvlId - GVL ID for the module
* @returns {boolean}
*/
-function validateRules(rule, consentData, currentModule, gvlid) {
- // if vendor has exception => always true
+export function validateRules(rule, consentData, currentModule, gvlId) {
+ const purposeId = TCF2[Object.keys(TCF2).filter(purposeName => TCF2[purposeName].name === rule.purpose)[0]].id;
+
+ // return 'true' if vendor present in 'vendorExceptions'
if (includes(rule.vendorExceptions || [], currentModule)) {
return true;
}
- // if enforcePurpose is false or purpose was granted isAllowed is true, otherwise false
- const purposeAllowed = rule.enforcePurpose === false || utils.deepAccess(consentData, 'vendorData.purpose.consents.1') === true;
- // if enforceVendor is false or vendor was granted isAllowed is true, otherwise false
- const vendorAllowed = rule.enforceVendor === false || utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlid}`) === true;
+
+ // get data from the consent string
+ const purposeConsent = utils.deepAccess(consentData, `vendorData.purpose.consents.${purposeId}`);
+ const vendorConsent = utils.deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`);
+ const liTransparency = utils.deepAccess(consentData, `vendorData.purpose.legitimateInterests.${purposeId}`);
+
+ /*
+ Since vendor exceptions have already been handled, the purpose as a whole is allowed if it's not being enforced
+ or the user has consented. Similar with vendors.
+ */
+ const purposeAllowed = rule.enforcePurpose === false || purposeConsent === true;
+ const vendorAllowed = rule.enforceVendor === false || vendorConsent === true;
+
+ /*
+ Few if any vendors should be declaring Legitimate Interest for Device Access (Purpose 1), but some are claiming
+ LI for Basic Ads (Purpose 2). Prebid.js can't check to see who's declaring what legal basis, so if LI has been
+ established for Purpose 2, allow the auction to take place and let the server sort out the legal basis calculation.
+ */
+ if (purposeId === 2) {
+ return (purposeAllowed && vendorAllowed) || (liTransparency === true);
+ }
+
return purposeAllowed && vendorAllowed;
}
@@ -65,22 +177,26 @@ export function deviceAccessHook(fn, gvlid, moduleName, result) {
const consentData = gdprDataHandler.getConsentData();
if (consentData && consentData.gdprApplies) {
if (consentData.apiVersion === 2) {
- if (!gvlid) {
- gvlid = getGvlid();
+ const curBidder = config.getCurrentBidder();
+ // Bidders have a copy of storage object with bidder code binded. Aliases will also pass the same bidder code when invoking storage functions and hence if alias tries to access device we will try to grab the gvl id for alias instead of original bidder
+ if (curBidder && (curBidder != moduleName) && adapterManager.aliasRegistry[curBidder] === moduleName) {
+ gvlid = getGvlid(curBidder);
+ } else {
+ gvlid = getGvlid(moduleName) || gvlid;
}
- const curModule = moduleName || config.getCurrentBidder();
- const purpose1Rule = find(enforcementRules, hasPurpose1);
+ const curModule = moduleName || curBidder;
let isAllowed = validateRules(purpose1Rule, consentData, curModule, gvlid);
if (isAllowed) {
result.valid = true;
fn.call(this, gvlid, moduleName, result);
} else {
- utils.logWarn(`User denied Permission for Device access for ${curModule}`);
+ curModule && utils.logWarn(`TCF2 denied device access for ${curModule}`);
result.valid = false;
+ storageBlocked.push(curModule);
fn.call(this, gvlid, moduleName, result);
}
} else {
- utils.logInfo('Enforcing TCF2 only');
+ // The module doesn't enforce TCF1.1 strings
result.valid = true;
fn.call(this, gvlid, moduleName, result);
}
@@ -100,21 +216,17 @@ export function userSyncHook(fn, ...args) {
const consentData = gdprDataHandler.getConsentData();
if (consentData && consentData.gdprApplies) {
if (consentData.apiVersion === 2) {
- const gvlid = getGvlid();
const curBidder = config.getCurrentBidder();
- if (gvlid) {
- const purpose1Rule = find(enforcementRules, hasPurpose1);
- let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid);
- if (isAllowed) {
- fn.call(this, ...args);
- } else {
- utils.logWarn(`User sync not allowed for ${curBidder}`);
- }
+ const gvlid = getGvlid(curBidder);
+ let isAllowed = validateRules(purpose1Rule, consentData, curBidder, gvlid);
+ if (isAllowed) {
+ fn.call(this, ...args);
} else {
utils.logWarn(`User sync not allowed for ${curBidder}`);
+ storageBlocked.push(curBidder);
}
} else {
- utils.logInfo('Enforcing TCF2 only');
+ // The module doesn't enforce TCF1.1 strings
fn.call(this, ...args);
}
} else {
@@ -132,24 +244,20 @@ export function userIdHook(fn, submodules, consentData) {
if (consentData && consentData.gdprApplies) {
if (consentData.apiVersion === 2) {
let userIdModules = submodules.map((submodule) => {
- const gvlid = submodule.submodule.gvlid;
+ const gvlid = getGvlid(submodule.submodule);
const moduleName = submodule.submodule.name;
- if (gvlid) {
- const purpose1Rule = find(enforcementRules, hasPurpose1);
- let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid);
- if (isAllowed) {
- return submodule;
- } else {
- utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`);
- }
+ let isAllowed = validateRules(purpose1Rule, consentData, moduleName, gvlid);
+ if (isAllowed) {
+ return submodule;
} else {
utils.logWarn(`User denied permission to fetch user id for ${moduleName} User id module`);
+ storageBlocked.push(moduleName);
}
return undefined;
}).filter(module => module)
- fn.call(this, userIdModules, {...consentData, hasValidated: true});
+ fn.call(this, userIdModules, { ...consentData, hasValidated: true });
} else {
- utils.logInfo('Enforcing TCF2 only');
+ // The module doesn't enforce TCF1.1 strings
fn.call(this, submodules, consentData);
}
} else {
@@ -157,28 +265,137 @@ export function userIdHook(fn, submodules, consentData) {
}
}
-const hasPurpose1 = (rule) => { return rule.purpose === purpose1 }
+/**
+ * Checks if bidders are allowed in the auction.
+ * Enforces "purpose 2 (Basic Ads)" of TCF v2.0 spec
+ * @param {Function} fn - Function reference to the original function.
+ * @param {Array} adUnits
+ */
+export function makeBidRequestsHook(fn, adUnits, ...args) {
+ const consentData = gdprDataHandler.getConsentData();
+ if (consentData && consentData.gdprApplies) {
+ if (consentData.apiVersion === 2) {
+ adUnits.forEach(adUnit => {
+ adUnit.bids = adUnit.bids.filter(bid => {
+ const currBidder = bid.bidder;
+ const gvlId = getGvlid(currBidder);
+ if (includes(biddersBlocked, currBidder)) return false;
+ const isAllowed = !!validateRules(purpose2Rule, consentData, currBidder, gvlId);
+ if (!isAllowed) {
+ utils.logWarn(`TCF2 blocked auction for ${currBidder}`);
+ biddersBlocked.push(currBidder);
+ }
+ return isAllowed;
+ });
+ });
+ fn.call(this, adUnits, ...args);
+ } else {
+ // The module doesn't enforce TCF1.1 strings
+ fn.call(this, adUnits, ...args);
+ }
+ } else {
+ fn.call(this, adUnits, ...args);
+ }
+}
+
+/**
+ * Checks if Analytics adapters are allowed to send data to their servers for furhter processing.
+ * Enforces "purpose 7 (Measurement)" of TCF v2.0 spec
+ * @param {Function} fn - Function reference to the original function.
+ * @param {Array} config - Configuration object passed to pbjs.enableAnalytics()
+ */
+export function enableAnalyticsHook(fn, config) {
+ const consentData = gdprDataHandler.getConsentData();
+ if (consentData && consentData.gdprApplies) {
+ if (consentData.apiVersion === 2) {
+ if (!utils.isArray(config)) {
+ config = [config]
+ }
+ config = config.filter(conf => {
+ const analyticsAdapterCode = conf.provider;
+ const gvlid = getGvlid(analyticsAdapterCode);
+ const isAllowed = !!validateRules(purpose7Rule, consentData, analyticsAdapterCode, gvlid);
+ if (!isAllowed) {
+ analyticsBlocked.push(analyticsAdapterCode);
+ utils.logWarn(`TCF2 blocked analytics adapter ${conf.provider}`);
+ }
+ return isAllowed;
+ });
+ fn.call(this, config);
+ } else {
+ // This module doesn't enforce TCF1.1 strings
+ fn.call(this, config);
+ }
+ } else {
+ fn.call(this, config);
+ }
+}
+
+/**
+ * Compiles the TCF2.0 enforcement results into an object, which is emitted as an event payload to "tcf2Enforcement" event.
+ */
+function emitTCF2FinalResults() {
+ // remove null and duplicate values
+ const formatArray = function (arr) {
+ return arr.filter((i, k) => i !== null && arr.indexOf(i) === k);
+ }
+ const tcf2FinalResults = {
+ storageBlocked: formatArray(storageBlocked),
+ biddersBlocked: formatArray(biddersBlocked),
+ analyticsBlocked: formatArray(analyticsBlocked)
+ };
+
+ events.emit(EVENTS.TCF2_ENFORCEMENT, tcf2FinalResults);
+}
+
+events.on(EVENTS.AUCTION_END, emitTCF2FinalResults);
+
+/*
+ Set of callback functions used to detect presence of a TCF rule, passed as the second argument to find().
+*/
+const hasPurpose1 = (rule) => { return rule.purpose === TCF2.purpose1.name }
+const hasPurpose2 = (rule) => { return rule.purpose === TCF2.purpose2.name }
+const hasPurpose7 = (rule) => { return rule.purpose === TCF2.purpose7.name }
/**
- * A configuration function that initializes some module variables, as well as add hooks
- * @param {Object} config GDPR enforcement config object
+ * A configuration function that initializes some module variables, as well as adds hooks
+ * @param {Object} config - GDPR enforcement config object
*/
export function setEnforcementConfig(config) {
const rules = utils.deepAccess(config, 'gdpr.rules');
if (!rules) {
- utils.logWarn('GDPR enforcement rules not defined, exiting enforcement module');
- return;
+ utils.logWarn('TCF2: enforcing P1 and P2 by default');
+ enforcementRules = DEFAULT_RULES;
+ } else {
+ enforcementRules = rules;
+ }
+
+ purpose1Rule = find(enforcementRules, hasPurpose1);
+ purpose2Rule = find(enforcementRules, hasPurpose2);
+ purpose7Rule = find(enforcementRules, hasPurpose7);
+
+ if (!purpose1Rule) {
+ purpose1Rule = DEFAULT_RULES[0];
}
- enforcementRules = rules;
- const hasDefinedPurpose1 = find(enforcementRules, hasPurpose1);
- if (hasDefinedPurpose1 && !addedDeviceAccessHook) {
+ if (!purpose2Rule) {
+ purpose2Rule = DEFAULT_RULES[1];
+ }
+
+ if (purpose1Rule && !addedDeviceAccessHook) {
addedDeviceAccessHook = true;
validateStorageEnforcement.before(deviceAccessHook, 49);
registerSyncInner.before(userSyncHook, 48);
// Using getHook as user id and gdprEnforcement are both optional modules. Using import will auto include the file in build
getHook('validateGdprEnforcement').before(userIdHook, 47);
}
+ if (purpose2Rule) {
+ getHook('makeBidRequests').before(makeBidRequestsHook);
+ }
+
+ if (purpose7Rule) {
+ getHook('enableAnalyticsCb').before(enableAnalyticsHook);
+ }
}
config.getConfig('consentManagement', config => setEnforcementConfig(config.consentManagement));
diff --git a/modules/gjirafaBidAdapter.js b/modules/gjirafaBidAdapter.js
new file mode 100644
index 00000000000..ca7fb4af32d
--- /dev/null
+++ b/modules/gjirafaBidAdapter.js
@@ -0,0 +1,91 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+
+const BIDDER_CODE = 'gjirafa';
+const ENDPOINT_URL = 'https://central.gjirafa.com/bid';
+const DIMENSION_SEPARATOR = 'x';
+const SIZE_SEPARATOR = ';';
+
+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) {
+ return !!(bid.params.propertyId && bid.params.placementId);
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ let response = validBidRequests.map(bidRequest => {
+ let sizes = generateSizeParam(bidRequest.sizes);
+ let propertyId = bidRequest.params.propertyId;
+ let placementId = bidRequest.params.placementId;
+ let adUnitId = bidRequest.adUnitCode;
+ let pageViewGuid = bidRequest.params.pageViewGuid || '';
+ let contents = bidRequest.params.contents || [];
+ const body = {
+ sizes: sizes,
+ adUnitId: adUnitId,
+ placementId: placementId,
+ propertyId: propertyId,
+ pageViewGuid: pageViewGuid,
+ url: bidderRequest ? bidderRequest.refererInfo.referer : '',
+ requestid: bidRequest.bidderRequestId,
+ bidid: bidRequest.bidId,
+ contents: contents
+ };
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: body
+ };
+ });
+ return response
+ },
+ /**
+ * 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) {
+ window.adnResponse = serverResponse;
+ const responses = serverResponse.body;
+ const bidResponses = [];
+ for (var i = 0; i < responses.length; i++) {
+ const bidResponse = {
+ requestId: bidRequest.data.bidid,
+ cpm: responses[i].CPM,
+ width: responses[i].Width,
+ height: responses[i].Height,
+ creativeId: responses[i].CreativeId,
+ currency: responses[i].Currency,
+ netRevenue: responses[i].NetRevenue,
+ ttl: responses[i].TTL,
+ referrer: responses[i].Referrer,
+ ad: responses[i].Ad
+ };
+ bidResponses.push(bidResponse);
+ }
+ return bidResponses;
+ }
+}
+
+/**
+* Generate size param for bid request using sizes array
+*
+* @param {Array} sizes Possible sizes for the ad unit.
+* @return {string} Processed sizes param to be used for the bid request.
+*/
+function generateSizeParam(sizes) {
+ return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR);
+}
+
+registerBidder(spec);
diff --git a/modules/gjirafaBidAdapter.md b/modules/gjirafaBidAdapter.md
index 1ec8222d8de..53d3a76c5ed 100644
--- a/modules/gjirafaBidAdapter.md
+++ b/modules/gjirafaBidAdapter.md
@@ -1,36 +1,51 @@
# Overview
Module Name: Gjirafa Bidder Adapter Module
Type: Bidder Adapter
-Maintainer: agonq@gjirafa.com
+Maintainer: drilon@gjirafa.com
# Description
Gjirafa Bidder Adapter for Prebid.js.
# Test Parameters
var adUnits = [
-{
- code: 'test-div',
- sizes: [[728, 90]], // leaderboard
- bids: [
- {
- bidder: 'gjirafa',
- params: {
- placementId: '71-3'
- }
- }
- ]
-},{
- code: 'test-div',
- sizes: [[300, 250]], // mobile rectangle
- bids: [
- {
- bidder: 'gjirafa',
- params: {
- minCPM: 0.0001,
- minCPC: 0.001,
- explicit: true
- }
- }
- ]
-}
-];
\ No newline at end of file
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[728, 90]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'gjirafa',
+ params: {
+ propertyId: '105227',
+ placementId: '846841'
+ }
+ }
+ ]
+ },
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'gjirafa',
+ params: {
+ propertyId: '105227',
+ placementId: '846848',
+ contents: [ //optional
+ {
+ type: 'article',
+ id: '123'
+ }
+ ]
+ }
+ }
+ ]
+ }
+];
diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js
new file mode 100644
index 00000000000..48b72671d6a
--- /dev/null
+++ b/modules/gptPreAuction.js
@@ -0,0 +1,101 @@
+import { config } from '../src/config.js';
+import * as utils from '../src/utils.js';
+import { getHook } from '../src/hook.js';
+import find from 'core-js-pure/features/array/find.js';
+
+const MODULE_NAME = 'GPT Pre-Auction';
+export let _currentConfig = {};
+let hooksAdded = false;
+
+export const appendGptSlots = adUnits => {
+ const { customGptSlotMatching } = _currentConfig;
+
+ if (!utils.isGptPubadsDefined()) {
+ return;
+ }
+
+ const adUnitMap = adUnits.reduce((acc, adUnit) => {
+ acc[adUnit.code] = adUnit;
+ return acc;
+ }, {});
+
+ window.googletag.pubads().getSlots().forEach(slot => {
+ const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching
+ ? customGptSlotMatching(slot)
+ : utils.isAdUnitCodeMatchingSlot(slot));
+
+ if (matchingAdUnitCode) {
+ const adUnit = adUnitMap[matchingAdUnitCode];
+ adUnit.fpd = adUnit.fpd || {};
+ adUnit.fpd.context = adUnit.fpd.context || {};
+
+ const context = adUnit.fpd.context;
+ context.adServer = context.adServer || {};
+ context.adServer.name = 'gam';
+ context.adServer.adSlot = slot.getAdUnitPath();
+ }
+ });
+};
+
+export const appendPbAdSlot = adUnit => {
+ adUnit.fpd = adUnit.fpd || {};
+ adUnit.fpd.context = adUnit.fpd.context || {};
+ const context = adUnit.fpd.context;
+ const { customPbAdSlot } = _currentConfig;
+
+ if (customPbAdSlot) {
+ context.pbAdSlot = customPbAdSlot(adUnit.code, utils.deepAccess(context, 'adServer.adSlot'));
+ return;
+ }
+
+ // use context.pbAdSlot if set
+ if (context.pbAdSlot) {
+ return;
+ }
+ // use data attribute 'data-adslotid' if set
+ try {
+ const adUnitCodeDiv = document.getElementById(adUnit.code);
+ if (adUnitCodeDiv.dataset.adslotid) {
+ context.pbAdSlot = adUnitCodeDiv.dataset.adslotid;
+ return;
+ }
+ } catch (e) {}
+ // banner adUnit, use GPT adunit if defined
+ if (context.adServer) {
+ context.pbAdSlot = context.adServer.adSlot;
+ return;
+ }
+ context.pbAdSlot = adUnit.code;
+};
+
+export const makeBidRequestsHook = (fn, adUnits, ...args) => {
+ appendGptSlots(adUnits);
+ adUnits.forEach(adUnit => {
+ appendPbAdSlot(adUnit);
+ });
+ return fn.call(this, adUnits, ...args);
+};
+
+const handleSetGptConfig = moduleConfig => {
+ _currentConfig = utils.pick(moduleConfig, [
+ 'enabled', enabled => enabled !== false,
+ 'customGptSlotMatching', customGptSlotMatching =>
+ typeof customGptSlotMatching === 'function' && customGptSlotMatching,
+ 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot,
+ ]);
+
+ if (_currentConfig.enabled) {
+ if (!hooksAdded) {
+ getHook('makeBidRequests').before(makeBidRequestsHook);
+ hooksAdded = true;
+ }
+ } else {
+ utils.logInfo(`${MODULE_NAME}: Turning off module`);
+ _currentConfig = {};
+ getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove();
+ hooksAdded = false;
+ }
+};
+
+config.getConfig('gptPreAuction', config => handleSetGptConfig(config.gptPreAuction));
+handleSetGptConfig({});
diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js
index d18effa349b..b4b741ac783 100644
--- a/modules/gridBidAdapter.js
+++ b/modules/gridBidAdapter.js
@@ -6,6 +6,7 @@ import {config} from '../src/config.js';
const BIDDER_CODE = 'grid';
const ENDPOINT_URL = 'https://grid.bidswitch.net/hb';
+const NEW_ENDPOINT_URL = 'https://grid.bidswitch.net/hbjson';
const SYNC_URL = 'https://x.bidswitch.net/sync?ssp=themediagrid';
const TIME_TO_LIVE = 360;
const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js';
@@ -40,117 +41,22 @@ export const spec = {
*
* @param {BidRequest[]} validBidRequests - an array of bids
* @param {bidderRequest} bidderRequest bidder request object
- * @return ServerRequest Info describing the request to the server.
+ * @return {ServerRequest[]} Info describing the request to the server.
*/
buildRequests: function(validBidRequests, bidderRequest) {
- const auids = [];
- const bidsMap = {};
- const slotsMapByUid = {};
- const sizeMap = {};
- const bids = validBidRequests || [];
- let pageKeywords = null;
- let reqId;
-
- bids.forEach(bid => {
- reqId = bid.bidderRequestId;
- const {params: {uid}, adUnitCode, mediaTypes} = bid;
- auids.push(uid);
- const sizesId = utils.parseSizesInput(bid.sizes);
-
- if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) {
- pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords);
- }
-
- const addedSizes = {};
- sizesId.forEach((sizeId) => {
- addedSizes[sizeId] = true;
- });
- const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes'));
- const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize'));
- bannerSizesId.concat(videoSizesId).forEach((sizeId) => {
- if (!addedSizes[sizeId]) {
- addedSizes[sizeId] = true;
- sizesId.push(sizeId);
- }
- });
-
- if (!slotsMapByUid[uid]) {
- slotsMapByUid[uid] = {};
- }
- const slotsMap = slotsMapByUid[uid];
- if (!slotsMap[adUnitCode]) {
- slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []};
- } else {
- slotsMap[adUnitCode].bids.push(bid);
- }
- const slot = slotsMap[adUnitCode];
-
- sizesId.forEach((sizeId) => {
- sizeMap[sizeId] = true;
- if (!bidsMap[uid]) {
- bidsMap[uid] = {};
- }
-
- if (!bidsMap[uid][sizeId]) {
- bidsMap[uid][sizeId] = [slot];
- } else {
- bidsMap[uid][sizeId].push(slot);
- }
- slot.parents.push({parent: bidsMap[uid], key: sizeId, uid});
- });
+ const oldFormatBids = [];
+ const newFormatBids = [];
+ validBidRequests.forEach((bid) => {
+ bid.params.useNewFormat ? newFormatBids.push(bid) : oldFormatBids.push(bid);
});
-
- const configKeywords = utils.transformBidderParamKeywords({
- 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null,
- 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null
- });
-
- if (configKeywords.length) {
- pageKeywords = (pageKeywords || []).concat(configKeywords);
- }
-
- if (pageKeywords && pageKeywords.length > 0) {
- pageKeywords.forEach(deleteValues);
+ const requests = [];
+ if (newFormatBids.length) {
+ requests.push(buildNewRequest(newFormatBids, bidderRequest));
}
-
- const payload = {
- auids: auids.join(','),
- sizes: utils.getKeys(sizeMap).join(','),
- r: reqId,
- wrapperType: 'Prebid_js',
- wrapperVersion: '$prebid.version$'
- };
-
- if (pageKeywords) {
- payload.keywords = JSON.stringify(pageKeywords);
+ if (oldFormatBids.length) {
+ requests.push(buildOldRequest(oldFormatBids, bidderRequest));
}
-
- if (bidderRequest) {
- if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) {
- payload.u = bidderRequest.refererInfo.referer;
- }
- if (bidderRequest.timeout) {
- payload.wtimeout = bidderRequest.timeout;
- }
- if (bidderRequest.gdprConsent) {
- if (bidderRequest.gdprConsent.consentString) {
- payload.gdpr_consent = bidderRequest.gdprConsent.consentString;
- }
- payload.gdpr_applies =
- (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean')
- ? Number(bidderRequest.gdprConsent.gdprApplies) : 1;
- }
- if (bidderRequest.uspConsent) {
- payload.us_privacy = bidderRequest.uspConsent;
- }
- }
-
- return {
- method: 'GET',
- url: ENDPOINT_URL,
- data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''),
- bidsMap: bidsMap,
- };
+ return requests;
},
/**
* Unpack the response from the server into a list of bids.
@@ -162,7 +68,6 @@ export const spec = {
interpretResponse: function(serverResponse, bidRequest) {
serverResponse = serverResponse && serverResponse.body;
const bidResponses = [];
- const bidsMap = bidRequest.bidsMap;
let errorMessage;
@@ -173,7 +78,7 @@ export const spec = {
if (!errorMessage && serverResponse.seatbid) {
serverResponse.seatbid.forEach(respItem => {
- _addBidResponse(_getBidFromResponse(respItem), bidsMap, bidResponses);
+ _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses);
});
}
if (errorMessage) utils.logError(errorMessage);
@@ -224,68 +129,76 @@ function _getBidFromResponse(respItem) {
return respItem && respItem.bid && respItem.bid[0];
}
-function _addBidResponse(serverBid, bidsMap, bidResponses) {
+function _addBidResponse(serverBid, bidRequest, bidResponses) {
if (!serverBid) return;
let errorMessage;
if (!serverBid.auid) errorMessage = LOG_ERROR_MESS.noAuid + JSON.stringify(serverBid);
if (!serverBid.adm) errorMessage = LOG_ERROR_MESS.noAdm + JSON.stringify(serverBid);
else {
- const awaitingBids = bidsMap[serverBid.auid];
- if (awaitingBids) {
- const sizeId = `${serverBid.w}x${serverBid.h}`;
- if (awaitingBids[sizeId]) {
- const slot = awaitingBids[sizeId][0];
-
- const bid = slot.bids.shift();
-
- const bidResponse = {
- requestId: bid.bidId, // bid.bidderRequestId,
- bidderCode: spec.code,
- cpm: serverBid.price,
- width: serverBid.w,
- height: serverBid.h,
- creativeId: serverBid.auid, // bid.bidId,
- currency: 'USD',
- netRevenue: false,
- ttl: TIME_TO_LIVE,
- dealId: serverBid.dealid
- };
-
- if (serverBid.content_type === 'video') {
- bidResponse.vastXml = serverBid.adm;
- bidResponse.mediaType = VIDEO;
- bidResponse.adResponse = {
- content: bidResponse.vastXml
- };
- if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) {
- bidResponse.renderer = createRenderer(bidResponse, {
- id: bid.bidId,
- url: RENDERER_URL
- });
- }
- } else {
- bidResponse.ad = serverBid.adm;
- bidResponse.mediaType = BANNER;
+ let bid = null;
+ let slot = null;
+ const bidsMap = bidRequest.bidsMap;
+ if (bidRequest.newFormat) {
+ bid = bidsMap[serverBid.impid];
+ } else {
+ const awaitingBids = bidsMap[serverBid.auid];
+ if (awaitingBids) {
+ const sizeId = `${serverBid.w}x${serverBid.h}`;
+ if (awaitingBids[sizeId]) {
+ slot = awaitingBids[sizeId][0];
+ bid = slot.bids.shift();
}
- bidResponses.push(bidResponse);
+ } else {
+ errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid;
+ }
+ }
- if (!slot.bids.length) {
- slot.parents.forEach(({parent, key, uid}) => {
- const index = parent[key].indexOf(slot);
- if (index > -1) {
- parent[key].splice(index, 1);
- }
- if (!parent[key].length) {
- delete parent[key];
- if (!utils.getKeys(parent).length) {
- delete bidsMap[uid];
- }
- }
+ if (bid) {
+ const bidResponse = {
+ requestId: bid.bidId, // bid.bidderRequestId,
+ bidderCode: spec.code,
+ cpm: serverBid.price,
+ width: serverBid.w,
+ height: serverBid.h,
+ creativeId: serverBid.auid, // bid.bidId,
+ currency: 'USD',
+ netRevenue: false,
+ ttl: TIME_TO_LIVE,
+ dealId: serverBid.dealid
+ };
+
+ if (serverBid.content_type === 'video') {
+ bidResponse.vastXml = serverBid.adm;
+ bidResponse.mediaType = VIDEO;
+ bidResponse.adResponse = {
+ content: bidResponse.vastXml
+ };
+ if (!bid.renderer && (!bid.mediaTypes || !bid.mediaTypes.video || bid.mediaTypes.video.context === 'outstream')) {
+ bidResponse.renderer = createRenderer(bidResponse, {
+ id: bid.bidId,
+ url: RENDERER_URL
});
}
+ } else {
+ bidResponse.ad = serverBid.adm;
+ bidResponse.mediaType = BANNER;
+ }
+ bidResponses.push(bidResponse);
+
+ if (slot && !slot.bids.length) {
+ slot.parents.forEach(({parent, key, uid}) => {
+ const index = parent[key].indexOf(slot);
+ if (index > -1) {
+ parent[key].splice(index, 1);
+ }
+ if (!parent[key].length) {
+ delete parent[key];
+ if (!utils.getKeys(parent).length) {
+ delete bidsMap[uid];
+ }
+ }
+ });
}
- } else {
- errorMessage = LOG_ERROR_MESS.noPlacementCode + serverBid.auid;
}
}
if (errorMessage) {
@@ -293,6 +206,326 @@ function _addBidResponse(serverBid, bidsMap, bidResponses) {
}
}
+function buildOldRequest(validBidRequests, bidderRequest) {
+ const auids = [];
+ const bidsMap = {};
+ const slotsMapByUid = {};
+ const sizeMap = {};
+ const bids = validBidRequests || [];
+ let pageKeywords = null;
+ let reqId;
+
+ bids.forEach(bid => {
+ reqId = bid.bidderRequestId;
+ const {params: {uid}, adUnitCode, mediaTypes} = bid;
+ auids.push(uid);
+ const sizesId = utils.parseSizesInput(bid.sizes);
+
+ if (!pageKeywords && !utils.isEmpty(bid.params.keywords)) {
+ pageKeywords = utils.transformBidderParamKeywords(bid.params.keywords);
+ }
+
+ const addedSizes = {};
+ sizesId.forEach((sizeId) => {
+ addedSizes[sizeId] = true;
+ });
+ const bannerSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'banner.sizes'));
+ const videoSizesId = utils.parseSizesInput(utils.deepAccess(mediaTypes, 'video.playerSize'));
+ bannerSizesId.concat(videoSizesId).forEach((sizeId) => {
+ if (!addedSizes[sizeId]) {
+ addedSizes[sizeId] = true;
+ sizesId.push(sizeId);
+ }
+ });
+
+ if (!slotsMapByUid[uid]) {
+ slotsMapByUid[uid] = {};
+ }
+ const slotsMap = slotsMapByUid[uid];
+ if (!slotsMap[adUnitCode]) {
+ slotsMap[adUnitCode] = {adUnitCode, bids: [bid], parents: []};
+ } else {
+ slotsMap[adUnitCode].bids.push(bid);
+ }
+ const slot = slotsMap[adUnitCode];
+
+ sizesId.forEach((sizeId) => {
+ sizeMap[sizeId] = true;
+ if (!bidsMap[uid]) {
+ bidsMap[uid] = {};
+ }
+
+ if (!bidsMap[uid][sizeId]) {
+ bidsMap[uid][sizeId] = [slot];
+ } else {
+ bidsMap[uid][sizeId].push(slot);
+ }
+ slot.parents.push({parent: bidsMap[uid], key: sizeId, uid});
+ });
+ });
+
+ const configKeywords = utils.transformBidderParamKeywords({
+ 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null,
+ 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null
+ });
+
+ if (configKeywords.length) {
+ pageKeywords = (pageKeywords || []).concat(configKeywords);
+ }
+
+ if (pageKeywords && pageKeywords.length > 0) {
+ pageKeywords.forEach(deleteValues);
+ }
+
+ const payload = {
+ auids: auids.join(','),
+ sizes: utils.getKeys(sizeMap).join(','),
+ r: reqId,
+ wrapperType: 'Prebid_js',
+ wrapperVersion: '$prebid.version$'
+ };
+
+ if (pageKeywords) {
+ payload.keywords = JSON.stringify(pageKeywords);
+ }
+
+ if (bidderRequest) {
+ if (bidderRequest.refererInfo && bidderRequest.refererInfo.referer) {
+ payload.u = bidderRequest.refererInfo.referer;
+ }
+ if (bidderRequest.timeout) {
+ payload.wtimeout = bidderRequest.timeout;
+ }
+ if (bidderRequest.gdprConsent) {
+ if (bidderRequest.gdprConsent.consentString) {
+ payload.gdpr_consent = bidderRequest.gdprConsent.consentString;
+ }
+ payload.gdpr_applies =
+ (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean')
+ ? Number(bidderRequest.gdprConsent.gdprApplies) : 1;
+ }
+ if (bidderRequest.uspConsent) {
+ payload.us_privacy = bidderRequest.uspConsent;
+ }
+ }
+
+ return {
+ method: 'GET',
+ url: ENDPOINT_URL,
+ data: utils.parseQueryStringParameters(payload).replace(/\&$/, ''),
+ bidsMap: bidsMap
+ }
+}
+
+function buildNewRequest(validBidRequests, bidderRequest) {
+ let pageKeywords = null;
+ let jwpseg = null;
+ let content = null;
+ let schain = null;
+ let userId = null;
+ let user = null;
+ let userExt = null;
+ let {bidderRequestId, auctionId, gdprConsent, uspConsent, timeout, refererInfo} = bidderRequest;
+
+ const referer = refererInfo ? encodeURIComponent(refererInfo.referer) : '';
+ const imp = [];
+ const bidsMap = {};
+
+ validBidRequests.forEach((bid) => {
+ if (!bidderRequestId) {
+ bidderRequestId = bid.bidderRequestId;
+ }
+ if (!auctionId) {
+ auctionId = bid.auctionId;
+ }
+ if (!schain) {
+ schain = bid.schain;
+ }
+ if (!userId) {
+ userId = bid.userId;
+ }
+ const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, realTimeData} = bid;
+ bidsMap[bidId] = bid;
+ if (!pageKeywords && !utils.isEmpty(keywords)) {
+ pageKeywords = utils.transformBidderParamKeywords(keywords);
+ }
+ if (realTimeData && realTimeData.jwTargeting) {
+ if (!jwpseg && realTimeData.jwTargeting.segments) {
+ jwpseg = realTimeData.segments;
+ }
+ if (!content && realTimeData.content) {
+ content = realTimeData.content;
+ }
+ }
+ let impObj = {
+ id: bidId,
+ tagid: uid.toString(),
+ ext: {
+ divid: adUnitCode
+ }
+ };
+
+ if (!mediaTypes || mediaTypes[BANNER]) {
+ const banner = createBannerRequest(bid, mediaTypes ? mediaTypes[BANNER] : {});
+ if (banner) {
+ impObj.banner = banner;
+ }
+ }
+ if (mediaTypes && mediaTypes[VIDEO]) {
+ const video = createVideoRequest(bid, mediaTypes[VIDEO]);
+ if (video) {
+ impObj.video = video;
+ }
+ }
+
+ if (impObj.banner || impObj.video) {
+ imp.push(impObj);
+ }
+ });
+
+ const source = {
+ tid: auctionId,
+ ext: {
+ wrapper: 'Prebid_js',
+ wrapper_version: '$prebid.version$'
+ }
+ };
+
+ if (schain) {
+ source.ext.schain = schain;
+ }
+
+ const tmax = config.getConfig('bidderTimeout') || timeout;
+
+ let request = {
+ id: bidderRequestId,
+ site: {
+ page: referer
+ },
+ tmax,
+ source,
+ imp
+ };
+
+ if (content) {
+ request.site.content = content;
+ }
+
+ if (jwpseg && jwpseg.length) {
+ user = {
+ data: [{
+ name: 'iow_labs_pub_data',
+ segment: jwpseg.map((seg) => {
+ return {name: 'jwpseg', value: seg};
+ })
+ }]
+ };
+ }
+
+ if (gdprConsent && gdprConsent.consentString) {
+ userExt = {consent: gdprConsent.consentString};
+ }
+
+ if (userId) {
+ userExt = userExt || {};
+ if (userId.tdid) {
+ userExt.unifiedid = userId.tdid;
+ }
+ if (userId.id5id) {
+ userExt.id5id = userId.id5id;
+ }
+ if (userId.digitrustid && userId.digitrustid.data && userId.digitrustid.data.id) {
+ userExt.digitrustid = userId.digitrustid.data.id;
+ }
+ if (userId.lipb && userId.lipb.lipbid) {
+ userExt.liveintentid = userId.lipb.lipbid;
+ }
+ }
+
+ if (userExt && Object.keys(userExt).length) {
+ user = user || {};
+ user.ext = userExt;
+ }
+
+ if (user) {
+ request.user = user;
+ }
+
+ const configKeywords = utils.transformBidderParamKeywords({
+ 'user': utils.deepAccess(config.getConfig('fpd.user'), 'keywords') || null,
+ 'context': utils.deepAccess(config.getConfig('fpd.context'), 'keywords') || null
+ });
+
+ if (configKeywords.length) {
+ pageKeywords = (pageKeywords || []).concat(configKeywords);
+ }
+
+ if (pageKeywords && pageKeywords.length > 0) {
+ pageKeywords.forEach(deleteValues);
+ }
+
+ if (pageKeywords) {
+ request.ext = {
+ keywords: pageKeywords
+ };
+ }
+
+ if (gdprConsent && gdprConsent.gdprApplies) {
+ request.regs = {
+ ext: {
+ gdpr: gdprConsent.gdprApplies ? 1 : 0
+ }
+ }
+ }
+
+ if (uspConsent) {
+ if (!request.regs) {
+ request.regs = {ext: {}};
+ }
+ request.regs.ext.us_privacy = uspConsent;
+ }
+
+ return {
+ method: 'POST',
+ url: NEW_ENDPOINT_URL,
+ data: JSON.stringify(request),
+ newFormat: true,
+ bidsMap
+ };
+}
+
+function createVideoRequest(bid, mediaType) {
+ const {playerSize, mimes, durationRangeSec} = mediaType;
+ const size = (playerSize || bid.sizes || [])[0];
+ if (!size) return;
+
+ let result = utils.parseGPTSingleSizeArrayToRtbSize(size);
+
+ if (mimes) {
+ result.mimes = mimes;
+ }
+
+ if (durationRangeSec && durationRangeSec.length === 2) {
+ result.minduration = durationRangeSec[0];
+ result.maxduration = durationRangeSec[1];
+ }
+
+ return result;
+}
+
+function createBannerRequest(bid, mediaType) {
+ const sizes = mediaType.sizes || bid.sizes;
+ if (!sizes || !sizes.length) return;
+
+ let format = sizes.map((size) => utils.parseGPTSingleSizeArrayToRtbSize(size));
+ let result = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]);
+
+ if (format.length) {
+ result.format = format
+ }
+ return result;
+}
+
function outstreamRender (bid) {
bid.renderer.push(() => {
window.ANOutstreamVideo.renderAd({
diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js
index 54d845be7ad..f8e17e0fe71 100644
--- a/modules/gumgumBidAdapter.js
+++ b/modules/gumgumBidAdapter.js
@@ -198,6 +198,34 @@ function _getVidParams (attributes) {
};
}
+/**
+ * Gets bidfloor
+ * @param {Object} mediaTypes
+ * @param {Number} bidfloor
+ * @param {Object} bid
+ * @returns {Number} floor
+ */
+function _getFloor (mediaTypes, bidfloor, bid) {
+ const curMediaType = Object.keys(mediaTypes)[0] || 'banner';
+ let floor = bidfloor || 0;
+
+ if (typeof bid.getFloor === 'function') {
+ const floorInfo = bid.getFloor({
+ currency: 'USD',
+ mediaType: curMediaType,
+ size: '*'
+ });
+
+ if (typeof floorInfo === 'object' &&
+ floorInfo.currency === 'USD' &&
+ !isNaN(parseFloat(floorInfo.floor))) {
+ floor = Math.max(floor, parseFloat(floorInfo.floor));
+ }
+ }
+
+ return floor;
+}
+
/**
* Make a server request from the list of BidRequests.
*
@@ -219,15 +247,24 @@ function buildRequests (validBidRequests, bidderRequest) {
transactionId,
userId = {}
} = bidRequest;
- const bannerSizes = mediaTypes.banner && mediaTypes.banner.sizes;
+ const bidFloor = _getFloor(mediaTypes, params.bidfloor, bidRequest);
+ let sizes = [1, 1];
let data = {};
+ if (mediaTypes.banner) {
+ sizes = mediaTypes.banner.sizes;
+ } else if (mediaTypes.video) {
+ sizes = mediaTypes.video.playerSize;
+ }
+
if (pageViewId) {
data.pv = pageViewId;
}
- if (params.bidfloor) {
- data.fp = params.bidfloor;
+
+ if (bidFloor) {
+ data.fp = bidFloor;
}
+
if (params.inScreenPubID) {
data.pubId = params.inScreenPubID;
data.pi = 2;
@@ -238,6 +275,17 @@ function buildRequests (validBidRequests, bidderRequest) {
}
if (params.inSlot) {
data.si = parseInt(params.inSlot, 10);
+ // check for sizes and type
+ if (params.sizes && Array.isArray(params.sizes)) {
+ const bf = params.sizes.reduce(function(r, i) {
+ // only push if it's an array of length 2
+ if (Array.isArray(i) && i.length === 2) {
+ r.push(`${i[0]}x${i[1]}`);
+ }
+ return r;
+ }, []);
+ data.bf = bf.toString();
+ }
data.pi = 3;
}
if (params.ICV) {
@@ -278,7 +326,7 @@ function buildRequests (validBidRequests, bidderRequest) {
tId: transactionId,
pi: data.pi,
selector: params.selector,
- sizes: bannerSizes,
+ sizes,
url: BID_ENDPOINT,
method: 'GET',
data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), _getTradeDeskIDParam(userId))
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/ixBidAdapter.js b/modules/ixBidAdapter.js
index b54114c176e..77d4220e59a 100644
--- a/modules/ixBidAdapter.js
+++ b/modules/ixBidAdapter.js
@@ -128,6 +128,9 @@ function parseBid(rawBid, currency, bidRequest) {
bid.meta.networkId = utils.deepAccess(rawBid, 'ext.dspid');
bid.meta.brandId = utils.deepAccess(rawBid, 'ext.advbrandid');
bid.meta.brandName = utils.deepAccess(rawBid, 'ext.advbrand');
+ if (rawBid.adomain && rawBid.adomain.length > 0) {
+ bid.meta.advertiserDomains = rawBid.adomain;
+ }
return bid;
}
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/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js
index b331448161e..4b1c162c67c 100644
--- a/modules/livewrappedAnalyticsAdapter.js
+++ b/modules/livewrappedAnalyticsAdapter.js
@@ -34,6 +34,9 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
cache.auctions[args.auctionId].timeStamp = args.start;
args.bids.forEach(function(bidRequest) {
+ cache.auctions[args.auctionId].gdprApplies = args.gdprConsent ? args.gdprConsent.gdprApplies : undefined;
+ cache.auctions[args.auctionId].gdprConsent = args.gdprConsent ? args.gdprConsent.consentString : undefined;
+
cache.auctions[args.auctionId].bids[bidRequest.bidId] = {
bidder: bidRequest.bidder,
adUnit: bidRequest.adUnitCode,
@@ -116,9 +119,11 @@ livewrappedAnalyticsAdapter.enableAnalytics = function (config) {
};
livewrappedAnalyticsAdapter.sendEvents = function() {
+ var sentRequests = getSentRequests();
var events = {
publisherId: initOptions.publisherId,
- requests: getSentRequests(),
+ gdpr: sentRequests.gdpr,
+ requests: sentRequests.sentRequests,
responses: getResponses(),
wins: getWins(),
timeouts: getTimeouts(),
@@ -144,10 +149,23 @@ function getAdblockerRecovered() {
function getSentRequests() {
var sentRequests = [];
+ var gdpr = [];
Object.keys(cache.auctions).forEach(auctionId => {
+ let auction = cache.auctions[auctionId];
+ var gdprPos = 0;
+ for (gdprPos = 0; gdprPos < gdpr.length; gdprPos++) {
+ if (gdpr[gdprPos].gdprApplies == auction.gdprApplies &&
+ gdpr[gdprPos].gdprConsent == auction.gdprConsent) {
+ break;
+ }
+ }
+
+ if (gdprPos == gdpr.length) {
+ gdpr[gdprPos] = {gdprApplies: auction.gdprApplies, gdprConsent: auction.gdprConsent};
+ }
+
Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
- let auction = cache.auctions[auctionId];
let bid = auction.bids[bidId];
if (!(bid.sendStatus & REQUESTSENT)) {
bid.sendStatus |= REQUESTSENT;
@@ -155,13 +173,14 @@ function getSentRequests() {
sentRequests.push({
timeStamp: auction.timeStamp,
adUnit: bid.adUnit,
- bidder: bid.bidder
+ bidder: bid.bidder,
+ gdpr: gdprPos
});
}
});
});
- return sentRequests;
+ return {gdpr: gdpr, sentRequests: sentRequests};
}
function getResponses() {
diff --git a/modules/malltvBidAdapter.js b/modules/malltvBidAdapter.js
new file mode 100644
index 00000000000..4cdb5d45328
--- /dev/null
+++ b/modules/malltvBidAdapter.js
@@ -0,0 +1,91 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+
+const BIDDER_CODE = 'malltv';
+const ENDPOINT_URL = 'https://central.mall.tv/bid';
+const DIMENSION_SEPARATOR = 'x';
+const SIZE_SEPARATOR = ';';
+
+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) {
+ return !!(bid.params.propertyId && bid.params.placementId);
+ },
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {validBidRequests[]} - an array of bids
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ let response = validBidRequests.map(bidRequest => {
+ let sizes = generateSizeParam(bidRequest.sizes);
+ let propertyId = bidRequest.params.propertyId;
+ let placementId = bidRequest.params.placementId;
+ let adUnitId = bidRequest.adUnitCode;
+ let pageViewGuid = bidRequest.params.pageViewGuid || '';
+ let contents = bidRequest.params.contents || [];
+ const body = {
+ sizes: sizes,
+ adUnitId: adUnitId,
+ placementId: placementId,
+ propertyId: propertyId,
+ pageViewGuid: pageViewGuid,
+ url: bidderRequest ? bidderRequest.refererInfo.referer : '',
+ requestid: bidRequest.bidderRequestId,
+ bidid: bidRequest.bidId,
+ contents: contents
+ };
+ return {
+ method: 'POST',
+ url: ENDPOINT_URL,
+ data: body
+ };
+ });
+ return response
+ },
+ /**
+ * 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) {
+ window.adnResponse = serverResponse;
+ const responses = serverResponse.body;
+ const bidResponses = [];
+ for (var i = 0; i < responses.length; i++) {
+ const bidResponse = {
+ requestId: bidRequest.data.bidid,
+ cpm: responses[i].CPM,
+ width: responses[i].Width,
+ height: responses[i].Height,
+ creativeId: responses[i].CreativeId,
+ currency: responses[i].Currency,
+ netRevenue: responses[i].NetRevenue,
+ ttl: responses[i].TTL,
+ referrer: responses[i].Referrer,
+ ad: responses[i].Ad
+ };
+ bidResponses.push(bidResponse);
+ }
+ return bidResponses;
+ }
+}
+
+/**
+* Generate size param for bid request using sizes array
+*
+* @param {Array} sizes Possible sizes for the ad unit.
+* @return {string} Processed sizes param to be used for the bid request.
+*/
+function generateSizeParam(sizes) {
+ return sizes.map(size => size.join(DIMENSION_SEPARATOR)).join(SIZE_SEPARATOR);
+}
+
+registerBidder(spec);
diff --git a/modules/malltvBidAdapter.md b/modules/malltvBidAdapter.md
new file mode 100644
index 00000000000..72db0cef6c7
--- /dev/null
+++ b/modules/malltvBidAdapter.md
@@ -0,0 +1,51 @@
+# Overview
+Module Name: MallTV Bidder Adapter Module
+Type: Bidder Adapter
+Maintainer: drilon@gjirafa.com
+
+# Description
+MallTV Bidder Adapter for Prebid.js.
+
+# Test Parameters
+var adUnits = [
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 300]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'malltv',
+ params: {
+ propertyId: '105134',
+ placementId: '846832'
+ }
+ }
+ ]
+ },
+ {
+ code: 'test-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250], [300, 300]]
+ }
+ },
+ bids: [
+ {
+ bidder: 'malltv',
+ params: {
+ propertyId: '105134',
+ placementId: '846832',
+ contents: [ //optional
+ {
+ type: 'video',
+ id: '123'
+ }
+ ]
+ }
+ }
+ ]
+ }
+];
diff --git a/modules/mediaforceBidAdapter.js b/modules/mediaforceBidAdapter.js
index fc0c44f6d82..7e5c06b1b48 100644
--- a/modules/mediaforceBidAdapter.js
+++ b/modules/mediaforceBidAdapter.js
@@ -1,30 +1,113 @@
import * as utils from '../src/utils.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
-import {BANNER} from '../src/mediaTypes.js';
+import {BANNER, NATIVE} from '../src/mediaTypes.js';
const BIDDER_CODE = 'mediaforce';
const ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid';
+const TEST_ENDPOINT_URL = 'https://rtb.mfadsrvr.com/header_bid?debug_key=abcdefghijklmnop';
+const NATIVE_ID_MAP = {};
+const NATIVE_PARAMS = {
+ title: {
+ id: 1,
+ name: 'title'
+ },
+ icon: {
+ id: 2,
+ type: 1,
+ name: 'img'
+ },
+ image: {
+ id: 3,
+ type: 3,
+ name: 'img'
+ },
+ body: {
+ id: 4,
+ name: 'data',
+ type: 2
+ },
+ sponsoredBy: {
+ id: 5,
+ name: 'data',
+ type: 1
+ },
+ cta: {
+ id: 6,
+ type: 12,
+ name: 'data'
+ },
+ body2: {
+ id: 7,
+ name: 'data',
+ type: 10
+ },
+ rating: {
+ id: 8,
+ name: 'data',
+ type: 3
+ },
+ likes: {
+ id: 9,
+ name: 'data',
+ type: 4
+ },
+ downloads: {
+ id: 10,
+ name: 'data',
+ type: 5
+ },
+ displayUrl: {
+ id: 11,
+ name: 'data',
+ type: 11
+ },
+ price: {
+ id: 12,
+ name: 'data',
+ type: 6
+ },
+ salePrice: {
+ id: 13,
+ name: 'data',
+ type: 7
+ },
+ address: {
+ id: 14,
+ name: 'data',
+ type: 9
+ },
+ phone: {
+ id: 15,
+ name: 'data',
+ type: 8
+ }
+};
+
+Object.keys(NATIVE_PARAMS).forEach((key) => {
+ NATIVE_ID_MAP[NATIVE_PARAMS[key].id] = key;
+});
export const spec = {
code: BIDDER_CODE,
- supportedMediaTypes: [BANNER],
+ supportedMediaTypes: [BANNER, NATIVE],
/**
- * 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.
- */
+ * 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) {
return !!((typeof bid.params === 'object') && bid.params.placement_id && bid.params.publisher_id);
},
/**
- * Make a server request from the list of BidRequests.
- *
- * @param {validBidRequests[]} - an array of bids
- * @return ServerRequest Info describing the request to the server.
- */
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {BidRequest[]} validBidRequests - an array of bids
+ * @param {bidderRequest} bidderRequest bidder request object
+ * @return ServerRequest Info describing the request to the server.
+ */
buildRequests: function(validBidRequests, bidderRequest) {
if (validBidRequests.length === 0) {
return;
@@ -32,11 +115,12 @@ export const spec = {
const referer = bidderRequest && bidderRequest.refererInfo ? encodeURIComponent(bidderRequest.refererInfo.referer) : '';
const dnt = utils.getDNT() ? 1 : 0;
- let imp = [];
- let requests = []
+ let requests = [];
validBidRequests.forEach(bid => {
let tagid = bid.params.placement_id;
let bidfloor = bid.params.bidfloor ? parseFloat(bid.params.bidfloor) : 0;
+ let imp = [];
+ let validImp = false;
let impObj = {
id: bid.bidId,
tagid: tagid,
@@ -47,11 +131,16 @@ export const spec = {
switch (mediaTypes) {
case BANNER:
impObj.banner = createBannerRequest(bid);
- imp.push(impObj);
+ validImp = true;
+ break;
+ case NATIVE:
+ impObj.native = createNativeRequest(bid);
+ validImp = true;
break;
default: return;
}
}
+ validImp && imp.push(impObj);
let request = {
id: bid.transactionId,
@@ -73,7 +162,7 @@ export const spec = {
};
requests.push({
method: 'POST',
- url: ENDPOINT_URL,
+ url: bid.params.is_test ? TEST_ENDPOINT_URL : ENDPOINT_URL,
data: JSON.stringify(request)
});
});
@@ -81,11 +170,12 @@ export const spec = {
},
/**
- * 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.
- */
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @param {BidRequest} bidRequest
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
interpretResponse: function(serverResponse, bidRequest) {
if (!serverResponse || !serverResponse.body) {
return [];
@@ -99,15 +189,35 @@ export const spec = {
const bid = {
requestId: serverBid.impid,
cpm: parseFloat(serverBid.price),
- width: serverBid.w,
- height: serverBid.h,
creativeId: serverBid.adid,
currency: cur,
netRevenue: true,
ttl: serverBid.ttl || 300,
- ad: serverBid.adm,
burl: serverBid.burl,
};
+ if (serverBid.dealid) {
+ bid.dealId = serverBid.dealid;
+ }
+ let jsonAdm;
+ let adm = serverBid.adm;
+ let ext = serverBid.ext;
+ try {
+ jsonAdm = JSON.parse(adm);
+ } catch (err) {}
+ if (jsonAdm && jsonAdm.native) {
+ ext = ext || {};
+ ext.native = jsonAdm.native;
+ adm = null;
+ }
+ if (adm) {
+ bid.width = serverBid.w;
+ bid.height = serverBid.h;
+ bid.ad = adm;
+ bid.mediaType = BANNER;
+ } else if (ext && ext.native) {
+ bid.native = parseNative(ext.native);
+ bid.mediaType = NATIVE;
+ }
bidResponses.push(bid);
})
@@ -117,9 +227,9 @@ export const spec = {
},
/**
- * Register bidder specific code, which will execute if a bid from this bidder won the auction
- * @param {Bid} The bid that won the auction
- */
+ * 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) {
const cpm = utils.deepAccess(bid, 'adserverTargeting.hb_pb') || '';
if (utils.isStr(bid.burl) && bid.burl !== '') {
@@ -127,7 +237,8 @@ export const spec = {
utils.triggerPixel(bid.burl);
}
},
-}
+};
+
registerBidder(spec);
function getLanguage() {
@@ -149,3 +260,73 @@ function createBannerRequest(bid) {
}
return r
}
+
+function parseNative(native) {
+ const {assets, link, imptrackers, jstracker} = native;
+ const result = {
+ clickUrl: link.url,
+ clickTrackers: link.clicktrackers || [],
+ impressionTrackers: imptrackers || [],
+ javascriptTrackers: jstracker ? [jstracker] : []
+ };
+
+ (assets || []).forEach((asset) => {
+ const {id, img, data, title} = asset;
+ const key = NATIVE_ID_MAP[id];
+ if (key) {
+ if (!utils.isEmpty(title)) {
+ result.title = title.text
+ } else if (!utils.isEmpty(img)) {
+ result[key] = {
+ url: img.url,
+ height: img.h,
+ width: img.w
+ }
+ } else if (!utils.isEmpty(data)) {
+ result[key] = data.value;
+ }
+ }
+ });
+
+ return result;
+}
+
+function createNativeRequest(bid) {
+ const assets = [];
+ if (bid.nativeParams) {
+ Object.keys(bid.nativeParams).forEach((key) => {
+ if (NATIVE_PARAMS[key]) {
+ const {name, type, id} = NATIVE_PARAMS[key];
+ const assetObj = type ? {type} : {};
+ let {len, sizes, required, aspect_ratios: aRatios} = bid.nativeParams[key];
+ if (len) {
+ assetObj.len = len;
+ }
+ if (aRatios && aRatios[0]) {
+ aRatios = aRatios[0];
+ let wmin = aRatios.min_width || 0;
+ let hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0;
+ assetObj.wmin = wmin;
+ assetObj.hmin = hmin;
+ }
+ if (sizes && sizes.length) {
+ sizes = [].concat(...sizes);
+ assetObj.w = sizes[0];
+ assetObj.h = sizes[1];
+ }
+ const asset = {required: required ? 1 : 0, id};
+ asset[name] = assetObj;
+ assets.push(asset);
+ }
+ });
+ }
+ return {
+ ver: '1.2',
+ request: {
+ assets: assets,
+ context: 1,
+ plcmttype: 1,
+ ver: '1.2'
+ }
+ }
+}
diff --git a/modules/mediaforceBidAdapter.md b/modules/mediaforceBidAdapter.md
index e16d4178b3f..f8e6903516f 100644
--- a/modules/mediaforceBidAdapter.md
+++ b/modules/mediaforceBidAdapter.md
@@ -33,3 +33,36 @@ Module that connects to mediaforce's demand sources
}
];
```
+
+```
+ var adUnits = [
+ {
+ code: 'test-div',
+ mediaTypes: {
+ native: {
+ title: {
+ required: true,
+ len: 800
+ },
+ image: {
+ required: true,
+ sizes: [420, 315],
+ },
+ sponsoredBy: {
+ required: false
+ }
+ }
+ },
+ bids: [
+ {
+ bidder: "mediaforce",
+ params: {
+ placement_id: 'pl12345', // required
+ publisher_id: 'pub111', // required
+ is_test: true
+ }
+ }
+ ]
+ }
+ ];
+```
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/medianetBidAdapter.js b/modules/medianetBidAdapter.js
index 5cc18acb424..5decaa148e3 100644
--- a/modules/medianetBidAdapter.js
+++ b/modules/medianetBidAdapter.js
@@ -1,7 +1,7 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import * as utils from '../src/utils.js';
import { config } from '../src/config.js';
-import { BANNER, NATIVE } from '../src/mediaTypes.js';
+import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
import { getRefererInfo } from '../src/refererDetection.js';
const BIDDER_CODE = 'medianet';
@@ -21,7 +21,7 @@ let refererInfo = getRefererInfo();
let mnData = {};
mnData.urlData = {
- domain: utils.parseUrl(refererInfo.referer).host,
+ domain: utils.parseUrl(refererInfo.referer).hostname,
page: refererInfo.referer,
isTop: refererInfo.reachedTop
}
@@ -131,11 +131,16 @@ function getCoordinates(id) {
return null;
}
-function extParams(params, gdpr, uspConsent, userId) {
- let windowSize = spec.getWindowSize();
- let gdprApplies = !!(gdpr && gdpr.gdprApplies);
- let uspApplies = !!(uspConsent);
- let coppaApplies = !!(config.getConfig('coppa'));
+function extParams(bidRequest, bidderRequests) {
+ const params = utils.deepAccess(bidRequest, 'params');
+ const gdpr = utils.deepAccess(bidderRequests, 'gdprConsent');
+ const uspConsent = utils.deepAccess(bidderRequests, 'uspConsent');
+ const userId = utils.deepAccess(bidRequest, 'userId');
+ const sChain = utils.deepAccess(bidRequest, 'schain') || {};
+ const windowSize = spec.getWindowSize();
+ const gdprApplies = !!(gdpr && gdpr.gdprApplies);
+ const uspApplies = !!(uspConsent);
+ const coppaApplies = !!(config.getConfig('coppa'));
return Object.assign({},
{ customer_id: params.cid },
{ prebid_version: $$PREBID_GLOBAL$$.version },
@@ -146,7 +151,8 @@ function extParams(params, gdpr, uspConsent, userId) {
{coppa_applies: coppaApplies},
windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize },
userId && { user_id: userId },
- $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true }
+ $$PREBID_GLOBAL$$.medianetGlobals.analyticsEnabled && { analytics: true },
+ !utils.isEmpty(sChain) && {schain: sChain}
);
}
@@ -160,7 +166,15 @@ function slotParams(bidRequest) {
},
all: bidRequest.params
};
- let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes || [];
+ let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || [];
+
+ const videoInMediaType = utils.deepAccess(bidRequest, 'mediaTypes.video') || {};
+ const videoInParams = utils.deepAccess(bidRequest, 'params.video') || {};
+ const videoCombinedObj = Object.assign({}, videoInParams, videoInMediaType);
+
+ if (!utils.isEmpty(videoCombinedObj)) {
+ params.video = videoCombinedObj;
+ }
if (bannerSizes.length > 0) {
params.banner = transformSizes(bannerSizes);
@@ -246,7 +260,7 @@ function getBidderURL(cid) {
function generatePayload(bidRequests, bidderRequests) {
return {
site: siteDetails(bidRequests[0].params.site),
- ext: extParams(bidRequests[0].params, bidderRequests.gdprConsent, bidderRequests.uspConsent, bidRequests[0].userId),
+ ext: extParams(bidRequests[0], bidderRequests),
id: bidRequests[0].auctionId,
imp: bidRequests.map(request => slotParams(request)),
tmax: bidderRequests.timeout || config.getConfig('bidderTimeout')
@@ -305,7 +319,7 @@ export const spec = {
code: BIDDER_CODE,
gvlid: 142,
- supportedMediaTypes: [BANNER, NATIVE],
+ supportedMediaTypes: [BANNER, NATIVE, VIDEO],
/**
* Determines whether or not the given bid request is valid.
diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md
index 8259c32c9d3..b465d678fc9 100644
--- a/modules/medianetBidAdapter.md
+++ b/modules/medianetBidAdapter.md
@@ -14,19 +14,24 @@ This adapter currently only supports Banner Ads.
# Sample Ad Unit: For Publishers
```javascript
var adUnits = [{
- code: 'media.net-hb-ad-123456-1',
- sizes: [
- [300, 250],
- [300, 600],
- ],
- bids: [{
- bidder: 'medianet',
- params: {
- cid: '',
- bidfloor: '',
- crid: ''
- }
- }]
+ code: 'media.net-hb-ad-123456-1',
+ mediaTypes: {
+ banner: {
+ sizes: [
+ [728, 90],
+ [300, 600],
+ [300, 250]
+ ],
+ }
+ },
+ bids: [{
+ bidder: 'medianet',
+ params: {
+ cid: '',
+ bidfloor: '',
+ crid: ''
+ }
+ }]
}];
```
@@ -35,69 +40,146 @@ var adUnits = [{
```html
```
+# Ad Unit and Setup: For Testing (Video Instream)
-# Ad Unit and Setup: For Testing (Native)
+```html
+
+
+
+```
+# Ad Unit and Setup: For Testing (Video Outstream)
```html
+```
+
+# Ad Unit and Setup: For Testing (Native)
+
+```html
+
+
+
+
diff --git a/modules/mediasquareBidAdapter.js b/modules/mediasquareBidAdapter.js
index cb52c288caf..288526d3cc5 100644
--- a/modules/mediasquareBidAdapter.js
+++ b/modules/mediasquareBidAdapter.js
@@ -136,7 +136,7 @@ export const spec = {
// fires a pixel to confirm a winning bid
let params = [];
let endpoint = document.location.search.match(/msq_test=true/) ? BIDDER_URL_TEST : BIDDER_URL_PROD;
- let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond']
+ let paramsToSearchFor = ['cpm', 'size', 'mediaType', 'currency', 'creativeId', 'adUnitCode', 'timeToRespond', 'auctionId', 'requestId']
if (bid.hasOwnProperty('mediasquare')) {
if (bid['mediasquare'].hasOwnProperty('bidder')) { params.push('bidder=' + bid['mediasquare']['bidder']); }
if (bid['mediasquare'].hasOwnProperty('code')) { params.push('code=' + bid['mediasquare']['code']); }
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/openxAnalyticsAdapter.js b/modules/openxAnalyticsAdapter.js
index 7addfe68bc6..3ee8ab588a9 100644
--- a/modules/openxAnalyticsAdapter.js
+++ b/modules/openxAnalyticsAdapter.js
@@ -1,260 +1,730 @@
import adapter from '../src/AnalyticsAdapter.js';
import CONSTANTS from '../src/constants.json';
import adapterManager from '../src/adapterManager.js';
-import { config } from '../src/config.js';
import { ajax } from '../src/ajax.js';
-import * as utils from '../src/utils.js';
+import find from 'core-js-pure/features/array/find.js';
+import includes from 'core-js-pure/features/array/includes.js';
+const utils = require('../src/utils.js');
+
+export const AUCTION_STATES = {
+ INIT: 'initialized', // auction has initialized
+ ENDED: 'ended', // all auction requests have been accounted for
+ COMPLETED: 'completed' // all slots have rendered
+};
+
+const ADAPTER_VERSION = '0.1';
+const SCHEMA_VERSION = '0.1';
+
+const AUCTION_END_WAIT_TIME = 1000;
+const URL_PARAM = '';
+const ANALYTICS_TYPE = 'endpoint';
+const ENDPOINT = 'https://prebid.openx.net/ox/analytics/';
+// Event Types
const {
- EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, BID_WON }
+ EVENTS: { AUCTION_INIT, BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, AUCTION_END, BID_WON }
} = CONSTANTS;
-
const SLOT_LOADED = 'slotOnload';
-const ENDPOINT = 'https://ads.openx.net/w/1.0/pban';
+const UTM_TAGS = [
+ 'utm_campaign',
+ 'utm_source',
+ 'utm_medium',
+ 'utm_term',
+ 'utm_content'
+];
+const UTM_TO_CAMPAIGN_PROPERTIES = {
+ 'utm_campaign': 'name',
+ 'utm_source': 'source',
+ 'utm_medium': 'medium',
+ 'utm_term': 'term',
+ 'utm_content': 'content'
+};
-let initOptions;
+/**
+ * @typedef {Object} OxAnalyticsConfig
+ * @property {string} orgId
+ * @property {string} publisherPlatformId
+ * @property {number} publisherAccountId
+ * @property {number} sampling
+ * @property {boolean} enableV2
+ * @property {boolean} testPipeline
+ * @property {Object} campaign
+ * @property {number} payloadWaitTime
+ * @property {number} payloadWaitTimePadding
+ * @property {Array} adUnits
+ */
+
+/**
+ * @type {OxAnalyticsConfig}
+ */
+const DEFAULT_ANALYTICS_CONFIG = {
+ orgId: void (0),
+ publisherPlatformId: void (0),
+ publisherAccountId: void (0),
+ sampling: 0.05, // default sampling rate of 5%
+ testCode: 'default',
+ campaign: {},
+ adUnits: [],
+ payloadWaitTime: AUCTION_END_WAIT_TIME,
+ payloadWaitTimePadding: 2000
+};
+// Initialization
+/**
+ * @type {OxAnalyticsConfig}
+ */
+let analyticsConfig;
let auctionMap = {};
+let auctionOrder = 1; // tracks the number of auctions ran on the page
-function onAuctionInit({ auctionId }) {
- auctionMap[auctionId] = {
- adUnitMap: {}
- };
+let googletag = window.googletag || {};
+googletag.cmd = googletag.cmd || [];
+
+let openxAdapter = Object.assign(adapter({ urlParam: URL_PARAM, analyticsType: ANALYTICS_TYPE }));
+
+openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics;
+
+openxAdapter.enableAnalytics = function(adapterConfig = {options: {}}) {
+ if (isValidConfig(adapterConfig)) {
+ analyticsConfig = {...DEFAULT_ANALYTICS_CONFIG, ...adapterConfig.options};
+
+ // campaign properties defined by config will override utm query parameters
+ analyticsConfig.campaign = {...buildCampaignFromUtmCodes(), ...analyticsConfig.campaign};
+
+ utils.logInfo('OpenX Analytics enabled with config', analyticsConfig);
+
+ // override track method with v2 handlers
+ openxAdapter.track = prebidAnalyticsEventHandler;
+
+ googletag.cmd.push(function () {
+ let pubads = googletag.pubads();
+
+ if (pubads.addEventListener) {
+ pubads.addEventListener(SLOT_LOADED, args => {
+ openxAdapter.track({eventType: SLOT_LOADED, args});
+ utils.logInfo('OX: SlotOnLoad event triggered');
+ });
+ }
+ });
+
+ openxAdapter.originEnableAnalytics(adapterConfig);
+ }
+};
+
+adapterManager.registerAnalyticsAdapter({
+ adapter: openxAdapter,
+ code: 'openx'
+});
+
+export default openxAdapter;
+
+/**
+ * Test Helper Functions
+ */
+
+// reset the cache for unit tests
+openxAdapter.reset = function() {
+ auctionMap = {};
+ auctionOrder = 1;
+};
+
+/**
+ * Private Functions
+ */
+
+function isValidConfig({options: analyticsOptions}) {
+ let hasOrgId = analyticsOptions && analyticsOptions.orgId !== void (0);
+
+ const fieldValidations = [
+ // tuple of property, type, required
+ ['orgId', 'string', hasOrgId],
+ ['publisherPlatformId', 'string', !hasOrgId],
+ ['publisherAccountId', 'number', !hasOrgId],
+ ['sampling', 'number', false],
+ ['enableV2', 'boolean', false],
+ ['testPipeline', 'boolean', false],
+ ['adIdKey', 'string', false],
+ ['payloadWaitTime', 'number', false],
+ ['payloadWaitTimePadding', 'number', false],
+ ];
+
+ let failedValidation = find(fieldValidations, ([property, type, required]) => {
+ // if required, the property has to exist
+ // if property exists, type check value
+ return (required && !analyticsOptions.hasOwnProperty(property)) ||
+ /* eslint-disable valid-typeof */
+ (analyticsOptions.hasOwnProperty(property) && typeof analyticsOptions[property] !== type);
+ });
+ if (failedValidation) {
+ let [property, type, required] = failedValidation;
+
+ if (required) {
+ utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to exist and of type '${type}'`);
+ } else {
+ utils.logError(`OpenXAnalyticsAdapter: Expected '${property}' to be type '${type}'`);
+ }
+ }
+
+ return !failedValidation;
}
-function onBidRequested({ auctionId, auctionStart, bids, start }) {
- const adUnitMap = auctionMap[auctionId]['adUnitMap'];
+function buildCampaignFromUtmCodes() {
+ let campaign = {};
+ let queryParams = utils.parseQS(utils.getWindowLocation() && utils.getWindowLocation().search);
- bids.forEach(bid => {
- const { adUnitCode, bidId, bidder, params, transactionId } = bid;
+ UTM_TAGS.forEach(function(utmKey) {
+ let utmValue = queryParams[utmKey];
+ if (utmValue) {
+ let key = UTM_TO_CAMPAIGN_PROPERTIES[utmKey];
+ campaign[key] = utmValue;
+ }
+ });
+ return campaign;
+}
+
+function detectMob() {
+ if (
+ navigator.userAgent.match(/Android/i) ||
+ navigator.userAgent.match(/webOS/i) ||
+ navigator.userAgent.match(/iPhone/i) ||
+ navigator.userAgent.match(/iPad/i) ||
+ navigator.userAgent.match(/iPod/i) ||
+ navigator.userAgent.match(/BlackBerry/i) ||
+ navigator.userAgent.match(/Windows Phone/i)
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+function detectOS() {
+ if (navigator.userAgent.indexOf('Android') != -1) return 'Android';
+ if (navigator.userAgent.indexOf('like Mac') != -1) return 'iOS';
+ if (navigator.userAgent.indexOf('Win') != -1) return 'Windows';
+ if (navigator.userAgent.indexOf('Mac') != -1) return 'Macintosh';
+ if (navigator.userAgent.indexOf('Linux') != -1) return 'Linux';
+ if (navigator.appVersion.indexOf('X11') != -1) return 'Unix';
+ return 'Others';
+}
- adUnitMap[adUnitCode] = adUnitMap[adUnitCode] || {
- auctionId,
- auctionStart,
- transactionId,
- bidMap: {}
+function detectBrowser() {
+ var isChrome =
+ /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
+ var isCriOS = navigator.userAgent.match('CriOS');
+ var isSafari =
+ /Safari/.test(navigator.userAgent) &&
+ /Apple Computer/.test(navigator.vendor);
+ var isFirefox = /Firefox/.test(navigator.userAgent);
+ var isIE =
+ /Trident/.test(navigator.userAgent) || /MSIE/.test(navigator.userAgent);
+ var isEdge = /Edge/.test(navigator.userAgent);
+ if (isIE) return 'Internet Explorer';
+ if (isEdge) return 'Microsoft Edge';
+ if (isCriOS) return 'Chrome';
+ if (isSafari) return 'Safari';
+ if (isFirefox) return 'Firefox';
+ if (isChrome) return 'Chrome';
+ return 'Others';
+}
+
+function prebidAnalyticsEventHandler({eventType, args}) {
+ utils.logMessage(eventType, Object.assign({}, args));
+ switch (eventType) {
+ case AUCTION_INIT:
+ onAuctionInit(args);
+ break;
+ case BID_REQUESTED:
+ onBidRequested(args);
+ break;
+ case BID_RESPONSE:
+ onBidResponse(args);
+ break;
+ case BID_TIMEOUT:
+ onBidTimeout(args);
+ break;
+ case AUCTION_END:
+ onAuctionEnd(args);
+ break;
+ case BID_WON:
+ onBidWon(args);
+ break;
+ case SLOT_LOADED:
+ onSlotLoadedV2(args);
+ break;
+ }
+}
+
+/**
+ * @typedef {Object} PbAuction
+ * @property {string} auctionId - Auction ID of the request this bid responded to
+ * @property {number} timestamp //: 1586675964364
+ * @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364
+ * @property {string} auctionStatus //: "inProgress"
+ * @property {Array} adUnits //: [{…}]
+ * @property {string} adUnitCodes //: ["video1"]
+ * @property {string} labels //: undefined
+ * @property {Array} bidderRequests //: (2) [{…}, {…}]
+ * @property {Array} noBids //: []
+ * @property {Array} bidsReceived //: []
+ * @property {Array} winningBids //: []
+ * @property {number} timeout //: 3000
+ * @property {Object} config //: {publisherPlatformId: "a3aece0c-9e80-4316-8deb-faf804779bd1", publisherAccountId: 537143056, sampling: 1, enableV2: true}/*
+ */
+
+function onAuctionInit({auctionId, timestamp: startTime, timeout, adUnitCodes}) {
+ auctionMap[auctionId] = {
+ id: auctionId,
+ startTime,
+ endTime: void (0),
+ timeout,
+ auctionOrder,
+ userIds: [],
+ adUnitCodesCount: adUnitCodes.length,
+ adunitCodesRenderedCount: 0,
+ state: AUCTION_STATES.INIT,
+ auctionSendDelayTimer: void (0),
+ };
+
+ // setup adunit properties in map
+ auctionMap[auctionId].adUnitCodeToAdUnitMap = adUnitCodes.reduce((obj, adunitCode) => {
+ obj[adunitCode] = {
+ code: adunitCode,
+ adPosition: void (0),
+ bidRequestsMap: {}
};
+ return obj;
+ }, {});
+
+ auctionOrder++;
+}
- adUnitMap[adUnitCode]['bidMap'][bidId] = {
+/**
+ * @typedef {Object} PbBidRequest
+ * @property {string} auctionId - Auction ID of the request this bid responded to
+ * @property {number} auctionStart //: 1586675964364
+ * @property {Object} refererInfo
+ * @property {PbBidderRequest} bids
+ * @property {number} start - Start timestamp of the bidder request
+ *
+ */
+
+/**
+ * @typedef {Object} PbBidderRequest
+ * @property {string} adUnitCode - Name of div or google adunit path
+ * @property {string} bidder - Bame of bidder
+ * @property {string} bidId - Identifies the bid request
+ * @property {Object} mediaTypes
+ * @property {Object} params
+ * @property {string} src
+ * @property {Object} userId - Map of userId module to module object
+ */
+
+/**
+ * Tracks the bid request
+ * @param {PbBidRequest} bidRequest
+ */
+function onBidRequested(bidRequest) {
+ const {auctionId, bids: bidderRequests, start} = bidRequest;
+ const auction = auctionMap[auctionId];
+ const adUnitCodeToAdUnitMap = auction.adUnitCodeToAdUnitMap;
+
+ bidderRequests.forEach(bidderRequest => {
+ const { adUnitCode, bidder, bidId: requestId, mediaTypes, params, src, userId } = bidderRequest;
+
+ auction.userIds.push(userId);
+ adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId] = {
bidder,
params,
- requestTimestamp: start
+ mediaTypes,
+ source: src,
+ startTime: start,
+ timedOut: false,
+ bids: {}
};
});
}
-function onBidResponse({
- auctionId,
- adUnitCode,
- requestId: bidId,
- cpm,
- creativeId,
- responseTimestamp,
- ts,
- adId
-}) {
- const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode];
- const bid = adUnit['bidMap'][bidId];
- bid.cpm = cpm;
- bid.creativeId = creativeId;
- bid.responseTimestamp = responseTimestamp;
- bid.ts = ts;
- bid.adId = adId;
+/**
+ *
+ * @param {BidResponse} bidResponse
+ */
+function onBidResponse(bidResponse) {
+ let {
+ auctionId,
+ adUnitCode,
+ requestId,
+ cpm,
+ creativeId,
+ requestTimestamp,
+ responseTimestamp,
+ ts,
+ mediaType,
+ dealId,
+ ttl,
+ netRevenue,
+ currency,
+ originalCpm,
+ originalCurrency,
+ width,
+ height,
+ timeToRespond: latency,
+ adId,
+ meta
+ } = bidResponse;
+
+ auctionMap[auctionId].adUnitCodeToAdUnitMap[adUnitCode].bidRequestsMap[requestId].bids[adId] = {
+ cpm,
+ creativeId,
+ requestTimestamp,
+ responseTimestamp,
+ ts,
+ adId,
+ meta,
+ mediaType,
+ dealId,
+ ttl,
+ netRevenue,
+ currency,
+ originalCpm,
+ originalCurrency,
+ width,
+ height,
+ latency,
+ winner: false,
+ rendered: false,
+ renderTime: 0,
+ };
}
function onBidTimeout(args) {
- utils
- ._map(args, value => value)
- .forEach(({ auctionId, adUnitCode, bidId }) => {
- const bid =
- auctionMap[auctionId]['adUnitMap'][adUnitCode]['bidMap'][bidId];
- bid.timedOut = true;
- });
+ utils._each(args, ({auctionId, adUnitCode, bidId: requestId}) => {
+ let timedOutRequest = utils.deepAccess(auctionMap,
+ `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}`);
+
+ if (timedOutRequest) {
+ timedOutRequest.timedOut = true;
+ }
+ });
}
+/**
+ *
+ * @param {PbAuction} endedAuction
+ */
+function onAuctionEnd(endedAuction) {
+ let auction = auctionMap[endedAuction.auctionId];
+
+ if (!auction) {
+ return;
+ }
-function onBidWon({ auctionId, adUnitCode, requestId: bidId }) {
- const adUnit = auctionMap[auctionId]['adUnitMap'][adUnitCode];
- const bid = adUnit['bidMap'][bidId];
- bid.won = true;
+ clearAuctionTimer(auction);
+ auction.endTime = endedAuction.auctionEnd;
+ auction.state = AUCTION_STATES.ENDED;
+ delayedSend(auction);
}
-function onSlotLoaded({ slot }) {
- const targeting = slot.getTargetingKeys().reduce((targeting, key) => {
- targeting[key] = slot.getTargeting(key);
- return targeting;
- }, {});
- utils.logMessage(
- 'GPT slot is loaded. Current targeting set on slot:',
- targeting
- );
+/**
+ *
+ * @param {BidResponse} bidResponse
+ */
+function onBidWon(bidResponse) {
+ const { auctionId, adUnitCode, requestId, adId } = bidResponse;
+ let winningBid = utils.deepAccess(auctionMap,
+ `${auctionId}.adUnitCodeToAdUnitMap.${adUnitCode}.bidRequestsMap.${requestId}.bids.${adId}`);
+
+ if (winningBid) {
+ winningBid.winner = true
+ }
+}
- const adId = slot.getTargeting('hb_adid')[0];
- if (!adId) {
- return;
+/**
+ *
+ * @param {GoogleTagSlot} slot
+ * @param {string} serviceName
+ */
+function onSlotLoadedV2({ slot }) {
+ const renderTime = Date.now();
+ const elementId = slot.getSlotElementId();
+ const bidId = slot.getTargeting('hb_adid')[0];
+
+ let [auction, adUnit, bid] = getPathToBidResponseByBidId(bidId);
+
+ if (!auction) {
+ // attempt to get auction by adUnitCode
+ auction = getAuctionByGoogleTagSLot(slot);
+
+ if (!auction) {
+ return; // slot is not participating in an active prebid auction
+ }
}
- const adUnit = getAdUnitByAdId(adId);
- if (!adUnit) {
- return;
+ clearAuctionTimer(auction);
+
+ // track that an adunit code has completed within an auction
+ auction.adunitCodesRenderedCount++;
+
+ // mark adunit as rendered
+ if (bid) {
+ let {x, y} = getPageOffset();
+ bid.rendered = true;
+ bid.renderTime = renderTime;
+ adUnit.adPosition = isAtf(elementId, x, y) ? 'ATF' : 'BTF';
}
- const adUnitData = getAdUnitData(adUnit);
- const performanceData = getPerformanceData(adUnit.auctionStart);
- const commonFields = {
- 'hb.asiid': slot.getAdUnitPath(),
- 'hb.cur': config.getConfig('currency.adServerCurrency'),
- 'hb.pubid': initOptions.publisherId
- };
+ if (auction.adunitCodesRenderedCount === auction.adUnitCodesCount) {
+ auction.state = AUCTION_STATES.COMPLETED;
+ }
- const data = Object.assign({}, adUnitData, performanceData, commonFields);
- sendEvent(data);
+ // prepare to send regardless if auction is complete or not as a failsafe in case not all events are tracked
+ // add additional padding when not all slots are rendered
+ delayedSend(auction);
}
-function getAdUnitByAdId(adId) {
- let result;
+function isAtf(elementId, scrollLeft = 0, scrollTop = 0) {
+ let elem = document.querySelector('#' + elementId);
+ let isAtf = false;
+ if (elem) {
+ let bounding = elem.getBoundingClientRect();
+ if (bounding) {
+ let windowWidth = (window.innerWidth || document.documentElement.clientWidth);
+ let windowHeight = (window.innerHeight || document.documentElement.clientHeight);
+
+ // intersection coordinates
+ let left = Math.max(0, bounding.left + scrollLeft);
+ let right = Math.min(windowWidth, bounding.right + scrollLeft);
+ let top = Math.max(0, bounding.top + scrollTop);
+ let bottom = Math.min(windowHeight, bounding.bottom + scrollTop);
+
+ let intersectionWidth = right - left;
+ let intersectionHeight = bottom - top;
+
+ let intersectionArea = (intersectionHeight > 0 && intersectionWidth > 0) ? (intersectionHeight * intersectionWidth) : 0;
+ let adSlotArea = (bounding.right - bounding.left) * (bounding.bottom - bounding.top);
+
+ if (adSlotArea > 0) {
+ // Atleast 50% of intersection in window
+ isAtf = intersectionArea * 2 >= adSlotArea;
+ }
+ }
+ } else {
+ utils.logWarn('OX: DOM element not for id ' + elementId);
+ }
+ return isAtf;
+}
- utils._map(auctionMap, value => value).forEach(auction => {
- utils._map(auction.adUnitMap, value => value).forEach(adUnit => {
- utils._map(adUnit.bidMap, value => value).forEach(bid => {
- if (adId === bid.adId) {
- result = adUnit;
- }
- })
- });
- });
+// backwards compatible pageOffset from https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX
+function getPageOffset() {
+ var x = (window.pageXOffset !== undefined)
+ ? window.pageXOffset
+ : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
- return result;
+ var y = (window.pageYOffset !== undefined)
+ ? window.pageYOffset
+ : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+ return {x, y};
}
-function getAdUnitData(adUnit) {
- const bids = utils._map(adUnit.bidMap, value => value);
- const bidders = bids.map(bid => bid.bidder);
- const requestTimes = bids.map(
- bid => bid.requestTimestamp && bid.requestTimestamp - adUnit.auctionStart
- );
- const responseTimes = bids.map(
- bid => bid.responseTimestamp && bid.responseTimestamp - adUnit.auctionStart
- );
- const bidValues = bids.map(bid => bid.cpm || 0);
- const timeouts = bids.map(bid => !!bid.timedOut);
- const creativeIds = bids.map(bid => bid.creativeId);
- const winningBid = bids.filter(bid => bid.won)[0];
- const winningExchangeIndex = bids.indexOf(winningBid);
- const openxBid = bids.filter(bid => bid.bidder === 'openx')[0];
+function delayedSend(auction) {
+ const delayTime = auction.adunitCodesRenderedCount === auction.adUnitCodesCount
+ ? analyticsConfig.payloadWaitTime
+ : analyticsConfig.payloadWaitTime + analyticsConfig.payloadWaitTimePadding;
- return {
- 'hb.ct': adUnit.auctionStart,
- 'hb.rid': adUnit.auctionId,
- 'hb.exn': bidders.join(','),
- 'hb.sts': requestTimes.join(','),
- 'hb.ets': responseTimes.join(','),
- 'hb.bv': bidValues.join(','),
- 'hb.to': timeouts.join(','),
- 'hb.crid': creativeIds.join(','),
- 'hb.we': winningExchangeIndex,
- 'hb.g1': winningExchangeIndex === -1,
- dddid: adUnit.transactionId,
- ts: openxBid && openxBid.ts,
- auid: openxBid && openxBid.params && openxBid.params.unit
- };
+ auction.auctionSendDelayTimer = setTimeout(() => {
+ let payload = JSON.stringify([buildAuctionPayload(auction)]);
+ ajax(ENDPOINT, deleteAuctionMap, payload, { contentType: 'application/json' });
+
+ function deleteAuctionMap() {
+ delete auctionMap[auction.id];
+ }
+ }, delayTime);
}
-function getPerformanceData(auctionStart) {
- let timing;
- try {
- timing = window.top.performance.timing;
- } catch (e) {}
+function clearAuctionTimer(auction) {
+ // reset the delay timer to send the auction data
+ if (auction.auctionSendDelayTimer) {
+ clearTimeout(auction.auctionSendDelayTimer);
+ auction.auctionSendDelayTimer = void (0);
+ }
+}
- if (!timing) {
- return;
+/**
+ * Returns the path to a bid (auction, adunit, bidRequest, and bid) based on a bidId
+ * @param {string} bidId
+ * @returns {Array<*>}
+ */
+function getPathToBidResponseByBidId(bidId) {
+ let auction;
+ let adUnit;
+ let bidResponse;
+
+ if (!bidId) {
+ return [];
}
- const { fetchStart, domContentLoadedEventEnd, loadEventEnd } = timing;
- const domContentLoadTime = domContentLoadedEventEnd - fetchStart;
- const pageLoadTime = loadEventEnd - fetchStart;
- const timeToAuction = auctionStart - fetchStart;
- const timeToRender = Date.now() - fetchStart;
+ utils._each(auctionMap, currentAuction => {
+ // skip completed auctions
+ if (currentAuction.state === AUCTION_STATES.COMPLETED) {
+ return;
+ }
- return {
- 'hb.dcl': domContentLoadTime,
- 'hb.dl': pageLoadTime,
- 'hb.tta': timeToAuction,
- 'hb.ttr': timeToRender
- };
+ utils._each(currentAuction.adUnitCodeToAdUnitMap, (currentAdunit) => {
+ utils._each(currentAdunit.bidRequestsMap, currentBiddRequest => {
+ utils._each(currentBiddRequest.bids, (currentBidResponse, bidResponseId) => {
+ if (bidId === bidResponseId) {
+ auction = currentAuction;
+ adUnit = currentAdunit;
+ bidResponse = currentBidResponse;
+ }
+ });
+ });
+ });
+ });
+ return [auction, adUnit, bidResponse];
}
-function sendEvent(data) {
- utils._map(data, (value, key) => [key, value]).forEach(([key, value]) => {
- if (
- value === undefined ||
- value === null ||
- (typeof value === 'number' && isNaN(value))
- ) {
- delete data[key];
+function getAuctionByGoogleTagSLot(slot) {
+ let slotAdunitCodes = [slot.getSlotElementId(), slot.getAdUnitPath()];
+ let slotAuction;
+
+ utils._each(auctionMap, auction => {
+ if (auction.state === AUCTION_STATES.COMPLETED) {
+ return;
}
+
+ utils._each(auction.adUnitCodeToAdUnitMap, (bidderRequestIdMap, adUnitCode) => {
+ if (includes(slotAdunitCodes, adUnitCode)) {
+ slotAuction = auction;
+ }
+ });
});
- ajax(ENDPOINT, null, data, { method: 'GET' });
+
+ return slotAuction;
}
-let googletag = window.googletag || {};
-googletag.cmd = googletag.cmd || [];
-googletag.cmd.push(function() {
- googletag.pubads().addEventListener(SLOT_LOADED, args => {
- openxAdapter.track({ eventType: SLOT_LOADED, args });
- });
-});
+function buildAuctionPayload(auction) {
+ let {startTime, endTime, state, timeout, auctionOrder, userIds, adUnitCodeToAdUnitMap} = auction;
+ let {orgId, publisherPlatformId, publisherAccountId, campaign} = analyticsConfig;
-const openxAdapter = Object.assign(
- adapter({ url: ENDPOINT, analyticsType: 'endpoint' }),
- {
- track({ eventType, args }) {
- utils.logMessage(eventType, Object.assign({}, args));
- switch (eventType) {
- case AUCTION_INIT:
- onAuctionInit(args);
- break;
- case BID_REQUESTED:
- onBidRequested(args);
- break;
- case BID_RESPONSE:
- onBidResponse(args);
- break;
- case BID_TIMEOUT:
- onBidTimeout(args);
- break;
- case BID_WON:
- onBidWon(args);
- break;
- case SLOT_LOADED:
- onSlotLoaded(args);
- break;
+ return {
+ adapterVersion: ADAPTER_VERSION,
+ schemaVersion: SCHEMA_VERSION,
+ orgId,
+ publisherPlatformId,
+ publisherAccountId,
+ campaign,
+ state,
+ startTime,
+ endTime,
+ timeLimit: timeout,
+ auctionOrder,
+ deviceType: detectMob() ? 'Mobile' : 'Desktop',
+ deviceOSType: detectOS(),
+ browser: detectBrowser(),
+ testCode: analyticsConfig.testCode,
+ // return an array of module name that have user data
+ userIdProviders: buildUserIdProviders(userIds),
+ adUnits: buildAdUnitsPayload(adUnitCodeToAdUnitMap),
+ };
+
+ function buildAdUnitsPayload(adUnitCodeToAdUnitMap) {
+ return utils._map(adUnitCodeToAdUnitMap, (adUnit) => {
+ let {code, adPosition} = adUnit;
+
+ return {
+ code,
+ adPosition,
+ bidRequests: buildBidRequestPayload(adUnit.bidRequestsMap)
+ };
+
+ function buildBidRequestPayload(bidRequestsMap) {
+ return utils._map(bidRequestsMap, (bidRequest) => {
+ let {bidder, source, bids, mediaTypes, timedOut} = bidRequest;
+ return {
+ bidder,
+ source,
+ hasBidderResponded: Object.keys(bids).length > 0,
+ availableAdSizes: getMediaTypeSizes(mediaTypes),
+ availableMediaTypes: getMediaTypes(mediaTypes),
+ timedOut,
+ bidResponses: utils._map(bidRequest.bids, (bidderBidResponse) => {
+ let {
+ cpm,
+ creativeId,
+ ts,
+ meta,
+ mediaType,
+ dealId,
+ ttl,
+ netRevenue,
+ currency,
+ width,
+ height,
+ latency,
+ winner,
+ rendered,
+ renderTime
+ } = bidderBidResponse;
+
+ return {
+ microCpm: cpm * 1000000,
+ netRevenue,
+ currency,
+ mediaType,
+ height,
+ width,
+ size: `${width}x${height}`,
+ dealId,
+ latency,
+ ttl,
+ winner,
+ creativeId,
+ ts,
+ rendered,
+ renderTime,
+ meta
+ }
+ })
+ }
+ });
}
- }
+ });
}
-);
-
-// save the base class function
-openxAdapter.originEnableAnalytics = openxAdapter.enableAnalytics;
-// override enableAnalytics so we can get access to the config passed in from the page
-openxAdapter.enableAnalytics = function(config) {
- if (!config || !config.options || !config.options.publisherId) {
- utils.logError('OpenX analytics adapter: publisherId is required.');
- return;
+ function buildUserIdProviders(userIds) {
+ return utils._map(userIds, (userId) => {
+ return utils._map(userId, (id, module) => {
+ return hasUserData(module, id) ? module : false
+ }).filter(module => module);
+ }).reduce(utils.flatten, []).filter(utils.uniques).sort();
}
- initOptions = config.options;
- openxAdapter.originEnableAnalytics(config); // call the base class function
-};
-// reset the cache for unit tests
-openxAdapter.reset = function() {
- auctionMap = {};
-};
+ function hasUserData(module, idOrIdObject) {
+ let normalizedId;
+
+ switch (module) {
+ case 'digitrustid':
+ normalizedId = utils.deepAccess(idOrIdObject, 'data.id');
+ break;
+ case 'lipb':
+ normalizedId = idOrIdObject.lipbid;
+ break;
+ default:
+ normalizedId = idOrIdObject;
+ }
-adapterManager.registerAnalyticsAdapter({
- adapter: openxAdapter,
- code: 'openx'
-});
+ return !utils.isEmpty(normalizedId);
+ }
-export default openxAdapter;
+ function getMediaTypeSizes(mediaTypes) {
+ return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => {
+ return utils.parseSizesInput(mediaTypeConfig.sizes)
+ .map(size => `${mediaType}_${size}`);
+ }).reduce(utils.flatten, []);
+ }
+
+ function getMediaTypes(mediaTypes) {
+ return utils._map(mediaTypes, (mediaTypeConfig, mediaType) => mediaType);
+ }
+}
diff --git a/modules/openxAnalyticsAdapter.md b/modules/openxAnalyticsAdapter.md
index ac739f36c76..af40486f2a4 100644
--- a/modules/openxAnalyticsAdapter.md
+++ b/modules/openxAnalyticsAdapter.md
@@ -102,11 +102,13 @@ Configuration options are a follows:
| Property | Type | Required? | Description | Example |
|:---|:---|:---|:---|:---|
-| `publisherPlatformId` | `string` | Yes | Used to determine ownership of data. | `a3aece0c-9e80-4316-8deb-faf804779bd1` |
-| `publisherAccountId` | `number` | Yes | Used to determine ownership of data. | `1537143056` |
+| `orgId` | `string` | Yes | Used to determine ownership of data. | `aa1bb2cc-3dd4-4316-8deb-faf804779bd1` |
+| `publisherPlatformId` | `string` | No **__Deprecated. Please use orgId__** | Used to determine ownership of data. | `a3aece0c-9e80-4316-8deb-faf804779bd1` |
+| `publisherAccountId` | `number` | No **__Deprecated. Please use orgId__** | Used to determine ownership of data. | `1537143056` |
| `sampling` | `number` | Yes | Sampling rate | Undefined or `1.00` - No sampling. Analytics is sent all the time. 0.5 - 50% of users will send analytics data. |
| `testCode` | `string` | No | Used to label analytics data for the purposes of tests. This label is treated as a dimension and can be compared against other labels. | `timeout_config_1` `timeout_config_2` `timeout_default` |
-
+| `campaign` | `Object` | No | Object with 5 parameters: content medium name source term Each parameter is a free-form string. Refer to metrics doc on when to use these fields. By setting a value to one of these properties, you override the associated url utm query parameter. | |
+| `payloadWaitTime` | `number` | No | Delay after all slots of an auction renders before the payload is sent. Defaults to 100ms | 1000 |
---
# Viewing Data
@@ -114,7 +116,7 @@ The Prebid Report available in the Reporting in the Cloud tool, allows you to vi
**To view your data:**
-1. Log in to Reporting in the Cloud.
+1. Log in to [OpenX Reporting](https://openx.sigmoid.io/app).
2. In the top right, click on the **View** list and then select **Prebidreport**.
diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js
index d5630c2fad4..f4b6288cd55 100644
--- a/modules/openxBidAdapter.js
+++ b/modules/openxBidAdapter.js
@@ -6,7 +6,9 @@ import {BANNER, VIDEO} from '../src/mediaTypes.js';
const SUPPORTED_AD_TYPES = [BANNER, VIDEO];
const BIDDER_CODE = 'openx';
const BIDDER_CONFIG = 'hb_pb';
-const BIDDER_VERSION = '3.0.2';
+const BIDDER_VERSION = '3.0.3';
+
+const DEFAULT_CURRENCY = 'USD';
export const USER_ID_CODE_TO_QUERY_ARG = {
britepoolid: 'britepoolid', // BritePool ID
@@ -16,7 +18,7 @@ export const USER_ID_CODE_TO_QUERY_ARG = {
idl_env: 'lre', // LiveRamp IdentityLink
lipb: 'lipbid', // LiveIntent ID
netId: 'netid', // netID
- parrableid: 'parrableid', // Parrable ID
+ parrableId: 'parrableid', // Parrable ID
pubcid: 'pubcid', // PubCommon ID
tdid: 'ttduuid', // The Trade Desk Unified ID
};
@@ -276,6 +278,9 @@ function appendUserIdsToQueryParams(queryParams, userIds) {
case 'lipb':
queryParams[key] = userIdObjectOrValue.lipbid;
break;
+ case 'parrableId':
+ queryParams[key] = userIdObjectOrValue.eid;
+ break;
default:
queryParams[key] = userIdObjectOrValue;
}
@@ -336,8 +341,10 @@ function buildOXBannerRequest(bids, bidderRequest) {
let customFloorsForAllBids = [];
let hasCustomFloor = false;
bids.forEach(function (bid) {
- if (bid.params.customFloor) {
- customFloorsForAllBids.push((Math.round(bid.params.customFloor * 100) / 100) * 1000);
+ let floor = getBidFloor(bid, BANNER);
+
+ if (floor) {
+ customFloorsForAllBids.push(floor);
hasCustomFloor = true;
} else {
customFloorsForAllBids.push(0);
@@ -416,6 +423,10 @@ function generateVideoParameters(bid, bidderRequest) {
queryParams.vmimes = oxVideoConfig.mimes;
}
+ if (bid.params.test) {
+ queryParams.vtest = 1;
+ }
+
return queryParams;
}
@@ -449,4 +460,20 @@ function createVideoBidResponses(response, {bid, startTime}) {
return bidResponses;
}
+function getBidFloor(bidRequest, mediaType) {
+ let floorInfo = {};
+ const currency = config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY;
+
+ if (typeof bidRequest.getFloor === 'function') {
+ floorInfo = bidRequest.getFloor({
+ currency: currency,
+ mediaType: mediaType,
+ size: '*'
+ });
+ }
+ let floor = floorInfo.floor || bidRequest.params.customFloor || 0;
+
+ return Math.round(floor * 1000); // normalize to microCpm
+}
+
registerBidder(spec);
diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js
index d14e2bebd72..e01746af487 100644
--- a/modules/orbidderBidAdapter.js
+++ b/modules/orbidderBidAdapter.js
@@ -1,18 +1,20 @@
import {registerBidder} from '../src/adapters/bidderFactory.js';
import { getStorageManager } from '../src/storageManager.js';
-const storage = getStorageManager();
+const storageManager = getStorageManager();
export const spec = {
code: 'orbidder',
- orbidderHost: (() => {
- let ret = 'https://orbidder.otto.de';
+ hostname: 'https://orbidder.otto.de',
+
+ getHostname() {
+ let ret = this.hostname;
try {
- ret = storage.getDataFromLocalStorage('ov_orbidder_host') || ret;
+ ret = storageManager.getDataFromLocalStorage('ov_orbidder_host') || ret;
} catch (e) {
}
return ret;
- })(),
+ },
isBidRequestValid(bid) {
return !!(bid.sizes && bid.bidId && bid.params &&
@@ -23,6 +25,7 @@ export const spec = {
},
buildRequests(validBidRequests, bidderRequest) {
+ const hostname = this.getHostname();
return validBidRequests.map((bidRequest) => {
let referer = '';
if (bidderRequest && bidderRequest.refererInfo) {
@@ -30,7 +33,7 @@ export const spec = {
}
const ret = {
- url: `${spec.orbidderHost}/bid`,
+ url: `${hostname}/bid`,
method: 'POST',
options: { withCredentials: true },
data: {
diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js
index 87f140556d0..451ab654c53 100644
--- a/modules/ozoneBidAdapter.js
+++ b/modules/ozoneBidAdapter.js
@@ -539,7 +539,7 @@ export const spec = {
*/
findAllUserIds(bidRequest) {
var ret = {};
- let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableid', 'idl_env', 'digitrustid', 'criteortus'];
+ let searchKeysSingle = ['pubcid', 'tdid', 'id5id', 'parrableId', 'idl_env', 'digitrustid', 'criteortus'];
if (bidRequest.hasOwnProperty('userId')) {
for (let arrayId in searchKeysSingle) {
let key = searchKeysSingle[arrayId];
@@ -679,7 +679,7 @@ export const spec = {
this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1);
this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1);
this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1);
- this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableid`), 'parrable.com', 1);
+ this.addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableId.eid`), 'parrable.com', 1);
}
return eids;
},
diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js
index ec033e62983..2d6a2d6d6e5 100644
--- a/modules/parrableIdSystem.js
+++ b/modules/parrableIdSystem.js
@@ -113,6 +113,51 @@ function migrateLegacyCookies(parrableId) {
}
}
+function shouldFilterImpression(configParams, parrableId) {
+ const config = configParams.timezoneFilter;
+
+ if (!config) {
+ return false;
+ }
+
+ if (parrableId) {
+ return false;
+ }
+
+ const offset = (new Date()).getTimezoneOffset() / 60;
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
+
+ function isAllowed() {
+ if (utils.isEmpty(config.allowedZones) &&
+ utils.isEmpty(config.allowedOffsets)) {
+ return true;
+ }
+ if (utils.contains(config.allowedZones, zone)) {
+ return true;
+ }
+ if (utils.contains(config.allowedOffsets, offset)) {
+ return true;
+ }
+ return false;
+ }
+
+ function isBlocked() {
+ if (utils.isEmpty(config.blockedZones) &&
+ utils.isEmpty(config.blockedOffsets)) {
+ return false;
+ }
+ if (utils.contains(config.blockedZones, zone)) {
+ return true;
+ }
+ if (utils.contains(config.blockedOffsets, offset)) {
+ return true;
+ }
+ return false;
+ }
+
+ return !isAllowed() || isBlocked();
+}
+
function fetchId(configParams) {
if (!isValidConfig(configParams)) return;
@@ -122,6 +167,10 @@ function fetchId(configParams) {
migrateLegacyCookies(parrableId);
}
+ if (shouldFilterImpression(configParams, parrableId)) {
+ return null;
+ }
+
const eid = (parrableId) ? parrableId.eid : null;
const refererInfo = getRefererInfo();
const uspString = uspDataHandler.getConsentData();
@@ -204,7 +253,7 @@ export const parrableIdSubmodule = {
*/
decode(parrableId) {
if (parrableId && utils.isPlainObject(parrableId)) {
- return { 'parrableid': parrableId.eid };
+ return { parrableId };
}
return undefined;
},
diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js
index a5dcf1b1d3b..0ff967f1da9 100644
--- a/modules/prebidServerBidAdapter/index.js
+++ b/modules/prebidServerBidAdapter/index.js
@@ -240,27 +240,6 @@ function doClientSideSyncs(bidders) {
});
}
-function _getDigiTrustQueryParams(bidRequest = {}) {
- function getDigiTrustId(bidRequest) {
- const bidRequestDigitrust = utils.deepAccess(bidRequest, 'bids.0.userId.digitrustid.data');
- if (bidRequestDigitrust) {
- return bidRequestDigitrust;
- }
-
- const digiTrustUser = config.getConfig('digiTrustId');
- return (digiTrustUser && digiTrustUser.success && digiTrustUser.identity) || null;
- }
- let digiTrustId = getDigiTrustId(bidRequest);
- // Verify there is an ID and this user has not opted out
- if (!digiTrustId || (digiTrustId.privacy && digiTrustId.privacy.optout)) {
- return null;
- }
- return {
- id: digiTrustId.id,
- keyv: digiTrustId.keyv
- };
-}
-
function _appendSiteAppDevice(request, pageUrl) {
if (!request) return;
@@ -525,6 +504,9 @@ const OPEN_RTB_PROTOCOL = {
// Don't push oustream w/o renderer to request object.
utils.logError('Outstream bid without renderer cannot be sent to Prebid Server.');
} else {
+ if (videoParams.context === 'instream' && !videoParams.hasOwnProperty('placement')) {
+ videoParams.placement = 1;
+ }
mediaTypes['video'] = videoParams;
}
}
@@ -568,9 +550,20 @@ const OPEN_RTB_PROTOCOL = {
*/
const pbAdSlot = utils.deepAccess(adUnit, 'fpd.context.pbAdSlot');
if (typeof pbAdSlot === 'string' && pbAdSlot) {
- utils.deepSetValue(imp, 'ext.context.data.adslot', pbAdSlot);
+ utils.deepSetValue(imp, 'ext.context.data.pbadslot', pbAdSlot);
}
+ /**
+ * Copy GAM AdUnit and Name to imp
+ */
+ ['name', 'adSlot'].forEach(name => {
+ /** @type {(string|undefined)} */
+ const value = utils.deepAccess(adUnit, `fpd.context.adserver.${name}`);
+ if (typeof value === 'string' && value) {
+ utils.deepSetValue(imp, `ext.context.data.adserver.${name.toLowerCase()}`, value);
+ }
+ });
+
Object.assign(imp, mediaTypes);
// if storedAuctionResponse has been set, pass SRID
@@ -627,11 +620,6 @@ const OPEN_RTB_PROTOCOL = {
_appendSiteAppDevice(request, firstBidRequest.refererInfo.referer);
- const digiTrust = _getDigiTrustQueryParams(firstBidRequest);
- if (digiTrust) {
- utils.deepSetValue(request, 'user.ext.digitrust', digiTrust);
- }
-
// pass schain object if it is present
const schain = utils.deepAccess(bidRequests, '0.bids.0.schain');
if (schain) {
@@ -658,6 +646,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 6d35a0a74cc..1b865e05c0a 100644
--- a/modules/priceFloors.js
+++ b/modules/priceFloors.js
@@ -103,7 +103,7 @@ export function getFirstMatchingFloor(floorData, bidObject, responseObject = {})
// if we already have gotten the matching rule from this matching input then use it! No need to look again
let previousMatch = utils.deepAccess(floorData, `matchingInputs.${matchingInput}`);
if (previousMatch) {
- return previousMatch;
+ return {...previousMatch};
}
let allPossibleMatches = generatePossibleEnumerations(fieldValues, utils.deepAccess(floorData, 'schema.delimiter') || '|');
let matchingRule = find(allPossibleMatches, hashValue => floorData.values.hasOwnProperty(hashValue));
@@ -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);
}
@@ -286,9 +286,10 @@ export function updateAdUnitsForAuction(adUnits, floorData, auctionId) {
bid.auctionId = auctionId;
bid.floorData = {
skipped: floorData.skipped,
- modelVersion: utils.deepAccess(floorData, 'data.modelVersion'),
- location: utils.deepAccess(floorData, 'data.location'),
skipRate: floorData.skipRate,
+ modelVersion: utils.deepAccess(floorData, 'data.modelVersion'),
+ location: utils.deepAccess(floorData, 'data.location', 'noData'),
+ floorProvider: floorData.floorProvider,
fetchStatus: _floorsConfig.fetchStatus
}
});
@@ -513,6 +514,7 @@ export function handleFetchResponse(fetchResponse) {
_floorsConfig.data = fetchData;
// set skipRate override if necessary
_floorsConfig.skipRate = utils.isNumber(fetchData.skipRate) ? fetchData.skipRate : _floorsConfig.skipRate;
+ _floorsConfig.floorProvider = fetchData.floorProvider || _floorsConfig.floorProvider;
}
// if any auctions are waiting for fetch to finish, we need to continue them!
@@ -568,6 +570,7 @@ export function handleSetFloorsConfig(config) {
_floorsConfig = utils.pick(config, [
'enabled', enabled => enabled !== false, // defaults to true
'auctionDelay', auctionDelay => auctionDelay || 0,
+ 'floorProvider', floorProvider => utils.deepAccess(config, 'data.floorProvider', floorProvider),
'endpoint', endpoint => endpoint || {},
'skipRate', () => !isNaN(utils.deepAccess(config, 'data.skipRate')) ? config.data.skipRate : config.skipRate || 0,
'enforcement', enforcement => utils.pick(enforcement || {}, [
@@ -679,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/priceFloors.md b/modules/priceFloors.md
index d09be78c620..36ac07ee972 100644
--- a/modules/priceFloors.md
+++ b/modules/priceFloors.md
@@ -11,6 +11,7 @@ pbjs.setConfig({
enforceJS: true //defaults to true
},
auctionDelay: 150, // in milliseconds defaults to 0
+ floorProvider: 'awesomeFloorProviderName', // name of the floor provider (optional)
endpoint: {
url: 'http://localhost:1500/floor-domains',
method: 'GET' // Only get supported for now
@@ -40,6 +41,7 @@ pbjs.setConfig({
| enabled | Wether to turn off or on the floors module |
| enforcement | object of booleans which control certain features of the module |
| auctionDelay | The time to suspend and auction while waiting for a real time price floors fetch to come back |
+| floorProvider | A string identifying the floor provider.|
| endpoint | An object describing the endpoint to retrieve floor data from. GET only |
| data | The data to be used to select appropriate floors. See schema for more detail |
| additionalSchemaFields | An object of additional fields to be used in a floor data object. The schema is KEY: function to retrieve the match |
@@ -59,4 +61,4 @@ This function can takes in an object with the following optional parameters:
If a bid adapter passes in `*` as an attribute, then the `priceFloors` module will attempt to select the best rule based on context.
-For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it.
\ No newline at end of file
+For example, if an adapter passes in a `*`, but the bidRequest only has a single size and a single mediaType, then the `getFloor` function will attempt to get a rule for that size before matching with the `*` catch-all. Similarily, if mediaType can be inferred on the bidRequest, it will use it.
diff --git a/modules/projectLimeLightBidAdapter.js b/modules/projectLimeLightBidAdapter.js
index f2ff77f6229..1beba906917 100644
--- a/modules/projectLimeLightBidAdapter.js
+++ b/modules/projectLimeLightBidAdapter.js
@@ -4,7 +4,6 @@ import {ajax} from '../src/ajax.js';
import * as utils from '../src/utils.js';
const BIDDER_CODE = 'project-limelight';
-const URL = 'https://ads.project-limelight.com/hb';
/**
* Determines whether or not the given bid response is valid.
@@ -25,19 +24,6 @@ function isBidResponseValid(bid) {
return false;
}
-function extractBidSizes(bid) {
- const bidSizes = [];
-
- bid.sizes.forEach(size => {
- bidSizes.push({
- width: size[0],
- height: size[1]
- });
- });
-
- return bidSizes;
-}
-
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
@@ -65,30 +51,10 @@ export const spec = {
} catch (e) {
utils.logMessage(e);
winTop = window;
- };
- const placements = [];
- const request = {
- 'secure': (location.protocol === 'https:'),
- 'deviceWidth': winTop.screen.width,
- 'deviceHeight': winTop.screen.height,
- 'adUnits': placements
- };
- for (let i = 0; i < validBidRequests.length; i++) {
- const bid = validBidRequests[i];
- const params = bid.params;
- placements.push({
- id: params.adUnitId,
- bidId: bid.bidId,
- transactionId: bid.transactionId,
- sizes: extractBidSizes(bid),
- type: params.adUnitType.toUpperCase()
- });
}
- return {
- method: 'POST',
- url: URL,
- data: request
- };
+ const placements = utils.groupBy(validBidRequests.map(bidRequest => buildPlacement(bidRequest)), 'host')
+ return Object.keys(placements)
+ .map(host => buildRequest(winTop, host, placements[host].map(placement => placement.adUnit)));
},
onBidWon: (bid) => {
@@ -123,3 +89,34 @@ export const spec = {
};
registerBidder(spec);
+
+function buildRequest(winTop, host, adUnits) {
+ return {
+ method: 'POST',
+ url: `https://${host}/hb`,
+ data: {
+ secure: (location.protocol === 'https:'),
+ deviceWidth: winTop.screen.width,
+ deviceHeight: winTop.screen.height,
+ adUnits: adUnits
+ }
+ }
+}
+
+function buildPlacement(bidRequest) {
+ return {
+ host: bidRequest.params.host,
+ adUnit: {
+ id: bidRequest.params.adUnitId,
+ bidId: bidRequest.bidId,
+ transactionId: bidRequest.transactionId,
+ sizes: bidRequest.sizes.map(size => {
+ return {
+ width: size[0],
+ height: size[1]
+ }
+ }),
+ type: bidRequest.params.adUnitType.toUpperCase()
+ }
+ }
+}
diff --git a/modules/projectLimeLightBidAdapter.md b/modules/projectLimeLightBidAdapter.md
index 71621983b89..15aa170cd2e 100644
--- a/modules/projectLimeLightBidAdapter.md
+++ b/modules/projectLimeLightBidAdapter.md
@@ -18,6 +18,7 @@ Module that connects to Project Limelight SSP demand sources
bids: [{
bidder: 'project-limelight',
params: {
+ host: 'ads.project-limelight.com',
adUnitId: 0,
adUnitType: 'banner'
}
@@ -34,6 +35,7 @@ var videoAdUnit = [{
bids: [{
bidder: 'project-limelight',
params: {
+ host: 'ads.project-limelight.com',
adUnitId: 0,
adUnitType: 'video'
}
diff --git a/modules/pubCommonIdSystem.js b/modules/pubCommonIdSystem.js
index 8e2be1207f5..9516934de42 100644
--- a/modules/pubCommonIdSystem.js
+++ b/modules/pubCommonIdSystem.js
@@ -7,11 +7,14 @@
import * as utils from '../src/utils.js';
import {submodule} from '../src/hook.js';
+import {getStorageManager} from '../src/storageManager.js';
const PUB_COMMON_ID = 'PublisherCommonId';
const MODULE_NAME = 'pubCommonId';
+const storage = getStorageManager(null, 'pubCommonId');
+
/** @type {Submodule} */
export const pubCommonIdSubmodule = {
/**
@@ -90,6 +93,33 @@ export const pubCommonIdSubmodule = {
const callback = this.makeCallback(pixelUrl, storedId);
return callback ? {callback: callback} : {id: storedId};
}
+ },
+
+ /**
+ * @param {string} domain
+ * @param {HTMLDocument} document
+ * @return {(string|undefined)}
+ */
+ domainOverride: function () {
+ const domainElements = document.domain.split('.');
+ const cookieName = `_gd${Date.now()}`;
+ for (let i = 0, topDomain; i < domainElements.length; i++) {
+ const nextDomain = domainElements.slice(i).join('.');
+
+ // write test cookie
+ storage.setCookie(cookieName, '1', undefined, undefined, nextDomain);
+
+ // read test cookie to verify domain was valid
+ if (storage.getCookie(cookieName) === '1') {
+ // delete test cookie
+ storage.setCookie(cookieName, '', 'Thu, 01 Jan 1970 00:00:01 GMT', undefined, nextDomain);
+ // cookie was written successfully using test domain so the topDomain is updated
+ topDomain = nextDomain;
+ } else {
+ // cookie failed to write using test domain so exit by returning the topDomain
+ return topDomain;
+ }
+ }
}
};
diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js
index d42073b3da5..05f18f99a9a 100644
--- a/modules/pubgeniusBidAdapter.js
+++ b/modules/pubgeniusBidAdapter.js
@@ -16,7 +16,6 @@ import {
const BIDDER_VERSION = '1.0.0';
const BASE_URL = 'https://ortb.adpearl.io';
-const AUCTION_URL = BASE_URL + '/prebid/auction';
export const spec = {
code: 'pubgenius',
@@ -31,7 +30,7 @@ export const spec = {
}
const sizes = deepAccess(bid, 'mediaTypes.banner.sizes');
- return Boolean(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2));
+ return !!(sizes && sizes.length) && sizes.every(size => isArrayOfNums(size, 2));
},
buildRequests: function (bidRequests, bidderRequest) {
@@ -66,7 +65,7 @@ export const spec = {
deepSetValue(data, 'regs.ext.us_privacy', usp);
}
- const schain = bidderRequest.schain;
+ const schain = bidRequests[0].schain;
if (schain) {
deepSetValue(data, 'source.ext.schain', schain);
}
@@ -85,7 +84,7 @@ export const spec = {
return {
method: 'POST',
- url: AUCTION_URL,
+ url: `${getBaseUrl()}/prebid/auction`,
data,
};
},
@@ -128,7 +127,7 @@ export const spec = {
const qs = parseQueryStringParameters(params);
syncs.push({
type: 'iframe',
- url: `${BASE_URL}/usersync/pixels.html?${qs}`,
+ url: `${getBaseUrl()}/usersync/pixels.html?${qs}`,
});
}
@@ -136,7 +135,7 @@ export const spec = {
},
onTimeout(data) {
- ajax(`${BASE_URL}/prebid/events?type=timeout`, null, JSON.stringify(data), {
+ ajax(`${getBaseUrl()}/prebid/events?type=timeout`, null, JSON.stringify(data), {
method: 'POST',
});
},
@@ -170,14 +169,26 @@ function buildImp(bid) {
}
function buildSite(bidderRequest) {
- const pageUrl = config.getConfig('pageUrl') || bidderRequest.refererInfo.referer;
+ let site = null;
+ const { refererInfo } = bidderRequest;
+
+ const pageUrl = config.getConfig('pageUrl') || refererInfo.canonicalUrl || refererInfo.referer;
if (pageUrl) {
- return {
- page: pageUrl,
- };
+ site = site || {};
+ site.page = pageUrl;
+ }
+
+ if (refererInfo.reachedTop) {
+ try {
+ const pageRef = window.top.document.referrer;
+ if (pageRef) {
+ site = site || {};
+ site.ref = pageRef;
+ }
+ } catch (e) {}
}
- return null;
+ return site;
}
function interpretBid(bid) {
@@ -205,4 +216,9 @@ function numericBoolean(value) {
return value ? 1 : 0;
}
+function getBaseUrl() {
+ const pubg = config.getConfig('pubgenius');
+ return (pubg && pubg.endpoint) || BASE_URL;
+}
+
registerBidder(spec);
diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js
index a050d499640..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);
@@ -986,6 +1085,7 @@ export const spec = {
newBid.meta.buyerId = bid.ext.advid;
}
if (bid.adomain && bid.adomain.length > 0) {
+ newBid.meta.advertiserDomains = bid.adomain;
newBid.meta.clickUrl = bid.adomain[0];
}
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/pubperfAnalyticsAdapter.js b/modules/pubperfAnalyticsAdapter.js
new file mode 100644
index 00000000000..800ea1cd550
--- /dev/null
+++ b/modules/pubperfAnalyticsAdapter.js
@@ -0,0 +1,36 @@
+/**
+ * Analytics Adapter for Pubperf
+ */
+
+import adapter from '../src/AnalyticsAdapter.js';
+import adapterManager from '../src/adapterManager.js';
+import * as utils from '../src/utils.js';
+
+var pubperfAdapter = adapter({
+ global: 'pubperf_pbjs',
+ analyticsType: 'bundle',
+ handler: 'on'
+});
+
+pubperfAdapter.originEnableAnalytics = pubperfAdapter.enableAnalytics;
+
+pubperfAdapter.enableAnalytics = config => {
+ if (!config || !config.provider || config.provider !== 'pubperf') {
+ utils.logError('expected config.provider to equal pubperf');
+ return;
+ }
+ if (!window['pubperf_pbjs']) {
+ utils.logError(
+ `Make sure that Pubperf tag from https://www.pubperf.com is included before the Prebid configuration.`
+ );
+ return;
+ }
+ pubperfAdapter.originEnableAnalytics(config);
+}
+
+adapterManager.registerAnalyticsAdapter({
+ adapter: pubperfAdapter,
+ code: 'pubperf'
+});
+
+export default pubperfAdapter;
diff --git a/modules/pubperfAnalyticsAdapter.md b/modules/pubperfAnalyticsAdapter.md
new file mode 100644
index 00000000000..50ac3691dda
--- /dev/null
+++ b/modules/pubperfAnalyticsAdapter.md
@@ -0,0 +1,27 @@
+# Overview
+
+```
+Module Name: Pubperf Analytics Adapter
+Module Type: Analytics Adapter
+Maintainer: support@transfon.com
+```
+
+# Description
+
+Transfon's pubperf analytics adaptor allows you to view detailed auction and prebid information in Meridian. Contact support@transfon.com for more information or to sign up for analytics.
+
+For more information, please visit https://www.pubperf.com.
+
+
+# Sample pubperf tag to be placed before prebid tag
+
+```
+(function(i, s, o, g, r, a, m, z) {i['pubperf_pbjs'] = r;i[r] = i[r] || function() {z = Array.prototype.slice.call(arguments);z.unshift(+new Date());(i[r].q = i[r].q || []).push(z)}, i[r].t = 1, i[r].l = 1 * new Date();a = s.createElement(o),m = s.getElementsByTagName(o)[0];a.async = 1;a.src = g;m.parentNode.insertBefore(a, m)})(window, document, 'script', 'https://t.pubperf.com/t/b5a635e307.js', 'pubperf_pbjs');
+```
+
+# Test Parameters
+```
+{
+ provider: 'pubperf'
+}
+```
diff --git a/modules/pubwiseAnalyticsAdapter.js b/modules/pubwiseAnalyticsAdapter.js
index 915aeb58f99..74b56c21a2b 100644
--- a/modules/pubwiseAnalyticsAdapter.js
+++ b/modules/pubwiseAnalyticsAdapter.js
@@ -4,7 +4,6 @@ import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
import { getStorageManager } from '../src/storageManager.js';
const utils = require('../src/utils.js');
-
const storage = getStorageManager();
/****
@@ -17,30 +16,54 @@ const storage = getStorageManager();
pbjs.enableAnalytics({
provider: 'pubwise',
options: {
- site: 'test-test-test-test',
- endpoint: 'https://api.pubwise.io/api/v4/event/add/',
+ site: 'b1ccf317-a6fc-428d-ba69-0c9c208aa61c'
}
});
- */
+
+Changes in 4.0 Version
+4.0.1 - Initial Version for Prebid 4.x, adds activationId, adds additiona testing, removes prebid global in favor of a prebid.version const
+4.0.2 - Updates to include dedicated default site to keep everything from getting rate limited
+
+*/
const analyticsType = 'endpoint';
-const analyticsName = 'PubWise Analytics: ';
-let defaultUrl = 'https://api.pubwise.io/api/v4/event/default/';
-let pubwiseVersion = '3.0';
-let pubwiseSchema = 'AVOCET';
-let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v4/event/default/', debug: ''};
+const analyticsName = 'PubWise:';
+const prebidVersion = '$prebid.version$';
+let pubwiseVersion = '4.0.1';
+let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v5/event/add/', debug: null};
let pwAnalyticsEnabled = false;
let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''};
+let sessionData = {sessionId: '', activationId: ''};
+let pwNamespace = 'pubwise';
+let pwEvents = [];
+let metaData = {};
+let auctionEnded = false;
+let sessTimeout = 60 * 30 * 1000; // 30 minutes, G Analytics default session length
+let sessName = 'sess_id';
+let sessTimeoutName = 'sess_timeout';
-function markEnabled() {
- utils.logInfo(`${analyticsName}Enabled`, configOptions);
- pwAnalyticsEnabled = true;
+function enrichWithSessionInfo(dataBag) {
+ try {
+ // eslint-disable-next-line
+ // console.log(sessionData);
+ dataBag['session_id'] = sessionData.sessionId;
+ dataBag['activation_id'] = sessionData.activationId;
+ } catch (e) {
+ dataBag['error_sess'] = 1;
+ }
+
+ return dataBag;
}
function enrichWithMetrics(dataBag) {
try {
+ if (window.PREBID_TIMEOUT) {
+ dataBag['target_timeout'] = window.PREBID_TIMEOUT;
+ } else {
+ dataBag['target_timeout'] = 'NA';
+ }
dataBag['pw_version'] = pubwiseVersion;
- dataBag['pbjs_version'] = $$PREBID_GLOBAL$$.version;
+ dataBag['pbjs_version'] = prebidVersion;
dataBag['debug'] = configOptions.debug;
} catch (e) {
dataBag['error_metric'] = 1;
@@ -54,7 +77,7 @@ function enrichWithUTM(dataBag) {
try {
for (let prop in utmKeys) {
utmKeys[prop] = utils.getParameterByName(prop);
- if (utmKeys[prop] != '') {
+ if (utmKeys[prop]) {
newUtm = true;
dataBag[prop] = utmKeys[prop];
}
@@ -62,64 +85,246 @@ function enrichWithUTM(dataBag) {
if (newUtm === false) {
for (let prop in utmKeys) {
- let itemValue = storage.getDataFromLocalStorage(`pw-${prop}`);
- if (itemValue.length !== 0) {
+ let itemValue = storage.getDataFromLocalStorage(setNamespace(prop));
+ if (itemValue !== null && typeof itemValue !== 'undefined' && itemValue.length !== 0) {
dataBag[prop] = itemValue;
}
}
} else {
for (let prop in utmKeys) {
- storage.setDataInLocalStorage(`pw-${prop}`, utmKeys[prop]);
+ storage.setDataInLocalStorage(setNamespace(prop), utmKeys[prop]);
}
}
} catch (e) {
- utils.logInfo(`${analyticsName}Error`, e);
+ pwInfo(`Error`, e);
dataBag['error_utm'] = 1;
}
return dataBag;
}
-function sendEvent(eventType, data) {
- utils.logInfo(`${analyticsName}Event ${eventType} ${pwAnalyticsEnabled}`, data);
+function expireUtmData() {
+ pwInfo(`Session Expiring UTM Data`);
+ for (let prop in utmKeys) {
+ storage.removeDataFromLocalStorage(setNamespace(prop));
+ }
+}
+
+function enrichWithCustomSegments(dataBag) {
+ // c_script_type: '', c_slot1: '', c_slot2: '', c_slot3: '', c_slot4: ''
+ if (configOptions.custom) {
+ if (configOptions.custom.c_script_type) {
+ dataBag['c_script_type'] = configOptions.custom.c_script_type;
+ }
+
+ if (configOptions.custom.c_host) {
+ dataBag['c_host'] = configOptions.custom.c_host;
+ }
+
+ if (configOptions.custom.c_slot1) {
+ dataBag['c_slot1'] = configOptions.custom.c_slot1;
+ }
+
+ if (configOptions.custom.c_slot2) {
+ dataBag['c_slot2'] = configOptions.custom.c_slot2;
+ }
+
+ if (configOptions.custom.c_slot3) {
+ dataBag['c_slot3'] = configOptions.custom.c_slot3;
+ }
+
+ if (configOptions.custom.c_slot4) {
+ dataBag['c_slot4'] = configOptions.custom.c_slot4;
+ }
+ }
+
+ return dataBag;
+}
+
+function setNamespace(itemText) {
+ return pwNamespace.concat('_' + itemText);
+}
+
+function localStorageSessTimeoutName() {
+ return setNamespace(sessTimeoutName);
+}
+
+function localStorageSessName() {
+ return setNamespace(sessName);
+}
+
+function extendUserSessionTimeout() {
+ storage.setDataInLocalStorage(localStorageSessTimeoutName(), Date.now().toString());
+}
+
+function userSessionID() {
+ return storage.getDataFromLocalStorage(localStorageSessName()) ? localStorage.getItem(localStorageSessName()) : '';
+}
+
+function sessionExpired() {
+ let sessLastTime = storage.getDataFromLocalStorage(localStorageSessTimeoutName());
+ return (Date.now() - parseInt(sessLastTime)) > sessTimeout;
+}
+
+function flushEvents() {
+ if (pwEvents.length > 0) {
+ let dataBag = {metaData: metaData, eventList: pwEvents.splice(0)}; // put all the events together with the metadata and send
+ ajax(configOptions.endpoint, (result) => pwInfo(`Result`, result), JSON.stringify(dataBag));
+ }
+}
+
+function isIngestedEvent(eventType) {
+ const ingested = [
+ CONSTANTS.EVENTS.AUCTION_INIT,
+ CONSTANTS.EVENTS.BID_REQUESTED,
+ CONSTANTS.EVENTS.BID_RESPONSE,
+ CONSTANTS.EVENTS.BID_WON,
+ CONSTANTS.EVENTS.BID_TIMEOUT,
+ CONSTANTS.EVENTS.AD_RENDER_FAILED,
+ CONSTANTS.EVENTS.TCF2_ENFORCEMENT
+ ];
+ return ingested.indexOf(eventType) !== -1;
+}
- // put the typical items in the data bag
- let dataBag = {
- eventType: eventType,
- args: data,
- target_site: configOptions.site,
- pubwiseSchema: pubwiseSchema,
- debug: configOptions.debug ? 1 : 0,
- };
+function markEnabled() {
+ pwInfo(`Enabled`, configOptions);
+ pwAnalyticsEnabled = true;
+ setInterval(flushEvents, 100);
+}
+
+function pwInfo(info, context) {
+ utils.logInfo(`${analyticsName} ` + info, context);
+}
- dataBag = enrichWithMetrics(dataBag);
- // for certain events, track additional info
- if (eventType == CONSTANTS.EVENTS.AUCTION_INIT) {
- dataBag = enrichWithUTM(dataBag);
+function filterBidResponse(data) {
+ let modified = Object.assign({}, data);
+ // clean up some properties we don't track in public version
+ if (typeof modified.ad !== 'undefined') {
+ modified.ad = '';
}
+ if (typeof modified.adUrl !== 'undefined') {
+ modified.adUrl = '';
+ }
+ if (typeof modified.adserverTargeting !== 'undefined') {
+ modified.adserverTargeting = '';
+ }
+ if (typeof modified.ts !== 'undefined') {
+ modified.ts = '';
+ }
+ // clean up a property to make simpler
+ if (typeof modified.statusMessage !== 'undefined' && modified.statusMessage === 'Bid returned empty or error response') {
+ modified.statusMessage = 'eoe';
+ }
+ modified.auctionEnded = auctionEnded;
+ return modified;
+}
- ajax(configOptions.endpoint, (result) => utils.logInfo(`${analyticsName}Result`, result), JSON.stringify(dataBag));
+function filterAuctionInit(data) {
+ let modified = Object.assign({}, data);
+
+ modified.refererInfo = {};
+ // handle clean referrer, we only need one
+ if (typeof modified.bidderRequests !== 'undefined' && typeof modified.bidderRequests[0] !== 'undefined' && typeof modified.bidderRequests[0].refererInfo !== 'undefined') {
+ modified.refererInfo = modified.bidderRequests[0].refererInfo;
+ }
+
+ if (typeof modified.adUnitCodes !== 'undefined') {
+ delete modified.adUnitCodes;
+ }
+ if (typeof modified.adUnits !== 'undefined') {
+ delete modified.adUnits;
+ }
+ if (typeof modified.bidderRequests !== 'undefined') {
+ delete modified.bidderRequests;
+ }
+ if (typeof modified.bidsReceived !== 'undefined') {
+ delete modified.bidsReceived;
+ }
+ if (typeof modified.config !== 'undefined') {
+ delete modified.config;
+ }
+ if (typeof modified.noBids !== 'undefined') {
+ delete modified.noBids;
+ }
+ if (typeof modified.winningBids !== 'undefined') {
+ delete modified.winningBids;
+ }
+
+ return modified;
}
-let pubwiseAnalytics = Object.assign(adapter(
- {
- defaultUrl,
- analyticsType
- }),
-{
+let pubwiseAnalytics = Object.assign(adapter({analyticsType}), {
// Override AnalyticsAdapter functions by supplying custom methods
track({eventType, args}) {
- sendEvent(eventType, args);
+ this.handleEvent(eventType, args);
}
});
+pubwiseAnalytics.handleEvent = function(eventType, data) {
+ // we log most events, but some are information
+ if (isIngestedEvent(eventType)) {
+ pwInfo(`Emitting Event ${eventType} ${pwAnalyticsEnabled}`, data);
+
+ // record metadata
+ metaData = {
+ target_site: configOptions.site,
+ debug: configOptions.debug ? 1 : 0,
+ };
+ metaData = enrichWithSessionInfo(metaData);
+ metaData = enrichWithMetrics(metaData);
+ metaData = enrichWithUTM(metaData);
+ metaData = enrichWithCustomSegments(metaData);
+
+ // add data on init to the metadata container
+ if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) {
+ data = filterAuctionInit(data);
+ } else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) {
+ data = filterBidResponse(data);
+ }
+
+ // add all ingested events
+ pwEvents.push({
+ eventType: eventType,
+ args: data
+ });
+ } else {
+ pwInfo(`Skipping Event ${eventType} ${pwAnalyticsEnabled}`, data);
+ }
+
+ // once the auction ends, or the event is a bid won send events
+ if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) {
+ flushEvents();
+ }
+}
+
+pubwiseAnalytics.storeSessionID = function (userSessID) {
+ storage.setDataInLocalStorage(localStorageSessName(), userSessID);
+ pwInfo(`New Session Generated`, userSessID);
+};
+
+// ensure a session exists, if not make one, always store it
+pubwiseAnalytics.ensureSession = function () {
+ if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') {
+ let generatedId = utils.generateUUID();
+ expireUtmData();
+ this.storeSessionID(generatedId);
+ sessionData.sessionId = generatedId;
+ }
+ // eslint-disable-next-line
+ // console.log('ensured session');
+ extendUserSessionTimeout();
+};
+
pubwiseAnalytics.adapterEnableAnalytics = pubwiseAnalytics.enableAnalytics;
pubwiseAnalytics.enableAnalytics = function (config) {
- if (config.options.debug === undefined) {
- config.options.debug = utils.debugTurnedOn();
+ configOptions = Object.assign(configOptions, config.options);
+ // take the PBJS debug for our debug setting if no PW debug is defined
+ if (configOptions.debug === null) {
+ configOptions.debug = utils.debugTurnedOn();
}
- configOptions = config.options;
markEnabled();
+ sessionData.activationId = utils.generateUUID();
+ this.ensureSession();
pubwiseAnalytics.adapterEnableAnalytics(config);
};
diff --git a/modules/pubxBidAdapter.js b/modules/pubxBidAdapter.js
new file mode 100644
index 00000000000..44c95e8e19a
--- /dev/null
+++ b/modules/pubxBidAdapter.js
@@ -0,0 +1,47 @@
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+const BIDDER_CODE = 'pubx';
+const BID_ENDPOINT = 'https://api.primecaster.net/adlogue/api/slot/bid';
+export const spec = {
+ code: BIDDER_CODE,
+ isBidRequestValid: function(bid) {
+ if (!(bid.params.sid)) {
+ return false;
+ } else { return true }
+ },
+ buildRequests: function(validBidRequests) {
+ return validBidRequests.map(bidRequest => {
+ const bidId = bidRequest.bidId;
+ const params = bidRequest.params;
+ const sid = params.sid;
+ const payload = {
+ sid: sid
+ };
+ return {
+ id: bidId,
+ method: 'GET',
+ url: BID_ENDPOINT,
+ data: payload,
+ }
+ });
+ },
+ interpretResponse: function(serverResponse, bidRequest) {
+ const body = serverResponse.body;
+ const bidResponses = [];
+ if (body.cid) {
+ const bidResponse = {
+ requestId: bidRequest.id,
+ cpm: body.cpm,
+ currency: body.currency,
+ width: body.width,
+ height: body.height,
+ creativeId: body.cid,
+ netRevenue: true,
+ ttl: body.TTL,
+ ad: body.adm
+ };
+ bidResponses.push(bidResponse);
+ } else {};
+ return bidResponses;
+ }
+}
+registerBidder(spec);
diff --git a/modules/pubxBidAdapter.md b/modules/pubxBidAdapter.md
new file mode 100644
index 00000000000..da7d960c831
--- /dev/null
+++ b/modules/pubxBidAdapter.md
@@ -0,0 +1,32 @@
+# Overview
+
+Module Name: pubx Bid Adapter
+
+Maintainer: x@pub-x.io
+
+# Description
+
+Module that connects to Pub-X's demand sources
+Supported MediaTypes: banner only
+
+# Test Parameters
+```javascript
+ var adUnits = [
+ {
+ code: 'test',
+ mediaTypes: {
+ banner: {
+ sizes: [300,250]
+ }
+ },
+ bids: [
+ {
+ bidder: 'pubx',
+ params: {
+ sid: 'eDMR' //ID should be provided by Pub-X
+ }
+ }
+ ]
+ }
+ ];
+```
\ No newline at end of file
diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js
index 7bfa8686728..33fdaa44100 100644
--- a/modules/pulsepointBidAdapter.js
+++ b/modules/pulsepointBidAdapter.js
@@ -421,7 +421,7 @@ function user(bidRequest, bidderRequest) {
addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo');
addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink');
addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5-sync.com');
- addExternalUserId(ext.eids, bidRequest.userId.parrableid, 'parrable.com');
+ addExternalUserId(ext.eids, utils.deepAccess(bidRequest, 'userId.parrableId.eid'), 'parrable.com');
// liveintent
if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) {
addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com');
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/quantumBidAdapter.js b/modules/quantumBidAdapter.js
deleted file mode 100644
index f5da6e022f1..00000000000
--- a/modules/quantumBidAdapter.js
+++ /dev/null
@@ -1,320 +0,0 @@
-import * as utils from '../src/utils.js';
-import { BANNER, NATIVE } from '../src/mediaTypes.js';
-import {registerBidder} from '../src/adapters/bidderFactory.js';
-
-const BIDDER_CODE = 'quantum';
-const ENDPOINT_URL = 'https://s.sspqns.com/hb';
-export const spec = {
- code: BIDDER_CODE,
- aliases: ['quantx', 'qtx'], // short code
- supportedMediaTypes: [BANNER, NATIVE],
-
- /**
- * 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) {
- return !!(bid.params && bid.params.placementId);
- },
- /**
- * Make a server request from the list of BidRequests.
- *
- * @param {validBidRequests[]} - an array of bids
- * @return ServerRequest Info describing the request to the server.
- */
- buildRequests: function (bidRequests, bidderRequest) {
- return bidRequests.map(bid => {
- const qtxRequest = {};
- let bidId = '';
-
- const params = bid.params;
- let placementId = params.placementId;
-
- let devEnpoint = false;
- if (params.useDev && params.useDev === '1') {
- devEnpoint = 'https://sdev.sspqns.com/hb';
- }
- let renderMode = 'native';
- for (let i = 0; i < bid.sizes.length; i++) {
- if (bid.sizes[i][0] > 1 && bid.sizes[i][1] > 1) {
- renderMode = 'banner';
- break;
- }
- }
-
- let mediaType = (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) ? 'native' : 'banner';
-
- if (mediaType === 'native') {
- renderMode = 'native';
- }
-
- if (!bidId) {
- bidId = bid.bidId;
- }
- qtxRequest.auid = placementId;
-
- if (bidderRequest && bidderRequest.gdprConsent) {
- qtxRequest.quantx_user_consent_string = bidderRequest.gdprConsent.consentString;
- qtxRequest.quantx_gdpr = bidderRequest.gdprConsent.gdprApplies === true ? 1 : 0;
- };
-
- const url = devEnpoint || ENDPOINT_URL;
-
- return {
- method: 'GET',
- bidId: bidId,
- sizes: bid.sizes,
- mediaType: mediaType,
- renderMode: renderMode,
- url: url,
- 'data': qtxRequest,
- bidderRequest
- };
- });
- },
- /**
- * 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 serverBody = serverResponse.body;
- const bidResponses = [];
- let responseCPM;
- let bid = {};
- let id = bidRequest.bidId;
-
- if (serverBody.price && serverBody.price !== 0) {
- responseCPM = parseFloat(serverBody.price);
-
- bid.creativeId = serverBody.creative_id || '0';
- bid.cpm = responseCPM;
- bid.requestId = bidRequest.bidId;
- bid.width = 1;
- bid.height = 1;
- bid.ttl = 200;
- bid.netRevenue = true;
- bid.currency = 'USD';
-
- if (serverBody.native) {
- bid.native = serverBody.native;
- }
- if (serverBody.cobj) {
- bid.cobj = serverBody.cobj;
- }
- if (!utils.isEmpty(bidRequest.sizes)) {
- bid.width = bidRequest.sizes[0][0];
- bid.height = bidRequest.sizes[0][1];
- }
-
- bid.nurl = serverBody.nurl;
- bid.sync = serverBody.sync;
- if (bidRequest.renderMode && bidRequest.renderMode === 'banner') {
- bid.mediaType = 'banner';
- if (serverBody.native) {
- const adAssetsUrl = 'https://cdn.elasticad.net/native/serve/js/quantx/quantumAd/';
- let assets = serverBody.native.assets;
- let link = serverBody.native.link;
-
- let trackers = [];
- if (serverBody.native.imptrackers) {
- trackers = serverBody.native.imptrackers;
- }
-
- let jstracker = '';
- if (serverBody.native.jstracker) {
- jstracker = encodeURIComponent(serverBody.native.jstracker);
- }
-
- if (serverBody.nurl) {
- trackers.push(serverBody.nurl);
- }
-
- let ad = {};
- ad['trackers'] = trackers;
- ad['jstrackers'] = jstracker;
- ad['eventtrackers'] = serverBody.native.eventtrackers || [];
-
- for (let i = 0; i < assets.length; i++) {
- let asset = assets[i];
- switch (asset['id']) {
- case 1:
- ad['title'] = asset['title']['text'];
- break;
- case 2:
- ad['sponsor_logo'] = asset['img']['url'];
- break;
- case 3:
- ad['content'] = asset['data']['value'];
- break;
- case 4:
- ad['main_image'] = asset['img']['url'];
- break;
- case 6:
- ad['teaser_type'] = 'vast';
- ad['video_url'] = asset['video']['vasttag'];
- break;
- case 10:
- ad['sponsor_name'] = asset['data']['value'];
- break;
- case 2001:
- ad['expanded_content_type'] = 'embed';
- ad['expanded_summary'] = asset['data']['value'];
- break;
- case 2002:
- ad['expanded_content_type'] = 'vast';
- ad['expanded_summary'] = asset['data']['value'];
- break;
- case 2003:
- ad['sponsor_url'] = asset['data']['value'];
- break;
- case 2004: // prism
- ad['content_type'] = 'prism';
- break;
- case 2005: // internal_landing_page
- ad['content_type'] = 'internal_landing_page';
- ad['internal_content_link'] = asset['data']['value'];
- break;
- case 2006: // teaser as vast
- ad['teaser_type'] = 'vast';
- ad['video_url'] = asset['data']['value'];
- break;
- case 2007:
- ad['autoexpand_content_type'] = asset['data']['value'];
- break;
- case 2022: // content page
- ad['content_type'] = 'full_text';
- ad['full_text'] = asset['data']['value'];
- break;
- }
- }
-
- ad['action_url'] = link.url;
-
- if (!ad['sponsor_url']) {
- ad['sponsor_url'] = ad['action_url'];
- }
-
- ad['clicktrackers'] = [];
- if (link.clicktrackers) {
- ad['clicktrackers'] = link.clicktrackers;
- }
-
- ad['main_image'] = 'https://resize-ssp.adux.com/scalecrop-290x130/' + window.btoa(ad['main_image']) + '/external';
-
- bid.ad = '' +
- '
' +
- '
' +
- '
' +
- ' ' +
- '
' +
- '
' + ad['title'] + ' ' +
- '
' + ad['content'] + '
' +
- '
' +
- '
Sponsored by ' +
- ' ' +
- '
' +
- '
' +
- '
' +
- '' +
- '' +
- '
' +
- '
';
- }
- } else {
- // native
- bid.mediaType = 'native';
- if (bidRequest.mediaType === 'native') {
- if (serverBody.native) {
- let assets = serverBody.native.assets;
- let link = serverBody.native.link;
-
- let trackers = [];
- if (serverBody.native.imptrackers) {
- trackers = serverBody.native.imptrackers;
- }
-
- if (serverBody.nurl) {
- trackers.push(serverBody.nurl);
- }
-
- let native = {};
-
- for (let i = 0; i < assets.length; i++) {
- let asset = assets[i];
- switch (asset['id']) {
- case 1:
- native.title = asset['title']['text'];
- break;
- case 2:
- native.icon = {
- url: asset['img']['url'],
- width: asset['img']['w'],
- height: asset['img']['h']
- };
- break;
- case 3:
- native.body = asset['data']['value'];
- break;
- case 4:
- native.image = {
- url: asset['img']['url'],
- width: asset['img']['w'],
- height: asset['img']['h']
- };
- break;
- case 10:
- native.sponsoredBy = asset['data']['value'];
- break;
- }
- }
- native.cta = 'read more';
- if (serverBody.language) {
- native.cta = 'read more';
- }
-
- native.clickUrl = link.url;
- native.impressionTrackers = trackers;
- if (link.clicktrackers) {
- native.clickTrackers = link.clicktrackers;
- }
- native.eventtrackers = native.eventtrackers || [];
-
- bid.qtx_native = utils.deepClone(serverBody.native);
- bid.native = native;
- }
- }
- }
- bidResponses.push(bid);
- }
-
- return bidResponses;
- },
-
- /**
- * Register the user sync pixels which should be dropped after the auction.
- *
- * @param {SyncOptions} syncOptions Which user syncs are allowed?
- * @param {ServerResponse} serverResponse A successful response from the server
- * @return {UserSync[]} The user syncs which should be dropped.
- */
- getUserSyncs: function (syncOptions, serverResponse) {
- const syncs = [];
- utils._each(serverResponse, function(serverResponse) {
- if (serverResponse.body && serverResponse.body.sync) {
- utils._each(serverResponse.body.sync, function (pixel) {
- syncs.push({
- type: 'image',
- url: pixel
- });
- });
- }
- });
- return syncs;
- }
-}
-registerBidder(spec);
diff --git a/modules/quantumBidAdapter.md b/modules/quantumBidAdapter.md
deleted file mode 100644
index 572ca9ecd37..00000000000
--- a/modules/quantumBidAdapter.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# Overview
-
-```
-Module Name: Quantum Advertising Bid Adapter
-Module Type: Bidder Adapter
-Maintainer: support.mediareporting@adux.com
-```
-
-# Description
-
-Connects to Quantum's ssp for bids.
-
-# Sample Ad Unit: For Publishers
-```
-var adUnits = [{
- code: 'quantum-adUnit-id-1',
- sizes: [[300, 250]],
- bids: [{
- bidder: 'quantum',
- params: {
- placementId: 21546 //quantum adUnit id
- }
- }]
- },{
- code: 'quantum-native-adUnit-id-1',
- sizes: [[0, 0]],
- mediaTypes: 'native',
- bids: [{
- bidder: 'quantum',
- params: {
- placementId: 21546 //quantum adUnit id
- }
- }]
- }];
-```
-
-# Ad Unit and Setup: For Testing
-
-```
-
-
-
-
-
-
-
-
-
- ```
diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js
index 43bef356a73..e51cc79eb82 100755
--- a/modules/richaudienceBidAdapter.js
+++ b/modules/richaudienceBidAdapter.js
@@ -132,7 +132,7 @@ export const spec = {
var consent = '';
if (gdprConsent && typeof gdprConsent.consentString === 'string' && typeof gdprConsent.consentString != 'undefined') {
- consent = `consentString=’${gdprConsent.consentString}`
+ consent = `consentString=${gdprConsent.consentString}`
}
if (syncOptions.iframeEnabled) {
diff --git a/modules/rtbsapeBidAdapter.js b/modules/rtbsapeBidAdapter.js
new file mode 100644
index 00000000000..8473ef4dbb3
--- /dev/null
+++ b/modules/rtbsapeBidAdapter.js
@@ -0,0 +1,142 @@
+import * as utils from '../src/utils.js';
+import {registerBidder} from '../src/adapters/bidderFactory.js';
+import {BANNER, VIDEO} from '../src/mediaTypes.js';
+import {OUTSTREAM} from '../src/video.js';
+import {Renderer} from '../src/Renderer.js';
+import {triggerPixel} from '../src/utils.js';
+
+const BIDDER_CODE = 'rtbsape';
+const ENDPOINT = 'https://ssp-rtb.sape.ru/prebid';
+const RENDERER_SRC = 'https://cdn-rtb.sape.ru/js/player.js';
+const MATCH_SRC = 'https://www.acint.net/mc/?dp=141';
+
+export const spec = {
+ code: BIDDER_CODE,
+ aliases: ['sape'],
+ supportedMediaTypes: [BANNER, VIDEO],
+
+ /**
+ * 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) {
+ return !!(bid && bid.mediaTypes && (bid.mediaTypes.banner || bid.mediaTypes.video) && bid.params && bid.params.placeId);
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {BidRequest[]} validBidRequests an array of bids
+ * @param bidderRequest
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests: function (validBidRequests, bidderRequest) {
+ let tz = (new Date()).getTimezoneOffset()
+ let padInt = (v) => (v < 10 ? '0' + v : '' + v);
+
+ return {
+ url: ENDPOINT,
+ method: 'POST',
+ data: {
+ auctionId: bidderRequest.auctionId,
+ requestId: bidderRequest.bidderRequestId,
+ bids: validBidRequests,
+ timezone: (tz > 0 ? '-' : '+') + padInt(Math.floor(Math.abs(tz) / 60)) + ':' + padInt(Math.abs(tz) % 60),
+ refererInfo: bidderRequest.refererInfo
+ },
+ }
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {ServerResponse} serverResponse A successful response from the server.
+ * @param {{data: {bids: [{mediaTypes: {banner: boolean}}]}}} bidRequest Info describing the request to the server.
+ * @return {Bid[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function (serverResponse, bidRequest) {
+ if (!(serverResponse.body && Array.isArray(serverResponse.body.bids))) {
+ return [];
+ }
+
+ let bids = {};
+ bidRequest.data.bids.forEach(bid => bids[bid.bidId] = bid);
+
+ return serverResponse.body.bids.map(bid => {
+ let requestBid = bids[bid.requestId];
+ let context = utils.deepAccess(requestBid, 'mediaTypes.video.context');
+
+ if (context === OUTSTREAM && (bid.vastUrl || bid.vastXml)) {
+ let renderer = Renderer.install({
+ id: bid.requestId,
+ url: RENDERER_SRC,
+ loaded: false
+ });
+
+ let muted = utils.deepAccess(requestBid, 'params.video.playerMuted');
+ if (typeof muted === 'undefined') {
+ muted = true;
+ }
+
+ bid.playerMuted = muted;
+ bid.renderer = renderer
+
+ renderer.setRender(setOutstreamRenderer);
+ }
+
+ return bid;
+ });
+ },
+
+ /**
+ * Register the user sync pixels which should be dropped after the auction.
+ *
+ * @param {SyncOptions} syncOptions Which user syncs are allowed?
+ * @return {UserSync[]} The user syncs which should be dropped.
+ */
+ getUserSyncs: function (syncOptions) {
+ const sync = [];
+ if (syncOptions.iframeEnabled) {
+ sync.push({
+ type: 'iframe',
+ url: MATCH_SRC
+ });
+ }
+ return sync;
+ },
+
+ /**
+ * Register bidder specific code, which will execute if a bid from this bidder won the auction
+ * @param {Bid} bid The bid that won the auction
+ */
+ onBidWon: function(bid) {
+ if (bid.nurl) {
+ triggerPixel(bid.nurl);
+ }
+ }
+}
+
+/**
+ * Initialize RtbSape outstream player
+ *
+ * @param bid
+ */
+function setOutstreamRenderer(bid) {
+ let props = {};
+ if (bid.vastUrl) {
+ props.url = bid.vastUrl;
+ }
+ if (bid.vastXml) {
+ props.xml = bid.vastXml;
+ }
+ bid.renderer.push(() => {
+ let player = window.sapeRtbPlayerHandler(bid.adUnitCode, bid.width, bid.height, bid.playerMuted, {singleton: true});
+ props.onComplete = () => player.destroy();
+ props.onError = () => player.destroy();
+ player.addSlot(props);
+ });
+}
+
+registerBidder(spec);
diff --git a/modules/rtbsapeBidAdapter.md b/modules/rtbsapeBidAdapter.md
new file mode 100644
index 00000000000..6b1afe3867d
--- /dev/null
+++ b/modules/rtbsapeBidAdapter.md
@@ -0,0 +1,51 @@
+# Overview
+
+```
+Module Name: RtbSape Bid Adapter
+Module Type: Bidder Adapter
+Maintainer: sergey@sape.ru
+```
+
+# Description
+Our module makes it easy to integrate RtbSape demand sources into your website.
+
+Supported Ad format:
+* Banner
+* Video (instream and outstream)
+
+# Test Parameters
+```
+var adUnits = [
+ // Banner adUnit
+ {
+ code: 'banner-div',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250]],
+ }
+ },
+ bids: [{
+ bidder: 'rtbsape',
+ params: {
+ placeId: 553307
+ }
+ }]
+ },
+ // Video adUnit
+ {
+ code: 'video-div',
+ mediaTypes: {
+ video: {
+ context: 'outstream',
+ playerSize: [600, 340]
+ }
+ },
+ bids: [{
+ bidder: 'rtbsape',
+ params: {
+ placeId: 553309
+ }
+ }]
+ }
+];
+```
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 00ad14dd316..00fcd6ba8ff 100644
--- a/modules/rubiconAnalyticsAdapter.js
+++ b/modules/rubiconAnalyticsAdapter.js
@@ -195,15 +195,24 @@ function sendMessage(auctionId, bidWonId) {
// pick our of top level floor data we want to send!
if (auctionCache.floorData) {
- auction.floors = utils.pick(auctionCache.floorData, [
- 'location',
- 'modelName', () => auctionCache.floorData.modelVersion,
- 'skipped',
- 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'),
- 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'),
- 'skipRate', skipRate => !isNaN(skipRate) ? skipRate : 0,
- 'fetchStatus'
- ]);
+ if (auctionCache.floorData.location === 'noData') {
+ auction.floors = utils.pick(auctionCache.floorData, [
+ 'location',
+ 'fetchStatus',
+ 'floorProvider as provider'
+ ]);
+ } else {
+ auction.floors = utils.pick(auctionCache.floorData, [
+ 'location',
+ 'modelVersion as modelName',
+ 'skipped',
+ 'enforcement', () => utils.deepAccess(auctionCache.floorData, 'enforcements.enforceJS'),
+ 'dealsEnforced', () => utils.deepAccess(auctionCache.floorData, 'enforcements.floorDeals'),
+ 'skipRate',
+ 'fetchStatus',
+ 'floorProvider as provider'
+ ]);
+ }
}
if (serverConfig) {
@@ -269,7 +278,7 @@ function getBidPrice(bid) {
}
}
-export function parseBidResponse(bid, previousBidResponse) {
+export function parseBidResponse(bid, previousBidResponse, auctionFloorData) {
// The current bidResponse for this matching requestId/bidRequestId
let responsePrice = getBidPrice(bid)
// we need to compare it with the previous one (if there was one)
@@ -368,8 +377,9 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
cacheEntry.bids = {};
cacheEntry.bidsWon = {};
cacheEntry.referrer = args.bidderRequests[0].refererInfo.referer;
- if (utils.deepAccess(args, 'bidderRequests.0.bids.0.floorData')) {
- cacheEntry.floorData = {...utils.deepAccess(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;
@@ -538,7 +548,8 @@ let rubiconAdapter = Object.assign({}, baseAdapter, {
adapterManager.registerAnalyticsAdapter({
adapter: rubiconAdapter,
- code: 'rubicon'
+ code: 'rubicon',
+ gvlid: 52
});
export default rubiconAdapter;
diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js
index cd621010a9b..979cc430f15 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) {
@@ -332,9 +304,20 @@ export const spec = {
*/
const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot');
if (typeof pbAdSlot === 'string' && pbAdSlot) {
- utils.deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot);
+ utils.deepSetValue(data.imp[0].ext, 'context.data.pbadslot', pbAdSlot);
}
+ /**
+ * Copy GAM AdUnit and Name to imp
+ */
+ ['name', 'adSlot'].forEach(name => {
+ /** @type {(string|undefined)} */
+ const value = utils.deepAccess(bidRequest, `fpd.context.adserver.${name}`);
+ if (typeof value === 'string' && value) {
+ utils.deepSetValue(data.imp[0].ext, `context.data.adserver.${name.toLowerCase()}`, value);
+ }
+ });
+
// if storedAuctionResponse has been set, pass SRID
if (bidRequest.storedAuctionResponse) {
utils.deepSetValue(data.imp[0], 'ext.prebid.storedauctionresponse.id', bidRequest.storedAuctionResponse.toString());
@@ -518,11 +501,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;
}
@@ -530,25 +518,34 @@ export const spec = {
// For SRA we need to explicitly put empty semi colons so AE treats it as empty, instead of copying the latter value
data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : '';
- if (bidRequest.userId) {
- if (bidRequest.userId.tdid) {
- data['tpid_tdid'] = bidRequest.userId.tdid;
+ if (bidRequest.userIdAsEids && bidRequest.userIdAsEids.length) {
+ const unifiedId = find(bidRequest.userIdAsEids, eid => eid.source === 'adserver.org');
+ if (unifiedId) {
+ data['tpid_tdid'] = unifiedId.uids[0].id;
}
-
- // support liveintent ID
- if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) {
- data['tpid_liveintent.com'] = bidRequest.userId.lipb.lipbid;
- if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) {
- data['tg_v.LIseg'] = bidRequest.userId.lipb.segments.join(',');
+ const liveintentId = find(bidRequest.userIdAsEids, eid => eid.source === 'liveintent.com');
+ if (liveintentId) {
+ data['tpid_liveintent.com'] = liveintentId.uids[0].id;
+ if (liveintentId.ext && Array.isArray(liveintentId.ext.segments) && liveintentId.ext.segments.length) {
+ data['tg_v.LIseg'] = liveintentId.ext.segments.join(',');
}
}
-
- // support identityLink (aka LiveRamp)
- if (bidRequest.userId.idl_env) {
- data['tpid_liveramp.com'] = bidRequest.userId.idl_env;
+ const liverampId = find(bidRequest.userIdAsEids, eid => eid.source === 'liveramp.com');
+ if (liverampId) {
+ data['x_liverampidl'] = liverampId.uids[0].id;
+ }
+ const sharedId = find(bidRequest.userIdAsEids, eid => eid.source === 'sharedid.org');
+ if (sharedId) {
+ data['eid_sharedid.org'] = `${sharedId.uids[0].id}^${sharedId.uids[0].atype}^${sharedId.uids[0].ext.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') {
@@ -593,7 +590,16 @@ export const spec = {
*/
const pbAdSlot = utils.deepAccess(bidRequest, 'fpd.context.pbAdSlot');
if (typeof pbAdSlot === 'string' && pbAdSlot) {
- data['tg_i.dfp_ad_unit_code'] = pbAdSlot.replace(/^\/+/, '');
+ data['tg_i.pbadslot'] = pbAdSlot.replace(/^\/+/, '');
+ }
+
+ /**
+ * GAM Ad Unit
+ * @type {(string|undefined)}
+ */
+ const gamAdUnit = utils.deepAccess(bidRequest, 'fpd.context.adServer.adSlot');
+ if (typeof gamAdUnit === 'string' && gamAdUnit) {
+ data['tg_i.dfp_ad_unit_code'] = gamAdUnit.replace(/^\/+/, '');
}
// digitrust properties
@@ -1148,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/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js
new file mode 100755
index 00000000000..0e53d2a300d
--- /dev/null
+++ b/modules/saambaaBidAdapter.js
@@ -0,0 +1,401 @@
+import * as utils from '../src/utils.js';
+import { config } from '../src/config.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { VIDEO, BANNER } from '../src/mediaTypes.js';
+import find from 'core-js-pure/features/array/find.js';
+import includes from 'core-js-pure/features/array/includes.js';
+
+const ADAPTER_VERSION = '1.0';
+const BIDDER_CODE = 'saambaa';
+
+export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';
+export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';
+export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js';
+export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip'];
+export const DEFAULT_MIMES = ['video/mp4', 'application/javascript'];
+
+let pubid = '';
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, VIDEO],
+
+ isBidRequestValid(bidRequest) {
+ if (typeof bidRequest != 'undefined') {
+ if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; }
+ if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; }
+ return true;
+ } else { return false; }
+ },
+
+ buildRequests(bids, bidderRequest) {
+ let requests = [];
+ let videoBids = bids.filter(bid => isVideoBidValid(bid));
+ let bannerBids = bids.filter(bid => isBannerBidValid(bid));
+ videoBids.forEach(bid => {
+ pubid = getVideoBidParam(bid, 'pubid');
+ requests.push({
+ method: 'POST',
+ url: VIDEO_ENDPOINT + pubid,
+ data: createVideoRequestData(bid, bidderRequest),
+ bidRequest: bid
+ });
+ });
+
+ bannerBids.forEach(bid => {
+ pubid = getBannerBidParam(bid, 'pubid');
+
+ requests.push({
+ method: 'POST',
+ url: BANNER_ENDPOINT + pubid,
+ data: createBannerRequestData(bid, bidderRequest),
+ bidRequest: bid
+ });
+ });
+ return requests;
+ },
+
+ interpretResponse(serverResponse, {bidRequest}) {
+ let response = serverResponse.body;
+ if (response !== null && utils.isEmpty(response) == false) {
+ if (isVideoBid(bidRequest)) {
+ let bidResponse = {
+ requestId: response.id,
+ bidderCode: BIDDER_CODE,
+ cpm: response.seatbid[0].bid[0].price,
+ width: response.seatbid[0].bid[0].w,
+ height: response.seatbid[0].bid[0].h,
+ ttl: response.seatbid[0].bid[0].ttl || 60,
+ creativeId: response.seatbid[0].bid[0].crid,
+ currency: response.cur,
+ meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain },
+ mediaType: VIDEO,
+ netRevenue: true
+ }
+
+ if (response.seatbid[0].bid[0].adm) {
+ bidResponse.vastXml = response.seatbid[0].bid[0].adm;
+ bidResponse.adResponse = {
+ content: response.seatbid[0].bid[0].adm
+ };
+ } else {
+ bidResponse.vastUrl = response.seatbid[0].bid[0].nurl;
+ }
+
+ return bidResponse;
+ } else {
+ return {
+ requestId: response.id,
+ bidderCode: BIDDER_CODE,
+ cpm: response.seatbid[0].bid[0].price,
+ width: response.seatbid[0].bid[0].w,
+ height: response.seatbid[0].bid[0].h,
+ ad: response.seatbid[0].bid[0].adm,
+ ttl: response.seatbid[0].bid[0].ttl || 60,
+ creativeId: response.seatbid[0].bid[0].crid,
+ currency: response.cur,
+ meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain },
+ mediaType: BANNER,
+ netRevenue: true
+ }
+ }
+ }
+ }
+};
+
+function isBannerBid(bid) {
+ return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid);
+}
+
+function isVideoBid(bid) {
+ return utils.deepAccess(bid, 'mediaTypes.video');
+}
+
+function isVideoBidValid(bid) {
+ return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement');
+}
+
+function isBannerBidValid(bid) {
+ return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement');
+}
+
+function getVideoBidParam(bid, key) {
+ return utils.deepAccess(bid, 'params.video.' + key) || utils.deepAccess(bid, 'params.' + key);
+}
+
+function getBannerBidParam(bid, key) {
+ return utils.deepAccess(bid, 'params.banner.' + key) || utils.deepAccess(bid, 'params.' + key);
+}
+
+function isMobile() {
+ return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent);
+}
+
+function isConnectedTV() {
+ return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent);
+}
+
+function getDoNotTrack() {
+ return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes';
+}
+
+function findAndFillParam(o, key, value) {
+ try {
+ if (typeof value === 'function') {
+ o[key] = value();
+ } else {
+ o[key] = value;
+ }
+ } catch (ex) {}
+}
+
+function getOsVersion() {
+ let clientStrings = [
+ { s: 'Android', r: /Android/ },
+ { s: 'iOS', r: /(iPhone|iPad|iPod)/ },
+ { s: 'Mac OS X', r: /Mac OS X/ },
+ { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ },
+ { s: 'Linux', r: /(Linux|X11)/ },
+ { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ },
+ { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ },
+ { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ },
+ { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ },
+ { s: 'Windows Vista', r: /Windows NT 6.0/ },
+ { s: 'Windows Server 2003', r: /Windows NT 5.2/ },
+ { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ },
+ { s: 'UNIX', r: /UNIX/ },
+ { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ }
+ ];
+ let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent));
+ return cs ? cs.s : 'unknown';
+}
+
+function getFirstSize(sizes) {
+ return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined };
+}
+
+function parseSizes(sizes) {
+ return utils.parseSizesInput(sizes).map(size => {
+ let [ width, height ] = size.split('x');
+ return {
+ w: parseInt(width, 10) || undefined,
+ h: parseInt(height, 10) || undefined
+ };
+ });
+}
+
+function getVideoSizes(bid) {
+ return parseSizes(utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes);
+}
+
+function getBannerSizes(bid) {
+ return parseSizes(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes);
+}
+
+function getTopWindowReferrer() {
+ try {
+ return window.top.document.referrer;
+ } catch (e) {
+ return '';
+ }
+}
+
+function getVideoTargetingParams(bid) {
+ return Object.keys(Object(bid.params.video))
+ .filter(param => includes(VIDEO_TARGETING, param))
+ .reduce((obj, param) => {
+ obj[ param ] = bid.params.video[ param ];
+ return obj;
+ }, {});
+}
+
+function createVideoRequestData(bid, bidderRequest) {
+ let topLocation = getTopWindowLocation(bidderRequest);
+ let topReferrer = getTopWindowReferrer();
+
+ // if size is explicitly given via adapter params
+ let paramSize = getVideoBidParam(bid, 'size');
+ let sizes = [];
+
+ if (typeof paramSize !== 'undefined' && paramSize != '') {
+ sizes = parseSizes(paramSize);
+ } else {
+ sizes = getVideoSizes(bid);
+ }
+ const firstSize = getFirstSize(sizes);
+
+ let video = getVideoTargetingParams(bid);
+ const o = {
+ 'device': {
+ 'langauge': (global.navigator.language).split('-')[0],
+ 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0),
+ 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2,
+ 'js': 1,
+ 'os': getOsVersion()
+ },
+ 'at': 2,
+ 'site': {},
+ 'tmax': 3000,
+ 'cur': ['USD'],
+ 'id': bid.bidId,
+ 'imp': [],
+ 'regs': {
+ 'ext': {
+ }
+ },
+ 'user': {
+ 'ext': {
+ }
+ }
+ };
+
+ o.site['page'] = topLocation.href;
+ o.site['domain'] = topLocation.hostname;
+ o.site['search'] = topLocation.search;
+ o.site['domain'] = topLocation.hostname;
+ o.site['ref'] = topReferrer;
+ o.site['mobile'] = isMobile() ? 1 : 0;
+ const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0;
+
+ o.device['dnt'] = getDoNotTrack() ? 1 : 0;
+
+ findAndFillParam(o.site, 'name', function() {
+ return global.top.document.title;
+ });
+
+ findAndFillParam(o.device, 'h', function() {
+ return global.screen.height;
+ });
+ findAndFillParam(o.device, 'w', function() {
+ return global.screen.width;
+ });
+
+ let placement = getVideoBidParam(bid, 'placement');
+ let floor = getVideoBidParam(bid, 'floor');
+ if (floor == null) { floor = 0.5; }
+
+ for (let j = 0; j < sizes.length; j++) {
+ o.imp.push({
+ 'id': '' + j,
+ 'displaymanager': '' + BIDDER_CODE,
+ 'displaymanagerver': '' + ADAPTER_VERSION,
+ 'tagId': placement,
+ 'bidfloor': floor,
+ 'bidfloorcur': 'USD',
+ 'secure': secure,
+ 'video': Object.assign({
+ 'id': utils.generateUUID(),
+ 'pos': 0,
+ 'w': firstSize.w,
+ 'h': firstSize.h,
+ 'mimes': DEFAULT_MIMES
+ }, video)
+
+ });
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ let { gdprApplies, consentString } = bidderRequest.gdprConsent;
+ o.regs.ext = {'gdpr': gdprApplies ? 1 : 0};
+ o.user.ext = {'consent': consentString};
+ }
+
+ return o;
+}
+
+function getTopWindowLocation(bidderRequest) {
+ let url = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer;
+ return utils.parseUrl(config.getConfig('pageUrl') || url, { decodeSearchAsString: true });
+}
+
+function createBannerRequestData(bid, bidderRequest) {
+ let topLocation = getTopWindowLocation(bidderRequest);
+ let topReferrer = getTopWindowReferrer();
+
+ // if size is explicitly given via adapter params
+
+ let paramSize = getBannerBidParam(bid, 'size');
+ let sizes = [];
+ if (typeof paramSize !== 'undefined' && paramSize != '') {
+ sizes = parseSizes(paramSize);
+ } else {
+ sizes = getBannerSizes(bid);
+ }
+
+ const o = {
+ 'device': {
+ 'langauge': (global.navigator.language).split('-')[0],
+ 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0),
+ 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2,
+ 'js': 1
+ },
+ 'at': 2,
+ 'site': {},
+ 'tmax': 3000,
+ 'cur': ['USD'],
+ 'id': bid.bidId,
+ 'imp': [],
+ 'regs': {
+ 'ext': {
+ }
+ },
+ 'user': {
+ 'ext': {
+ }
+ }
+ };
+
+ o.site['page'] = topLocation.href;
+ o.site['domain'] = topLocation.hostname;
+ o.site['search'] = topLocation.search;
+ o.site['domain'] = topLocation.hostname;
+ o.site['ref'] = topReferrer;
+ o.site['mobile'] = isMobile() ? 1 : 0;
+ const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0;
+
+ o.device['dnt'] = getDoNotTrack() ? 1 : 0;
+
+ findAndFillParam(o.site, 'name', function() {
+ return global.top.document.title;
+ });
+
+ findAndFillParam(o.device, 'h', function() {
+ return global.screen.height;
+ });
+ findAndFillParam(o.device, 'w', function() {
+ return global.screen.width;
+ });
+
+ let placement = getBannerBidParam(bid, 'placement');
+ for (let j = 0; j < sizes.length; j++) {
+ let size = sizes[j];
+
+ let floor = getBannerBidParam(bid, 'floor');
+ if (floor == null) { floor = 0.1; }
+
+ o.imp.push({
+ 'id': '' + j,
+ 'displaymanager': '' + BIDDER_CODE,
+ 'displaymanagerver': '' + ADAPTER_VERSION,
+ 'tagId': placement,
+ 'bidfloor': floor,
+ 'bidfloorcur': 'USD',
+ 'secure': secure,
+ 'banner': {
+ 'id': utils.generateUUID(),
+ 'pos': 0,
+ 'w': size['w'],
+ 'h': size['h']
+ }
+ });
+ }
+
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ let { gdprApplies, consentString } = bidderRequest.gdprConsent;
+ o.regs.ext = {'gdpr': gdprApplies ? 1 : 0};
+ o.user.ext = {'consent': consentString};
+ }
+
+ return o;
+}
+registerBidder(spec);
diff --git a/modules/saambaaBidAdapter.md b/modules/saambaaBidAdapter.md
new file mode 100755
index 00000000000..2d391da7628
--- /dev/null
+++ b/modules/saambaaBidAdapter.md
@@ -0,0 +1,69 @@
+# Overview
+
+```
+Module Name: Saambaa Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: matt.voigt@saambaa.com
+```
+
+# Description
+
+Connects to Saambaa exchange for bids.
+
+Saambaa bid adapter supports Banner and Video ads currently.
+
+For more informatio
+
+# Sample Display Ad Unit: For Publishers
+```javascript
+
+var displayAdUnit = [
+{
+ code: 'display',
+ mediaTypes: {
+ banner: {
+ sizes: [[300, 250],[320, 50]]
+ }
+ }
+ bids: [{
+ bidder: 'saambaa',
+ params: {
+ pubid: '121ab139faf7ac67428a23f1d0a9a71b',
+ placement: 1234,
+ size: '320x50'
+ }
+ }]
+}];
+```
+
+# Sample Video Ad Unit: For Publishers
+```javascript
+
+var videoAdUnit = {
+ code: 'video',
+ sizes: [320,480],
+ mediaTypes: {
+ video: {
+ playerSize : [[320, 480]],
+ context: 'instream'
+ }
+ },
+ bids: [
+ {
+ bidder: 'saambaa',
+ params: {
+ pubid: '121ab139faf7ac67428a23f1d0a9a71b',
+ placement: 1234,
+ size: "320x480",
+ video: {
+ id: 123,
+ skip: 1,
+ mimes : ['video/mp4', 'application/javascript'],
+ playbackmethod : [2,6],
+ maxduration: 30
+ }
+ }
+ }
+ ]
+ };
+```
\ No newline at end of file
diff --git a/modules/serverbidBidAdapter.md b/modules/serverbidBidAdapter.md
deleted file mode 100644
index 87b51e665e2..00000000000
--- a/modules/serverbidBidAdapter.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# Overview
-
-Module Name: Serverbid Bid Adapter
-
-Module Type: Bid Adapter
-
-Maintainer: jgrimes@serverbid.com, jswart@serverbid.com
-
-# Description
-
-Connects to Serverbid for receiving bids from configured demand sources.
-
-# Test Parameters
-```javascript
- var adUnits = [
- {
- code: 'test-ad-1',
- sizes: [[300, 250]],
- bids: [
- {
- bidder: 'serverbid',
- params: {
- networkId: '9969',
- siteId: '980639'
- }
- }
- ]
- },
- {
- code: 'test-ad-2',
- sizes: [[300, 250]],
- bids: [
- {
- bidder: 'serverbid',
- params: {
- networkId: '9969',
- siteId: '980639',
- zoneIds: [178503]
- }
- }
- ]
- }
- ];
-```
diff --git a/modules/sharedIdSystem.md b/modules/sharedIdSystem.md
index acb076ed97f..a4541c16c49 100644
--- a/modules/sharedIdSystem.md
+++ b/modules/sharedIdSystem.md
@@ -13,7 +13,7 @@ ex: $ gulp build --modules=userId,sharedIdSystem
Individual params may be set for the Shared ID User ID Submodule.
```
pbjs.setConfig({
- usersync: {
+ userSync: {
userIds: [{
name: 'sharedId',
params: {
diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js
new file mode 100644
index 00000000000..4409e4e9dfb
--- /dev/null
+++ b/modules/smartxBidAdapter.js
@@ -0,0 +1,392 @@
+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;
+ let smartPlayObj = {
+ minAdWidth: 290,
+ maxAdWidth: 900,
+ elementLocator: {
+ allowInViewport: false,
+ minimumElementWidth: 290,
+ scanPixelsBelowViewport: 800
+ },
+ onStartCallback: function (m, n) {
+ try {
+ window.sc_smartIntxtStart(n);
+ } catch (f) {}
+ },
+ onCappedCallback: function (m, n) {
+ try {
+ window.sc_smartIntxtNoad(n);
+ } catch (f) {}
+ },
+ onEndCallback: function (m, n) {
+ try {
+ window.sc_smartIntxtEnd(n);
+ } catch (f) {}
+ },
+ };
+ 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 {
+ window.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/spotxBidAdapter.js b/modules/spotxBidAdapter.js
index 1733a176eba..9a3779dc65c 100644
--- a/modules/spotxBidAdapter.js
+++ b/modules/spotxBidAdapter.js
@@ -331,6 +331,11 @@ export const spec = {
height: spotxBid.h
};
+ bid.meta = bid.meta || {};
+ if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) {
+ bid.meta.advertiserDomains = spotxBid.adomain;
+ }
+
const context1 = utils.deepAccess(currentBidRequest, 'mediaTypes.video.context');
const context2 = utils.deepAccess(currentBidRequest, 'params.ad_unit');
if (context1 == 'outstream' || context2 == 'outstream') {
@@ -392,7 +397,7 @@ function createOutstreamScript(bid) {
utils.logMessage('[SPOTX][renderer] Default beahavior');
if (utils.getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) {
- dataSpotXParams['data-spotx_ad_mute'] = '0';
+ dataSpotXParams['data-spotx_ad_mute'] = '1';
}
dataSpotXParams['data-spotx_collapse'] = '0';
dataSpotXParams['data-spotx_autoplay'] = '1';
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 = `
+
+
+
+
+
+
+
+
+
+
+