Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨amp-o2-player add handling consent data #31005

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 96 additions & 0 deletions extensions/amp-o2-player/0.1/amp-o2-player.js
Expand Up @@ -14,9 +14,16 @@
* limitations under the License.
*/

import {CONSENT_POLICY_STATE} from '../../../src/consent-state';
import {MessageType} from '../../../src/3p-frame-messaging';
import {Services} from '../../../src/services';
import {dict} from '../../../src/utils/object';
import {
getConsentPolicyInfo,
getConsentPolicyState,
} from '../../../src/consent';
import {isLayoutSizeDefined} from '../../../src/layout';
import {listenFor} from '../../../src/iframe-helper';
import {setIsMediaComponent} from '../../../src/video-interface';
import {userAssert} from '../../../src/log';

Expand Down Expand Up @@ -123,10 +130,99 @@ class AmpO2Player extends AMP.BaseElement {
iframe.src = this.src_;
this.iframe_ = /** @type {HTMLIFrameElement} */ (iframe);
this.applyFillContent(iframe);

listenFor(iframe, MessageType.SEND_CONSENT_DATA, (data, source, origin) => {
this.sendConsentData_(source, origin);
});

this.element.appendChild(iframe);
return this.loadPromise(iframe);
}

/**
* Requests consent data from consent module
* and forwards information to iframe
* @param {Window} source
* @param {string} origin
* @private
*/
sendConsentData_(source, origin) {
const consentPolicyId = super.getConsentPolicy() || 'default';
const consentStringPromise = this.getConsentString_(consentPolicyId);
const consentPolicyStatePromise = this.getConsentPolicyState_(
consentPolicyId
);

Promise.all([consentPolicyStatePromise, consentStringPromise]).then(
(consents) => {
let consentData;
switch (consents[0]) {
case CONSENT_POLICY_STATE.SUFFICIENT:
consentData = {
'gdprApplies': true,
'user_consent': 1,
'gdprString': consents[1],
};
break;
case CONSENT_POLICY_STATE.INSUFFICIENT:
case CONSENT_POLICY_STATE.UNKNOWN:
consentData = {
'gdprApplies': true,
'user_consent': 0,
'gdprString': consents[1],
};
break;
case CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED:
default:
consentData = {
'gdprApplies': false,
};
}

this.sendConsentDataToIframe_(
source,
origin,
dict({
'sentinel': 'amp',
'type': MessageType.CONSENT_DATA,
'consentData': consentData,
})
);
}
);
}

/**
* Send consent data to iframe
* @param {Window} source
* @param {string} origin
* @param {JsonObject} data
* @private
*/
sendConsentDataToIframe_(source, origin, data) {
source./*OK*/ postMessage(data, origin);
}

/**
* Get the consent string
* @param {string} consentPolicyId
* @private
* @return {Promise}
*/
getConsentString_(consentPolicyId = 'default') {
return getConsentPolicyInfo(this.element, consentPolicyId);
}

/**
* Get the consent policy state
* @param {string} consentPolicyId
* @private
* @return {Promise}
*/
getConsentPolicyState_(consentPolicyId = 'default') {
return getConsentPolicyState(this.element, consentPolicyId);
}

