-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Timeout RTD module: initial release (#7395)
* Add Prebid timeout RTD module * increase test coverage * Add header to doc * Lint fixes * Add unknown connection speed to doc * Fix doc, add unit test
- Loading branch information
1 parent
35b5dca
commit 46fe440
Showing
3 changed files
with
663 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,173 @@ | ||
|
||
import { submodule } from '../src/hook.js'; | ||
import * as ajax from '../src/ajax.js'; | ||
import * as utils from '../src/utils.js'; | ||
import { getGlobal } from '../src/prebidGlobal.js'; | ||
|
||
const SUBMODULE_NAME = 'timeout'; | ||
|
||
// this allows the stubbing of functions during testing | ||
export const timeoutRtdFunctions = { | ||
getDeviceType, | ||
getConnectionSpeed, | ||
checkVideo, | ||
calculateTimeoutModifier, | ||
handleTimeoutIncrement | ||
}; | ||
|
||
function getDeviceType() { | ||
const userAgent = window.navigator.userAgent.toLowerCase(); | ||
if ((/ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test(userAgent))) { | ||
return 5; // tablet | ||
} | ||
if ((/iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test(userAgent))) { | ||
return 4; // mobile | ||
} | ||
return 2; // personal computer | ||
} | ||
|
||
function checkVideo(adUnits) { | ||
return adUnits.some((adUnit) => { | ||
return adUnit.mediaTypes && adUnit.mediaTypes.video; | ||
}); | ||
} | ||
|
||
function getConnectionSpeed() { | ||
const connection = window.navigator.connection || window.navigator.mozConnection || window.navigator.webkitConnection || {} | ||
const connectionType = connection.type || connection.effectiveType; | ||
|
||
switch (connectionType) { | ||
case 'slow-2g': | ||
case '2g': | ||
return 'slow'; | ||
|
||
case '3g': | ||
return 'medium'; | ||
|
||
case 'bluetooth': | ||
case 'cellular': | ||
case 'ethernet': | ||
case 'wifi': | ||
case 'wimax': | ||
case '4g': | ||
return 'fast'; | ||
} | ||
|
||
return 'unknown'; | ||
} | ||
/** | ||
* Calculate the time to be added to the timeout | ||
* @param {Array} adUnits | ||
* @param {Object} rules | ||
* @return {int} | ||
*/ | ||
function calculateTimeoutModifier(adUnits, rules) { | ||
utils.logInfo('Timeout rules', rules); | ||
let timeoutModifier = 0; | ||
let toAdd = 0; | ||
|
||
if (rules.includesVideo) { | ||
const hasVideo = timeoutRtdFunctions.checkVideo(adUnits); | ||
toAdd = rules.includesVideo[hasVideo] || 0; | ||
utils.logInfo(`Adding ${toAdd} to timeout for includesVideo ${hasVideo}`) | ||
timeoutModifier += toAdd; | ||
} | ||
|
||
if (rules.numAdUnits) { | ||
const numAdUnits = adUnits.length; | ||
if (rules.numAdUnits[numAdUnits]) { | ||
timeoutModifier += rules.numAdUnits[numAdUnits]; | ||
} else { | ||
for (const [rangeStr, timeoutVal] of Object.entries(rules.numAdUnits)) { | ||
const [lowerBound, upperBound] = rangeStr.split('-'); | ||
if (parseInt(lowerBound) <= numAdUnits && numAdUnits <= parseInt(upperBound)) { | ||
utils.logInfo(`Adding ${timeoutVal} to timeout for numAdUnits ${numAdUnits}`) | ||
timeoutModifier += timeoutVal; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (rules.deviceType) { | ||
const deviceType = timeoutRtdFunctions.getDeviceType(); | ||
toAdd = rules.deviceType[deviceType] || 0; | ||
utils.logInfo(`Adding ${toAdd} to timeout for deviceType ${deviceType}`) | ||
timeoutModifier += toAdd; | ||
} | ||
|
||
if (rules.connectionSpeed) { | ||
const connectionSpeed = timeoutRtdFunctions.getConnectionSpeed(); | ||
toAdd = rules.connectionSpeed[connectionSpeed] || 0; | ||
utils.logInfo(`Adding ${toAdd} to timeout for connectionSpeed ${connectionSpeed}`) | ||
timeoutModifier += toAdd; | ||
} | ||
|
||
utils.logInfo('timeout Modifier calculated', timeoutModifier); | ||
return timeoutModifier; | ||
} | ||
|
||
/** | ||
* | ||
* @param {Object} reqBidsConfigObj | ||
* @param {function} callback | ||
* @param {Object} config | ||
* @param {Object} userConsent | ||
*/ | ||
function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { | ||
utils.logInfo('Timeout rtd config', config); | ||
const timeoutUrl = utils.deepAccess(config, 'params.endpoint.url'); | ||
if (timeoutUrl) { | ||
utils.logInfo('Timeout url', timeoutUrl); | ||
ajax.ajaxBuilder()(timeoutUrl, { | ||
success: function(response) { | ||
try { | ||
const rules = JSON.parse(response); | ||
timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, rules); | ||
} catch (e) { | ||
utils.logError('Error parsing json response from timeout provider.') | ||
} | ||
callback(); | ||
}, | ||
error: function(errorStatus) { | ||
utils.logError('Timeout request error!', errorStatus); | ||
callback(); | ||
} | ||
}); | ||
} else if (utils.deepAccess(config, 'params.rules')) { | ||
timeoutRtdFunctions.handleTimeoutIncrement(reqBidsConfigObj, utils.deepAccess(config, 'params.rules')); | ||
callback(); | ||
} else { | ||
utils.logInfo('No timeout endpoint or timeout rules found. Exiting timeout rtd module'); | ||
callback(); | ||
} | ||
} | ||
|
||
/** | ||
* Gets the timeout modifier, adds it to the bidder timeout, and sets it to reqBidsConfigObj | ||
* @param {Object} reqBidsConfigObj | ||
* @param {Object} rules | ||
*/ | ||
function handleTimeoutIncrement(reqBidsConfigObj, rules) { | ||
const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; | ||
const timeoutModifier = timeoutRtdFunctions.calculateTimeoutModifier(adUnits, rules); | ||
const bidderTimeout = getGlobal().getConfig('bidderTimeout'); | ||
reqBidsConfigObj.timeout = bidderTimeout + timeoutModifier; | ||
} | ||
|
||
/** @type {RtdSubmodule} */ | ||
export const timeoutSubmodule = { | ||
/** | ||
* used to link submodule with realTimeData | ||
* @type {string} | ||
*/ | ||
name: SUBMODULE_NAME, | ||
init: () => true, | ||
getBidRequestData, | ||
}; | ||
|
||
function registerSubModule() { | ||
submodule('realTimeData', timeoutSubmodule); | ||
} | ||
|
||
registerSubModule(); |
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,151 @@ | ||
--- | ||
layout: page_v2 | ||
title: Timeout Rtd Module | ||
description: Module for managing timeouts in real time | ||
page_type: module | ||
module_type: rtd | ||
module_code : example | ||
enable_download : true | ||
sidebarType : 1 | ||
--- | ||
|
||
## Overview | ||
The timeout RTD module enables publishers to set rules that determine the timeout based on | ||
certain features. It supports rule sets dynamically retrieved from a timeout provider as well as rules | ||
set directly via configuration. | ||
Build the timeout RTD module into the Prebid.js package with: | ||
``` | ||
gulp build --modules=timeoutRtdProvider,rtdModule... | ||
``` | ||
|
||
## Configuration | ||
The module is configured in the realTimeData.dataProviders object. The module will override | ||
`bidderTimeout` in the pbjs config. | ||
|
||
### Timeout Data Provider interface | ||
The timeout RTD module provides an interface of dynamically fetching timeout rules from | ||
a data provider just before the auction begins. The endpoint url is set in the config just as in | ||
the example below, and the timeout data will be used when making bid requests. | ||
|
||
``` | ||
pbjs.setConfig({ | ||
... | ||
"realTimeData": { | ||
"dataProviders": [{ | ||
"name": 'timeout', | ||
"params": { | ||
"endpoint": { | ||
"url": "http://{cdn-link}.json" | ||
} | ||
} | ||
} | ||
]}, | ||
// This value below will be modified by the timeout RTD module if it successfully | ||
// fetches the timeout data. | ||
"bidderTimeout": 1500, | ||
... | ||
}); | ||
``` | ||
|
||
Sample Endpoint Response: | ||
``` | ||
{ | ||
"rules": { | ||
"includesVideo": { | ||
"true": 200, | ||
"false": 50 | ||
}, | ||
"numAdUnits" : { | ||
"1-5": 100, | ||
"6-10": 200, | ||
"11-15": 300 | ||
}, | ||
"deviceType": { | ||
"2": 50, | ||
"4": 100, | ||
"5": 200 | ||
}, | ||
"connectionSpeed": { | ||
"slow": 200, | ||
"medium": 100, | ||
"fast": 50, | ||
"unknown": 10 | ||
}, | ||
} | ||
``` | ||
|
||
### Rule Handling: | ||
The rules retrieved from the endpoint will be used to add time to the `bidderTimeout` based on certain features such as | ||
the user's deviceType, connection speed, etc. These rules can also be configured statically on page via a `rules` object. | ||
Note that the timeout Module will ignore the static rules if an endpoint url is provided. The timeout rules follow the | ||
format: | ||
``` | ||
{ | ||
'<feature>': { | ||
'<key>': <milliseconds to be added to timeout> | ||
} | ||
} | ||
``` | ||
See bottom of page for examples. | ||
|
||
Currently supported features: | ||
|
||
|Name |Description | Keys | Example | ||
| :------------ | :------------ | :------------ |:------------ | | ||
| includesVideo | Adds time to the timeout based on whether there is a video ad unit in the auction or not | 'true'/'false'| { "true": 200, "false": 50 } | | ||
| numAdUnits | Adds time based on the number of ad units. Ranges in the format `'lowerbound-upperbound` are accepted. This range is inclusive | numbers or number ranges | {"1": 50, "2-5": 100, "6-10": 200} | | ||
| deviceType | Adds time based on device type| 2, 4, or 5| {"2": 50, "4": 100} | | ||
| connectionSpeed | Adds time based on connection speed. `connectionSpeed` defaults to 'unknown' if connection speed cannot be determined | slow, medium, fast, or unknown | { "slow": 200} | | ||
|
||
If there are multiple rules set, all of them would be used and any that apply will be added to the base timeout. For example, if the rules object contains: | ||
``` | ||
{ | ||
"includesVideo": { | ||
"true": 200, | ||
"false": 50 | ||
}, | ||
"numAdUnits" : { | ||
"1-3": 100, | ||
"4-5": 200 | ||
} | ||
} | ||
``` | ||
and there are 3 ad units in the auction, all of which are banner, then the timeout to be added will be 150 milliseconds (50 for `includesVideo[false]` + 100 for `numAdUnits['1-3']`). | ||
|
||
Full example: | ||
``` | ||
pbjs.setConfig({ | ||
... | ||
"realTimeData": { | ||
"dataProviders": [{ | ||
"name": 'timeout', | ||
"params": { | ||
"rules": { | ||
"includesVideo": { | ||
"true": 200, | ||
"false": 50 | ||
}, | ||
"numAdUnits" : { | ||
"1-5": 100, | ||
"6-10": 200, | ||
"11-15": 300 | ||
}, | ||
"deviceType": { | ||
"2": 50, | ||
"4": 100, | ||
"5": 200 | ||
}, | ||
"connectionSpeed": { | ||
"slow": 200, | ||
"medium": 100, | ||
"fast": 50, | ||
"unknown": 10 | ||
}, | ||
} | ||
} | ||
]} | ||
... | ||
// The timeout RTD module will add time to `bidderTimeout` based on the rules set above. | ||
"bidderTimeout": 1500, | ||
``` |
Oops, something went wrong.