-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Qortex RTD Provider: initial release (#10480)
* 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
Showing
4 changed files
with
568 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ const _approvedLoadExternalJSList = [ | |
'clean.io', | ||
'a1Media', | ||
'geoedge', | ||
'qortex' | ||
] | ||
|
||
/** | ||
|
Oops, something went wrong.