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

Djax Analytic Adapter #12180

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
152 changes: 152 additions & 0 deletions modules/djaxAnalyticsAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import AnalyticsAdapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import {prefixLog, isPlainObject} from '../src/utils.js';
import {has as hasEvent} from '../src/events.js';
import adapterManager from '../src/adapterManager.js';
import {ajaxBuilder} from '../src/ajax.js';

const DEFAULTS = {
batchSize: 1,
batchDelay: 100,
method: 'POST'
}

const TYPES = {
handler: 'function',
batchSize: 'number',
batchDelay: 'number',
gvlid: 'number',
}

const MAX_CALL_DEPTH = 20;

export function DjaxAnalytics() {
const parent = AnalyticsAdapter({analyticsType: 'endpoint'});
const {logError, logWarn} = prefixLog('Djax analytics:');
let batch = [];
let callDepth = 0;
let options, handler, timer, translate;

function optionsAreValid(options) {
if (!options.url && !options.handler) {
logError('options must specify either `url` or `handler`')
return false;
}
if (options.hasOwnProperty('method') && !['GET', 'POST'].includes(options.method)) {
logError('options.method must be GET or POST');
return false;
}
for (const [field, type] of Object.entries(TYPES)) {
// eslint-disable-next-line valid-typeof
if (options.hasOwnProperty(field) && typeof options[field] !== type) {
logError(`options.${field} must be a ${type}`);
return false;
}
}
if (options.hasOwnProperty('events')) {
if (!isPlainObject(options.events)) {
logError('options.events must be an object');
return false;
}
for (const [event, handler] of Object.entries(options.events)) {
if (!hasEvent(event)) {
logWarn(`options.events.${event} does not match any known Prebid event`);
}
if (typeof handler !== 'function') {
logError(`options.events.${event} must be a function`);
return false;
}
}
}
return true;
}

function processBatch() {
const currentBatch = batch;
batch = [];
callDepth++;
try {
// the pub-provided handler may inadvertently cause an infinite chain of events;
// even just logging an exception from it may cause an AUCTION_DEBUG event, that
// gets back to the handler, that throws another exception etc.
// to avoid the issue, put a cap on recursion
if (callDepth === MAX_CALL_DEPTH) {
logError('detected probable infinite recursion, discarding events', currentBatch);
}
if (callDepth >= MAX_CALL_DEPTH) {
return;
}
try {
handler(currentBatch);
} catch (e) {
logError('error executing options.handler', e);
}
} finally {
callDepth--;
}
}

function translator(eventHandlers) {
if (!eventHandlers) {
return (data) => data;
}
return function ({eventType, args}) {
if (eventHandlers.hasOwnProperty(eventType)) {
try {
return eventHandlers[eventType](args);
} catch (e) {
logError(`error executing options.events.${eventType}`, e);
}
}
}
}

return Object.assign(
Object.create(parent),
{
gvlid(config) {
return config?.options?.gvlid
},
enableAnalytics(config) {
if (optionsAreValid(config?.options || {})) {
options = Object.assign({}, DEFAULTS, config.options);
handler = options.handler || defaultHandler(options);
translate = translator(options.events);
parent.enableAnalytics.call(this, config);
}
},
track(event) {
const datum = translate(event);
if (datum != null) {
batch.push(datum);
if (timer != null) {
clearTimeout(timer);
timer = null;
}
if (batch.length >= options.batchSize) {
processBatch();
} else {
timer = setTimeout(processBatch, options.batchDelay);
}
}
}
}
)
}

export function defaultHandler({url, method, batchSize, ajax = ajaxBuilder()}) {
const callbacks = {
success() {},
error() {}
}
const extract = batchSize > 1 ? (events) => events : (events) => events[0];
const serialize = method === 'GET' ? (data) => ({data: JSON.stringify(data)}) : (data) => JSON.stringify(data);

return function (events) {
ajax(url, callbacks, serialize(extract(events)), {method, keepalive: true})
}
}

adapterManager.registerAnalyticsAdapter({
adapter: DjaxAnalytics(),
code: 'djax',
});
48 changes: 48 additions & 0 deletions modules/djaxAnalyticsAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Overview
```
Module Name: djax Analytics Adapter
Module Type: Analytics Adapter
Maintainer: support@djaxtech.com
```

### Usage

The djax analytics adapter can be used by all clients .

### Example Configuration

```javascript
pbjs.enableAnalytics({
provider: 'djax',
options: {
options: {
url: 'https://example.com', // change your end point url to fetch the tracked information
}
});


// Based on events
pbjs.enableAnalytics({
provider: 'djax',
options: {
url: 'https://example.com',
batchSize: 10,
events: {
bidRequested(request) {
return {
type: 'REQUEST',
auctionId: request.auctionId,
bidder: request.bidderCode
}
},
bidResponse(response) {
return {
type: 'RESPONSE',
auctionId: response.auctionId,
bidder: response.bidderCode
}
}
}
}
})
```
Loading