Skip to content

Commit

Permalink
WURFL RTD submodule: initial version (#11840)
Browse files Browse the repository at this point in the history
* WURFL Rtd Provider: initial version

* WURFL Rtd Provider: import fetch method from ajax.js module

* WURFL Rtd Provider: unit tests

* WURFL Rtd Provider: list wurflRtdProvider in the .submodules.json file

* WURFL Rtd Provider: remove wurfl from adloader.js

* WURFL Rtd Provider: update to use loadExternalScript

* WURFL Rtd Provider: update to use sendBeacon from ajax.js
  • Loading branch information
lucor authored Jul 26, 2024
1 parent fa78a8b commit 5cefb01
Show file tree
Hide file tree
Showing 6 changed files with 713 additions and 1 deletion.
106 changes: 106 additions & 0 deletions integrationExamples/gpt/wurflRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<html>

<head>
<meta http-equiv="delegate-ch" content="sec-ch-ua https://prebid.wurflcloud.com; sec-ch-ua-bitness https://prebid.wurflcloud.com; sec-ch-ua-arch https://prebid.wurflcloud.com; sec-ch-ua-model https://prebid.wurflcloud.com; sec-ch-ua-platform https://prebid.wurflcloud.com; sec-ch-ua-platform-version https://prebid.wurflcloud.com; sec-ch-ua-full-version https://prebid.wurflcloud.com; sec-ch-ua-full-version-list https://prebid.wurflcloud.com; sec-ch-ua-mobile https://prebid.wurflcloud.com">
<script async src="../../build/dev/prebid.js"></script>
<script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
<script>
var FAILSAFE_TIMEOUT = 3300;
var PREBID_TIMEOUT = 2000;

var adUnits = [
{
code: 'div-gpt-ad-1460505748561-0',
mediaTypes: {
banner: {
sizes: [[300, 250]],
}
},
sizes: [
[300, 250],
[728, 90]
],
bids: [
{
bidder: 'appnexus',
params: {
placementId: 13144370
}
},
]

}];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
</script>

<script>
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function () {
googletag.pubads().disableInitialLoad();
});

