-
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.
PBJS-ORTB conversion library (#8738)
* Automatically resolve libraries and their dependencies * Move AnalyticsAdapter under libraries * PBJS-ORTB conversion library * Update ortbConverter README * Improvements on request generation * refactor openxOrtbBidAdapter * Update README * Filter out imps that have no id * Fix nativeMapper logic * Prebid core: simplify native ORTB logic * Remove nativeMapper; do not mix ORTB request and response * fix lint * set bidRequest.nativeParams after ortb -> legacy conversion * Fix native trackers for native ortb responses (including Prebid Server) * Clean up native processors; add s2sConfig.ortbNative * Make ext.prebid.floors.enabled respect first party data * do not set placement for oustream video * Improvements * convert improvedigitalBidAdapter * Update openxOrtbBidAdapter * Improve video validation logic * Move skip validation back to improvedigital * Update README * Override (not merge) context.nativeRequest * Update improvedigital adapter * Address PR feedback on improvedigital * Fix tmax to s2sConfig.timeout for PBS adapter * PBS adapter: set source.ext.schain with the most commonly used schain * Remove dead code * Use auctionId for source.tid * Update package-lock * Set source.tid to auctionId * Update README * Update README * Update README
- Loading branch information
Showing
55 changed files
with
3,970 additions
and
1,864 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,135 @@ | ||
import {compose} from './lib/composer.js'; | ||
import {logError, memoize} from '../../src/utils.js'; | ||
import {DEFAULT_PROCESSORS} from './processors/default.js'; | ||
import {BID_RESPONSE, DEFAULT, getProcessors, IMP, REQUEST, RESPONSE} from '../../src/pbjsORTB.js'; | ||
import {mergeProcessors} from './lib/mergeProcessors.js'; | ||
|
||
export function ortbConverter({ | ||
context: defaultContext = {}, | ||
processors = defaultProcessors, | ||
overrides = {}, | ||
imp, | ||
request, | ||
bidResponse, | ||
response, | ||
} = {}) { | ||
const REQ_CTX = new WeakMap(); | ||
|
||
function builder(slot, wrapperFn, builderFn, errorHandler) { | ||
let build; | ||
return function () { | ||
if (build == null) { | ||
build = (function () { | ||
let delegate = builderFn.bind(this, compose(processors()[slot] || {}, overrides[slot] || {})); | ||
if (wrapperFn) { | ||
delegate = wrapperFn.bind(this, delegate); | ||
} | ||
return function () { | ||
try { | ||
return delegate.apply(this, arguments); | ||
} catch (e) { | ||
errorHandler.call(this, e, ...arguments); | ||
} | ||
} | ||
})(); | ||
} | ||
return build.apply(this, arguments); | ||
} | ||
} | ||
|
||
const buildImp = builder(IMP, imp, | ||
function (process, bidRequest, context) { | ||
const imp = {}; | ||
process(imp, bidRequest, context); | ||
return imp; | ||
}, | ||
function (error, bidRequest, context) { | ||
logError('Error while converting bidRequest to ORTB imp; request skipped.', {error, bidRequest, context}); | ||
} | ||
); | ||
|
||
const buildRequest = builder(REQUEST, request, | ||
function (process, imps, bidderRequest, context) { | ||
const ortbRequest = {imp: imps}; | ||
process(ortbRequest, bidderRequest, context); | ||
return ortbRequest; | ||
}, | ||
function (error, imps, bidderRequest, context) { | ||
logError('Error while converting to ORTB request', {error, imps, bidderRequest, context}); | ||
throw error; | ||
} | ||
); | ||
|
||
const buildBidResponse = builder(BID_RESPONSE, bidResponse, | ||
function (process, bid, context) { | ||
const bidResponse = {}; | ||
process(bidResponse, bid, context); | ||
return bidResponse; | ||
}, | ||
function (error, bid, context) { | ||
logError('Error while converting ORTB seatbid.bid to bidResponse; bid skipped.', {error, bid, context}); | ||
} | ||
); | ||
|
||
const buildResponse = builder(RESPONSE, response, | ||
function (process, bidResponses, ortbResponse, context) { | ||
const response = {bids: bidResponses}; | ||
process(response, ortbResponse, context); | ||
return response; | ||
}, | ||
function (error, bidResponses, ortbResponse, context) { | ||
logError('Error while converting from ORTB response', {error, bidResponses, ortbResponse, context}); | ||
throw error; | ||
} | ||
); | ||
|
||
return { | ||
toORTB({bidderRequest, bidRequests, context = {}}) { | ||
bidRequests = bidRequests || bidderRequest.bids; | ||
const ctx = { | ||
req: Object.assign({bidRequests}, defaultContext, context), | ||
imp: {} | ||
} | ||
const imps = bidRequests.map(bidRequest => { | ||
const impContext = Object.assign({bidderRequest, reqContext: ctx.req}, defaultContext, context); | ||
const result = buildImp(bidRequest, impContext); | ||
if (result != null) { | ||
if (result.hasOwnProperty('id')) { | ||
impContext.bidRequest = bidRequest; | ||
ctx.imp[result.id] = impContext; | ||
return result; | ||
} | ||
logError('Converted ORTB imp does not specify an id, ignoring bid request', bidRequest, result); | ||
} | ||
}).filter(Boolean); | ||
|
||
const request = buildRequest(imps, bidderRequest, ctx.req); | ||
ctx.req.bidderRequest = bidderRequest; | ||
if (request != null) { | ||
REQ_CTX.set(request, ctx); | ||
} | ||
return request; | ||
}, | ||
fromORTB({request, response}) { | ||
const ctx = REQ_CTX.get(request); | ||
if (ctx == null) { | ||
throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') | ||
} | ||
function augmentContext(ctx, extraParams = {}) { | ||
return Object.assign({ortbRequest: request}, extraParams, ctx); | ||
} | ||
const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); | ||
const bidResponses = (response.seatbid || []).flatMap(seatbid => | ||
(seatbid.bid || []).map((bid) => { | ||
if (impsById.hasOwnProperty(bid.impid) && ctx.imp.hasOwnProperty(bid.impid)) { | ||
return buildBidResponse(bid, augmentContext(ctx.imp[bid.impid], {imp: impsById[bid.impid], seatbid, ortbResponse: response})); | ||
} | ||
logError('ORTB response seatbid[].bid[].impid does not match any imp in request; ignoring bid', bid); | ||
}) | ||
).filter(Boolean); | ||
return buildResponse(bidResponses, response, augmentContext(ctx.req)); | ||
} | ||
} | ||
} | ||
|
||
export const defaultProcessors = memoize(() => mergeProcessors(DEFAULT_PROCESSORS, getProcessors(DEFAULT))); |
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,43 @@ | ||
const SORTED = new WeakMap(); | ||
|
||
/** | ||
* @typedef {Object} Component | ||
* A component function, that can be composed with other compatible functions into one. | ||
* Compatible functions take the same arguments; return values are ignored. | ||
* | ||
* @property {function} fn the component function; | ||
* @property {number} priority determines the order in which this function will run when composed with others. | ||
*/ | ||
|
||
/** | ||
* | ||
* @param {Object[string, Component]} components to compose | ||
* @param {Object[string, function|boolean]} overrides a map from component name, to a function that should override that component. | ||
* Override functions are replacements, except that they get the original function they are overriding as their first argument. If the override | ||
* is `false`, the component is disabled. | ||
* | ||
* @return a function that will run all components in order of priority, with functions from `overrides` taking | ||
* precedence over components that match names | ||
*/ | ||
export function compose(components, overrides = {}) { | ||
if (!SORTED.has(components)) { | ||
const sorted = Object.entries(components); | ||
sorted.sort((a, b) => { | ||
a = a[1].priority || 0; | ||
b = b[1].priority || 0; | ||
return a === b ? 0 : a > b ? -1 : 1 | ||
}); | ||
SORTED.set(components, sorted.map(([name, cmp]) => [name, cmp.fn])) | ||
} | ||
const fns = SORTED.get(components) | ||
.filter(([name]) => !overrides.hasOwnProperty(name) || overrides[name]) | ||
.map(function ([name, fn]) { | ||
return overrides.hasOwnProperty(name) ? overrides[name].bind(this, fn) : fn; | ||
}); | ||
return function () { | ||
const args = Array.from(arguments); | ||
fns.forEach(fn => { | ||
fn.apply(this, args); | ||
}) | ||
} | ||
} |
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,9 @@ | ||
import {PROCESSOR_TYPES} from '../../../src/pbjsORTB.js'; | ||
|
||
export function mergeProcessors(...processors) { | ||
const left = processors.shift(); | ||
const right = processors.length > 1 ? mergeProcessors(...processors) : processors[0]; | ||
return Object.fromEntries( | ||
PROCESSOR_TYPES.map(type => [type, Object.assign({}, left[type], right[type])]) | ||
) | ||
} |
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,14 @@ | ||
import {parseSizesInput} from '../../../src/utils.js'; | ||
|
||
export function sizesToFormat(sizes) { | ||
sizes = parseSizesInput(sizes); | ||
|
||
// get sizes in form [{ w: <int>, h: <int> }, ...] | ||
return sizes.map(size => { | ||
const [width, height] = size.split('x'); | ||
return { | ||
w: parseInt(width, 10), | ||
h: parseInt(height, 10) | ||
}; | ||
}); | ||
} |
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,40 @@ | ||
import {createTrackPixelHtml, deepAccess, inIframe, mergeDeep} from '../../../src/utils.js'; | ||
import {BANNER} from '../../../src/mediaTypes.js'; | ||
import {sizesToFormat} from '../lib/sizes.js'; | ||
|
||
/** | ||
* fill in a request `imp` with banner parameters from `bidRequest`. | ||
*/ | ||
export function fillBannerImp(imp, bidRequest, context) { | ||
if (context.mediaType && context.mediaType !== BANNER) return; | ||
|
||
const bannerParams = deepAccess(bidRequest, 'mediaTypes.banner'); | ||
if (bannerParams) { | ||
const banner = { | ||
topframe: inIframe() === true ? 0 : 1 | ||
}; | ||
if (bannerParams.sizes) { | ||
banner.format = sizesToFormat(bannerParams.sizes); | ||
} | ||
if (bannerParams.hasOwnProperty('pos')) { | ||
banner.pos = bannerParams.pos; | ||
} | ||
|
||
imp.banner = mergeDeep(banner, imp.banner); | ||
} | ||
} | ||
|
||
export function bannerResponseProcessor({createPixel = (url) => createTrackPixelHtml(decodeURIComponent(url))} = {}) { | ||
return function fillBannerResponse(bidResponse, bid) { | ||
if (bidResponse.mediaType === BANNER) { | ||
if (bid.adm && bid.nurl) { | ||
bidResponse.ad = bid.adm; | ||
bidResponse.ad += createPixel(bid.nurl); | ||
} else if (bid.adm) { | ||
bidResponse.ad = bid.adm; | ||
} else if (bid.nurl) { | ||
bidResponse.adUrl = bid.nurl; | ||
} | ||
} | ||
}; | ||
} |
Oops, something went wrong.