-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add AMX adapter (after fixing ie11 test failure)
- Loading branch information
Showing
4 changed files
with
739 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
import { registerBidder } from '../src/adapters/bidderFactory.js'; | ||
import { BANNER, VIDEO } from '../src/mediaTypes.js'; | ||
import { parseUrl, deepAccess, _each, formatQS, getUniqueIdentifierStr, triggerPixel } from '../src/utils.js'; | ||
|
||
const BIDDER_CODE = 'amx'; | ||
const SIMPLE_TLD_TEST = /\.co\.\w{2,4}$/; | ||
const DEFAULT_ENDPOINT = 'https://prebid.a-mo.net/a/c'; | ||
const VERSION = 'pba1.0'; | ||
const xmlDTDRxp = /^\s*<\?xml[^\?]+\?>/; | ||
const VAST_RXP = /^\s*<\??(?:vast|xml)/i; | ||
const TRACKING_ENDPOINT = 'https://1x1.a-mo.net/hbx/'; | ||
|
||
const getLocation = (request) => | ||
parseUrl(deepAccess(request, 'refererInfo.canonicalUrl', location.href)) | ||
|
||
const largestSize = (sizes, mediaTypes) => { | ||
const allSizes = sizes | ||
.concat(deepAccess(mediaTypes, `${BANNER}.sizes`, []) || []) | ||
.concat(deepAccess(mediaTypes, `${VIDEO}.sizes`, []) || []) | ||
|
||
return allSizes.sort((a, b) => (b[0] * b[1]) - (a[0] * a[1]))[0]; | ||
} | ||
|
||
function flatMap(input, mapFn) { | ||
if (input == null) { | ||
return [] | ||
} | ||
return input.map(mapFn) | ||
.reduce((acc, item) => item != null && acc.concat(item), []) | ||
} | ||
|
||
const generateDTD = (xmlDocument) => | ||
`<?xml version="${xmlDocument.xmlVersion}" encoding="${xmlDocument.xmlEncoding}" ?>`; | ||
|
||
const isVideoADM = (html) => html != null && VAST_RXP.test(html); | ||
const getMediaType = (bid) => isVideoADM(bid.adm) ? VIDEO : BANNER; | ||
const nullOrType = (value, type) => | ||
value == null || (typeof value) === type // eslint-disable-line valid-typeof | ||
|
||
function getID(loc) { | ||
const host = loc.hostname.split('.'); | ||
const short = host.slice( | ||
host.length - (SIMPLE_TLD_TEST.test(loc.host) ? 3 : 2) | ||
).join('.'); | ||
return btoa(short).replace(/=+$/, ''); | ||
} | ||
|
||
const enc = encodeURIComponent; | ||
|
||
function nestedQs (qsData) { | ||
const out = []; | ||
Object.keys(qsData || {}).forEach((key) => { | ||
out.push(enc(key) + '=' + enc(String(qsData[key]))); | ||
}); | ||
|
||
return enc(out.join('&')); | ||
} | ||
|
||
function createBidMap(bids) { | ||
const out = {}; | ||
_each(bids, (bid) => { | ||
out[bid.bidId] = convertRequest(bid) | ||
}) | ||
return out; | ||
} | ||
|
||
const trackEvent = (eventName, data) => | ||
triggerPixel(`${TRACKING_ENDPOINT}g_${eventName}?${formatQS({ | ||
...data, | ||
ts: Date.now(), | ||
eid: getUniqueIdentifierStr(), | ||
})}`); | ||
|
||
function convertRequest(bid) { | ||
const size = largestSize(bid.sizes, bid.mediaTypes) || [0, 0]; | ||
const isVideoBid = bid.mediaType === VIDEO || VIDEO in bid.mediaTypes | ||
const av = isVideoBid || size[1] > 100; | ||
const tid = deepAccess(bid, 'params.tagId') | ||
|
||
const params = { | ||
av, | ||
vr: isVideoBid, | ||
aw: size[0], | ||
ah: size[1], | ||
tf: 0, | ||
}; | ||
|
||
if (typeof tid === 'string' && tid.length > 0) { | ||
params.i = tid; | ||
} | ||
return params; | ||
} | ||
|
||
function decorateADM(bid) { | ||
const impressions = deepAccess(bid, 'ext.himp', []) | ||
.concat(bid.nurl != null ? [bid.nurl] : []) | ||
.filter((imp) => imp != null && imp.length > 0) | ||
.map((src) => `<img src="${src}" width="0" height="0"/>`) | ||
.join(''); | ||
return bid.adm + impressions; | ||
} | ||
|
||
function transformXmlSimple(bid) { | ||
const pixels = [] | ||
_each([bid.nurl].concat(bid.ext != null && bid.ext.himp != null ? bid.ext.himp : []), (pixel) => { | ||
if (pixel != null) { | ||
pixels.push(`<Impression><![CDATA[${pixel}]]></Impression>`) | ||
} | ||
}); | ||
// find the current "Impression" here & slice ours in | ||
const impressionIndex = bid.adm.indexOf('<Impression') | ||
return bid.adm.slice(0, impressionIndex) + pixels.join('') + bid.adm.slice(impressionIndex) | ||
} | ||
|
||
function getOuterHTML(node) { | ||
return 'outerHTML' in node && node.outerHTML != null | ||
? node.outerHTML : (new XMLSerializer()).serializeToString(node) | ||
} | ||
|
||
function decorateVideoADM(bid) { | ||
if (typeof DOMParser === 'undefined' || DOMParser.prototype.parseFromString == null) { | ||
return transformXmlSimple(bid) | ||
} | ||
|
||
const doc = new DOMParser().parseFromString(bid.adm, 'text/xml'); | ||
if (doc == null || doc.querySelector('parsererror') != null) { | ||
return null; | ||
} | ||
|
||
const root = doc.querySelector('InLine,Wrapper') | ||
if (root == null) { | ||
return null; | ||
} | ||
|
||
const pixels = [bid.nurl].concat(bid.ext != null && bid.ext.himp != null ? bid.ext.himp : []) | ||
.filter((url) => url != null); | ||
|
||
_each(pixels, (pxl) => { | ||
const imagePixel = doc.createElement('Impression'); | ||
const cdata = doc.createCDATASection(pxl); | ||
imagePixel.appendChild(cdata); | ||
root.appendChild(imagePixel); | ||
}); | ||
|
||
const dtdMatch = xmlDTDRxp.exec(bid.adm); | ||
return (dtdMatch != null ? dtdMatch[0] : generateDTD(doc)) + getOuterHTML(doc.documentElement); | ||
} | ||
|
||
function resolveSize(bid, request, bidId) { | ||
if (bid.w != null && bid.w > 1 && bid.h != null && bid.h > 1) { | ||
return [bid.w, bid.h]; | ||
} | ||
|
||
const bidRequest = request.m[bidId]; | ||
if (bidRequest == null) { | ||
return [0, 0]; | ||
} | ||
|
||
return [bidRequest.aw, bidRequest.ah]; | ||
} | ||
|
||
export const spec = { | ||
code: BIDDER_CODE, | ||
supportedMediaTypes: [BANNER, VIDEO], | ||
|
||
isBidRequestValid(bid) { | ||
return nullOrType(deepAccess(bid, 'params.endpoint', null), 'string') && | ||
nullOrType(deepAccess(bid, 'params.tagId', null), 'string') && | ||
nullOrType(deepAccess(bid, 'params.testMode', null), 'boolean'); | ||
}, | ||
|
||
buildRequests(bidRequests, bidderRequest) { | ||
const loc = getLocation(bidderRequest); | ||
const tagId = deepAccess(bidRequests[0], 'params.tagId', null); | ||
const testMode = deepAccess(bidRequests[0], 'params.testMode', 0); | ||
|
||
const payload = { | ||
a: bidderRequest.auctionId, | ||
B: 0, | ||
b: loc.host, | ||
tm: testMode, | ||
V: '$prebid.version$', | ||
i: (testMode && tagId != null) ? tagId : getID(loc), | ||
l: {}, | ||
f: 0.01, | ||
cv: VERSION, | ||
st: 'prebid', | ||
h: screen.height, | ||
w: screen.width, | ||
gs: deepAccess(bidderRequest, 'gdprConsent.gdprApplies', '0'), | ||
gc: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), | ||
u: deepAccess(bidderRequest, 'refererInfo.canonicalUrl', loc.href), | ||
do: loc.host, | ||
re: deepAccess(bidderRequest, 'refererInfo.referer'), | ||
usp: bidderRequest.uspConsent || '1---', | ||
smt: 1, | ||
d: '', | ||
m: createBidMap(bidRequests), | ||
}; | ||
|
||
return { | ||
data: payload, | ||
method: 'POST', | ||
url: deepAccess(bidRequests[0], 'params.endpoint', DEFAULT_ENDPOINT), | ||
withCredentials: true, | ||
}; | ||
}, | ||
|
||
getUserSyncs(syncOptions, serverResponses) { | ||
if (serverResponses == null || serverResponses.length === 0) { | ||
return [] | ||
} | ||
const output = [] | ||
_each(serverResponses, function ({ body: response }) { | ||
if (response != null && response.p != null && response.p.hreq) { | ||
_each(response.p.hreq, function (syncPixel) { | ||
const pixelType = syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image'; | ||
if (syncOptions.iframeEnabled || pixelType === 'image') { | ||
output.push({ | ||
url: syncPixel, | ||
type: pixelType, | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
return output; | ||
}, | ||
|
||
interpretResponse(serverResponse, request) { | ||
// validate the body/response | ||
const response = serverResponse.body; | ||
if (response == null || typeof response === 'string') { | ||
return []; | ||
} | ||
|
||
return flatMap(Object.keys(response.r), (bidID) => { | ||
return flatMap(response.r[bidID], (siteBid) => | ||
siteBid.b.map((bid) => { | ||
const mediaType = getMediaType(bid); | ||
// let ad = null; | ||
let ad = mediaType === BANNER ? decorateADM(bid) : decorateVideoADM(bid); | ||
if (ad == null) { | ||
return null; | ||
} | ||
|
||
const size = resolveSize(bid, request.data, bidID); | ||
|
||
return ({ | ||
requestId: bidID, | ||
cpm: bid.price, | ||
width: size[0], | ||
height: size[1], | ||
creativeId: bid.crid, | ||
currency: 'USD', | ||
netRevenue: true, | ||
[mediaType === VIDEO ? 'vastXml' : 'ad']: ad, | ||
meta: { | ||
advertiserDomains: bid.adomain, | ||
mediaType, | ||
}, | ||
ttl: mediaType === VIDEO ? 90 : 70 | ||
}); | ||
})).filter((possibleBid) => possibleBid != null); | ||
}); | ||
}, | ||
|
||
onSetTargeting(targetingData) { | ||
if (targetingData == null) { | ||
return; | ||
} | ||
|
||
trackEvent('pbst', { | ||
A: targetingData.bidder, | ||
w: targetingData.width, | ||
h: targetingData.height, | ||
bid: targetingData.adId, | ||
c1: targetingData.mediaType, | ||
np: targetingData.cpm, | ||
aud: targetingData.requestId, | ||
a: targetingData.adUnitCode, | ||
c2: nestedQs(targetingData.adserverTargeting), | ||
}); | ||
}, | ||
|
||
onTimeout(timeoutData) { | ||
if (timeoutData == null) { | ||
return; | ||
} | ||
|
||
trackEvent('pbto', { | ||
A: timeoutData.bidder, | ||
bid: timeoutData.bidId, | ||
a: timeoutData.adUnitCode, | ||
cn: timeoutData.timeout, | ||
aud: timeoutData.auctionId, | ||
}); | ||
}, | ||
|
||
onBidWon(bidWinData) { | ||
if (bidWinData == null) { | ||
return; | ||
} | ||
|
||
trackEvent('pbwin', { | ||
A: bidWinData.bidder, | ||
w: bidWinData.width, | ||
h: bidWinData.height, | ||
bid: bidWinData.adId, | ||
C: bidWinData.mediaType === BANNER ? 0 : 1, | ||
np: bidWinData.cpm, | ||
a: bidWinData.adUnitCode, | ||
}); | ||
}, | ||
}; | ||
|
||
registerBidder(spec); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
Overview | ||
======== | ||
|
||
``` | ||
Module Name: AMX Adapter | ||
Module Type: Bidder Adapter | ||
Maintainer: prebid.support@amxrtb.com | ||
``` | ||
|
||
Description | ||
=========== | ||
|
||
This module connects web publishers to AMX RTB video and display demand. | ||
|
||
# Bid Parameters | ||
|
||
| Key | Required | Example | Description | | ||
| --- | -------- | ------- | ----------- | | ||
| `endpoint` | **yes** | `https://prebid.a-mo.net/a/c` | The url including https:// and any path | | ||
| `testMode` | no | `true` | this will activate test mode / 100% fill with sample ads | | ||
| `tagId` | no | `"cHJlYmlkLm9yZw"` | can be used for more specific targeting of inventory. Your account manager will provide this ID if needed | | ||
|
||
# Test Parameters | ||
|
||
``` | ||
var adUnits = [{ | ||
code: 'test-div', | ||
sizes: [[300, 250]], | ||
bids: [{ | ||
bidder: 'amx', | ||
params: { | ||
testMode: true, | ||
tagId: 'cHJlYmlkLm9yZw' | ||
}, | ||
}] | ||
}] | ||
``` |
Oops, something went wrong.