Skip to content

Commit

Permalink
consent hash & multiHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
dgirardi committed Jul 27, 2023
1 parent f688821 commit 3f49f21
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 33 deletions.
22 changes: 10 additions & 12 deletions src/adapterManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
getUniqueIdentifierStr,
getUserConfiguredParams,
groupBy,
isArray, isPlainObject,
isArray,
isPlainObject,
isValidMediaTypes,
logError,
logInfo,
Expand All @@ -30,7 +31,12 @@ import {hook} from './hook.js';
import {find, includes} from './polyfill.js';
import {adunitCounter} from './adUnits.js';
import {getRefererInfo} from './refererDetection.js';
import {GDPR_GVLIDS, GdprConsentHandler, GppConsentHandler, UspConsentHandler} from './consentHandler.js';
import {
GDPR_GVLIDS,
gdprDataHandler,
uspDataHandler,
gppDataHandler,
} from './consentHandler.js';
import * as events from './events.js';
import CONSTANTS from './constants.json';
import {useMetrics} from './utils/perfMetrics.js';
Expand All @@ -41,6 +47,8 @@ import {ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS} from './activities/activ
import {ACTIVITY_PARAM_ANL_CONFIG, ACTIVITY_PARAM_S2S_NAME, activityParamsBuilder} from './activities/params.js';
import {redactor} from './activities/redactor.js';

export {gdprDataHandler, gppDataHandler, uspDataHandler, coppaDataHandler} from './consentHandler.js';

export const PBS_ADAPTER_NAME = 'pbsBidAdapter';
export const PARTITIONS = {
CLIENT: 'client',
Expand Down Expand Up @@ -192,16 +200,6 @@ function getAdUnitCopyForClientAdapters(adUnits) {
return adUnitsClientCopy;
}

export let gdprDataHandler = new GdprConsentHandler();
export let uspDataHandler = new UspConsentHandler();
export let gppDataHandler = new GppConsentHandler();

export let coppaDataHandler = {
getCoppa: function() {
return !!(config.getConfig('coppa'))
}
};

/**
* Filter and/or modify media types for ad units based on the given labels.
*
Expand Down
73 changes: 69 additions & 4 deletions src/consentHandler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {isStr, timestamp} from './utils.js';
import {cyrb53Hash, isStr, timestamp} from './utils.js';
import {defer, GreedyPromise} from './utils/promise.js';
import {config} from './config.js';

/**
* Placeholder gvlid for when vendor consent is not required. When this value is used as gvlid, the gdpr
Expand All @@ -14,7 +15,10 @@ export class ConsentHandler {
#data;
#defer;
#ready;
#dirty = true;
#hash;
generatedTime;
hashFields;

constructor() {
this.reset();
Expand Down Expand Up @@ -74,15 +78,24 @@ export class ConsentHandler {

setConsentData(data, time = timestamp()) {
this.generatedTime = time;
this.#dirty = true;
this.#resolve(data);
}

getConsentData() {
return this.#data;
}

get hash() {
if (this.#dirty) {
this.#hash = cyrb53Hash(JSON.stringify(this.#data && this.hashFields ? this.hashFields.map(f => this.#data[f]) : this.#data))
this.#dirty = false;
}
return this.#hash;
}
}

export class UspConsentHandler extends ConsentHandler {
class UspConsentHandler extends ConsentHandler {
getConsentMeta() {
const consentData = this.getConsentData();
if (consentData && this.generatedTime) {
Expand All @@ -94,7 +107,8 @@ export class UspConsentHandler extends ConsentHandler {
}
}

export class GdprConsentHandler extends ConsentHandler {
class GdprConsentHandler extends ConsentHandler {
hashFields = ['gdprApplies', 'consentString']
getConsentMeta() {
const consentData = this.getConsentData();
if (consentData && consentData.vendorData && this.generatedTime) {
Expand All @@ -108,7 +122,8 @@ export class GdprConsentHandler extends ConsentHandler {
}
}

export class GppConsentHandler extends ConsentHandler {
class GppConsentHandler extends ConsentHandler {
hashFields = ['applicableSections', 'gppString'];
getConsentMeta() {
const consentData = this.getConsentData();
if (consentData && this.generatedTime) {
Expand Down Expand Up @@ -159,4 +174,54 @@ export function gvlidRegistry() {
}
}

export const gdprDataHandler = new GdprConsentHandler();
export const uspDataHandler = new UspConsentHandler();
export const gppDataHandler = new GppConsentHandler();
export const coppaDataHandler = (() => {
function getCoppa() {
return !!(config.getConfig('coppa'))
}
return {
getCoppa,
getConsentData: getCoppa,
getConsentMeta: getCoppa,
get promise() {
return GreedyPromise.resolve(getCoppa())
},
get hash() {
return getCoppa() ? '1' : '0'
}
}
})();

export const GDPR_GVLIDS = gvlidRegistry();

const ALL_HANDLERS = {
gdpr: gdprDataHandler,
usp: uspDataHandler,
gpp: gppDataHandler,
coppa: coppaDataHandler,
}

export function multiHandler(handlers = ALL_HANDLERS) {
handlers = Object.entries(handlers);
function collector(method) {
return function () {
return Object.fromEntries(handlers.map(([name, handler]) => [name, handler[method]()]))
}
}
return Object.assign(
{
get promise() {
return Promise.all(handlers.map(([name, handler]) => handler.promise.then(val => [name, val])))
.then(entries => Object.fromEntries(entries));
},
get hash() {
return handlers.map(([_, handler]) => handler.hash).join(':');
}
},
Object.fromEntries(['getConsentData', 'getConsentMeta'].map(n => [n, collector(n)])),
)
}

export const allConsent = multiHandler();
19 changes: 3 additions & 16 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ import {executeRenderer, isRendererRequired} from './Renderer.js';
import {createBid} from './bidfactory.js';
import {storageCallbacks} from './storageManager.js';
import {emitAdRenderFail, emitAdRenderSucceeded} from './adRendering.js';
import {default as adapterManager, gdprDataHandler, getS2SBidderSet, gppDataHandler, uspDataHandler} from './adapterManager.js';
import {default as adapterManager, getS2SBidderSet} from './adapterManager.js';
import CONSTANTS from './constants.json';
import * as events from './events.js';
import {newMetrics, useMetrics} from './utils/perfMetrics.js';
import {defer, GreedyPromise} from './utils/promise.js';
import {enrichFPD} from './fpd/enrichment.js';
import {allConsent} from './consentHandler.js';

const pbjsInstance = getGlobal();
const { triggerUserSyncs } = userSync;
Expand Down Expand Up @@ -330,23 +331,9 @@ pbjsInstance.getAdserverTargeting = function (adUnitCode) {
return targeting.getAllTargeting(adUnitCode);
};

/**
* returns all consent data
* @return {Object} Map of consent types and data
* @alias module:pbjs.getConsentData
*/
function getConsentMetadata() {
return {
gdpr: gdprDataHandler.getConsentMeta(),
usp: uspDataHandler.getConsentMeta(),
gpp: gppDataHandler.getConsentMeta(),
coppa: !!(config.getConfig('coppa'))
}
}

pbjsInstance.getConsentMetadata = function () {
logInfo('Invoking $$PREBID_GLOBAL$$.getConsentMetadata');
return getConsentMetadata();
return allConsent.getConsentMeta()
};

function getBids(type) {
Expand Down
80 changes: 79 additions & 1 deletion test/spec/unit/core/consentHandler_spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ConsentHandler, gvlidRegistry} from '../../../../src/consentHandler.js';
import {multiHandler, ConsentHandler, gvlidRegistry} from '../../../../src/consentHandler.js';

describe('Consent data handler', () => {
let handler;
Expand Down Expand Up @@ -56,6 +56,84 @@ describe('Consent data handler', () => {
})
})
});

