From 8686d9eeaf029a4ccfb24602f9594eeefb065056 Mon Sep 17 00:00:00 2001 From: Hugh0222 Date: Wed, 18 Dec 2024 05:12:13 +0800 Subject: [PATCH] Brainx Bid Adapter : initial release (#12413) * add brainx adpater * fix adpater md * delete console * fix repeat * Modify fixed parameters * Modify endpoint to be optional * fix email * update endpoint url * remove hello_html & x-domain history changes * fix size * remove empty line * Update brainxBidAdapter.md --------- Co-authored-by: hugh.qu Co-authored-by: Chris Huie --- integrationExamples/gpt/hello_world.html | 2 +- modules/brainxBidAdapter.js | 117 ++++++++++++++++++ modules/brainxBidAdapter.md | 56 +++++++++ test/spec/modules/brainxBidAdapter_spec.js | 132 +++++++++++++++++++++ 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 modules/brainxBidAdapter.js create mode 100644 modules/brainxBidAdapter.md create mode 100644 test/spec/modules/brainxBidAdapter_spec.js diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 03a2356f0ef..bcf0c2fa1f2 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -92,4 +92,4 @@
Div-1
- \ No newline at end of file + diff --git a/modules/brainxBidAdapter.js b/modules/brainxBidAdapter.js new file mode 100644 index 00000000000..69832987fb7 --- /dev/null +++ b/modules/brainxBidAdapter.js @@ -0,0 +1,117 @@ +import { deepAccess, generateUUID, isArray, logWarn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.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 METHOD = 'POST'; +const TTL = 200; +const NET_REV = true; +let ENDPOINT = 'https://dsp.brainx.tech/bid' +// let ENDPOINT = 'http://adx-engine-gray.tec-do.cn/bid' + +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: IAB_GVL_ID, + // aliases: [ + // { code: "myalias", gvlid: IAB_GVL_ID_IF_DIFFERENT } + // ], + isBidRequestValid: function (bid) { + if (!(hasBanner(bid) || hasVideo(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; + }, + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({ bidRequests, bidderRequest }) + ENDPOINT = String(deepAccess(bidRequests[0], 'params.endpoint')) ? deepAccess(bidRequests[0], 'params.endpoint') : ENDPOINT + data.user = { + buyeruid: generateUUID() + } + return { + method: METHOD, + url: `${ENDPOINT}?token=${String(deepAccess(bidRequests[0], 'params.pubId'))}`, + data + } + }, + interpretResponse(response, request) { + let 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 bids; + }, + // 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 hasVideo(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.video'); +} + +registerBidder(spec); diff --git a/modules/brainxBidAdapter.md b/modules/brainxBidAdapter.md new file mode 100644 index 00000000000..a734147321b --- /dev/null +++ b/modules/brainxBidAdapter.md @@ -0,0 +1,56 @@ +# brianx Bidder Adapter + +## Overview + +``` +Module Name: brianx Bidder Adapter +Module Type: Bidder Adapter +Maintainer: brainx.official@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` | optional | String | The endpoint provided by Brainx Url. | `https://dsp.brainx.tech/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', + endpoint: 'https://dsp.brainx.tech/bid' + } + }] +}]; +``` + +* For video ads, enable prebid cache. + +```javascript +pbjs.setConfig({ + ortb2: { + ortbVersion: '2.5' + }, + debug: false // or true +}); +``` 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); + }); + }); +});