pbjs.que.push(function () {
// configure the WURFL RTD module
pbjs.setConfig({
debug: true, // enabled for testing purposes
realTimeData: {
auctionDelay: 2000,
dataProviders: [
// WURFL RTD module configuration
{
name: 'wurfl',
waitForIt: true
},
]
}
});

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>

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-1460505748561-0').addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h2>Prebid.js Test</h2>
<h5>Div-1</h5>
<div id='div-gpt-ad-1460505748561-0'>
<script type='text/javascript'>
googletag.cmd.push(function () { googletag.display('div-gpt-ad-1460505748561-0'); });
</script>
</div>
</body>

</html>
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@
"sirdataRtdProvider",
"symitriDapRtdProvider",
"timeoutRtdProvider",
"weboramaRtdProvider"
"weboramaRtdProvider",
"wurflRtdProvider"
],
"fpdModule": [
"validationFpdModule",
Expand Down
213 changes: 213 additions & 0 deletions modules/wurflRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { submodule } from '../src/hook.js';
import { fetch, sendBeacon } from '../src/ajax.js';
import { loadExternalScript } from '../src/adloader.js';
import {
mergeDeep,
prefixLog,
} from '../src/utils.js';

// Constants
const REAL_TIME_MODULE = 'realTimeData';
const MODULE_NAME = 'wurfl';

// WURFL_JS_HOST is the host for the WURFL service endpoints
const WURFL_JS_HOST = 'https://prebid.wurflcloud.com';
// WURFL_JS_ENDPOINT_PATH is the path for the WURFL.js endpoint used to load WURFL data
const WURFL_JS_ENDPOINT_PATH = '/wurfl.js';
// STATS_ENDPOINT_PATH is the path for the stats endpoint used to send analytics data
const STATS_ENDPOINT_PATH = '/v1/prebid/stats';

const logger = prefixLog('[WURFL RTD Submodule]');

// enrichedBidders holds a list of prebid bidder names, of bidders which have been
// injected with WURFL data
const enrichedBidders = new Set();

/**
* init initializes the WURFL RTD submodule
* @param {Object} config Configuration for WURFL RTD submodule
* @param {Object} userConsent User consent data
*/
const init = (config, userConsent) => {
logger.logMessage('initialized');
return true;
}

/**
* getBidRequestData enriches the OpenRTB 2.0 device data with WURFL data
* @param {Object} reqBidsConfigObj Bid request configuration object
* @param {Function} callback Called on completion
* @param {Object} config Configuration for WURFL RTD submodule
* @param {Object} userConsent User consent data
*/
const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => {
const altHost = config.params?.altHost ?? null;
const isDebug = config.params?.debug ?? false;

const bidders = new Set();
reqBidsConfigObj.adUnits.forEach(adUnit => {
adUnit.bids.forEach(bid => {
bidders.add(bid.bidder);
});
});

let host = WURFL_JS_HOST;
if (altHost) {
host = altHost;
}

const url = new URL(host);
url.pathname = WURFL_JS_ENDPOINT_PATH;

if (isDebug) {
url.searchParams.set('debug', 'true')
}

url.searchParams.set('mode', 'prebid')
logger.logMessage('url', url.toString());

try {
loadExternalScript(url.toString(), MODULE_NAME, () => {
logger.logMessage('script injected');
window.WURFLPromises.complete.then((res) => {
logger.logMessage('received data', res);
if (!res.wurfl_pbjs) {
logger.logError('invalid WURFL.js for Prebid response');
} else {
enrichBidderRequests(reqBidsConfigObj, bidders, res);
}
callback();
});
});
} catch (err) {
logger.logError(err);
callback();
}
}

/**
* enrichBidderRequests enriches the OpenRTB 2.0 device data with WURFL data for Business Edition
* @param {Object} reqBidsConfigObj Bid request configuration object
* @param {Array} bidders List of bidders
* @param {Object} wjsResponse WURFL.js response
*/
function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) {
const authBidders = wjsResponse.wurfl_pbjs?.authorized_bidders ?? {};
const caps = wjsResponse.wurfl_pbjs?.caps ?? [];

bidders.forEach((bidderCode) => {
if (bidderCode in authBidders) {
// inject WURFL data
enrichedBidders.add(bidderCode);
const data = bidderData(wjsResponse.WURFL, caps, authBidders[bidderCode]);
logger.logMessage(`injecting data for ${bidderCode}: `, data);
enrichBidderRequest(reqBidsConfigObj, bidderCode, data);
return;
}
// inject WURFL low entropy data
const data = lowEntropyData(wjsResponse.WURFL, wjsResponse.wurfl_pbjs?.low_entropy_caps);
logger.logMessage(`injecting low entropy data for ${bidderCode}: `, data);
enrichBidderRequest(reqBidsConfigObj, bidderCode, data);
});
}

/**
* bidderData returns the WURFL data for a bidder
* @param {Object} wurflData WURFL data
* @param {Array} caps Capability list
* @param {Array} filter Filter list
* @returns {Object} Bidder data
*/
export const bidderData = (wurflData, caps, filter) => {
const data = {};
caps.forEach((cap, index) => {
if (!filter.includes(index)) {
return;
}
if (cap in wurflData) {
data[cap] = wurflData[cap];
}
});
return data;
}

/**
* lowEntropyData returns the WURFL low entropy data
* @param {Object} wurflData WURFL data
* @param {Array} lowEntropyCaps Low entropy capability list
* @returns {Object} Bidder data
*/
export const lowEntropyData = (wurflData, lowEntropyCaps) => {
const data = {};
lowEntropyCaps.forEach((cap, _) => {
let value = wurflData[cap];
if (cap == 'complete_device_name') {
value = value.replace(/Apple (iP(hone|ad|od)).*/, 'Apple iP$2');
}
data[cap] = value;
});
return data;
}

/**
* enrichBidderRequest enriches the bidder request with WURFL data
* @param {Object} reqBidsConfigObj Bid request configuration object
* @param {String} bidderCode Bidder code
* @param {Object} wurflData WURFL data
*/
export const enrichBidderRequest = (reqBidsConfigObj, bidderCode, wurflData) => {
const ortb2data = {
'device': {
'ext': {
'wurfl': wurflData,
}
},
};
mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data });
}

