Skip to content

Commit

Permalink
Prebid 9: remove support for GPP 1.0 (#11461)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgirardi authored May 22, 2024
1 parent 6a539d3 commit 6d5e416
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 563 deletions.
201 changes: 29 additions & 172 deletions modules/consentManagementGpp.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {gppDataHandler} from '../src/adapterManager.js';
import {timedAuctionHook} from '../src/utils/perfMetrics.js';
import {enrichFPD} from '../src/fpd/enrichment.js';
import {getGlobal} from '../src/prebidGlobal.js';
import {cmpClient, MODE_CALLBACK, MODE_MIXED, MODE_RETURN} from '../libraries/cmp/cmpClient.js';
import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js';
import {GreedyPromise} from '../src/utils/promise.js';
import {buildActivityParams} from '../src/activities/params.js';

Expand Down Expand Up @@ -38,9 +38,6 @@ function lookupStaticConsentData(callbacks) {
return pipeCallbacks(() => processCmpData(staticConsentData), callbacks);
}

const GPP_10 = '1.0';
const GPP_11 = '1.1';

class GPPError {
constructor(message, arg) {
this.message = message;
Expand All @@ -49,104 +46,22 @@ class GPPError {
}

export class GPPClient {
static CLIENTS = {};

static register(apiVersion, defaultVersion = false) {
this.apiVersion = apiVersion;
this.CLIENTS[apiVersion] = this;
if (defaultVersion) {
this.CLIENTS.default = this;
}
}

apiVersion = '1.1';
static INST;

/**
* Ping the CMP to set up an appropriate client for it, and initialize it.
*
* @param mkCmp
* @returns {Promise<[GPPClient,Promise<{}>]>} a promise to two objects:
* - a GPPClient that talks the best GPP dialect we know for the CMP's version;
* - a promise to GPP data.
*/
static init(mkCmp = cmpClient) {
let inst = this.INST;
if (!inst) {
let err;
const reset = () => err && (this.INST = null);
inst = this.INST = this.ping(mkCmp).catch(e => {
err = true;
reset();
throw e;
static get(mkCmp = cmpClient) {
if (this.INST == null) {
const cmp = mkCmp({
apiName: '__gpp',
apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use),
mode: MODE_CALLBACK
});
reset();
}
return inst.then(([client, pingData]) => [
client,
client.initialized ? client.refresh() : client.init(pingData)
]);
}

/**
* Ping the CMP to determine its version and set up a client appropriate for it.
*
* @param mkCmp
* @returns {Promise<[GPPClient, {}]>} a promise to two objects:
* - a GPPClient that talks the best GPP dialect we know for the CMP's version;
* - the result from pinging the CMP.
*/
static ping(mkCmp = cmpClient) {
const cmpOptions = {
apiName: '__gpp',
apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use)
};

// in 1.0, 'ping' should return pingData but ignore callback;
// in 1.1 it should not return anything but run the callback
// the following looks for either - but once the version is known, produce a client that knows whether the
// rest of the interactions should pick return values or pass callbacks

const probe = mkCmp({...cmpOptions, mode: MODE_RETURN});
return new GreedyPromise((resolve, reject) => {
if (probe == null) {
reject(new GPPError('GPP CMP not found'));
return;
if (cmp == null) {
throw new GPPError('GPP CMP not found');
}
let done = false; // some CMPs do both return value and callbacks - avoid repeating log messages
const pong = (result, success) => {
if (done) return;
if (success != null && !success) {
reject(result);
return;
}
if (result == null) return;
done = true;
const cmpVersion = result?.gppVersion;
const Client = this.getClient(cmpVersion);
if (cmpVersion !== Client.apiVersion) {
logWarn(`Unrecognized GPP CMP version: ${cmpVersion}. Continuing using GPP API version ${Client}...`);
} else {
logInfo(`Using GPP version ${cmpVersion}`);
}
const mode = Client.apiVersion === GPP_10 ? MODE_MIXED : MODE_CALLBACK;
const client = new Client(
cmpVersion,
mkCmp({...cmpOptions, mode})
);
resolve([client, result]);
};

probe({
command: 'ping',
callback: pong
}).then((res) => pong(res, true), reject);
}).finally(() => {
probe && probe.close();
});
}

static getClient(cmpVersion) {
return this.CLIENTS.hasOwnProperty(cmpVersion) ? this.CLIENTS[cmpVersion] : this.CLIENTS.default;
this.INST = new this(cmp);
}
return this.INST;
}

#resolve;
Expand All @@ -155,9 +70,7 @@ export class GPPClient {

initialized = false;

constructor(cmpVersion, cmp) {
this.apiVersion = this.constructor.apiVersion;
this.cmpVersion = cmp;
constructor(cmp) {
this.cmp = cmp;
[this.#resolve, this.#reject] = [0, 1].map(slot => (result) => {
while (this.#pending.length) {
Expand All @@ -176,6 +89,9 @@ export class GPPClient {
init(pingData) {
const ready = this.updateWhenReady(pingData);
if (!this.initialized) {
if (pingData.gppVersion !== this.apiVersion) {
logWarn(`Unrecognized GPP CMP version: ${pingData.apiVersion}. Continuing using GPP API version ${this.apiVersion}...`);
}
this.initialized = true;
this.cmp({
command: 'addEventListener',
Expand All @@ -184,7 +100,7 @@ export class GPPClient {
this.#reject(new GPPError('Received error response from CMP', event));
} else if (event?.pingData?.cmpStatus === 'error') {
this.#reject(new GPPError('CMP status is "error"; please check CMP setup', event));
} else if (this.isCMPReady(event?.pingData || {}) && this.events.includes(event?.eventName)) {
} else if (this.isCMPReady(event?.pingData || {}) && ['sectionChange', 'signalStatus'].includes(event?.eventName)) {
this.#resolve(this.updateConsent(event.pingData));
}
}
Expand All @@ -194,7 +110,7 @@ export class GPPClient {
}

refresh() {
return this.cmp({command: 'ping'}).then(this.updateWhenReady.bind(this));
return this.cmp({command: 'ping'}).then(this.init.bind(this));
}

/**
Expand All @@ -204,15 +120,14 @@ export class GPPClient {
* @returns {Promise<{}>} a promise to GPP consent data
*/
updateConsent(pingData) {
return this.getGPPData(pingData).then((data) => {
if (data == null || isEmpty(data)) {
throw new GPPError('Received empty response from CMP', data);
return new GreedyPromise(resolve => {
if (pingData == null || isEmpty(pingData)) {
throw new GPPError('Received empty response from CMP', pingData);
}
return processCmpData(data);
}).then((data) => {
logInfo('Retrieved GPP consent from CMP:', data);
return data;
});
const consentData = processCmpData(pingData);
logInfo('Retrieved GPP consent from CMP:', consentData);
resolve(consentData);
})
}

/**
Expand All @@ -236,68 +151,10 @@ export class GPPClient {
updateWhenReady(pingData) {
return this.isCMPReady(pingData) ? this.updateConsent(pingData) : this.nextUpdate();
}
}

// eslint-disable-next-line no-unused-vars
class GPP10Client extends GPPClient {
static {
super.register(GPP_10);
}

events = ['sectionChange', 'cmpStatus'];

isCMPReady(pingData) {
return pingData.cmpStatus === 'loaded';
}

getGPPData(pingData) {
const parsedSections = GreedyPromise.all(
(pingData.supportedAPIs || pingData.apiSupport || []).map((api) => this.cmp({
command: 'getSection',
parameter: api
}).catch(err => {
logWarn(`Could not retrieve GPP section '${api}'`, err);
}).then((section) => [api, section]))
).then(sections => {
// parse single section object into [core, gpc] to uniformize with 1.1 parsedSections
return Object.fromEntries(
sections.filter(([_, val]) => val != null)
.map(([api, section]) => {
const subsections = [
Object.fromEntries(Object.entries(section).filter(([k]) => k !== 'Gpc'))
];
if (section.Gpc != null) {
subsections.push({
SubsectionType: 1,
Gpc: section.Gpc
});
}
return [api, subsections];
})
);
});
return GreedyPromise.all([
this.cmp({command: 'getGPPData'}),
parsedSections
]).then(([gppData, parsedSections]) => Object.assign({}, gppData, {parsedSections}));
}
}

// eslint-disable-next-line no-unused-vars
class GPP11Client extends GPPClient {
static {
super.register(GPP_11, true);
}

events = ['sectionChange', 'signalStatus'];

isCMPReady(pingData) {
return pingData.signalStatus === 'ready';
}

getGPPData(pingData) {
return GreedyPromise.resolve(pingData);
}
}

/**
Expand All @@ -308,7 +165,7 @@ class GPP11Client extends GPPClient {
* @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)
*/
export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) {
pipeCallbacks(() => GPPClient.init(mkCmp).then(([client, gppDataPm]) => gppDataPm), {onSuccess, onError});
pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError});
}

// add new CMPs here, with their dedicated lookup function
Expand Down Expand Up @@ -425,9 +282,9 @@ function processCmpData(consentData) {
}
['usnatv1', 'uscav1'].forEach(section => {
if (consentData?.parsedSections?.[section]) {
logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData)
logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData);
}
})
});
return storeConsentData(consentData);
}

Expand Down
Loading

0 comments on commit 6d5e416

Please sign in to comment.