describe('getHash', () => {
it('is defined when null', () => {
expect(handler.hash).be.a('string');
});
it('changes when a field is updated', () => {
const h1 = handler.hash;
handler.setConsentData({field: 'value', enabled: false});
const h2 = handler.hash;
expect(h2).to.not.eql(h1);
handler.setConsentData({field: 'value', enabled: true});
const h3 = handler.hash;
expect(h3).to.not.eql(h2);
expect(h3).to.not.eql(h1);
});
it('does not change when fields are unchanged', () => {
handler.setConsentData({field: 'value', enabled: true});
const h1 = handler.hash;
handler.setConsentData({field: 'value', enabled: true});
expect(handler.hash).to.eql(h1);
});
it('does not change when non-hashFields are updated', () => {
handler.hashFields = ['field', 'enabled'];
handler.setConsentData({field: 'value', enabled: true});
const h1 = handler.hash;
handler.setConsentData({field: 'value', enabled: true, other: 'data'});
expect(handler.hash).to.eql(h1);
})
})
});

describe('multiHandler', () => {
let handlers, multi;
beforeEach(() => {
handlers = {h1: {}, h2: {}};
multi = multiHandler(handlers);
});

['getConsentData', 'getConsentMeta'].forEach(method => {
describe(method, () => {
it('combines results from underlying handlers', () => {
handlers.h1[method] = () => 'one';
handlers.h2[method] = () => 'two';
expect(multi[method]()).to.eql({
h1: 'one',
h2: 'two',
})
});
});
});

describe('.promise', () => {
it('resolves all underlying promises', (done) => {
handlers.h1.promise = Promise.resolve('one');
let resolver, result;
handlers.h2.promise = new Promise((resolve) => { resolver = resolve });
multi.promise.then((val) => {
result = val;
expect(result).to.eql({
h1: 'one',
h2: 'two'
});
done();
})
handlers.h1.promise.then(() => {
expect(result).to.not.exist;
resolver('two');
});
})
});

describe('.hash', () => {
it('concats underlying hashses', () => {
handlers.h1.hash = 'one';
handlers.h2.hash = 'two';
expect(multi.hash).to.eql('one:two');
})
})
})

describe('gvlidRegistry', () => {
Expand Down

0 comments on commit 3f49f21

Please sign in to comment.