Skip to content

Commit

Permalink
Merge pull request #8 from prebid/master
Browse files Browse the repository at this point in the history
Sync with master
  • Loading branch information
pm-nitin-shirsat committed Aug 25, 2023
2 parents 51fc097 + 3c70ac7 commit 6a72e8c
Show file tree
Hide file tree
Showing 180 changed files with 9,531 additions and 2,626 deletions.
15 changes: 3 additions & 12 deletions integrationExamples/gpt/growthcode.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,11 @@
provider: 'growthCodeAnalytics',
options: {
pid: 'TEST01',
//url: 'http://localhost:8080/v3/pb/analytics',
trackEvents: [
'auctionInit',
'auctionEnd',
'bidAdjustment',
'bidTimeout',
'bidTimeout',
'bidRequested',
'bidResponse',
'setTargeting',
'requestBids',
'addAdUnits',
'noBid',
'bidWon',
'bidderDone']
]
}
});
pbjs.setConfig({
Expand All @@ -80,7 +71,7 @@
auctionDelay: 1000,
dataProviders: [{
name: 'growthCodeRtd',
waitForIt: true,
waitForIt: false,
params: {
pid: 'TEST01',
}
Expand Down
72 changes: 72 additions & 0 deletions integrationExamples/topics/topics-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// This is an example of a server-side endpoint that is utilizing the Topics API header functionality.
// Note: This test endpoint requires the following to run: node.js, npm, express, cors, body-parser

const bodyParser = require('body-parser');
const cors = require('cors');
const express = require('express');

const port = process.env.PORT || 3000;

const app = express();
app.use(cors());
app.use(
bodyParser.urlencoded({
extended: true,
})
);
app.use(bodyParser.json());
app.use(express.static('public'));
app.set('port', port);

const listener = app.listen(port, () => {
const host =
listener.address().address === '::'
? 'http://localhost'
: 'http://' + listener.address().address;
// eslint-disable-next-line no-console
console.log(
`${__filename} is listening on ${host}:${listener.address().port}\n`
);
});

app.get('*', (req, res) => {
res.setHeader('Observe-Browsing-Topics', '?1');

const resData = {
segment: {
domain: req.hostname,
topics: generateTopicArrayFromHeader(req.headers['sec-browsing-topics']),
bidder: req.query['bidder'],
},
date: Date.now(),
};

res.json(resData);
});

const generateTopicArrayFromHeader = (topicString) => {
const result = [];
const topicArray = topicString.split(', ');
if (topicArray.length > 1) {
topicArray.pop();
topicArray.map((topic) => {
const topicId = topic.split(';')[0];
const versionsString = topic.split(';')[1].split('=')[1];
const [config, taxonomy, model] = versionsString.split(':');
const numTopicsWithSameVersions = topicId
.substring(1, topicId.length - 1)
.split(' ');

numTopicsWithSameVersions.map((tpId) => {
result.push({
topic: tpId,
version: versionsString,
configVersion: config,
taxonomyVersion: taxonomy,
modelVersion: model,
});
});
});
}
return result;
};
62 changes: 46 additions & 16 deletions libraries/cmp/cmpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,53 @@ import {GreedyPromise} from '../../src/utils/promise.js';
* @typedef {function} CMPClient
*
* @param {{}} params CMP parameters. Currently this is a subset of {command, callback, parameter, version}.
* @returns {Promise<*>} a promise that:
* - if a `callback` param was provided, resolves (with no result) just before the first time it's run;
* - if `callback` was *not* provided, resolves to the return value of the CMP command
* @param {bool} once if true, discard cross-frame event listeners once a reply message is received.
* @returns {Promise<*>} a promise to the API's "result" - see the `mode` argument to `cmpClient` on how that's determined.
* @property {boolean} isDirect true if the CMP is directly accessible (no postMessage required)
* @property {() => void} close close the client; currently, this just stops listening for cross-frame messages.
*/

/**
* Returns a function that can interface with a CMP regardless of where it's located.
* Returns a client function that can interface with a CMP regardless of where it's located.
*
* @param apiName name of the CMP api, e.g. "__gpp"
* @param apiVersion? CMP API version
* @param apiArgs? names of the arguments taken by the api function, in order.
* @param callbackArgs? names of the cross-frame response payload properties that should be passed as callback arguments, in order
* @param mode? controls the callbacks passed to the underlying API, and how the promises returned by the client are resolved.
*
* The client behaves differently when it's provided a `callback` argument vs when it's not - for short, let's name these
* cases "subscriptions" and "one-shot calls" respectively:
*
* With `mode: MODE_MIXED` (the default), promises returned on subscriptions are resolved to undefined when the callback
* is first run (that is, the promise resolves when the CMP replies, but what it replies with is discarded and
* left for the callback to deal with). For one-shot calls, the returned promise is resolved to the API's
* return value when it's directly accessible, or with the result from the first (and, presumably, the only)
* cross-frame reply when it's not;
*
* With `mode: MODE_RETURN`, the returned promise always resolves to the API's return value - which is taken to be undefined
* when cross-frame;
*
* With `mode: MODE_CALLBACK`, the underlying API is expected to never directly return anything significant; instead,
* it should always accept a callback and - for one-shot calls - invoke it only once with the result. The client will
* automatically generate an appropriate callback for one-shot calls and use the result it's given to resolve
* the returned promise. Subscriptions are treated in the same way as MODE_MIXED.
*
* @param win
* @returns {CMPClient} CMP invocation function (or null if no CMP was found).
*/

export const MODE_MIXED = 0;
export const MODE_RETURN = 1;
export const MODE_CALLBACK = 2;

export function cmpClient(
{
apiName,
apiVersion,
apiArgs = ['command', 'callback', 'parameter', 'version'],
callbackArgs = ['returnValue', 'success'],
mode = MODE_MIXED,
},
win = window
) {
Expand Down Expand Up @@ -89,15 +114,15 @@ export function cmpClient(
}

function wrapCallback(callback, resolve, reject, preamble) {
const haveCb = typeof callback === 'function';

return function (result, success) {
preamble && preamble();
const resolver = success == null || success ? resolve : reject;
if (typeof callback === 'function') {
resolver();
return callback.apply(this, arguments);
} else {
resolver(result);
if (mode !== MODE_RETURN) {
const resolver = success == null || success ? resolve : reject;
resolver(haveCb ? undefined : result);
}
haveCb && callback.apply(this, arguments);
}
}

Expand All @@ -108,17 +133,17 @@ export function cmpClient(
return new GreedyPromise((resolve, reject) => {
const ret = cmpFrame[apiName](...resolveParams({
...params,
callback: params.callback && wrapCallback(params.callback, resolve, reject)
callback: (params.callback || mode === MODE_CALLBACK) ? wrapCallback(params.callback, resolve, reject) : undefined,
}).map(([_, val]) => val));
if (params.callback == null) {
if (mode === MODE_RETURN || (params.callback == null && mode === MODE_MIXED)) {
resolve(ret);
}
});
};
} else {
win.addEventListener('message', handleMessage, false);

client = function invokeCMPFrame(params) {
client = function invokeCMPFrame(params, once = false) {
return new GreedyPromise((resolve, reject) => {
// call CMP via postMessage
const callId = Math.random().toString();
Expand All @@ -129,11 +154,16 @@ export function cmpClient(
}
};

cmpCallbacks[callId] = wrapCallback(params?.callback, resolve, reject, params?.callback == null && (() => { delete cmpCallbacks[callId] }));
cmpCallbacks[callId] = wrapCallback(params?.callback, resolve, reject, (once || params?.callback == null) && (() => { delete cmpCallbacks[callId] }));
cmpFrame.postMessage(msg, '*');
if (mode === MODE_RETURN) resolve();
});
};
}
client.isDirect = isDirect;
return client;
return Object.assign(client, {
isDirect,
close() {
!isDirect && win.removeEventListener('message', handleMessage);
}
})
}
33 changes: 25 additions & 8 deletions libraries/mspa/activityControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ACTIVITY_TRANSMIT_PRECISE_GEO
} from '../../src/activities/activities.js';
import {gppDataHandler} from '../../src/adapterManager.js';
import {logInfo} from '../../src/utils.js';

// default interpretation for MSPA consent(s):
// https://docs.prebid.org/features/mspa-usnat.html
Expand All @@ -24,7 +25,9 @@ export function isBasicConsentDenied(cd) {
// minors 13+ who have not given consent
cd.KnownChildSensitiveDataConsents[0] === 1 ||
// minors under 13 cannot consent
isApplicable(cd.KnownChildSensitiveDataConsents[1]);
isApplicable(cd.KnownChildSensitiveDataConsents[1]) ||
// covered cannot be zero
cd.MspaCoveredTransaction === 0;
}

export function sensitiveNoticeIs(cd, value) {
Expand Down Expand Up @@ -85,26 +88,40 @@ const CONSENT_RULES = {
[ACTIVITY_ENRICH_EIDS]: isConsentDenied,
[ACTIVITY_ENRICH_UFPD]: isTransmitUfpdConsentDenied,
[ACTIVITY_TRANSMIT_PRECISE_GEO]: isTransmitGeoConsentDenied
}
};

export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDataHandler.getConsentData()?.applicableSections) {
return function() {
return function () {
if (applicableSids().some(sid => sids.includes(sid))) {
const consent = getConsent();
if (consent == null) {
return {allow: false, reason: 'consent data not available'};
}
if (denies(consent)) {
return {allow: false}
return {allow: false};
}
}
}
};
}

function flatSection(subsections) {
if (subsections == null) return subsections;
return subsections.reduceRight((subsection, consent) => {
return Object.assign(consent, subsection);
}, {});
}

export function setupRules(api, sids, normalizeConsent = (c) => c, rules = CONSENT_RULES, registerRule = registerActivityControl, getConsentData = () => gppDataHandler.getConsentData()) {
const unreg = [];
const ruleName = `MSPA (GPP '${api}' for section${sids.length > 1 ? 's' : ''} ${sids.join(', ')})`;
logInfo(`Enabling activity controls for ${ruleName}`)
Object.entries(rules).forEach(([activity, denies]) => {
unreg.push(registerRule(activity, `MSPA (${api})`, mspaRule(sids, () => normalizeConsent(getConsentData()?.sectionData?.[api]), denies, () => getConsentData()?.applicableSections || [])))
})
return () => unreg.forEach(ur => ur())
unreg.push(registerRule(activity, ruleName, mspaRule(
sids,
() => normalizeConsent(flatSection(getConsentData()?.parsedSections?.[api])),
denies,
() => getConsentData()?.applicableSections || []
)));
});
return () => unreg.forEach(ur => ur());
}
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"zeotapIdPlusIdSystem",
"adqueryIdSystem",
"gravitoIdSystem",
"freepassIdSystem"
"freepassIdSystem",
"operaadsIdSystem"
],
"adpod": [
"freeWheelAdserverVideo",
Expand Down
2 changes: 1 addition & 1 deletion modules/a1MediaRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const REAL_TIME_MODULE = 'realTimeData';
const MODULE_NAME = 'a1Media';
const SCRIPT_URL = 'https://linkback.contentsfeed.com/src';
export const A1_SEG_KEY = '__a1tg';
export const A1_AUD_KEY = 'a1gid';
export const A1_AUD_KEY = 'a1_gid';

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

Expand Down
30 changes: 26 additions & 4 deletions modules/adagioBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,17 @@ function _getUspConsent(bidderRequest) {
return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false;
}

function _getGppConsent(bidderRequest) {
let gpp = deepAccess(bidderRequest, 'gppConsent.gppString')
let gppSid = deepAccess(bidderRequest, 'gppConsent.applicableSections')

if (!gpp || !gppSid) {
gpp = deepAccess(bidderRequest, 'ortb2.regs.gpp', '')
gppSid = deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', [])
}
return { gpp, gppSid }
}

function _getSchain(bidRequest) {
return deepAccess(bidRequest, 'schain');
}
Expand Down Expand Up @@ -976,6 +987,7 @@ export const spec = {
const gdprConsent = _getGdprConsent(bidderRequest) || {};
const uspConsent = _getUspConsent(bidderRequest) || {};
const coppa = _getCoppa();
const gppConsent = _getGppConsent(bidderRequest)
const schain = _getSchain(validBidRequests[0]);
const eids = _getEids(validBidRequests[0]) || [];
const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled')
Expand All @@ -984,7 +996,7 @@ export const spec = {
const aucId = generateUUID()

const adUnits = _map(validBidRequests, (rawBidRequest) => {
const bidRequest = {...rawBidRequest}
const bidRequest = deepClone(rawBidRequest);

// Fix https://github.com/prebid/Prebid.js/issues/9781
bidRequest.auctionId = aucId
Expand Down Expand Up @@ -1118,15 +1130,23 @@ export const spec = {
// remove useless props
delete adUnitCopy.floorData;
delete adUnitCopy.params.siteId;
delete adUnitCopy.userId
delete adUnitCopy.userIdAsEids
delete adUnitCopy.userId;
delete adUnitCopy.userIdAsEids;

groupedAdUnits[adUnitCopy.params.organizationId] = groupedAdUnits[adUnitCopy.params.organizationId] || [];
groupedAdUnits[adUnitCopy.params.organizationId].push(adUnitCopy);

return groupedAdUnits;
}, {});

// Adding more params on the original bid object.
// Those params are not sent to the server.
// They are used for further operations on analytics adapter.
validBidRequests.forEach(rawBidRequest => {
rawBidRequest.params.adagioAuctionId = aucId
rawBidRequest.params.pageviewId = pageviewId
});

// Build one request per organizationId
const requests = _map(Object.keys(groupedAdUnits), organizationId => {
return {
Expand All @@ -1143,7 +1163,9 @@ export const spec = {
regs: {
gdpr: gdprConsent,
coppa: coppa,
ccpa: uspConsent
ccpa: uspConsent,
gpp: gppConsent.gpp,
gppSid: gppConsent.gppSid
},
schain: schain,
user: {
Expand Down
Loading

0 comments on commit 6a72e8c

Please sign in to comment.