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

Refactored URL query parameter passthrough for additional values, cha… #2758

Merged
merged 1 commit into from
Jun 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
183 changes: 100 additions & 83 deletions modules/lkqdBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,13 @@ import { VIDEO } from 'src/mediaTypes';

const BIDDER_CODE = 'lkqd';
const BID_TTL_DEFAULT = 300;
const ENDPOINT = 'https://ssp.lkqd.net/ad?pid=[PLACEMENT_ID]&sid=[SITE_ID]&output=[OUTPUT]&execution=[EXECUTION]&placement=[PLACEMENT]&playinit=[PLAY_INIT]&volume=[VOLUME]&timeout=[TIMEOUT]&width=[WIDTH]‌&height=[HEIGHT]&pbt=[PREBID_TOKEN]‌&dnt=[DO_NOT_TRACK]‌&pageurl=[PAGEURL]‌&contentid=[CONTENT_ID]‌&contenttitle=[CONTENT_TITLE]‌&contentlength=[CONTENT_LENGTH]‌&contenturl=[CONTENT_URL]&prebid=true';

const PID_KEY = '[PLACEMENT_ID]';
const SID_KEY = '[SITE_ID]';
const OUTPUT_KEY = '[OUTPUT]';
const EXECUTION_KEY = '[EXECUTION]';
const PLACEMENT_KEY = '[PLACEMENT]';
const PLAYINIT_KEY = '[PLAY_INIT]';
const VOLUME_KEY = '[VOLUME]';
const TIMEOUT_KEY = '[TIMEOUT]';
const WIDTH_KEY = '[WIDTH]';
const HEIGHT_KEY = '[HEIGHT]';
const DNT_KEY = '[DO_NOT_TRACK]';
const PAGEURL_KEY = '[PAGEURL]';
const CONTENTID_KEY = '[CONTENT_ID]';
const CONTENTTITLE_KEY = '[CONTENT_TITLE]';
const CONTENTLENGTH_KEY = '[CONTENT_LENGTH]';
const CONTENTURL_KEY = '[CONTENT_URL]';

const PID_DEFAULT = null;
const SID_DEFAULT = null;
const OUTPUT_DEFAULT = 'vast';
const EXECUTION_DEFAULT = 'any';
const PLACEMENT_DEFAULT = '';
const PLAYINIT_DEFAULT = 'auto';
const VOLUME_DEFAULT = '100';
const TIMEOUT_DEFAULT = '';
const WIDTH_DEFAULT = null;
const HEIGHT_DEFAULT = null;
const DNT_DEFAULT = null;
const PAGEURL_DEFAULT = null;
const CONTENTID_DEFAULT = null;
const CONTENTTITLE_DEFAULT = null;
const CONTENTLENGTH_DEFAULT = null;
const CONTENTURL_DEFAULT = null;
const ENDPOINT = 'https://v.lkqd.net/ad';

const PARAM_OUTPUT_DEFAULT = 'vast';
const PARAM_EXECUTION_DEFAULT = 'any';
const PARAM_SUPPORT_DEFAULT = 'html5';
const PARAM_PLAYINIT_DEFAULT = 'auto';
const PARAM_VOLUME_DEFAULT = '100';

