From d717131280044ed33b0aab3dd5a78942928fdc36 Mon Sep 17 00:00:00 2001 From: changjun Date: Tue, 10 Oct 2023 14:59:57 +0900 Subject: [PATCH 01/13] TPMN Support Video --- integrationExamples/gpt/tpmn_example.html | 168 +++++++ .../gpt/tpmn_serverless_example.html | 121 +++++ modules/tpmnBidAdapter.js | 387 +++++++++++----- modules/tpmnBidAdapter.md | 86 +++- test/spec/modules/tpmnBidAdapter_spec.js | 425 ++++++++++++------ 5 files changed, 930 insertions(+), 257 deletions(-) create mode 100644 integrationExamples/gpt/tpmn_example.html create mode 100644 integrationExamples/gpt/tpmn_serverless_example.html diff --git a/integrationExamples/gpt/tpmn_example.html b/integrationExamples/gpt/tpmn_example.html new file mode 100644 index 00000000000..7c2f0ac28f5 --- /dev/null +++ b/integrationExamples/gpt/tpmn_example.html @@ -0,0 +1,168 @@ + + + + + Prebid.js Banner Example + + + + + + + + + + +

Prebid.js TPMN Banner Example

+ +
+

Prebid.js TPMN Video Example

+
+ +
+
+
+ diff --git a/integrationExamples/gpt/tpmn_serverless_example.html b/integrationExamples/gpt/tpmn_serverless_example.html new file mode 100644 index 00000000000..0acaefbeb9c --- /dev/null +++ b/integrationExamples/gpt/tpmn_serverless_example.html @@ -0,0 +1,121 @@ + + + + + + + + + + + + + +

Ad Serverless Test Page

