Skip to content

Commit

Permalink
✨[amp-consent] Implement getTCData command for TCF PostMessage API (
Browse files Browse the repository at this point in the history
…#32411)

* init

* MinimalPingReturn object

* Create Manager

* clean up
  • Loading branch information
Micajuine Ho committed Feb 9, 2021
1 parent 147e1c3 commit 01fb8ef
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 31 deletions.
11 changes: 10 additions & 1 deletion examples/amp-consent/amp-consent-3p-postmessage.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,16 @@ <h3>Image that is blocked by both consent</h3>
<h3>Image that is NOT blocked by consent</h3>
<amp-img src="https://lh3.googleusercontent.com/pSECrJ82R7-AqeBCOEPGPM9iG9OEIQ_QXcbubWIOdkY=w300-h200-no-n" width="300" height="200"></amp-img>

<amp-video-iframe data-block-on-consent id="myVideo" src="./diy-3p-iframe-tcf-postmessage.html" width="720" height="405" layout="responsive" autoplay>
<amp-video-iframe data-block-on-consent='_till_responded' id="myVideo" src="./diy-3p-iframe-tcf-postmessage.html" width="720" height="405" layout="responsive" autoplay>
<div placeholder>
This is a placeholder
</div>
<div fallback>
This is a fallback
</div>
</amp-video-iframe>

<amp-video-iframe data-block-on-consent id="myVideo2" src="./diy-3p-iframe-tcf-postmessage.html" width="720" height="405" layout="responsive" autoplay>
<div placeholder>
This is a placeholder
</div>
Expand Down
6 changes: 6 additions & 0 deletions examples/amp-consent/diy-3p-iframe-tcf-postmessage.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
const msg = {
__tcfapiCall: {
command: cmd,
parameter: arg,
version: version,
callId: callId,
},
Expand Down Expand Up @@ -205,6 +206,11 @@
// should get response from window.top's CMP
console.log('Ping return:', pingReturn);
});

__tcfapi("getTCData", 2, (tcData, success) => {
// should get response from window.top's CMP
console.log('Minimal TCData return:', tcData);
});
</script>
</body>
</html>
99 changes: 96 additions & 3 deletions extensions/amp-consent/0.1/tcf-api-command-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ import {TCF_POST_MESSAGE_API_COMMANDS} from './consent-info';
import {isEnumValue, isObject} from '../../../src/types';
import {user} from '../../../src/log';

/**
* Event status is only defined for addEventListener command.
* @typedef {{
* tcfPolicyVersion: number,
* gdprApplies: (boolean|undefined),
* tcString: (string|undefined),
* listenerId: (string|undefined),
* cmpStatus: (string),
* eventStatus: (string),
* additionalData: (Object),
* }}
*/
export let MinimalTcData;

/**
* @typedef {{
* gdprApplies: (boolean|undefined),
Expand All @@ -33,6 +47,7 @@ const TAG = 'amp-consent';
const TCF_POLICY_VERSION = 2;
const CMP_STATUS = 'loaded';
const CMP_LOADED = true;
const EVENT_STATUS = 'tcloaded';

export class TcfApiCommandManager {
/**
Expand Down Expand Up @@ -60,13 +75,80 @@ export class TcfApiCommandManager {
this.handlePingEvent_(payload, win);
break;
case TCF_POST_MESSAGE_API_COMMANDS.GET_TC_DATA:
this.handleGetTcData_(payload, win);
break;
case TCF_POST_MESSAGE_API_COMMANDS.ADD_EVENT_LISTENER:
case TCF_POST_MESSAGE_API_COMMANDS.REMOVE_EVENT_LISTENER:
default:
return;
}
}

/**
* @return {!Promise<Array>}
*/
getTcDataPromises_() {
const consentStringInfoPromise = this.policyManager_.getConsentStringInfo(
'default'
);
const metadataPromise = this.policyManager_.getConsentMetadataInfo(
'default'
);
const sharedDataPromise = this.policyManager_.getMergedSharedData(
'default'
);

return Promise.all([
metadataPromise,
sharedDataPromise,
consentStringInfoPromise,
]);
}

