-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 || {}; | ||
let _customUserSync; | ||
|
||
function isBidRequestValid(bid) { | ||
if (typeof bid.params !== 'object') { | ||
return false; | ||
|
@@ -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)) { | ||
|
@@ -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 []; | ||
|
@@ -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); | ||
} | ||
|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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++) { | ||
|
@@ -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(); | ||
|
@@ -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; | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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