From 0be342247e7419c771751dc6b8beef4bf534697f Mon Sep 17 00:00:00 2001
From: borisjaskerovich <41855079+borisjaskerovich@users.noreply.github.com>
Date: Mon, 4 Feb 2019 09:02:40 +0200
Subject: [PATCH] Timmedia/timadapter 2.0 (#3497)
* add tim adapter
* add tim spec file
* add tim md file
* added tests. change adapter.
* dummy commit for re-running tests
* new code for prebid2.0
* change alias name
* change domain name
---
modules/timBidAdapter.js | 177 ++++++++++++++++++++++++
modules/timBidAdapter.md | 26 ++++
test/spec/modules/timBidAdapter_spec.js | 152 ++++++++++++++++++++
3 files changed, 355 insertions(+)
create mode 100644 modules/timBidAdapter.js
create mode 100644 modules/timBidAdapter.md
create mode 100644 test/spec/modules/timBidAdapter_spec.js
diff --git a/modules/timBidAdapter.js b/modules/timBidAdapter.js
new file mode 100644
index 00000000000..0539f37deef
--- /dev/null
+++ b/modules/timBidAdapter.js
@@ -0,0 +1,177 @@
+import * as utils from 'src/utils';
+import {registerBidder} from 'src/adapters/bidderFactory';
+import * as bidfactory from '../src/bidfactory';
+var CONSTANTS = require('src/constants.json');
+const BIDDER_CODE = 'tim';
+
+function parseBidRequest(bidRequest) {
+ let params = bidRequest.url.split('?')[1];
+ var obj = {};
+ var pairs = params.split('&');
+ try {
+ for (var i in pairs) {
+ var split = pairs[i].split('=');
+ obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);
+ }
+ } catch (e) {
+ utils.logError(e);
+ }
+
+ return JSON.parse(obj.br);
+}
+
+function formatAdMarkup(bid) {
+ var adm = bid.adm;
+ if ('nurl' in bid) {
+ adm += createTrackPixelHtml(bid.nurl);
+ }
+ return `
${adm}`;
+}
+
+function createTrackPixelHtml(url) {
+ if (!url) {
+ return '';
+ }
+ let img = '';
+ img += '
';
+ return img;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ aliases: ['timmedia'],
+
+ isBidRequestValid: function(bid) {
+ if (bid.params && bid.params.publisherid && bid.params.placementCode) {
+ return true;
+ } if (!bid.params) {
+ utils.logError('bid not valid: params were not provided');
+ } else if (!bid.params.publisherid) {
+ utils.logError('bid not valid: publisherid was not provided');
+ } else if (!bid.params.placementCode) {
+ utils.logError('bid not valid: placementCode was not provided');
+ } return false;
+ },
+
+ buildRequests: function(validBidRequests, bidderRequest) {
+ var requests = [];
+ for (var i = 0; i < validBidRequests.length; i++) {
+ requests.push(this.createRTBRequestURL(validBidRequests[i]));
+ }
+ return requests;
+ },
+
+ createRTBRequestURL: function(bidReq) {
+ // build bid request object
+ var domain = window.location.host;
+ var page = window.location.href;
+ var publisherid = bidReq.params.publisherid;
+ var bidFloor = bidReq.params.bidfloor;
+ var placementCode = bidReq.params.placementCode;
+
+ var adW = bidReq.mediaTypes.banner.sizes[0][0];
+ var adH = bidReq.mediaTypes.banner.sizes[0][1];
+
+ // build bid request with impressions
+ var bidRequest = {
+ id: utils.getUniqueIdentifierStr(),
+ imp: [{
+ id: bidReq.bidId,
+ banner: {
+ w: adW,
+ h: adH
+ },
+ tagid: placementCode,
+ bidfloor: bidFloor
+ }],
+ site: {
+ domain: domain,
+ page: page,
+ publisher: {
+ id: publisherid
+ }
+ },
+ device: {
+ 'language': this.getLanguage(),
+ 'w': adW,
+ 'h': adH,
+ 'js': 1,
+ 'ua': navigator.userAgent
+ }
+ };
+ if (!bidFloor) {
+ delete bidRequest.imp['bidfloor'];
+ }
+
+ bidRequest.bidId = bidReq.bidId;
+ var url = '//hb.timmedia-hb.com/api/v2/services/prebid/' + publisherid + '/' + placementCode + '?' + 'br=' + encodeURIComponent(JSON.stringify(bidRequest));
+ return {
+ method: 'GET',
+ url: url,
+ data: '',
+ options: {withCredentials: false}
+ };
+ },
+
+ interpretResponse: function(serverResponse, bidRequest) {
+ bidRequest = parseBidRequest(bidRequest);
+ var bidResp = serverResponse.body;
+ const bidResponses = [];
+ if ((!bidResp || !bidResp.id) ||
+ (!bidResp.seatbid || bidResp.seatbid.length === 0 || !bidResp.seatbid[0].bid || bidResp.seatbid[0].bid.length === 0)) {
+ return [];
+ }
+ bidResp.seatbid[0].bid.forEach(function (bidderBid) {
+ var responseCPM;
+ var placementCode = '';
+ if (bidRequest) {
+ var bidResponse = bidfactory.createBid(1);
+ placementCode = bidRequest.placementCode;
+ bidRequest.status = CONSTANTS.STATUS.GOOD;
+ responseCPM = parseFloat(bidderBid.price);
+ if (responseCPM === 0) {
+ var bid = bidfactory.createBid(2);
+ bid.bidderCode = BIDDER_CODE;
+ bidResponses.push(bid);
+ return bidResponses;
+ }
+ bidResponse.placementCode = placementCode;
+ bidResponse.size = bidRequest.sizes;
+ bidResponse.creativeId = bidderBid.id;
+ bidResponse.bidderCode = BIDDER_CODE;
+ bidResponse.cpm = responseCPM;
+ bidResponse.ad = formatAdMarkup(bidderBid);
+ bidResponse.width = parseInt(bidderBid.w);
+ bidResponse.height = parseInt(bidderBid.h);
+ bidResponse.currency = bidResp.cur;
+ bidResponse.netRevenue = true;
+ bidResponse.requestId = bidRequest.bidId;
+ bidResponse.ttl = 180;
+ bidResponses.push(bidResponse);
+ }
+ });
+ return bidResponses;
+ },
+ getLanguage: function() {
+ const language = navigator.language ? 'language' : 'userLanguage';
+ return navigator[language].split('-')[0];
+ },
+
+ getUserSyncs: function(syncOptions, serverResponses) {
+ const syncs = []
+ return syncs;
+ },
+
+ onTimeout: function(data) {
+ // Bidder specifc code
+ },
+
+ onBidWon: function(bid) {
+ // Bidder specific code
+ },
+
+ onSetTargeting: function(bid) {
+ // Bidder specific code
+ },
+}
+registerBidder(spec);
diff --git a/modules/timBidAdapter.md b/modules/timBidAdapter.md
new file mode 100644
index 00000000000..684f2e5f7c4
--- /dev/null
+++ b/modules/timBidAdapter.md
@@ -0,0 +1,26 @@
+# Overview
+
+```
+Module Name: tim Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: boris@thetimmedia.com
+```
+
+# Description
+
+Module that connects to tim's demand sources
+
+# Test Parameters
+```
+ var adUnits = [{
+ "code":"99",
+ "sizes":[[300,250]],
+ "bids":[{"bidder":"tim",
+ "params":{
+ "placementCode":"testPlacementCode",
+ "publisherid":"testpublisherid"
+ }
+ }]
+ }]
+```
+
diff --git a/test/spec/modules/timBidAdapter_spec.js b/test/spec/modules/timBidAdapter_spec.js
new file mode 100644
index 00000000000..0dc14bf3e03
--- /dev/null
+++ b/test/spec/modules/timBidAdapter_spec.js
@@ -0,0 +1,152 @@
+import { expect } from 'chai';
+import { spec } from 'modules/timBidAdapter';
+
+describe('timAdapterTests', function () {
+ describe('bidRequestValidity', function () {
+ it('bidRequest with publisherid and placementCode params', function () {
+ expect(spec.isBidRequestValid({
+ bidder: 'tim',
+ params: {
+ publisherid: 'testid',
+ placementCode: 'testplacement'
+ }
+ })).to.equal(true);
+ });
+
+ it('bidRequest with only publisherid', function () {
+ expect(spec.isBidRequestValid({
+ bidder: 'tim',
+ params: {
+ publisherid: 'testid'
+ }
+ })).to.equal(false);
+ });
+
+ it('bidRequest with only placementCode', function () {
+ expect(spec.isBidRequestValid({
+ bidder: 'tim',
+ params: {
+ placementCode: 'testplacement'
+ }
+ })).to.equal(false);
+ });
+
+ it('bidRequest without params', function () {
+ expect(spec.isBidRequestValid({
+ bidder: 'tim',
+ })).to.equal(false);
+ });
+ });
+
+ describe('buildRequests', function () {
+ const validBidRequests = [{
+ 'bidder': 'tim',
+ 'params': {'placementCode': 'placementCode', 'publisherid': 'testpublisherid'},
+ 'mediaTypes': {'banner': {'sizes': [[300, 250]]}},
+ 'adUnitCode': 'adUnitCode',
+ 'transactionId': 'transactionId',
+ 'sizes': [[300, 250]],
+ 'bidId': 'bidId',
+ 'bidderRequestId': 'bidderRequestId',
+ 'auctionId': 'auctionId',
+ 'src': 'client',
+ 'bidRequestsCount': 1
+ }];
+
+ it('bidRequest method', function () {
+ const requests = spec.buildRequests(validBidRequests);
+ expect(requests[0].method).to.equal('GET');
+ });
+
+ it('bidRequest url', function () {
+ const requests = spec.buildRequests(validBidRequests);
+ expect(requests[0].url).to.exist;
+ });
+
+ it('bidRequest data', function () {
+ const requests = spec.buildRequests(validBidRequests);
+ expect(requests[0].data).to.exist;
+ });
+
+ it('bidRequest options', function () {
+ const requests = spec.buildRequests(validBidRequests);
+ expect(requests[0].options).to.exist;
+ });
+ });
+
+ describe('interpretResponse', function () {
+ const bidRequest = {
+ 'method': 'GET',
+ 'url': '//bidder.url/api/prebid/testpublisherid/header-bid-tag-0?br=%7B%22id%22%3A%223a3ac0d7fc2548%22%2C%22imp%22%3A%5B%7B%22id%22%3A%22251b8a6d3aac3e%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22header-bid-tag-0%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22www.chinatimes.com%22%2C%22page%22%3A%22http%3A%2F%2Fwww.chinatimes.com%2Fa%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%22251b8a6d3aac3e%22%7D',
+ 'data': '',
+ 'options': {'withCredentials': false}
+ };
+
+ const serverResponse = {
+ 'body': {
+ 'id': 'id',
+ 'seatbid': []
+ },
+ 'headers': {}
+ };
+
+ it('check empty array response', function () {
+ const result = spec.interpretResponse(serverResponse, bidRequest);
+ expect(result).to.deep.equal([]);
+ });
+
+ const validBidRequest = {
+ 'method': 'GET',
+ 'url': '//bidder.url/api/v2/services/prebid/testpublisherid/placementCodeTest?br=%7B%22id%22%3A%2248640869bd9db94%22%2C%22imp%22%3A%5B%7B%22id%22%3A%224746fcaa11197f3%22%2C%22banner%22%3A%7B%22w%22%3A300%2C%22h%22%3A250%7D%2C%22tagid%22%3A%22placementCodeTest%22%7D%5D%2C%22site%22%3A%7B%22domain%22%3A%22mediamart.tv%22%2C%22page%22%3A%22http%3A%2F%2Fmediamart.tv%2Fsas%2Ftests%2FDesktop%2Fcaesar%2Fdfptest.html%22%2C%22publisher%22%3A%7B%22id%22%3A%22testpublisherid%22%7D%7D%2C%22device%22%3A%7B%22language%22%3A%22en%22%2C%22w%22%3A300%2C%22h%22%3A250%2C%22js%22%3A1%2C%22ua%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%20(KHTML%2C%20like%20Gecko)%20Chrome%2F71.0.3578.98%20Safari%2F537.36%22%7D%2C%22bidId%22%3A%224746fcaa11197f3%22%7D',
+ 'data': '',
+ 'options': {'withCredentials': false}
+ };
+ const validServerResponse = {
+ 'body': {'id': 'id',
+ 'seatbid': [
+ {'bid': [{'id': 'id',
+ 'impid': 'impid',
+ 'price': 3,
+ 'nurl': 'https://bidder.url/api/v1/?price=${AUCTION_PRICE}&bidcur=USD&bidid=bidid=true',
+ 'adm': '',
+ 'adomain': [''],
+ 'cid': '1',
+ 'crid': '700',
+ 'w': 300,
+ 'h': 250
+ }]}],
+ 'bidid': 'bidid',
+ 'cur': 'USD'
+ },
+ 'headers': {}
+ };
+ it('required keys', function () {
+ const result = spec.interpretResponse(validServerResponse, validBidRequest);
+
+ let requiredKeys = [
+ 'requestId',
+ 'creativeId',
+ 'adId',
+ 'cpm',
+ 'width',
+ 'height',
+ 'currency',
+ 'netRevenue',
+ 'ttl',
+ 'ad'
+ ];
+
+ let resultKeys = Object.keys(result[0]);
+ requiredKeys.forEach(function(key) {
+ expect(resultKeys.indexOf(key) !== -1).to.equal(true);
+ });
+ })
+ });
+
+ describe('getUserSyncs', function () {
+ it('check empty response getUserSyncs', function () {
+ const result = spec.getUserSyncs('', '');
+ expect(result).to.deep.equal([]);
+ });
+ });
+});