Skip to content

Rayn RTD Module: initial release #11054

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

Merged
merged 1 commit into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions integrationExamples/gpt/raynRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<html>
<head>
<script>
var segments = {
"4": {
"3": ["264", "267", "261"],
"4": ["438"]
},
"903555595": {
"7": {
"2": ["51", "246"]
}
},
};
window.localStorage.setItem(
"rayn-segtax",
JSON.stringify(segments)
);
</script>
<script>
var FAILSAFE_TIMEOUT = 2000;

var test_div_sizes = [
[300, 250],
[300, 600]
];

var adUnits = [
{
code: "test-div",
mediaTypes: {
banner: {
sizes: test_div_sizes,
},
},
bids: [
{
bidder: "appnexus",
params: {
placementId: 13144370,
},
},
],
},
];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
</script>
<script src="../../build/dev/prebid.js" async></script>

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

pbjs.que.push(function () {
pbjs.setConfig({
debug: true,
realTimeData: {
auctionDelay: AUCTION_DELAY,
dataProviders: [
{
name: "rayn",
waitForIt: true,
params: {
bidders: ['appnexus'],
integration: {
iabAudienceCategories: {
v1_1: {
tier: 3,
enabled: true,
},
},
iabContentCategories: {
v3_0: {
tier: 2,
enabled: true,
},
v2_2: {
tier: 1,
enabled: false,
},
},
}
},
},
],
},
});
pbjs.setBidderConfig({
bidders: ["appnexus", "pubmatic"],
config: {
ortb2: {
user: {
ext: {
data: {
registered: true,
interests: ["cars"],
},
},
},
},
},
});

pbjs.addAdUnits(adUnits);
pbjs.requestBids({ bidsBackHandler: sendAdserverRequest });
});

function sendAdserverRequest() {
document.getElementById("rayn-segments").innerHTML =
window.localStorage.getItem("rayn-segtax");

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>
(function () {
var gads = document.createElement("script");
gads.async = true;
gads.type = "text/javascript";
gads.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js';
var node = document.getElementsByTagName("script")[0];
node.parentNode.insertBefore(gads, node);
})();
</script>

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-1', test_div_sizes, "test-div").addService(googletag.pubads());
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h2>Rayn RTD Prebid</h2>

<div id="test-div">
<script>
googletag.cmd.push(function () {
googletag.display("test-div");
});
</script>
</div>

Rayn Segments:
<div id="rayn-segments"></div>
</body>
</html>
198 changes: 198 additions & 0 deletions modules/raynRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
* This module adds the Rayn provider to the real time data module
* The {@link module:modules/realTimeData} module is required
* The module will fetch real-time audience and context data from Rayn
* @module modules/raynRtdProvider
* @requires module:modules/realTimeData
*/

import { MODULE_TYPE_RTD } from '../src/activities/modules.js';
import { submodule } from '../src/hook.js';
import { getStorageManager } from '../src/storageManager.js';
import { deepAccess, deepSetValue, logError, logMessage, mergeDeep } from '../src/utils.js';

const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'rayn';
const RAYN_TCF_ID = 1220;
const LOG_PREFIX = 'RaynJS: ';
export const SEGMENTS_RESOLVER = 'rayn.io';
export const RAYN_LOCAL_STORAGE_KEY = 'rayn-segtax';

const defaultIntegration = {
iabAudienceCategories: {
v1_1: {
tier: 6,
enabled: true,
},
},
iabContentCategories: {
v3_0: {
tier: 4,
enabled: true,
},
v2_2: {
tier: 4,
enabled: true,
},
},
};

export const storage = getStorageManager({
moduleType: MODULE_TYPE_RTD,
moduleName: SUBMODULE_NAME,
});

function init(moduleConfig, userConsent) {
return true;
}

/**
* Create and return ORTB2 object with segtax and segments
* @param {number} segtax
* @param {Array} segmentIds
* @param {number} maxTier
* @return {Array}
*/
export function generateOrtbDataObject(segtax, segment, maxTier) {
const segmentIds = [];

try {
Object.keys(segment).forEach(tier => {
if (tier <= maxTier) {
segmentIds.push(...segment[tier].map((id) => {
return { id };
}))
}
});
} catch (error) {
logError(LOG_PREFIX, error);
}

return {
name: SEGMENTS_RESOLVER,
ext: {
segtax,
},
segment: segmentIds,
};
}

/**
* Generates checksum
* @param {string} url
* @returns {string}
*/
export function generateChecksum(stringValue) {
const l = stringValue.length;
let i = 0;
let h = 0;
if (l > 0) while (i < l) h = ((h << 5) - h + stringValue.charCodeAt(i++)) | 0;
return h.toString();
};

/**
* Gets an object of segtax and segment IDs from LocalStorage
* or return the default value provided.
* @param {string} key
* @return {Object}
*/
export function readSegments(key) {
try {
return JSON.parse(storage.getDataFromLocalStorage(key));
} catch (error) {
logError(LOG_PREFIX, error);
return null;
}
}

/**
* Pass segments to configured bidders, using ORTB2
* @param {Object} bidConfig
* @param {Array} bidders
* @param {Object} integrationConfig
* @param {Array} segments
* @return {void}
*/
export function setSegmentsAsBidderOrtb2(bidConfig, bidders, integrationConfig, segments, checksum) {
const raynOrtb2 = {};

const raynContentData = [];
if (integrationConfig.iabContentCategories.v2_2.enabled && segments[checksum] && segments[checksum][6]) {
raynContentData.push(generateOrtbDataObject(6, segments[checksum][6], integrationConfig.iabContentCategories.v2_2.tier));
}
if (integrationConfig.iabContentCategories.v3_0.enabled && segments[checksum] && segments[checksum][7]) {
raynContentData.push(generateOrtbDataObject(7, segments[checksum][7], integrationConfig.iabContentCategories.v3_0.tier));
}
if (raynContentData.length > 0) {
deepSetValue(raynOrtb2, 'site.content.data', raynContentData);
}

if (integrationConfig.iabAudienceCategories.v1_1.enabled && segments[4]) {
const raynUserData = [generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)];
deepSetValue(raynOrtb2, 'user.data', raynUserData);
}

if (!bidders || bidders.length === 0 || !segments || Object.keys(segments).length <= 0) {
mergeDeep(bidConfig?.ortb2Fragments?.global, raynOrtb2);
} else {
const bidderConfig = Object.fromEntries(
bidders.map((bidder) => [bidder, raynOrtb2]),
);
mergeDeep(bidConfig?.ortb2Fragments?.bidder, bidderConfig);
}
}

