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

New bid adapter for Smaato #5418

Merged
merged 39 commits into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
843583a
SmaatoBidAdapter: Initial commit
sbrosinski May 15, 2020
8f27ce2
Add consent management
sbrosinski May 18, 2020
3051852
Add consent test
sbrosinski May 18, 2020
72975e7
Smaato: Additional tests
sbrosinski May 22, 2020
b4d80db
Smaato: Test endpoint for prebid requests
sbrosinski May 25, 2020
23a2928
Cleaned up test code
sbrosinski May 27, 2020
ed41c93
Validate bid requests
sbrosinski May 29, 2020
9f0946d
Improve banner ad rendering
sbrosinski May 29, 2020
d733642
Smaato: Img ad renderer
sbrosinski Jun 2, 2020
7d8ec0e
Cleanup
sbrosinski Jun 4, 2020
9121829
Smaato: Handle TTL dynamically
sbrosinski Jun 8, 2020
119e19d
Smaato. Render richmedia ads
sbrosinski Jun 8, 2020
ec39ebc
Smaato: Bugfixes
sbrosinski Jun 10, 2020
9145f21
Smaato: More meta data
sbrosinski Jun 16, 2020
249769c
Smaato: Consent string handling
sbrosinski Jun 16, 2020
24609ae
Smaato: new endpoint
sbrosinski Jun 18, 2020
f4f8c31
Smaato: Fix test
sbrosinski Jun 18, 2020
c932b7d
Smaato: Fix test
sbrosinski Jun 18, 2020
cfa4dee
Smaato: Add additional optional params
sbrosinski Jun 22, 2020
beca5e1
Smaato: Use first party data for additional context
sbrosinski Jun 23, 2020
0d3ee12
Merge branch 'master' into smaato-adapter
sbrosinski Jun 23, 2020
0a1bc9d
Smaato: Undoing changes to karma conf
sbrosinski Jun 23, 2020
ecd9c9c
Smaato: Undo karma konf changes
sbrosinski Jun 23, 2020
1f0cbb8
Smaato: Fixed test
sbrosinski Jun 24, 2020
a43760f
Smaato: Update adapter doc
sbrosinski Jun 24, 2020
2375c17
Smaato: remove unused code
sbrosinski Jun 24, 2020
1119229
Smaato: Remove package-lock.json
sbrosinski Jun 24, 2020
c70d057
Smaato: Fix package-lock.json
sbrosinski Jun 24, 2020
9cc9f80
Smaato: stricter parsing of first party data
sbrosinski Jun 25, 2020
e34e6f0
Smaato: increase adapter version
sbrosinski Jun 25, 2020
4a5e2a6
Smaato: Fix fpd types to reflect openrtb types
sbrosinski Jun 30, 2020
fa4c3e9
Smaato: WiP for video support
sbrosinski Jul 1, 2020
4aedc23
Smaato: WiP on video support
sbrosinski Jul 3, 2020
fec868d
Smaato: Video support
sbrosinski Jul 10, 2020
ef00d2f
Smaato: Fix test data
sbrosinski Jul 10, 2020
4c2509e
Merge branch 'master' of github.com:prebid/Prebid.js into smaato-adapter
sbrosinski Jul 10, 2020
a3a2fbc
Merge branch 'smaato-adapter' into smaato-adapter-video
sbrosinski Jul 10, 2020
b8cc0fb
Smaato: Review feedback
sbrosinski Jul 22, 2020
039071b
Smaato: Provide valid publisherId / adspaceId
sbrosinski Jul 22, 2020
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
268 changes: 268 additions & 0 deletions modules/smaatoBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
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 = 'smaato';
const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid';
const CLIENT = 'prebid_js_$prebid.version$_1.0'

