Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SupplyChain object support in Prebid #4084

Merged
merged 41 commits into from
Aug 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b66e75a
moving dctr related code in a function
pm-harshad-mane Jul 16, 2019
edd7614
moving parsedRequest variable out of the loop
pm-harshad-mane Jul 16, 2019
3c9227f
Merge remote-tracking branch 'upstream/master' into small_changes
pm-harshad-mane Jul 16, 2019
f0fcc28
Merge remote-tracking branch 'upstream/master' into small_changes
pm-harshad-mane Aug 9, 2019
e5dd135
added a todo comment
pm-harshad-mane Aug 12, 2019
16bd479
exporting hasOwn function
pm-harshad-mane Aug 12, 2019
7d4cfde
added functionality to pass schain object
pm-harshad-mane Aug 12, 2019
e4cef55
changed logMessage to logError
pm-harshad-mane Aug 12, 2019
4f7ba93
Moved schain related code from adapaterManager.js to schain.js
pm-harshad-mane Aug 12, 2019
f7f9f3f
indentation chnages
pm-harshad-mane Aug 12, 2019
f44628e
logical bug fix
pm-harshad-mane Aug 12, 2019
ec0c32f
added test cases for schain
pm-harshad-mane Aug 12, 2019
9e6ee7d
PubMatic: pass schain object in request
pm-harshad-mane Aug 12, 2019
51d12e6
indentation
pm-harshad-mane Aug 12, 2019
abdcbe6
unit test for PubMatic schain support
pm-harshad-mane Aug 12, 2019
2fe6047
using isInteger from utils
pm-harshad-mane Aug 13, 2019
80b1787
moved schain as a module
pm-harshad-mane Aug 13, 2019
693399c
indentation
pm-harshad-mane Aug 13, 2019
7320a52
removed commented code
pm-harshad-mane Aug 13, 2019
88f495f
added try-catch as the statement code was breaking CI for IE-11
pm-harshad-mane Aug 13, 2019
d09eab4
Revert "added try-catch as the statement code was breaking CI for IE-11"
pm-harshad-mane Aug 13, 2019
e9606bf
added a try-catch for a staement as it was breaking CI sometimes
pm-harshad-mane Aug 14, 2019
355e353
added schain.md for schain module
pm-harshad-mane Aug 14, 2019
1458815
added a few links
pm-harshad-mane Aug 14, 2019
f73d4c1
fixed typos
pm-harshad-mane Aug 14, 2019
b7fa658
simplified the approach in adpater code
pm-harshad-mane Aug 14, 2019
25f877c
trying to restart CI
pm-harshad-mane Aug 14, 2019
b30dcaf
Revert "trying to restart CI"
pm-harshad-mane Aug 14, 2019
e891b9d
adding support in prebidServerBidAdpater as well
pm-harshad-mane Aug 15, 2019
9072e73
bug fix
pm-harshad-mane Aug 15, 2019
02e2e38
minor changes
pm-harshad-mane Aug 15, 2019
9b2a639
modified a comment
pm-harshad-mane Aug 15, 2019
fec36b8
added name to a test case
pm-harshad-mane Aug 20, 2019
4c218f3
Revert "added a try-catch for a staement as it was breaking CI someti…
pm-harshad-mane Aug 23, 2019
31d00d5
moving schain validation logic inside PM adapter
pm-harshad-mane Aug 27, 2019
e344855
Revert "moving schain validation logic inside PM adapter"
pm-harshad-mane Aug 28, 2019
7e4188f
added validation mode: strict, relaxed, off
pm-harshad-mane Aug 28, 2019
fd34738
updated documentation
pm-harshad-mane Aug 28, 2019
01b9c68
Merge remote-tracking branch 'upstream/master' into small_changes
pm-harshad-mane Aug 28, 2019
0c896df
moved a comment
pm-harshad-mane Aug 28, 2019
1f9d0eb
changed value in example
pm-harshad-mane Aug 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/prebidServerBidAdapter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,14 @@ const OPEN_RTB_PROTOCOL = {
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) {
request.source.ext = {
schain: schain
};
}

if (!utils.isEmpty(aliases)) {
request.ext.prebid.aliases = aliases;
}
Expand Down
92 changes: 53 additions & 39 deletions modules/pubmaticBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,38 @@ function _blockedIabCategoriesValidation(payload, blockedIabCategories) {
}
}

