diff --git a/modules/videofyBidAdapter.js b/modules/videofyBidAdapter.js new file mode 100644 index 00000000000..07e95689ab4 --- /dev/null +++ b/modules/videofyBidAdapter.js @@ -0,0 +1,262 @@ +import { VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { Renderer } from '../src/Renderer.js'; + +const BIDDER_CODE = 'videofy'; +const TTL = 600; + +function avRenderer(bid) { + bid.renderer.push(function() { + let eventCallback = bid && bid.renderer && bid.renderer.handleVideoEvent ? bid.renderer.handleVideoEvent : null; + window.aniviewRenderer.renderAd({ + id: bid.adUnitCode + '_' + bid.adId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: bid.adUnitCode, + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + vastXml: bid.vastXml, + config: bid.params[0].rendererConfig, + eventsCallback: eventCallback, + bid: bid + }); + }); +} + +function newRenderer(bidRequest) { + const renderer = Renderer.install({ + url: 'https://player.srv-mars.com/script/6.1/prebidRenderer.js', + config: {}, + loaded: false, + }); + + try { + renderer.setRender(avRenderer); + } catch (err) { + } + + return renderer; +} + +function isBidRequestValid(bid) { + if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } + + return true; +} +let irc = 0; +function buildRequests(validBidRequests, bidderRequest) { + let bidRequests = []; + + for (let i = 0; i < validBidRequests.length; i++) { + let bidRequest = validBidRequests[i]; + var sizes = [[640, 480]]; + + if (bidRequest.mediaTypes && bidRequest.mediaTypes.video && bidRequest.mediaTypes.video.playerSize) { + sizes = bidRequest.mediaTypes.video.playerSize; + } else { + if (bidRequest.sizes) { + sizes = bidRequest.sizes; + } + } + if (sizes.length === 2 && typeof sizes[0] === 'number') { + sizes = [[sizes[0], sizes[1]]]; + } + + for (let j = 0; j < sizes.length; j++) { + let size = sizes[j]; + let playerWidth; + let playerHeight; + + if (size && size.length == 2) { + playerWidth = size[0]; + playerHeight = size[1]; + } else { + playerWidth = 640; + playerHeight = 480; + } + + let s2sParams = {}; + + for (var attrname in bidRequest.params) { + if (bidRequest.params.hasOwnProperty(attrname) && attrname.indexOf('AV_') == 0) { + s2sParams[attrname] = bidRequest.params[attrname]; + } + }; + + if (s2sParams.AV_APPPKGNAME && !s2sParams.AV_URL) { s2sParams.AV_URL = s2sParams.AV_APPPKGNAME; } + if (!s2sParams.AV_IDFA && !s2sParams.AV_URL) { + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + s2sParams.AV_URL = bidderRequest.refererInfo.referer; + } else { + s2sParams.AV_URL = window.location.href; + } + } + if (s2sParams.AV_IDFA && !s2sParams.AV_AID) { s2sParams.AV_AID = s2sParams.AV_IDFA; } + if (s2sParams.AV_AID && !s2sParams.AV_IDFA) { s2sParams.AV_IDFA = s2sParams.AV_AID; } + + s2sParams.cb = Math.floor(Math.random() * 999999999); + s2sParams.AV_WIDTH = playerWidth; + s2sParams.AV_HEIGHT = playerHeight; + s2sParams.bidWidth = playerWidth; + s2sParams.bidHeight = playerHeight; + s2sParams.bidId = bidRequest.bidId; + s2sParams.pbjs = 1; + s2sParams.tgt = 10; + s2sParams.s2s = '1'; + s2sParams.irc = irc; + irc++; + s2sParams.wpm = 1; + + if (bidderRequest && bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.gdprApplies) { + s2sParams.AV_GDPR = 1; + s2sParams.AV_CONSENT = bidderRequest.gdprConsent.consentString; + } + } + if (bidderRequest && bidderRequest.uspConsent) { + s2sParams.AV_CCPA = bidderRequest.uspConsent; + } + + let serverDomain = (bidRequest.params && bidRequest.params.serverDomain) ? bidRequest.params.serverDomain : 'servx.srv-mars.com'; + let servingUrl = 'https://' + serverDomain + '/api/adserver/vast3/'; + + bidRequests.push({ + method: 'GET', + url: servingUrl, + data: s2sParams, + bidRequest + }); + } + } + + return bidRequests; +} +function getCpmData(xml) { + let ret = {cpm: 0, currency: 'USD'}; + if (xml) { + let ext = xml.getElementsByTagName('Extensions'); + if (ext && ext.length > 0) { + ext = ext[0].getElementsByTagName('Extension'); + if (ext && ext.length > 0) { + for (var i = 0; i < ext.length; i++) { + if (ext[i].getAttribute('type') == 'ANIVIEW') { + let price = ext[i].getElementsByTagName('Cpm'); + if (price && price.length == 1) { + ret.cpm = price[0].textContent; + } + break; + } + } + } + } + } + return ret; +} +function interpretResponse(serverResponse, bidRequest) { + let bidResponses = []; + if (serverResponse && serverResponse.body) { + if (serverResponse.error) { + return bidResponses; + } else { + try { + let bidResponse = {}; + if (bidRequest && bidRequest.data && bidRequest.data.bidId && bidRequest.data.bidId !== '') { + let xmlStr = serverResponse.body; + let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + if (xml && xml.getElementsByTagName('parsererror').length == 0) { + let cpmData = getCpmData(xml); + if (cpmData && cpmData.cpm > 0) { + bidResponse.requestId = bidRequest.data.bidId; + bidResponse.bidderCode = BIDDER_CODE; + bidResponse.ad = ''; + bidResponse.cpm = cpmData.cpm; + bidResponse.width = bidRequest.data.AV_WIDTH; + bidResponse.height = bidRequest.data.AV_HEIGHT; + bidResponse.ttl = TTL; + bidResponse.creativeId = xml.getElementsByTagName('Ad') && xml.getElementsByTagName('Ad')[0] && xml.getElementsByTagName('Ad')[0].getAttribute('id') ? xml.getElementsByTagName('Ad')[0].getAttribute('id') : 'creativeId'; + bidResponse.currency = cpmData.currency; + bidResponse.netRevenue = true; + var blob = new Blob([xmlStr], { + type: 'application/xml' + }); + bidResponse.vastUrl = window.URL.createObjectURL(blob); + bidResponse.vastXml = xmlStr; + bidResponse.mediaType = VIDEO; + if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = newRenderer(bidRequest); } + + bidResponses.push(bidResponse); + } + } else {} + } else {} + } catch (e) {} + } + } else {} + + return bidResponses; +} + +function getSyncData(xml, options) { + let ret = []; + if (xml) { + let ext = xml.getElementsByTagName('Extensions'); + if (ext && ext.length > 0) { + ext = ext[0].getElementsByTagName('Extension'); + if (ext && ext.length > 0) { + for (var i = 0; i < ext.length; i++) { + if (ext[i].getAttribute('type') == 'ANIVIEW') { + let syncs = ext[i].getElementsByTagName('AdServingSync'); + if (syncs && syncs.length == 1) { + try { + let data = JSON.parse(syncs[0].textContent); + if (data && data.trackers && data.trackers.length) { + data = data.trackers; + for (var j = 0; j < data.length; j++) { + if (typeof data[j] === 'object' && typeof data[j].url === 'string' && data[j].e === 'inventory') { + if (data[j].t == 1 && options.pixelEnabled) { + ret.push({url: data[j].url, type: 'image'}); + } else { + if (data[j].t == 3 && options.iframeEnabled) { + ret.push({url: data[j].url, type: 'iframe'}); + } + } + } + } + } + } catch (e) {} + } + break; + } + } + } + } + } + return ret; +} + +function getUserSyncs(syncOptions, serverResponses) { + if (serverResponses && serverResponses[0] && serverResponses[0].body) { + if (serverResponses.error) { + return []; + } else { + try { + let xmlStr = serverResponses[0].body; + let xml = new window.DOMParser().parseFromString(xmlStr, 'text/xml'); + if (xml && xml.getElementsByTagName('parsererror').length == 0) { + let syncData = getSyncData(xml, syncOptions); + return syncData; + } + } catch (e) {} + } + } +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [VIDEO], + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/videofyBidAdapter.md b/modules/videofyBidAdapter.md new file mode 100644 index 00000000000..b50eaf5672e --- /dev/null +++ b/modules/videofyBidAdapter.md @@ -0,0 +1,36 @@ +# Overview + +``` +Module Name: Videofy Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support1@videofy.ai +``` + +# Description + +Connects to Videofy for bids. + +Videofy bid adapter supports Video ads currently. + +# Sample Ad Unit: For Publishers +```javascript +var videoAdUnit = [ +{ + code: 'video1', + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'outstream' + }, + }, + bids: [{ + bidder: 'videofy', + params: { + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '5d19dfca4b6236688c0a2fc4' + } + }] +}]; +``` + +``` diff --git a/test/spec/modules/videofyBidAdapter_spec.js b/test/spec/modules/videofyBidAdapter_spec.js new file mode 100644 index 00000000000..e221ece45b8 --- /dev/null +++ b/test/spec/modules/videofyBidAdapter_spec.js @@ -0,0 +1,200 @@ +import { spec } from 'modules/videofyBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +const { expect } = require('chai'); + +describe('Videofy Bid Adapter Test', function () { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'videofy', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'video1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + }; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = { + something: 'is wrong' + }; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let bid2Requests = [ + { + 'bidder': 'videofy', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [[300, 250], [640, 480]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + let bid1Request = [ + { + 'bidder': 'videofy', + 'params': { + 'AV_PUBLISHERID': '123456', + 'AV_CHANNELID': '123456' + }, + 'adUnitCode': 'test1', + 'sizes': [640, 480], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'requestId': 'a09c66c3-53e3-4428-b296-38fc08e7cd2a', + 'transactionId': 'd6f6b392-54a9-454c-85fb-a2fd882c4a2d', + } + ]; + + it('Test 2 requests', function () { + const requests = spec.buildRequests(bid2Requests); + expect(requests.length).to.equal(2); + const r1 = requests[0]; + const d1 = requests[0].data; + expect(d1).to.have.property('AV_PUBLISHERID'); + expect(d1.AV_PUBLISHERID).to.equal('123456'); + expect(d1).to.have.property('AV_CHANNELID'); + expect(d1.AV_CHANNELID).to.equal('123456'); + expect(d1).to.have.property('AV_WIDTH'); + expect(d1.AV_WIDTH).to.equal(300); + expect(d1).to.have.property('AV_HEIGHT'); + expect(d1.AV_HEIGHT).to.equal(250); + expect(d1).to.have.property('AV_URL'); + expect(d1).to.have.property('cb'); + expect(d1).to.have.property('s2s'); + expect(d1.s2s).to.equal('1'); + expect(d1).to.have.property('pbjs'); + expect(d1.pbjs).to.equal(1); + expect(r1).to.have.property('url'); + expect(r1.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); + const r2 = requests[1]; + const d2 = requests[1].data; + expect(d2).to.have.property('AV_PUBLISHERID'); + expect(d2.AV_PUBLISHERID).to.equal('123456'); + expect(d2).to.have.property('AV_CHANNELID'); + expect(d2.AV_CHANNELID).to.equal('123456'); + expect(d2).to.have.property('AV_WIDTH'); + expect(d2.AV_WIDTH).to.equal(640); + expect(d2).to.have.property('AV_HEIGHT'); + expect(d2.AV_HEIGHT).to.equal(480); + expect(d2).to.have.property('AV_URL'); + expect(d2).to.have.property('cb'); + expect(d2).to.have.property('s2s'); + expect(d2.s2s).to.equal('1'); + expect(d2).to.have.property('pbjs'); + expect(d2.pbjs).to.equal(1); + expect(r2).to.have.property('url'); + expect(r2.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); + }); + + it('Test 1 request', function () { + const requests = spec.buildRequests(bid1Request); + expect(requests.length).to.equal(1); + const r = requests[0]; + const d = requests[0].data; + expect(d).to.have.property('AV_PUBLISHERID'); + expect(d.AV_PUBLISHERID).to.equal('123456'); + expect(d).to.have.property('AV_CHANNELID'); + expect(d.AV_CHANNELID).to.equal('123456'); + expect(d).to.have.property('AV_WIDTH'); + expect(d.AV_WIDTH).to.equal(640); + expect(d).to.have.property('AV_HEIGHT'); + expect(d.AV_HEIGHT).to.equal(480); + expect(d).to.have.property('AV_URL'); + expect(d).to.have.property('cb'); + expect(d).to.have.property('s2s'); + expect(d.s2s).to.equal('1'); + expect(d).to.have.property('pbjs'); + expect(d.pbjs).to.equal(1); + expect(r).to.have.property('url'); + expect(r.url).to.contain('https://servx.srv-mars.com/api/adserver/vast3/'); + }); + }); + + describe('interpretResponse', function () { + let bidRequest = { + 'url': 'https://servx.srv-mars.com/api/adserver/vast3/', + 'data': { + 'bidId': '253dcb69fb2577', + AV_PUBLISHERID: '55b78633181f4603178b4568', + AV_CHANNELID: '55b7904d181f46410f8b4568', + } + }; + let serverResponse = {}; + serverResponse.body = 'FORDFORD00:00:15'; + + it('Check bid interpretResponse', function () { + const BIDDER_CODE = 'videofy'; + let bidResponses = spec.interpretResponse(serverResponse, bidRequest); + expect(bidResponses.length).to.equal(1); + let bidResponse = bidResponses[0]; + expect(bidResponse.requestId).to.equal(bidRequest.data.bidId); + expect(bidResponse.bidderCode).to.equal(BIDDER_CODE); + expect(bidResponse.cpm).to.equal('2'); + expect(bidResponse.ttl).to.equal(600); + expect(bidResponse.currency).to.equal('USD'); + expect(bidResponse.netRevenue).to.equal(true); + expect(bidResponse.mediaType).to.equal('video'); + }); + + it('safely handles XML parsing failure from invalid bid response', function () { + let invalidServerResponse = {}; + invalidServerResponse.body = ''; + + let result = spec.interpretResponse(invalidServerResponse, bidRequest); + expect(result.length).to.equal(0); + }); + + it('handles nobid responses', function () { + let nobidResponse = {}; + nobidResponse.body = ''; + + let result = spec.interpretResponse(nobidResponse, bidRequest); + expect(result.length).to.equal(0); + }); + }); + + describe('getUserSyncs', function () { + it('Check get sync pixels from response', function () { + let pixelUrl = 'https://sync.pixel.url/sync'; + let pixelEvent = 'inventory'; + let pixelType = '3'; + let pixelStr = '{"url":"' + pixelUrl + '", "e":"' + pixelEvent + '", "t":' + pixelType + '}'; + let bidResponse = 'FORDFORD00:00:15'; + let serverResponse = [ + {body: bidResponse} + ]; + let syncPixels = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: true}, serverResponse); + expect(syncPixels.length).to.equal(1); + let pixel = syncPixels[0]; + expect(pixel.url).to.equal(pixelUrl); + expect(pixel.type).to.equal('iframe'); + }); + }); +});