/**
* Transform BidRequest to OpenRTB-formatted BidRequest Object
* @param {Array<BidRequest>} validBidRequests
* @param {any} bidderRequest
* @returns {string}
*/
const buildOpenRtbBidRequestPayload = (validBidRequests, bidderRequest) => {
/**
* Turn incoming prebid sizes into openRtb format mapping.
* @param {*} sizes in format [[10, 10], [20, 20]]
* @returns array of openRtb format mappings [{w: 10, h: 10}, {w: 20, h: 20}]
*/
const parseSizes = (sizes) => {
return sizes.map((size) => {
return {w: size[0], h: size[1]};
})
}

const imp = validBidRequests.map(br => {
const bannerMediaType = utils.deepAccess(br, 'mediaTypes.banner');
const videoMediaType = utils.deepAccess(br, 'mediaTypes.video');
let result = {
id: br.bidId,
tagid: utils.deepAccess(br, 'params.adspaceId')
}

if (bannerMediaType) {
const sizes = parseSizes(utils.getAdUnitSizes(br));
result.banner = {
w: sizes[0].w,
h: sizes[0].h,
format: sizes
msm0504 marked this conversation as resolved.
Show resolved Hide resolved
}
}

if (videoMediaType) {
result.video = {
mimes: videoMediaType.mimes,
minduration: videoMediaType.minduration,
startdelay: videoMediaType.startdelay,
linearity: videoMediaType.linearity,
w: videoMediaType.playerSize[0][0],
h: videoMediaType.playerSize[0][1],
maxduration: videoMediaType.maxduration,
skip: videoMediaType.skip,
protocols: videoMediaType.protocols,
ext: {
rewarded: videoMediaType.ext && videoMediaType.ext.rewarded ? videoMediaType.ext.rewarded : 0
},
skipmin: videoMediaType.skipmin,
api: videoMediaType.api
}
}

return result;
});

const request = {
id: bidderRequest.auctionId,
at: 1,
imp,
cur: ['USD'],
tmax: bidderRequest.timeout,
site: {
id: window.location.hostname,
publisher: {
id: utils.deepAccess(validBidRequests[0], 'params.publisherId')
},
domain: window.location.hostname,
page: window.location.href,
ref: bidderRequest.refererInfo.referer
},
device: {
language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '',
ua: navigator.userAgent,
dnt: utils.getDNT() ? 1 : 0,
h: screen.height,
w: screen.width
},
regs: {
coppa: config.getConfig('coppa') === true ? 1 : 0,
ext: {}
},
user: {
ext: {}
},
ext: {
client: CLIENT
}
};

Object.assign(request.user, config.getConfig('fpd.user'));
Object.assign(request.site, config.getConfig('fpd.context'));

if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies === true) {
utils.deepSetValue(request, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0);
utils.deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString);
}

if (bidderRequest.uspConsent !== undefined) {
utils.deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent);
}

utils.logInfo('[SMAATO] OpenRTB Request:', request);
return JSON.stringify(request);
}