function _handleDealCustomTargetings(payload, dctrArr, validBidRequests) {
var dctr = '';
var dctrLen;
// set dctr value in site.ext, if present in validBidRequests[0], else ignore
if (dctrArr.length > 0) {
if (validBidRequests[0].params.hasOwnProperty('dctr')) {
dctr = validBidRequests[0].params.dctr;
if (utils.isStr(dctr) && dctr.length > 0) {
var arr = dctr.split('|');
dctr = '';
arr.forEach(val => {
dctr += (val.length > 0) ? (val.trim() + '|') : '';
});
dctrLen = dctr.length;
if (dctr.substring(dctrLen, dctrLen - 1) === '|') {
dctr = dctr.substring(0, dctrLen - 1);
}
payload.site.ext = {
key_val: dctr.trim()
}
} else {
utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value');
}
if (dctrArr.length > 1) {
utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits');
}
} else {
utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits');
}
}
}

pm-harshad-mane marked this conversation as resolved.
Show resolved Hide resolved
export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO, NATIVE],
Expand Down Expand Up @@ -779,11 +811,10 @@ export const spec = {
var conf = _initConf(refererInfo);
var payload = _createOrtbTemplate(conf);
var bidCurrency = '';
var dctr = '';
var dctrLen;
var dctrArr = [];
var bid;
var blockedIabCategories = [];

validBidRequests.forEach(originalBid => {
bid = utils.deepClone(originalBid);
bid.params.adSlot = bid.params.adSlot || '';
Expand Down Expand Up @@ -835,6 +866,21 @@ export const spec = {
payload.ext.wrapper.wp = 'pbjs';
payload.user.gender = (conf.gender ? conf.gender.trim() : UNDEFINED);
payload.user.geo = {};
payload.user.geo.lat = _parseSlotParam('lat', conf.lat);
payload.user.geo.lon = _parseSlotParam('lon', conf.lon);
payload.user.yob = _parseSlotParam('yob', conf.yob);
payload.device.geo = payload.user.geo;
payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim();
payload.site.domain = _getDomainFromURL(payload.site.page);

// adding schain object
if (validBidRequests[0].schain) {
payload.source = {
ext: {
schain: validBidRequests[0].schain
}
};
}

// Attaching GDPR Consent Params
if (bidderRequest && bidderRequest.gdprConsent) {
Expand All @@ -849,43 +895,10 @@ export const spec = {
};
}

payload.user.geo.lat = _parseSlotParam('lat', conf.lat);
payload.user.geo.lon = _parseSlotParam('lon', conf.lon);
payload.user.yob = _parseSlotParam('yob', conf.yob);
payload.device.geo = payload.user.geo;
payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim();
payload.site.domain = _getDomainFromURL(payload.site.page);

// set dctr value in site.ext, if present in validBidRequests[0], else ignore
if (dctrArr.length > 0) {
if (validBidRequests[0].params.hasOwnProperty('dctr')) {
dctr = validBidRequests[0].params.dctr;
if (utils.isStr(dctr) && dctr.length > 0) {
var arr = dctr.split('|');
dctr = '';
arr.forEach(val => {
dctr += (val.length > 0) ? (val.trim() + '|') : '';
});
dctrLen = dctr.length;
if (dctr.substring(dctrLen, dctrLen - 1) === '|') {
dctr = dctr.substring(0, dctrLen - 1);
}
payload.site.ext = {
key_val: dctr.trim()
}
} else {
utils.logWarn(LOG_WARN_PREFIX + 'Ignoring param : dctr with value : ' + dctr + ', expects string-value, found empty or non-string value');
}
if (dctrArr.length > 1) {
utils.logWarn(LOG_WARN_PREFIX + 'dctr value found in more than 1 adunits. Value from 1st adunit will be picked. Ignoring values from subsequent adunits');
}
} else {
utils.logWarn(LOG_WARN_PREFIX + 'dctr value not found in 1st adunit, ignoring values from subsequent adunits');
}
}

_handleDealCustomTargetings(payload, dctrArr, validBidRequests);
_handleEids(payload, validBidRequests);
_blockedIabCategoriesValidation(payload, blockedIabCategories);

return {
method: 'POST',
url: ENDPOINT,
Expand All @@ -902,6 +915,8 @@ export const spec = {
interpretResponse: (response, request) => {
const bidResponses = [];
var respCur = DEFAULT_CURRENCY;
let parsedRequest = JSON.parse(request.data);
let parsedReferrer = parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : '';
try {
if (response.body && response.body.seatbid && utils.isArray(response.body.seatbid)) {
// Supporting multiple bid responses for same adSize
Expand All @@ -910,7 +925,6 @@ export const spec = {
seatbidder.bid &&
utils.isArray(seatbidder.bid) &&
seatbidder.bid.forEach(bid => {
let parsedRequest = JSON.parse(request.data);
let newBid = {
requestId: bid.impid,
cpm: (parseFloat(bid.price) || 0).toFixed(2),
Expand All @@ -921,7 +935,7 @@ export const spec = {
currency: respCur,
netRevenue: NET_REVENUE,
ttl: 300,
referrer: parsedRequest.site && parsedRequest.site.ref ? parsedRequest.site.ref : '',
referrer: parsedReferrer,
ad: bid.adm
};
if (parsedRequest.imp && parsedRequest.imp.length > 0) {
Expand Down
147 changes: 147 additions & 0 deletions modules/schain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {config} from '../src/config';
import {getGlobal} from '../src/prebidGlobal';
import { isNumber, isStr, isArray, isPlainObject, hasOwn, logError, isInteger } from '../src/utils';

// https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md

const schainErrorPrefix = 'Invalid schain object found: ';
const shouldBeAString = ' should be a string';
const shouldBeAnInteger = ' should be an Integer';
const shouldBeAnObject = ' should be an object';
const shouldBeAnArray = ' should be an Array';
const MODE = {
STRICT: 'strict',
RELAXED: 'relaxed',
OFF: 'off'
};

// validate the supply chain object
export function isSchainObjectValid(schainObject, returnOnError) {
if (!isPlainObject(schainObject)) {
logError(schainErrorPrefix + `schain` + shouldBeAnObject);
if (returnOnError) return false;
}

// complete: Integer
if (!isNumber(schainObject.complete) || !isInteger(schainObject.complete)) {
logError(schainErrorPrefix + `schain.complete` + shouldBeAnInteger);
if (returnOnError) return false;
}

// ver: String
if (!isStr(schainObject.ver)) {
logError(schainErrorPrefix + `schain.ver` + shouldBeAString);
if (returnOnError) return false;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One concern: the schema ultimately depends upon the version of the Supply Chain spec that is being used. Ex: they recently released a "final" version of the previous draft that renamed a number of fields.

To accommodate this, a lot of the validation logic will probably need to be dependent upon the version. Ex:

export function isSchainObjectValid(schainObject) {
  // Move the version-checking logic to the very beginning
  if ( !(isPlainObject(schainObject) && isStr(schainObject.ver) )  ) {
      logError(schainErrorPrefix + `schain.ver` + shouldBeAString);
      return false;
  }

  // Now that we've verified there is a version, we need to evaluate the appropriate version of the schema.
  switch(schainObject.ver) {
    case "1.0":
      return isSchainObjectValid_1_0(schainObject);

    case "2.0":
      return isSchainObjectValid_2_0(schainObject);

    default:
      logError("Unsupported schain version: " + schainObject.ver);
      return false;
  }
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this a bit more, though... I'm concerned about the fact that "Prebid.js" and "The Supply Chain Spec" have completely different release cycles. That is: there could be a new release of the "Supply Chain Spec" at any time. With the current arrangement, it seems like nobody would be able to upgrade to this new version of the "Supply Chain Spec" until after there has been a Prebid.js update.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had asked @pm-harshad-mane to put the validation logic in. Considering we're facing a deadline from some DSPs to complete the schain work, I'm wondering if the better option is just to remove the validation logic and let bidders validate the schain objects themselves if they wish to.


// ext: Object [optional]
if (hasOwn(schainObject, 'ext')) {
if (!isPlainObject(schainObject.ext)) {
logError(schainErrorPrefix + `schain.ext` + shouldBeAnObject);
if (returnOnError) return false;
}
}

// nodes: Array of objects
if (!isArray(schainObject.nodes)) {
logError(schainErrorPrefix + `schain.nodes` + shouldBeAnArray);
if (returnOnError) return false;
}

// now validate each node
let isEachNodeIsValid = true;
schainObject.nodes.forEach(node => {
// asi: String
if (!isStr(node.asi)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].asi` + shouldBeAString);
}

// sid: String
if (!isStr(node.sid)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].sid` + shouldBeAString);
}

// hp: Integer
if (!isNumber(node.hp) || !isInteger(node.hp)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].hp` + shouldBeAnInteger);
}

// rid: String [Optional]
if (hasOwn(node, 'rid')) {
if (!isStr(node.rid)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].rid` + shouldBeAString);
}
}

// name: String [Optional]
if (hasOwn(node, 'name')) {
if (!isStr(node.name)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].name` + shouldBeAString);
}
}

// domain: String [Optional]
if (hasOwn(node, 'domain')) {
if (!isStr(node.domain)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].domain` + shouldBeAString);
}
}

// ext: Object [Optional]
if (hasOwn(node, 'ext')) {
if (!isPlainObject(node.ext)) {
isEachNodeIsValid = isEachNodeIsValid && false;
logError(schainErrorPrefix + `schain.nodes[].ext` + shouldBeAnObject);
}
}
});

if (returnOnError && !isEachNodeIsValid) {
return false;
}

return true;
}

export function copySchainObjectInAdunits(adUnits, schainObject) {
// copy schain object in all adUnits as adUnits[].bid.schain
adUnits.forEach(adUnit => {
adUnit.bids.forEach(bid => {
bid.schain = schainObject;
});
});
}

export function init(config) {
let mode = MODE.STRICT;
getGlobal().requestBids.before(function(fn, reqBidsConfigObj) {
let schainObject = config.getConfig('schain');
if (!isPlainObject(schainObject)) {
logError(schainErrorPrefix + 'schain config will not be passed to bidders as schain is not an object.');
} else {
if (isStr(schainObject.validation) && Object.values(MODE).indexOf(schainObject.validation) != -1) {
mode = schainObject.validation;
}
if (mode === MODE.OFF) {
// no need to validate
copySchainObjectInAdunits(reqBidsConfigObj.adUnits || getGlobal().adUnits, schainObject.config);
} else {
if (isSchainObjectValid(schainObject.config, mode === MODE.STRICT)) {
copySchainObjectInAdunits(reqBidsConfigObj.adUnits || getGlobal().adUnits, schainObject.config);
} else {
logError(schainErrorPrefix + 'schain config will not be passed to bidders as it is not valid.');
}
}
}
// calling fn allows prebid to continue processing
return fn.call(this, reqBidsConfigObj);
}, 40);
}

init(config)
50 changes: 50 additions & 0 deletions modules/schain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# schain module

Aggregators who manage Prebid wrappers on behalf of multiple publishers need to declare their intermediary status in the Supply Chain Object.
As the spec prohibits us from adding upstream intermediaries, Prebid requests in this case need to come with the schain information.
In this use case, it's seems cumbersome to have every bidder in the wrapper separately configured the same schain information.

Refer:
- https://iabtechlab.com/sellers-json/
- https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md

## Sample code for passing the schain object
```
pbjs.setConfig( {
"schain":
"validation": "strict",
"config": {
"ver":"1.0",
"complete": 1,
"nodes": [
{
"asi":"indirectseller.com",
"sid":"00001",
"hp":1
},

{
"asi":"indirectseller-2.com",
"sid":"00002",
"hp":0
},
]
}
}
});
```

## Workflow
The schain module is not enabled by default as it may not be neccessary for all publishers.
If required, schain module can be included as following
```
$ gulp build --modules=schain,pubmaticBidAdapter,openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter
```
The schain module will validate the schain object passed using pbjs.setConfig API.
If the schain object is valid then it will be passed on to bidders/adapters in ```validBidRequests[].schain```
You may refer pubmaticBidAdapter implementaion for the same.

## Validation modes
- ```strict```: It is the default validation mode. In this mode, schain object will not be passed to adapters if it is invalid. Errors are thrown for invalid schain object.
- ```relaxed```: In this mode, errors are thrown for an invalid schain object but the invalid schain object is still passed to adapters.
- ```off```: In this mode, no validations are performed and schain object is passed as is to adapters.
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ export function _map(object, callback) {
return output;
}

var hasOwn = function (objectToCheck, propertyToCheckFor) {
export function hasOwn(objectToCheck, propertyToCheckFor) {
if (objectToCheck.hasOwnProperty) {
return objectToCheck.hasOwnProperty(propertyToCheckFor);
} else {
Expand Down
Loading