Skip to content

Commit 4eeab9b

Browse files
SlikeTechclaude
andauthored
Add GDPR/consent support to amp-slikeplayer (#40500)
* Add GDPR/consent support to amp-slikeplayer Integrate AMP consent framework so the Slike player iframe can request and receive consent data via postMessage, enabling cleo-player to handle personalisation and analytics decisions based on user consent state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add unit tests for consent data forwarding in amp-slikeplayer Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 44604cf commit 4eeab9b

File tree

3 files changed

+196
-0
lines changed

3 files changed

+196
-0
lines changed

examples/slikeplayer.amp.html

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@
7474
custom-element="amp-slikeplayer"
7575
src="https://cdn.ampproject.org/v0/amp-slikeplayer-0.1.js"
7676
></script>
77+
<script
78+
async
79+
custom-element="amp-consent"
80+
src="https://cdn.ampproject.org/v0/amp-consent-0.1.js"
81+
></script>
82+
<script
83+
async
84+
custom-element="amp-geo"
85+
src="https://cdn.ampproject.org/v0/amp-geo-0.1.js"
86+
></script>
7787

7888
<style amp-custom>
7989
body {
@@ -249,6 +259,67 @@
249259
font-weight: 600;
250260
}
251261

262+
.consent-ui {
263+
background: #333;
264+
color: white;
265+
padding: 16px 24px;
266+
display: flex;
267+
align-items: center;
268+
justify-content: space-between;
269+
flex-wrap: wrap;
270+
gap: 12px;
271+
font-size: 0.95rem;
272+
}
273+
274+
.consent-ui p {
275+
margin: 0;
276+
flex: 1;
277+
min-width: 200px;
278+
}
279+
280+
.consent-ui .btn-group {
281+
display: flex;
282+
gap: 8px;
283+
}
284+
285+
.consent-ui button {
286+
border: none;
287+
border-radius: 4px;
288+
padding: 8px 20px;
289+
font-size: 0.9rem;
290+
cursor: pointer;
291+
font-weight: 600;
292+
}
293+
294+
.consent-ui .btn-accept {
295+
background: #4facfe;
296+
color: white;
297+
}
298+
299+
.consent-ui .btn-reject {
300+
background: #666;
301+
color: white;
302+
}
303+
304+
.post-consent-ui {
305+
background: #e8f5e9;
306+
padding: 8px 16px;
307+
text-align: center;
308+
font-size: 0.85rem;
309+
color: #333;
310+
}
311+
312+
.post-consent-ui button {
313+
border: none;
314+
background: #667eea;
315+
color: white;
316+
border-radius: 4px;
317+
padding: 4px 12px;
318+
margin-left: 8px;
319+
cursor: pointer;
320+
font-size: 0.85rem;
321+
}
322+
252323
@media (max-width: 768px) {
253324
.header h1 {
254325
font-size: 2rem;
@@ -267,6 +338,50 @@
267338
</head>
268339

269340
<body>
341+
<!-- Geo detection for consent -->
342+
<amp-geo layout="nodisplay">
343+
<script type="application/json">
344+
{
345+
"ISOCountryGroups": {
346+
"eea": ["at", "be", "bg", "hr", "cy", "cz", "dk", "ee", "fi", "fr",
347+
"de", "gr", "hu", "ie", "it", "lv", "lt", "lu", "mt", "nl",
348+
"pl", "pt", "ro", "sk", "si", "es", "se", "gb", "is", "li",
349+
"no", "ch"]
350+
}
351+
}
352+
</script>
353+
</amp-geo>
354+
355+
<!-- Consent management -->
356+
<amp-consent id="slike-consent" layout="nodisplay">
357+
<script type="application/json">
358+
{
359+
"consentInstanceId": "slike-player-consent",
360+
"consentRequired": true,
361+
"promptUI": "consent-prompt",
362+
"postPromptUI": "post-consent-prompt"
363+
}
364+
</script>
365+
<div id="consent-prompt" class="consent-ui">
366+
<p>
367+
We use cookies and data to deliver and improve our services. By
368+
accepting, you agree to personalized content and ads.
369+
</p>
370+
<div class="btn-group">
371+
<button class="btn-accept" on="tap:slike-consent.accept">
372+
Accept
373+
</button>
374+
<button class="btn-reject" on="tap:slike-consent.reject">
375+
Reject
376+
</button>
377+
</div>
378+
</div>
379+
<div id="post-consent-prompt" class="post-consent-ui">
380+
Consent preference saved.
381+
<button on="tap:slike-consent.prompt">Manage</button>
382+
</div>
383+
</amp-consent>
384+
270385
<!-- Header Section -->
271386
<div class="header">
272387
<div class="container">

extensions/amp-slikeplayer/0.1/amp-slikeplayer.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {installVideoManagerForDoc} from '#service/video-manager-impl';
1010
import {getData, listen} from '#utils/event-helper';
1111
import {userAssert} from '#utils/log';
1212

13+
import {getConsentDataToForward} from '../../../src/consent';
1314
import {disableScrollingOnIframe} from '../../../src/iframe-helper';
1415
import {
1516
addUnsafeAllowAutoplay,
@@ -266,12 +267,20 @@ export class AmpSlikeplayer extends AMP.BaseElement {
266267
}
267268

268269
const data = objOrParseJson(messageData);
270+
271+
// Handle consent request from iframe (sent as raw object with type field)
272+
if (data['type'] === 'send-consent-data') {
273+
this.sendConsentData_();
274+
return;
275+
}
276+
269277
const event = data['event'];
270278
const detail = data['detail'];
271279
if (event === 'ready') {
272280
detail && this.onReadyOnce_(detail);
273281
return;
274282
}
283+
275284
const {element} = this;
276285
if (redispatch(element, event, CleoEvent)) {
277286
return;
@@ -338,6 +347,29 @@ export class AmpSlikeplayer extends AMP.BaseElement {
338347
this.postMessage_('handleViewport', inViewport);
339348
}
340349

350+
/**
351+
* Fetches consent data from AMP consent service
352+
* and forwards it to the iframe via postMessage.
353+
* @private
354+
*/
355+
sendConsentData_() {
356+
getConsentDataToForward(this.element, this.getConsentPolicy()).then(
357+
(consents) => {
358+
if (!this.iframe_ || !this.iframe_.contentWindow) {
359+
return;
360+
}
361+
this.iframe_.contentWindow./*OK*/ postMessage(
362+
{
363+
'sentinel': 'amp',
364+
'type': 'consent-data',
365+
...consents,
366+
},
367+
this.targetOrigin_
368+
);
369+
}
370+
);
371+
}
372+
341373
/**
342374
* @override
343375
*/

extensions/amp-slikeplayer/0.1/test/test-amp-slikeplayer.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import '../amp-slikeplayer';
22

33
import {listenOncePromise} from '#utils/event-helper';
44

5+
import * as consent from '../../../../src/consent';
56
import {VideoEvents_Enum} from '../../../../src/video-interface';
67

78
describes.realWin(
@@ -212,5 +213,53 @@ describes.realWin(
212213
// Subsequent layout should be possible
213214
await el.layoutCallback();
214215
});
216+
217+
it('calls sendConsentData_ on send-consent-data message', async () => {
218+
const consentData = {
219+
consentPolicyState: 1,
220+
consentString: 'abc123',
221+
consentMetadata: {gdprApplies: true, purposeOne: true},
222+
consentPolicySharedData: null,
223+
};
224+
env.sandbox
225+
.stub(consent, 'getConsentDataToForward')
226+
.resolves(consentData);
227+
228+
const {iframe, impl} = await buildPlayer();
229+
const sendSpy = env.sandbox.spy(impl, 'sendConsentData_');
230+
231+
// Simulate consent request from iframe (raw object, not JSON)
232+
impl.onMessage_({
233+
source: iframe.contentWindow,
234+
data: {type: 'send-consent-data', sentinel: 'amp'},
235+
});
236+
237+
expect(sendSpy).to.have.been.calledOnce;
238+
239+
// Wait for the consent promise to resolve
240+
await new Promise((r) => setTimeout(r, 0));
241+
242+
expect(consent.getConsentDataToForward).to.have.been.calledOnce;
243+
});
244+
245+
it('does not send consent data if iframe is gone', async () => {
246+
const consentData = {consentPolicyState: 2};
247+
env.sandbox
248+
.stub(consent, 'getConsentDataToForward')
249+
.resolves(consentData);
250+
251+
const {iframe, impl} = await buildPlayer();
252+
253+
// Destroy iframe before consent resolves
254+
impl.iframe_ = null;
255+
256+
impl.onMessage_({
257+
source: iframe.contentWindow,
258+
data: {type: 'send-consent-data', sentinel: 'amp'},
259+
});
260+
261+
await new Promise((r) => setTimeout(r, 0));
262+
// No error thrown — silently skipped
263+
});
215264
}
216265
);

0 commit comments

Comments
 (0)