Skip to content

Commit

Permalink
Detect ad blocker recovered requests + send dynamic parameters to ada…
Browse files Browse the repository at this point in the history
…pter + minor fixes (prebid#3749)

* Livewrapped bid and analytics adapter

* Fixed some tests for browser compatibility

* Fixed some tests for browser compatibility

* Changed analytics adapter code name

* Fix double quote in debug message

* modified how gdpr is being passed

* Added support for Publisher Common ID Module

* Corrections for ttr in analytics

* ANalytics updates

* Auction start time stamp changed

* Detect recovered ad blocked requests
Make it possible to pass dynamic parameters to adapter
  • Loading branch information
bjorn-lw authored and Alex committed Aug 1, 2019
1 parent 04f8923 commit ec58183
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 12 deletions.
31 changes: 23 additions & 8 deletions modules/livewrappedAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const cache = {

let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE}), {
track({eventType, args}) {
const time = utils.timestamp();
utils.logInfo('LIVEWRAPPED_EVENT:', [eventType, args]);

switch (eventType) {
Expand All @@ -30,16 +31,18 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
break;
case CONSTANTS.EVENTS.BID_REQUESTED:
utils.logInfo('LIVEWRAPPED_BID_REQUESTED:', args);
cache.auctions[args.auctionId].timeStamp = args.start;

args.bids.forEach(function(bidRequest) {
cache.auctions[args.auctionId].timeStamp = args.start;
cache.auctions[args.auctionId].bids[bidRequest.bidId] = {
bidder: bidRequest.bidder,
adUnit: bidRequest.adUnitCode,
isBid: false,
won: false,
timeout: false,
sendStatus: 0
sendStatus: 0,
readyToSend: 0,
start: args.start
}

utils.logInfo(bidRequest);
Expand All @@ -49,25 +52,30 @@ let livewrappedAnalyticsAdapter = Object.assign(adapter({EMPTYURL, ANALYTICSTYPE
case CONSTANTS.EVENTS.BID_RESPONSE:
utils.logInfo('LIVEWRAPPED_BID_RESPONSE:', args);

let bidResponse = cache.auctions[args.auctionId].bids[args.adId];
let bidResponse = cache.auctions[args.auctionId].bids[args.requestId];
bidResponse.isBid = args.getStatusCode() === CONSTANTS.STATUS.GOOD;
bidResponse.width = args.width;
bidResponse.height = args.height;
bidResponse.cpm = args.cpm;
bidResponse.ttr = args.timeToRespond;
bidResponse.readyToSend = 1;
if (!bidResponse.ttr) {
bidResponse.ttr = time - bidResponse.start;
}
break;
case CONSTANTS.EVENTS.BIDDER_DONE:
utils.logInfo('LIVEWRAPPED_BIDDER_DONE:', args);
args.bids.forEach(doneBid => {
let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId];
let bid = cache.auctions[doneBid.auctionId].bids[doneBid.bidId || doneBid.requestId];
if (!bid.ttr) {
bid.ttr = Date.now() - args.auctionStart;
bid.ttr = time - bid.start;
}
bid.readyToSend = 1;
});
break;
case CONSTANTS.EVENTS.BID_WON:
utils.logInfo('LIVEWRAPPED_BID_WON:', args);
let wonBid = cache.auctions[args.auctionId].bids[args.adId];
let wonBid = cache.auctions[args.auctionId].bids[args.requestId];
wonBid.won = true;
if (wonBid.sendStatus != 0) {
livewrappedAnalyticsAdapter.sendEvents();
Expand Down Expand Up @@ -105,7 +113,8 @@ livewrappedAnalyticsAdapter.sendEvents = function() {
requests: getSentRequests(),
responses: getResponses(),
wins: getWins(),
timeouts: getTimeouts()
timeouts: getTimeouts(),
rcv: getAdblockerRecovered()
};

if (events.requests.length == 0 &&
Expand All @@ -118,6 +127,12 @@ livewrappedAnalyticsAdapter.sendEvents = function() {
ajax(URL, undefined, JSON.stringify(events), {method: 'POST'});
}

function getAdblockerRecovered() {
try {
return utils.getWindowTop().I12C && utils.getWindowTop().I12C.Morph === 1;
} catch (e) {}
}

function getSentRequests() {
var sentRequests = [];

Expand Down Expand Up @@ -147,7 +162,7 @@ function getResponses() {
Object.keys(cache.auctions[auctionId].bids).forEach(bidId => {
let auction = cache.auctions[auctionId];
let bid = auction.bids[bidId];
if (!(bid.sendStatus & RESPONSESENT) && !bid.timeout) {
if (bid.readyToSend && !(bid.sendStatus & RESPONSESENT) && !bid.timeout) {
bid.sendStatus |= RESPONSESENT;

responses.push({
Expand Down
11 changes: 10 additions & 1 deletion modules/livewrappedBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const spec = {
* seats: List of bidders and seats Optional. {"bidder name": ["seat 1", "seat 2"], ...}
* deviceId: Device id if available Optional.
* ifa: Advertising ID Optional.
* options Dynamic data Optional. Optional data to send into adapter.
*
* @param {BidRequest} bid The bid params to validate.
* @return boolean True if this is a valid bid, and false otherwise.
Expand Down Expand Up @@ -69,6 +70,7 @@ export const spec = {
gdprApplies: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.gdprApplies : undefined,
gdprConsent: bidderRequest.gdprConsent ? bidderRequest.gdprConsent.consentString : undefined,
cookieSupport: !utils.isSafariBrowser() && utils.cookiesAreEnabled(),
rcv: getAdblockerRecovered(),
adRequests: [...adRequests]
};
const payloadString = JSON.stringify(payload);
Expand Down Expand Up @@ -178,7 +180,8 @@ function bidToAdRequest(bid) {
callerAdUnitId: bid.params.adUnitName || bid.adUnitCode || bid.placementCode,
bidId: bid.bidId,
transactionId: bid.transactionId,
formats: bid.sizes.map(sizeToFormat)
formats: bid.sizes.map(sizeToFormat),
options: bid.params.options
};
}

Expand All @@ -189,4 +192,10 @@ function sizeToFormat(size) {
}
}

function getAdblockerRecovered() {
try {
return utils.getWindowTop().I12C && utils.getWindowTop().I12C.Morph === 1;
} catch (e) {}
}

registerBidder(spec);
55 changes: 52 additions & 3 deletions test/spec/modules/livewrappedAnalyticsAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CONSTANTS from 'src/constants.json';
import { config } from 'src/config';

let events = require('src/events');
let utils = require('src/utils');
let adapterManager = require('src/adapterManager').default;

const {
Expand All @@ -27,6 +28,7 @@ const BID1 = {
cpm: 1.1,
timeToRespond: 200,
bidId: '2ecff0db240757',
requestId: '2ecff0db240757',
adId: '2ecff0db240757',
auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa',
getStatusCode() {
Expand All @@ -40,9 +42,20 @@ const BID2 = Object.assign({}, BID1, {
cpm: 2.2,
timeToRespond: 300,
bidId: '3ecff0db240757',
requestId: '3ecff0db240757',
adId: '3ecff0db240757',
});

const BID3 = {
bidId: '4ecff0db240757',
requestId: '4ecff0db240757',
adId: '4ecff0db240757',
auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa',
getStatusCode() {
return CONSTANTS.STATUS.NO_BID;
}
};

const MOCK = {
AUCTION_INIT: {
'auctionId': '25c6d7f5-699a-4bfc-87c9-996f915341fa',
Expand All @@ -61,6 +74,11 @@ const MOCK = {
'bidder': 'livewrapped',
'adUnitCode': 'box_d_1',
'bidId': '3ecff0db240757',
},
{
'bidder': 'livewrapped',
'adUnitCode': 'box_d_2',
'bidId': '4ecff0db240757',
}
],
'start': 1519149562216
Expand All @@ -73,17 +91,20 @@ const MOCK = {
},
BID_WON: [
Object.assign({}, BID1, {
'status': 'rendered'
'status': 'rendered',
'requestId': '2ecff0db240757'
}),
Object.assign({}, BID2, {
'status': 'rendered'
'status': 'rendered',
'requestId': '3ecff0db240757'
})
],
BIDDER_DONE: {
'bidderCode': 'livewrapped',
'bids': [
BID1,
BID2
BID2,
BID3
]
},
BID_TIMEOUT: [
Expand All @@ -106,6 +127,11 @@ const ANALYTICS_MESSAGE = {
adUnit: 'box_d_1',
bidder: 'livewrapped',
timeStamp: 1519149562216
},
{
adUnit: 'box_d_2',
bidder: 'livewrapped',
timeStamp: 1519149562216
}
],
responses: [
Expand All @@ -128,6 +154,13 @@ const ANALYTICS_MESSAGE = {
cpm: 2.2,
ttr: 300,
IsBid: true
},
{
timeStamp: 1519149562216,
adUnit: 'box_d_2',
bidder: 'livewrapped',
ttr: 200,
IsBid: false
}
],
timeouts: [],
Expand Down Expand Up @@ -177,6 +210,7 @@ describe('Livewrapped analytics adapter', function () {
xhr.onCreate = request => requests.push(request);

sandbox.stub(events, 'getEvents').returns([]);
sandbox.stub(utils, 'timestamp').returns(1519149562416);

clock = sandbox.useFakeTimers(1519767013781);
});
Expand Down Expand Up @@ -206,6 +240,7 @@ describe('Livewrapped analytics adapter', function () {
});

it('should build a batched message from prebid events', function () {
sandbox.stub(utils, 'getWindowTop').returns({});
performStandardAuction();

clock.tick(BID_WON_TIMEOUT + 1000);
Expand Down Expand Up @@ -261,5 +296,19 @@ describe('Livewrapped analytics adapter', function () {
expect(message.timeouts[0].bidder).to.equal('livewrapped');
expect(message.timeouts[0].adUnit).to.equal('panorama_d_1');
});

it('should detect adblocker recovered request', function () {
sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } });
performStandardAuction();

clock.tick(BID_WON_TIMEOUT + 1000);

expect(requests.length).to.equal(1);
let request = requests[0];

let message = JSON.parse(request.requestBody);

expect(message.rcv).to.equal(true);
});
});
});
58 changes: 58 additions & 0 deletions test/spec/modules/livewrappedBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,64 @@ describe('Livewrapped adapter tests', function () {
expect(data).to.deep.equal(expectedQuery);
});

it('should make a well-formed single request object with optional parameters', function() {
sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
let testbidRequest = clone(bidderRequest);
delete testbidRequest.bids[0].params.userId;
delete testbidRequest.bids[0].params.seats;
delete testbidRequest.bids[0].params.adUnitId;
testbidRequest.bids[0].params.options = {keyvalues: [{key: 'key', value: 'value'}]};
let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
let data = JSON.parse(result.data);

let expectedQuery = {
auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
url: 'http://www.domain.com',
version: '1.1',
cookieSupport: true,
adRequests: [{
callerAdUnitId: 'panorama_d_1',
bidId: '2ffb201a808da7',
transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
formats: [{width: 980, height: 240}, {width: 980, height: 120}],
options: {keyvalues: [{key: 'key', value: 'value'}]}
}]
};

expect(data).to.deep.equal(expectedQuery);
});

it('should make a well-formed single request object with ad blocker revovered parameter', function() {
sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } });
sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
let testbidRequest = clone(bidderRequest);
delete testbidRequest.bids[0].params.userId;
delete testbidRequest.bids[0].params.seats;
delete testbidRequest.bids[0].params.adUnitId;
let result = spec.buildRequests(testbidRequest.bids, testbidRequest);
let data = JSON.parse(result.data);

let expectedQuery = {
auctionId: 'F7557995-65F5-4682-8782-7D5D34D82A8C',
publisherId: '26947112-2289-405D-88C1-A7340C57E63E',
url: 'http://www.domain.com',
version: '1.1',
cookieSupport: true,
rcv: true,
adRequests: [{
callerAdUnitId: 'panorama_d_1',
bidId: '2ffb201a808da7',
transactionId: '3D1C8CF7-D288-4D7F-8ADD-97C553056C3D',
formats: [{width: 980, height: 240}, {width: 980, height: 120}]
}]
};

expect(data).to.deep.equal(expectedQuery);
});

it('should pass gdpr true parameters', function() {
sandbox.stub(utils, 'isSafariBrowser').callsFake(() => false);
sandbox.stub(utils, 'cookiesAreEnabled').callsFake(() => true);
Expand Down

0 comments on commit ec58183

Please sign in to comment.