export const spec = {
code: BIDDER_CODE,
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: (bid) => {
return typeof bid.params === 'object' &&
typeof bid.params.publisherId === 'string' &&
typeof bid.params.adspaceId === 'string';
},

/**
* 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: (validBidRequests, bidderRequest) => {
utils.logInfo('[SMAATO] Client version:', CLIENT);
return {
method: 'POST',
url: validBidRequests[0].params.endpoint || SMAATO_ENDPOINT,
data: buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest),
options: {
withCredentials: true,
crossOrigin: true,
}
};
},
/**
* 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: (serverResponse, bidRequest) => {
// response is empty (HTTP 204)
if (utils.isEmpty(serverResponse.body)) {
utils.logInfo('[SMAATO] Empty response body HTTP 204, no bids');
return []; // no bids
}

let serverResponseHeaders = serverResponse.headers;
const smtAdType = serverResponseHeaders.get('X-SMT-ADTYPE');

const smtExpires = serverResponseHeaders.get('X-SMT-Expires');
let ttlSec = 300;
utils.logInfo('[SMAATO] Expires:', smtExpires);
if (smtExpires) {
ttlSec = Math.floor((smtExpires - Date.now()) / 1000);
}

const res = serverResponse.body;
utils.logInfo('[SMAATO] OpenRTB Response:', res);

var bids = [];
res.seatbid.forEach(sb => {
sb.bid.forEach(b => {
let resultingBid = {
requestId: b.impid,
cpm: b.price || 0,
width: b.w,
height: b.h,
ttl: ttlSec,
creativeId: b.crid,
dealId: b.dealid || null,
netRevenue: true,
currency: res.cur,
meta: {
advertiserDomains: b.adomain,
networkName: b.bidderName,
agencyId: sb.seat
}
};

switch (smtAdType) {
case 'Img':
resultingBid.ad = createImgAd(b.adm);
resultingBid.meta.mediaType = BANNER;
bids.push(resultingBid);
break;
case 'Richmedia':
resultingBid.ad = createRichmediaAd(b.adm);
resultingBid.meta.mediaType = BANNER;
bids.push(resultingBid);
break;
case 'Video':
resultingBid.vastXml = b.adm;
resultingBid.meta.mediaType = VIDEO;
bids.push(resultingBid);
break;
default:
utils.logInfo('[SMAATO] Invalid ad type:', smtAdType);
}
});
});

utils.logInfo('[SMAATO] Prebid bids:', bids);
return bids;
},

/**
* 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.
* @return {UserSync[]} The user syncs which should be dropped.
*/
getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => {
const syncs = []
return syncs;
}
}
registerBidder(spec);

const createImgAd = (adm) => {
const image = JSON.parse(adm).image;

let clickEvent = '';
image.clicktrackers.forEach(src => {
clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`;
})

let markup = `<div style="cursor:pointer" onclick="${clickEvent};window.open(decodeURIComponent('${encodeURIComponent(image.img.ctaurl)}'));"><img src="${image.img.url}" width="${image.img.w}" height="${image.img.h}"/>`;

image.impressiontrackers.forEach(src => {
markup += `<img src="${src}" alt="" width="0" height="0"/>`;
});

return markup + '</div>';
};

const createRichmediaAd = (adm) => {
const rich = JSON.parse(adm).richmedia;
let clickEvent = '';
rich.clicktrackers.forEach(src => {
clickEvent += `fetch(decodeURIComponent('${encodeURIComponent(src)}'), {cache: 'no-cache'});`;
})

let markup = `<div onclick="${clickEvent}">${rich.mediadata.content}`;

rich.impressiontrackers.forEach(src => {
markup += `<img src="${src}" alt="" width="0" height="0"/>`;
});

return markup + '</div>';
};
64 changes: 64 additions & 0 deletions modules/smaatoBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Overview

```
Module Name: Smaato Bidder Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

The Smaato adapter requires setup and approval from the Smaato team, even for existing Smaato publishers. Please reach out to your account team or [email protected] for more information.

# Test Parameters

For banner adunits:

```
var adUnits = [{
"code": "banner-unit",
"mediaTypes": {
"banner": {
"sizes": [320, 50]
}
},
"bids": [{
"bidder": "smaato",
"params": {
"publisherId": "1100042525",
"adspaceId": "130563103"
}
}]
}];
```

For video adunits:

```
var adUnits = [{
"code": "video unit",
"mediaTypes": {
"video": {
"context": "instream",
"playerSize": [640, 480],
"mimes": ["video/mp4"],
"minduration": 5,
"maxduration": 30,
"startdelay": 0,
"linearity": 1,
"protocols": [7],
"skip": 1,
"skipmin": 5,
"api": [7],
"ext": {"rewarded": 0}
}
},
"bids": [{
"bidder": "smaato",
"params": {
"publisherId": "1100042525",
"adspaceId": "130563103"
}
}]
}];
```
Loading