Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invibes Bid Adapter - cookies update & user sync #2512

Merged
merged 1 commit into from
May 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 121 additions & 61 deletions modules/invibesBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,26 @@ export const spec = {
interpretResponse: function (responseObj, requestParams) {
return handleResponse(responseObj, requestParams != null ? requestParams.bidRequests : null);
},
getUserSyncs: function(syncOptions) {
getUserSyncs: function (syncOptions) {
if (syncOptions.iframeEnabled) {
handlePostMessage();
const syncUrl = buildSyncUrl();

return {
type: 'iframe',
url: CONSTANTS.SYNC_ENDPOINT
url: syncUrl
};
}
}
};

registerBidder(spec);

// some state info is required: cookie info, unique user visit id
const topWin = getTopMostWindow();
let invibes = topWin.invibes = topWin.invibes || {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for creating the invibes object on the top window and attaching some of the functions below to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The location is on the top window so that we don't initialize our local id on this page twice. The cookie info and the id are passed to the user sync function. Also the functions for cookies are reused inside the postMessage, so it made sense to just put them all inside the same object

let _customUserSync;

function isBidRequestValid(bid) {
if (typeof bid.params !== 'object') {
return false;
Expand All @@ -62,47 +70,53 @@ function isBidRequestValid(bid) {
function buildRequest(bidRequests, auctionStart) {
// invibes only responds to 1 bid request for each user visit
const _placementIds = [];
let _loginId, _customEndpoint, _bidContainerId;
let _loginId, _customEndpoint;
let _ivAuctionStart = auctionStart || Date.now();

bidRequests.forEach(function (bidRequest) {
bidRequest.startTime = new Date().getTime();
_placementIds.push(bidRequest.params.placementId);
_loginId = _loginId || bidRequest.params.loginId;
_customEndpoint = _customEndpoint || bidRequest.params.customEndpoint;
_bidContainerId = _bidContainerId || bidRequest.params.adContainerId || bidRequest.params.bidContainerId;
_customUserSync = _customUserSync || bidRequest.params.customUserSync;
});

const topWin = getTopMostWindow();
const invibes = topWin.invibes = topWin.invibes || {};
invibes.visitId = invibes.visitId || generateRandomId();
invibes.bidContainerId = invibes.bidContainerId || _bidContainerId;

initDomainId(invibes);
cookieDomain = detectTopmostCookieDomain();
invibes.noCookies = invibes.noCookies || invibes.getCookie('ivNoCookie');
invibes.optIn = invibes.optIn || invibes.getCookie('ivOptIn');

initDomainId();

const currentQueryStringParams = parseQueryStringParams();

let data = {
location: getDocumentLocation(topWin),
videoAdHtmlId: generateRandomId(),
showFallback: currentQueryStringParams['advs'] === '0',
ivbsCampIdsLocal: getCookie('IvbsCampIdsLocal'),
ivbsCampIdsLocal: invibes.getCookie('IvbsCampIdsLocal'),
lId: invibes.dom.id,

bidParamsJson: JSON.stringify({
placementIds: _placementIds,
loginId: _loginId,
bidContainerId: _bidContainerId,
auctionStartTime: _ivAuctionStart,
bidVersion: CONSTANTS.PREBID_VERSION
}),
capCounts: getCappedCampaignsAsString(),

vId: invibes.visitId,
width: topWin.innerWidth,
height: topWin.innerHeight
height: topWin.innerHeight,

noc: !cookieDomain
};

if (invibes.optIn) {
data.oi = 1;
}

const parametersToPassForward = 'videoaddebug,advs,bvci,bvid,istop,trybvid,trybvci'.split(',');
for (let key in currentQueryStringParams) {
if (currentQueryStringParams.hasOwnProperty(key)) {
Expand Down Expand Up @@ -143,9 +157,6 @@ function handleResponse(responseObj, bidRequests) {
return [];
}

const topWin = getTopMostWindow();
const invibes = topWin.invibes = topWin.invibes || {};

if (typeof invibes.bidResponse === 'object') {
utils.logInfo('Invibes Adapter - Bid response already received. Invibes only responds to one bid request per user visit');
return [];
Expand Down Expand Up @@ -191,8 +202,7 @@ function handleResponse(responseObj, bidRequests) {
});

const now = Date.now();
invibes.ivLogger = invibes.ivLogger || initLogger();
invibes.ivLogger.info('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.');
ivLogger.info('Bid auction started at ' + bidModel.AuctionStartTime + ' . Invibes registered the bid at ' + now + ' ; bid request took a total of ' + (now - bidModel.AuctionStartTime) + ' ms.');
} else {
utils.logInfo('Invibes Adapter - Incorrect Placement Id: ' + bidRequest.params.placementId);
}
Expand Down Expand Up @@ -300,35 +310,69 @@ function getCappedCampaignsAsString() {
.join(',');
}

function initLogger() {
const noop = function () { };
const noop = function () { };

function initLogger() {
if (localStorage && localStorage.InvibesDEBUG) {
return window.console;
}

return { info: noop, error: noop, log: noop, warn: noop, debug: noop };
}

function buildSyncUrl() {
let syncUrl = _customUserSync || CONSTANTS.SYNC_ENDPOINT;
syncUrl += '?visitId=' + invibes.visitId;

if (invibes.optIn) {
syncUrl += '&optIn=1';
}

const did = invibes.getCookie('ivbsdid');
if (did) {
syncUrl += '&ivbsdid=' + encodeURIComponent(did);
}

const bks = invibes.getCookie('ivvbks');
if (bks) {
syncUrl += '&ivvbks=' + encodeURIComponent(bks);
}

return syncUrl;
}

function handlePostMessage() {
try {
if (window.addEventListener) {
window.addEventListener('message', acceptPostMessage);
}
} catch (e) { }
}

function acceptPostMessage(e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason the this message? It's not clear to me why the caller does take care of setting cookies, etc and why the code to do so is inside your Prebid adapter.

Copy link
Contributor Author

@rcheptanariu rcheptanariu May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user sync happens in the iframe, on a different domain than the initial page. We need to update our local id if the user sync was successful, so the only way to do this was with a postMessage from the iframe.

let msg = e.data || {};
if (msg.ivbscd === 1) {
invibes.setCookie(msg.name, msg.value, msg.exdays, msg.domain);
} else if (msg.ivbscd === 2) {
invibes.dom.graduate();
}
}

const ivLogger = initLogger();

/// Local domain cookie management =====================
let Uid = {
invibes.Uid = {
generate: function () {
let date = new Date().getTime();
if (date > 151 * 10e9) {
let datePart = Math.floor(date / 1000).toString(36);
let maxRand = parseInt('zzzzzz', 36)
let randPart = Math.floor(Math.random() * maxRand).toString(36);
return datePart + '.' + randPart;
}
},
getCrTime: function (s) {
let toks = s.split('.');
return parseInt(toks[0] || 0, 36) * 1e3;
let maxRand = parseInt('zzzzzz', 36)
let mkRand = function () { return Math.floor(Math.random() * maxRand).toString(36); };
let rand1 = mkRand();
let rand2 = mkRand();
return rand1 + rand2;
}
};

let cookieDomain, noCookies;
function getCookie(name) {
let cookieDomain;
invibes.getCookie = function (name) {
let i, x, y;
let cookies = document.cookie.split(';');
for (i = 0; i < cookies.length; i++) {
Expand All @@ -341,8 +385,9 @@ function getCookie(name) {
}
};

function setCookie(name, value, exdays, domain) {
if (noCookies && name != 'ivNoCookie' && (exdays || 0) >= 0) { return; }
invibes.setCookie = function (name, value, exdays, domain) {
let whiteListed = name == 'ivNoCookie' || name == 'IvbsCampIdsLocal';
if (invibes.noCookies && !whiteListed && (exdays || 0) >= 0) { return; }
if (exdays > 365) { exdays = 365; }
domain = domain || cookieDomain;
let exdate = new Date();
Expand All @@ -354,86 +399,101 @@ function setCookie(name, value, exdays, domain) {
};

let detectTopmostCookieDomain = function () {
let testCookie = Uid.generate();
let testCookie = invibes.Uid.generate();
let hostParts = location.host.split('.');
if (hostParts.length === 1) {
return location.host;
}
for (let i = hostParts.length - 1; i >= 0; i--) {
let domain = '.' + hostParts.slice(i).join('.');
setCookie(testCookie, testCookie, 1, domain);
let val = getCookie(testCookie);
invibes.setCookie(testCookie, testCookie, 1, domain);
let val = invibes.getCookie(testCookie);
if (val === testCookie) {
setCookie(testCookie, testCookie, -1, domain);
invibes.setCookie(testCookie, testCookie, -1, domain);
return domain;
}
}
};
cookieDomain = detectTopmostCookieDomain();
noCookies = getCookie('ivNoCookie');

function initDomainId(invibes, persistence) {
if (typeof invibes.dom === 'object') {
return;
}
let initDomainId = function (options) {
if (invibes.dom) { return; }

options = options || {};

let cookiePersistence = {
cname: 'ivbsdid',
load: function () {
let str = getCookie(this.cname) || '';
let str = invibes.getCookie(this.cname) || '';
try {
return JSON.parse(str);
} catch (e) { }
},
save: function (obj) {
setCookie(this.cname, JSON.stringify(obj), 365);
invibes.setCookie(this.cname, JSON.stringify(obj), 365);
}
};

persistence = persistence || cookiePersistence;
let persistence = options.persistence || cookiePersistence;
let state;
const minHC = 5;
let minHC = 7;

let validGradTime = function (d) {
const min = 151 * 10e9;
let yesterday = new Date().getTime() - 864 * 10e4
return d > min && d < yesterday;
let validGradTime = function (state) {
if (!state.cr) { return false; }
let min = 151 * 10e9;
if (state.cr < min) {
return false;
}
let now = new Date().getTime();
let age = now - state.cr;
let minAge = 24 * 60 * 60 * 1000;
return age > minAge;
};

state = persistence.load() || {
id: Uid.generate(),
id: invibes.Uid.generate(),
cr: new Date().getTime(),
hc: 1,
temp: 1
};

if (state.id.match(/\./)) {
state.id = invibes.Uid.generate();
}

let graduate;

let setId = function () {
invibes.dom = {
id: state.temp ? undefined : state.id,
tempId: state.id,
id: !state.cr && invibes.optIn ? state.id : undefined,
tempId: invibes.optIn ? state.id : undefined,
graduate: graduate
};
};

graduate = function () {
if (!state.temp) { return; }
delete state.temp;
if (!state.cr) { return; }
delete state.cr;
delete state.hc;
persistence.save(state);
setId();
}

if (state.temp) {
if (state.cr && !options.noVisit) {
if (state.hc < minHC) {
state.hc++;
}
if (state.hc >= minHC && validGradTime(Uid.getCrTime(state.id))) {
if (state.hc >= minHC && validGradTime(state)) {
graduate();
}
}

persistence.save(state);
setId();
ivLogger.info('Did=' + invibes.dom.id);
};
// =====================

export function resetInvibes() {
invibes.optIn = undefined;
invibes.noCookies = undefined;
invibes.dom = undefined;
invibes.bidResponse = undefined;
}
Loading