function _validateId(id) {
if (id && typeof id !== 'undefined' && parseInt(id) > 0) {
Expand All @@ -58,18 +30,6 @@ function isBidRequestValid(bidRequest) {
return false;
}

function _replaceMacro(key, paramValue, defaultValue, url) {
if (url && typeof url === 'string' && url !== '' && url.indexOf(key) > 0) {
if (paramValue) {
url = url.replace(key, paramValue);
} else if (defaultValue || defaultValue == '') {
url = url.replace(key, defaultValue);
}
}

return url;
}

function buildRequests(validBidRequests) {
let bidRequests = [];

Expand Down Expand Up @@ -101,29 +61,101 @@ function buildRequests(validBidRequests) {
}

let sspUrl = ENDPOINT.concat();
let sspData = {};

// required parameters
sspUrl = _replaceMacro(PID_KEY, bidRequest.params.placementId, PID_DEFAULT, sspUrl);
sspUrl = _replaceMacro(SID_KEY, bidRequest.params.siteId, SID_DEFAULT, sspUrl);
sspData.pid = bidRequest.params.placementId;
sspData.sid = bidRequest.params.siteId;
sspData.prebid = true;

// optional parameters
sspUrl = _replaceMacro(OUTPUT_KEY, bidRequest.params.output, OUTPUT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(EXECUTION_KEY, bidRequest.params.execution, EXECUTION_DEFAULT, sspUrl);
sspUrl = _replaceMacro(PLACEMENT_KEY, bidRequest.params.placement, PLACEMENT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(PLAYINIT_KEY, bidRequest.params.playinit, PLAYINIT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(VOLUME_KEY, bidRequest.params.volume, VOLUME_DEFAULT, sspUrl);
sspUrl = _replaceMacro(TIMEOUT_KEY, bidRequest.params.timeout, TIMEOUT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(WIDTH_KEY, playerWidth, WIDTH_DEFAULT, sspUrl);
sspUrl = _replaceMacro(HEIGHT_KEY, playerHeight, HEIGHT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(DNT_KEY, bidRequest.params.dnt, DNT_DEFAULT, sspUrl);
sspUrl = _replaceMacro(PAGEURL_KEY, bidRequest.params.pageurl, PAGEURL_DEFAULT, sspUrl);
sspUrl = _replaceMacro(CONTENTID_KEY, bidRequest.params.contentId, CONTENTID_DEFAULT, sspUrl);
sspUrl = _replaceMacro(CONTENTTITLE_KEY, bidRequest.params.contentTitle, CONTENTTITLE_DEFAULT, sspUrl);
sspUrl = _replaceMacro(CONTENTLENGTH_KEY, bidRequest.params.contentLength, CONTENTLENGTH_DEFAULT, sspUrl);
sspUrl = _replaceMacro(CONTENTURL_KEY, bidRequest.params.contentUrl, CONTENTURL_DEFAULT, sspUrl);
if (bidRequest.params.hasOwnProperty('output') && bidRequest.params.output != null) {
sspData.output = bidRequest.params.output;
} else {
sspData.output = PARAM_OUTPUT_DEFAULT;
}
if (bidRequest.params.hasOwnProperty('execution') && bidRequest.params.execution != null) {
sspData.execution = bidRequest.params.execution;
} else {
sspData.execution = PARAM_EXECUTION_DEFAULT;
}
if (bidRequest.params.hasOwnProperty('support') && bidRequest.params.support != null) {
sspData.support = bidRequest.params.support;
} else {
sspData.support = PARAM_SUPPORT_DEFAULT;
}
if (bidRequest.params.hasOwnProperty('playinit') && bidRequest.params.playinit != null) {
sspData.playinit = bidRequest.params.playinit;
} else {
sspData.playinit = PARAM_PLAYINIT_DEFAULT;
}
if (bidRequest.params.hasOwnProperty('volume') && bidRequest.params.volume != null) {
sspData.volume = bidRequest.params.volume;
} else {
sspData.volume = PARAM_VOLUME_DEFAULT;
}
if (playerWidth) {
sspData.width = playerWidth;
}
if (playerHeight) {
sspData.height = playerHeight;
}
if (bidRequest.params.hasOwnProperty('vpaidmode') && bidRequest.params.vpaidmode != null) {
sspData.vpaidmode = bidRequest.params.vpaidmode;
}
if (bidRequest.params.hasOwnProperty('appname') && bidRequest.params.appname != null) {
sspData.appname = bidRequest.params.appname;
}
if (bidRequest.params.hasOwnProperty('bundleid') && bidRequest.params.bundleid != null) {
sspData.bundleid = bidRequest.params.bundleid;
}
if (bidRequest.params.hasOwnProperty('aid') && bidRequest.params.aid != null) {
sspData.aid = bidRequest.params.aid;
}
if (bidRequest.params.hasOwnProperty('idfa') && bidRequest.params.idfa != null) {
sspData.idfa = bidRequest.params.idfa;
}
if (bidRequest.params.hasOwnProperty('gdpr') && bidRequest.params.gdpr != null) {
sspData.gdpr = bidRequest.params.gdpr;
}
if (bidRequest.params.hasOwnProperty('gdprcs') && bidRequest.params.gdprcs != null) {
sspData.gdprcs = bidRequest.params.gdprcs;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

just verifying that you expect gdpr params to be passed through in the bid params rather than the bidderRequest.gdprConsent object generally sent to buildRequests(): buildRequests: function (bidRequests, bidderRequest) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you very much for confirming on this, it's an interesting point. You are correct that we are using custom parameters for our GDPR handling, even though there is likely overlap between your gdprConsent object and our gdprcs (consent string). I was unaware of bidderRequest.gdprConsent so I will have to familiarize myself with the documentation - we will likely add support through this property in a later pull request, but for the moment we are going to proceed with our own implementation. Thank you for bringing this feature to my attention!

if (bidRequest.params.hasOwnProperty('flrd') && bidRequest.params.flrd != null) {
sspData.flrd = bidRequest.params.flrd;
}
if (bidRequest.params.hasOwnProperty('flrmp') && bidRequest.params.flrmp != null) {
sspData.flrmp = bidRequest.params.flrmp;
}
if (bidRequest.params.hasOwnProperty('placement') && bidRequest.params.placement != null) {
sspData.placement = bidRequest.params.placement;
}
if (bidRequest.params.hasOwnProperty('timeout') && bidRequest.params.timeout != null) {
sspData.timeout = bidRequest.params.timeout;
}
if (bidRequest.params.hasOwnProperty('dnt') && bidRequest.params.dnt != null) {
sspData.dnt = bidRequest.params.dnt;
}
if (bidRequest.params.hasOwnProperty('pageurl') && bidRequest.params.pageurl != null) {
sspData.pageurl = bidRequest.params.pageurl;
}
if (bidRequest.params.hasOwnProperty('contentId') && bidRequest.params.contentId != null) {
sspData.contentid = bidRequest.params.contentId;
}
if (bidRequest.params.hasOwnProperty('contentTitle') && bidRequest.params.contentTitle != null) {
sspData.contenttitle = bidRequest.params.contentTitle;
}
if (bidRequest.params.hasOwnProperty('contentLength') && bidRequest.params.contentLength != null) {
sspData.contentlength = bidRequest.params.contentLength;
}
if (bidRequest.params.hasOwnProperty('contentUrl') && bidRequest.params.contentUrl != null) {
sspData.contenturl = bidRequest.params.contentUrl;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

just a suggestion to make this more concise - maybe create a function to do this repetitious task. Something like:
function _addParam(param, default) {
if (bidRequest.params.hasOwnProperty(param) && bidRequest.params[param] != null) {
sspData[param] = bidRequest.params[param];
} else if (default != null) {
sspData[param] = default;
}
}

then you just repeat
_addParam(<bidParam>, <optionalDefault>);
or could even put it in a loop (loop through all your params)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a good suggestion - I will look into adding this improvement in a future commit but I would consider it out of scope and don't want it to hold up this current pull request.

// random number to prevent caching
sspUrl = sspUrl + '‌&rnd=' + Math.floor(Math.random() * 999999999);
sspData.rnd = Math.floor(Math.random() * 999999999);

let sspData = {};
// Prebid.js required properties
sspData.bidId = bidRequest.bidId;
sspData.bidWidth = playerWidth;
sspData.bidHeight = playerHeight;
Expand All @@ -149,26 +181,10 @@ function interpretResponse(serverResponse, bidRequest) {
try {
let bidResponse = {};
if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') {
let sspXml = new window.DOMParser().parseFromString(serverResponse.body, 'text/xml');
let sspXmlString = serverResponse.body;
let sspXml = new window.DOMParser().parseFromString(sspXmlString, 'text/xml');
if (sspXml && sspXml.getElementsByTagName('parsererror').length == 0) {
let sspUrl = bidRequest.url.concat();
let prebidToken;
let extensions = sspXml.getElementsByTagName('Extension');

if (extensions && extensions.length) {
for (let i = 0; i < extensions.length; i++) {
if (extensions[i].getAttribute('id') === 'prebidToken') {
prebidToken = extensions[i]
}
}
if (prebidToken) {
sspUrl = sspUrl + '&pbt' + prebidToken;
} else {
utils.logWarn('Warning: Could not determine token, cannot guarantee same ad will be received after auctionEnd');
}
} else {
utils.logWarn('Warning: Response did not contain a token, cannot guarantee same ad will be received after auctionEnd');
}

bidResponse.requestId = bidRequest.data.bidId;
bidResponse.bidderCode = BIDDER_CODE;
Expand All @@ -181,6 +197,7 @@ function interpretResponse(serverResponse, bidRequest) {
bidResponse.currency = sspXml.getElementsByTagName('Pricing')[0].getAttribute('currency');
bidResponse.netRevenue = true;
bidResponse.vastUrl = sspUrl;
bidResponse.vastXml = sspXmlString;
bidResponse.mediaType = VIDEO;

bidResponses.push(bidResponse);
Expand Down
16 changes: 15 additions & 1 deletion modules/lkqdBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ LKQD bid adapter supports Video ads currently.
For more information about [LKQD Ad Serving and Management](http://www.lkqd.com/ad-serving-and-management/), please contact [info@lkqd.com](info@lkqd.com).

# Sample Ad Unit: For Publishers
```
```javascript
var videoAdUnit = [
{
code: 'video1',
Expand All @@ -31,3 +31,17 @@ var videoAdUnit = [
}
}]
}];
```

# Configuration

The LKQD Bidder Adapter expects Prebid Cache to be enabled so that we can store and retrieve a single vastXml. If this value is not set it will have to use vastUrl to make a duplicate call to the SSP and cannot guarantee the same ad will be received after auctionEnd.

```javascript
pbjs.setConfig({
usePrebidCache: true,
cache: {
url: 'https://prebid.adnxs.com/pbc/v1/cache'
}
});
```
68 changes: 42 additions & 26 deletions test/spec/modules/lkqdBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('LKQD Bid Adapter Test', () => {
});

describe('buildRequests', () => {
const ENDPOINT = 'https://ssp.lkqd.net/ad?';
const ENDPOINT = 'https://v.lkqd.net/ad';
let bidRequests = [
{
'bidder': 'lkqd',
Expand Down Expand Up @@ -76,41 +76,57 @@ describe('LKQD Bid Adapter Test', () => {
it('should populate available parameters', () => {
const requests = spec.buildRequests(bidRequests);
expect(requests.length).to.equal(2);
const r1 = requests[0].url;
expect(r1).to.contain('?pid=263');
expect(r1).to.contain('&sid=662921');
expect(r1).to.contain('&width=300');
expect(r1).to.contain('&height=250');
const r2 = requests[1].url;
expect(r2).to.contain('?pid=263');
expect(r2).to.contain('&sid=662921');
expect(r2).to.contain('&width=640');
expect(r2).to.contain('&height=480');
const r1 = requests[0].data;
expect(r1).to.have.property('pid');
expect(r1.pid).to.equal('263');
expect(r1).to.have.property('sid');
expect(r1.sid).to.equal('662921');
expect(r1).to.have.property('width');
expect(r1.width).to.equal(300);
expect(r1).to.have.property('height');
expect(r1.height).to.equal(250);
const r2 = requests[1].data;
expect(r2).to.have.property('pid');
expect(r2.pid).to.equal('263');
expect(r2).to.have.property('sid');
expect(r2.sid).to.equal('662921');
expect(r2).to.have.property('width');
expect(r2.width).to.equal(640);
expect(r2).to.have.property('height');
expect(r2.height).to.equal(480);
});

it('should not populate unspecified parameters', () => {
const requests = spec.buildRequests(bidRequests);
expect(requests.length).to.equal(2);
const r1 = requests[0].url;
expect(r1).to.contain('‌&contentid=[CONTENT_ID]');
expect(r1).to.contain('‌&contenttitle=[CONTENT_TITLE]');
expect(r1).to.contain('‌&contentlength=[CONTENT_LENGTH]');
expect(r1).to.contain('&height=250');
const r2 = requests[1].url;
expect(r2).to.contain('‌&contentid=[CONTENT_ID]');
expect(r2).to.contain('‌&contenttitle=[CONTENT_TITLE]');
expect(r2).to.contain('‌&contentlength=[CONTENT_LENGTH]');
expect(r2).to.contain('‌&contenturl=[CONTENT_URL]');
const r1 = requests[0].data;
expect(r1).to.not.have.property('dnt');
expect(r1).to.not.have.property('pageurl');
expect(r1).to.not.have.property('contentid');
expect(r1).to.not.have.property('contenttitle');
expect(r1).to.not.have.property('contentlength');
expect(r1).to.not.have.property('contenturl');
const r2 = requests[1].data;
expect(r2).to.not.have.property('dnt');
expect(r2).to.not.have.property('pageurl');
expect(r2).to.not.have.property('contentid');
expect(r2).to.not.have.property('contenttitle');
expect(r2).to.not.have.property('contentlength');
expect(r2).to.not.have.property('contenturl');
});

it('should handle single size request', () => {
const requests = spec.buildRequests(bidRequest);
expect(requests.length).to.equal(1);
const r1 = requests[0].url;
expect(r1).to.contain('?pid=263');
expect(r1).to.contain('&sid=662921');
expect(r1).to.contain('&width=640');
expect(r1).to.contain('&height=480');
const r1 = requests[0].data;
expect(r1).to.have.property('pid');
expect(r1.pid).to.equal('263');
expect(r1).to.have.property('sid');
expect(r1.sid).to.equal('662921');
expect(r1).to.have.property('width');
expect(r1.width).to.equal(640);
expect(r1).to.have.property('height');
expect(r1.height).to.equal(480);
});

it('sends bid request to ENDPOINT via GET', () => {
Expand Down