diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js new file mode 100644 index 00000000000..0bf805fb94e --- /dev/null +++ b/modules/hadronAnalyticsAdapter.js @@ -0,0 +1,199 @@ +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import * as utils from '../src/utils.js'; +import CONSTANTS from '../src/constants.json'; +import { getStorageManager } from '../src/storageManager.js'; + +/** + * hadronAnalyticsAdapter.js - Audigent Hadron Analytics Adapter + */ + +const HADRON_ANALYTICS_URL = 'https://analytics.hadron.ad.gt/api/v1/analytics' +const HADRONID_ANALYTICS_VER = 'pbadgt0'; +const DEFAULT_PARTNER_ID = 0; +const AU_GVLID = 561; + +export const storage = getStorageManager(); + +var viewId = utils.generateUUID(); + +var partnerId = DEFAULT_PARTNER_ID; +var eventsToTrack = []; + +var w = window; +var d = document; +var e = d.documentElement; +var g = d.getElementsByTagName('body')[0]; +var x = w.innerWidth || e.clientWidth || g.clientWidth; +var y = w.innerHeight || e.clientHeight || g.clientHeight; + +var pageView = { + eventType: 'pageView', + userAgent: window.navigator.userAgent, + timestamp: Date.now(), + timezoneOffset: new Date().getTimezoneOffset(), + language: window.navigator.language, + vendor: window.navigator.vendor, + pageUrl: window.top.location.href, + screenWidth: x, + screenHeight: y +}; + +var eventQueue = [ + pageView +]; + +var startAuction = 0; +var bidRequestTimeout = 0; +let analyticsType = 'endpoint'; + +let hadronAnalyticsAdapter = Object.assign(adapter({url: HADRON_ANALYTICS_URL, analyticsType}), { + track({eventType, args}) { + args = args ? JSON.parse(JSON.stringify(args)) : {}; + var data = {}; + if (!eventsToTrack.includes(eventType)) return; + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + data = args; + startAuction = data.timestamp; + bidRequestTimeout = data.timeout; + break; + } + + case CONSTANTS.EVENTS.AUCTION_END: { + data = args; + data.start = startAuction; + data.end = Date.now(); + break; + } + + case CONSTANTS.EVENTS.BID_ADJUSTMENT: { + data.bidders = args; + break; + } + + case CONSTANTS.EVENTS.BID_TIMEOUT: { + data.bidders = args; + data.duration = bidRequestTimeout; + break; + } + + case CONSTANTS.EVENTS.BID_REQUESTED: { + data = args; + break; + } + + case CONSTANTS.EVENTS.BID_RESPONSE: { + data = args; + delete data.ad; + break; + } + + case CONSTANTS.EVENTS.BID_WON: { + data = args; + delete data.ad; + delete data.adUrl; + break; + } + + case CONSTANTS.EVENTS.BIDDER_DONE: { + data = args; + break; + } + + case CONSTANTS.EVENTS.SET_TARGETING: { + data.targetings = args; + break; + } + + case CONSTANTS.EVENTS.REQUEST_BIDS: { + data = args; + break; + } + + case CONSTANTS.EVENTS.ADD_AD_UNITS: { + data = args; + break; + } + + case CONSTANTS.EVENTS.AD_RENDER_FAILED: { + data = args; + break; + } + + default: + return; + } + + data.eventType = eventType; + data.timestamp = data.timestamp || Date.now(); + + sendEvent(data); + } +}); + +hadronAnalyticsAdapter.originEnableAnalytics = hadronAnalyticsAdapter.enableAnalytics; + +hadronAnalyticsAdapter.enableAnalytics = function(conf = {}) { + if (typeof conf.options === 'object') { + if (conf.options.partnerId) { + partnerId = conf.options.partnerId; + } else { + partnerId = DEFAULT_PARTNER_ID; + } + if (conf.options.eventsToTrack) { + eventsToTrack = conf.options.eventsToTrack; + } + } else { + utils.logError('HADRON_ANALYTICS_NO_CONFIG_ERROR'); + return; + } + + hadronAnalyticsAdapter.originEnableAnalytics(conf); +} + +function flush() { + // Don't send anything if no partner id was declared + if (partnerId === DEFAULT_PARTNER_ID) return; + if (eventQueue.length > 1) { + var data = { + pageViewId: viewId, + ver: HADRONID_ANALYTICS_VER, + partnerId: partnerId, + events: eventQueue + }; + + ajax(HADRON_ANALYTICS_URL, + () => utils.logInfo('HADRON_ANALYTICS_BATCH_SEND'), + JSON.stringify(data), + { + contentType: 'application/json', + method: 'POST' + } + ); + + eventQueue = [ + pageView + ]; + } +} + +function sendEvent(event) { + eventQueue.push(event); + utils.logInfo(`HADRON_ANALYTICS_EVENT ${event.eventType} `, event); + + if (event.eventType === CONSTANTS.EVENTS.AUCTION_END) { + flush(); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: hadronAnalyticsAdapter, + code: 'hadronAnalytics', + gvlid: AU_GVLID +}); + +hadronAnalyticsAdapter.flush = flush; + +export default hadronAnalyticsAdapter; diff --git a/modules/hadronAnalyticsAdapter.md b/modules/hadronAnalyticsAdapter.md new file mode 100644 index 00000000000..f549cf502b2 --- /dev/null +++ b/modules/hadronAnalyticsAdapter.md @@ -0,0 +1,48 @@ +# Overview +Module Name: Hadron Analytics Adapter + +Module Type: Analytics Adapter + +Maintainer: [audigent.com](https://audigent.com) + +# Hadron ID + +The Hadron ID is a container that publishers and ad tech platforms can use to +recognise users' segments where 3rd party cookies are not available. +The Hadron ID is designed to respect users' privacy choices and publishers’ +preferences throughout the advertising value chain. +For more information about the Hadron ID and detailed integration docs, please visit +[our brochure](https://audigent.com/hadron-id). + +# Hadron Analytics Registration + +The Hadron Analytics Adapter is free to use for our customers. +Please visit [audigent/hadron-id](https://audigent.com/hadron-id) to request a demo or get more info. + +The partners' privacy policy is at [https://audigent.com/privacypolicy/#partners](https://audigent.com/privacypolicy/#partners). + +## Hadron Analytics Configuration + +First, make sure to add the Hadron Analytics submodule to your Prebid.js package with: + +``` +gulp build --modules=...,hadronAnalyticsAdapter +``` + +The following configuration parameters are available: + +```javascript +pbjs.enableAnalytics({ + provider: 'hadron', + options: { + partnerId: 1234, // change to the Partner ID you got from Audigent + eventsToTrack: ['auctionEnd','bidWon'] + } +}); +``` + +| Parameter | Scope | Type | Description | Example | +| --- | --- | --- |---------------------------------------------------------| --- | +| provider | Required | String | The name of this module: `hadronAnalytics` | `hadronAnalytics` | +| options.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | +| options.eventsToTrack | Optional | Array of strings | Overrides the set of tracked events | `['auctionEnd','bidWon']` | diff --git a/test/spec/modules/hadronAnalyticsAdapter_spec.js b/test/spec/modules/hadronAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..bea131fb78f --- /dev/null +++ b/test/spec/modules/hadronAnalyticsAdapter_spec.js @@ -0,0 +1,60 @@ +import adapterManager from '../../../src/adapterManager.js'; +import hadronAnalyticsAdapter from '../../../modules/hadronAnalyticsAdapter.js'; +import { expect } from 'chai'; +import * as events from '../../../src/events.js'; +import constants from '../../../src/constants.json'; +import { generateUUID } from '../../../src/utils.js'; +import { server } from 'test/mocks/xhr.js'; + +describe('Hadron analytics adapter', () => { + beforeEach(() => { + hadronAnalyticsAdapter.enableAnalytics({ + options: { + partnerId: 12349, + eventsToTrack: ['auctionInit', 'auctionEnd', 'bidWon', + 'bidderDone', 'requestBids', 'addAdUnits', 'setTargeting', 'adRenderFailed', + 'bidResponse', 'bidTimeout', 'bidRequested', 'bidAdjustment', 'nonExistingEvent' + ], + } + }); + }); + + afterEach(() => { + hadronAnalyticsAdapter.disableAnalytics(); + }); + + it('registers itself with the adapter manager', () => { + const adapter = adapterManager.getAnalyticsAdapter('hadronAnalytics'); + expect(adapter).to.exist; + expect(adapter.gvlid).to.be.a('number'); + expect(adapter.adapter).to.equal(hadronAnalyticsAdapter); + }); + + it('tolerates undefined or empty config', () => { + hadronAnalyticsAdapter.enableAnalytics(undefined); + hadronAnalyticsAdapter.enableAnalytics({}); + }); + + it('sends auction end events to the backend', () => { + const auction = { + auctionId: generateUUID(), + adUnits: [{ + code: 'usr1234', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90]] + } + }, + adUnitCodes: ['usr1234'] + }], + }; + events.emit(constants.EVENTS.AUCTION_END, auction); + assert(server.requests.length > 0) + const body = JSON.parse(server.requests[0].requestBody); + var eventTypes = []; + body.events.forEach(e => eventTypes.push(e.eventType)); + assert(eventTypes.length > 0) + assert(eventTypes.indexOf(constants.EVENTS.AUCTION_END) > -1); + hadronAnalyticsAdapter.disableAnalytics(); + }); +});