+ + +
+
+ + diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index bac99d578c5..00b2ea88fa0 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -1,126 +1,222 @@ /* eslint-disable no-tabs */ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { parseUrl, deepAccess } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { getStorageManager } from '../src/storageManager.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; import { config } from '../src/config.js'; +import * as utils from '../src/utils.js'; -export const ADAPTER_VERSION = '1'; -const SUPPORTED_AD_TYPES = [BANNER]; const BIDDER_CODE = 'tpmn'; -const URL = 'https://ad.tpmn.co.kr/prebidhb.tpmn'; -const IFRAMESYNC = 'https://ad.tpmn.co.kr/sync.tpmn?type=iframe'; -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +const DEFAULT_BID_TTL = 500; +const DEFAULT_CURRENCY = 'USD'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +// const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder'; +const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder'; +const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe'; +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + 'battr' +]; +const BANNER_ORTB_PARAMS = [ + 'battr' +]; +export const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +export const ADAPTER_VERSION = '2.0'; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, /** - *Determines whether or not the given bid request is valid. - * - * @param {object} bid The bid to validate. - * @return boolean True if this is a valid bid, and false otherwise. + * Register bidder specific code, which will execute if a bid from this bidder won the auction + * @param {Bid} bid The bid that won the auction */ - isBidRequestValid: function (bid) { - return 'params' in bid && - 'inventoryId' in bid.params && - 'publisherId' in bid.params && - !isNaN(Number(bid.params.inventoryId)) && - bid.params.inventoryId > 0 && - (typeof bid.mediaTypes.banner.sizes != 'undefined'); // only accepting appropriate sizes - }, - - /** - * @param {BidRequest[]} bidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ - buildRequests: (bidRequests, bidderRequest) => { - if (bidRequests.length === 0) { - return []; + onBidWon: function (bid) { + if (bid.burl) { + utils.triggerPixel(bid.burl); } - const bids = bidRequests.map(bidToRequest); - const bidderApiUrl = URL; - const payload = { - 'bids': [...bids], - 'site': createSite(bidderRequest.refererInfo) - }; - return [{ - method: 'POST', - url: bidderApiUrl, - data: payload - }]; + } +} + +function isBidRequestValid(bid) { + return (isValidInventoryId(bid) && (isValidBannerRequest(bid) || isValidVideoRequest(bid))); +} + +function isValidInventoryId(bid) { + return 'params' in bid && 'inventoryId' in bid.params && utils.isNumber(bid.params.inventoryId); +} + +function isValidBannerRequest(bid) { + const bannerSizes = utils.deepAccess(bid, `mediaTypes.${BANNER}.sizes`); + return utils.isArray(bannerSizes) && bannerSizes.length > 0 && bannerSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); +} + +function isValidVideoRequest(bid) { + const videoSizes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.playerSize`); + const videoMimes = utils.deepAccess(bid, `mediaTypes.${VIDEO}.mimes`); + + const isValidVideoSize = utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); + const isValidVideoMimes = utils.isArray(videoMimes) && videoMimes.length > 0; + return isValidVideoSize && isValidVideoMimes; +} + +function buildRequests(validBidRequests, bidderRequest) { + if (validBidRequests.length === 0 || !bidderRequest) return []; + let bannerBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.banner')); + let videoBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.video')); + let requests = []; + + bannerBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, BANNER)); + }); + + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + + return requests; +} + +function createRequest(bidRequests, bidderRequest, mediaType) { + const rtbData = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + + const bid = bidRequests.find((b) => b.params.inventoryId) + // console.log('createRequest : ', mediaType, bid); + + if (!rtbData.site) rtbData.site = {} + rtbData.site = createSite(bidderRequest.refererInfo) + + if (bidderRequest.gdprConsent) { + if (!rtbData.user) rtbData.user = {}; + if (!rtbData.user.ext) rtbData.user.ext = {}; + if (!rtbData.regs) rtbData.regs = {}; + if (!rtbData.regs.ext) rtbData.regs.ext = {}; + rtbData.user.ext.consent = bidderRequest.gdprConsent.consentString; + rtbData.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + if (bid.params.inventoryId) rtbData.ext = {}; + if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId + if (bid.params.bcat) rtbData.bcat = bid.params.bcat; + if (bid.params.badv) rtbData.badv = bid.params.badv; + if (bid.params.bapp) rtbData.bapp = bid.params.bapp; + + return { + method: 'POST', + url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, + data: rtbData, + options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + } +} + +function interpretResponse(response, request) { + return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids; +} + +registerBidder(spec); + +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: DEFAULT_BID_TTL, + currency: DEFAULT_CURRENCY }, - /** - * 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, serverRequest) { - if (!Array.isArray(serverResponse.body)) { - return []; + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + imp.secure = Number(window.location.protocol === 'https:'); + if (!imp.bidfloor && bidRequest.params.bidFloor) { + imp.bidfloor = bidRequest.params.bidFloor; + } + if (bidRequest.mediaTypes[VIDEO]) { + imp = buildVideoImp(bidRequest, imp); + } else if (bidRequest.mediaTypes[BANNER]) { + imp = buildBannerImp(bidRequest, imp); } - // server response body is an array of bid results - const bidResults = serverResponse.body; - // our server directly returns the format needed by prebid.js so no more - // transformation is needed here. - return bidResults; + + return imp; }, + bidResponse(buildBidResponse, bid, context) { + const {bidRequest} = context; + const bidResponse = buildBidResponse(bid, context); - getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { - const syncArr = []; - if (syncOptions.iframeEnabled) { - let policyParam = ''; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - policyParam += `&ccpa_consent=${uspConsent.consentString}`; + utils.logWarn('Building bidResponse'); + utils.logWarn('bid', bid); + utils.logWarn('bidRequest', bidRequest); + utils.logWarn('bidResponse', bidResponse); + + if (bidResponse.mediaType === BANNER) { + bidResponse.ad = bid.adm; + } else if (bidResponse.mediaType === VIDEO) { + if (bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.rendererUrl = VIDEO_RENDERER_URL; + bidResponse.renderer = createRenderer(bidRequest); } - const coppa = config.getConfig('coppa') ? 1 : 0; - policyParam += `&coppa=${coppa}`; - syncArr.push({ - type: 'iframe', - url: IFRAMESYNC + policyParam - }); - } else { - syncArr.push({ - type: 'image', - url: 'https://x.bidswitch.net/sync?ssp=tpmn' - }); - syncArr.push({ - type: 'image', - url: 'https://gocm.c.appier.net/tpmn' - }); - syncArr.push({ - type: 'image', - url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' - }); - syncArr.push({ - type: 'image', - url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' - }); } - return syncArr; - }, -}; + return bidResponse; + } +}); -registerBidder(spec); +function createRenderer(bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: VIDEO_RENDERER_URL, + config: utils.deepAccess(bid, 'renderer.options'), + loaded: false, + adUnitCode: bid.adUnitCode + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + return renderer; +} + +function outstreamRender(bid, doc) { + bid.renderer.push(() => { + const win = utils.getWindowFromDocument(doc) || window; + win.ANOutstreamVideo.renderAd({ + sizes: [bid.playerWidth, bid.playerHeight], + targetId: bid.adUnitCode, + rendererOptions: bid.renderer.getConfig(), + adResponse: { content: bid.vastXml } + + }, handleOutstreamRendererEvents.bind(null, bid)); + }); +} + +function handleOutstreamRendererEvents(bid, id, eventName) { + bid.renderer.handleVideoEvent({ id, eventName }); +} /** * Creates site description object */ function createSite(refInfo) { - let url = parseUrl(refInfo.page || ''); + let url = utils.parseUrl(refInfo.page || ''); let site = { 'domain': url.hostname, 'page': url.protocol + '://' + url.hostname + url.pathname }; + if (refInfo.ref) { site.ref = refInfo.ref } @@ -131,34 +227,91 @@ function createSite(refInfo) { return site; } -function parseSize(size) { - let sizeObj = {} - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - return sizeObj; -} +function buildVideoImp(bidRequest, imp) { + const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); + const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); + const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; + + const videoSizes = (videoAdUnitParams && videoAdUnitParams.playerSize) || []; + + if (videoSizes && videoSizes.length > 0) { + utils.deepSetValue(imp, 'video.w', videoSizes[0][0]); + utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); } - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) -} -function getBannerSizes(bidRequest) { - return parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes); + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `video.${param}`, videoParams[param]); + } + }); + + if (imp.video && videoParams?.context === 'outstream') { + imp.video.placement = imp.video.placement || 4; + } + + return { ...imp }; } -function bidToRequest(bid) { - const bidObj = {}; - bidObj.sizes = getBannerSizes(bid); +function buildBannerImp(bidRequest, imp) { + const bannerAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}`, {}); + const bannerBidderParams = utils.deepAccess(bidRequest, `params.${BANNER}`, {}); + + const bannerParams = { ...bannerAdUnitParams, ...bannerBidderParams }; + + let sizes = bidRequest.mediaTypes.banner.sizes; - bidObj.inventoryId = bid.params.inventoryId; - bidObj.publisherId = bid.params.publisherId; - bidObj.bidId = bid.bidId; - bidObj.adUnitCode = bid.adUnitCode; - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - bidObj.auctionId = bid.auctionId; + if (sizes) { + utils.deepSetValue(imp, 'banner.w', sizes[0][0]); + utils.deepSetValue(imp, 'banner.h', sizes[0][1]); + } + + BANNER_ORTB_PARAMS.forEach((param) => { + if (bannerParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `banner.${param}`, bannerParams[param]); + } + }); + + return { ...imp }; +} - return bidObj; +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncArr = []; + if (syncOptions.iframeEnabled) { + let policyParam = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + policyParam += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + policyParam += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + if (uspConsent && uspConsent.consentString) { + policyParam += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + policyParam += `&coppa=${coppa}`; + syncArr.push({ + type: 'iframe', + url: IFRAMESYNC + '?' + policyParam + }); + } else { + syncArr.push({ + type: 'image', + url: 'https://x.bidswitch.net/sync?ssp=tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://gocm.c.appier.net/tpmn' + }); + syncArr.push({ + type: 'image', + url: 'https://info.mmnneo.com/getGuidRedirect.info?url=https%3A%2F%2Fad.tpmn.co.kr%2Fcookiesync.tpmn%3Ftpmn_nid%3Dbf91e8b3b9d3f1af3fc1d657f090b4fb%26tpmn_buid%3D' + }); + syncArr.push({ + type: 'image', + url: 'https://sync.aralego.com/idSync?redirect=https%3A%2F%2Fad.tpmn.co.kr%2FpixelCt.tpmn%3Ftpmn_nid%3Dde91e8b3b9d3f1af3fc1d657f090b815%26tpmn_buid%3DSspCookieUserId' + }); + } + return syncArr; } diff --git a/modules/tpmnBidAdapter.md b/modules/tpmnBidAdapter.md index 8387528bb0f..6c915b2417c 100644 --- a/modules/tpmnBidAdapter.md +++ b/modules/tpmnBidAdapter.md @@ -11,10 +11,27 @@ Maintainer: develop@tpmn.co.kr Connects to TPMN exchange for bids. NOTE: -- TPMN bid adapter only supports Banner at the moment. +- TPMN bid adapter only supports MediaType BANNER, VIDEO. - Multi-currency is not supported. +- Please contact the TPMN sales team via email for "inventoryId" issuance. -# Sample Ad Unit Config + +# Bid Parameters + +## bids.params (Banner, Video) +***Pay attention to the case sensitivity.*** + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| -------------- | ----------- | ------------------------------------------ | ------------- | ------------ | +| `inventoryId` | required | Ad Inventory id TPMN | 123 | Number | +| `bidFloor` | recommended | Minimum price in USD. bidFloor applies to a specific unit. | 1.50 | Number | +| `bcat` | optional | IAB 5.1 Content Categories | ['IAB7-39'] | [String] | +| `badv` | optional | IAB Block list of advertisers by their domains | ['example.com'] | [String] | +| `bapp` | optional | IAB Block list of applications | ['com.blocked'] | [String] | + + +# Banner Ad Unit Config ``` var adUnits = [{ // Banner adUnit @@ -22,16 +39,77 @@ NOTE: mediaTypes: { banner: { sizes: [[300, 250], [320, 50]], // banner size + battr: [1,2,3] // optional } }, bids: [ { bidder: 'tpmn', params: { - inventoryId: '1', - publisherId: 'TPMN' + inventoryId: 1, // required + bidFloor: 2.0, // recommended + ... // bcat, badv, bapp // optional } } ] }]; +``` + + +# mediaTypes Parameters + +## mediaTypes.banner + +The following banner parameters are supported here so publishers may fully declare their banner inventory: + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| `sizes` | required | Avalaible sizes supported for banner ad unit | [ [300, 250], [300, 600] ] | [[Integer, Integer], [Integer, Integer]] | +| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] | +## mediaTypes.video + +We support the following OpenRTB params that can be specified in `mediaTypes.video` or in `bids[].params.video` + +{: .table .table-bordered .table-striped } +| Name | Scope | Description | Example | Type | +| --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | +| `context` | required | instream or outstream |'outstream' | string | +| `playerSize` | required | Avalaible sizes supported for video ad unit. | [300, 250] | [Integer, Integer] | +| `mimes` | required | List of content MIME types supported by the player. | ['video/mp4']| [String]| +| `protocols` | optional | Supported video bid response protocol values. | [2,3,5,6] | [integers]| +| `api` | optional | Supported API framework values. | [2] | [integers] | +| `maxduration` | optional | Maximum video ad duration in seconds. | 30 | Integer | +| `minduration` | optional | Minimum video ad duration in seconds. | 6 | Integer | +| `startdelay` | optional | Indicates the start delay in seconds for pre-roll, mid-roll, or post-roll ad placements. | 0 | Integer | +| `placement` | optional | Placement type for the impression. | 1 | Integer | +| `minbitrate` | optional | Minimum bit rate in Kbps. | 300 | Integer | +| `maxbitrate` | optional | Maximum bit rate in Kbps. | 9600 | Integer | +| `playbackmethod` | optional | Playback methods that may be in use. Only one method is typically used in practice. | [2]| [Integers] | +| `linearity` | optional | OpenRTB2 linearity. in-strea,overlay... | 1 | Integer | +| `skip` | optional | Indicates if the player will allow the video to be skipped, where 0 = no, 1 = yes . | 1 | Integer | +| `battr` | optional | IAB 5.3 Creative Attributes | [1,2,3] | [Number] | + + +# Video Ad Unit Config +``` + var adUnits = [{ + code: 'video-div', + mediaTypes: { + video: { + context: 'instream', // required + mimes: ['video/mp4'], // required + playerSize: [ 640, 480 ], // required + ... // skippable, startdelay, battr.. // optional + } + }, + bids: [{ + bidder: 'tpmn', + params: { + inventoryId: 2, // required + bidFloor: 2.0, // recommended + ... // bcat, badv, bapp // optional + } + }] + }]; ``` \ No newline at end of file diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index e2b14b18f61..0d3abd29f8b 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -1,16 +1,126 @@ /* eslint-disable no-tabs */ -import {expect} from 'chai'; -import {spec, storage} from 'modules/tpmnBidAdapter.js'; -import {generateUUID} from '../../../src/utils.js'; -import {newBidder} from '../../../src/adapters/bidderFactory'; +import { spec, storage, VIDEO_RENDERER_URL, ADAPTER_VERSION } from 'modules/tpmnBidAdapter.js'; +import { generateUUID } from '../../../src/utils.js'; +import { expect } from 'chai'; +import * as utils from 'src/utils'; import * as sinon from 'sinon'; +const BIDDER_CODE = 'tpmn'; +const BANNER_BID = { + bidder: BIDDER_CODE, + params: { + inventoryId: 1 + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const VIDEO_BID = { + bidder: BIDDER_CODE, + params: { + inventoryId: 1 + }, + mediaTypes: { + video: { + context: 'outstream', + api: [1, 2, 4, 6], + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + playerSize: [[1024, 768]], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0 + }, + }, + adUnitCode: 'adUnitCode1', + bidId: 'bidId', + bidderRequestId: 'bidderRequestId', + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', +}; + +const BIDDER_REQUEST = { + auctionId: 'auctionId-56a2-4f71-9098-720a68f2f708', + bidderRequestId: 'bidderRequestId', + timeout: 500, + refererInfo: { + page: 'https://hello-world-page.com/', + domain: 'hello-world-page.com', + ref: 'http://example-domain.com/foo', + } +}; + +const BANNER_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidId': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 0.18, + 'adm': '', + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'w': 300, + 'h': 250 + } + ] + } + ], + 'cur': 'USD' +}; + +const VIDEO_BID_RESPONSE = { + 'id': 'bidderRequestId', + 'bidid': 'bidid', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'id', + 'impid': 'bidId', + 'price': 1.09, + 'adid': '144762342', + 'burl': 'http://0.0.0.0:8181/burl', + 'adm': '', + 'adomain': [ + 'https://dummydomain.com' + ], + 'cid': 'cid', + 'crid': 'crid', + 'iurl': 'iurl', + 'cat': [], + 'h': 768, + 'w': 1024 + } + ] + } + ], + 'cur': 'USD' +}; + describe('tpmnAdapterTests', function () { - const adapter = newBidder(spec); - const BIDDER_CODE = 'tpmn'; let sandbox = sinon.sandbox.create(); let getCookieStub; - beforeEach(function () { $$PREBID_GLOBAL$$.bidderSettings = { tpmn: { @@ -27,152 +137,195 @@ describe('tpmnAdapterTests', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }); - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function') - }) + describe('isBidRequestValid()', function () { + it('should accept request if placementId is passed', function () { + let bid = { + bidder: BIDDER_CODE, + params: { + inventoryId: 123 + }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } + }; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should reject requests without params', function () { + let bid = { + bidder: BIDDER_CODE, + params: {} + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(BANNER_BID)).to.equal(true); + expect(spec.isBidRequestValid(VIDEO_BID)).to.equal(true); + }); }); - describe('isBidRequestValid', function () { - let bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', - params: { - inventoryId: '1', - publisherId: 'TPMN' - }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', - mediaTypes: { - banner: { - sizes: [[300, 250]] + describe('buildRequests()', function () { + it('should have gdpr data if applicable', function () { + const bid = utils.deepClone(BANNER_BID); + + const req = Object.assign({}, BIDDER_REQUEST, { + gdprConsent: { + consentString: 'consentString', + gdprApplies: true, } - } - }; + }); + let request = spec.buildRequests([bid], req)[0]; - it('should return true if a bid is valid banner bid request', function () { - expect(spec.isBidRequestValid(bid)).to.be.equal(true); + const payload = request.data; + expect(payload.user.ext).to.have.property('consent', req.gdprConsent.consentString); + expect(payload.regs.ext).to.have.property('gdpr', 1); }); - it('should return false where requried param is missing', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); - }); + it('should properly forward ORTB blocking params', function () { + let bid = utils.deepClone(BANNER_BID); + bid = utils.mergeDeep(bid, { + params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'] }, + mediaTypes: { banner: { battr: [1] } } + }); - it('should return false when required param values have invalid type', function () { - let bid = Object.assign({}, bid); - bid.params = { - 'inventoryId': null, - 'publisherId': null - }; - expect(spec.isBidRequestValid(bid)).to.be.equal(false); + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + + expect(request).to.exist.and.to.be.an('object'); + const payload = request.data; + expect(payload).to.have.deep.property('bcat', ['IAB1-1']); + expect(payload).to.have.deep.property('badv', ['example.com']); + expect(payload).to.have.deep.property('bapp', ['com.example']); + expect(payload.imp[0].banner).to.have.deep.property('battr', [1]); }); - }); - describe('buildRequests', function () { - it('should return an empty list if there are no bid requests', function () { - const emptyBidRequests = []; - const bidderRequest = {}; - const request = spec.buildRequests(emptyBidRequests, bidderRequest); - expect(request).to.be.an('array').that.is.empty; + context('when mediaType is banner', function () { + it('should build correct request for banner bid with both w, h', () => { + const bid = utils.deepClone(BANNER_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const requestData = request.data; + + expect(requestData.imp[0].banner.w).to.equal(300); + expect(requestData.imp[0].banner.h).to.equal(250); + }); + + it('should create request data', function () { + const bid = utils.deepClone(BANNER_BID); + + let [request] = spec.buildRequests([bid], BIDDER_REQUEST); + expect(request).to.exist.and.to.be.a('object'); + const payload = request.data; + expect(payload.imp[0]).to.have.property('id', bid.bidId); + }); }); - it('should generate a POST server request with bidder API url, data', function () { - const bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', - params: { - inventoryId: '1', - publisherId: 'TPMN' - }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }; - const tempBidRequests = [bid]; - const tempBidderRequest = { - refererInfo: { - page: 'http://localhost/test', - site: { - domain: 'localhost', - page: 'http://localhost/test' - } - } - }; - const builtRequest = spec.buildRequests(tempBidRequests, tempBidderRequest); - - expect(builtRequest).to.have.lengthOf(1); - expect(builtRequest[0].method).to.equal('POST'); - expect(builtRequest[0].url).to.match(/ad.tpmn.co.kr\/prebidhb.tpmn/); - const apiRequest = builtRequest[0].data; - expect(apiRequest.site).to.deep.equal({ - domain: 'localhost', - page: 'http://localhost/test' + + context('when mediaType is video', function () { + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video; + + expect(spec.isBidRequestValid(bid)).to.equal(false); }); - expect(apiRequest.bids).to.have.lengthOf('1'); - expect(apiRequest.bids[0].inventoryId).to.equal('1'); - expect(apiRequest.bids[0].publisherId).to.equal('TPMN'); - expect(apiRequest.bids[0].bidId).to.equal('29092404798c9'); - expect(apiRequest.bids[0].adUnitCode).to.equal('temp-unitcode'); - expect(apiRequest.bids[0].auctionId).to.equal('da1d7a33-0260-4e83-a621-14674116f3f9'); - expect(apiRequest.bids[0].sizes).to.have.lengthOf('1'); - expect(apiRequest.bids[0].sizes[0]).to.deep.equal({ - width: 300, - height: 250 + + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video.playerSize; + + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should use bidder video params if they are set', () => { + const videoBidWithParams = utils.deepClone(VIDEO_BID); + const bidderVideoParams = { + api: [1, 2], + mimes: ['video/mp4'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + minduration: 0, + maxduration: 60, + w: 1024, + h: 768, + startdelay: 0 + }; + + videoBidWithParams.params.video = bidderVideoParams; + + const requests = spec.buildRequests([videoBidWithParams], BIDDER_REQUEST); + const request = requests[0].data; + + expect(request.imp[0]).to.deep.include({ + video: { + ...bidderVideoParams, + w: videoBidWithParams.mediaTypes.video.playerSize[0][0], + h: videoBidWithParams.mediaTypes.video.playerSize[0][1], + }, + }); }); }); }); - describe('interpretResponse', function () { - const bid = { - adUnitCode: 'temp-unitcode', - bidder: 'tpmn', - params: { - inventoryId: '1', - publisherId: 'TPMN' - }, - bidId: '29092404798c9', - bidderRequestId: 'a01', - auctionId: 'da1d7a33-0260-4e83-a621-14674116f3f9', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - } - }; - const tempBidRequests = [bid]; + describe('interpretResponse()', function () { + context('when mediaType is banner', function () { + it('should correctly interpret valid banner response', function () { + const bid = utils.deepClone(BANNER_BID); + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const response = utils.deepClone(BANNER_BID_RESPONSE); - it('should return an empty aray to indicate no valid bids', function () { - const emptyServerResponse = {}; - const bidResponses = spec.interpretResponse(emptyServerResponse, tempBidRequests); - expect(bidResponses).is.an('array').that.is.empty; - }); - it('should return an empty array to indicate no valid bids', function () { - const mockBidResult = { - requestId: '9cf19229-34f6-4d06-bc1d-0e44e8d616c8', - cpm: 10.0, - creativeId: '1', - width: 300, - height: 250, - netRevenue: true, - currency: 'USD', - ttl: 1800, - ad: '
TPMN HeaderBidding!
', - adType: 'banner' - }; - const testServerResponse = { - headers: [], - body: [mockBidResult] - }; - const bidResponses = spec.interpretResponse(testServerResponse, tempBidRequests); - expect(bidResponses).deep.equal([mockBidResult]); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('banner'); + expect(bids[0].burl).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].ad).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].creativeId).to.equal(BANNER_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(500); + expect(bids[0].netRevenue).to.equal(true); + }); + + it('should handle empty bid response', function () { + const bid = utils.deepClone(BANNER_BID); + + let request = spec.buildRequests([bid], BIDDER_REQUEST)[0]; + const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} }); + const bids = spec.interpretResponse(EMPTY_RESP, request); + expect(bids).to.be.empty; + }); }); + if (FEATURES.VIDEO) { + context('when mediaType is video', function () { + it('should correctly interpret valid instream video response', () => { + const bid = utils.deepClone(VIDEO_BID); + + const [request] = spec.buildRequests([bid], BIDDER_REQUEST); + const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request); + expect(bids).to.be.an('array').that.is.not.empty; + + expect(bids[0].mediaType).to.equal('video'); + expect(bids[0].burl).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].burl); + expect(bids[0].currency).to.equal('USD'); + expect(bids[0].requestId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].impid); + expect(bids[0].cpm).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].price); + expect(bids[0].width).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].w); + expect(bids[0].height).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].h); + expect(bids[0].vastXml).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].adm); + expect(bids[0].rendererUrl).to.equal(VIDEO_RENDERER_URL); + expect(bids[0].creativeId).to.equal(VIDEO_BID_RESPONSE.seatbid[0].bid[0].crid); + expect(bids[0].meta.advertiserDomains[0]).to.equal('https://dummydomain.com'); + expect(bids[0].ttl).to.equal(500); + expect(bids[0].netRevenue).to.equal(true); + }); + }); + } }); describe('getUserSync', function () { From 792fc2ce6e74debec8b342030393405f68ac9438 Mon Sep 17 00:00:00 2001 From: changjun Date: Tue, 17 Oct 2023 12:06:35 +0900 Subject: [PATCH 02/13] add video param plcmt --- modules/tpmnBidAdapter.js | 8 +++++++- test/spec/modules/tpmnBidAdapter_spec.js | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 00b2ea88fa0..756dcc34858 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -29,7 +29,8 @@ const VIDEO_ORTB_PARAMS = [ 'playbackmethod', 'api', 'linearity', - 'battr' + 'battr', + 'plcmt' ]; const BANNER_ORTB_PARAMS = [ 'battr' @@ -246,8 +247,13 @@ function buildVideoImp(bidRequest, imp) { } }); + if (imp.video && videoParams?.context === 'instream') { + imp.video.placement = imp.video.placement || 1; + imp.video.plcmt = imp.video.plcmt || 1; + } if (imp.video && videoParams?.context === 'outstream') { imp.video.placement = imp.video.placement || 4; + imp.video.plcmt = imp.video.plcmt || 2; } return { ...imp }; diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index 0d3abd29f8b..e9c4ad207f0 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -38,6 +38,7 @@ const VIDEO_BID = { playerSize: [[1024, 768]], protocols: [3, 4, 7, 8, 10], placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, startdelay: 0 @@ -245,6 +246,7 @@ describe('tpmnAdapterTests', function () { playbackmethod: [3, 4], protocols: [5, 6], placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, w: 1024, From da8e9286419f7b9a254f9bdd0ca79ea8490d5acc Mon Sep 17 00:00:00 2001 From: changjun Date: Tue, 17 Oct 2023 12:15:03 +0900 Subject: [PATCH 03/13] update ortb bcat,badv,bapp --- modules/tpmnBidAdapter.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 756dcc34858..662116c7f24 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -115,10 +115,22 @@ function createRequest(bidRequests, bidderRequest, mediaType) { } if (bid.params.inventoryId) rtbData.ext = {}; if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId - if (bid.params.bcat) rtbData.bcat = bid.params.bcat; - if (bid.params.badv) rtbData.badv = bid.params.badv; - if (bid.params.bapp) rtbData.bapp = bid.params.bapp; - + + const ortb2Data = bidderRequest?.ortb2 || {}; + const bcat = ortb2Data?.bcat || bid.params.bcat || []; + const badv = ortb2Data?.badv || bid.params.badv || []; + const bapp = ortb2Data?.bapp || bid.params.bapp || []; + + if (bcat.length > 0) { + rtbData.bcat = bcat; + } + if (badv.length > 0) { + rtbData.badv = badv; + } + if (badv.length > 0) { + rtbData.bapp = bapp; + } + return { method: 'POST', url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, From 8bf75676b74ca461f4cf76b884bf99c8e720af52 Mon Sep 17 00:00:00 2001 From: changjun Date: Tue, 17 Oct 2023 12:20:04 +0900 Subject: [PATCH 04/13] instream video is sound on default, outstream video sound off default --- modules/tpmnBidAdapter.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 662116c7f24..d64c39faa9d 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -115,7 +115,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { } if (bid.params.inventoryId) rtbData.ext = {}; if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId - + const ortb2Data = bidderRequest?.ortb2 || {}; const bcat = ortb2Data?.bcat || bid.params.bcat || []; const badv = ortb2Data?.badv || bid.params.badv || []; @@ -130,7 +130,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { if (badv.length > 0) { rtbData.bapp = bapp; } - + return { method: 'POST', url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, @@ -262,10 +262,12 @@ function buildVideoImp(bidRequest, imp) { if (imp.video && videoParams?.context === 'instream') { imp.video.placement = imp.video.placement || 1; imp.video.plcmt = imp.video.plcmt || 1; + imp.video.playbackmethod = imp.video.playbackmethod || 1; } if (imp.video && videoParams?.context === 'outstream') { imp.video.placement = imp.video.placement || 4; - imp.video.plcmt = imp.video.plcmt || 2; + imp.video.plcmt = imp.video.plcmt || 4; + imp.video.playbackmethod = imp.video.playbackmethod || 2; } return { ...imp }; From 2699115a76ed891ec7f911ad924206b399790ad9 Mon Sep 17 00:00:00 2001 From: changjun Date: Wed, 18 Oct 2023 14:27:18 +0900 Subject: [PATCH 05/13] remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS --- modules/tpmnBidAdapter.js | 49 ++++++++++++++------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index d64c39faa9d..752cc3e2f08 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -14,27 +14,6 @@ const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; // const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder'; const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder'; const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe'; -const VIDEO_ORTB_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'placement', - 'protocols', - 'startdelay', - 'skip', - 'skipafter', - 'minbitrate', - 'maxbitrate', - 'delivery', - 'playbackmethod', - 'api', - 'linearity', - 'battr', - 'plcmt' -]; -const BANNER_ORTB_PARAMS = [ - 'battr' -]; export const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const ADAPTER_VERSION = '2.0'; @@ -183,6 +162,18 @@ const CONVERTER = ortbConverter({ } } return bidResponse; + }, + overrides: { + imp: { + video(orig, imp, bidRequest, context) { + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + } + } } }); @@ -253,11 +244,9 @@ function buildVideoImp(bidRequest, imp) { utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); } - VIDEO_ORTB_PARAMS.forEach((param) => { - if (videoParams.hasOwnProperty(param)) { - utils.deepSetValue(imp, `video.${param}`, videoParams[param]); - } - }); + if (videoParams.hasOwnProperty('battr')) { + utils.deepSetValue(imp, `video.battr`, videoParams['battr']); + } if (imp.video && videoParams?.context === 'instream') { imp.video.placement = imp.video.placement || 1; @@ -286,11 +275,9 @@ function buildBannerImp(bidRequest, imp) { utils.deepSetValue(imp, 'banner.h', sizes[0][1]); } - BANNER_ORTB_PARAMS.forEach((param) => { - if (bannerParams.hasOwnProperty(param)) { - utils.deepSetValue(imp, `banner.${param}`, bannerParams[param]); - } - }); + if (bannerParams.hasOwnProperty('battr')) { + utils.deepSetValue(imp, `banner.battr`, bannerParams['battr']); + } return { ...imp }; } From 7942b313f73d0c11c8b8f0a93787c3ff8326126e Mon Sep 17 00:00:00 2001 From: changjun Date: Wed, 18 Oct 2023 15:05:09 +0900 Subject: [PATCH 06/13] fixed incorrect placement default format & update example --- integrationExamples/gpt/tpmn_example.html | 6 +++--- modules/tpmnBidAdapter.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/integrationExamples/gpt/tpmn_example.html b/integrationExamples/gpt/tpmn_example.html index 7c2f0ac28f5..f215181c7e0 100644 --- a/integrationExamples/gpt/tpmn_example.html +++ b/integrationExamples/gpt/tpmn_example.html @@ -44,7 +44,7 @@ context: 'outstream', playerSize: [640, 480], mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], + //playbackmethod: [2, 4, 6], api: [1, 2, 4, 6], protocols: [3, 4, 7, 8, 10], placement: 1, @@ -136,8 +136,8 @@ googletag.cmd.push(function () { pbjs.que.push(function () { - pbjs.setTargetingForGPTAsync('banner-div-0'); - pbjs.setTargetingForGPTAsync('video-div-1'); + const adUnitCodes = adUnits.map(adUnit => adUnit.code); + pbjs.setTargetingForGPTAsync(adUnitCodes); googletag.pubads().refresh(); }); }); diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 752cc3e2f08..c436c3f0d9a 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -251,12 +251,12 @@ function buildVideoImp(bidRequest, imp) { if (imp.video && videoParams?.context === 'instream') { imp.video.placement = imp.video.placement || 1; imp.video.plcmt = imp.video.plcmt || 1; - imp.video.playbackmethod = imp.video.playbackmethod || 1; + imp.video.playbackmethod = imp.video.playbackmethod || [1]; } if (imp.video && videoParams?.context === 'outstream') { imp.video.placement = imp.video.placement || 4; imp.video.plcmt = imp.video.plcmt || 4; - imp.video.playbackmethod = imp.video.playbackmethod || 2; + imp.video.playbackmethod = imp.video.playbackmethod || [2]; } return { ...imp }; From 21254cb76677ab3ccfb56a65dcbc9c7063a37d45 Mon Sep 17 00:00:00 2001 From: changjun Date: Wed, 18 Oct 2023 18:35:11 +0900 Subject: [PATCH 07/13] update test case (Your tests failed on CircleCI) --- test/spec/modules/tpmnBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index e9c4ad207f0..a896aff775b 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -242,7 +242,7 @@ describe('tpmnAdapterTests', function () { const videoBidWithParams = utils.deepClone(VIDEO_BID); const bidderVideoParams = { api: [1, 2], - mimes: ['video/mp4'], + mimes: ['video/mp4', 'video/x-flv'], playbackmethod: [3, 4], protocols: [5, 6], placement: 1, From 1901ef13355d1ad2d017644728da6daf52ee5d64 Mon Sep 17 00:00:00 2001 From: changjun Date: Wed, 18 Oct 2023 19:55:41 +0900 Subject: [PATCH 08/13] Revert "remove VIDEO_ORTB_PARAMS, BANNER_ORTB_PARAMS" This reverts commit 2699115a76ed891ec7f911ad924206b399790ad9. --- modules/tpmnBidAdapter.js | 49 +++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index c436c3f0d9a..8130d7db032 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -14,6 +14,27 @@ const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; // const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder'; const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder'; const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe'; +const VIDEO_ORTB_PARAMS = [ + 'mimes', + 'minduration', + 'maxduration', + 'placement', + 'protocols', + 'startdelay', + 'skip', + 'skipafter', + 'minbitrate', + 'maxbitrate', + 'delivery', + 'playbackmethod', + 'api', + 'linearity', + 'battr', + 'plcmt' +]; +const BANNER_ORTB_PARAMS = [ + 'battr' +]; export const VIDEO_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; export const ADAPTER_VERSION = '2.0'; @@ -162,18 +183,6 @@ const CONVERTER = ortbConverter({ } } return bidResponse; - }, - overrides: { - imp: { - video(orig, imp, bidRequest, context) { - let videoParams = bidRequest.mediaTypes[VIDEO]; - if (videoParams) { - videoParams = Object.assign({}, videoParams, bidRequest.params.video); - bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} - } - orig(imp, bidRequest, context); - } - } } }); @@ -244,9 +253,11 @@ function buildVideoImp(bidRequest, imp) { utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); } - if (videoParams.hasOwnProperty('battr')) { - utils.deepSetValue(imp, `video.battr`, videoParams['battr']); - } + VIDEO_ORTB_PARAMS.forEach((param) => { + if (videoParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `video.${param}`, videoParams[param]); + } + }); if (imp.video && videoParams?.context === 'instream') { imp.video.placement = imp.video.placement || 1; @@ -275,9 +286,11 @@ function buildBannerImp(bidRequest, imp) { utils.deepSetValue(imp, 'banner.h', sizes[0][1]); } - if (bannerParams.hasOwnProperty('battr')) { - utils.deepSetValue(imp, `banner.battr`, bannerParams['battr']); - } + BANNER_ORTB_PARAMS.forEach((param) => { + if (bannerParams.hasOwnProperty(param)) { + utils.deepSetValue(imp, `banner.${param}`, bannerParams[param]); + } + }); return { ...imp }; } From f80a73026101f9d084ca6802a93a53b8e65304f5 Mon Sep 17 00:00:00 2001 From: changjun Date: Mon, 30 Oct 2023 10:04:00 +0900 Subject: [PATCH 09/13] Remove unnecessary code to meet guidelines. --- modules/tpmnBidAdapter.js | 157 +++++------------------ test/spec/modules/tpmnBidAdapter_spec.js | 133 +++++++++++++++---- 2 files changed, 140 insertions(+), 150 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 8130d7db032..9df8fa0bb49 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -14,25 +14,7 @@ const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; // const BIDDER_ENDPOINT_URL = 'http://localhost:8081/ortb/pbjs_bidder'; const BIDDER_ENDPOINT_URL = 'https://gat.tpmn.io/ortb/pbjs_bidder'; const IFRAMESYNC = 'https://gat.tpmn.io/sync/iframe'; -const VIDEO_ORTB_PARAMS = [ - 'mimes', - 'minduration', - 'maxduration', - 'placement', - 'protocols', - 'startdelay', - 'skip', - 'skipafter', - 'minbitrate', - 'maxbitrate', - 'delivery', - 'playbackmethod', - 'api', - 'linearity', - 'battr', - 'plcmt' -]; -const BANNER_ORTB_PARAMS = [ +const COMMON_PARAMS = [ 'battr' ]; @@ -80,18 +62,22 @@ function isValidVideoRequest(bid) { } function buildRequests(validBidRequests, bidderRequest) { - if (validBidRequests.length === 0 || !bidderRequest) return []; - let bannerBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.banner')); - let videoBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.video')); let requests = []; + try { + if (validBidRequests.length === 0 || !bidderRequest) return []; + let bannerBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.banner')); + let videoBids = validBidRequests.filter(bid => utils.deepAccess(bid, 'mediaTypes.video')); - bannerBids.forEach(bid => { - requests.push(createRequest([bid], bidderRequest, BANNER)); - }); + bannerBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, BANNER)); + }); - videoBids.forEach(bid => { - requests.push(createRequest([bid], bidderRequest, VIDEO)); - }); + videoBids.forEach(bid => { + requests.push(createRequest([bid], bidderRequest, VIDEO)); + }); + } catch (err) { + utils.logWarn('buildRequests', err); + } return requests; } @@ -100,19 +86,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { const rtbData = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) const bid = bidRequests.find((b) => b.params.inventoryId) - // console.log('createRequest : ', mediaType, bid); - - if (!rtbData.site) rtbData.site = {} - rtbData.site = createSite(bidderRequest.refererInfo) - if (bidderRequest.gdprConsent) { - if (!rtbData.user) rtbData.user = {}; - if (!rtbData.user.ext) rtbData.user.ext = {}; - if (!rtbData.regs) rtbData.regs = {}; - if (!rtbData.regs.ext) rtbData.regs.ext = {}; - rtbData.user.ext.consent = bidderRequest.gdprConsent.consentString; - rtbData.regs.ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } if (bid.params.inventoryId) rtbData.ext = {}; if (bid.params.inventoryId) rtbData.ext.inventoryId = bid.params.inventoryId @@ -145,7 +119,7 @@ function interpretResponse(response, request) { registerBidder(spec); -const CONVERTER = ortbConverter({ +export const CONVERTER = ortbConverter({ context: { netRevenue: true, ttl: DEFAULT_BID_TTL, @@ -153,16 +127,16 @@ const CONVERTER = ortbConverter({ }, imp(buildImp, bidRequest, context) { let imp = buildImp(bidRequest, context); - imp.secure = Number(window.location.protocol === 'https:'); if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; } - if (bidRequest.mediaTypes[VIDEO]) { - imp = buildVideoImp(bidRequest, imp); - } else if (bidRequest.mediaTypes[BANNER]) { - imp = buildBannerImp(bidRequest, imp); - } - + [VIDEO, BANNER].forEach(namespace => { + COMMON_PARAMS.forEach(param => { + if (bidRequest.params.hasOwnProperty(param)) { + utils.deepSetValue(imp, `${namespace}.${param}`, bidRequest.params[param]) + } + }) + }) return imp; }, bidResponse(buildBidResponse, bid, context) { @@ -183,6 +157,18 @@ const CONVERTER = ortbConverter({ } } return bidResponse; + }, + overrides: { + imp: { + video(orig, imp, bidRequest, context) { + let videoParams = bidRequest.mediaTypes[VIDEO]; + if (videoParams) { + videoParams = Object.assign({}, videoParams, bidRequest.params.video); + bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} + } + orig(imp, bidRequest, context); + }, + }, } }); @@ -220,81 +206,6 @@ function handleOutstreamRendererEvents(bid, id, eventName) { bid.renderer.handleVideoEvent({ id, eventName }); } -/** - * Creates site description object - */ -function createSite(refInfo) { - let url = utils.parseUrl(refInfo.page || ''); - let site = { - 'domain': url.hostname, - 'page': url.protocol + '://' + url.hostname + url.pathname - }; - - if (refInfo.ref) { - site.ref = refInfo.ref - } - let keywords = document.getElementsByTagName('meta')['keywords']; - if (keywords && keywords.content) { - site.keywords = keywords.content; - } - return site; -} - -function buildVideoImp(bidRequest, imp) { - const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); - const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); - - const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; - - const videoSizes = (videoAdUnitParams && videoAdUnitParams.playerSize) || []; - - if (videoSizes && videoSizes.length > 0) { - utils.deepSetValue(imp, 'video.w', videoSizes[0][0]); - utils.deepSetValue(imp, 'video.h', videoSizes[0][1]); - } - - VIDEO_ORTB_PARAMS.forEach((param) => { - if (videoParams.hasOwnProperty(param)) { - utils.deepSetValue(imp, `video.${param}`, videoParams[param]); - } - }); - - if (imp.video && videoParams?.context === 'instream') { - imp.video.placement = imp.video.placement || 1; - imp.video.plcmt = imp.video.plcmt || 1; - imp.video.playbackmethod = imp.video.playbackmethod || [1]; - } - if (imp.video && videoParams?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; - imp.video.plcmt = imp.video.plcmt || 4; - imp.video.playbackmethod = imp.video.playbackmethod || [2]; - } - - return { ...imp }; -} - -function buildBannerImp(bidRequest, imp) { - const bannerAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}`, {}); - const bannerBidderParams = utils.deepAccess(bidRequest, `params.${BANNER}`, {}); - - const bannerParams = { ...bannerAdUnitParams, ...bannerBidderParams }; - - let sizes = bidRequest.mediaTypes.banner.sizes; - - if (sizes) { - utils.deepSetValue(imp, 'banner.w', sizes[0][0]); - utils.deepSetValue(imp, 'banner.h', sizes[0][1]); - } - - BANNER_ORTB_PARAMS.forEach((param) => { - if (bannerParams.hasOwnProperty(param)) { - utils.deepSetValue(imp, `banner.${param}`, bannerParams[param]); - } - }); - - return { ...imp }; -} - function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { const syncArr = []; if (syncOptions.iframeEnabled) { diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index a896aff775b..c9994353a30 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -4,6 +4,9 @@ import { generateUUID } from '../../../src/utils.js'; import { expect } from 'chai'; import * as utils from 'src/utils'; import * as sinon from 'sinon'; +import 'modules/consentManagement.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; +import {mockGdprConsent} from '../../helpers/consentData.js'; const BIDDER_CODE = 'tpmn'; const BANNER_BID = { @@ -172,12 +175,12 @@ describe('tpmnAdapterTests', function () { it('should have gdpr data if applicable', function () { const bid = utils.deepClone(BANNER_BID); - const req = Object.assign({}, BIDDER_REQUEST, { + const req = syncAddFPDToBidderRequest(Object.assign({}, BIDDER_REQUEST, { gdprConsent: { consentString: 'consentString', gdprApplies: true, } - }); + })); let request = spec.buildRequests([bid], req)[0]; const payload = request.data; @@ -188,7 +191,7 @@ describe('tpmnAdapterTests', function () { it('should properly forward ORTB blocking params', function () { let bid = utils.deepClone(BANNER_BID); bid = utils.mergeDeep(bid, { - params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'] }, + params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'], battr: [1] }, mediaTypes: { banner: { battr: [1] } } }); @@ -208,9 +211,9 @@ describe('tpmnAdapterTests', function () { const [request] = spec.buildRequests([bid], BIDDER_REQUEST); const requestData = request.data; - - expect(requestData.imp[0].banner.w).to.equal(300); - expect(requestData.imp[0].banner.h).to.equal(250); + // expect(requestData.imp[0].banner).to.equal(null); + expect(requestData.imp[0].banner.format[0].w).to.equal(300); + expect(requestData.imp[0].banner.format[0].h).to.equal(250); }); it('should create request data', function () { @@ -238,35 +241,111 @@ describe('tpmnAdapterTests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should use bidder video params if they are set', () => { - const videoBidWithParams = utils.deepClone(VIDEO_BID); - const bidderVideoParams = { - api: [1, 2], - mimes: ['video/mp4', 'video/x-flv'], - playbackmethod: [3, 4], - protocols: [5, 6], + it('when mediaType is Video - check', () => { + const bid = utils.deepClone(VIDEO_BID); + const check = { + w: 1024, + h: 768, + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + api: [1, 2, 4, 6], + protocols: [3, 4, 7, 8, 10], placement: 1, - plcmt: 1, minduration: 0, maxduration: 60, - w: 1024, - h: 768, - startdelay: 0 + startdelay: 0, + plcmt: 1 }; + setTimeout(() => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + const requests = spec.buildRequests([bid], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video).to.deep.include({...check}); + done(); + }, 200); + }); - videoBidWithParams.params.video = bidderVideoParams; + it('when mediaType New Video', () => { + const NEW_VIDEO_BID = { + 'bidder': 'tpmn', + 'params': {'inventoryId': 2, 'bidFloor': 2}, + 'userId': {'pubcid': '88a49ee6-beeb-4dd6-92ac-3b6060e127e1'}, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': ['video/mp4'], + 'playerSize': [[1024, 768]], + 'playbackmethod': [2, 4, 6], + 'protocols': [3, 4], + 'api': [1, 2, 3, 6], + 'placement': 1, + 'minduration': 0, + 'maxduration': 30, + 'startdelay': 0, + 'skip': 1, + 'plcmt': 4 + } + }, + }; - const requests = spec.buildRequests([videoBidWithParams], BIDDER_REQUEST); - const request = requests[0].data; + const check = { + w: 1024, + h: 768, + mimes: [ 'video/mp4' ], + playbackmethod: [2, 4, 6], + api: [1, 2, 3, 6], + protocols: [3, 4], + placement: 1, + minduration: 0, + maxduration: 30, + startdelay: 0, + skip: 1, + plcmt: 4 + } - expect(request.imp[0]).to.deep.include({ - video: { - ...bidderVideoParams, - w: videoBidWithParams.mediaTypes.video.playerSize[0][0], - h: videoBidWithParams.mediaTypes.video.playerSize[0][1], - }, - }); + setTimeout(() => { + expect(spec.isBidRequestValid(NEW_VIDEO_BID)).to.equal(true); + let requests = spec.buildRequests([NEW_VIDEO_BID], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video.w).to.equal(check.w); + expect(request.imp[0].video.h).to.equal(check.h); + expect(request.imp[0].video.placement).to.equal(check.placement); + expect(request.imp[0].video.minduration).to.equal(check.minduration); + expect(request.imp[0].video.maxduration).to.equal(check.maxduration); + expect(request.imp[0].video.startdelay).to.equal(check.startdelay); + expect(request.imp[0].video.skip).to.equal(check.skip); + expect(request.imp[0].video.plcmt).to.equal(check.plcmt); + expect(request.imp[0].video.mimes).to.deep.have.same.members(check.mimes); + expect(request.imp[0].video.playbackmethod).to.deep.have.same.members(check.playbackmethod); + expect(request.imp[0].video.api).to.deep.have.same.members(check.api); + expect(request.imp[0].video.protocols).to.deep.have.same.members(check.protocols); + done(); + }, 200); }); + + // it('should use bidder video params if they are set', () => { + // const bid = utils.deepClone(VIDEO_BID); + // const check = { + // api: [1, 2], + // mimes: ['video/mp4', 'video/x-flv'], + // playbackmethod: [3, 4], + // protocols: [5, 6], + // placement: 1, + // plcmt: 4, + // minduration: 0, + // maxduration: 30, + // startdelay: 0 + // }; + // bid.mediaTypes.video = check; + // bid.mediaTypes.context = 'outstream'; + // setTimeout(() => { + // expect(spec.isBidRequestValid(bid)).to.equal(true); + // const requests = spec.buildRequests([bid], BIDDER_REQUEST); + // const request = requests[0].data; + // expect(request.imp[0].video).to.deep.include({...check}); + // done(); + // }, 200); + // }); }); }); From 97b1761fe686380d4fa20fbe24e6025fa5d22907 Mon Sep 17 00:00:00 2001 From: changjun Date: Mon, 30 Oct 2023 17:48:29 +0900 Subject: [PATCH 10/13] Remove unnecessary code to meet guidelines. --- modules/tpmnBidAdapter.js | 2 +- test/spec/modules/tpmnBidAdapter_spec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 9df8fa0bb49..e486109ce0b 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -119,7 +119,7 @@ function interpretResponse(response, request) { registerBidder(spec); -export const CONVERTER = ortbConverter({ +const CONVERTER = ortbConverter({ context: { netRevenue: true, ttl: DEFAULT_BID_TTL, diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index c9994353a30..3220c5ecb5c 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -241,7 +241,7 @@ describe('tpmnAdapterTests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('when mediaType is Video - check', () => { + it('when mediaType is Video - check', (done) => { const bid = utils.deepClone(VIDEO_BID); const check = { w: 1024, @@ -265,7 +265,7 @@ describe('tpmnAdapterTests', function () { }, 200); }); - it('when mediaType New Video', () => { + it('when mediaType New Video', (done) => { const NEW_VIDEO_BID = { 'bidder': 'tpmn', 'params': {'inventoryId': 2, 'bidFloor': 2}, From 4a856504978da242a9b6c8506b5d528d6ff62292 Mon Sep 17 00:00:00 2001 From: changjun Date: Tue, 31 Oct 2023 14:16:30 +0900 Subject: [PATCH 11/13] fix Build fail --- modules/tpmnBidAdapter.md | 4 +- test/spec/modules/tpmnBidAdapter_spec.js | 193 ++++++++++++----------- 2 files changed, 101 insertions(+), 96 deletions(-) diff --git a/modules/tpmnBidAdapter.md b/modules/tpmnBidAdapter.md index 6c915b2417c..3b016d7e5b2 100644 --- a/modules/tpmnBidAdapter.md +++ b/modules/tpmnBidAdapter.md @@ -75,7 +75,7 @@ We support the following OpenRTB params that can be specified in `mediaTypes.vid | Name | Scope | Description | Example | Type | | --------- | ------------| ----------------------------------------------------------------- | --------- | --------- | | `context` | required | instream or outstream |'outstream' | string | -| `playerSize` | required | Avalaible sizes supported for video ad unit. | [300, 250] | [Integer, Integer] | +| `playerSize` | required | Avalaible sizes supported for video ad unit. | [[300, 250]] | [Integer, Integer] | | `mimes` | required | List of content MIME types supported by the player. | ['video/mp4']| [String]| | `protocols` | optional | Supported video bid response protocol values. | [2,3,5,6] | [integers]| | `api` | optional | Supported API framework values. | [2] | [integers] | @@ -99,7 +99,7 @@ We support the following OpenRTB params that can be specified in `mediaTypes.vid video: { context: 'instream', // required mimes: ['video/mp4'], // required - playerSize: [ 640, 480 ], // required + playerSize: [[ 640, 480 ]], // required ... // skippable, startdelay, battr.. // optional } }, diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index 3220c5ecb5c..505bc9d878f 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -227,83 +227,85 @@ describe('tpmnAdapterTests', function () { }); context('when mediaType is video', function () { - it('should return false when there is no video in mediaTypes', () => { - const bid = utils.deepClone(VIDEO_BID); - delete bid.mediaTypes.video; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + if (FEATURES.VIDEO) { + it('should return false when there is no video in mediaTypes', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video; - it('should reutrn false if player size is not set', () => { - const bid = utils.deepClone(VIDEO_BID); - delete bid.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + } - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); + if (FEATURES.VIDEO) { + it('should reutrn false if player size is not set', () => { + const bid = utils.deepClone(VIDEO_BID); + delete bid.mediaTypes.video.playerSize; - it('when mediaType is Video - check', (done) => { - const bid = utils.deepClone(VIDEO_BID); - const check = { - w: 1024, - h: 768, - mimes: ['video/mp4'], - playbackmethod: [2, 4, 6], - api: [1, 2, 4, 6], - protocols: [3, 4, 7, 8, 10], - placement: 1, - minduration: 0, - maxduration: 60, - startdelay: 0, - plcmt: 1 - }; - setTimeout(() => { + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + } + if (FEATURES.VIDEO) { + it('when mediaType is Video - check', () => { + const bid = utils.deepClone(VIDEO_BID); + const check = { + w: 1024, + h: 768, + mimes: ['video/mp4'], + playbackmethod: [2, 4, 6], + api: [1, 2, 4, 6], + protocols: [3, 4, 7, 8, 10], + placement: 1, + minduration: 0, + maxduration: 60, + startdelay: 0, + plcmt: 1 + }; expect(spec.isBidRequestValid(bid)).to.equal(true); const requests = spec.buildRequests([bid], BIDDER_REQUEST); const request = requests[0].data; expect(request.imp[0].video).to.deep.include({...check}); - done(); - }, 200); - }); + }); + } - it('when mediaType New Video', (done) => { - const NEW_VIDEO_BID = { - 'bidder': 'tpmn', - 'params': {'inventoryId': 2, 'bidFloor': 2}, - 'userId': {'pubcid': '88a49ee6-beeb-4dd6-92ac-3b6060e127e1'}, - 'mediaTypes': { - 'video': { - 'context': 'outstream', - 'mimes': ['video/mp4'], - 'playerSize': [[1024, 768]], - 'playbackmethod': [2, 4, 6], - 'protocols': [3, 4], - 'api': [1, 2, 3, 6], - 'placement': 1, - 'minduration': 0, - 'maxduration': 30, - 'startdelay': 0, - 'skip': 1, - 'plcmt': 4 - } - }, - }; - - const check = { - w: 1024, - h: 768, - mimes: [ 'video/mp4' ], - playbackmethod: [2, 4, 6], - api: [1, 2, 3, 6], - protocols: [3, 4], - placement: 1, - minduration: 0, - maxduration: 30, - startdelay: 0, - skip: 1, - plcmt: 4 - } + if (FEATURES.VIDEO) { + it('when mediaType New Video', () => { + const NEW_VIDEO_BID = { + 'bidder': 'tpmn', + 'params': {'inventoryId': 2, 'bidFloor': 2}, + 'userId': {'pubcid': '88a49ee6-beeb-4dd6-92ac-3b6060e127e1'}, + 'mediaTypes': { + 'video': { + 'context': 'outstream', + 'mimes': ['video/mp4'], + 'playerSize': [[1024, 768]], + 'playbackmethod': [2, 4, 6], + 'protocols': [3, 4], + 'api': [1, 2, 3, 6], + 'placement': 1, + 'minduration': 0, + 'maxduration': 30, + 'startdelay': 0, + 'skip': 1, + 'plcmt': 4 + } + }, + }; + + const check = { + w: 1024, + h: 768, + mimes: [ 'video/mp4' ], + playbackmethod: [2, 4, 6], + api: [1, 2, 3, 6], + protocols: [3, 4], + placement: 1, + minduration: 0, + maxduration: 30, + startdelay: 0, + skip: 1, + plcmt: 4 + } - setTimeout(() => { expect(spec.isBidRequestValid(NEW_VIDEO_BID)).to.equal(true); let requests = spec.buildRequests([NEW_VIDEO_BID], BIDDER_REQUEST); const request = requests[0].data; @@ -319,33 +321,36 @@ describe('tpmnAdapterTests', function () { expect(request.imp[0].video.playbackmethod).to.deep.have.same.members(check.playbackmethod); expect(request.imp[0].video.api).to.deep.have.same.members(check.api); expect(request.imp[0].video.protocols).to.deep.have.same.members(check.protocols); - done(); - }, 200); - }); + }); + } + + if (FEATURES.VIDEO) { + it('should use bidder video params if they are set', () => { + let bid = utils.deepClone(VIDEO_BID); + const check = { + api: [1, 2], + mimes: ['video/mp4', 'video/x-flv'], + playbackmethod: [3, 4], + protocols: [5, 6], + placement: 1, + plcmt: 1, + minduration: 0, + maxduration: 30, + startdelay: 0, + w: 640, + h: 480 + + }; + bid.mediaTypes.video = {...check}; + bid.mediaTypes.video.context = 'instream'; + bid.mediaTypes.video.playerSize = [[640, 480]]; - // it('should use bidder video params if they are set', () => { - // const bid = utils.deepClone(VIDEO_BID); - // const check = { - // api: [1, 2], - // mimes: ['video/mp4', 'video/x-flv'], - // playbackmethod: [3, 4], - // protocols: [5, 6], - // placement: 1, - // plcmt: 4, - // minduration: 0, - // maxduration: 30, - // startdelay: 0 - // }; - // bid.mediaTypes.video = check; - // bid.mediaTypes.context = 'outstream'; - // setTimeout(() => { - // expect(spec.isBidRequestValid(bid)).to.equal(true); - // const requests = spec.buildRequests([bid], BIDDER_REQUEST); - // const request = requests[0].data; - // expect(request.imp[0].video).to.deep.include({...check}); - // done(); - // }, 200); - // }); + expect(spec.isBidRequestValid(bid)).to.equal(true); + const requests = spec.buildRequests([bid], BIDDER_REQUEST); + const request = requests[0].data; + expect(request.imp[0].video).to.deep.include({...check}); + }); + } }); }); From 6ca428c56f201017401d7f61a423120084930b8e Mon Sep 17 00:00:00 2001 From: changjun Date: Wed, 1 Nov 2023 09:45:19 +0900 Subject: [PATCH 12/13] delete logging code --- modules/tpmnBidAdapter.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index e486109ce0b..61f8a4c98af 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -142,12 +142,6 @@ const CONVERTER = ortbConverter({ bidResponse(buildBidResponse, bid, context) { const {bidRequest} = context; const bidResponse = buildBidResponse(bid, context); - - utils.logWarn('Building bidResponse'); - utils.logWarn('bid', bid); - utils.logWarn('bidRequest', bidRequest); - utils.logWarn('bidResponse', bidResponse); - if (bidResponse.mediaType === BANNER) { bidResponse.ad = bid.adm; } else if (bidResponse.mediaType === VIDEO) { From 6187d7a33dcd8c27995c5e35c4b7eca2f4ec204f Mon Sep 17 00:00:00 2001 From: yeongjaeju Date: Thu, 30 Nov 2023 10:00:12 +0900 Subject: [PATCH 13/13] Remove OptionsObject(Credentials information needs to be sent.) --- modules/tpmnBidAdapter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/tpmnBidAdapter.js b/modules/tpmnBidAdapter.js index 61f8a4c98af..89951a99f12 100644 --- a/modules/tpmnBidAdapter.js +++ b/modules/tpmnBidAdapter.js @@ -108,8 +108,7 @@ function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', url: BIDDER_ENDPOINT_URL + '?v=' + ADAPTER_VERSION, - data: rtbData, - options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + data: rtbData } }