From c34d66ccfdd2ddc4d29359f21e6df86dea33c7bc Mon Sep 17 00:00:00 2001 From: "hugh.qu" Date: Thu, 31 Oct 2024 16:25:50 +0800 Subject: [PATCH 1/8] add brainx adpater --- modules/brainxBidAdapter.js | 778 +++++++++++++++++++++ modules/brainxBidAdapter.md | 55 ++ test/spec/modules/brainxBidAdapter_spec.js | 132 ++++ 3 files changed, 965 insertions(+) create mode 100644 modules/brainxBidAdapter.js create mode 100644 modules/brainxBidAdapter.md create mode 100644 test/spec/modules/brainxBidAdapter_spec.js diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js new file mode 100644 index 00000000000..722c851c69a --- /dev/null +++ b/modules/brainxBidAdapter.js @@ -0,0 +1,778 @@ +import { + deepAccess, + isArray, + isPlainObject, + isStr, + logError, + logWarn, + deepSetValue, + getDNT, + isFn, + generateUUID +} from '../src/utils.js'; +// import { config } from 'src/config'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; +// import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { Renderer } from '../src/Renderer.js'; +import { OUTSTREAM } from '../src/video.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { config } from '../src/config.js'; + +const BIDDER_CODE = 'brainx'; +// const ENDPOINT_URL = 'http://adx-engine-gray.tec-do.cn/bid'; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_LANGUAGE = 'en'; +const NET_REVENUE = true; +// const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +const BANNER_DEFAULTS = { + SIZE: [300, 250] +} + +const VIDEO_DEFAULTS = { + PROTOCOLS: [2, 3, 5, 6], + MIMES: ['video/mp4'], + PLAYBACK_METHODS: [1, 2, 3, 4], + DELIVERY: [1], + API: [1, 2, 5], + SIZE: [640, 480] +} +const NATIVE_DEFAULTS = { + IMAGE_TYPE: { + ICON: 1, + MAIN: 3, + }, + ASSET_ID: { + TITLE: 1, + IMAGE: 2, + ICON: 3, + BODY: 4, + SPONSORED: 5, + CTA: 6 + }, + DATA_ASSET_TYPE: { + SPONSORED: 1, + DESC: 2, + CTA_TEXT: 12, + }, + LENGTH: { + TITLE: 90, + BODY: 140, + SPONSORED: 25, + CTA: 20 + } +} + +export const spec = { + code: BIDDER_CODE, + // gvlid: 0000000000, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + // aliases: [{ code: 'myAlias', gvlid: 99999999999 }], + /** + * 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: function (bid) { + logWarn('检查 bid request'); + if (!(hasBannerMediaType(bid) || hasVideoMediaType(bid))) { + logWarn('Invalid bid request - missing required mediaTypes'); + return false; + } + if (!(bid && bid.params)) { + logWarn('Invalid bid request - missing required bid data'); + return false; + } + + if (!(bid.params.pubId)) { + logWarn('Invalid bid request - missing required field pubId'); + return false; + } + return true; + }, + /** + * 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: function (validBidRequests, bidderRequest) { + // if (bidderRequest && bidderRequest.gdprConsent) { + // adapterRequest.gdpr_consent = { + // consent_string: bidderRequest.gdprConsent.consentString, + // // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true + // // consent_required: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true + // consent_required: false + // } + // adapterRequest.gdpr_consent = 'undefined' + // } + + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests) + // convertOrtbRequestToProprietaryNative(validBidRequests); + + return validBidRequests.map(validBidRequest => (buildOpenRtbBidRequest(validBidRequest, bidderRequest))) + }, + /** + * 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: function (serverResponse, bidRequest) { + + let bidResponses = []; + + let serverBody; + console.log('serverBody-=======', serverResponse); + if ((serverBody = serverResponse.body) && serverBody.seatbid && isArray(serverBody.seatbid)) { + serverBody.seatbid.forEach((seatbidder) => { + if (seatbidder.bid && isArray(seatbidder.bid)) { + bidResponses = seatbidder.bid.map((bid) => buildBidResponse(bid, bidRequest.originalBidRequest, serverBody)); + console.log('bidResponses-=======', bidResponses); + } + }); + } + + return bidResponses; + }, + // getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { + // const syncs = [] + // console.log('gdprConsent-=======', gdprConsent); + // console.log('syncOptions-=======', syncOptions) + // console.log('serverResponses-=======', serverResponses) + // var gdpr_params; + // // if (typeof gdprConsent.gdprApplies === 'boolean') { + // // gdpr_params = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + // // } else { + // // gdpr_params = `gdpr_consent=${gdprConsent.consentString}`; + // // } + + // // if (syncOptions.iframeEnabled) { + // // syncs.push({ + // // type: 'iframe', + // // url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html?' + gdpr_params + // // }); + // // } + // if (syncOptions.pixelEnabled && serverResponses.length > 0) { + // syncs.push({ + // type: 'image', + // url: serverResponses[0].body.userSync.url + gdpr_params + // }); + // } + // return syncs; + // }, + onTimeout: function (data) { + // Bidder specifc code + }, + onBidWon: function (bid) { + // Bidder specific code + }, + onSetTargeting: function (bid) { + // Bidder specific code + }, + // onBidderError: function ({ error, bidderRequest }) { + // // Bidder specific code + // }, + onAdRenderSucceeded: function (bid) { + // Bidder specific code + } +} + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} +function hasVideoMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +function buildOpenRtbBidRequest(bidRequest, bidderRequest) { + // build OpenRTB request body + const payload = { + id: bidderRequest.bidderRequestId, + tmax: bidderRequest.timeout, + test: config.getConfig('debug') ? 1 : 0, + imp: createImp(bidRequest), + device: getDevice(), + at: 1, + bcat: getBcat(bidRequest), + cur: [DEFAULT_CURRENCY], + regs: { + coppa: config.getConfig('coppa') ? 1 : 0, + ext: {} + }, + user: { + buyeruid: generateUUID() + } + } + payload.device.ip = navigator.ip || '202.100.48.46'; + + fulfillInventoryInfo(payload, bidRequest, bidderRequest); + + const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + deepSetValue(payload, 'regs.ext.gdpr', 1); + deepSetValue(payload, 'user.ext.consent', gdprConsent.consentString); + } + + const uspConsent = deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + deepSetValue(payload, 'regs.ext.us_privacy', uspConsent); + } + + const eids = deepAccess(bidRequest, 'userIdAsEids', []); + if (eids.length > 0) { + deepSetValue(payload, 'user.eids', eids); + } + + return { + method: 'POST', + url: `${String(deepAccess(bidRequest, 'params.endpoint'))}?token=${String(deepAccess(bidRequest, 'params.pubId'))}`, + data: payload, + }; + + // return { + // method: 'POST', + // url: ENDPOINT + String(deepAccess(bidRequest, 'params.publisherId')) + + // '?ep=' + String(deepAccess(bidRequest, 'params.endpointId')), + // data: JSON.stringify(payload), + // options: { + // contentType: 'application/json', + // customHeaders: { + // 'x-openrtb-version': 2.5 + // } + // }, + // // set original bid request, so we can get it from interpretResponse + // originalBidRequest: bidRequest + // } +} + +function fulfillInventoryInfo(payload, bidRequest, bidderRequest) { + let info = deepAccess(bidRequest, 'params. '); + // 1.If the inventory info for site specified, use the site object provided in params. + let key = 'site'; + if (!isPlainObject(info)) { + info = deepAccess(bidRequest, 'params.app'); + if (isPlainObject(info)) { + // 2.If the inventory info for app specified, use the app object provided in params. + key = 'app'; + } else { + // 3.Otherwise, we use site by default. + info = {}; + } + } + // Fulfill key parameters. + info.id = String(deepAccess(bidRequest, 'params.publisherId')); + info.domain = info.domain || bidderRequest?.refererInfo?.domain || window.location.host; + if (key === 'site') { + info.ref = info.ref || bidderRequest?.refererInfo?.ref || ''; + info.page = info.page || bidderRequest?.refererInfo?.page; + } + + payload[key] = info; +} + +function getLanguage() { + const lang = (navigator.languages && navigator.languages[0]) || + navigator.language || navigator.userLanguage; + return lang ? lang.split('-')[0] : DEFAULT_LANGUAGE; +} + +function getDevice() { + const device = config.getConfig('device') || {}; + + device.w = device.w || window.screen.width; + device.h = device.h || window.screen.height; + device.ua = device.ua || navigator.userAgent; + device.language = device.language || getLanguage(); + device.dnt = typeof device.dnt === 'number' + ? device.dnt : (getDNT() ? 1 : 0); + // data.id = request.bidId; + device.ip = navigator.ip || '202.100.48.46'; + device.geo = device.geo || {}; + device.geo.country = device.geo.country || 'HKG'; + device.os = device.os || 'Android'; + // device = { + // ...device, + // geo: { + // ...device.geo, + // country: 'HKG' + // }, + // os: 'Android' + // } + return device; +} + +function getBcat(bidRequest) { + let bcat = []; + + const pBcat = deepAccess(bidRequest, 'params.bcat'); + if (pBcat) { + bcat = bcat.concat(pBcat); + } + + return bcat; +} + +function buildBidResponse(bid, bidRequest, responseBody) { + let mediaType = BANNER; + let nativeResponse; + + if (/VAST\s+version/.test(bid.adm)) { + mediaType = VIDEO; + } else { + let markup; + try { + markup = JSON.parse(bid.adm); + } catch (e) { + markup = null; + } + + // OpenRtb Markup Response Object + // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.1 + if (markup && isPlainObject(markup.native)) { + mediaType = NATIVE; + nativeResponse = markup.native; + } + } + + const currency = responseBody.cur || DEFAULT_CURRENCY; + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + + const categories = deepAccess(bid, 'cat', []); + + const bidResponse = { + requestId: bid.impid, + cpm: cpm, + currency: currency, + mediaType: mediaType, + ttl: 300, + creativeId: bid.crid || bid.id, + netRevenue: NET_REVENUE, + nurl: bid.nurl, + lurl: bid.lurl, + meta: { + mediaType: mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + } + }; + + if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { + bidResponse.meta.advertiserDomains = bid.adomain; + bidResponse.meta.clickUrl = bid.adomain[0]; + } + + switch (mediaType) { + case VIDEO: { + const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', VIDEO_DEFAULTS.SIZE); + const size = canonicalizeSizesArray(playerSize)[0]; + + bidResponse.vastXml = bid.adm; + + bidResponse.width = bid.w || size[0]; + bidResponse.height = bid.h || size[1]; + + const context = deepAccess(bidRequest, 'mediaTypes.video.context'); + + // if outstream video, add a default render for it. + if (context === OUTSTREAM) { + // fill adResponse, will be used in ANOutstreamVideo.renderAd + bidResponse.adResponse = { + content: bidResponse.vastXml, + width: bidResponse.width, + height: bidResponse.height, + player_width: size[0], + player_height: size[1], + }; + bidResponse.renderer = createRenderer(bidRequest); + } + break; + } + case NATIVE: { + bidResponse.native = interpretNativeAd(nativeResponse, currency, cpm); + break; + } + default: { + bidResponse.ad = bid.adm; + + bidResponse.width = bid.w; + bidResponse.height = bid.h; + } + } + return bidResponse; +} + +function createImp(bidRequest) { + const imp = []; + + const impItem = { + id: bidRequest.bidId, + tagid: String(deepAccess(bidRequest, 'params.placementId')), + }; + + let mediaType, size; + let bannerReq, videoReq, nativeReq; + + if ((bannerReq = deepAccess(bidRequest, 'mediaTypes.banner'))) { + size = canonicalizeSizesArray(bannerReq.sizes || BANNER_DEFAULTS.SIZE)[0]; + + impItem.banner = { + w: size[0], + h: size[1], + pos: 0, + }; + + mediaType = BANNER; + } else if ((videoReq = deepAccess(bidRequest, 'mediaTypes.video'))) { + size = canonicalizeSizesArray(videoReq.playerSize || VIDEO_DEFAULTS.SIZE)[0]; + + impItem.video = { + w: size[0], + h: size[1], + pos: 0, + mimes: videoReq.mimes || VIDEO_DEFAULTS.MIMES, + protocols: videoReq.protocols || VIDEO_DEFAULTS.PROTOCOLS, + startdelay: typeof videoReq.startdelay === 'number' ? videoReq.startdelay : 0, + skip: typeof videoReq.skip === 'number' ? videoReq.skip : 0, + playbackmethod: videoReq.playbackmethod || VIDEO_DEFAULTS.PLAYBACK_METHODS, + delivery: videoReq.delivery || VIDEO_DEFAULTS.DELIVERY, + api: videoReq.api || VIDEO_DEFAULTS.API, + }; + + mediaType = VIDEO; + } else if ((nativeReq = deepAccess(bidRequest, 'mediaTypes.native'))) { + const params = bidRequest.nativeParams || nativeReq; + + const request = { + native: { + ver: '1.1', + assets: createNativeAssets(params), + } + }; + + impItem.native = { + ver: '1.1', + request: JSON.stringify(request), + }; + + mediaType = NATIVE; + } + + const floorDetail = getBidFloor(bidRequest, { + mediaType: mediaType || '*', + size: size || '*' + }); + + impItem.bidfloor = floorDetail.floor; + impItem.bidfloorcur = floorDetail.currency; + + if (mediaType) { + imp.push(impItem); + } + + return imp; +} + +/** + * Get bid floor price + * + * @param {BidRequest} bid + * @param {Params} params + * @returns {Floor} floor price + */ +function getBidFloor(bid, { mediaType = '*', size = '*' }) { + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, + size + }); + + if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor)) { + return { + currency: floorInfo.currency || DEFAULT_CURRENCY, + floor: floorInfo.floor + }; + } + } + + return { + currency: DEFAULT_CURRENCY, + floor: 0.0 + } +} + +function createNativeAssets(params) { + const assets = []; + + if (params.title) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.TITLE, + required: params.title.required ? 1 : 0, + title: { + len: params.title.len || NATIVE_DEFAULTS.LENGTH.TITLE + } + }) + } + + if (params.image) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.IMAGE, + required: params.image.required ? 1 : 0, + img: mapNativeImage(params.image, NATIVE_DEFAULTS.IMAGE_TYPE.MAIN) + }) + } + + if (params.icon) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.ICON, + required: params.icon.required ? 1 : 0, + img: mapNativeImage(params.icon, NATIVE_DEFAULTS.IMAGE_TYPE.ICON) + }) + } + + if (params.sponsoredBy) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.SPONSORED, + required: params.sponsoredBy.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.SPONSORED, + len: params.sponsoredBy.len | NATIVE_DEFAULTS.LENGTH.SPONSORED + } + }) + } + + if (params.body) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.BODY, + required: params.body.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.DESC, + len: params.body.len || NATIVE_DEFAULTS.LENGTH.BODY + } + }) + } + + if (params.cta) { + assets.push({ + id: NATIVE_DEFAULTS.ASSET_ID.CTA, + required: params.cta.required ? 1 : 0, + data: { + type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.CTA_TEXT, + len: params.cta.len || NATIVE_DEFAULTS.LENGTH.CTA + } + }) + } + + return assets; +} + +function canonicalizeSizesArray(sizes) { + if (sizes.length === 2 && !isArray(sizes[0])) { + return [sizes]; + } + return sizes; +} + +/** + * Create native image object + * + * @param {Object} image + * @param {Number} type + * @returns {NativeImage} + */ +function mapNativeImage(image, type) { + const img = { type: type }; + + if (image.aspect_ratios) { + const ratio = image.aspect_ratios[0]; + const minWidth = ratio.min_width || 100; + + img.wmin = minWidth; + img.hmin = (minWidth / ratio.ratio_width * ratio.ratio_height); + } + + if (image.sizes) { + const size = canonicalizeSizesArray(image.sizes)[0]; + + img.w = size[0]; + img.h = size[1]; + } + + return img; +} + +function interpretNativeAd(nativeResponse, currency, cpm) { + const native = {}; + + // OpenRtb Link Object + // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.7 + const clickUrl = deepAccess(nativeResponse, 'link.url'); + if (clickUrl && isStr(clickUrl)) { + native.clickUrl = decodeURIComponent(clickUrl); + } + + const clickTrackers = deepAccess(nativeResponse, 'link.clicktrackers'); + if (clickTrackers && isArray(clickTrackers)) { + native.clickTrackers = clickTrackers + .filter(Boolean) + .map( + url => decodeURIComponent(url) + .replace(/\$\{AUCTION_PRICE\}/g, cpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, currency) + ); + } + + if (nativeResponse.imptrackers && isArray(nativeResponse.imptrackers)) { + native.impressionTrackers = nativeResponse.imptrackers + .filter(Boolean) + .map( + url => decodeURIComponent(url) + .replace(/\$\{AUCTION_PRICE\}/g, cpm) + .replace(/\$\{AUCTION_CURRENCY\}/g, currency) + ); + } + + if (nativeResponse.jstracker && isStr(nativeResponse.jstracker)) { + native.javascriptTrackers = [nativeResponse.jstracker]; + } + + let assets; + if ((assets = nativeResponse.assets) && isArray(assets)) { + assets.forEach((asset) => { + switch (asset.id) { + case NATIVE_DEFAULTS.ASSET_ID.TITLE: { + const title = deepAccess(asset, 'title.text'); + if (title) { + native.title = title; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.IMAGE: { + if (asset.img) { + native.image = { + url: decodeURIComponent(asset.img.url), + width: asset.img.w, + height: asset.img.h + } + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.ICON: { + if (asset.img) { + native.icon = { + url: decodeURIComponent(asset.img.url), + width: asset.img.w, + height: asset.img.h + } + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.BODY: { + const body = deepAccess(asset, 'data.value'); + if (body) { + native.body = body; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.SPONSORED: { + const sponsoredBy = deepAccess(asset, 'data.value'); + if (sponsoredBy) { + native.sponsoredBy = sponsoredBy; + } + break; + } + case NATIVE_DEFAULTS.ASSET_ID.CTA: { + const cta = deepAccess(asset, 'data.value'); + if (cta) { + native.cta = cta; + } + break; + } + } + }); + } + + return native; +} + +function createRenderer(bidRequest) { + const globalRenderer = deepAccess(bidRequest, 'renderer'); + const currentRenderer = deepAccess(bidRequest, 'mediaTypes.video.renderer'); + + let url; + let config = {}; + let render = function (bid) { + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [bid.width, bid.height], + targetId: bid.adUnitCode, + adResponse: bid.adResponse, + }); + }); + }; + + if (currentRenderer) { + url = currentRenderer.url; + config = currentRenderer.options; + render = currentRenderer.render; + } else if (globalRenderer) { + url = globalRenderer.url; + config = globalRenderer.options; + render = globalRenderer.render; + } + + const renderer = Renderer.install({ + id: bidRequest.bidId, + url: url, + loaded: false, + config: config, + adUnitCode: bidRequest.adUnitCode + }); + + try { + renderer.setRender(render); + } catch (e) { + logError(BIDDER_CODE, 'Error calling setRender on renderer', e); + } + return renderer; +} + +registerBidder(spec); + +/** + * 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. + */ + +/** + * Register bidder specific code, which will execute if bidder timed out after an auction + * @param {data} Containing timeout specific data + */ + +/** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} The bid that won the auction + */ + +/** + * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder + * @param {Bid} The bid of which the targeting has been set + */ + +/** + * Register bidder specific code, which will execute if the bidder responded with an error + * @param {error, bidderRequest} An object with the XMLHttpRequest error and the bid request object + */ + +/** + * Register bidder specific code, which will execute if the ad + * has been rendered successfully + * @param {bid} bid request object + */ + +// } diff --git a/modules/brainxBidAdapter.md b/modules/brainxBidAdapter.md new file mode 100644 index 00000000000..e63cf9037f0 --- /dev/null +++ b/modules/brainxBidAdapter.md @@ -0,0 +1,55 @@ +# brianx Bidder Adapter + +## Overview + +``` +Module Name: brianx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: hugh.qu@tec-do.com +``` + +## Description + +Module that connects to brianx's demand sources + +## Bid Parameters + +| Name | Scope | Type | Description | Example | +| ------- | -------- | ------ | --------------------------------------- | ---------------- | +| `pubId` | required | String | The Pub Id provided by Brainx Ads. | `F7B53DBC-85C1-4685-9A06-9CF4B6261FA3` | +| `endpoint` | required | String | The endpoint provided by Brainx Url. | `http://adx-engine-gray.tec-do.cn/bid` | + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [320, 250], + [320, 480] + ] + } + }, + bids: [{ + bidder: 'brianx', + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + } + }] +}]; +``` + +* For video ads, enable prebid cache. + +```javascript +pbjs.setConfig({ + ortb2: { + ortbVersion: '2.5' + }, + debug: true // or false +}); +``` \ No newline at end of file diff --git a/test/spec/modules/brainxBidAdapter_spec.js b/test/spec/modules/brainxBidAdapter_spec.js new file mode 100644 index 00000000000..1d01e2cc642 --- /dev/null +++ b/test/spec/modules/brainxBidAdapter_spec.js @@ -0,0 +1,132 @@ +// import or require modules necessary for the test, e.g.: +import { expect } from 'chai'; // may prefer 'assert' in place of 'expect' +import { spec } from 'modules/brainxBidAdapter.js'; +import utils, { deepClone } from '../../../src/utils'; +// import adapter from 'src/adapters/'; +import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes.js'; + +describe('Brain-X Aapater', function () { + describe('isBidRequestValid', function () { + it('undefined bid should return false', function () { + expect(spec.isBidRequestValid()).to.be.false; + }); + + it('null bid should return false', function () { + expect(spec.isBidRequestValid(null)).to.be.false; + }); + + it('bid.params should be set', function () { + expect(spec.isBidRequestValid({})).to.be.false; + }); + + it('bid.params.pubId should be set', function () { + expect(spec.isBidRequestValid({ + params: { pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', endpoint: 'http://adx-engine-gray.tec-do.cn/bid' } + })).to.be.false; + }); + }) + + // describe('isBidRequestValid', function () { + // it('Test the banner request processing function', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the video request processing function', function () { + // const request = spec.buildRequests(videoRequest, videoRequest[0]); + // expect(request).to.not.be.empty; + // const payload = request.data; + // expect(payload).to.not.be.empty; + // }); + // it('Test the param', function () { + // const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + // const payload = JSON.parse(request.data); + // expect(payload.imp[0].tagid).to.eql(videoRequest[0].params.tagid); + // expect(payload.imp[0].bidfloor).to.eql(videoRequest[0].params.bidfloor); + // }); + // }) + + describe('interpretResponse', function () { + it('Test banner interpretResponse', function () { + const serverResponse = { + body: { + 'bidid': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'cur': 'USD', + 'id': '28f8f1f525372a', + 'seatbid': [ + { + 'bid': [ + { + 'adid': '76797', + 'adm': "
\n \n
\n
\n \n \n
\n
\n\n", + 'adomain': [ + 'taobao.com' + ], + 'bundle': 'com.taobao', + 'burl': 'https://adx-event-server.bidtrail.top/billing?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEC-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=P-1b7JJWs-uUQ68A37V4xDLplU0&auction_price=${AUCTION_PRICE}', + 'cat': [ + 'IAB18-5' + ], + 'cid': '428', + 'crid': 'D06g1RVGMEnC+9Le4SZMJw==', + 'h': 480, + 'id': 'a82042c055b04e539ec6876112c10ced1729663902983', + 'impid': '3c1dd9e1700358', + 'iurl': 'https://creative.bidtrail.top/png/2/20f24c10f21e/091b422e3014033e57acffcf2a5c71dbb17383ec15ac9421', + 'lurl': 'https://notice-sg.bidtrail.top/loss?bid_id=a82042c055b04e539ec6876112c10ced1729663902983&sign=e384268bc03c8fbc&campaign_id=428&ad_group_id=9761&ad_id=76797&creative_id=6526&affiliate_id=297&loss_code=${AUCTION_LOSS}', + 'nurl': 'https://adx-event-server.bidtrail.top/winnotice?s=Eid0ZWMxNDk4NDQzODk3MjcwOTU1MzAwNzM3YjczMTQwMTA4ODk5ODQaDjI4ZjhmMWY1MjUzNzJhIKkCKgbmtYvor5U6DmxvY2FsaG9zdDo5OTk5Qgl1bmRlZmluZWRKA0hLR1ADYAFoAXADeAOAAQKKAQpjb20udGFvYmFvlQEX2U49nQEX2U49pQEX2U49ugEqCO4HEiUI7gcSCFRlY2RvRFNQGA01F9lOPTgBQBVKC1RlY2RvRFNQX1NHwAHuB8gBjcqCwKsy0AEB-gGbAmh0dHBzOi8vbm90aWNlLXNnLmJpZHRyYWlsLnRvcC93aW4_YmlkX2lkPWE4MjA0MmMwNTViMDRlNTM5ZWM2ODc2MTEyYzEwY2VkMTcyOTY2MzkwMjk4MyZjYW1wYWlnbl9pZD00MjgmYWRfZ3JvdXBfaWQ9OTc2MSZhZF9pZD03Njc5NyZjcmVhdGl2ZV9pZD02NTI2JmFmZmlsaWF0ZV9pZD0yOTcmYnVuZGxlPSZmaXJzdF9zc3A9YnJhaW54LnRlY2gmcHVibGlzaF9pZD0mb3M9QW5kcm9pZCZ0YWdfaWQ9dW5kZWZpbmVkJmJpZF9zdWNjZXNzX3ByaWNlPTAuMDUwNSZzaWduPWUzODQyNjhiYzAzYzhmYmOSAgp0YW9iYW8uY29togILYnJhaW54LnRlY2ioAgGyAgdhbmRyb2lkwAICygICc2fQAgHYAo3hlcWrMuACjeGVxasy&v=jFUg_b6F14d50JL-M6UE5Jc8VuA&auction_price=${AUCTION_PRICE}', + 'price': 0.0505, + 'w': 320 + } + ], + 'group': 0, + 'seat': 'agency' + } + ] + } + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + auctionId: '3eedbf83-7d1d-423c-be27-39e4af687040', + auctionStart: 1729663900819, + adUnitCode: 'dev-1', + bidId: '28f8f1f525372a', + bidder: 'brainx', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + params: { + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'http://adx-engine-gray.tec-do.cn/bid' + }, + src: 'client' + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body.seatbid[0].bid[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.mediaType).to.equal(BANNER); + expect(bidResponse.requestId).to.equal(bid.impid); + expect(bidResponse.cpm).to.equal(parseFloat(bid.price).toFixed(2)) + expect(bidResponse.currency).to.equal(serverResponse.body.cur); + expect(bidResponse.creativeId).to.equal(bid.crid || bid.id); + expect(bidResponse.netRevenue).to.be.true; + expect(bidResponse.nurl).to.equal(bid.nurl); + expect(bidResponse.lurl).to.equal(bid.lurl); + + expect(bidResponse.meta).to.be.an('object'); + expect(bidResponse.meta.mediaType).to.equal(BANNER); + expect(bidResponse.meta.primaryCatId).to.equal('IAB18-5'); + // expect(bidResponse.meta.secondaryCatIds).to.deep.equal(['IAB8']); + expect(bidResponse.meta.advertiserDomains).to.deep.equal(bid.adomain); + expect(bidResponse.meta.clickUrl).to.equal(bid.adomain[0]); + + expect(bidResponse.ad).to.equal(bid.adm); + expect(bidResponse.width).to.equal(bid.w); + expect(bidResponse.height).to.equal(bid.h); + }); + }); +}); From c52b2e7375302e5592e3426587b03fb31bf8d6d0 Mon Sep 17 00:00:00 2001 From: "hugh.qu" Date: Thu, 31 Oct 2024 16:31:30 +0800 Subject: [PATCH 2/8] fix adpater md --- modules/brainxBidAdapter.md | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/brainxBidAdapter.md b/modules/brainxBidAdapter.md index e63cf9037f0..c3bf91f378d 100644 --- a/modules/brainxBidAdapter.md +++ b/modules/brainxBidAdapter.md @@ -38,6 +38,7 @@ var adUnits = [{ bidder: 'brianx', params: { pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'http://adx-engine-gray.tec-do.cn/bid' } }] }]; From 5426a3b01bb29009f1bc112ab7fc55a2e207421e Mon Sep 17 00:00:00 2001 From: "hugh.qu" Date: Thu, 31 Oct 2024 16:54:01 +0800 Subject: [PATCH 3/8] delete console --- modules/brainxBidAdapter.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js index 722c851c69a..a1b1020dff1 100644 --- a/modules/brainxBidAdapter.js +++ b/modules/brainxBidAdapter.js @@ -120,16 +120,15 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function (serverResponse, bidRequest) { - let bidResponses = []; let serverBody; - console.log('serverBody-=======', serverResponse); + // console.log('serverBody-=======', serverResponse); if ((serverBody = serverResponse.body) && serverBody.seatbid && isArray(serverBody.seatbid)) { serverBody.seatbid.forEach((seatbidder) => { if (seatbidder.bid && isArray(seatbidder.bid)) { bidResponses = seatbidder.bid.map((bid) => buildBidResponse(bid, bidRequest.originalBidRequest, serverBody)); - console.log('bidResponses-=======', bidResponses); + // console.log('bidResponses-=======', bidResponses); } }); } From 59e0a80b055ff3c67cc6c1a0faf95e5988c101d2 Mon Sep 17 00:00:00 2001 From: "hugh.qu" Date: Tue, 5 Nov 2024 15:34:14 +0800 Subject: [PATCH 4/8] fix repeat --- modules/brainxBidAdapter.js | 831 ++++-------------------------------- 1 file changed, 91 insertions(+), 740 deletions(-) diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js index a1b1020dff1..a79cfe5fadd 100644 --- a/modules/brainxBidAdapter.js +++ b/modules/brainxBidAdapter.js @@ -1,82 +1,31 @@ -import { - deepAccess, - isArray, - isPlainObject, - isStr, - logError, - logWarn, - deepSetValue, - getDNT, - isFn, - generateUUID -} from '../src/utils.js'; -// import { config } from 'src/config'; +import { deepAccess, generateUUID, isArray, logWarn } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js'; -// import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import { Renderer } from '../src/Renderer.js'; -import { OUTSTREAM } from '../src/video.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +// import { config } from 'src/config.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js' import { config } from '../src/config.js'; const BIDDER_CODE = 'brainx'; -// const ENDPOINT_URL = 'http://adx-engine-gray.tec-do.cn/bid'; -const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_LANGUAGE = 'en'; -const NET_REVENUE = true; -// const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; -const BANNER_DEFAULTS = { - SIZE: [300, 250] -} +const METHOD = 'POST'; +const TTL = 200; +const NET_REV = true; -const VIDEO_DEFAULTS = { - PROTOCOLS: [2, 3, 5, 6], - MIMES: ['video/mp4'], - PLAYBACK_METHODS: [1, 2, 3, 4], - DELIVERY: [1], - API: [1, 2, 5], - SIZE: [640, 480] -} -const NATIVE_DEFAULTS = { - IMAGE_TYPE: { - ICON: 1, - MAIN: 3, - }, - ASSET_ID: { - TITLE: 1, - IMAGE: 2, - ICON: 3, - BODY: 4, - SPONSORED: 5, - CTA: 6 - }, - DATA_ASSET_TYPE: { - SPONSORED: 1, - DESC: 2, - CTA_TEXT: 12, - }, - LENGTH: { - TITLE: 90, - BODY: 140, - SPONSORED: 25, - CTA: 20 +const converter = ortbConverter({ + context: { + // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them + netRevenue: NET_REV, // or false if your adapter should set bidResponse.netRevenue = false + ttl: TTL // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) } -} +}); export const spec = { code: BIDDER_CODE, - // gvlid: 0000000000, - supportedMediaTypes: [BANNER, VIDEO, NATIVE], - // aliases: [{ code: 'myAlias', gvlid: 99999999999 }], - /** - * 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. - */ + // gvlid: IAB_GVL_ID, + // aliases: [ + // { code: "myalias", gvlid: IAB_GVL_ID_IF_DIFFERENT } + // ], isBidRequestValid: function (bid) { - logWarn('检查 bid request'); - if (!(hasBannerMediaType(bid) || hasVideoMediaType(bid))) { + if (!(hasBanner(bid) || hasVideo(bid))) { logWarn('Invalid bid request - missing required mediaTypes'); return false; } @@ -91,687 +40,89 @@ export const spec = { } return true; }, - /** - * 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: function (validBidRequests, bidderRequest) { - // if (bidderRequest && bidderRequest.gdprConsent) { - // adapterRequest.gdpr_consent = { - // consent_string: bidderRequest.gdprConsent.consentString, - // // will check if the gdprApplies field was populated with a boolean value (ie from page config). If it's undefined, then default to true - // // consent_required: (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? bidderRequest.gdprConsent.gdprApplies : true - // consent_required: false - // } - // adapterRequest.gdpr_consent = 'undefined' - // } - - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests) - // convertOrtbRequestToProprietaryNative(validBidRequests); + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidRequests, bidderRequest }) + const device = config.getConfig('device') || {}; + // console.log('data-==========', data); + // console.log('bidRequests-==========', bidRequests); + // console.log('bidderRequest-==========', bidderRequest); + data.user = { + buyeruid: generateUUID() + } + data.device.ip = navigator.ip || '202.100.48.46'; + data.device.os = 'Android' + data.device.geo = device.geo || { + country: 'HKG' + }; - return validBidRequests.map(validBidRequest => (buildOpenRtbBidRequest(validBidRequest, bidderRequest))) + return { + method: METHOD, + url: `${String(deepAccess(bidRequests[0], 'params.endpoint'))}?token=${String(deepAccess(bidRequests[0], 'params.pubId'))}`, + data + } }, - /** - * 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: function (serverResponse, bidRequest) { - let bidResponses = []; - - let serverBody; - // console.log('serverBody-=======', serverResponse); - if ((serverBody = serverResponse.body) && serverBody.seatbid && isArray(serverBody.seatbid)) { - serverBody.seatbid.forEach((seatbidder) => { - if (seatbidder.bid && isArray(seatbidder.bid)) { - bidResponses = seatbidder.bid.map((bid) => buildBidResponse(bid, bidRequest.originalBidRequest, serverBody)); - // console.log('bidResponses-=======', bidResponses); + interpretResponse(response, request) { + let bids = []; + // console.log('response-==========', response); + // console.log('request-==========', request); + // const bids = converter.fromORTB({ response: response.body, request: request.data }).bids; + if (response.body && response.body.seatbid && isArray(response.body.seatbid)) { + response.body.seatbid.forEach(function (bidder) { + if (isArray(bidder.bid)) { + bidder.bid.map((bid) => { + let serverBody = response.body; + // bidRequest = request.originalBidRequest, + let mediaType = BANNER; + let currency = serverBody.cur || 'USD' + + const cpm = (parseFloat(bid.price) || 0).toFixed(2); + const categories = deepAccess(bid, 'cat', []); + + const bidRes = { + ad: bid.adm, + width: bid.w, + height: bid.h, + requestId: bid.impid, + cpm: cpm, + currency: currency, + mediaType: mediaType, + ttl: TTL, + creativeId: bid.crid || bid.id, + netRevenue: NET_REV, + nurl: bid.nurl, + lurl: bid.lurl, + meta: { + mediaType: mediaType, + primaryCatId: categories[0], + secondaryCatIds: categories.slice(1), + } + }; + if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { + bidRes.meta.advertiserDomains = bid.adomain; + bidRes.meta.clickUrl = bid.adomain[0]; + } + bids.push(bidRes); + }) } }); } - return bidResponses; - }, - // getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { - // const syncs = [] - // console.log('gdprConsent-=======', gdprConsent); - // console.log('syncOptions-=======', syncOptions) - // console.log('serverResponses-=======', serverResponses) - // var gdpr_params; - // // if (typeof gdprConsent.gdprApplies === 'boolean') { - // // gdpr_params = `gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - // // } else { - // // gdpr_params = `gdpr_consent=${gdprConsent.consentString}`; - // // } - - // // if (syncOptions.iframeEnabled) { - // // syncs.push({ - // // type: 'iframe', - // // url: '//acdn.adnxs.com/ib/static/usersync/v3/async_usersync.html?' + gdpr_params - // // }); - // // } - // if (syncOptions.pixelEnabled && serverResponses.length > 0) { - // syncs.push({ - // type: 'image', - // url: serverResponses[0].body.userSync.url + gdpr_params - // }); - // } - // return syncs; - // }, - onTimeout: function (data) { - // Bidder specifc code - }, - onBidWon: function (bid) { - // Bidder specific code + // console.log('bids-==========', bids); + return bids; }, - onSetTargeting: function (bid) { - // Bidder specific code - }, - // onBidderError: function ({ error, bidderRequest }) { - // // Bidder specific code - // }, - onAdRenderSucceeded: function (bid) { - // Bidder specific code - } -} - -function hasBannerMediaType(bidRequest) { + // getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { }, + // onTimeout: function (timeoutData) { }, + // onBidWon: function (bid) { }, + // onSetTargeting: function (bid) { }, + // onBidderError: function ({ error, bidderRequest }) { }, + // onAdRenderSucceeded: function (bid) { }, + supportedMediaTypes: [BANNER] +} +function hasBanner(bidRequest) { return !!deepAccess(bidRequest, 'mediaTypes.banner'); } -function hasVideoMediaType(bidRequest) { +function hasVideo(bidRequest) { return !!deepAccess(bidRequest, 'mediaTypes.video'); } -function buildOpenRtbBidRequest(bidRequest, bidderRequest) { - // build OpenRTB request body - const payload = { - id: bidderRequest.bidderRequestId, - tmax: bidderRequest.timeout, - test: config.getConfig('debug') ? 1 : 0, - imp: createImp(bidRequest), - device: getDevice(), - at: 1, - bcat: getBcat(bidRequest), - cur: [DEFAULT_CURRENCY], - regs: { - coppa: config.getConfig('coppa') ? 1 : 0, - ext: {} - }, - user: { - buyeruid: generateUUID() - } - } - payload.device.ip = navigator.ip || '202.100.48.46'; - - fulfillInventoryInfo(payload, bidRequest, bidderRequest); - - const gdprConsent = deepAccess(bidderRequest, 'gdprConsent'); - if (!!gdprConsent && gdprConsent.gdprApplies) { - deepSetValue(payload, 'regs.ext.gdpr', 1); - deepSetValue(payload, 'user.ext.consent', gdprConsent.consentString); - } - - const uspConsent = deepAccess(bidderRequest, 'uspConsent'); - if (uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', uspConsent); - } - - const eids = deepAccess(bidRequest, 'userIdAsEids', []); - if (eids.length > 0) { - deepSetValue(payload, 'user.eids', eids); - } - - return { - method: 'POST', - url: `${String(deepAccess(bidRequest, 'params.endpoint'))}?token=${String(deepAccess(bidRequest, 'params.pubId'))}`, - data: payload, - }; - - // return { - // method: 'POST', - // url: ENDPOINT + String(deepAccess(bidRequest, 'params.publisherId')) + - // '?ep=' + String(deepAccess(bidRequest, 'params.endpointId')), - // data: JSON.stringify(payload), - // options: { - // contentType: 'application/json', - // customHeaders: { - // 'x-openrtb-version': 2.5 - // } - // }, - // // set original bid request, so we can get it from interpretResponse - // originalBidRequest: bidRequest - // } -} - -function fulfillInventoryInfo(payload, bidRequest, bidderRequest) { - let info = deepAccess(bidRequest, 'params. '); - // 1.If the inventory info for site specified, use the site object provided in params. - let key = 'site'; - if (!isPlainObject(info)) { - info = deepAccess(bidRequest, 'params.app'); - if (isPlainObject(info)) { - // 2.If the inventory info for app specified, use the app object provided in params. - key = 'app'; - } else { - // 3.Otherwise, we use site by default. - info = {}; - } - } - // Fulfill key parameters. - info.id = String(deepAccess(bidRequest, 'params.publisherId')); - info.domain = info.domain || bidderRequest?.refererInfo?.domain || window.location.host; - if (key === 'site') { - info.ref = info.ref || bidderRequest?.refererInfo?.ref || ''; - info.page = info.page || bidderRequest?.refererInfo?.page; - } - - payload[key] = info; -} - -function getLanguage() { - const lang = (navigator.languages && navigator.languages[0]) || - navigator.language || navigator.userLanguage; - return lang ? lang.split('-')[0] : DEFAULT_LANGUAGE; -} - -function getDevice() { - const device = config.getConfig('device') || {}; - - device.w = device.w || window.screen.width; - device.h = device.h || window.screen.height; - device.ua = device.ua || navigator.userAgent; - device.language = device.language || getLanguage(); - device.dnt = typeof device.dnt === 'number' - ? device.dnt : (getDNT() ? 1 : 0); - // data.id = request.bidId; - device.ip = navigator.ip || '202.100.48.46'; - device.geo = device.geo || {}; - device.geo.country = device.geo.country || 'HKG'; - device.os = device.os || 'Android'; - // device = { - // ...device, - // geo: { - // ...device.geo, - // country: 'HKG' - // }, - // os: 'Android' - // } - return device; -} - -function getBcat(bidRequest) { - let bcat = []; - - const pBcat = deepAccess(bidRequest, 'params.bcat'); - if (pBcat) { - bcat = bcat.concat(pBcat); - } - - return bcat; -} - -function buildBidResponse(bid, bidRequest, responseBody) { - let mediaType = BANNER; - let nativeResponse; - - if (/VAST\s+version/.test(bid.adm)) { - mediaType = VIDEO; - } else { - let markup; - try { - markup = JSON.parse(bid.adm); - } catch (e) { - markup = null; - } - - // OpenRtb Markup Response Object - // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.1 - if (markup && isPlainObject(markup.native)) { - mediaType = NATIVE; - nativeResponse = markup.native; - } - } - - const currency = responseBody.cur || DEFAULT_CURRENCY; - const cpm = (parseFloat(bid.price) || 0).toFixed(2); - - const categories = deepAccess(bid, 'cat', []); - - const bidResponse = { - requestId: bid.impid, - cpm: cpm, - currency: currency, - mediaType: mediaType, - ttl: 300, - creativeId: bid.crid || bid.id, - netRevenue: NET_REVENUE, - nurl: bid.nurl, - lurl: bid.lurl, - meta: { - mediaType: mediaType, - primaryCatId: categories[0], - secondaryCatIds: categories.slice(1), - } - }; - - if (bid.adomain && isArray(bid.adomain) && bid.adomain.length > 0) { - bidResponse.meta.advertiserDomains = bid.adomain; - bidResponse.meta.clickUrl = bid.adomain[0]; - } - - switch (mediaType) { - case VIDEO: { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', VIDEO_DEFAULTS.SIZE); - const size = canonicalizeSizesArray(playerSize)[0]; - - bidResponse.vastXml = bid.adm; - - bidResponse.width = bid.w || size[0]; - bidResponse.height = bid.h || size[1]; - - const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - - // if outstream video, add a default render for it. - if (context === OUTSTREAM) { - // fill adResponse, will be used in ANOutstreamVideo.renderAd - bidResponse.adResponse = { - content: bidResponse.vastXml, - width: bidResponse.width, - height: bidResponse.height, - player_width: size[0], - player_height: size[1], - }; - bidResponse.renderer = createRenderer(bidRequest); - } - break; - } - case NATIVE: { - bidResponse.native = interpretNativeAd(nativeResponse, currency, cpm); - break; - } - default: { - bidResponse.ad = bid.adm; - - bidResponse.width = bid.w; - bidResponse.height = bid.h; - } - } - return bidResponse; -} - -function createImp(bidRequest) { - const imp = []; - - const impItem = { - id: bidRequest.bidId, - tagid: String(deepAccess(bidRequest, 'params.placementId')), - }; - - let mediaType, size; - let bannerReq, videoReq, nativeReq; - - if ((bannerReq = deepAccess(bidRequest, 'mediaTypes.banner'))) { - size = canonicalizeSizesArray(bannerReq.sizes || BANNER_DEFAULTS.SIZE)[0]; - - impItem.banner = { - w: size[0], - h: size[1], - pos: 0, - }; - - mediaType = BANNER; - } else if ((videoReq = deepAccess(bidRequest, 'mediaTypes.video'))) { - size = canonicalizeSizesArray(videoReq.playerSize || VIDEO_DEFAULTS.SIZE)[0]; - - impItem.video = { - w: size[0], - h: size[1], - pos: 0, - mimes: videoReq.mimes || VIDEO_DEFAULTS.MIMES, - protocols: videoReq.protocols || VIDEO_DEFAULTS.PROTOCOLS, - startdelay: typeof videoReq.startdelay === 'number' ? videoReq.startdelay : 0, - skip: typeof videoReq.skip === 'number' ? videoReq.skip : 0, - playbackmethod: videoReq.playbackmethod || VIDEO_DEFAULTS.PLAYBACK_METHODS, - delivery: videoReq.delivery || VIDEO_DEFAULTS.DELIVERY, - api: videoReq.api || VIDEO_DEFAULTS.API, - }; - - mediaType = VIDEO; - } else if ((nativeReq = deepAccess(bidRequest, 'mediaTypes.native'))) { - const params = bidRequest.nativeParams || nativeReq; - - const request = { - native: { - ver: '1.1', - assets: createNativeAssets(params), - } - }; - - impItem.native = { - ver: '1.1', - request: JSON.stringify(request), - }; - - mediaType = NATIVE; - } - - const floorDetail = getBidFloor(bidRequest, { - mediaType: mediaType || '*', - size: size || '*' - }); - - impItem.bidfloor = floorDetail.floor; - impItem.bidfloorcur = floorDetail.currency; - - if (mediaType) { - imp.push(impItem); - } - - return imp; -} - -/** - * Get bid floor price - * - * @param {BidRequest} bid - * @param {Params} params - * @returns {Floor} floor price - */ -function getBidFloor(bid, { mediaType = '*', size = '*' }) { - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType, - size - }); - - if (isPlainObject(floorInfo) && !isNaN(floorInfo.floor)) { - return { - currency: floorInfo.currency || DEFAULT_CURRENCY, - floor: floorInfo.floor - }; - } - } - - return { - currency: DEFAULT_CURRENCY, - floor: 0.0 - } -} - -function createNativeAssets(params) { - const assets = []; - - if (params.title) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.TITLE, - required: params.title.required ? 1 : 0, - title: { - len: params.title.len || NATIVE_DEFAULTS.LENGTH.TITLE - } - }) - } - - if (params.image) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.IMAGE, - required: params.image.required ? 1 : 0, - img: mapNativeImage(params.image, NATIVE_DEFAULTS.IMAGE_TYPE.MAIN) - }) - } - - if (params.icon) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.ICON, - required: params.icon.required ? 1 : 0, - img: mapNativeImage(params.icon, NATIVE_DEFAULTS.IMAGE_TYPE.ICON) - }) - } - - if (params.sponsoredBy) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.SPONSORED, - required: params.sponsoredBy.required ? 1 : 0, - data: { - type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.SPONSORED, - len: params.sponsoredBy.len | NATIVE_DEFAULTS.LENGTH.SPONSORED - } - }) - } - - if (params.body) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.BODY, - required: params.body.required ? 1 : 0, - data: { - type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.DESC, - len: params.body.len || NATIVE_DEFAULTS.LENGTH.BODY - } - }) - } - - if (params.cta) { - assets.push({ - id: NATIVE_DEFAULTS.ASSET_ID.CTA, - required: params.cta.required ? 1 : 0, - data: { - type: NATIVE_DEFAULTS.DATA_ASSET_TYPE.CTA_TEXT, - len: params.cta.len || NATIVE_DEFAULTS.LENGTH.CTA - } - }) - } - - return assets; -} - -function canonicalizeSizesArray(sizes) { - if (sizes.length === 2 && !isArray(sizes[0])) { - return [sizes]; - } - return sizes; -} - -/** - * Create native image object - * - * @param {Object} image - * @param {Number} type - * @returns {NativeImage} - */ -function mapNativeImage(image, type) { - const img = { type: type }; - - if (image.aspect_ratios) { - const ratio = image.aspect_ratios[0]; - const minWidth = ratio.min_width || 100; - - img.wmin = minWidth; - img.hmin = (minWidth / ratio.ratio_width * ratio.ratio_height); - } - - if (image.sizes) { - const size = canonicalizeSizesArray(image.sizes)[0]; - - img.w = size[0]; - img.h = size[1]; - } - - return img; -} - -function interpretNativeAd(nativeResponse, currency, cpm) { - const native = {}; - - // OpenRtb Link Object - // https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-Native-Ads-Specification-1-1_2016.pdf#5.7 - const clickUrl = deepAccess(nativeResponse, 'link.url'); - if (clickUrl && isStr(clickUrl)) { - native.clickUrl = decodeURIComponent(clickUrl); - } - - const clickTrackers = deepAccess(nativeResponse, 'link.clicktrackers'); - if (clickTrackers && isArray(clickTrackers)) { - native.clickTrackers = clickTrackers - .filter(Boolean) - .map( - url => decodeURIComponent(url) - .replace(/\$\{AUCTION_PRICE\}/g, cpm) - .replace(/\$\{AUCTION_CURRENCY\}/g, currency) - ); - } - - if (nativeResponse.imptrackers && isArray(nativeResponse.imptrackers)) { - native.impressionTrackers = nativeResponse.imptrackers - .filter(Boolean) - .map( - url => decodeURIComponent(url) - .replace(/\$\{AUCTION_PRICE\}/g, cpm) - .replace(/\$\{AUCTION_CURRENCY\}/g, currency) - ); - } - - if (nativeResponse.jstracker && isStr(nativeResponse.jstracker)) { - native.javascriptTrackers = [nativeResponse.jstracker]; - } - - let assets; - if ((assets = nativeResponse.assets) && isArray(assets)) { - assets.forEach((asset) => { - switch (asset.id) { - case NATIVE_DEFAULTS.ASSET_ID.TITLE: { - const title = deepAccess(asset, 'title.text'); - if (title) { - native.title = title; - } - break; - } - case NATIVE_DEFAULTS.ASSET_ID.IMAGE: { - if (asset.img) { - native.image = { - url: decodeURIComponent(asset.img.url), - width: asset.img.w, - height: asset.img.h - } - } - break; - } - case NATIVE_DEFAULTS.ASSET_ID.ICON: { - if (asset.img) { - native.icon = { - url: decodeURIComponent(asset.img.url), - width: asset.img.w, - height: asset.img.h - } - } - break; - } - case NATIVE_DEFAULTS.ASSET_ID.BODY: { - const body = deepAccess(asset, 'data.value'); - if (body) { - native.body = body; - } - break; - } - case NATIVE_DEFAULTS.ASSET_ID.SPONSORED: { - const sponsoredBy = deepAccess(asset, 'data.value'); - if (sponsoredBy) { - native.sponsoredBy = sponsoredBy; - } - break; - } - case NATIVE_DEFAULTS.ASSET_ID.CTA: { - const cta = deepAccess(asset, 'data.value'); - if (cta) { - native.cta = cta; - } - break; - } - } - }); - } - - return native; -} - -function createRenderer(bidRequest) { - const globalRenderer = deepAccess(bidRequest, 'renderer'); - const currentRenderer = deepAccess(bidRequest, 'mediaTypes.video.renderer'); - - let url; - let config = {}; - let render = function (bid) { - bid.renderer.push(() => { - window.ANOutstreamVideo.renderAd({ - sizes: [bid.width, bid.height], - targetId: bid.adUnitCode, - adResponse: bid.adResponse, - }); - }); - }; - - if (currentRenderer) { - url = currentRenderer.url; - config = currentRenderer.options; - render = currentRenderer.render; - } else if (globalRenderer) { - url = globalRenderer.url; - config = globalRenderer.options; - render = globalRenderer.render; - } - - const renderer = Renderer.install({ - id: bidRequest.bidId, - url: url, - loaded: false, - config: config, - adUnitCode: bidRequest.adUnitCode - }); - - try { - renderer.setRender(render); - } catch (e) { - logError(BIDDER_CODE, 'Error calling setRender on renderer', e); - } - return renderer; -} - registerBidder(spec); - -/** - * 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. - */ - -/** - * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data - */ - -/** - * Register bidder specific code, which will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction - */ - -/** - * Register bidder specific code, which will execute when the adserver targeting has been set for a bid from this bidder - * @param {Bid} The bid of which the targeting has been set - */ - -/** - * Register bidder specific code, which will execute if the bidder responded with an error - * @param {error, bidderRequest} An object with the XMLHttpRequest error and the bid request object - */ - -/** - * Register bidder specific code, which will execute if the ad - * has been rendered successfully - * @param {bid} bid request object - */ - -// } From bb0a25b0592eacc1453a5422072d2172c9aedb29 Mon Sep 17 00:00:00 2001 From: "hugh.qu" Date: Thu, 7 Nov 2024 10:22:40 +0800 Subject: [PATCH 5/8] Modify fixed parameters --- integrationExamples/gpt/hello_world.html | 7 ++++--- integrationExamples/gpt/x-domain/creative.html | 2 +- modules/brainxBidAdapter.js | 16 +--------------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 03a2356f0ef..1f93e0e7192 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -20,15 +20,16 @@ code: 'div-gpt-ad-1460505748561-0', mediaTypes: { banner: { - sizes: [[300, 250]], + sizes: [[320, 250]], } }, // Replace this object to test a new Adapter! bids: [{ - bidder: 'appnexus', + bidder: 'brianx', params: { - placementId: 13144370 + pubId: 'F7B53DBC-85C1-4685-9A06-9CF4B6261FA3', + endpoint: 'http://adx-engine-gray.tec-do.cn/bid' } }] diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 63842b00882..b7ba6b36a79 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - +