Skip to content

Commit

Permalink
Add referer detection module (prebid#3067)
Browse files Browse the repository at this point in the history
* add referere detection module

* dont log all errors on console

* Update message

* Add jsdoc
  • Loading branch information
jaiminpanchal27 authored and jsnellbaker committed Sep 12, 2018
1 parent f272031 commit 079e27f
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 3 deletions.
10 changes: 10 additions & 0 deletions modules/appnexusBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ export const spec = {
};
}

if (bidderRequest && bidderRequest.refererInfo) {
let refererinfo = {
rd_ref: bidderRequest.refererInfo.referer,
rd_top: bidderRequest.refererInfo.reachedTop,
rd_ifs: bidderRequest.refererInfo.numIframes,
rd_stk: bidderRequest.refererInfo.stack.join(',')
}
payload.referrer_detection = refererinfo;
}

const payloadString = JSON.stringify(payload);
return {
method: 'POST',
Expand Down
10 changes: 7 additions & 3 deletions src/adaptermanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { config, RANDOM } from 'src/config';
import includes from 'core-js/library/fn/array/includes';
import find from 'core-js/library/fn/array/find';
import { adunitCounter } from './adUnits';
import { getRefererInfo } from './refererDetection';

var utils = require('./utils.js');
var CONSTANTS = require('./constants.json');
Expand Down Expand Up @@ -97,7 +98,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels}) {
bidId: bid.bid_id || utils.getUniqueIdentifierStr(),
bidderRequestId,
auctionId,
bidRequestsCount: adunitCounter.getCounter(adUnit.code)
bidRequestsCount: adunitCounter.getCounter(adUnit.code),
}));
}
return bids;
Expand Down Expand Up @@ -165,6 +166,7 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
if (config.getConfig('bidderSequence') === RANDOM) {
bidderCodes = shuffle(bidderCodes);
}
const refererInfo = getRefererInfo();

let clientBidderCodes = bidderCodes;
let clientTestAdapters = [];
Expand Down Expand Up @@ -195,7 +197,8 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsS2SCopy, labels}),
auctionStart: auctionStart,
timeout: _s2sConfig.timeout,
src: CONSTANTS.S2S.SRC
src: CONSTANTS.S2S.SRC,
refererInfo
};
if (bidderRequest.bids.length !== 0) {
bidRequests.push(bidderRequest);
Expand Down Expand Up @@ -228,7 +231,8 @@ exports.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout,
bidderRequestId,
bids: getBids({bidderCode, auctionId, bidderRequestId, 'adUnits': adUnitsClientCopy, labels}),
auctionStart: auctionStart,
timeout: cbTimeout
timeout: cbTimeout,
refererInfo
};
const adapter = _bidderRegistry[bidderCode];
if (!adapter) {
Expand Down
152 changes: 152 additions & 0 deletions src/refererDetection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { logWarn } from './utils';

export function detectReferer(win) {
function getLevels() {
let levels = walkUpWindows();
let ancestors = getAncestorOrigins();

if (ancestors) {
for (let i = 0, l = ancestors.length; i < l; i++) {
levels[i].ancestor = ancestors[i];
}
}
return levels;
}

function getAncestorOrigins() {
try {
if (!win.location.ancestorOrigins) {
return;
}
return win.location.ancestorOrigins;
} catch (e) {
// Ignore error
}
}

function getPubUrlStack(levels) {
let stack = [];
let defUrl = null;
let encodedUrl = null;
let frameLocation = null;
let prevFrame = null;
let prevRef = null;
let ancestor = null;
let detectedRefererUrl = null;

let i;
for (i = levels.length - 1; i >= 0; i--) {
try {
frameLocation = levels[i].location;
} catch (e) {
// Ignore error
}

if (frameLocation) {
encodedUrl = encodeURIComponent(frameLocation);
stack.push(encodedUrl);
if (!detectedRefererUrl) {
detectedRefererUrl = encodedUrl;
}
} else if (i !== 0) {
prevFrame = levels[i - 1];
try {
prevRef = prevFrame.referrer;
ancestor = prevFrame.ancestor;
} catch (e) {
// Ignore error
}

if (prevRef) {
encodedUrl = encodeURIComponent(prevRef);
stack.push(encodedUrl);
if (!detectedRefererUrl) {
detectedRefererUrl = encodedUrl;
}
} else if (ancestor) {
encodedUrl = encodeURIComponent(ancestor);
stack.push(encodedUrl);
if (!detectedRefererUrl) {
detectedRefererUrl = encodedUrl;
}
} else {
stack.push(defUrl);
}
} else {
stack.push(defUrl);
}
}
return {
stack,
detectedRefererUrl
};
}

function walkUpWindows() {
let acc = [];
let currentWindow;
do {
try {
currentWindow = currentWindow ? currentWindow.parent : win;
try {
acc.push({
referrer: currentWindow.document.referrer || null,
location: currentWindow.location.href || null,
isTop: (currentWindow == win.top)
});
} catch (e) {
acc.push({
referrer: null,
location: null,
isTop: (currentWindow == win.top)
});
logWarn('Trying to access cross domain iframe. Continuing without referrer and location');
}
} catch (e) {
acc.push({
referrer: null,
location: null,
isTop: false
});
return acc;
}
} while (currentWindow != win.top);
return acc;
}

/**
* Referer info
* @typedef {Object} refererInfo
* @property {string} referer detected top url
* @property {boolean} reachedTop whether prebid was able to walk upto top window or not
* @property {number} numIframes number of iframes
* @property {string} stack comma separated urls of all origins
*/

/**
* Get referer info
* @returns {refererInfo}
*/
function refererInfo() {
try {
let levels = getLevels();
let numIframes = levels.length - 1;
let reachedTop = (levels[numIframes].location !== null ||
(numIframes > 0 && levels[numIframes - 1].referrer !== null));
let stackInfo = getPubUrlStack(levels);

return {
referer: stackInfo.detectedRefererUrl,
reachedTop,
numIframes,
stack: stackInfo.stack,
};
} catch (e) {
// Ignore error
}
}

return refererInfo;
}

export const getRefererInfo = detectReferer(window);
26 changes: 26 additions & 0 deletions test/spec/modules/appnexusBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,32 @@ describe('AppNexusAdapter', function () {
lng: -75.3009142
});
});

it('should add referer info to payload', function () {
const bidRequest = Object.assign({}, bidRequests[0])
const bidderRequest = {
refererInfo: {
referer: 'http%3A%2F%2Fexample.com%2Fpage.html',
reachedTop: true,
numIframes: 2,
stack: [
'http%3A%2F%2Fexample.com%2Fpage.html',
'http%3A%2F%2Fexample.com%2Fiframe1.html',
'http%3A%2F%2Fexample.com%2Fiframe2.html'
]
}
}
const request = spec.buildRequests([bidRequest], bidderRequest);
const payload = JSON.parse(request.data);

expect(payload.referrer_detection).to.exist;
expect(payload.referrer_detection).to.deep.equal({
rd_ref: 'http%3A%2F%2Fexample.com%2Fpage.html',
rd_top: true,
rd_ifs: 2,
rd_stk: bidderRequest.refererInfo.stack.join(',')
});
});
})

describe('interpretResponse', function () {
Expand Down
80 changes: 80 additions & 0 deletions test/spec/refererDetection_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { detectReferer } from 'src/refererDetection';
import { expect } from 'chai';

var mocks = {
createFakeWindow: function (referrer, href) {
return {
document: {
referrer: referrer
},
location: {
href: href,
// TODO: add ancestorOrigins to increase test coverage
},
parent: null,
top: null
};
}
}

describe('referer detection', () => {
it('should return referer details in nested friendly iframes', function() {
// Fake window object to test friendly iframes
// - Main page http://example.com/page.html
// - - Iframe1 http://example.com/iframe1.html
// - - - Iframe2 http://example.com/iframe2.html
let mockIframe2WinObject = mocks.createFakeWindow('http://example.com/iframe1.html', 'http://example.com/iframe2.html');
let mockIframe1WinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/iframe1.html');
let mainWinObject = mocks.createFakeWindow('http://example.com/page.html', 'http://example.com/page.html');
mockIframe2WinObject.parent = mockIframe1WinObject;
mockIframe2WinObject.top = mainWinObject;
mockIframe1WinObject.parent = mainWinObject;
mockIframe1WinObject.top = mainWinObject;
mainWinObject.top = mainWinObject;

const getRefererInfo = detectReferer(mockIframe2WinObject);
let result = getRefererInfo();
let expectedResult = {
referer: 'http%3A%2F%2Fexample.com%2Fpage.html',
reachedTop: true,
numIframes: 2,
stack: [
'http%3A%2F%2Fexample.com%2Fpage.html',
'http%3A%2F%2Fexample.com%2Fiframe1.html',
'http%3A%2F%2Fexample.com%2Fiframe2.html'
]
};
expect(result).to.deep.equal(expectedResult);
});

it('should return referer details in nested cross domain iframes', function() {
// Fake window object to test cross domain iframes.
// - Main page http://example.com/page.html
// - - Iframe1 http://aaa.com/iframe1.html
// - - - Iframe2 http://bbb.com/iframe2.html
let mockIframe2WinObject = mocks.createFakeWindow('http://aaa.com/iframe1.html', 'http://bbb.com/iframe2.html');
// Sinon cannot throw exception when accessing a propery so passing null to create cross domain
// environment for refererDetection module
let mockIframe1WinObject = mocks.createFakeWindow(null, null);
let mainWinObject = mocks.createFakeWindow(null, null);
mockIframe2WinObject.parent = mockIframe1WinObject;
mockIframe2WinObject.top = mainWinObject;
mockIframe1WinObject.parent = mainWinObject;
mockIframe1WinObject.top = mainWinObject;
mainWinObject.top = mainWinObject;

const getRefererInfo = detectReferer(mockIframe2WinObject);
let result = getRefererInfo();
let expectedResult = {
referer: 'http%3A%2F%2Faaa.com%2Fiframe1.html',
reachedTop: false,
numIframes: 2,
stack: [
null,
'http%3A%2F%2Faaa.com%2Fiframe1.html',
'http%3A%2F%2Fbbb.com%2Fiframe2.html'
]
};
expect(result).to.deep.equal(expectedResult);
});
});

0 comments on commit 079e27f

Please sign in to comment.