Skip to content

Commit

Permalink
sspBC Bid Adaptor : add native support, instream video support, & tes…
Browse files Browse the repository at this point in the history
…t coverage updates (#7447)

* Update tests for sspBC adapter

Update tests for sspBC adapter:
- change userSync test (due to tcf param appended in v4.6)
- add tests for onBidWon and onTimeout

* [sspbc-adapter] RC for 5.2 version of sspBCBidAdapter

* [sspbc-adapter] RC for 5.2 version of sspBCBidAdapter(fixed commit)

Co-authored-by: Wojciech Biały <wb@WojciechBialy.local>
  • Loading branch information
wojciech-bialy-wpm and Wojciech Biały authored Oct 4, 2021
1 parent 20e0c44 commit d15d519
Show file tree
Hide file tree
Showing 3 changed files with 418 additions and 75 deletions.
219 changes: 207 additions & 12 deletions modules/sspBCBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { isArray, deepAccess, logWarn, parseUrl } from '../src/utils.js';
import { ajax } from '../src/ajax.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER } from '../src/mediaTypes.js';
import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js';
import strIncludes from 'core-js-pure/features/string/includes.js';

const BIDDER_CODE = 'sspBC';
const BIDDER_URL = 'https://ssp.wp.pl/bidder/';
const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync';
const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify';
const TMAX = 450;
const BIDDER_VERSION = '5.0';
const BIDDER_VERSION = '5.2';
const W = window;
const { navigator } = W;
const oneCodeDetection = {};
Expand Down Expand Up @@ -155,6 +155,108 @@ const mapBanner = slot => {
}
}

/**
* @param {string} paramName Native parameter name
* @param {object} paramValue Native parameter value
* @returns {object} native asset object that conforms to ortb native ads spec
*/
const mapAsset = (paramName, paramValue) => {
let asset;
switch (paramName) {
case 'title':
asset = {
id: 0,
required: paramValue.required,
title: { len: paramValue.len }
}
break;
case 'cta':
asset = {
id: 1,
required: paramValue.required,
data: { type: 12 }
}
break;
case 'icon':
asset = {
id: 2,
required: paramValue.required,
img: { type: 1, w: paramValue.sizes[0], h: paramValue.sizes[1] }
}
break;
case 'image':
asset = {
id: 3,
required: paramValue.required,
img: { type: 3, w: paramValue.sizes[0], h: paramValue.sizes[1] }
}
break;
case 'body':
asset = {
id: 4,
required: paramValue.required,
data: { type: 2 }
}
break;
case 'sponsoredBy':
asset = {
id: 5,
required: paramValue.required,
data: { type: 1 }
}
break;
}
return asset;
}

/**
* @param {object} slot Ad Unit Params by Prebid
* @returns {object} native object that conforms to ortb native ads spec
*/
const mapNative = slot => {
const native = utils.deepAccess(slot, 'mediaTypes.native');
let assets;
if (native) {
const nativeParams = Object.keys(native);
assets = [];
nativeParams.forEach(par => {
const newAsset = mapAsset(par, native[par]);
if (newAsset) { assets.push(newAsset) };
});
}
return assets ? { request: JSON.stringify({ native: { assets } }) } : undefined;
}

var mapVideo = slot => {
var video = utils.deepAccess(slot, 'mediaTypes.video');
var videoParamsUsed = ['api', 'context', 'linearity', 'maxduration', 'mimes', 'protocols'];
var videoAssets;

if (video) {
var videoParams = Object.keys(video);
var playerSize = video.playerSize;
videoAssets = {}; // player width / height

if (playerSize) {
var maxSize = playerSize.reduce(function (prev, next) {
return next[0] >= prev[0] && next[1] >= prev[1] ? next : prev;
}, [1, 1]);
videoAssets.w = maxSize[0];
videoAssets.h = maxSize[1];
} // remaining supported params

videoParams.forEach(function (par) {
if (videoParamsUsed.indexOf(par) >= 0) {
videoAssets[par] = video[par];
}

;
});
}

return videoAssets;
};

