Skip to content

Commit

Permalink
Support obtaining consent from cmpapi through iframe postmessage
Browse files Browse the repository at this point in the history
  • Loading branch information
zapo committed Mar 3, 2025
1 parent 8a76aec commit dc4eff4
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/core/regs/consent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ function computeConsent(defaultReg: Regulation | null, cmp: CMPSignals, conf: CM

function getConsent(defaultReg: Regulation | null, conf: CMPApiConfig = {}): Consent {
const cmp: CMPSignals = {};
tcf.cmpapi.installFrameProxy();
gpp.cmpapi.installFrameProxy();

const consent = computeConsent(defaultReg, cmp, conf);

onTCFChange((data) => {
Expand Down
79 changes: 78 additions & 1 deletion lib/core/regs/gpp/cmpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,81 @@ type AddEventListener = {

type AddEventListenerCallback = (data: AddEventListener, success: boolean) => void;

export { PingReturn };
// installFrameProxy is a helper function that attempts to install
// a proxy for the CMP API. This is required when the CMP API is installed
// in a parent frame (eg when the SDK is loaded in an iframe).
// See https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#using-postmessage
//
// To do so, this function looks for a ancestor frame named __gppLocator and communicates
// with it using postMessage by mocking window.__gpp.
function installFrameProxy() {
// Already there, might be installed by ourselves or a CMP script
if (typeof window.__gpp === "function") {
return;
}

let cmpFrame;
const cmpCallbacks: Record<string, AddEventListenerCallback> = {};

let frame = window;
while (frame) {
// throws a reference error if no frames exist
try {
// @ts-ignore
if (frame.frames["__gppLocator"]) {
cmpFrame = frame;
break;
}
} catch {
// ignore
}

if (frame === window.top) {
break;
}
// @ts-ignore
frame = frame.parent;
}

// No CMP frame found
if (!cmpFrame) {
return;
}

// Install the API that forwards to the CMP frame
window.__gpp = function (command, callback) {
const callId = Math.random() + "";
cmpCallbacks[callId] = callback;

cmpFrame.postMessage({ __gppCall: { command, version: "1.1", callId } }, "*");
};

// Receive responses
window.addEventListener(
"message",
(event) => {
let json: { __gppReturn?: { callId: string; success: boolean; returnValue: AddEventListener } } = {};
try {
json = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
} catch {
// ignore
}

const payload = json.__gppReturn;
if (!payload) {
return;
}

if (typeof cmpCallbacks[payload.callId] === "function") {
// Call the callback
cmpCallbacks[payload.callId](payload.returnValue, payload.success);
// Remove the callback
delete cmpCallbacks[payload.callId];
}
},
false
);
}

export type { PingReturn };
export { installFrameProxy };
79 changes: 78 additions & 1 deletion lib/core/regs/tcf/cmpapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,81 @@ type TCData = {

type AddEventListenerCallback = (data: TCData, success: boolean) => void;

export { TCData };
// installFrameProxy is a helper function that attempts to install
// a proxy for the CMP API. This is required when the CMP API is installed
// in a parent frame (eg when the SDK is loaded in an iframe).
// See https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md#using-postmessage
//
// To do so, this function looks for a ancestor frame named __tcfapiLocator and communicates
// with it using postMessage by mocking window.__tcfapi.
function installFrameProxy() {
// Already there, might be installed by ourselves or a CMP script
if (typeof window.__tcfapi === "function") {
return;
}

let cmpFrame;
const cmpCallbacks: Record<string, AddEventListenerCallback> = {};

let frame = window;
while (frame) {
// throws a reference error if no frames exist
try {
// @ts-ignore
if (frame.frames["__tcfapiLocator"]) {
cmpFrame = frame;
break;
}
} catch {
// ignore
}

if (frame === window.top) {
break;
}
// @ts-ignore
frame = frame.parent;
}

// No CMP frame found
if (!cmpFrame) {
return;
}

// Install the API that forwards to the CMP frame
window.__tcfapi = function (command, version, callback) {
const callId = Math.random() + "";
cmpCallbacks[callId] = callback;

cmpFrame.postMessage({ __tcfapiCall: { command, version, callId } }, "*");
};

// Receive responses
window.addEventListener(
"message",
(event) => {
let json: { __tcfapiReturn?: { callId: string; returnValue: TCData; success: boolean } } = {};
try {
json = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
} catch {
// ignore
}

const payload = json.__tcfapiReturn;
if (!payload) {
return;
}

if (typeof cmpCallbacks[payload.callId] === "function") {
// Call the callback
cmpCallbacks[payload.callId](payload.returnValue, payload.success);
// Remove the callback
delete cmpCallbacks[payload.callId];
}
},
false
);
}

export type { TCData };
export { installFrameProxy };

0 comments on commit dc4eff4

Please sign in to comment.