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

Contxtful RTD Provider: Initial Release #10550

Merged
merged 11 commits into from
Jan 11, 2024
91 changes: 91 additions & 0 deletions integrationExamples/gpt/contxtfulRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<html>

<head>
<script src="http://localhost:9999/build/dev/prebid.js" async></script>
<script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
<script async>
const FAILSAFE_TIMEOUT = 8000;
const PREBID_TIMEOUT = 5000;

const bidders = [
{
bidder: 'appnexus',
params: {
placementId: 13144370
}
}
];

var adUnits = [
{
code: 'div-gpt-ad-1460505748561-0',
mediaTypes: {
banner: {
sizes: [[300, 250], [300, 600]],
}
},
bids: bidders,
}
];


var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function () {
googletag.pubads().disableInitialLoad();
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-1460505748561-0').addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});

pbjs.que.push(function () {
pbjs.setConfig({
debug: true,
realTimeData: {
auctionDelay: 100,
dataProviders: [
{
name: "contxtful",
waitForIt: true,
params: {
version: "Contact contact@contxtful.com for the API version",
customer: "Contact contact@contxtful.com for the customer ID"
}
}
]
}
});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
timeout: PREBID_TIMEOUT
});
});

function sendAdserverRequest() {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
googletag.cmd.push(function () {
pbjs.que.push(function () {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}

setTimeout(function () {
sendAdserverRequest();
}, FAILSAFE_TIMEOUT);

</script>
</head>

<body>
<h2>Contxtful RTD Provider</h2>
<div id='div-gpt-ad-1460505748561-0'></div>
</div>
</body>

</html>
148 changes: 148 additions & 0 deletions modules/contxtfulRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Contxtful Technologies Inc
* This RTD module provides receptivity feature that can be accessed using the
* getReceptivity() function. The value returned by this function enriches the ad-units
* that are passed within the `getTargetingData` functions and GAM.
*
*/

import { submodule } from '../src/hook.js';
import {
logInfo,
logError,
isStr,
isEmptyStr,
buildUrl,
} from '../src/utils.js';
import { loadExternalScript } from '../src/adloader.js';

const MODULE_NAME = 'contxtful';
const MODULE = `${MODULE_NAME}RtdProvider`;

const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io';

let initialReceptivity = null;
let contxtfulModule = null;

/**
* Init function used to start sub module
* @param { { params: { version: String, customer: String } } } config
* @return { Boolean }
*/
function init(config) {
logInfo(MODULE, 'init', config);
initialReceptivity = null;
contxtfulModule = null;

try {
const {version, customer} = extractParameters(config);
initCustomer(version, customer);
return true;
} catch (error) {
logError(MODULE, error);
return false;
}
}

/**
* Extract required configuration for the sub module.
* validate that all required configuration are present and are valid.
* Throws an error if any config is missing of invalid.
* @param { { params: { version: String, customer: String } } } config
* @return { { version: String, customer: String } }
* @throws params.{name} should be a non-empty string
*/
function extractParameters(config) {
const version = config?.params?.version;
if (!isStr(version) || isEmptyStr(version)) {
throw Error(`${MODULE}: params.version should be a non-empty string`);
}

const customer = config?.params?.customer;
if (!isStr(customer) || isEmptyStr(customer)) {
throw Error(`${MODULE}: params.customer should be a non-empty string`);
}

return {version, customer};
}

/**
* Initialize sub module for a customer.
* This will load the external resources for the sub module.
* @param { String } version
* @param { String } customer
*/
function initCustomer(version, customer) {
const CONNECTOR_URL = buildUrl({
patmmccann marked this conversation as resolved.
Show resolved Hide resolved
protocol: 'https',
host: CONTXTFUL_RECEPTIVITY_DOMAIN,
pathname: `/${version}/prebid/${customer}/connector/p.js`,
});

const externalScript = loadExternalScript(CONNECTOR_URL, MODULE_NAME);
addExternalScriptEventListener(externalScript);
}

/**
* Add event listener to the script tag for the expected events from the external script.
* @param { HTMLScriptElement } script
*/
function addExternalScriptEventListener(script) {
if (!script) {
return;
}

script.addEventListener('initialReceptivity', ({ detail }) => {
let receptivityState = detail?.ReceptivityState;
if (isStr(receptivityState) && !isEmptyStr(receptivityState)) {
initialReceptivity = receptivityState;
}
});

script.addEventListener('rxEngineIsReady', ({ detail: api }) => {
contxtfulModule = api;
});
}

/**
* Return current receptivity.
* @return { { ReceptivityState: String } }
*/
function getReceptivity() {
return {
ReceptivityState: contxtfulModule?.GetReceptivity()?.ReceptivityState || initialReceptivity
};
}

/**
* Set targeting data for ad server
* @param { [String] } adUnits
* @param {*} _config
* @param {*} _userConsent
* @return {{ code: { ReceptivityState: String } }}
*/
function getTargetingData(adUnits, _config, _userConsent) {
logInfo(MODULE, 'getTargetingData');
if (!adUnits) {
return {};
}

const receptivity = getReceptivity();
if (!receptivity?.ReceptivityState) {
return {};
}

return adUnits.reduce((targets, code) => {
targets[code] = receptivity;
return targets;
}, {});
}

export const contxtfulSubmodule = {
name: MODULE_NAME,
init,
extractParameters,
getTargetingData,
};

submodule('realTimeData', contxtfulSubmodule);
65 changes: 65 additions & 0 deletions modules/contxtfulRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Overview

**Module Name:** Contxtful RTD Provider
**Module Type:** RTD Provider
**Maintainer:** [prebid@contxtful.com](mailto:prebid@contxtful.com)

# Description

The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time.

To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [prebid@contxtful.com](mailto:prebid@contxtful.com).

# Configuration

## Build Instructions

To incorporate this module into your `prebid.js`, compile the module using the following command:

```sh
gulp build --modules=contxtfulRtdProvider,<other modules...>
```

## Module Configuration

Configure the `contxtfulRtdProvider` by passing the required settings through the `setConfig` function in `prebid.js`.

```js
import pbjs from 'prebid.js';

pbjs.setConfig({
"realTimeData": {
"auctionDelay": 1000,
"dataProviders": [
{
"name": "contxtful",
"waitForIt": true,
"params": {
"version": "<API Version>",
"customer": "<Contxtful Customer ID>"
}
}
]
}
});
```

### Configuration Parameters

| Name | Type | Scope | Description |
|------------|----------|----------|-------------------------------------------|
| `version` | `string` | Required | Specifies the API version of Contxtful. |
| `customer` | `string` | Required | Your unique customer identifier. |

# Usage

The `contxtfulRtdProvider` module loads an external JavaScript file and authenticates with Contxtful APIs. The `getTargetingData` function then adds a `ReceptivityState` to each ad slot, which can have one of two values: `Receptive` or `NonReceptive`.

```json
{
"adUnitCode1": { "ReceptivityState": "Receptive" },
"adUnitCode2": { "ReceptivityState": "NonReceptive" }
}
```

This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process.
3 changes: 2 additions & 1 deletion src/adloader.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ const _approvedLoadExternalJSList = [
'clean.io',
'a1Media',
'geoedge',
'qortex'
'qortex',
'contxtful'
]

/**
Expand Down
Loading