Skip to content

Commit

Permalink
51Degrees RTD submodule: optimise ORTB2 enrichment speed (prebid#12394)
Browse files Browse the repository at this point in the history
* 51Degrees RTD submodule: optimise ORTB2 enrichment speed by eliminating redundant call

* 51Degrees RTD submodule: mock HEV

---------

Co-authored-by: Bohdan V <[email protected]>
  • Loading branch information
jwrosewell and BohdanVV authored Nov 12, 2024
1 parent 805714c commit e16c01f
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 32 deletions.
7 changes: 3 additions & 4 deletions integrationExamples/gpt/51DegreesRtdProvider_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
name: '51Degrees',
waitForIt: true,
params: {
// Get your resource key from https://configure.51degrees.com/tWrhNfY6
// Get your resource key from https://configure.51degrees.com/HNZ75HT1
resourceKey: '<YOUR_RESOURCE_KEY>',
// alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen end point
// onPremiseJSUrl: 'https://localhost/51Degrees.core.js'
Expand Down Expand Up @@ -181,12 +181,11 @@ <h3>div-banner-native-2</h3>
<h3>Testing/Debugging Guidance</h3>
<ol>
<li>Make sure you have <code>debug: true</code> under <code>pbjs.setConfig</code> in this example code (be sure to remove it for production!)
<li>Make sure you have replaced <code>&lt;YOUR RESOURCE KEY&gt;</code> in this example code with the one you have obtained
from the <a href="https://configure.51degrees.com/tWrhNfY6" target="blank;">51Degrees Configurator Tool</a></li>
<li>Make sure you have replaced <code>&lt;YOUR RESOURCE KEY&gt;</code> in this example code with the one you have obtained
from the <a href="https://configure.51degrees.com/HNZ75HT1" target="blank;">51Degrees Configurator Tool</a></li>
<li>Open DevTools Console in your browser and refresh the page</li>
<li>Observe the enriched ortb device data shown below and also in the console as part of the <code>[51Degrees RTD Submodule]: reqBidsConfigObj:</code> message (under <code>reqBidsConfigObj.global.device</code>)</li>
</ol>

</div>
<div id="enriched-51" style="display: none">
<h3>Enriched ORTB2 device data</h3>
Expand Down
78 changes: 54 additions & 24 deletions modules/51DegreesRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {submodule} from '../src/hook.js';
import {
deepAccess,
deepSetValue,
formatQS,
mergeDeep,
prefixLog,
} from '../src/utils.js';
Expand Down Expand Up @@ -107,15 +108,42 @@ export const extractConfig = (moduleConfig, reqBidsConfigObj) => {
* @param {Object} pathData API path data
* @param {string} [pathData.resourceKey] Resource key
* @param {string} [pathData.onPremiseJSUrl] On-premise JS URL
* @param {Object<string, any>} [pathData.hev] High entropy values
* @param {Window} [win] Window object (mainly for testing)
* @returns {string} 51Degrees JS URL
*/
export const get51DegreesJSURL = (pathData) => {
if (pathData.onPremiseJSUrl) {
return pathData.onPremiseJSUrl;
}
return `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`;
export const get51DegreesJSURL = (pathData, win) => {
const _window = win || window;
const baseURL = pathData.onPremiseJSUrl || `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`;

const queryPrefix = baseURL.includes('?') ? '&' : '?';
const qs = {};

deepSetNotEmptyValue(
qs,
'51D_GetHighEntropyValues',
pathData.hev && Object.keys(pathData.hev).length ? btoa(JSON.stringify(pathData.hev)) : null,
);
deepSetNotEmptyValue(qs, '51D_ScreenPixelsHeight', _window?.screen?.height);
deepSetNotEmptyValue(qs, '51D_ScreenPixelsWidth', _window?.screen?.width);
deepSetNotEmptyValue(qs, '51D_PixelRatio', _window?.devicePixelRatio);

const _qs = formatQS(qs);
const _qsString = _qs ? `${queryPrefix}${_qs}` : '';

return `${baseURL}${_qsString}`;
}

/**
* Retrieves high entropy values from `navigator.userAgentData` if available
*
* @param {Array<string>} hints - An array of hints indicating which high entropy values to retrieve
* @returns {Promise<undefined | Object<string, any>>} A promise that resolves to an object containing high entropy values if supported, or `undefined` if not
*/
export const getHighEntropyValues = async (hints) => {
return navigator?.userAgentData?.getHighEntropyValues?.(hints);
};

/**
* Check if meta[http-equiv="Delegate-CH"] tag is present in the document head and points to 51Degrees cloud
*
Expand Down Expand Up @@ -251,10 +279,6 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
logMessage('Resource key: ', resourceKey);
logMessage('On-premise JS URL: ', onPremiseJSUrl);

// Get 51Degrees JS URL, which is either cloud or on-premise
const scriptURL = get51DegreesJSURL(resourceKey ? {resourceKey} : {onPremiseJSUrl});
logMessage('URL of the script to be injected: ', scriptURL);

// Check if 51Degrees meta is present (cloud only)
if (resourceKey) {
logMessage('Checking if 51Degrees meta is present in the document head');
Expand All @@ -263,21 +287,27 @@ export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, user
}
}

// Inject 51Degrees script, get device data and merge it into the ORTB2 object
loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => {
logMessage('Successfully injected 51Degrees script');
const fod = /** @type {Object} */ (window.fod);
// Convert and merge device data in the callback
fod.complete((data) => {
logMessage('51Degrees raw data: ', data);
mergeDeep(
reqBidsConfigObj.ortb2Fragments.global,
convert51DegreesDataToOrtb2(data),
);
logMessage('reqBidsConfigObj: ', reqBidsConfigObj);
callback();
});
}, document, {crossOrigin: 'anonymous'});
getHighEntropyValues(['model', 'platform', 'platformVersion', 'fullVersionList']).then((hev) => {
// Get 51Degrees JS URL, which is either cloud or on-premise
const scriptURL = get51DegreesJSURL({resourceKey, onPremiseJSUrl, hev});
logMessage('URL of the script to be injected: ', scriptURL);

// Inject 51Degrees script, get device data and merge it into the ORTB2 object
loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => {
logMessage('Successfully injected 51Degrees script');
const fod = /** @type {Object} */ (window.fod);
// Convert and merge device data in the callback
fod.complete((data) => {
logMessage('51Degrees raw data: ', data);
mergeDeep(
reqBidsConfigObj.ortb2Fragments.global,
convert51DegreesDataToOrtb2(data),
);
logMessage('reqBidsConfigObj: ', reqBidsConfigObj);
callback();
});
}, document, {crossOrigin: 'anonymous'});
});
} catch (error) {
// In case of an error, log it and continue
logError(error);
Expand Down
106 changes: 102 additions & 4 deletions test/spec/modules/51DegreesRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,114 @@ describe('51DegreesRtdProvider', function() {
});

describe('get51DegreesJSURL', function() {
const hev = {
'brands': [
{
'brand': 'Chromium',
'version': '130'
},
{
'brand': 'Google Chrome',
'version': '130'
},
{
'brand': 'Not?A_Brand',
'version': '99'
}
],
'fullVersionList': [
{
'brand': 'Chromium',
'version': '130.0.6723.92'
},
{
'brand': 'Google Chrome',
'version': '130.0.6723.92'
},
{
'brand': 'Not?A_Brand',
'version': '99.0.0.0'
}
],
'mobile': false,
'model': '',
'platform': 'macOS',
'platformVersion': '14.6.1'
};
const mockWindow = {
...window,
screen: {
height: 1117,
width: 1728,
},
devicePixelRatio: 2,
};

it('returns the cloud URL if the resourceKey is provided', function() {
const config = {resourceKey: 'TEST_RESOURCE_KEY'};
expect(get51DegreesJSURL(config)).to.equal(
'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js'
expect(get51DegreesJSURL(config, mockWindow)).to.equal(
'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js?' +
`51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
`51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
`51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});

it('returns the on-premise URL if the onPremiseJSUrl is provided', function () {
const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'};
expect(get51DegreesJSURL(config, mockWindow)).to.equal(
`https://example.com/51Degrees.core.js?` +
`51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
`51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
`51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});

it('doesn\'t override static query string parameters', function () {
const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js?test=1'};
expect(get51DegreesJSURL(config, mockWindow)).to.equal(
`https://example.com/51Degrees.core.js?test=1&` +
`51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
`51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
`51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});

it('adds high entropy values to the query string, if available', async function () {
const config = {
onPremiseJSUrl: 'https://example.com/51Degrees.core.js',
hev,
};
expect(get51DegreesJSURL(config, mockWindow)).to.equal(
`https://example.com/51Degrees.core.js?` +
`51D_GetHighEntropyValues=${btoa(JSON.stringify(hev))}&` +
`51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
`51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
`51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});

it('returns the on-premise URL if the onPremiseJSUrl is provided', function() {
it('doesn\'t add high entropy values to the query string if object is empty', function () {
const config = {
onPremiseJSUrl: 'https://example.com/51Degrees.core.js',
hev: {},
};
expect(get51DegreesJSURL(config, mockWindow)).to.equal(
`https://example.com/51Degrees.core.js?` +
`51D_ScreenPixelsHeight=${mockWindow.screen.height}&` +
`51D_ScreenPixelsWidth=${mockWindow.screen.width}&` +
`51D_PixelRatio=${mockWindow.devicePixelRatio}`
);
});

it('keeps the original URL if none of the additional parameters are available', function () {
// delete screen and devicePixelRatio properties to test the case when they are not available
delete mockWindow.screen;
delete mockWindow.devicePixelRatio;

const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'};
expect(get51DegreesJSURL(config)).to.equal('https://example.com/51Degrees.core.js');
expect(get51DegreesJSURL(config, mockWindow)).to.equal('https://example.com/51Degrees.core.js');
expect(get51DegreesJSURL(config, window)).to.not.equal('https://example.com/51Degrees.core.js');
});
});

Expand Down

0 comments on commit e16c01f

Please sign in to comment.