Skip to content

Commit

Permalink
Qortex RTD Provider: initial release (#10480)
Browse files Browse the repository at this point in the history
* branch from master

* adloader

* resolves testing issue

---------

Co-authored-by: Mick <[email protected]>
  • Loading branch information
mickannese and Mick committed Sep 26, 2023
1 parent 2995c27 commit 069d86b
Show file tree
Hide file tree
Showing 4 changed files with 568 additions and 0 deletions.
165 changes: 165 additions & 0 deletions modules/qortexRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { submodule } from '../src/hook.js';
import { ajax } from '../src/ajax.js';
import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js';
import { loadExternalScript } from '../src/adloader.js';
import * as events from '../src/events.js';
import CONSTANTS from '../src/constants.json';

let requestUrl;
let bidderArray;
let impressionIds;
let currentSiteContext;

/**
* Init if module configuration is valid
* @param {Object} config Module configuration
* @returns {Boolean}
*/
function init (config) {
if (!config?.params?.groupId?.length > 0) {
logWarn('Qortex RTD module config does not contain valid groupId parameter. Config params: ' + JSON.stringify(config.params))
return false;
} else {
initializeModuleData(config);
}
if (config?.params?.tagConfig) {
loadScriptTag(config)
}
return true;
}

/**
* Processess prebid request and attempts to add context to ort2b fragments
* @param {Object} reqBidsConfig Bid request configuration object
* @param {Function} callback Called on completion
*/
function getBidRequestData (reqBidsConfig, callback) {
if (reqBidsConfig?.adUnits?.length > 0) {
getContext()
.then(contextData => {
setContextData(contextData)
addContextToRequests(reqBidsConfig)
callback();
})
.catch((e) => {
logWarn(e?.message);
callback();
});
} else {
logWarn('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfig))
callback();
}
}

/**
* determines whether to send a request to context api and does so if necessary
* @returns {Promise} ortb Content object
*/
export function getContext () {
if (!currentSiteContext) {
logMessage('Requesting new context data');
return new Promise((resolve, reject) => {
const callbacks = {
success(text, data) {
const result = data.status === 200 ? JSON.parse(data.response)?.content : null;
resolve(result);
},
error(error) {
reject(new Error(error));
}
}
ajax(requestUrl, callbacks)
})
} else {
logMessage('Adding Content object from existing context data');
return new Promise(resolve => resolve(currentSiteContext));
}
}

/**
* Updates bidder configs with the response from Qortex context services
* @param {Object} reqBidsConfig Bid request configuration object
* @param {string[]} bidders Bidders specified in module's configuration
*/
export function addContextToRequests (reqBidsConfig) {
if (currentSiteContext === null) {
logWarn('No context data recieved at this time');
} else {
const fragment = { site: {content: currentSiteContext} }
if (bidderArray?.length > 0) {
bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment}))
} else if (!bidderArray) {
mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment);
} else {
logWarn('Config contains an empty bidders array, unable to determine which bids to enrich');
}
}
}

/**
* Loads Qortex header tag using data passed from module config object
* @param {Object} config module config obtained during init
*/
export function loadScriptTag(config) {
const code = 'qortex';
const groupId = config.params.groupId;
const src = 'https://tags.qortex.ai/bootstrapper'
const attr = {'data-group-id': groupId}
const tc = config.params.tagConfig

Object.keys(tc).forEach(p => {
attr[`data-${p.replace(/([A-Z])/g, (m) => `-${m.toLowerCase()}`)}`] = tc[p]
})

addEventListener('qortex-rtd', (e) => {
const billableEvent = {
vendor: code,
billingId: generateUUID(),
type: e?.detail?.type,
accountId: groupId
}
switch (e?.detail?.type) {
case 'qx-impression':
const {uid} = e.detail;
if (!uid || impressionIds.has(uid)) {
logWarn(`recieved invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`)
return;
} else {
logMessage('recieved billable event: qx-impression')
impressionIds.add(uid)
billableEvent.transactionId = e.detail.uid;
events.emit(CONSTANTS.EVENTS.BILLABLE_EVENT, billableEvent);
break;
}
default:
logWarn(`recieved invalid billable event: ${e.detail?.type}`)
}
})

loadExternalScript(src, code, undefined, undefined, attr);
}

/**
* Helper function to set initial values when they are obtained by init
* @param {Object} config module config obtained during init
*/
export function initializeModuleData(config) {
const DEFAULT_API_URL = 'https://demand.qortex.ai';
const {apiUrl, groupId, bidders} = config.params;
requestUrl = `${apiUrl || DEFAULT_API_URL}/api/v1/analyze/${groupId}/prebid`;
bidderArray = bidders;
impressionIds = new Set();
currentSiteContext = null;
}

export function setContextData(value) {
currentSiteContext = value
}

export const qortexSubmodule = {
name: 'qortex',
init,
getBidRequestData
}

submodule('realTimeData', qortexSubmodule);
69 changes: 69 additions & 0 deletions modules/qortexRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Qortex Real-time Data Submodule

## Overview

```
Module Name: Qortex RTD Provider
Module Type: RTD Provider
Maintainer: [email protected]
```

## Description

The Qortex RTD module appends contextual segments to the bidding object based on the content of a page using the Qortex API.

Upon load, the Qortex context API will analyze the bidder page (video, text, image, etc.) and will return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26). The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data.


## Build
```
gulp build --modules="rtdModule,qortexRtdProvider,qortexBidAdapter,..."
```

> `rtdModule` is a required module to use Qortex RTD module.
## Configuration

Please refer to [Prebid Documentation](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-realTimeData) on RTD module configuration for details on required and optional parameters of `realTimeData`

When configuring Qortex as a data provider, refer to the template below to add the necessary information to ensure the proper connection is made.

### RTD Module Setup

```javascript
pbjs.setConfig({
realTimeData: {
auctionDelay: 1000,
dataProviders: [{
name: 'qortex',
waitForIt: true,
params: {
groupId: 'ABC123', //required
bidders: ['qortex', 'adapter2'], //optional (see below)
tagConfig: { // optional, please reach out to your account manager for configuration reccommendation
videoContainer: 'string',
htmlContainer: 'string',
attachToTop: 'string',
esm6Mod: 'string',
continuousLoad: 'string'
}
}
}]
}
});
```

### Paramter Details

#### `groupId` - Required
- The Qortex groupId linked to the publisher, this is required to make a request using this adapter

#### `bidders` - optional
- If this parameter is included, it must be an array of the strings that match the bidder code of the prebid adapters you would like this module to impact. `ortb2.site.content` will be updated *only* for adapters in this array

- If this parameter is omitted, the RTD module will default to updating `ortb2.site.content` on *all* bid adapters being used on the page

#### `tagConfig` - optional
- This optional parameter is an object containing the config settings that could be usedto initialize the Qortex integration on your page. A preconfigured object for this step will be provided to you by the Qortex team.

- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal.
1 change: 1 addition & 0 deletions src/adloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const _approvedLoadExternalJSList = [
'clean.io',
'a1Media',
'geoedge',
'qortex'
]

/**
Expand Down
Loading

0 comments on commit 069d86b

Please sign in to comment.