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

33Across ID System: Fix merge conflicts between master & 9.0 #11715

Merged
merged 10 commits into from
Jun 5, 2024
71 changes: 48 additions & 23 deletions modules/33acrossIdSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { submodule } from '../src/hook.js';
import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js';
import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js';
import { MODULE_TYPE_UID } from '../src/activities/modules.js';
import { domainOverrideToRootDomain } from '../libraries/domainOverrideToRootDomain/index.js';

/**
* @typedef {import('../modules/userId/index.js').Submodule} Submodule
Expand All @@ -31,6 +32,10 @@ const DEFAULT_TPID_SUPPORT = true;

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

export const domainUtils = {
domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME)
};

function calculateResponseObj(response) {
if (!response.succeeded) {
if (response.error == 'Cookied User') {
Expand All @@ -54,7 +59,7 @@ function calculateResponseObj(response) {
};
}

function calculateQueryStringParams(pid, gdprConsentData, storageConfig) {
function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) {
const uspString = uspDataHandler.getConsentData();
const coppaValue = coppaDataHandler.getCoppa();
const gppConsent = gppDataHandler.getConsentData();
Expand Down Expand Up @@ -82,12 +87,12 @@ function calculateQueryStringParams(pid, gdprConsentData, storageConfig) {
params.gdpr_consent = gdprConsentData.consentString;
}

const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig);
const fp = getStoredValue(STORAGE_FPID_KEY, enabledStorageTypes);
if (fp) {
params.fp = encodeURIComponent(fp);
}

const tp = getStoredValue(STORAGE_TPID_KEY, storageConfig);
const tp = getStoredValue(STORAGE_TPID_KEY, enabledStorageTypes);
if (tp) {
params.tp = encodeURIComponent(tp);
}
Expand All @@ -99,32 +104,42 @@ function deleteFromStorage(key) {
if (storage.cookiesAreEnabled()) {
const expiredDate = new Date(0).toUTCString();

storage.setCookie(key, '', expiredDate, 'Lax');
storage.setCookie(key, '', expiredDate, 'Lax', domainUtils.domainOverride());
}

storage.removeDataFromLocalStorage(key);
}

function storeValue(key, value, storageConfig = {}) {
if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) {
const expirationInMs = 60 * 60 * 24 * 1000 * storageConfig.expires;
const expirationTime = new Date(Date.now() + expirationInMs);
function storeValue(key, value, { enabledStorageTypes, expires }) {
enabledStorageTypes.forEach(storageType => {
if (storageType === STORAGE_TYPE_COOKIES) {
const expirationInMs = 60 * 60 * 24 * 1000 * expires;
const expirationTime = new Date(Date.now() + expirationInMs);

storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax');
} else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) {
storage.setDataInLocalStorage(key, value);
}
storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax', domainUtils.domainOverride());
} else if (storageType === STORAGE_TYPE_LOCALSTORAGE) {
storage.setDataInLocalStorage(key, value);
}
});
}

function getStoredValue(key, storageConfig = {}) {
if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) {
return storage.getCookie(key);
} else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) {
return storage.getDataFromLocalStorage(key);
}
function getStoredValue(key, enabledStorageTypes) {
let storedValue;

enabledStorageTypes.find(storageType => {
if (storageType === STORAGE_TYPE_COOKIES) {
storedValue = storage.getCookie(key);
} else if (storageType === STORAGE_TYPE_LOCALSTORAGE) {
storedValue = storage.getDataFromLocalStorage(key);
}

return !!storedValue;
});

return storedValue;
}

function handleSupplementalId(key, id, storageConfig = {}) {
function handleSupplementalId(key, id, storageConfig) {
id
? storeValue(key, id, storageConfig)
: deleteFromStorage(key);
Expand Down Expand Up @@ -160,7 +175,7 @@ export const thirthyThreeAcrossIdSubmodule = {
* @param {SubmoduleConfig} [config]
* @returns {IdResponse|undefined}
*/
getId({ params = { }, storage: storageConfig }, gdprConsentData) {
getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, gdprConsentData) {
if (typeof params.pid !== 'string') {
logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`);

Expand Down Expand Up @@ -192,11 +207,17 @@ export const thirthyThreeAcrossIdSubmodule = {
}

if (storeFpid) {
handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, storageConfig);
handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, {
enabledStorageTypes,
expires: storageConfig.expires
});
}

if (storeTpid) {
handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, storageConfig);
handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, {
enabledStorageTypes,
expires: storageConfig.expires
});
}

cb(responseObj.envelope);
Expand All @@ -206,10 +227,14 @@ export const thirthyThreeAcrossIdSubmodule = {

cb();
}
}, calculateQueryStringParams(pid, gdprConsentData, storageConfig), { method: 'GET', withCredentials: true });
}, calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes), {
method: 'GET',
withCredentials: true
});
}
};
},
domainOverride: domainUtils.domainOverride,
eids: {
'33acrossId': {
source: '33across.com',
Expand Down
4 changes: 2 additions & 2 deletions modules/33acrossIdSystem.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pbjs.setConfig({
name: "33acrossId",
storage: {
name: "33acrossId",
type: "html5",
type: "cookie&html5",
expires: 30,
refreshInSeconds: 8*3600
},
Expand All @@ -40,7 +40,7 @@ The following settings are available for the `storage` property in the `userSync
| Param name | Scope | Type | Description | Example |
| --- | --- | --- | --- | --- |
| name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` |
| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` |
| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` |
| expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `30`. | `30` |
| refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` |

Expand Down
9 changes: 4 additions & 5 deletions modules/adpod.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,6 @@ export function callPrebidCacheAfterAuction(bids, callback) {
/**
* Compare function to be used in sorting long-form bids. This will compare bids on price per second.
* @param {Object} bid
* @param {Object} bid
*/
export function sortByPricePerSecond(a, b) {
if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) {
Expand All @@ -465,10 +464,10 @@ export function sortByPricePerSecond(a, b) {

/**
* This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form
* @param {Object} options
* @param {Array[string]} codes
* @param {function} callback
* @returns targeting kvs for adUnitCodes
* @param {Object} options - Options for targeting.
* @param {Array<string>} options.codes - Array of ad unit codes.
* @param {function} options.callback - Callback function to handle the targeting key-value pairs.
* @returns {Object} Targeting key-value pairs for ad unit codes.
*/
export function getTargeting({ codes, callback } = {}) {
if (!callback) {
Expand Down
110 changes: 110 additions & 0 deletions modules/bidmaticBidAdapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ortbConverter } from '../libraries/ortbConverter/converter.js';
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { BANNER, VIDEO } from '../src/mediaTypes.js';
import { replaceAuctionPrice, isNumber, deepAccess, isFn } from '../src/utils.js';

export const END_POINT = 'https://adapter.bidmatic.io/ortb-client';
const BIDDER_CODE = 'bidmatic';
const DEFAULT_CURRENCY = 'USD';

export const converter = ortbConverter({
context: {
netRevenue: true,
ttl: 290,
},
imp(buildImp, bidRequest, context) {
const imp = buildImp(bidRequest, context);
const floorInfo = isFn(bidRequest.getFloor) ? bidRequest.getFloor({
currency: context.currency || 'USD',
size: '*',
mediaType: '*'
}) : {
floor: imp.bidfloor || deepAccess(bidRequest, 'params.bidfloor') || 0,
currency: DEFAULT_CURRENCY
};

if (floorInfo) {
imp.bidfloor = floorInfo.floor;
imp.bidfloorcur = floorInfo.currency;
}
imp.tagid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || bidRequest.adUnitCode;

return imp;
},
request(buildRequest, imps, bidderRequest, context) {
const request = buildRequest(imps, bidderRequest, context);
if (!request.cur) {
request.cur = [DEFAULT_CURRENCY];
}
return request;
},
bidResponse(buildBidResponse, bid, context) {
const { bidRequest } = context;

let resMediaType;
const reqMediaTypes = Object.keys(bidRequest.mediaTypes);
if (reqMediaTypes.length === 1) {
resMediaType = reqMediaTypes[0];
} else {
if (bid.adm.search(/^(<\?xml|<vast)/i) !== -1) {
resMediaType = VIDEO;
} else {
resMediaType = BANNER;
}
}

context.mediaType = resMediaType;

return buildBidResponse(bid, context);
}
});

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: [BANNER, VIDEO],
gvlid: 1134,
isBidRequestValid: function (bid) {
return isNumber(deepAccess(bid, 'params.source'))
},

buildRequests: function (validBidRequests, bidderRequest) {
const requestsBySource = validBidRequests.reduce((acc, bidRequest) => {
acc[bidRequest.params.source] = acc[bidRequest.params.source] || [];
acc[bidRequest.params.source].push(bidRequest);
return acc;
}, {});

return Object.entries(requestsBySource).map(([source, bidRequests]) => {
const data = converter.toORTB({ bidRequests, bidderRequest });
const url = new URL(END_POINT);
url.searchParams.append('source', source);
return {
method: 'POST',
url: url.toString(),
data: data,
options: {
withCredentials: true,
}
};
});
},

interpretResponse: function (serverResponse, bidRequest) {
if (!serverResponse || !serverResponse.body) return [];
const parsedSeatbid = serverResponse.body.seatbid.map(seatbidItem => {
const parsedBid = seatbidItem.bid.map((bidItem) => ({
...bidItem,
adm: replaceAuctionPrice(bidItem.adm, bidItem.price),
nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price)
}));
return { ...seatbidItem, bid: parsedBid };
});
const responseBody = { ...serverResponse.body, seatbid: parsedSeatbid };
return converter.fromORTB({
response: responseBody,
request: bidRequest.data,
}).bids;
},

};
registerBidder(spec);
26 changes: 26 additions & 0 deletions modules/bidmaticBidAdapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Overview

```
Module Name: Bidmatic Bid Adapter
Module Type: Bidder Adapter
Maintainer: [email protected]
```

# Description

Adds access to Bidmatic SSP oRTB service.

# Sample Ad Unit: For Publishers
```
var adUnits = [{
code: 'bg-test-rectangle',
sizes: [[300, 250]],
bids: [{
bidder: 'bidmatic',
params: {
source: 886409,
bidfloor: 0.1
}
}]
}]
```
7 changes: 4 additions & 3 deletions modules/consentManagementGpp.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,10 @@ export class GPPClient {
* This function handles interacting with an IAB compliant CMP to obtain the consent information of the user.
* Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function
* based on the appropriate result.
* @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP
* @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging)
* @param {Object} options - An object containing the callbacks.
* @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP.
* @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging).
* @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`.
*/
export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) {
pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError});
Expand Down Expand Up @@ -291,7 +293,6 @@ function processCmpData(consentData) {
/**
* Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction
* @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent)
* @param {{}} sectionData map from GPP section name to the result of calling a CMP's `getSection` (or equivalent)
*/
export function storeConsentData(gppData = {}) {
consentData = {
Expand Down
10 changes: 7 additions & 3 deletions modules/consentManagementTcf.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ const cmpCallMap = {

/**
* This function reads the consent string from the config to obtain the consent information of the user.
* @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP
* @param {Object} options - An object containing the callbacks.
* @param {function(Object): void} options.onSuccess - Acts as a success callback when the value is read from config; pass along consentObject from CMP.
* @param {function(string, ...Object?): void} [options.onError] - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). Optional.
*/
function lookupStaticConsentData({onSuccess, onError}) {
processCmpData(staticConsentData, {onSuccess, onError})
Expand All @@ -45,8 +47,10 @@ function lookupStaticConsentData({onSuccess, onError}) {
* This function handles interacting with an IAB compliant CMP to obtain the consent information of the user.
* Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function
* based on the appropriate result.
* @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP
* @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging)
* @param {Object} options - An object containing the callbacks.
* @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP.
* @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging).
* @param {function(Object): void} options.onEvent - Acts as an event callback for processing TCF data events from CMP.
*/
function lookupIabConsent({onSuccess, onError, onEvent}) {
function cmpResponseCallback(tcfData, success) {
Expand Down
10 changes: 6 additions & 4 deletions modules/consentManagementUsp.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ export const requestBidsHook = timedAuctionHook('usp', function requestBidsHook(
/**
* This function checks the consent data provided by USPAPI to ensure it's in an expected state.
* If it's bad, we exit the module depending on config settings.
* If it's good, then we store the value and exits the module.
* @param {object} consentObject required; object returned by USPAPI that contains user's consent choices
* @param {function(string)} onSuccess callback accepting the resolved consent USP consent string
* @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging)
* If it's good, then we store the value and exit the module.
*
* @param {Object} consentObject - The object returned by USPAPI that contains the user's consent choices.
* @param {Object} callbacks - An object containing the callback functions.
* @param {function(string): void} callbacks.onSuccess - Callback accepting the resolved USP consent string.
* @param {function(string, ...Object?): void} callbacks.onError - Callback accepting an error message and any extra error arguments (used purely for logging).
*/
function processUspData(consentObject, {onSuccess, onError}) {
const valid = !!(consentObject && consentObject.usPrivacy);
Expand Down
1 change: 1 addition & 0 deletions modules/currency.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export let responseReady = defer();

/**
* Configuration function for currency
* @param {object} config
* @param {string} [config.adServerCurrency = 'USD']
* ISO 4217 3-letter currency code that represents the target currency. (e.g. 'EUR'). If this value is present,
* the currency conversion feature is activated.
Expand Down
Loading