/**
* Real-time data retrieval from Rayn
* @param {Object} reqBidsConfigObj
* @param {function} callback
* @param {Object} config
* @param {Object} userConsent
* @return {void}
*/
function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) {
try {
const checksum = generateChecksum(window.location.href);

const segments = readSegments(RAYN_LOCAL_STORAGE_KEY);

const bidders = deepAccess(config, 'params.bidders');
const integrationConfig = mergeDeep(defaultIntegration, deepAccess(config, 'params.integration'));

if (segments && Object.keys(segments).length > 0 && (
segments[checksum] || (segments[4] &&
integrationConfig.iabAudienceCategories.v1_1.enabled &&
!integrationConfig.iabContentCategories.v2_2.enabled &&
!integrationConfig.iabContentCategories.v3_0.enabled
)
)) {
logMessage(LOG_PREFIX, `Segtax data from localStorage: ${JSON.stringify(segments)}`);
setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segments, checksum);
callback();
} else if (window.raynJS && typeof window.raynJS.getSegtax === 'function') {
window.raynJS.getSegtax().then((segtaxData) => {
logMessage(LOG_PREFIX, `Segtax data from RaynJS: ${JSON.stringify(segtaxData)}`);
setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segtaxData, checksum);
callback();
}).catch((error) => {
logError(LOG_PREFIX, error);
callback();
});
} else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not fall back to communicating with your endpoint from the module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann I'm not sure I understand what you mean exactly.
We have two steps, first we check for data in localStorage, then if our library is loaded we call it's function to retrieve the data and if none of the conditions are satisfied we just log a "no data" message and don't do anything.

Copy link
Collaborator

@patmmccann patmmccann Feb 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that. How about a third step; if neither local storage nor the library exist, you communicate with an endpoint to gather the segments? Not a deal breaker, just seems quite odd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann thank you for your observation. We may be adding this functionality at a later stage. For now we are relying on our customers to either run our library or populate local storage using our endpoints

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure your documentation reflects that

logMessage(LOG_PREFIX, 'No segtax data');
callback();
}
} catch (error) {
logError(LOG_PREFIX, error);
callback();
}
}

export const raynSubmodule = {
name: SUBMODULE_NAME,
init: init,
getBidRequestData: alterBidRequests,
gvlid: RAYN_TCF_ID,
};

submodule(MODULE_NAME, raynSubmodule);
Loading