/**
* Create minimal PingReturn object. Send to original iframe
* once object has been filled.
* @param {!Object} payload
* @param {!Window} win
*/
handleGetTcData_(payload, win) {
this.getTcDataPromises_().then((arr) => {
const returnValue = this.getMinimalTcData_(arr[0], arr[1], arr[2]);
const {callId} = payload;

this.sendTcfApiReturn_(win, returnValue, callId, true);
});
}

/**
* Create MinimalTCData object. Fill in fields dependent on
* command.
* @param {?Object} metadata
* @param {?Object} sharedData
* @param {?string} tcString
* @param {number=} opt_listenerId
* @return {!MinimalTcData} policyManager
*/
getMinimalTcData_(metadata, sharedData, tcString, opt_listenerId) {
const purposeOneTreatment = metadata ? metadata['purposeOne'] : undefined;
const gdprApplies = metadata ? metadata['gdprApplies'] : undefined;
const additionalConsent = metadata
? metadata['additionalConsent']
: undefined;
const additionalData = {...sharedData, additionalConsent};

return {
tcfPolicyVersion: TCF_POLICY_VERSION,
gdprApplies,
tcString,
listenerId: opt_listenerId,
cmpStatus: CMP_STATUS,
eventStatus: EVENT_STATUS,
purposeOneTreatment,
additionalData,
};
}

