UNPKG

mk9-prebid

Version:

Header Bidding Management Library

301 lines (275 loc) 8.56 kB
import * as utils from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; /** * Version of the FeedAd bid adapter * @type {string} */ const VERSION = '1.0.2'; /** * @typedef {object} FeedAdApiBidRequest * @inner * * @property {number} ad_type * @property {string} client_token * @property {string} placement_id * @property {string} sdk_version * @property {boolean} app_hybrid * * @property {string} [app_bundle_id] * @property {string} [app_name] * @property {object} [custom_params] * @property {number} [connectivity] * @property {string} [device_adid] * @property {string} [device_platform] */ /** * @typedef {object} FeedAdApiBidResponse * @inner * * @property {string} ad - Ad HTML payload * @property {number} cpm - number / float * @property {string} creativeId - ID of creative for tracking * @property {string} currency - 3-letter ISO 4217 currency-code * @property {number} height - Height of creative returned in [].ad * @property {boolean} netRevenue - Is the CPM net (true) or gross (false)? * @property {string} requestId - bids[].bidId * @property {number} ttl - Time to live for this ad * @property {number} width - Width of creative returned in [].ad */ /** * @typedef {object} FeedAdApiTrackingParams * @inner * * @property app_hybrid {boolean} * @property client_token {string} * @property klass {'prebid_bidWon'|'prebid_bidTimeout'} * @property placement_id {string} * @property prebid_auction_id {string} * @property prebid_bid_id {string} * @property prebid_transaction_id {string} * @property referer {string} * @property sdk_version {string} * @property [app_bundle_id] {string} * @property [app_name] {string} * @property [device_adid] {string} * @property [device_platform] {1|2|3} 1 - Android | 2 - iOS | 3 - Windows */ /** * The IAB TCF 2.0 vendor ID for the FeedAd GmbH */ const TCF_VENDOR_ID = 781; /** * Bidder network identity code * @type {string} */ const BIDDER_CODE = 'feedad'; /** * The media types supported by FeedAd * @type {MediaType[]} */ const MEDIA_TYPES = [BANNER]; /** * Tag for logging * @type {string} */ const TAG = '[FeedAd]'; /** * Pattern for valid placement IDs * @type {RegExp} */ const PLACEMENT_ID_PATTERN = /^[a-z0-9][a-z0-9_-]+[a-z0-9]$/; const API_ENDPOINT = 'https://api.feedad.com'; const API_PATH_BID_REQUEST = '/1/prebid/web/bids'; const API_PATH_TRACK_REQUEST = '/1/prebid/web/events'; /** * Stores temporary auction metadata * @type {Object.<string, {referer: string, transactionId: string}>} */ const BID_METADATA = {}; /** * Checks if the bid is compatible with FeedAd. * * @param {BidRequest} bid - the bid to check * @return {boolean} true if the bid is valid */ function isBidRequestValid(bid) { const clientToken = utils.deepAccess(bid, 'params.clientToken'); if (!clientToken || !isValidClientToken(clientToken)) { utils.logWarn(TAG, "missing or invalid parameter 'clientToken'. found value:", clientToken); return false; } const placementId = utils.deepAccess(bid, 'params.placementId'); if (!placementId || !isValidPlacementId(placementId)) { utils.logWarn(TAG, "missing or invalid parameter 'placementId'. found value:", placementId); return false; } return true; } /** * Checks if a client token is valid * @param {string} clientToken - the client token * @return {boolean} true if the token is valid */ function isValidClientToken(clientToken) { return typeof clientToken === 'string' && clientToken.length > 0; } /** * Checks if the given placement id is of a correct format. * Valid IDs are words of lowercase letters from a to z and numbers from 0 to 9. * The words can be separated by hyphens or underscores. * Multiple separators must not follow each other. * The whole placement ID must not be larger than 256 characters. * * @param placementId - the placement id to verify * @returns if the placement ID is valid. */ function isValidPlacementId(placementId) { return typeof placementId === 'string' && placementId.length > 0 && placementId.length <= 256 && PLACEMENT_ID_PATTERN.test(placementId); } /** * Checks if the given media types contain unsupported settings * @param {MediaTypes} mediaTypes - the media types to check * @return {MediaTypes} the unsupported settings, empty when all types are supported */ function filterSupportedMediaTypes(mediaTypes) { return { banner: mediaTypes.banner, video: mediaTypes.video && mediaTypes.video.context === 'outstream' ? mediaTypes.video : undefined, native: undefined }; } /** * Checks if the given media types are empty * @param {MediaTypes} mediaTypes - the types to check * @return {boolean} true if the types are empty */ function isMediaTypesEmpty(mediaTypes) { return Object.keys(mediaTypes).every(type => mediaTypes[type] === undefined); } /** * Creates the bid request params the api expects from the prebid bid request * @param {BidRequest} request - the validated prebid bid request * @return {FeedAdApiBidRequest} */ function createApiBidRParams(request) { return { ad_type: 0, client_token: request.params.clientToken, placement_id: request.params.placementId, sdk_version: `prebid_${VERSION}`, app_hybrid: false, }; } /** * Builds the bid request to the FeedAd Server * @param {BidRequest[]} validBidRequests - all validated bid requests * @param {object} bidderRequest - meta information * @return {ServerRequest|ServerRequest[]} */ function buildRequests(validBidRequests, bidderRequest) { if (!bidderRequest) { return []; } let acceptableRequests = validBidRequests.filter(request => !isMediaTypesEmpty(filterSupportedMediaTypes(request.mediaTypes))); if (acceptableRequests.length === 0) { return []; } let data = Object.assign({}, bidderRequest, { bids: acceptableRequests.map(req => { req.params = createApiBidRParams(req); return req; }) }); data.bids.forEach(bid => BID_METADATA[bid.bidId] = { referer: data.refererInfo.referer, transactionId: bid.transactionId }); if (bidderRequest.gdprConsent) { data.consentIabTcf = bidderRequest.gdprConsent.consentString; data.gdprApplies = bidderRequest.gdprConsent.gdprApplies; } return { method: 'POST', url: `${API_ENDPOINT}${API_PATH_BID_REQUEST}`, data, options: { contentType: 'application/json' } }; } /** * Adapts the FeedAd server response to Prebid format * @param {ServerResponse} serverResponse - the FeedAd server response * @param {BidRequest} request - the initial bid request * @returns {Bid[]} the FeedAd bids */ function interpretResponse(serverResponse, request) { /** * @type FeedAdApiBidResponse[] */ return typeof serverResponse.body === 'string' ? JSON.parse(serverResponse.body) : serverResponse.body; } /** * Creates the parameters for the FeedAd tracking call * @param {object} data - prebid data * @param {'prebid_bidWon'|'prebid_bidTimeout'} klass - type of tracking call * @return {FeedAdApiTrackingParams|null} */ function createTrackingParams(data, klass) { const bidId = data.bidId || data.requestId; if (!BID_METADATA.hasOwnProperty(bidId)) { return null; } const {referer, transactionId} = BID_METADATA[bidId]; delete BID_METADATA[bidId]; return { app_hybrid: false, client_token: data.params[0].clientToken, placement_id: data.params[0].placementId, klass, prebid_auction_id: data.auctionId, prebid_bid_id: bidId, prebid_transaction_id: transactionId, referer, sdk_version: VERSION }; } /** * Creates a tracking handler for the given event type * @param klass - the event type * @return {Function} the tracking handler function */ function trackingHandlerFactory(klass) { return (data) => { if (!data) { return; } let params = createTrackingParams(data, klass); if (params) { ajax(`${API_ENDPOINT}${API_PATH_TRACK_REQUEST}`, null, JSON.stringify(params), { withCredentials: true, method: 'POST', contentType: 'application/json' }); } } } /** * @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, gvlid: TCF_VENDOR_ID, supportedMediaTypes: MEDIA_TYPES, isBidRequestValid, buildRequests, interpretResponse, onTimeout: trackingHandlerFactory('prebid_bidTimeout'), onBidWon: trackingHandlerFactory('prebid_bidWon') }; registerBidder(spec);