这是indexloc提供的服务,不要输入任何密码
Skip to content
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
91 changes: 13 additions & 78 deletions modules/azerionedgeRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const SUBREAL_TIME_MODULE = 'azerionedge';
export const STORAGE_KEY = 'ht-pa-v1-a';

const IMPROVEDIGITAL_GVLID = '253';
const PURPOSES = ['1', '3', '5', '7', '9'];

export const storage = getStorageManager({
moduleType: MODULE_TYPE_RTD,
Expand All @@ -45,14 +44,21 @@ function getScriptURL(config) {
* Attach script tag to DOM
*
* @param {Object} config
* @param {Object} userConsent
*
* @return {void}
*/
export function attachScript(config) {
export function attachScript(config, userConsent) {
const script = getScriptURL(config);
loadExternalScript(script, SUBREAL_TIME_MODULE, () => {
if (typeof window.azerionPublisherAudiences === 'function') {
window.azerionPublisherAudiences(config.params?.process || {});
const publisherConfig = config.params?.process || {};
window.azerionPublisherAudiences({
...publisherConfig,
gdprApplies: userConsent?.gdpr?.gdprApplies,
gdprConsent: userConsent?.gdpr?.consentString,
uspConsent: userConsent?.usp,
});
}
});
}
Expand Down Expand Up @@ -109,79 +115,10 @@ export function setAudiencesToBidders(reqBidsConfigObj, config, audiences) {
* @return {boolean}
*/
function init(config, userConsent) {
if (hasUserConsented(userConsent)) {
attachScript(config);
}
attachScript(config, userConsent);
return true;
}

/**
* List the vendors consented coming from userConsent object.
*
* @param {Object} userConsent
*
* @return {Array}
*/
function getVendorsConsented(userConsent) {
const consents = userConsent?.gdpr?.vendorData?.vendor?.consents || {};
return Object.entries(consents).reduce((acc, [vendorId, consented]) => {
return consented ? [...acc, vendorId] : acc;
}, []);
}

/**
* List the purposes consented coming from userConsent object.
*
* @param {Object} userConsent
*
* @return {Array}
*/
export function getPurposesConsented(userConsent) {
const consents = userConsent?.gdpr?.vendorData?.purpose?.consents || {};
return Object.entries(consents).reduce((acc, [purposeId, consented]) => {
return consented ? [...acc, purposeId] : acc;
}, []);
}

/**
* Checks if GDPR gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasGDPRAccess(userConsent) {
const gdprApplies = userConsent?.gdpr?.gdprApplies;
const isVendorAllowed = getVendorsConsented(userConsent).includes(IMPROVEDIGITAL_GVLID);
const arePurposesAllowed = PURPOSES.every((purpose) => getPurposesConsented(userConsent).includes(purpose));
return !gdprApplies || (isVendorAllowed && arePurposesAllowed);
}

/**
* Checks if USP gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasUSPAccess(userConsent) {
const uspProvided = userConsent?.usp;
const hasProvidedUserNotice = uspProvided?.[1] !== 'N';
const hasNotOptedOut = uspProvided?.[2] !== 'Y';
return !uspProvided || (hasProvidedUserNotice && hasNotOptedOut);
}

/**
* Checks if GDPR/USP gives us access through the userConsent object.
*
* @param {Object} userConsent
*
* @return {boolean}
*/
export function hasUserConsented(userConsent) {
return hasGDPRAccess(userConsent) && hasUSPAccess(userConsent);
}

/**
* Real-time user audiences retrieval
*
Expand All @@ -198,11 +135,9 @@ export function getBidRequestData(
config,
userConsent
) {
if (hasUserConsented(userConsent)) {
const audiences = getAudiences();
if (audiences.length > 0) {
setAudiencesToBidders(reqBidsConfigObj, config, audiences);
}
const audiences = getAudiences();
if (audiences.length > 0) {
setAudiencesToBidders(reqBidsConfigObj, config, audiences);
}
callback();
}
Expand Down
153 changes: 30 additions & 123 deletions test/spec/modules/azerionedgeRtdProvider_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ describe('Azerion Edge RTD submodule', function () {
const bidders = ['appnexus', 'improvedigital'];
const process = { key: 'value' };
const dataProvider = { name: 'azerionedge', waitForIt: true };
const tcfGDPRNotApplicable = { gdprApplies: false };
const uspNotProvided = { usp: undefined };
const ignoreConsent = {gdpr: tcfGDPRNotApplicable, usp: uspNotProvided};
const userConsent = {gdpr: {gdprApplies: 'gdpr-applies', consentString: 'consent-string'}, usp: 'usp'};

const resetAll = () => {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
}

let reqBidsConfigObj;
let storageStub;
Expand All @@ -36,13 +39,11 @@ describe('Azerion Edge RTD submodule', function () {
let returned;

beforeEach(function () {
returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, ignoreConsent);
returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, userConsent);
});

it('should have the correct gvlid', () => {
expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(
IMPROVEDIGITAL_GVLID
);
expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(IMPROVEDIGITAL_GVLID);
});

it('should return true', function () {
Expand All @@ -58,18 +59,21 @@ describe('Azerion Edge RTD submodule', function () {
expect(loadExternalScript.args[0][0]).to.deep.equal(expected);
});

it('should call azerionPublisherAudiencesStub with empty configuration', function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal({});
[
['gdprApplies', userConsent.gdpr.gdprApplies],
['gdprConsent', userConsent.gdpr.consentString],
['uspConsent', userConsent.usp],
].forEach(([key, value]) => {
it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value});
});
});

describe('with key', function () {
beforeEach(function () {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
returned = azerionedgeRTD.azerionedgeSubmodule.init({
...dataProvider,
params: { key },
}, ignoreConsent);
resetAll();
const config = { ...dataProvider, params: { key } };
returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent);
});

it('should return true', function () {
Expand All @@ -84,120 +88,23 @@ describe('Azerion Edge RTD submodule', function () {

describe('with process configuration', function () {
beforeEach(function () {
window.azerionPublisherAudiences.resetHistory();
loadExternalScript.resetHistory();
returned = azerionedgeRTD.azerionedgeSubmodule.init({
...dataProvider,
params: { process },
}, ignoreConsent);
resetAll();
const config = { ...dataProvider, params: { process } };
returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent);
});

it('should return true', function () {
expect(returned).to.equal(true);
});

it('should call azerionPublisherAudiencesStub with process configuration', function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal(
process
);
});
});
});

describe('GDPR access', () => {
const vendorConsented = { '253': true }
const purposesConsented = {'1': true, '3': true, '5': true, '7': true, '9': true};
const partialPurposesConsented = {'1': true, '3': true, '5': true, '7': true};
const tcfConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: purposesConsented } } };
const tcfVendorNotConsented = { gdprApplies: true, vendorData: { purpose: {consents: purposesConsented} } };
const tcfPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented } } };
const tcfPartialPurposesNotConsented = { gdprApplies: true, vendorData: { vendor: { consents: vendorConsented }, purpose: { consents: partialPurposesConsented } } };

[
['not applicable', tcfGDPRNotApplicable, true],
['tcf consented', tcfConsented, true],
['tcf vendor not consented', tcfVendorNotConsented, false],
['tcf purposes not consented', tcfPurposesNotConsented, false],
['tcp partial purposes not consented', tcfPartialPurposesNotConsented, false],
].forEach(([info, gdpr, expected]) => {
it(`for ${info} should return ${expected}`, () => {
expect(azerionedgeRTD.hasGDPRAccess({gdpr})).to.equal(expected);
});

it(`for ${info} should load=${expected} the external script`, () => {
azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr, usp: uspNotProvided});
expect(loadExternalScript.called).to.equal(expected);
});

describe('for bid request data', function () {
let callbackStub;

beforeEach(function () {
callbackStub = sinon.mock();
azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr, usp: uspNotProvided});
});

it(`does call=${expected} the local storage looking for audiences`, function () {
expect(storageStub.called).to.equal(expected);
});

it('calls callback always', function () {
expect(callbackStub.called).to.be.true;
});
});
});
});

describe('USP acccess', () => {
const uspMalformed = -1;
const uspNotApplicable = '1---';
const uspUserNotifiedOptedOut = '1YY-';
const uspUserNotifiedNotOptedOut = '1YN-';
const uspUserNotifiedUnknownOptedOut = '1Y--';
const uspUserNotNotifiedOptedOut = '1NY-';
const uspUserNotNotifiedNotOptedOut = '1NN-';
const uspUserNotNotifiedUnknownOptedOut = '1N--';
const uspUserUnknownNotifiedOptedOut = '1-Y-';
const uspUserUnknownNotifiedNotOptedOut = '1-N-';
const uspUserUnknownNotifiedUnknownOptedOut = '1---';

[
['malformed', uspMalformed, true],
['not applicable', uspNotApplicable, true],
['not provided', uspNotProvided, true],
['user notified and opted out', uspUserNotifiedOptedOut, false],
['user notified and not opted out', uspUserNotifiedNotOptedOut, true],
['user notified and unknown opted out', uspUserNotifiedUnknownOptedOut, true],
['user not notified and opted out', uspUserNotNotifiedOptedOut, false],
['user not notified and not opted out', uspUserNotNotifiedNotOptedOut, false],
['user not notified and unknown opted out', uspUserNotNotifiedUnknownOptedOut, false],
['user unknown notified and opted out', uspUserUnknownNotifiedOptedOut, false],
['user unknown notified and not opted out', uspUserUnknownNotifiedNotOptedOut, true],
['user unknown notified and unknown opted out', uspUserUnknownNotifiedUnknownOptedOut, true],
].forEach(([info, usp, expected]) => {
it(`for ${info} should return ${expected}`, () => {
expect(azerionedgeRTD.hasUSPAccess({usp})).to.equal(expected);
});

it(`for ${info} should load=${expected} the external script`, () => {
azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, {gdpr: tcfGDPRNotApplicable, usp});
expect(loadExternalScript.called).to.equal(expected);
});

describe('for bid request data', function () {
let callbackStub;

beforeEach(function () {
callbackStub = sinon.mock();
azerionedgeRTD.azerionedgeSubmodule.getBidRequestData(reqBidsConfigObj, callbackStub, dataProvider, {gdpr: tcfGDPRNotApplicable, usp});
});

it(`does call=${expected} the local storage looking for audiences`, function () {
expect(storageStub.called).to.equal(expected);
});

it('calls callback always', function () {
expect(callbackStub.called).to.be.true;
[
['gdprApplies', userConsent.gdpr.gdprApplies],
['gdprConsent', userConsent.gdpr.consentString],
['uspConsent', userConsent.usp],
...Object.entries(process),
].forEach(([key, value]) => {
it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () {
expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value});
});
});
});
Expand Down