From 61b37d9741a0f8699cca6060f0fb154faba0a48e Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Thu, 5 Mar 2020 14:03:21 -0600 Subject: [PATCH 1/6] prebid adatper for windtalker --- modules/windtalkerBidAdapter.js | 333 ++++++++++++++++++++++++++++++++ modules/windtalkerBidAdapter.md | 89 +++++++++ 2 files changed, 422 insertions(+) create mode 100644 modules/windtalkerBidAdapter.js create mode 100644 modules/windtalkerBidAdapter.md diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js new file mode 100644 index 00000000000..74bc3a9c7fd --- /dev/null +++ b/modules/windtalkerBidAdapter.js @@ -0,0 +1,333 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; +import includes from 'core-js/library/fn/array/includes'; + +const NATIVE_DEFAULTS = { + TITLE_LEN: 100, + DESCR_LEN: 200, + SPONSORED_BY_LEN: 50, + IMG_MIN: 150, + ICON_MIN: 50, +}; +const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; +const VIDEO_TARGETING = ['mimes', 'skippable', 'playback_method', 'protocols', 'api']; +const DEFAULT_PROTOCOLS = [2, 3, 5, 6]; +const DEFAULT_APIS = [1, 2]; + +export const spec = { + + code: 'windtalker', + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + + isBidRequestValid: bid => ( + !!(bid && bid.params && bid.params.pubId && bid.params.placementId) + ), + buildRequests: (bidRequests, bidderRequest) => { + const request = { + id: bidRequests[0].bidderRequestId, + at: 2, + imp: bidRequests.map(slot => impression(slot)), + site: site(bidRequests, bidderRequest), + app: app(bidRequests), + device: device(bidRequests), + }; + applyGdpr(bidderRequest, request); + return { + method: 'POST', + url: 'https://windtalkerdisplay.hb.adp3.net/', + data: JSON.stringify(request), + }; + }, + interpretResponse: (response, request) => ( + bidResponseAvailable(request, response.body) + ), +}; + +function bidResponseAvailable(bidRequest, bidResponse) { + const idToImpMap = {}; + const idToBidMap = {}; + const ortbRequest = parse(bidRequest.data); + ortbRequest.imp.forEach(imp => { + idToImpMap[imp.id] = imp; + }); + if (bidResponse) { + bidResponse.seatbid.forEach(seatBid => seatBid.bid.forEach(bid => { + idToBidMap[bid.impid] = bid; + })); + } + const bids = []; + Object.keys(idToImpMap).forEach(id => { + if (idToBidMap[id]) { + const bid = {}; + bid.requestId = id; + bid.adId = id; + bid.creativeId = id; + bid.cpm = idToBidMap[id].price; + bid.currency = bidResponse.cur; + bid.ttl = 360; + bid.netRevenue = true; + if (idToImpMap[id]['native']) { + bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); + let nurl = idToBidMap[id].nurl; + nurl = nurl.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + nurl = nurl.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid['native']['impressionTrackers'] = [nurl]; + bid.mediaType = 'native'; + } else if (idToImpMap[id]['video']) { + bid.vastUrl = idToBidMap[id].adm; + bid.vastUrl = bid.vastUrl.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.crid = idToBidMap[id].crid; + bid.width = idToImpMap[id].video.w; + bid.height = idToImpMap[id].video.h; + bid.mediaType = 'video'; + } else if (idToImpMap[id]['banner']) { + bid.ad = idToBidMap[id].adm; + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_IMP_ID(%7D|\})/gi, idToBidMap[id].impid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_AD_ID(%7D|\})/gi, idToBidMap[id].adid); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); + bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); + bid.width = idToImpMap[id].banner.w; + bid.height = idToImpMap[id].banner.h; + bid.mediaType = 'banner'; + } + bids.push(bid); + } + }); + return bids; +} +function impression(slot) { + return { + id: slot.bidId, + secure: window.location.protocol === 'https:' ? 1 : 0, + 'banner': banner(slot), + 'native': nativeImpression(slot), + 'video': videoImpression(slot), + bidfloor: slot.params.bidFloor || '0.000001', + tagid: slot.params.placementId.toString(), + }; +} + +function getSizes(slot) { + if (slot.params.size) { + const size = slot.params.size.toUpperCase().split('X'); + return { + width: parseInt(size[0]), + height: parseInt(size[1]), + }; + } + return { + width: 1, + height: 1, + }; +} + +function banner(slot) { + if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { + const sizes = getSizes(slot); + return { + w: sizes.width, + h: sizes.height, + }; + } + return null; +} + +function videoImpression(slot) { + if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { + const sizes = getSizes(slot); + const video = { + w: sizes.width, + h: sizes.height, + mimes: DEFAULT_MIMES, + protocols: DEFAULT_PROTOCOLS, + api: DEFAULT_APIS, + }; + if (slot.params.video) { + Object.keys(slot.params.video).filter(param => includes(VIDEO_TARGETING, param)).forEach(param => video[param] = slot.params.video[param]); + } + return video; + } + return null; +} + +function nativeImpression(slot) { + if (slot.mediaType === 'native' || utils.deepAccess(slot, 'mediaTypes.native')) { + const assets = []; + addAsset(assets, titleAsset(1, slot.nativeParams.title, NATIVE_DEFAULTS.TITLE_LEN)); + addAsset(assets, dataAsset(2, slot.nativeParams.body, 2, NATIVE_DEFAULTS.DESCR_LEN)); + addAsset(assets, dataAsset(3, slot.nativeParams.sponsoredBy, 1, NATIVE_DEFAULTS.SPONSORED_BY_LEN)); + addAsset(assets, imageAsset(4, slot.nativeParams.icon, 1, NATIVE_DEFAULTS.ICON_MIN, NATIVE_DEFAULTS.ICON_MIN)); + addAsset(assets, imageAsset(5, slot.nativeParams.image, 3, NATIVE_DEFAULTS.IMG_MIN, NATIVE_DEFAULTS.IMG_MIN)); + return { + request: JSON.stringify({ assets }), + ver: '1.1', + }; + } + return null; +} + +function addAsset(assets, asset) { + if (asset) { + assets.push(asset); + } +} + +function titleAsset(id, params, defaultLen) { + if (params) { + return { + id, + required: params.required ? 1 : 0, + title: { + len: params.len || defaultLen, + }, + }; + } + return null; +} + +function imageAsset(id, params, type, defaultMinWidth, defaultMinHeight) { + return params ? { + id, + required: params.required ? 1 : 0, + img: { + type, + wmin: params.wmin || defaultMinWidth, + hmin: params.hmin || defaultMinHeight, + } + } : null; +} + +function dataAsset(id, params, type, defaultLen) { + return params ? { + id, + required: params.required ? 1 : 0, + data: { + type, + len: params.len || defaultLen, + } + } : null; +} + +function site(bidRequests, bidderRequest) { + const pubId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.pubId : '0'; + const siteId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.siteId : '0'; + const appParams = bidRequests[0].params.app; + if (!appParams) { + return { + publisher: { + id: pubId.toString(), + domain: getDomainFromUrl(bidderRequest.refererInfo.canonicalUrl || bidderRequest.refererInfo.referer), + }, + id: siteId.toString(), + ref: bidderRequest.refererInfo.referer, + page: bidderRequest.refererInfo.canonicalUrl || bidderRequest.refererInfo.referer, + } + } + return null; +} + +function app(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const appParams = bidderRequest[0].params.app; + if (appParams) { + return { + publisher: { + id: pubId.toString(), + }, + id: appParams.id, + name: appParams.name, + bundle: appParams.bundle, + storeurl: appParams.storeUrl, + domain: appParams.domain, + } + } + return null; +} + +function device(bidderRequest) { + const lat = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.latitude : ''; + const lon = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.longitude : ''; + const ifa = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.ifa : ''; + return { + dnt: utils.getDNT() ? 1 : 0, + ua: navigator.userAgent, + language: (navigator.language || navigator.browserLanguage || navigator.userLanguage || navigator.systemLanguage), + w: (window.screen.width || window.innerWidth), + h: (window.screen.height || window.innerHeigh), + geo: { + lat: lat, + lon: lon, + }, + ifa: ifa, + }; +} + +function parse(rawResponse) { + try { + if (rawResponse) { + return JSON.parse(rawResponse); + } + } catch (ex) { + utils.logError('windtalker.parse', 'ERROR', ex); + } + return null; +} + +function applyGdpr(bidderRequest, ortbRequest) { + if (bidderRequest && bidderRequest.gdprConsent) { + ortbRequest.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + ortbRequest.user = { ext: { consent: bidderRequest.gdprConsent.consentString } }; + } +} + +function nativeResponse(imp, bid) { + if (imp['native']) { + const nativeAd = parse(bid.adm); + const keys = {}; + keys.image = {}; + keys.icon = {}; + if (nativeAd && nativeAd['native'] && nativeAd['native'].assets) { + nativeAd['native'].assets.forEach(asset => { + keys.title = asset.title ? asset.title.text : keys.title; + keys.body = asset.data && asset.id === 2 ? asset.data.value : keys.body; + keys.sponsoredBy = asset.data && asset.id === 3 ? asset.data.value : keys.sponsoredBy; + keys.icon.url = asset.img && asset.id === 4 ? asset.img.url : keys.icon.url; + keys.icon.width = asset.img && asset.id === 4 ? asset.img.w : keys.icon.width; + keys.icon.height = asset.img && asset.id === 4 ? asset.img.h : keys.icon.height; + keys.image.url = asset.img && asset.id === 5 ? asset.img.url : keys.image.url; + keys.image.width = asset.img && asset.id === 5 ? asset.img.w : keys.image.width; + keys.image.height = asset.img && asset.id === 5 ? asset.img.h : keys.image.height; + }); + if (nativeAd['native'].link) { + keys.clickUrl = encodeURIComponent(nativeAd['native'].link.url); + } + return keys; + } + } + return null; +} + +function getDomainFromUrl(url) { + if (typeof window.URL === 'function') { + return (new window.URL(url)).hostname; + } else { + var domain = url; + + if (url.indexOf('//') > -1) { + domain = url.split('/')[2]; + } else { + domain = url.split('/')[0]; + } + + domain = domain.split(':')[0]; + domain = domain.split('?')[0]; + + return domain; + } +} + +registerBidder(spec); diff --git a/modules/windtalkerBidAdapter.md b/modules/windtalkerBidAdapter.md new file mode 100644 index 00000000000..2a97f6f3507 --- /dev/null +++ b/modules/windtalkerBidAdapter.md @@ -0,0 +1,89 @@ +# Overview + +**Module Name**: Windtalker Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: corbin@windtalker.io + +# Description + +Connects to Windtalker demand source to fetch bids. +Banner, Native, Video formats are supported. +Please use ```windtalker``` as the bidder code. + +# Test Parameters +``` + var adUnits = [{ + code: 'dfp-native-div', + mediaType: 'native', + mediaTypes: { + native: { + title: { + required: true, + len: 75 + }, + image: { + required: true + }, + body: { + len: 200 + }, + icon: { + required: false + } + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '29521', + siteId: '26048', + placementId: '123', + bidFloor: '0.001', // optional + ifa: 'XXX-XXX', // optional + latitude: '40.712775', // optional + longitude: '-74.005973', // optional + } + }] + }, + { + code: 'dfp-banner-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ], + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '29521', + siteId: '26049', + size: '300X250', + placementId: '123', + } + }] + }, + { + code: 'dfp-video-div', + sizes: [640, 480], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [{ + bidder: 'windtalker', + params: { + pubId: '29521', + siteId: '26049', + size: '640X480', + placementId: '123', + video: { + skipppable: true, + } + } + }] + } + ]; +``` From f1c6af89ed660597ba8b0f5cdd35a542b43a2bf7 Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Thu, 5 Mar 2020 14:15:07 -0600 Subject: [PATCH 2/6] file extension for imports --- modules/windtalkerBidAdapter.js | 35 +++++++++++++++++---------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js index 74bc3a9c7fd..44d2ecbb86e 100644 --- a/modules/windtalkerBidAdapter.js +++ b/modules/windtalkerBidAdapter.js @@ -1,7 +1,7 @@ -import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; -import includes from 'core-js/library/fn/array/includes'; +import * as utils from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import includes from 'core-js/library/fn/array/includes.js'; const NATIVE_DEFAULTS = { TITLE_LEN: 100, @@ -217,6 +217,7 @@ function site(bidRequests, bidderRequest) { const siteId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.siteId : '0'; const appParams = bidRequests[0].params.app; if (!appParams) { + return { publisher: { id: pubId.toString(), @@ -312,22 +313,22 @@ function nativeResponse(imp, bid) { } function getDomainFromUrl(url) { - if (typeof window.URL === 'function') { - return (new window.URL(url)).hostname; - } else { - var domain = url; - - if (url.indexOf('//') > -1) { - domain = url.split('/')[2]; + if (typeof window.URL === 'function') { + return (new window.URL(url)).hostname; } else { - domain = url.split('/')[0]; - } + var domain = url; - domain = domain.split(':')[0]; - domain = domain.split('?')[0]; + if (url.indexOf('//') > -1) { + domain = url.split('/')[2]; + } else { + domain = url.split('/')[0]; + } - return domain; - } + domain = domain.split(':')[0]; + domain = domain.split('?')[0]; + + return domain; + } } registerBidder(spec); From 0e4ca6f8f889e10e85ce051a0a2ef6e6b5a3b34b Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Thu, 5 Mar 2020 14:18:51 -0600 Subject: [PATCH 3/6] fix whitespace in adapter --- modules/windtalkerBidAdapter.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js index 44d2ecbb86e..4adb97c8bd9 100644 --- a/modules/windtalkerBidAdapter.js +++ b/modules/windtalkerBidAdapter.js @@ -217,7 +217,6 @@ function site(bidRequests, bidderRequest) { const siteId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.siteId : '0'; const appParams = bidRequests[0].params.app; if (!appParams) { - return { publisher: { id: pubId.toString(), @@ -313,22 +312,19 @@ function nativeResponse(imp, bid) { } function getDomainFromUrl(url) { - if (typeof window.URL === 'function') { - return (new window.URL(url)).hostname; + if (typeof window.URL === 'function') { + return (new window.URL(url)).hostname; + } else { + var domain = url; + if (url.indexOf('//') > -1) { + domain = url.split('/')[2]; } else { - var domain = url; - - if (url.indexOf('//') > -1) { - domain = url.split('/')[2]; - } else { - domain = url.split('/')[0]; - } - - domain = domain.split(':')[0]; - domain = domain.split('?')[0]; - - return domain; + domain = url.split('/')[0]; } + domain = domain.split(':')[0]; + domain = domain.split('?')[0]; + return domain; + } } registerBidder(spec); From 275899c8878739a28fa8b5b9eb9a65456e65e2ca Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Mon, 9 Mar 2020 11:51:39 -0500 Subject: [PATCH 4/6] removed inital value assignment per LGTM request --- modules/windtalkerBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js index 4adb97c8bd9..55b3380a189 100644 --- a/modules/windtalkerBidAdapter.js +++ b/modules/windtalkerBidAdapter.js @@ -315,7 +315,7 @@ function getDomainFromUrl(url) { if (typeof window.URL === 'function') { return (new window.URL(url)).hostname; } else { - var domain = url; + var domain; if (url.indexOf('//') > -1) { domain = url.split('/')[2]; } else { From 6bf28c6af70d93cb1639f1349e0c411252b6728c Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Thu, 19 Mar 2020 09:15:56 -0500 Subject: [PATCH 5/6] Minorchange to md file for windtalker --- modules/windtalkerBidAdapter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/windtalkerBidAdapter.md b/modules/windtalkerBidAdapter.md index 2a97f6f3507..e1f5303b98a 100644 --- a/modules/windtalkerBidAdapter.md +++ b/modules/windtalkerBidAdapter.md @@ -65,7 +65,7 @@ Please use ```windtalker``` as the bidder code. }] }, { - code: 'dfp-video-div', + code: 'dfp-video-div-container', sizes: [640, 480], mediaTypes: { video: { From 9d97c634a67e5baf6870edbcd54689783a45fe4a Mon Sep 17 00:00:00 2001 From: Chris DeGroat Date: Fri, 3 Apr 2020 11:33:24 -0500 Subject: [PATCH 6/6] Updated windtalker adapter with tests --- modules/windtalkerBidAdapter.js | 77 ++-- modules/windtalkerBidAdapter.md | 21 +- .../spec/modules/windtalkerBidAdapter_spec.js | 348 ++++++++++++++++++ 3 files changed, 384 insertions(+), 62 deletions(-) create mode 100644 test/spec/modules/windtalkerBidAdapter_spec.js diff --git a/modules/windtalkerBidAdapter.js b/modules/windtalkerBidAdapter.js index 55b3380a189..0c0ef571954 100644 --- a/modules/windtalkerBidAdapter.js +++ b/modules/windtalkerBidAdapter.js @@ -1,6 +1,5 @@ import * as utils from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import includes from 'core-js/library/fn/array/includes.js'; const NATIVE_DEFAULTS = { @@ -18,7 +17,7 @@ const DEFAULT_APIS = [1, 2]; export const spec = { code: 'windtalker', - supportedMediaTypes: [BANNER, NATIVE, VIDEO], + supportedMediaTypes: ['banner', 'native', 'video'], isBidRequestValid: bid => ( !!(bid && bid.params && bid.params.pubId && bid.params.placementId) @@ -28,7 +27,7 @@ export const spec = { id: bidRequests[0].bidderRequestId, at: 2, imp: bidRequests.map(slot => impression(slot)), - site: site(bidRequests, bidderRequest), + site: site(bidRequests), app: app(bidRequests), device: device(bidRequests), }; @@ -90,8 +89,8 @@ function bidResponseAvailable(bidRequest, bidResponse) { bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_PRICE(%7D|\})/gi, idToBidMap[id].price); bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_CURRENCY(%7D|\})/gi, bidResponse.cur); bid.ad = bid.ad.replace(/\$(%7B|\{)AUCTION_BID_ID(%7D|\})/gi, bidResponse.bidid); - bid.width = idToImpMap[id].banner.w; - bid.height = idToImpMap[id].banner.h; + bid.width = idToBidMap[id].w; + bid.height = idToBidMap[id].h; bid.mediaType = 'banner'; } bids.push(bid); @@ -111,37 +110,31 @@ function impression(slot) { }; } -function getSizes(slot) { - if (slot.params.size) { - const size = slot.params.size.toUpperCase().split('X'); - return { - width: parseInt(size[0]), - height: parseInt(size[1]), - }; - } - return { - width: 1, - height: 1, - }; -} - function banner(slot) { if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner')) { - const sizes = getSizes(slot); - return { - w: sizes.width, - h: sizes.height, - }; + const sizes = utils.deepAccess(slot, 'mediaTypes.banner.sizes'); + if (sizes.length > 1) { + let format = []; + for (let f = 0; f < sizes.length; f++) { + format.push({'w': sizes[f][0], 'h': sizes[f][1]}); + } + return {'format': format}; + } else { + return { + w: sizes[0][0], + h: sizes[0][1] + } + } } return null; } function videoImpression(slot) { if (slot.mediaType === 'video' || utils.deepAccess(slot, 'mediaTypes.video')) { - const sizes = getSizes(slot); + const sizes = utils.deepAccess(slot, 'mediaTypes.video.playerSize'); const video = { - w: sizes.width, - h: sizes.height, + w: sizes[0][0], + h: sizes[0][1], mimes: DEFAULT_MIMES, protocols: DEFAULT_PROTOCOLS, api: DEFAULT_APIS, @@ -212,19 +205,19 @@ function dataAsset(id, params, type, defaultLen) { } : null; } -function site(bidRequests, bidderRequest) { - const pubId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.pubId : '0'; - const siteId = bidRequests && bidRequests.length > 0 ? bidRequests[0].params.siteId : '0'; - const appParams = bidRequests[0].params.app; +function site(bidderRequest) { + const pubId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.pubId : '0'; + const siteId = bidderRequest && bidderRequest.length > 0 ? bidderRequest[0].params.siteId : '0'; + const appParams = bidderRequest[0].params.app; if (!appParams) { return { publisher: { id: pubId.toString(), - domain: getDomainFromUrl(bidderRequest.refererInfo.canonicalUrl || bidderRequest.refererInfo.referer), + domain: window.location.hostname, }, id: siteId.toString(), - ref: bidderRequest.refererInfo.referer, - page: bidderRequest.refererInfo.canonicalUrl || bidderRequest.refererInfo.referer, + ref: window.top.document.referrer, + page: window.location.href, } } return null; @@ -311,20 +304,4 @@ function nativeResponse(imp, bid) { return null; } -function getDomainFromUrl(url) { - if (typeof window.URL === 'function') { - return (new window.URL(url)).hostname; - } else { - var domain; - if (url.indexOf('//') > -1) { - domain = url.split('/')[2]; - } else { - domain = url.split('/')[0]; - } - domain = domain.split(':')[0]; - domain = domain.split('?')[0]; - return domain; - } -} - registerBidder(spec); diff --git a/modules/windtalkerBidAdapter.md b/modules/windtalkerBidAdapter.md index e1f5303b98a..f7441effc47 100644 --- a/modules/windtalkerBidAdapter.md +++ b/modules/windtalkerBidAdapter.md @@ -14,7 +14,6 @@ Please use ```windtalker``` as the bidder code. ``` var adUnits = [{ code: 'dfp-native-div', - mediaType: 'native', mediaTypes: { native: { title: { @@ -35,8 +34,8 @@ Please use ```windtalker``` as the bidder code. bids: [{ bidder: 'windtalker', params: { - pubId: '29521', - siteId: '26048', + pubId: '584971', + siteId: '584971', placementId: '123', bidFloor: '0.001', // optional ifa: 'XXX-XXX', // optional @@ -50,34 +49,32 @@ Please use ```windtalker``` as the bidder code. mediaTypes: { banner: { sizes: [ - [300, 250] + [300, 250],[300,600] ], } }, bids: [{ bidder: 'windtalker', params: { - pubId: '29521', - siteId: '26049', - size: '300X250', + pubId: '584971', + siteId: '584971', placementId: '123', } }] }, { - code: 'dfp-video-div-container', - sizes: [640, 480], + code: 'dfp-video-div', mediaTypes: { video: { + playerSize: [[640, 480]], context: "instream" } }, bids: [{ bidder: 'windtalker', params: { - pubId: '29521', - siteId: '26049', - size: '640X480', + pubId: '584971', + siteId: '584971', placementId: '123', video: { skipppable: true, diff --git a/test/spec/modules/windtalkerBidAdapter_spec.js b/test/spec/modules/windtalkerBidAdapter_spec.js new file mode 100644 index 00000000000..222a7611b01 --- /dev/null +++ b/test/spec/modules/windtalkerBidAdapter_spec.js @@ -0,0 +1,348 @@ +import {expect} from 'chai'; +import {spec} from 'modules/windtalkerBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +describe('Windtalker Adapter Tests', function () { + const slotConfigs = [{ + placementCode: '/DfpAccount1/slot1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bidId: 'bid12345', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '123', + bidFloor: '0.001', + ifa: 'IFA', + latitude: '40.712775', + longitude: '-74.005973' + } + }, { + placementCode: '/DfpAccount2/slot2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + bidId: 'bid23456', + mediaType: 'banner', + params: { + pubId: '29521', + siteId: '26047', + placementId: '1234', + bidFloor: '0.000001', + } + }]; + const nativeSlotConfig = [{ + placementCode: '/DfpAccount1/slot3', + bidId: 'bid12345', + mediaType: 'native', + nativeParams: { + title: { required: true, len: 200 }, + body: {}, + image: { wmin: 100 }, + sponsoredBy: { }, + icon: { } + }, + params: { + pubId: '29521', + placementId: '123', + siteId: '26047' + } + }]; + const videoSlotConfig = [{ + placementCode: '/DfpAccount1/slot4', + mediaTypes: { + video: { + playerSize: [[640, 480]] + } + }, + bidId: 'bid12345678', + mediaType: 'video', + video: { + skippable: true + }, + params: { + pubId: '29521', + placementId: '1234567', + siteId: '26047', + } + }]; + const appSlotConfig = [{ + placementCode: '/DfpAccount1/slot5', + bidId: 'bid12345', + params: { + pubId: '29521', + placementId: '1234', + app: { + id: '1111', + name: 'app name', + bundle: 'io.windtalker.apps', + storeUrl: 'https://windtalker.io/apps', + domain: 'windtalker.io' + } + } + }]; + + it('Verify build request', function () { + const request = spec.buildRequests(slotConfigs); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // site object + expect(ortbRequest.site).to.not.equal(null); + expect(ortbRequest.site.publisher).to.not.equal(null); + expect(ortbRequest.site.publisher.id).to.equal('29521'); + expect(ortbRequest.site.ref).to.equal(window.top.document.referrer); + expect(ortbRequest.site.page).to.equal(window.location.href); + expect(ortbRequest.imp).to.have.lengthOf(2); + // device object + expect(ortbRequest.device).to.not.equal(null); + expect(ortbRequest.device.ua).to.equal(navigator.userAgent); + expect(ortbRequest.device.ifa).to.equal('IFA'); + expect(ortbRequest.device.geo.lat).to.equal('40.712775'); + expect(ortbRequest.device.geo.lon).to.equal('-74.005973'); + // slot 1 + expect(ortbRequest.imp[0].tagid).to.equal('123'); + expect(ortbRequest.imp[0].banner).to.not.equal(null); + expect(ortbRequest.imp[0].banner.w).to.equal(300); + expect(ortbRequest.imp[0].banner.h).to.equal(250); + expect(ortbRequest.imp[0].bidfloor).to.equal('0.001'); + // slot 2 + expect(ortbRequest.imp[1].tagid).to.equal('1234'); + expect(ortbRequest.imp[1].banner).to.not.equal(null); + expect(ortbRequest.imp[1].banner.w).to.equal(728); + expect(ortbRequest.imp[1].banner.h).to.equal(90); + expect(ortbRequest.imp[1].bidfloor).to.equal('0.000001'); + }); + + it('Verify parse response', function () { + const request = spec.buildRequests(slotConfigs); + const ortbRequest = JSON.parse(request.data); + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + adm: 'This is an Ad', + w: 300, + h: 250 + }] + }], + cur: 'USD' + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.adId).to.equal('bid12345'); + expect(bid.creativeId).to.equal('bid12345'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(360); + }); + + it('Verify full passback', function () { + const request = spec.buildRequests(slotConfigs); + const bids = spec.interpretResponse({ body: null }, request) + expect(bids).to.have.lengthOf(0); + }); + + it('Verify Native request', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + // native impression + expect(ortbRequest.imp[0].tagid).to.equal('123'); + const nativePart = ortbRequest.imp[0]['native']; + expect(nativePart).to.not.equal(null); + expect(nativePart.ver).to.equal('1.1'); + expect(nativePart.request).to.not.equal(null); + // native request assets + const nativeRequest = JSON.parse(ortbRequest.imp[0]['native'].request); + expect(nativeRequest).to.not.equal(null); + expect(nativeRequest.assets).to.have.lengthOf(5); + expect(nativeRequest.assets[0].id).to.equal(1); + expect(nativeRequest.assets[1].id).to.equal(2); + expect(nativeRequest.assets[2].id).to.equal(3); + expect(nativeRequest.assets[3].id).to.equal(4); + expect(nativeRequest.assets[4].id).to.equal(5); + expect(nativeRequest.assets[0].required).to.equal(1); + expect(nativeRequest.assets[0].title).to.not.equal(null); + expect(nativeRequest.assets[0].title.len).to.equal(200); + expect(nativeRequest.assets[1].title).to.be.undefined; + expect(nativeRequest.assets[1].data).to.not.equal(null); + expect(nativeRequest.assets[1].data.type).to.equal(2); + expect(nativeRequest.assets[1].data.len).to.equal(200); + expect(nativeRequest.assets[2].required).to.equal(0); + expect(nativeRequest.assets[3].img).to.not.equal(null); + expect(nativeRequest.assets[3].img.wmin).to.equal(50); + expect(nativeRequest.assets[3].img.hmin).to.equal(50); + expect(nativeRequest.assets[3].img.type).to.equal(1); + expect(nativeRequest.assets[4].img).to.not.equal(null); + expect(nativeRequest.assets[4].img.wmin).to.equal(100); + expect(nativeRequest.assets[4].img.hmin).to.equal(150); + expect(nativeRequest.assets[4].img.type).to.equal(3); + }); + + it('Verify Native response', function () { + const request = spec.buildRequests(nativeSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + const nativeResponse = { + 'native': { + assets: [ + { id: 1, title: { text: 'Ad Title' } }, + { id: 2, data: { value: 'Test description' } }, + { id: 3, data: { value: 'Brand' } }, + { id: 4, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_icon.png', w: 100, h: 100 } }, + { id: 5, img: { url: 'https://adx1public.s3.amazonaws.com/creatives_image.png', w: 300, h: 300 } } + ], + link: { url: 'https://brand.com/' } + } + }; + const ortbResponse = { + seatbid: [{ + bid: [{ + impid: ortbRequest.imp[0].id, + price: 1.25, + nurl: 'https://rtb.adx1.com/log', + adm: JSON.stringify(nativeResponse) + }] + }], + cur: 'USD', + }; + const bids = spec.interpretResponse({ body: ortbResponse }, request); + // verify bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.25); + expect(bid.adId).to.equal('bid12345'); + expect(bid.ad).to.be.undefined; + expect(bid.mediaType).to.equal('native'); + const nativeBid = bid['native']; + expect(nativeBid).to.not.equal(null); + expect(nativeBid.title).to.equal('Ad Title'); + expect(nativeBid.sponsoredBy).to.equal('Brand'); + expect(nativeBid.icon.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_icon.png'); + expect(nativeBid.image.url).to.equal('https://adx1public.s3.amazonaws.com/creatives_image.png'); + expect(nativeBid.image.width).to.equal(300); + expect(nativeBid.image.height).to.equal(300); + expect(nativeBid.icon.width).to.equal(100); + expect(nativeBid.icon.height).to.equal(100); + expect(nativeBid.clickUrl).to.equal(encodeURIComponent('https://brand.com/')); + expect(nativeBid.impressionTrackers).to.have.lengthOf(1); + expect(nativeBid.impressionTrackers[0]).to.equal('https://rtb.adx1.com/log'); + }); + + it('Verify Video request', function () { + const request = spec.buildRequests(videoSlotConfig); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const videoRequest = JSON.parse(request.data); + // site object + expect(videoRequest.site).to.not.equal(null); + expect(videoRequest.site.publisher.id).to.equal('29521'); + expect(videoRequest.site.ref).to.equal(window.top.document.referrer); + expect(videoRequest.site.page).to.equal(window.location.href); + // device object + expect(videoRequest.device).to.not.equal(null); + expect(videoRequest.device.ua).to.equal(navigator.userAgent); + // slot 1 + expect(videoRequest.imp[0].tagid).to.equal('1234567'); + expect(videoRequest.imp[0].video).to.not.equal(null); + expect(videoRequest.imp[0].video.w).to.equal(640); + expect(videoRequest.imp[0].video.h).to.equal(480); + expect(videoRequest.imp[0].banner).to.equal(null); + expect(videoRequest.imp[0].native).to.equal(null); + }); + + it('Verify parse video response', function () { + const request = spec.buildRequests(videoSlotConfig); + const videoRequest = JSON.parse(request.data); + const videoResponse = { + seatbid: [{ + bid: [{ + impid: videoRequest.imp[0].id, + price: 1.90, + adm: 'https://vid.example.com/9876', + crid: '510511_754567308' + }] + }], + cur: 'USD' + }; + const bids = spec.interpretResponse({ body: videoResponse }, request); + expect(bids).to.have.lengthOf(1); + // verify first bid + const bid = bids[0]; + expect(bid.cpm).to.equal(1.90); + expect(bid.vastUrl).to.equal('https://vid.example.com/9876'); + expect(bid.crid).to.equal('510511_754567308'); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.adId).to.equal('bid12345678'); + expect(bid.netRevenue).to.equal(true); + expect(bid.currency).to.equal('USD'); + expect(bid.ttl).to.equal(360); + }); + + it('Verifies bidder code', function () { + expect(spec.code).to.equal('windtalker'); + }); + + it('Verifies supported media types', function () { + expect(spec.supportedMediaTypes).to.have.lengthOf(3); + expect(spec.supportedMediaTypes[0]).to.equal('banner'); + expect(spec.supportedMediaTypes[1]).to.equal('native'); + expect(spec.supportedMediaTypes[2]).to.equal('video'); + }); + + it('Verifies if bid request valid', function () { + expect(spec.isBidRequestValid(slotConfigs[0])).to.equal(true); + expect(spec.isBidRequestValid(slotConfigs[1])).to.equal(true); + expect(spec.isBidRequestValid(nativeSlotConfig[0])).to.equal(true); + expect(spec.isBidRequestValid(videoSlotConfig[0])).to.equal(true); + }); + + it('Verify app requests', function () { + const request = spec.buildRequests(appSlotConfig); + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.site).to.equal(null); + expect(ortbRequest.app).to.not.be.null; + expect(ortbRequest.app.publisher).to.not.equal(null); + expect(ortbRequest.app.publisher.id).to.equal('29521'); + expect(ortbRequest.app.id).to.equal('1111'); + expect(ortbRequest.app.name).to.equal('app name'); + expect(ortbRequest.app.bundle).to.equal('io.windtalker.apps'); + expect(ortbRequest.app.storeurl).to.equal('https://windtalker.io/apps'); + expect(ortbRequest.app.domain).to.equal('windtalker.io'); + }); + + it('Verify GDPR', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'serialized_gpdr_data' + } + }; + const request = spec.buildRequests(slotConfigs, bidderRequest); + expect(request.url).to.equal('https://windtalkerdisplay.hb.adp3.net/'); + expect(request.method).to.equal('POST'); + const ortbRequest = JSON.parse(request.data); + expect(ortbRequest.user).to.not.equal(null); + expect(ortbRequest.user.ext).to.not.equal(null); + expect(ortbRequest.user.ext.consent).to.equal('serialized_gpdr_data'); + expect(ortbRequest.regs).to.not.equal(null); + expect(ortbRequest.regs.ext).to.not.equal(null); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + }); +});