/**
* Create minimal PingReturn object. Send to original iframe
* once object has been filled.
Expand Down Expand Up @@ -102,14 +184,14 @@ export class TcfApiCommandManager {
* @param {!Window} win
* @param {!JsonObject} returnValue
* @param {string} callId
* @param {boolean=} success
* @param {boolean=} opt_success
*/
sendTcfApiReturn_(win, returnValue, callId, success) {
sendTcfApiReturn_(win, returnValue, callId, opt_success) {
if (!win) {
return;
}

const __tcfapiReturn = {returnValue, callId, success};
const __tcfapiReturn = {returnValue, callId, success: opt_success};
win./*OK*/ postMessage(
/** @type {!JsonObject} */ ({
__tcfapiReturn,
Expand Down Expand Up @@ -158,4 +240,15 @@ export class TcfApiCommandManager {
getMinimalPingReturnForTesting(metadata) {
return this.getMinimalPingReturn_(metadata);
}

/**
* @param {?Object} metadata
* @param {?Object} sharedData
* @param {?string} tcString
* @return {!MinimalPingReturn}
* @visibleForTesting
*/
getMinimalTcDataForTesting(metadata, sharedData, tcString) {
return this.getMinimalTcData_(metadata, sharedData, tcString);
}
}
158 changes: 131 additions & 27 deletions extensions/amp-consent/0.1/test/test-tcf-api-command-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,26 @@ describes.realWin(
let data;
let msg;
let tcfApiCommandManager;
let mockTcString;
let mockSharedData;
let callId;

beforeEach(() => {
mockWin = mockWindowInterface(window.sandbox);
mockWin.postMessage = window.sandbox.spy();
mockMetadata = {};
mockSharedData = {};
mockTcString = '';
mockPolicyManager = {
getConsentMetadataInfo: (opt_policy) => {
return Promise.resolve(mockMetadata);
},
getConsentStringInfo: (opt_policy) => {
return Promise.resolve(mockTcString);
},
getMergedSharedData: (opt_policy) => {
return Promise.resolve(mockSharedData);
},
};
});

Expand Down Expand Up @@ -97,9 +108,7 @@ describes.realWin(

describe('handlePingEvent', () => {
it('creates a minimal ping object', async () => {
mockMetadata = {
gdprApplies: false,
};
mockMetadata = getMetadata(false);
tcfApiCommandManager = new TcfApiCommandManager(mockPolicyManager);
expect(
tcfApiCommandManager.getMinimalPingReturnForTesting(mockMetadata)
Expand All @@ -112,9 +121,7 @@ describes.realWin(
});

it('creates a minimal ping object with no gdprApplies', async () => {
mockMetadata = {
gdprApplies: undefined,
};
mockMetadata = getMetadata();
tcfApiCommandManager = new TcfApiCommandManager(mockPolicyManager);
expect(
tcfApiCommandManager.getMinimalPingReturnForTesting(mockMetadata)
Expand All @@ -139,37 +146,134 @@ describes.realWin(
});

it('sends a minimal PingReturn via PostMessage', async () => {
const callId = 'pingCallId';
data = {
__tcfapiCall: {
'command': 'ping',
callId,
version: 2,
},
};
mockMetadata = {
gdprApplies: true,
};
callId = 'pingCallId';
data = getData('ping', callId);
mockMetadata = getMetadata(true);
tcfApiCommandManager = new TcfApiCommandManager(mockPolicyManager);
tcfApiCommandManager.handleTcfCommand(data, mockWin);
await macroTask();

// No 'success' sent for ping
const postMessageArgs = mockWin.postMessage.args[0];
expect(postMessageArgs[0]).to.deep.equals({
__tcfapiReturn: {
returnValue: {
cmpLoaded: true,
gdprApplies: mockMetadata.gdprApplies,
cmpStatus: 'loaded',
tcfPolicyVersion: 2,
},
expect(postMessageArgs[0]).to.deep.equals(
getTcfApiReturn(
callId,
success: undefined,
},
tcfApiCommandManager.getMinimalPingReturnForTesting(mockMetadata)
)
);
});
});

describe('handleGetTcData', () => {
it('creates a minimal TcData object', async () => {
mockMetadata = getMetadata(false, 'xyz987', false);
tcfApiCommandManager = new TcfApiCommandManager(mockPolicyManager);
expect(
tcfApiCommandManager.getMinimalTcDataForTesting(
mockMetadata,
mockSharedData,
mockTcString
)
).to.deep.equals({
tcfPolicyVersion: 2,
gdprApplies: false,
tcString: mockTcString,
listenerId: undefined,
cmpStatus: 'loaded',
eventStatus: 'tcloaded',
purposeOneTreatment: false,
additionalData: {'additionalConsent': 'xyz987'},
});

mockTcString = 'abc123';
mockSharedData = {'data': 'data1'};

expect(
tcfApiCommandManager.getMinimalTcDataForTesting(
mockMetadata,
mockSharedData,
mockTcString
)
).to.deep.equals({
tcfPolicyVersion: 2,
gdprApplies: false,
tcString: mockTcString,
listenerId: undefined,
cmpStatus: 'loaded',
eventStatus: 'tcloaded',
purposeOneTreatment: false,
additionalData: {'data': 'data1', 'additionalConsent': 'xyz987'},
});
});

it('sends a minimal TcData via PostMessage', async () => {
callId = 'getTcDataId';
data = getData('getTCData', callId);
mockMetadata = getMetadata(false, 'xyz987', false);
mockTcString = 'abc123';
mockSharedData = {'data': 'data1'};
tcfApiCommandManager = new TcfApiCommandManager(mockPolicyManager);
tcfApiCommandManager.handleTcfCommand(data, mockWin);
await macroTask();

const postMessageArgs = mockWin.postMessage.args[0];
expect(postMessageArgs[0]).to.deep.equals(
getTcfApiReturn(
callId,
tcfApiCommandManager.getMinimalTcDataForTesting(
mockMetadata,
mockSharedData,
mockTcString
),
true
)
);
});
});
});
}
);

/**
* @param {string} command
* @param {string} callId
* @param {string=} opt_parameter
*/
function getData(command, callId, opt_parameter) {
return {
__tcfapiCall: {
command,
callId,
version: 2,
parameter: opt_parameter,
},
};
}

/**
* @param {string=} opt_gdprApplies
* @param {string=} opt_addtlConsent
* @param {boolean=} opt_purposeOne
*/
function getMetadata(opt_gdprApplies, opt_addtlConsent, opt_purposeOne) {
return {
gdprApplies: opt_gdprApplies,
additionalConsent: opt_addtlConsent,
purposeOne: opt_purposeOne,
};
}

/**
* @param {string} callId
* @param {Object=} opt_returnValue
* @param {boolean=} opt_success
*/
function getTcfApiReturn(callId, opt_returnValue, opt_success) {
return {
__tcfapiReturn: {
callId,
returnValue: opt_returnValue,
success: opt_success,
},
};
}

0 comments on commit 01fb8ef

Please sign in to comment.