const mapImpression = slot => {
const { adUnitCode, bidId, params = {}, ortb2Imp = {} } = slot;
const { id, siteId } = params;
Expand All @@ -165,32 +267,100 @@ const mapImpression = slot => {
send this info as ext.pbsize
*/
const slotSize = slot.sizes.length ? slot.sizes.reduce((prev, next) => prev[0] * prev[1] <= next[0] * next[1] ? next : prev).join('x') : '1x1';
adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] + 1 : 1;
adSizesCalled[slotSize] = adSizesCalled[slotSize] ? adSizesCalled[slotSize] += 1 : 1;
ext.data = Object.assign({ pbsize: `${slotSize}_${adSizesCalled[slotSize]}` }, ext.data);

const imp = {
id: id && siteId ? id : 'bidid-' + bidId,
banner: mapBanner(slot),
// native: mapNative(slot),
native: mapNative(slot),
video: mapVideo(slot),
tagid: adUnitCode,
ext,
};

// Check floorprices for this imp
if (typeof slot.getFloor === 'function') {
let bannerFloor = 0;
// sspBC adapter accepts only floor per imp - check for maximum value for requested ad types and sizes
var bannerFloor = 0;
var nativeFloor = 0;
var videoFloor = 0; // sspBC adapter accepts only floor per imp - check for maximum value for requested ad types and sizes

if (slot.sizes.length) {
bannerFloor = slot.sizes.reduce((prev, next) => {
const currentFloor = slot.getFloor({ mediaType: 'banner', size: next }).floor;
bannerFloor = slot.sizes.reduce(function (prev, next) {
var currentFloor = slot.getFloor({
mediaType: 'banner',
size: next
}).floor;
return prev > currentFloor ? prev : currentFloor;
}, 0);
}
imp.bidfloor = bannerFloor;

nativeFloor = slot.getFloor({
mediaType: 'native'
});
videoFloor = slot.getFloor({
mediaType: 'video'
});
imp.bidfloor = Math.max(bannerFloor, nativeFloor, videoFloor);
}
return imp;
}

const isVideoAd = bid => {
const xmlTester = new RegExp(/^<\?xml/);
return bid.adm && bid.adm.match(xmlTester);
}

const isNativeAd = bid => {
const xmlTester = new RegExp(/^{['"]native['"]/);

return bid.adm && bid.adm.match(xmlTester);
}

const parseNative = nativeData => {
const result = {};
nativeData.assets.forEach(asset => {
const id = parseInt(asset.id);
switch (id) {
case 0:
result.title = asset.title.text;
break;
case 2:
result.icon = {
url: asset.img.url,
width: asset.img.w,
height: asset.img.h,
};
break;
case 3:
result.image = {
url: asset.img.url,
width: asset.img.w,
height: asset.img.h,
};
break;
case 4:
result.body = asset.data.value;
break;
case 5:
result.sponsoredBy = asset.data.value;
break;

default:
utils.logWarn('Unrecognized native asset', asset);
}
});
result.clickUrl = nativeData.link.url;
result.impressionTrackers = nativeData.imptrackers;

if (utils.isArray(nativeData.jstracker)) {
result.javascriptTrackers = nativeData.jstracker;
} else if (nativeData.jstracker) {
result.javascriptTrackers = [nativeData.jstracker];
}
return result;
}

const renderCreative = (site, auctionId, bid, seat, request) => {
let gam;

Expand Down Expand Up @@ -243,6 +413,7 @@ const renderCreative = (site, auctionId, bid, seat, request) => {
<script>
window.rekid = ${site.id};
window.slot = ${parseInt(site.slot, 10)};
window.responseTimestamp = ${Date.now()};
window.wp_sn = "${site.sn}";
window.mcad = JSON.parse(decodeURI(atob("${mcbase}")));
window.gdpr = ${JSON.stringify(request.gdprConsent)};
Expand All @@ -268,7 +439,7 @@ const renderCreative = (site, auctionId, bid, seat, request) => {
const spec = {
code: BIDDER_CODE,
aliases: [],
supportedMediaTypes: [BANNER],
supportedMediaTypes: [BANNER, NATIVE, VIDEO],
isBidRequestValid(bid) {
// as per OneCode integration, bids without params are valid
return true;
Expand Down Expand Up @@ -377,15 +548,39 @@ const spec = {
width: w,
height: h,
bidderCode: BIDDER_CODE,
mediaType: 'banner',
meta: {
advertiserDomains: adomain,
networkName: seat,
},
netRevenue: true,
ad: renderCreative(site, response.id, serverBid, seat, bidderRequest),
};

// mediaType and ad data for instream / native / banner
if (isVideoAd(serverBid)) {
// video
bid.adType = 'instream';
bid.mediaType = 'video';
bid.vastXml = serverBid.adm;
bid.vastContent = serverBid.adm;
} else if (isNativeAd(serverBid)) {
// native
bid.mediaType = 'native';
// check native object
try {
const nativeData = JSON.parse(serverBid.adm).native;
bid.native = parseNative(nativeData);
bid.width = 1;
bid.height = 1;
} catch (err) {
utils.logWarn('Could not parse native data', serverBid.adm);
bid.cpm = 0;
}
} else {
// banner ad (default)
bid.mediaType = 'banner';
bid.ad = renderCreative(site, response.id, serverBid, seat, bidderRequest);
}

if (bid.cpm > 0) {
bids.push(bid);
}
Expand Down
2 changes: 1 addition & 1 deletion modules/sspBCBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Maintainer: wojciech.bialy@grupawp.pl
# Description

Module that connects to Wirtualna Polska Media header bidding endpoint to fetch bids.
Only banner format is supported.
Supports Banner, Video (instream) and Native formats
Supported currencies: USD, EUR, PLN

Required parameters:
Expand Down
Loading

0 comments on commit d15d519

Please sign in to comment.