From 0880b3941151974693d9cd2bff572e6823756d93 Mon Sep 17 00:00:00 2001 From: Sergey Derbush Date: Mon, 1 Nov 2021 23:15:24 +0600 Subject: [PATCH] Add insticator analytics adapter - add event AD_RENDER_SUCCEEDED --- modules.json | 1 + modules/insticatorAnalyticsAdapter.js | 277 ++++++++++++++++++++++++++ modules/insticatorAnalyticsAdapter.md | 20 ++ modules/insticatorBidAdapter.js | 10 +- src/AnalyticsAdapter.js | 2 + src/constants.json | 1 + src/prebid.js | 13 +- 7 files changed, 322 insertions(+), 2 deletions(-) create mode 100644 modules/insticatorAnalyticsAdapter.js create mode 100644 modules/insticatorAnalyticsAdapter.md diff --git a/modules.json b/modules.json index fe3537a4ae4..edd3740028d 100644 --- a/modules.json +++ b/modules.json @@ -13,6 +13,7 @@ "gumgumBidAdapter", "improvedigitalBidAdapter", "insticatorBidAdapter", + "insticatorAnalyticsAdapter", "ixBidAdapter", "mediagoBidAdapter", "onetagBidAdapter", diff --git a/modules/insticatorAnalyticsAdapter.js b/modules/insticatorAnalyticsAdapter.js new file mode 100644 index 00000000000..be66f592847 --- /dev/null +++ b/modules/insticatorAnalyticsAdapter.js @@ -0,0 +1,277 @@ +import adapter from '../src/AnalyticsAdapter.js' +import adapterManager from '../src/adapterManager.js' +import CONSTANTS from '../src/constants.json' +import * as utils from '../src/utils.js' +import { ajax } from '../src/ajax.js' + +const baseUrl = 'https://tr.ingage.tech/' +const ENDPOINTS = { + AD_RENDER_FAILED: baseUrl + 'com.snowplowanalytics.iglu/v1?schema=iglu%3Acom.insticator%2Frender_failed%2Fjsonschema%2F3-0-0', + AD_RENDER_SUCCEEDED: baseUrl + 'com.snowplowanalytics.iglu/v1?schema=iglu%3Acom.insticator%2Frender_succeeded%2Fjsonschema%2F3-0-1', + BID_WON: baseUrl + 'com.snowplowanalytics.iglu/v1?schema=iglu%3Acom.insticator%2Fbid_won%2Fjsonschema%2F3-0-0' +} + +const analyticsType = 'endpoint' +const ADAPTER_CODE = 'insticator' + +const { + AUCTION_INIT, + BID_REQUESTED, + BID_RESPONSE, + BID_WON, + AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED +} = CONSTANTS.EVENTS + +const SERVER_EVENTS = { + AD_RENDER_FAILED: 'adRenderFailed', + AD_RENDER_SUCCEEDED: 'adRenderSucceeded', + WON: 'bidWon' +} + +const SERVER_BID_STATUS = { + BID_REQUESTED: 'bidRequested', + BID_RECEIVED: 'bidReceived', + BID_WON: 'bidWon' +} + +let auctions = {} + +const onAuctionInit = (args) => { + const { auctionId, adUnits, timestamp } = args + + let auction = auctions[auctionId] = { + ...args, + adUnits: {}, + auctionStart: timestamp, + } + + utils._each(adUnits, adUnit => { + auction.adUnits[adUnit.code] = { + ...adUnit, + auctionId, + adunid: adUnit.code, + bids: {}, + } + }) +} + +const onBidRequested = (args) => { + const { auctionId, bids, start, timeout } = args + const _start = start || Date.now() + const auction = auctions[auctionId] + const auctionAdUnits = auction.adUnits + + bids.forEach(bid => { + const { adUnitCode } = bid + const bidId = parseBidId(bid) + + auctionAdUnits[adUnitCode].bids[bidId] = { + ...bid, + timeout, + start: _start, + rs: _start - auction.auctionStart, + bidStatus: SERVER_BID_STATUS.BID_REQUESTED, + } + }) +} + +const onBidResponse = (args) => { + const { auctionId, adUnitCode } = args + const auction = auctions[auctionId] + const bidId = parseBidId(args) + let bid = auction.adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_RECEIVED, + end: args.responseTimestamp, + re: args.responseTimestamp - auction.auctionStart + }) +} + +const onBidWon = (args) => { + const { auctionId, adUnitCode } = args + const bidId = parseBidId(args) + const bid = auctions[auctionId].adUnits[adUnitCode].bids[bidId] + + Object.assign(bid, args, { + bidStatus: SERVER_BID_STATUS.BID_WON, + isW: true, + isH: true + }) + + const payload = { + auctionId, + adunid: adUnitCode, + bid: mapBid(bid, BID_WON) + } + + sendEvent(SERVER_EVENTS.WON, payload) +} + +const onAdRenderFailed = (args) => { + const { bid } = args + let data = { + timestamp: Date.now() + } + + if (bid) { + data.bid = mapBid(bid, AD_RENDER_FAILED) + } + + sendEvent(SERVER_EVENTS.AD_RENDER_FAILED, data) +} + +const onAdRenderSucceeded = (args) => { + const { bid } = args + let data = { + timestamp: Date.now() + } + + if (bid) { + data.bid = mapBid(bid, AD_RENDER_SUCCEEDED) + } + + sendEvent(SERVER_EVENTS.AD_RENDER_SUCCEEDED, data) +} + +var insticatorAdapter = Object.assign( + adapter({ analyticsType }), { + track({ eventType, args }) { + handleEvent(eventType, args) + } + } +) + +function handleEvent(eventType, args) { + switch (eventType) { + case AUCTION_INIT: + onAuctionInit(args) + break + case BID_REQUESTED: + onBidRequested(args) + break + case BID_RESPONSE: + onBidResponse(args) + break + case BID_WON: + onBidWon(args) + break + case AD_RENDER_FAILED: + onAdRenderFailed(args) + break + case AD_RENDER_SUCCEEDED: + onAdRenderSucceeded(args) + break + } +} + +function sendEvent(eventType, data) { + // let data = utils.deepClone(args) + let payload = { + eventType, + domain: window.location.hostname + } + if (data.bid) { + payload.bid = data.bid + } + if (data.timestamp) { + payload.timestamp = data.timestamp + } + if (data.auctionId) { + payload.auctionId = data.auctionId + } + if (data.adunid) { + payload.adunid = data.adunid + } + let endpoint + if (eventType === SERVER_EVENTS.AD_RENDER_FAILED) { + endpoint = ENDPOINTS.AD_RENDER_FAILED + } else if (eventType === SERVER_EVENTS.WON) { + endpoint = ENDPOINTS.BID_WON + } else if (eventType === SERVER_EVENTS.AD_RENDER_SUCCEEDED) { + endpoint = ENDPOINTS.AD_RENDER_SUCCEEDED + } + + if (endpoint) { + ajaxCall(endpoint, () => { }, JSON.stringify(payload), {}) + } +} + +function parseBidId(bid) { + return bid.bidId || bid.requestId +} + +function mapBid({ + bidStatus, + start, + end, + mediaType, + creativeId, + originalCpm, + originalCurrency, + source, + netRevenue, + currency, + width, + height, + timeToRespond, + responseTimestamp, + ...rest +}, eventType) { + const bidObj = { + bst: bidStatus, + s: start, + e: responseTimestamp || end, + mt: mediaType, + crId: creativeId, + oCpm: originalCpm, + oCur: originalCurrency, + src: source, + nrv: netRevenue, + cur: currency, + w: width, + h: height, + ttr: timeToRespond, + ...rest, + } + + delete bidObj['bidRequestsCount'] + delete bidObj['bidderRequestId'] + delete bidObj['bidderRequestsCount'] + delete bidObj['bidderWinsCount'] + delete bidObj['schain'] + delete bidObj['refererInfo'] + delete bidObj['statusMessage'] + delete bidObj['status'] + delete bidObj['adUrl'] + // delete bidObj['ad'] + delete bidObj['usesGenericKeys'] + delete bidObj['requestTimestamp'] + return bidObj +} + +function ajaxCall(endpoint, callback, data, options = {}) { + options.contentType = 'application/json' + + return ajax(endpoint, callback, data, options) +} + +insticatorAdapter.originEnableAnalytics = insticatorAdapter.enableAnalytics +insticatorAdapter.enableAnalytics = function (config) { + // initOptions = config.options; + insticatorAdapter.originEnableAnalytics(config) +}; + +insticatorAdapter.originDisableAnalytics = insticatorAdapter.disableAnalytics +insticatorAdapter.disableAnalytics = function () { + auctions = {} + insticatorAdapter.originDisableAnalytics() +} + +adapterManager.registerAnalyticsAdapter({ + adapter: insticatorAdapter, + code: ADAPTER_CODE +}) + +export default insticatorAdapter diff --git a/modules/insticatorAnalyticsAdapter.md b/modules/insticatorAnalyticsAdapter.md new file mode 100644 index 00000000000..6e0582a84d3 --- /dev/null +++ b/modules/insticatorAnalyticsAdapter.md @@ -0,0 +1,20 @@ +# Overview + +``` +Module Name: Insticator Analytics Adapter +Module Type: Analytics Adapter +Maintainer: contact@insticator.com +``` + +# Description + +Insticator's analytics adapter pushes prebid events into tracker. + +For more information, visit Insticator.com. + +# Test Parameters +``` +{ + provider: 'insticator' +} +``` diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 5b2135baa6c..69d95453abf 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -147,13 +147,21 @@ function buildRequest(validBidRequests, bidderRequest) { regs: buildRegs(bidderRequest), user: buildUser(), imp: validBidRequests.map((bidRequest) => buildImpression(bidRequest)), + ext: { + insticator: { + adapter: { + vendor: 'prebid', + prebid: '$prebid.version$' + } + } + } }; const params = config.getConfig('insticator.params'); if (params) { req.ext = { - insticator: params, + insticator: {...req.ext.insticator, ...params}, }; } diff --git a/src/AnalyticsAdapter.js b/src/AnalyticsAdapter.js index 80c12a3eb8e..97513b80cc7 100644 --- a/src/AnalyticsAdapter.js +++ b/src/AnalyticsAdapter.js @@ -18,6 +18,7 @@ const { BIDDER_DONE, SET_TARGETING, AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, AUCTION_DEBUG, ADD_AD_UNITS } @@ -113,6 +114,7 @@ export default function AnalyticsAdapter({ url, analyticsType, global, handler } [SET_TARGETING]: args => this.enqueue({ eventType: SET_TARGETING, args }), [AUCTION_END]: args => this.enqueue({ eventType: AUCTION_END, args }), [AD_RENDER_FAILED]: args => this.enqueue({ eventType: AD_RENDER_FAILED, args }), + [AD_RENDER_SUCCEEDED]: args => this.enqueue({ eventType: AD_RENDER_SUCCEEDED, args }), [AUCTION_DEBUG]: args => this.enqueue({ eventType: AUCTION_DEBUG, args }), [ADD_AD_UNITS]: args => this.enqueue({ eventType: ADD_AD_UNITS, args }), [AUCTION_INIT]: args => { diff --git a/src/constants.json b/src/constants.json index c43e88cf75f..0ecd9b1ac5b 100644 --- a/src/constants.json +++ b/src/constants.json @@ -37,6 +37,7 @@ "REQUEST_BIDS": "requestBids", "ADD_AD_UNITS": "addAdUnits", "AD_RENDER_FAILED": "adRenderFailed", + "AD_RENDER_SUCCEEDED": "adRenderSucceeded", "TCF2_ENFORCEMENT": "tcf2Enforcement", "AUCTION_DEBUG": "auctionDebug", "BID_VIEWABLE": "bidViewable", diff --git a/src/prebid.js b/src/prebid.js index f58c97ec581..07a6563cb48 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -23,7 +23,7 @@ const events = require('./events.js'); const { triggerUserSyncs } = userSync; /* private variables */ -const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED, STALE_RENDER } = CONSTANTS.EVENTS; +const { ADD_AD_UNITS, BID_WON, REQUEST_BIDS, SET_TARGETING, AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER } = CONSTANTS.EVENTS; const { PREVENT_WRITING_ON_MAIN_DOCUMENT, NO_AD, EXCEPTION, CANNOT_FIND_AD, MISSING_DOC_OR_ADID } = CONSTANTS.AD_RENDER_FAILED_REASON; const eventValidators = { @@ -375,6 +375,14 @@ function emitAdRenderFail({ reason, message, bid, id }) { events.emit(AD_RENDER_FAILED, data); } +function emitAdRenderSucceeded({ doc, bid, id }) { + const data = { doc }; + if (bid) data.bid = bid; + if (id) data.adId = id; + + events.emit(AD_RENDER_SUCCEEDED, data); +} + /** * This function will render the ad (based on params) in the given iframe document passed through. * Note that doc SHOULD NOT be the parent document page as we can't doc.write() asynchronously @@ -426,6 +434,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { if (isRendererRequired(renderer)) { executeRenderer(renderer, bid); + emitAdRenderSucceeded({ doc, bid, id }); } else if ((doc === document && !utils.inIframe()) || mediaType === 'video') { const message = `Error trying to write ad. Ad render call ad id ${id} was prevented from writing to the main document.`; emitAdRenderFail({reason: PREVENT_WRITING_ON_MAIN_DOCUMENT, message, bid, id}); @@ -444,6 +453,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { doc.close(); setRenderSize(doc, width, height); utils.callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); } else if (adUrl) { const iframe = utils.createInvisibleIframe(); iframe.height = height; @@ -455,6 +465,7 @@ $$PREBID_GLOBAL$$.renderAd = function (doc, id, options) { utils.insertElement(iframe, doc, 'body'); setRenderSize(doc, width, height); utils.callBurl(bid); + emitAdRenderSucceeded({ doc, bid, id }); } else { const message = `Error trying to write ad. No ad for bid response id: ${id}`; emitAdRenderFail({reason: NO_AD, message, bid, id});