-
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.
- Loading branch information
Showing
3 changed files
with
706 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
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]; | ||
} | ||
|
||
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 = {}; | ||
for (const bid of bids) { | ||
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 av = bid.mediaType === VIDEO || VIDEO in bid.mediaTypes; | ||
const tid = deepAccess(bid, 'params.tagId') | ||
|
||
const params = { | ||
av, | ||
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 decorateVideoADM(bid) { | ||
const doc = new DOMParser().parseFromString(bid.adm, 'text/xml'); | ||
if (doc.querySelector('parsererror') != null) { | ||
return null; | ||
} | ||
|
||
const root = doc.querySelector('InLine,Wrapper') | ||
if (root == null) { | ||
return null; | ||
} | ||
|
||
const pixels = [bid.nurl].concat(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)) + doc.documentElement.outerHTML; | ||
} | ||
|
||
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: 9, | ||
d: '', | ||
m: createBidMap(bidRequests), | ||
}; | ||
|
||
return { | ||
data: payload, | ||
method: 'POST', | ||
url: deepAccess(bidRequests[0], 'params.endpoint', DEFAULT_ENDPOINT), | ||
withCredentials: true, | ||
}; | ||
}, | ||
|
||
getUserSyncs(syncOptions, serverResponses) { | ||
return (serverResponses || []) | ||
.flatMap(({ body: response }) => | ||
response != null && response.p != null ? (response.p.hreq || []) : []) | ||
.map((syncPixel) => | ||
({ | ||
type: syncPixel.indexOf('__st=iframe') !== -1 ? 'iframe' : 'image', | ||
url: syncPixel | ||
}) | ||
).filter(({ | ||
type | ||
}) => syncOptions.iframeEnabled || type === 'image') | ||
}, | ||
|
||
interpretResponse(serverResponse, request) { | ||
// validate the body/response | ||
const response = serverResponse.body; | ||
if (response == null || typeof response === 'string') { | ||
return []; | ||
} | ||
|
||
return Object.keys(response.r).flatMap((bidID) => { | ||
const biddata = response.r[bidID]; | ||
return biddata.flatMap((siteBid) => | ||
siteBid.b.map((bid) => { | ||
const mediaType = getMediaType(bid); | ||
const 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 | `"eh3hffb"` | 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, | ||
endpoint: 'https://prebid.a-mo.net/a/c', | ||
}, | ||
}] | ||
}] | ||
``` |
Oops, something went wrong.