diff --git a/integrationExamples/gpt/sirdataRtdProvider_example.html b/integrationExamples/gpt/sirdataRtdProvider_example.html
new file mode 100644
index 00000000000..444c9133905
--- /dev/null
+++ b/integrationExamples/gpt/sirdataRtdProvider_example.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Give consent or make a choice in Europe. Module will add key/value pairs in ad calls. Check out for sd_rtd key in Google Ad call (https://securepubads.g.doubleclick.net/gampad/ads...) and in the payload sent to Xandr to endpoint https://ib.adnxs.com/ut/v3/prebid : tags[0].keywords.key[sd_rtd] should have an array of string as value. This array will mix user segments and/or page categories based on user's choices.
+
+
+ Basic Prebid.js Example
+ Div-1
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 0f62627822a..f5ee0500e13 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -33,6 +33,7 @@
"haloRtdProvider",
"jwplayerRtdProvider",
"reconciliationRtdProvider",
- "geoedgeRtdProvider"
+ "geoedgeRtdProvider",
+ "sirdataRtdProvider"
]
}
diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js
new file mode 100644
index 00000000000..373468b2f14
--- /dev/null
+++ b/modules/sirdataRtdProvider.js
@@ -0,0 +1,402 @@
+/**
+ * This module adds Sirdata provider to the real time data module
+ * The {@link module:modules/realTimeData} module is required
+ * The module will fetch segments (user-centric) and categories (page-centric) from Sirdata server
+ * The module will automatically handle user's privacy and choice in California (IAB TL CCPA Framework) and in Europe (IAB EU TCF FOR GDPR)
+ * @module modules/sirdataRtdProvider
+ * @requires module:modules/realTimeData
+ */
+import {getGlobal} from '../src/prebidGlobal.js';
+import * as utils from '../src/utils.js';
+import {submodule} from '../src/hook.js';
+import {ajax} from '../src/ajax.js';
+import findIndex from 'core-js-pure/features/array/find-index.js';
+import { getRefererInfo } from '../src/refererDetection.js';
+import { config } from '../src/config.js';
+
+/** @type {string} */
+const MODULE_NAME = 'realTimeData';
+const SUBMODULE_NAME = 'SirdataRTDModule';
+
+export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) {
+ const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits;
+ moduleConfig.params = moduleConfig.params || {};
+
+ var tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : '');
+ var gdprApplies = (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies ? userConsent.gdpr.gdprApplies : '');
+
+ moduleConfig.params.partnerId = moduleConfig.params.partnerId ? moduleConfig.params.partnerId : 1;
+ moduleConfig.params.key = moduleConfig.params.key ? moduleConfig.params.key : 1;
+
+ var sirdataDomain;
+ var sendWithCredentials;
+
+ if (userConsent.coppa || (userConsent.usp && (userConsent.usp[0] == '1' && (userConsent.usp[1] == 'N' || userConsent.usp[2] == 'Y')))) {
+ // if children or "Do not Sell" management in California, no segments, page categories only whatever TCF signal
+ sirdataDomain = 'cookieless-data.com';
+ sendWithCredentials = false;
+ gdprApplies = null;
+ tcString = '';
+ } else if (getGlobal().getConfig('consentManagement.gdpr')) {
+ // Default endpoint is cookieless if gdpr management is set. Needed because the cookie-based endpoint will fail and return error if user is located in Europe and no consent has been given
+ sirdataDomain = 'cookieless-data.com';
+ sendWithCredentials = false;
+ }
+
+ // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply
+ if (!sirdataDomain || !gdprApplies || (utils.deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) {
+ sirdataDomain = 'sddan.com';
+ sendWithCredentials = true;
+ }
+
+ var actualUrl = moduleConfig.params.actualUrl || getRefererInfo().referer;
+
+ const url = 'https://kvt.' + sirdataDomain + '/api/v1/public/p/' + moduleConfig.params.partnerId + '/d/' + moduleConfig.params.key + '/s?callback=&gdpr=' + gdprApplies + '&gdpr_consent=' + tcString + (actualUrl ? '&url=' + actualUrl : '');
+ ajax(url, {
+ success: function (response, req) {
+ if (req.status === 200) {
+ try {
+ const data = JSON.parse(response);
+ if (data && data.segments) {
+ addSegmentData(adUnits, data, moduleConfig, onDone);
+ } else {
+ onDone();
+ }
+ } catch (e) {
+ onDone();
+ utils.logError('unable to parse Sirdata data' + e);
+ }
+ } else if (req.status === 204) {
+ onDone();
+ }
+ },
+ error: function () {
+ onDone();
+ utils.logError('unable to get Sirdata data');
+ }
+ },
+ null,
+ {
+ contentType: 'text/plain',
+ method: 'GET',
+ withCredentials: sendWithCredentials,
+ referrerPolicy: 'unsafe-url',
+ crossOrigin: true
+ });
+}
+
+export function setGlobalOrtb2(segments, categories) {
+ try {
+ let addOrtb2 = {};
+ let testGlobal = getGlobal().getConfig('ortb2') || {};
+ if (!utils.deepAccess(testGlobal, 'user.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.user.ext.data.sd_rtd, segments)) {
+ utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {});
+ }
+ if (!utils.deepAccess(testGlobal, 'site.ext.data.sd_rtd') || !utils.deepEqual(testGlobal.site.ext.data.sd_rtd, categories)) {
+ utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {});
+ }
+ if (!utils.isEmpty(addOrtb2)) {
+ let ortb2 = {ortb2: utils.mergeDeep({}, testGlobal, addOrtb2)};
+ getGlobal().setConfig(ortb2);
+ }
+ } catch (e) {
+ utils.logError(e)
+ }
+
+ return true;
+}
+
+export function setBidderOrtb2(bidder, segments, categories) {
+ try {
+ let addOrtb2 = {};
+ let testBidder = utils.deepAccess(config.getBidderConfig(), bidder + '.ortb2') || {};
+ if (!utils.deepAccess(testBidder, 'user.ext.data.sd_rtd') || !utils.deepEqual(testBidder.user.ext.data.sd_rtd, segments)) {
+ utils.deepSetValue(addOrtb2, 'user.ext.data.sd_rtd', segments || {});
+ }
+ if (!utils.deepAccess(testBidder, 'site.ext.data.sd_rtd') || !utils.deepEqual(testBidder.site.ext.data.sd_rtd, categories)) {
+ utils.deepSetValue(addOrtb2, 'site.ext.data.sd_rtd', categories || {});
+ }
+ if (!utils.isEmpty(addOrtb2)) {
+ let ortb2 = {ortb2: utils.mergeDeep({}, testBidder, addOrtb2)};
+ getGlobal().setBidderConfig({ bidders: [bidder], config: ortb2 });
+ }
+ } catch (e) {
+ utils.logError(e)
+ }
+
+ return true;
+}
+
+export function loadCustomFunction (todo, adUnit, list, data, bid) {
+ try {
+ if (typeof todo == 'function') {
+ todo(adUnit, list, data, bid);
+ }
+ } catch (e) { utils.logError(e); }
+ return true;
+}
+
+export function getSegAndCatsArray(data, minScore) {
+ var sirdataData = {'segments': [], 'categories': []};
+ minScore = minScore && typeof minScore == 'number' ? minScore : 30;
+ try {
+ if (data && data.contextual_categories) {
+ for (let catId in data.contextual_categories) {
+ let value = data.contextual_categories[catId];
+ if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) {
+ sirdataData.categories.push(catId.toString());
+ }
+ }
+ }
+ } catch (e) { utils.logError(e); }
+ try {
+ if (data && data.segments) {
+ for (let segId in data.segments) {
+ sirdataData.segments.push(data.segments[segId].toString());
+ }
+ }
+ } catch (e) { utils.logError(e); }
+ return sirdataData;
+}
+
+export function addSegmentData(adUnits, data, moduleConfig, onDone) {
+ moduleConfig = moduleConfig || {};
+ moduleConfig.params = moduleConfig.params || {};
+ const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30;
+ var sirdataData = getSegAndCatsArray(data, globalMinScore);
+
+ if (!sirdataData || (sirdataData.segments.length < 1 && sirdataData.categories.length < 1)) { utils.logError('no cats'); onDone(); return adUnits; }
+
+ const sirdataList = sirdataData.segments.concat(sirdataData.categories);
+
+ var curationData = {'segments': [], 'categories': []};
+ var curationId = '1';
+ const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders));
+
+ // Global ortb2
+ if (!biddersParamsExist) {
+ setGlobalOrtb2(sirdataData.segments, sirdataData.categories);
+ }
+
+ // Google targeting
+ if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) {
+ try {
+ // For curation Google is pid 27449
+ curationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : '27449');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], globalMinScore);
+ }
+ window.googletag.pubads().getSlots().forEach(function(n) {
+ if (typeof n.setTargeting !== 'undefined') {
+ n.setTargeting('sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ })
+ } catch (e) { utils.logError(e); }
+ }
+
+ // Bid targeting level for FPD non-generic biders
+ var bidderIndex = '';
+ var indexFound = false;
+
+ adUnits.forEach(adUnit => {
+ if (!biddersParamsExist && !utils.deepAccess(adUnit, 'ortb2Imp.ext.data.sd_rtd')) {
+ utils.deepSetValue(adUnit, 'ortb2Imp.ext.data.sd_rtd', sirdataList);
+ }
+
+ adUnit.hasOwnProperty('bids') && adUnit.bids.forEach(bid => {
+ bidderIndex = (moduleConfig.params.hasOwnProperty('bidders') ? findIndex(moduleConfig.params.bidders, function(i) { return i.bidder === bid.bidder; }) : false);
+ indexFound = (!!(typeof bidderIndex == 'number' && bidderIndex >= 0));
+ try {
+ curationData = {'segments': [], 'categories': []};
+ let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore)
+
+ if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) {
+ switch (bid.bidder) {
+ case 'appnexus':
+ case 'appnexusAst':
+ case 'brealtime':
+ case 'emxdigital':
+ case 'pagescience':
+ case 'gourmetads':
+ case 'matomy':
+ case 'featureforward':
+ case 'oftmedia':
+ case 'districtm':
+ case 'adasta':
+ case 'beintoo':
+ case 'gravity':
+ case 'msq_classic':
+ case 'msq_max':
+ case '366_apx':
+ // For curation Xandr is pid 27446
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27446');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ utils.deepSetValue(bid, 'params.keywords.sd_rtd', sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ case 'smartadserver':
+ case 'smart':
+ var target = [];
+ if (bid.hasOwnProperty('params') && bid.params.hasOwnProperty('target')) {
+ target.push(bid.params.target);
+ }
+ // For curation Smart is pid 27440
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27440');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) {
+ if (target.indexOf('sd_rtd=' + entry) === -1) {
+ target.push('sd_rtd=' + entry);
+ }
+ });
+ utils.deepSetValue(bid, 'params.target', target.join(';'));
+ }
+ break;
+
+ case 'rubicon':
+ // For curation Magnite is pid 27518
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27452');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ case 'ix':
+ var ixConfig = getGlobal().getConfig('ix.firstPartyData.sd_rtd');
+ if (!ixConfig) {
+ // For curation index is pid 27248
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27248');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ var cappIxCategories = [];
+ var ixLength = 0;
+ var ixLimit = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('sizeLimit') ? moduleConfig.params.bidders[bidderIndex].sizeLimit : 1000);
+ // Push ids For publisher use and for curation if exists but limit size because the bidder uses GET parameters
+ sirdataList.concat(curationData.segments).concat(curationData.categories).forEach(function(entry) {
+ if (ixLength < ixLimit) {
+ cappIxCategories.push(entry);
+ ixLength += entry.toString().length;
+ }
+ });
+ getGlobal().setConfig({ix: {firstPartyData: {sd_rtd: cappIxCategories}}});
+ }
+ }
+ break;
+
+ case 'proxistore':
+ // For curation Proxistore is pid 27484
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27484');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ } else {
+ data.shared_taxonomy[curationId] = {contextual_categories: {}};
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ utils.deepSetValue(bid, 'ortb2.user.ext.data', {segments: sirdataData.segments.concat(curationData.segments), contextual_categories: {...data.contextual_categories, ...data.shared_taxonomy[curationId].contextual_categories}});
+ }
+ break;
+
+ case 'criteo':
+ // For curation Smart is pid 27443
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27443');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ setBidderOrtb2(bid.bidder, sirdataList.concat(curationData.segments).concat(curationData.categories), sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ case 'triplelift':
+ // For curation Triplelift is pid 27518
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27518');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ case 'avct':
+ case 'avocet':
+ // For curation Avocet is pid 27522
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27522');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ case 'smaato':
+ // For curation Smaato is pid 27520
+ curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : '27520');
+ if (data.shared_taxonomy && data.shared_taxonomy[curationId]) {
+ curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore);
+ }
+ if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) {
+ loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataList.concat(curationData.segments).concat(curationData.categories), data, bid);
+ } else {
+ setBidderOrtb2(bid.bidder, data.segments.concat(curationData.segments), sirdataList.concat(curationData.segments).concat(curationData.categories));
+ }
+ break;
+
+ default:
+ if (!biddersParamsExist || indexFound) {
+ if (!utils.deepAccess(bid, 'ortb2.site.ext.data.sd_rtd')) {
+ utils.deepSetValue(bid, 'ortb2.site.ext.data.sd_rtd', sirdataData.categories);
+ }
+ if (!utils.deepAccess(bid, 'ortb2.user.ext.data.sd_rtd')) {
+ utils.deepSetValue(bid, 'ortb2.user.ext.data.sd_rtd', sirdataData.segments);
+ }
+ }
+ }
+ }
+ } catch (e) { utils.logError(e) }
+ })
+ });
+
+ onDone();
+ return adUnits;
+}
+
+export function init(config) {
+ return true;
+}
+
+export const sirdataSubmodule = {
+ name: SUBMODULE_NAME,
+ init: init,
+ getBidRequestData: getSegmentsAndCategories
+};
+
+submodule(MODULE_NAME, sirdataSubmodule);
diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md
new file mode 100644
index 00000000000..f67e34db43a
--- /dev/null
+++ b/modules/sirdataRtdProvider.md
@@ -0,0 +1,169 @@
+# Sirdata Real-Time Data Submodule
+
+Module Name: Sirdata Rtd Provider
+Module Type: Rtd Provider
+Maintainer: bob@sirdata.com
+
+# Description
+
+Sirdata provides a disruptive API that allows its partners to leverage its
+cutting-edge contextualization technology and its audience segments based on
+cookies and consent or without cookies nor consent!
+
+User-based segments and page-level automatic contextual categories will be
+attached to bid request objects sent to different SSPs in order to optimize
+targeting.
+
+Automatic integration with Google Ad Manager and major bidders like Xandr/Appnexus,
+Smartadserver, Index Exchange, Proxistore, Magnite/Rubicon or Triplelift !
+
+User's country and choice management are included in the module, so it's 100%
+compliant with local and regional laws like GDPR and CCPA/CPRA.
+
+ORTB2 compliant and FPD support for Prebid versions < 4.29
+
+Contact bob@sirdata.com for information.
+
+### Publisher Usage
+
+Compile the Sirdata RTD module into your Prebid build:
+
+`gulp build --modules=rtdModule,sirdataRtdProvider`
+
+Add the Sirdata RTD provider to your Prebid config.
+
+Segments ids (user-centric) and category ids (page-centric) will be provided
+salted and hashed : you can use them with a dedicated and private matching table.
+Should you want to allow a SSP or a partner to curate your media and operate
+cross-publishers campaigns with our data, please ask Sirdata (bob@sirdata.com) to
+open it for you account.
+
+```
+pbjs.setConfig(
+ ...
+ realTimeData: {
+ auctionDelay: 1000,
+ dataProviders: [
+ {
+ name: "SirdataRTDModule",
+ waitForIt: true,
+ params: {
+ partnerId: 1,
+ key: 1,
+ setGptKeyValues: true,
+ contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale)
+ actualUrl: actual_url, //top location url, for contextual categories
+ bidders: [{
+ bidder: 'appnexus',
+ adUnitCodes: ['adUnit-1','adUnit-2'],
+ customFunction: overrideAppnexus,
+ curationId: '111',
+ },{
+ bidder: 'ix',
+ sizeLimit: 1200 //specific to Index Exchange,
+ contextualMinRelevancyScore: 50, //Min score to filter contextual category for curation in the bidder (0-100 scale)
+ }]
+ }
+ }
+ ]
+ }
+ ...
+}
+```
+
+### Parameter Descriptions for the Sirdata Configuration Section
+
+| Name |Type | Description | Notes |
+| :------------ | :------------ | :------------ |:------------ |
+| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' |
+| waitForIt | Boolean | Mandatory. Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false but recommended to true |
+| params | Object | | Optional |
+| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. |
+| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and get one running at bob@sirdata.com | Mandatory. Defaults 1. |
+| params.setGptKeyValues | Boolean | This parameter Sirdata to set Targeting for GPT/GAM | Optional. Defaults to true. |
+| params.contextualMinRelevancyScore | Integer | Min score to keep filter category in the bidders (0-100 scale). Optional. Defaults to 30. |
+| params.bidders | Object | Dictionary of bidders you would like to supply Sirdata data for. | Optional. In case no bidder is specified Sirdata will atend to ad data custom and ortb2 to all bidders, adUnits & Globalconfig |
+
+Bidders can receive common setting :
+| Name |Type | Description | Notes |
+| :------------ | :------------ | :------------ |:------------ |
+| bidder | String | Bidder name | Mandatory if params.bidders are specified |
+| adUnitCodes | Array of String | Use if you want to limit data injection to specified adUnits for the bidder | Optional. Default is false and data shared with the bidder isn't filtered |
+| customFunction | Function | Use it to override the way data is shared with a bidder | Optional. Default is false |
+| curationId | String | Specify the curation ID of the bidder. Provided by Sirdata, request it at bob@sirdata.com | Optional. Default curation ids are specified for main bidders |
+| contextualMinRelevancyScore | Integer | Min score to filter contextual categories for curation in the bidder (0-100 scale). Optional. Defaults to 30 or global params.contextualMinRelevancyScore if exits. |
+| sizeLimit | Integer | used only for bidder 'ix' to limit the size of the get parameter in Index Exchange ad call | Optional. Default is 1000 |
+
+
+### Overriding data sharing function
+As indicated above, it is possible to provide your own bid augmentation
+functions. This is useful if you know a bid adapter's API supports segment
+fields which aren't specifically being added to request objects in the Prebid
+bid adapter.
+
+Please see the following example, which provides a function to modify bids for
+a bid adapter called ix and overrides the appnexus.
+
+data Object format for usage in this kind of function :
+{
+ "segments":[111111,222222],
+ "contextual_categories":{"333333":100},
+ "shared_taxonomy":{
+ "27446":{ //CurationId
+ "segments":[444444,555555],
+ "contextual_categories":{"666666":100}
+ }
+ }
+}
+
+```
+function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) {
+ for (var i = 0; i < segmentsArray.length; i++) {
+ if (segmentsArray[i]) {
+ bid.params.user.segments.push(segmentsArray[i]);
+ }
+ }
+}
+
+pbjs.setConfig(
+ ...
+ realTimeData: {
+ auctionDelay: 1000,
+ dataProviders: [
+ {
+ name: "SirdataRTDModule",
+ waitForIt: true,
+ params: {
+ partnerId: 1,
+ key: 1,
+ setGptKeyValues: true,
+ contextualMinRelevancyScore: 50, //Min score to keep contextual category in the bidders (0-100 scale)
+ actualUrl: actual_url, //top location url, for contextual categories
+ bidders: [{
+ bidder: 'appnexus',
+ customFunction: overrideAppnexus,
+ curationId: '111'
+ },{
+ bidder: 'ix',
+ sizeLimit: 1200, //specific to Index Exchange
+ customFunction: function(adUnit, segmentsArray, dataObject, bid) {
+ bid.params.contextual.push(dataObject.contextual_categories);
+ },
+ }]
+ }
+ }
+ ]
+ }
+ ...
+}
+```
+
+### Testing
+
+To view an example of available segments returned by Sirdata's backends:
+
+`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter`
+
+and then point your browser at:
+
+`http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html`
\ No newline at end of file
diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js
new file mode 100644
index 00000000000..a16359c50cb
--- /dev/null
+++ b/test/spec/modules/sirdataRtdProvider_spec.js
@@ -0,0 +1,94 @@
+import { addSegmentData, getSegmentsAndCategories, sirdataSubmodule } from 'modules/sirdataRtdProvider.js';
+import { server } from 'test/mocks/xhr.js';
+
+const responseHeader = {'Content-Type': 'application/json'};
+
+describe('sirdataRtdProvider', function() {
+ describe('sirdataSubmodule', function() {
+ it('successfully instantiates', function () {
+ expect(sirdataSubmodule.init()).to.equal(true);
+ });
+ });
+
+ describe('Add Segment Data', function() {
+ it('adds segment data', function() {
+ const config = {
+ params: {
+ setGptKeyValues: false,
+ contextualMinRelevancyScore: 50,
+ bidders: [{
+ bidder: 'appnexus'
+ }, {
+ bidder: 'other'
+ }]
+ }
+ };
+
+ let adUnits = [
+ {
+ bids: [{
+ bidder: 'appnexus',
+ params: {
+ placementId: 13144370
+ }
+ }, {
+ bidder: 'other'
+ }]
+ }
+ ];
+
+ let data = {
+ segments: [111111, 222222],
+ contextual_categories: {'333333': 100}
+ };
+
+ addSegmentData(adUnits, data, config, () => {});
+ expect(adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']);
+ expect(adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']);
+ expect(adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']);
+ });
+ });
+
+ describe('Get Segments And Categories', function() {
+ it('gets data from async request and adds segment data', function() {
+ const config = {
+ params: {
+ setGptKeyValues: false,
+ contextualMinRelevancyScore: 50,
+ bidders: [{
+ bidder: 'appnexus'
+ }, {
+ bidder: 'other'
+ }]
+ }
+ };
+
+ let reqBidsConfigObj = {
+ adUnits: [{
+ bids: [{
+ bidder: 'appnexus',
+ params: {
+ placementId: 13144370
+ }
+ }, {
+ bidder: 'other'
+ }]
+ }]
+ };
+
+ let data = {
+ segments: [111111, 222222],
+ contextual_categories: {'333333': 100}
+ };
+
+ getSegmentsAndCategories(reqBidsConfigObj, () => {}, config, {});
+
+ let request = server.requests[0];
+ request.respond(200, responseHeader, JSON.stringify(data));
+
+ expect(reqBidsConfigObj.adUnits[0].bids[0].params.keywords).to.have.deep.property('sd_rtd', ['111111', '222222', '333333']);
+ expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.site.ext.data).to.have.deep.property('sd_rtd', ['333333']);
+ expect(reqBidsConfigObj.adUnits[0].bids[1].ortb2.user.ext.data).to.have.deep.property('sd_rtd', ['111111', '222222']);
+ });
+ });
+});