Skip to content
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

Weborama RTD Module : update gdpr purpose ids verification for TCF v2.2 #11089

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
135 changes: 99 additions & 36 deletions modules/weboramaRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,18 @@ import {
getGlobal
} from '../src/prebidGlobal.js';
import {
deepAccess,
deepClone,
deepSetValue,
isEmpty,
isFn,
logError,
logMessage,
isArray,
isStr,
isBoolean,
isEmpty,
isFn,
isPlainObject,
isStr,
logWarn,
mergeDeep
mergeDeep,
prefixLog,
} from '../src/utils.js';
import {
submodule
Expand All @@ -126,9 +126,13 @@ import {
import {
getStorageManager
} from '../src/storageManager.js';
import {
MODULE_TYPE_RTD
} from '../src/activities/modules.js';
import adapterManager from '../src/adapterManager.js';
import {MODULE_TYPE_RTD} from '../src/activities/modules.js';
import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js';
import {
tryAppendQueryString
} from '../libraries/urlUtils/urlUtils.js';

/** @type {string} */
const MODULE_NAME = 'realTimeData';
Expand Down Expand Up @@ -159,6 +163,8 @@ const SFBX_LITE_DATA_SOURCE_LABEL = 'lite';
/** @type {number} */
const GVLID = 284;

const logger = prefixLog('[WeboramaRTD]');

export const storage = getStorageManager({
moduleType: MODULE_TYPE_RTD,
moduleName: SUBMODULE_NAME
Expand Down Expand Up @@ -199,9 +205,11 @@ class WeboramaRtdProvider {
* @method
* @param {Object} moduleConfig
* @param {?ModuleParams} moduleConfig.params
* @param {Object} userConsent
* @param {?Object} userConsent.gdpr
* @return {boolean} true if module was initialized with success
*/
init(moduleConfig) {
init(moduleConfig, userConsent) {
/** @type {Object} */
const globalDefaults = {
setPrebidTargeting: true,
Expand All @@ -219,8 +227,14 @@ class WeboramaRtdProvider {
this.#components.WeboUserData.data = null;
this.#components.SfbxLiteData.data = null;

this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, 'token');
this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION);
const weboCtxRequiredFields = ['token'];

this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, {
requiredFields: weboCtxRequiredFields,
});
this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION, {
userConsent: userConsent || {},
});
this.#components.SfbxLiteData.initialized = this.#initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION);

return Object.values(this.#components).some((c) => c.initialized);
Expand Down Expand Up @@ -251,7 +265,7 @@ class WeboramaRtdProvider {
const weboCtxConf = moduleParams.weboCtxConf || {};

this.#fetchContextualProfile(weboCtxConf, (data) => {
logMessage('fetchContextualProfile on getBidRequestData is done');
logger.logMessage('fetchContextualProfile on getBidRequestData is done');

this.#setWeboContextualProfile(data);
}, () => {
Expand All @@ -276,17 +290,17 @@ class WeboramaRtdProvider {
const profileHandlers = this.#buildProfileHandlers(moduleParams);

if (isEmpty(profileHandlers)) {
logMessage('no data to set targeting');
logger.logMessage('no data to set targeting');
return {};
}

try {
return adUnitsCodes.reduce((data, adUnitCode) => {
data[adUnitCode] = profileHandlers.reduce((targeting, ph) => {
// logMessage(`check if should set targeting for adunit '${adUnitCode}'`);
// logger.logMessage(`check if should set targeting for adunit '${adUnitCode}'`);
const [data, metadata] = this.#copyDataAndMetadata(ph);
if (ph.setTargeting(adUnitCode, data, metadata)) {
// logMessage(`set targeting for adunit '${adUnitCode}', source '${metadata.source}'`);
// logger.logMessage(`set targeting for adunit '${adUnitCode}', source '${metadata.source}'`);

mergeDeep(targeting, data);
}
Expand All @@ -297,7 +311,7 @@ class WeboramaRtdProvider {
return data;
}, {});
} catch (e) {
logError(`unable to format weborama rtd targeting data:`, e);
logger.logError(`unable to format weborama rtd targeting data:`, e);

return {};
}
Expand All @@ -309,10 +323,12 @@ class WeboramaRtdProvider {
* @private
* @param {ModuleParams} moduleParams
* @param {string} subSection subsection name to initialize
* @param {string[]} requiredFields
* @param {?Object} extra
* @param {string[]} extra.requiredFields
* @param {?Object} extra.userConsent
* @return {boolean} true if module subsection was initialized with success
*/
#initSubSection(moduleParams, subSection, ...requiredFields) {
#initSubSection(moduleParams, subSection, extra) {
/** @type {CommonConf} */
const weboSectionConf = moduleParams[subSection] || { enabled: false };

Expand All @@ -325,21 +341,59 @@ class WeboramaRtdProvider {
try {
this.#normalizeConf(moduleParams, weboSectionConf);

extra = extra || {};
const requiredFields = extra?.requiredFields || [];

requiredFields.forEach(field => {
if (!(field in weboSectionConf)) {
throw `missing required field '${field}''`;
throw `missing required field '${field}'`;
}
});

if (isPlainObject(extra?.userConsent?.gdpr) && !this.#checkTCFv2(extra.userConsent.gdpr)) {
throw 'gdpr consent not ok';
}
} catch (e) {
logError(`unable to initialize: error on ${subSection} configuration:`, e);
logger.logError(`unable to initialize: error on '${subSection}' configuration:`, e);
return false;
}

logMessage(`weborama ${subSection} initialized with success`);
logger.logMessage(`weborama '${subSection}' initialized with success`);

return true;
}

/**
* check gdpr consent data
* @method
* @private
* @param {Object} gdpr
* @param {?boolean} gdpr.gdprApplies
* @param {?Object} gdpr.vendorData
* @param {?Object} gdpr.vendorData.purpose
* @param {?Object.<number, boolean>} gdpr.vendorData.purpose.consents
* @param {?Object} gdpr.vendorData.vendor
* @param {?Object.<number, boolean>} gdpr.vendorData.vendor.consents
* @return {boolean}
*/
// eslint-disable-next-line no-dupe-class-members
#checkTCFv2(gdpr) {
if (gdpr?.gdprApplies !== true) {
return true;
}

if (deepAccess(gdpr, 'vendorData.vendor.consents') &&
deepAccess(gdpr, 'vendorData.purpose.consents')) {
return gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id
gdpr.vendorData.purpose.consents[1] === true && // info storage access
gdpr.vendorData.purpose.consents[3] === true && // create personalized ads
gdpr.vendorData.purpose.consents[4] === true && // select personalized ads
gdpr.vendorData.purpose.consents[5] === true && // create personalized content
gdpr.vendorData.purpose.consents[6] === true; // select personalized content
}

return true;
}
/**
* normalize submodule configuration
* @method
Expand Down Expand Up @@ -455,7 +509,7 @@ class WeboramaRtdProvider {
const profileHandlers = this.#buildProfileHandlers(moduleParams);

if (isEmpty(profileHandlers)) {
logMessage('no data to send to bidders');
logger.logMessage('no data to send to bidders');
return;
}

Expand All @@ -465,27 +519,27 @@ class WeboramaRtdProvider {
adUnits.forEach(
adUnit => adUnit.bids?.forEach(
bid => profileHandlers.forEach(ph => {
// logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`);
// logger.logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`);

const [data, metadata] = this.#copyDataAndMetadata(ph);
if (ph.sendToBidders(bid, adUnit.code, data, metadata)) {
// logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`);
// logger.logMessage(`handling bidder '${bid.bidder}' with ${ph.metadata.source} data`);

this.#handleBid(reqBidsConfigObj, bid, data, ph.metadata);
}
})
)
);
} catch (e) {
logError('unable to send data to bidders:', e);
logger.logError('unable to send data to bidders:', e);
}

profileHandlers.forEach(ph => {
try {
const [data, metadata] = this.#copyDataAndMetadata(ph);
ph.onData(data, metadata);
} catch (e) {
logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e);
logger.logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e);
}
});
}
Expand Down Expand Up @@ -529,7 +583,7 @@ class WeboramaRtdProvider {
try {
assetID = weboCtxConf.assetID();
} catch (e) {
logError('unexpected error while fetching asset id from callback', e);
logger.logError('unexpected error while fetching asset id from callback', e);

onDone();

Expand All @@ -538,7 +592,7 @@ class WeboramaRtdProvider {
}

if (!assetID) {
logError('missing asset id');
logger.logError('missing asset id');

onDone();

Expand All @@ -565,7 +619,7 @@ class WeboramaRtdProvider {
};

const error = (e, req) => {
logError(`unable to get weborama data`, e, req);
logger.logError(`unable to get weborama data`, e, req);

onDone();
};
Expand Down Expand Up @@ -625,7 +679,7 @@ class WeboramaRtdProvider {
if (profileHandler) {
ph.push(profileHandler);
} else {
logMessage(`skip ${source} profile: no data`);
logger.logMessage(`skip ${source} profile: no data`);
}

return ph;
Expand Down Expand Up @@ -703,17 +757,26 @@ class WeboramaRtdProvider {
#handleBid(reqBidsConfigObj, bid, profile, metadata) {
this.#handleBidViaORTB2(reqBidsConfigObj, bid.bidder, profile, metadata);

/** @type {Object.<string,string>} */
const bidderAliasRegistry = adapterManager.aliasRegistry || {};

/** @type {string} */
const bidder = bidderAliasRegistry[bid.bidder] || bid.bidder;
const bidder = this.#getAdapterNameForAlias(bid.bidder);

if (bidder == 'appnexus') {
this.#handleAppnexusBid(reqBidsConfigObj, bid, profile);
}
}

/**
* return adapter name based on alias, if any
* @method
* @private
* @param {string} aliasName
* @returns {string}
*/
// eslint-disable-next-line no-dupe-class-members
#getAdapterNameForAlias(aliasName) {
return adapterManager.aliasRegistry[aliasName] || aliasName;
}

/**
* function that handles bid request data
* @method
Expand Down Expand Up @@ -760,13 +823,13 @@ class WeboramaRtdProvider {
// eslint-disable-next-line no-dupe-class-members
#handleBidViaORTB2(reqBidsConfigObj, bidder, profile, metadata) {
if (isBoolean(metadata.user)) {
logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`);
logger.logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`);
const section = metadata.user ? 'user' : 'site';
const path = `${section}.ext.data`;

this.#setBidderOrtb2(reqBidsConfigObj.ortb2Fragments?.bidder, bidder, path, profile)
} else {
logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`);
logger.logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`);
}
}
/**
Expand Down
17 changes: 13 additions & 4 deletions modules/weboramaRtdProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Weborama provides a Real-Time Data Submodule for `Prebid.js`, allowing to easy i

* LiTE by SFBX® (Local inApp Trust Engine) provides “Zero Party Data” given by users, stored and calculated only on the user’s device. Through a unique cohorting system, it enables better monetization in a consent/consentless and identity-less mode.

Contact prebid-support@weborama.com for more information.
Contact [prebid-support@weborama.com] for more information.

### Publisher Usage

Expand Down Expand Up @@ -79,7 +79,7 @@ pbjs.setConfig({

Each module can perform two actions:

* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html]) via `prebid.js`
* set targeting on [GPT](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForGPTAsync.html) / [AST](https://docs.prebid.org/dev-docs/publisher-api-reference/setTargetingForAst.html) via `prebid.js`

* send data to other `prebid.js` bidder modules (check the complete list at the end of this page)

Expand Down Expand Up @@ -117,9 +117,9 @@ On this section we will explain the `params.weboCtxConf` subconfiguration:
| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present|
| baseURLProfileAPI | String| if present, update the domain of the contextual api| Optional. Default is `ctx.weborama.com` |

#### WAM User-Centric Configuration
#### User-Centric Configuration

To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you lust include the `wamfactory` script in your pages with `wam2gam` feature activated.
To be possible use the integration with Weborama Audience Manager (WAM) you must be a client with an account id and you must include the `wamfactory` script in your pages with `wam2gam` feature activated.
Please contact weborama if you don't have it.

On this section we will explain the `params.weboUserDataConf` subconfiguration:
Expand All @@ -134,6 +134,15 @@ On this section we will explain the `params.weboUserDataConf` subconfiguration:
| localStorageProfileKey| String | can be used to customize the local storage key | Optional |
| enabled | Boolean| if false, will ignore this configuration| Default is `true` if this section is present|

##### User Consent

The WAM User-Centric configuration will check for user consent if gdpr applies. It will check for consent:

* Vendor ID 284 (Weborama)
* Purpose IDs: 1, 3, 4, 5 and 6

If the user consent does not match such conditions, this module will not load, means we will not check for any data in local storage and the default profile will be ignored.

#### Sfbx LiTE Site-Centric Configuration

To be possible use the integration between Weborama and Sfbx LiTE you should also contact SFBX® to setup this product.
Expand Down
Loading