diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index ab6f7c4898b..eefe63034bd 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -34,11 +34,14 @@ 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 }; } } @@ -46,6 +49,11 @@ export const spec = { 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,7 +70,7 @@ 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) { @@ -70,15 +78,16 @@ function buildRequest(bidRequests, auctionStart) { _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(); @@ -86,13 +95,12 @@ function buildRequest(bidRequests, auctionStart) { 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 }), @@ -100,9 +108,15 @@ function buildRequest(bidRequests, auctionStart) { 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,9 +310,9 @@ function getCappedCampaignsAsString() { .join(','); } -function initLogger() { - const noop = function () { }; +const noop = function () { }; +function initLogger() { if (localStorage && localStorage.InvibesDEBUG) { return window.console; } @@ -310,25 +320,59 @@ function initLogger() { 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) { + 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; +} diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 646448bd5a7..f6f601e0efc 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/invibesBidAdapter'; +import { spec, resetInvibes } from 'modules/invibesBidAdapter'; describe('invibesBidAdapter:', function () { const BIDDER_CODE = 'invibes'; @@ -41,7 +41,7 @@ describe('invibesBidAdapter:', function () { ]; beforeEach(function () { - top.window.invibes = null; + resetInvibes(); document.cookie = ''; this.cStub1 = sinon.stub(console, 'info'); }); @@ -120,30 +120,47 @@ describe('invibesBidAdapter:', function () { expect(request.data.lId).to.be.undefined; }); - it('does not overwrite the domain id', () => { - top.window.invibes = window.invibes || {}; - top.window.invibes.dom = {}; - let request = spec.buildRequests(bidRequests); - }); - it('doesnt send the domain id if not graduated', () => { - global.document.cookie = 'ivbsdid={"id":"p4vauj.4ekt9w","hc":3,"temp":1}'; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1522929537626,"hc":1}'; let request = spec.buildRequests(bidRequests); expect(request.data.lId).to.not.exist; }); it('graduate and send the domain id', () => { - global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s","hc":4,"temp":1}'; + top.window.invibes.optIn = 1; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":7}'; let request = spec.buildRequests(bidRequests); expect(request.data.lId).to.exist; }); it('send the domain id if already graduated', () => { - global.document.cookie = 'ivbsdid={"id":"p4rrk7.ax2i2s"}'; + top.window.invibes.optIn = 1; + global.document.cookie = 'ivbsdid={"id":"f8zoh044p9oi"}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.exist; + }); + + it('send the domain id after replacing it with new format', () => { + top.window.invibes.optIn = 1; + global.document.cookie = 'ivbsdid={"id":"f8zoh044p9oi.8537626"}'; let request = spec.buildRequests(bidRequests); expect(request.data.lId).to.exist; }); - }) + + it('try to graduate but not enough count - doesnt send the domain id', () => { + top.window.invibes.optIn = 1; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":5}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.not.exist; + }); + + it('try to graduate but not old enough - doesnt send the domain id', () => { + top.window.invibes.optIn = 1; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":' + Date.now() + ',"hc":5}'; + let request = spec.buildRequests(bidRequests); + expect(request.data.lId).to.not.exist; + }); + }); describe('interpretResponse', function () { let response = { @@ -238,7 +255,18 @@ describe('invibesBidAdapter:', function () { it('returns an iframe if enabled', () => { let response = spec.getUserSyncs({iframeEnabled: true}); expect(response.type).to.equal('iframe'); - expect(response.url).to.equal(SYNC_ENDPOINT); + expect(response.url).to.include(SYNC_ENDPOINT); + }); + + it('returns an iframe with params if enabled', () => { + top.window.invibes.optIn = 1; + global.document.cookie = 'ivvbks=17639.0,1,2'; + let response = spec.getUserSyncs({ iframeEnabled: true }); + expect(response.type).to.equal('iframe'); + expect(response.url).to.include(SYNC_ENDPOINT); + expect(response.url).to.include('optIn'); + expect(response.url).to.include('ivvbks'); + expect(response.url).to.include('ivbsdid'); }); it('returns undefined if iframe not enabled ', () => {