@iabtechlabtcf/core
Version:
Ensures consistent encoding and decoding of TC Signals for the iab. Transparency and Consent Framework (TCF).
143 lines (142 loc) • 8.25 kB
JavaScript
import { EncodingError } from '../errors/index.js';
import { RestrictionType } from '../model/index.js';
export class SemanticPreEncoder {
static processor = [
(tcModel) => tcModel,
(tcModel, gvl) => {
/**
* in case this wasn't set previously. This should filter out invalid
* purpose restrictions.
*/
tcModel.publisherRestrictions.gvl = gvl;
/**
* Purpose 1 is never allowed to be true for legitimate interest
* As of TCF v2.2 purposes 3,4,5 & 6 are not allowed to be true for LI
*/
tcModel.purposeLegitimateInterests.unset([1, 3, 4, 5, 6]);
/**
* If a Vendor does not declare a purpose for consent or legitimate
* interest they should not have a positive signal for it. This code
* removes positive signals created mistakingly.
*/
const vectorToIntMap = new Map();
vectorToIntMap.set('legIntPurposes', tcModel.vendorLegitimateInterests);
vectorToIntMap.set('purposes', tcModel.vendorConsents);
vectorToIntMap.forEach((vector, gvlVendorKey) => {
vector.forEach((value, vendorId) => {
if (value) {
const vendor = gvl.vendors[vendorId];
if (!vendor || vendor.deletedDate) {
/**
* If the vendor doesn't exist, then they should not receive a
* positive signal
*/
vector.unset(vendorId);
}
else if (vendor[gvlVendorKey].length === 0) {
if (gvlVendorKey === 'legIntPurposes' && vendor['purposes'].length === 0 && vendor['legIntPurposes'].length === 0 && vendor['specialPurposes'].length > 0) {
/**
* Per June 2021 Policy change, Vendors declaring only Special Purposes must
* have their legitimate interest Vendor bit set if they have been disclosed.
* This block ensures their LI bit remains set
*/
vector.set(vendorId);
}
else if (gvlVendorKey === 'legIntPurposes' && vendor['purposes'].length > 0 && vendor['legIntPurposes'].length === 0 && vendor['specialPurposes'].length > 0) {
/**
* Per June 2021 Policy change, Vendors declaring only Special Purposes must
* have their legitimate interest Vendor bit set if they have been disclosed.
* This block ensures their LI bit remains set
*/
vector.set(vendorId);
}
else {
/**
* If the vendor does exist, but they haven't declared any
* purposes for this legal basis, then we need to see if they can
* possibly have the legal basis from their flexible purposes.
*/
if (tcModel.isServiceSpecific) {
if (vendor.flexiblePurposes.length === 0) {
/**
* No flexible basis for any purposes, so we can safely remove
* this vendor from the legal basis.
*/
vector.unset(vendorId);
}
else {
/**
* They have some flexible purposes, we should check for a
* publisher restriction value that would enable this vendor to
* have the override-preferred basis.
*/
const restrictions = tcModel.publisherRestrictions.getRestrictions(vendorId);
let isValid = false;
for (let i = 0, len = restrictions.length; i < len && !isValid; i++) {
/**
* If this condition is true the loop will break. If we are
* dealing with the consent purposes ('purposes') and the
* publisher restriction overrides to consent then it is
* valid for the vendor to have a positive signal for
* consent. Likewise for legitimate interest purposes
* ('legIntPurposes') and requiring legitimate interest.
*/
isValid = ((restrictions[i].restrictionType === RestrictionType.REQUIRE_CONSENT &&
gvlVendorKey === 'purposes') ||
(restrictions[i].restrictionType === RestrictionType.REQUIRE_LI &&
gvlVendorKey === 'legIntPurposes'));
}
if (!isValid) {
/**
* if we came through the previous loop without finding a
* valid reasing: no overriding restrictions (changes in
* legal basis) then it's not valid for this vendor to have
* this legal basis.
*/
vector.unset(vendorId);
}
}
}
else {
/**
* This is a globally-scoped string so flexible purposes will not
* be able to change this value because purposeRestrictions only
* apply to service-specific strings.
*/
vector.unset(vendorId);
}
}
}
}
});
});
tcModel.vendorsDisclosed.set(gvl.vendors);
return tcModel;
},
];
static process(tcModel, options) {
const gvl = tcModel.gvl;
if (!gvl) {
throw new EncodingError('Unable to encode TCModel without a GVL');
}
if (!gvl.isReady) {
throw new EncodingError('Unable to encode TCModel tcModel.gvl.readyPromise is not resolved');
}
tcModel = tcModel.clone();
tcModel.consentLanguage = gvl.language.slice(0, 2).toUpperCase();
if (options?.version > 0 && options?.version <= this.processor.length) {
tcModel.version = options.version;
}
else {
/**
* this is equal to the latest or most current version
*/
tcModel.version = this.processor.length;
}
const processorFunctionIndex = tcModel.version - 1;
if (!this.processor[processorFunctionIndex]) {
throw new EncodingError(`Invalid version: ${tcModel.version}`);
}
return this.processor[processorFunctionIndex](tcModel, gvl);
}
}