/** @override */
pauseCallback() {
if (this.iframe_ && this.iframe_.contentWindow) {
Expand Down
153 changes: 152 additions & 1 deletion extensions/amp-o2-player/0.1/test/test-amp-o2-player.js
Expand Up @@ -15,6 +15,9 @@
*/

import '../amp-o2-player';
import * as iframeHelper from '../../../../src/iframe-helper';
import {CONSENT_POLICY_STATE} from '../../../../src/consent-state';
import {MessageType} from '../../../../src/3p-frame-messaging';

describes.realWin(
'amp-o2-player',
Expand All @@ -31,7 +34,7 @@ describes.realWin(
doc = win.document;
});

async function getO2player(attributes, opt_responsive) {
async function getO2player(attributes, opt_responsive, implExtends) {
const o2 = doc.createElement('amp-o2-player');
for (const key in attributes) {
o2.setAttribute(key, attributes[key]);
Expand All @@ -42,6 +45,10 @@ describes.realWin(
o2.setAttribute('layout', 'responsive');
}
doc.body.appendChild(o2);

if (implExtends) {
implExtends(o2);
}
await o2.build();
await o2.layoutCallback();
return o2;
Expand Down Expand Up @@ -143,5 +150,149 @@ describes.realWin(
'https://delivery.dev.vidible.tv/htmlembed/pid=123/456.html'
);
});

describe('sends a consent-data', () => {
let sendConsentDataToIframe;
const resSource = 'my source';
const resOrigin = 'my origin';
const resConsentString = 'consent string';
let consentData = {
'gdprApplies': true,
'user_consent': 1,
'gdprString': resConsentString,
};
const resData = {
sentinel: 'amp',
type: MessageType.CONSENT_DATA,
};

it('sends a consent-data CONSENT_POLICY_STATE.SUFFICIENT message', async function () {
resData.consentData = consentData;

const implExtends = function (o2) {
env.sandbox
.stub(o2.implementation_, 'getConsentString_')
.resolves(resConsentString);

env.sandbox
.stub(o2.implementation_, 'getConsentPolicyState_')
.resolves(CONSENT_POLICY_STATE.SUFFICIENT);

sendConsentDataToIframe = env.sandbox.spy(
o2.implementation_,
'sendConsentDataToIframe_'
);
};

env.sandbox
.stub(iframeHelper, 'listenFor')
.callsFake((iframe, message, callback) => {
expect(message).to.equal(MessageType.SEND_CONSENT_DATA);
callback('', resSource, resOrigin);
});

await getO2player(
{
'data-pid': '123',
'data-bcid': '456',
},
null,
implExtends
);

expect(sendConsentDataToIframe).to.have.been.calledWith(
resSource,
resOrigin,
resData
);
});

it('sends a consent-data INSUFFICIENT or UNKNOWN message', async function () {
consentData['user_consent'] = 0;
resData.consentData = consentData;

const implExtends = function (o2) {
env.sandbox
.stub(o2.implementation_, 'getConsentString_')
.resolves(resConsentString);

env.sandbox
.stub(o2.implementation_, 'getConsentPolicyState_')
.resolves(CONSENT_POLICY_STATE.INSUFFICIENT);

sendConsentDataToIframe = env.sandbox.spy(
o2.implementation_,
'sendConsentDataToIframe_'
);
};

env.sandbox
.stub(iframeHelper, 'listenFor')
.callsFake((iframe, message, callback) => {
expect(message).to.equal(MessageType.SEND_CONSENT_DATA);
callback('', resSource, resOrigin);
});

await getO2player(
{
'data-pid': '123',
'data-bcid': '456',
},
null,
implExtends
);

expect(sendConsentDataToIframe).to.have.been.calledWith(
resSource,
resOrigin,
resData
);
});

it('sends a consent-data UNKNOWN_NOT_REQUIRED or default message', async function () {
consentData = {
'gdprApplies': false,
};

resData.consentData = consentData;

const implExtends = function (o2) {
env.sandbox
.stub(o2.implementation_, 'getConsentString_')
.resolves(resConsentString);

env.sandbox
.stub(o2.implementation_, 'getConsentPolicyState_')
.resolves(CONSENT_POLICY_STATE.UNKNOWN_NOT_REQUIRED);

sendConsentDataToIframe = env.sandbox.spy(
o2.implementation_,
'sendConsentDataToIframe_'
);
};

env.sandbox
.stub(iframeHelper, 'listenFor')
.callsFake((iframe, message, callback) => {
expect(message).to.equal(MessageType.SEND_CONSENT_DATA);
callback('', resSource, resOrigin);
});

await getO2player(
{
'data-pid': '123',
'data-bcid': '456',
},
null,
implExtends
);

expect(sendConsentDataToIframe).to.have.been.calledWith(
resSource,
resOrigin,
resData
);
});
});
}
);
37 changes: 37 additions & 0 deletions extensions/amp-o2-player/amp-o2-player.md
Expand Up @@ -100,3 +100,40 @@ The following lists validation errors specific to the `amp-o2-player` tag:
<td>Error thrown when invalid value is given for attributes <code>height</code> or <code>width</code>. For example, <code>height=auto</code> triggers this error for all supported layout types, with the exception of <code>NODISPLAY</code>.</td>
</tr>
</table>

## Consent Data

Iframe inside `amp-o2-player` can send a message to receive consent data if a CMP is present on `amp-o2-player` parents page.

Example request for consent data from iframe:

```javascript
window.parent.postMessage(
{
sentinel: 'amp',
type: 'send-consent-data',
},
'*'
);
```

Example receive response for consent data:

```javascript
function isAmpMessage(event, type) {
return (
event.source == window.parent &&
event.origin != window.location.origin &&
event.data &&
event.data.sentinel == 'amp' &&
event.data.type == type
);
}

window.addEventListener('message', function (event) {
if (!isAmpMessage(event, 'consent-data')) {
return;
}
console.log(event.data.consentData);
});
```