/**
* onAuctionEndEvent is called when the auction ends
* @param {Object} auctionDetails Auction details
* @param {Object} config Configuration for WURFL RTD submodule
* @param {Object} userConsent User consent data
*/
function onAuctionEndEvent(auctionDetails, config, userConsent) {
const altHost = config.params?.altHost ?? null;

let host = WURFL_JS_HOST;
if (altHost) {
host = altHost;
}

const url = new URL(host);
url.pathname = STATS_ENDPOINT_PATH;

if (enrichedBidders.size === 0) {
return;
}

var payload = JSON.stringify({ bidders: [...enrichedBidders] });
const sentBeacon = sendBeacon(url.toString(), payload);
if (sentBeacon) {
return;
}

fetch(url.toString(), {
method: 'POST',
body: payload,
mode: 'no-cors',
keepalive: true
});
}

// The WURFL submodule
export const wurflSubmodule = {
name: MODULE_NAME,
init,
getBidRequestData,
onAuctionEndEvent,
}

// Register the WURFL submodule as submodule of realTimeData
submodule(REAL_TIME_MODULE, wurflSubmodule);
67 changes: 67 additions & 0 deletions modules/wurflRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# WURFL Real-time Data Submodule

## Overview

Module Name: WURFL Rtd Provider
Module Type: Rtd Provider
Maintainer: [email protected]

## Description

The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/).
The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilites like `is_mobile`, `complete_device_name` and `form_factor`.

For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js).

## User-Agent Client Hints

WURFL.js is fully compatible with Chromium's User-Agent Client Hints (UA-CH) initiative. If User-Agent Client Hints are absent in the HTTP headers that WURFL.js receives, the service will automatically fall back to using the User-Agent Client Hints' JS API to fetch [high entropy client hint values](https://wicg.github.io/ua-client-hints/#getHighEntropyValues) from the client device. However, we recommend that you explicitly opt-in/advertise support for User-Agent Client Hints on your website and delegate them to the WURFL.js service for the fastest detection experience. Our documentation regarding implementing User-Agent Client Hint support [is available here](https://docs.scientiamobile.com/guides/implementing-useragent-clienthints).

## Usage

### Build
```
gulp build --modules="wurflRtdProvider,appnexusBidAdapter,..."
```

### Configuration

Use `setConfig` to instruct Prebid.js to initilize the WURFL RTD module, as specified below.

This module is configured as part of the `realTimeData.dataProviders`

```javascript
var TIMEOUT = 1000;
pbjs.setConfig({
realTimeData: {
auctionDelay: TIMEOUT,
dataProviders: [{
name: 'wurfl',
waitForIt: true,
params: {
debug: false
}
}]
}
});
```

### Parameters

| Name | Type | Description | Default |
| :------------------------ | :------------ | :--------------------------------------------------------------- |:----------------- |
| name | String | Real time data module name | Always 'wurfl' |
| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` |
| params | Object | | |
| params.altHost | String | Alternate host to connect to WURFL.js | |
| params.debug | Boolean | Enable debug | `false` |

## Testing

To view an example of how the WURFL RTD module works :

`gulp serve --modules=wurflRtdProvider,appnexusBidAdapter`

and then point your browser at:

`http://localhost:9999/integrationExamples/gpt/wurflRtdProvider_example.html`
Loading

0 comments on commit 5cefb01

Please sign in to comment.