Skip to content

Commit

Permalink
Updated Sublime Bid Adapter to v0.5.0 (prebid#15)
Browse files Browse the repository at this point in the history
* Updated Sublime Bid Adapter to v0.5.0

* Fixed callbackname and added comments

* Fixed url-encoded data from notify + comments and comma

* Added params.debug and mandatory pixel logic

* Fix params debug

* More verbose on pixel debug

* Update isBidRequest validation and query string

* Add missing return statement

* Added pixels for bid and timeout

* Unified sendpixel and sendevent

* Made bid unique

* Refactored code + unit tests
  • Loading branch information
SublimeLeo authored Sep 24, 2019
1 parent 94488e4 commit ec4f667
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 112 deletions.
354 changes: 245 additions & 109 deletions modules/sublimeBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,135 +1,271 @@
import { registerBidder } from '../src/adapters/bidderFactory';
import { config } from '../src/config';
import * as utils from '../src/utils';
import * as url from '../src/url';

const BIDDER_CODE = 'sublime';
const DEFAULT_BID_HOST = 'pbjs.sskzlabs.com';
const DEFAULT_CALLBACK_NAME = 'sublime_prebid_callback';
const DEFAULT_CURRENCY = 'EUR';
const DEFAULT_PROTOCOL = 'https';
const DEFAULT_SAC_HOST = 'sac.ayads.co'
const DEFAULT_TTL = 600;
const SUBLIME_VERSION = '0.4.0';
const SUBLIME_ANTENNA = 'antenna.ayads.co';
const SUBLIME_VERSION = '0.5.0';

export const spec = {
code: BIDDER_CODE,
aliases: [],
/**
* Debug log message
* @param {String} msg
* @param {Object} obj
*/
function log(msg, obj) {
utils.logInfo('SublimeBidAdapter - ' + msg, obj);
}

// Default state
const state = {
zoneId: '',
transactionId: ''
};

/**
* Set a new state
* @param {Object} value
*/
function setState(value) {
Object.assign(state, value);
log('State has been updated :', state);
}

/**
* Send pixel to our debug endpoint
* @param {string} eventName - Event name that will be send in the e= query string
* @param {Boolean} isMandatoryPixel - If set to true, will always send the pixel
*/
function sendEvent(eventName, isMandatoryPixel = false) {
let shoudSendPixel = (isMandatoryPixel || state.debug);
let ts = Date.now();
let eventObject = {
t: ts,
tse: ts,
z: state.zoneId,
e: eventName,
src: 'pa',
puid: state.transactionId,
trId: state.transactionId,
ver: SUBLIME_VERSION,
};

if (shoudSendPixel) {
log('Sending pixel for event: ' + eventName, eventObject);

let queryString = url.formatQS(eventObject);
utils.triggerPixel('https://' + SUBLIME_ANTENNA + '/?' + queryString);
} else {
log('Not sending pixel for event (use debug: true to send it): ' + eventName, eventObject);
}
}

/**
* 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.
*/
function isBidRequestValid(bid) {
return !!Number(bid.params.zoneId);
}

/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} validBidRequests - An array of bids
* @param {Object} bidderRequest - Info describing the request to the server.
* @return {ServerRequest|ServerRequest[]} - Info describing the request to the server.
*/
function buildRequests(validBidRequests, bidderRequest) {
window.sublime = window.sublime || {};

/**
* 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: (bid) => {
return !!bid.params.zoneId;
},

/**
* Make a server request from the list of BidRequests.
*
* @param {BidRequest[]} validBidRequests An array of bids
* @param {Object} bidderRequest - Info describing the request to the server.
* @return ServerRequest Info describing the request to the server.
*/
buildRequests: (validBidRequests, bidderRequest) => {
let commonPayload = {
sublimeVersion: SUBLIME_VERSION,
// Current Prebid params
prebidVersion: '$prebid.version$',
currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY,
timeout: config.getConfig('bidderTimeout'),
let commonPayload = {
sublimeVersion: SUBLIME_VERSION,
// Current Prebid params
prebidVersion: '$prebid.version$',
currencyCode: config.getConfig('currency.adServerCurrency') || DEFAULT_CURRENCY,
timeout: (typeof bidderRequest === 'object' && !!bidderRequest) ? bidderRequest.timeout : config.getConfig('bidderTimeout'),
};

// RefererInfo
if (bidderRequest && bidderRequest.refererInfo) {
commonPayload.referer = bidderRequest.refererInfo.referer;
commonPayload.numIframes = bidderRequest.refererInfo.numIframes;
}
// GDPR handling
if (bidderRequest && bidderRequest.gdprConsent) {
commonPayload.gdprConsent = bidderRequest.gdprConsent.consentString;
commonPayload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side

// Injecting gdpr consent into sublime tag
window.sublime.gdpr = window.sublime.gdpr || {};
window.sublime.gdpr.injected = {
consentString: bidderRequest.gdprConsent.consentString,
gdprApplies: bidderRequest.gdprConsent.gdprApplies
};
}

// RefererInfo
if (bidderRequest && bidderRequest.refererInfo) {
commonPayload.referer = bidderRequest.refererInfo.referer;
commonPayload.numIframes = bidderRequest.refererInfo.numIframes;
}
// GDPR handling
if (bidderRequest && bidderRequest.gdprConsent) {
commonPayload.gdprConsent = bidderRequest.gdprConsent.consentString;
commonPayload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side
return validBidRequests.filter((bid, index) => {
// Any bidRequest after the first is skipped
if (index) {
let leftoverZonesIds = validBidRequests.slice(1).map(bid => { return bid.params.zoneId }).join(',');
utils.logWarn(`SublimeBidAdapter - ZoneIds ${leftoverZonesIds} are ignored. Only one ZoneId per page can be instanciated.`);
return false;
}

return validBidRequests.map(bid => {
let bidPayload = {
adUnitCode: bid.adUnitCode,
auctionId: bid.auctionId,
bidder: bid.bidder,
bidderRequestId: bid.bidderRequestId,
bidRequestsCount: bid.bidRequestsCount,
requestId: bid.bidId,
sizes: bid.sizes.map(size => ({
w: size[0],
h: size[1],
})),
transactionId: bid.transactionId,
zoneId: bid.params.zoneId,
};
return bid;
}).map(bid => {
let bidHost = bid.params.bidHost || DEFAULT_BID_HOST;
let callbackName = (bid.params.callbackName || DEFAULT_CALLBACK_NAME) + '_' + bid.params.zoneId;
let protocol = bid.params.protocol || DEFAULT_PROTOCOL;
let sacHost = bid.params.sacHost || DEFAULT_SAC_HOST;

let protocol = bid.params.protocol || DEFAULT_PROTOCOL;
let bidHost = bid.params.bidHost || DEFAULT_BID_HOST;
let payload = Object.assign({}, commonPayload, bidPayload);

return {
method: 'POST',
url: protocol + '://' + bidHost + '/bid',
data: payload,
options: {
contentType: 'application/json',
withCredentials: true
},
};
setState({
transactionId: bid.transactionId,
zoneId: bid.params.zoneId,
debug: bid.params.debug || false,
});
},

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @param {*} bidRequest An object with bid request informations
* @return {Bid[]} An array of bids which were nested inside the server.
*/
interpretResponse: (serverResponse, bidRequest) => {
const bidResponses = [];
const response = serverResponse.body;

if (response) {
if (response.timeout || !response.ad || response.ad.match(/<!-- No ad -->/gmi)) {
return bidResponses;
}

// Setting our returned sizes object to default values
let returnedSizes = {
width: 1800,
height: 1000
};

// Verifying Banner sizes
if (bidRequest && bidRequest.data && bidRequest.data.w === 1 && bidRequest.data.h === 1) {
// If banner sizes are 1x1 we set our default size object to 1x1
returnedSizes = {
width: 1,
height: 1
};
}

const bidResponse = {
requestId: response.requestId || '',
// Adding Sublime tag
let script = document.createElement('script');
script.type = 'application/javascript';
script.src = 'https://' + sacHost + '/sublime/' + bid.params.zoneId + '/prebid?callback=' + callbackName;
document.body.appendChild(script);

// Register a callback to send notify
window[callbackName] = (response) => {
let xhr = new XMLHttpRequest();
let hasAd = response.ad ? '1' : '0';
let url = protocol + '://' + bidHost + '/notify';

let params = {
a: hasAd,
ad: response.ad || '',
cpm: response.cpm || 0,
width: response.width || returnedSizes.width,
height: response.height || returnedSizes.height,
creativeId: response.creativeId || 1,
dealId: response.dealId || 1,
currency: response.currency || DEFAULT_CURRENCY,
netRevenue: response.netRevenue || true,
ttl: response.ttl || DEFAULT_TTL,
ad: response.ad,
notify: 1,
requestId: bid.bidId ? encodeURIComponent(bid.bidId) : null,
transactionId: bid.transactionId,
zoneId: bid.params.zoneId
};

bidResponses.push(bidResponse);
let queryString = Object.keys(params).map(key => {
return key + '=' + encodeURIComponent(params[key])
}).join('&');

xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(queryString);
return xhr;
};

let bidPayload = {
adUnitCode: bid.adUnitCode,
auctionId: bid.auctionId,
bidder: bid.bidder,
bidderRequestId: bid.bidderRequestId,
bidRequestsCount: bid.bidRequestsCount,
requestId: bid.bidId,
sizes: bid.sizes.map(size => ({
w: size[0],
h: size[1],
})),
transactionId: bid.transactionId,
zoneId: bid.params.zoneId,
};

let payload = Object.assign({}, commonPayload, bidPayload);

return {
method: 'POST',
url: protocol + '://' + bidHost + '/bid',
data: payload,
options: {
contentType: 'application/json',
withCredentials: true
},
}
});
}

/**
* Unpack the response from the server into a list of bids.
*
* @param {*} serverResponse A successful response from the server.
* @param {*} bidRequest An object with bid request informations
* @return {Bid[]} An array of bids which were nested inside the server.
*/
function interpretResponse(serverResponse, bidRequest) {
const bidResponses = [];
const response = serverResponse.body;

sendEvent('dintres');

if (response) {
if (response.timeout || !response.ad || /<!--\s+No\s+ad\s+-->/gmi.test(response.ad)) {
return bidResponses;
}

// Setting our returned sizes object to default values
let returnedSizes = {
width: 1800,
height: 1000
};

// Verifying Banner sizes
if (bidRequest && bidRequest.data && bidRequest.data.w === 1 && bidRequest.data.h === 1) {
// If banner sizes are 1x1 we set our default size object to 1x1
returnedSizes = {
width: 1,
height: 1
};
}

return bidResponses;
},
const bidResponse = {
requestId: response.requestId || '',
cpm: response.cpm || 0,
width: response.width || returnedSizes.width,
height: response.height || returnedSizes.height,
creativeId: response.creativeId || 1,
dealId: response.dealId || 1,
currency: response.currency || DEFAULT_CURRENCY,
netRevenue: response.netRevenue || true,
ttl: response.ttl || DEFAULT_TTL,
ad: response.ad,
};

sendEvent('bid', true);
bidResponses.push(bidResponse);
} else {
sendEvent('dnobid');
}

return bidResponses;
}

/**
* Send debug when we timeout
* @param {Object} timeoutData
*/
function onTimeout(timeoutData) {
log('Timeout from adapter', timeoutData);
sendEvent('dbidtimeout', true);
}

export const spec = {
code: BIDDER_CODE,
aliases: [],
isBidRequestValid: isBidRequestValid,
buildRequests: buildRequests,
interpretResponse: interpretResponse,
onTimeout: onTimeout,
};

registerBidder(spec);
3 changes: 0 additions & 3 deletions test/spec/modules/sublimeBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,14 @@ describe('Sublime Adapter', function() {

it('should have a post method', function() {
expect(request[0].method).to.equal('POST');
expect(request[1].method).to.equal('POST');
});

it('should contains a request id equals to the bid id', function() {
expect(request[0].data.requestId).to.equal(bidRequests[0].bidId);
expect(request[1].data.requestId).to.equal(bidRequests[1].bidId);
});

it('should have an url that contains bid keyword', function() {
expect(request[0].url).to.match(/bid/);
expect(request[1].url).to.match(/bid/);
});
});

Expand Down

0 comments on commit ec4f667

Please sign in to comment.