Skip to content

Commit 8ca8b63

Browse files
author
Micajuine Ho
authored
✨ amp-consent: gdprApplies value (#27759)
* Sync w/ cache from amp-consent * Tests * no caching for now * suggested changes * Never use local consentRequired
1 parent 612979e commit 8ca8b63

File tree

7 files changed

+173
-7
lines changed

7 files changed

+173
-7
lines changed

extensions/amp-consent/0.1/amp-consent.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ export class AmpConsent extends AMP.BaseElement {
420420
*/
421421
init_() {
422422
this.passSharedData_();
423+
this.setGdprApplies();
423424
this.syncRemoteConsentState_();
424425

425426
this.getConsentRequiredPromise_()
@@ -482,6 +483,30 @@ export class AmpConsent extends AMP.BaseElement {
482483
this.consentStateManager_.setConsentInstanceSharedData(sharedDataPromise);
483484
}
484485

486+
/**
487+
* Create and set gdprApplies promise form consent manager.
488+
* Default value to remote `consentRequired`, if no
489+
* `gdprApplies` value is provided.
490+
*
491+
* TODO(micajuinho) remove this method (and subsequent methods
492+
* in consent-state-manager) in favor of consolidation with
493+
* consentString
494+
*/
495+
setGdprApplies() {
496+
const responsePromise = this.getConsentRemote_();
497+
const gdprAppliesPromise = responsePromise.then((response) => {
498+
if (!response) {
499+
return null;
500+
}
501+
const gdprApplies = response['gdprApplies'];
502+
return gdprApplies === undefined || typeof gdprApplies !== 'boolean'
503+
? response['consentRequired']
504+
: gdprApplies;
505+
});
506+
507+
this.consentStateManager_.setConsentInstanceGdprApplies(gdprAppliesPromise);
508+
}
509+
485510
/**
486511
* Clear cache for server side decision and then sync.
487512
*/
@@ -497,6 +522,8 @@ export class AmpConsent extends AMP.BaseElement {
497522
this.consentStateManager_.setDirtyBit();
498523
}
499524

525+
// TODO(micajuineho) When we consolidate, add gdprApplies field
526+
// to be set with consentString.
500527
// Decision from promptUI takes precedence over consent decision from response
501528
if (
502529
!!response['consentRequired'] &&

extensions/amp-consent/0.1/consent-policy-manager.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ export class ConsentPolicyManager {
179179
consentStateChangeHandler_(info) {
180180
const state = info['consentState'];
181181
const consentStr = info['consentString'];
182-
const prevConsentStr = this.consentString_;
182+
const {consentString_: prevConsentStr} = this;
183+
183184
this.consentString_ = consentStr;
184185
if (state === CONSENT_ITEM_STATE.UNKNOWN) {
185186
// consent state has not been resolved yet.
@@ -268,6 +269,20 @@ export class ConsentPolicyManager {
268269
});
269270
}
270271

272+
/**
273+
* Get gdprApplies value of a policy.
274+
*
275+
* @param {string} policyId
276+
* @return {!Promise<Object>}
277+
*/
278+
getGdprApplies(policyId) {
279+
return this.whenPolicyResolved(policyId)
280+
.then(() => this.ConsentStateManagerPromise_)
281+
.then((manager) => {
282+
return manager.getConsentInstanceGdprApplies();
283+
});
284+
}
285+
271286
/**
272287
* Get the consent string value of a policy. Return a promise that resolves
273288
* when the policy resolves.

extensions/amp-consent/0.1/consent-state-manager.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ export class ConsentStateManager {
161161
this.instance_.sharedDataPromise = sharedDataPromise;
162162
}
163163

164+
/**
165+
* Sets a promise which resolves to a boolean that is to be returned
166+
* from the remote endpoint.
167+
*
168+
* @param {!Promise<?boolean>} gdprAppliesPromise
169+
*/
170+
setConsentInstanceGdprApplies(gdprAppliesPromise) {
171+
devAssert(this.instance_, '%s: cannot find the instance', TAG);
172+
this.instance_.gdprAppliesPromise = gdprAppliesPromise;
173+
}
174+
164175
/**
165176
* Sets the dirty bit so current consent info won't be used for
166177
* decision making on next visit
@@ -180,6 +191,16 @@ export class ConsentStateManager {
180191
return this.instance_.sharedDataPromise;
181192
}
182193

194+
/**
195+
* Returns a promise that resolves to a gdprApplies value
196+
*
197+
* @return {?Promise<?boolean>}
198+
*/
199+
getConsentInstanceGdprApplies() {
200+
devAssert(this.instance_, '%s: cannot find the instance', TAG);
201+
return this.instance_.gdprAppliesPromise;
202+
}
203+
183204
/**
184205
* Returns a promise that's resolved when consent instance is ready.
185206
* @return {*} TODO(#23582): Specify return type
@@ -226,6 +247,11 @@ export class ConsentInstance {
226247
/** @public {?Promise<Object>} */
227248
this.sharedDataPromise = null;
228249

250+
// TODO(micajuineho) remove this in favor
251+
// of consolidation with consentString
252+
/** @public {?Promise<?boolean>} */
253+
this.gdprAppliesPromise = null;
254+
229255
/** @private {Promise<!../../../src/service/storage-impl.Storage>} */
230256
this.storagePromise_ = Services.storageForDoc(ampdoc);
231257

@@ -276,11 +302,11 @@ export class ConsentInstance {
276302
update(state, consentString, opt_systemUpdate) {
277303
const localState =
278304
this.localConsentInfo_ && this.localConsentInfo_['consentState'];
279-
const localConsentStr =
280-
this.localConsentInfo_ && this.localConsentInfo_['consentString'];
281305
const calculatedState = recalculateConsentStateValue(state, localState);
282306

283307
if (state === CONSENT_ITEM_STATE.DISMISSED) {
308+
const localConsentStr =
309+
this.localConsentInfo_ && this.localConsentInfo_['consentString'];
284310
// If state is dismissed, use the old consent string.
285311
this.localConsentInfo_ = constructConsentInfo(
286312
calculatedState,
@@ -315,7 +341,7 @@ export class ConsentInstance {
315341

316342
if (isConsentInfoStoredValueSame(newConsentInfo, this.savedConsentInfo_)) {
317343
// Only update/save to localstorage if it's not dismiss
318-
// And the value is different from what is stored.
344+
// and the value is different from what is stored.
319345
return;
320346
}
321347

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,59 @@ describes.realWin(
385385
'https://server-test-3/':
386386
'{"consentRequired": true, "consentStateValue": "unknown"}',
387387
'https://geo-override-check2/': '{"consentRequired": true}',
388+
'https://gdpr-applies/':
389+
'{"consentRequired": true, "gdprApplies": false}',
388390
};
389391
});
390392

393+
describe('gdprApplies value', () => {
394+
it('uses given value', async () => {
395+
const inlineConfig = {
396+
'consentInstanceId': 'abc',
397+
'consentRequired': 'remote',
398+
'checkConsentHref': 'https://gdpr-applies/',
399+
};
400+
ampConsent = getAmpConsent(doc, inlineConfig);
401+
await ampConsent.buildCallback();
402+
await macroTask();
403+
const stateManagerGdprApplies = await ampConsent
404+
.getConsentStateManagerForTesting()
405+
.getConsentInstanceGdprApplies();
406+
expect(stateManagerGdprApplies).to.be.false;
407+
});
408+
409+
it('defaults to consentRequired remote value', async () => {
410+
const inlineConfig = {
411+
'consentInstanceId': 'abc',
412+
'consentRequired': 'remote',
413+
'checkConsentHref': 'https://geo-override-check2/',
414+
};
415+
ampConsent = getAmpConsent(doc, inlineConfig);
416+
await ampConsent.buildCallback();
417+
await macroTask();
418+
await expect(
419+
ampConsent
420+
.getConsentStateManagerForTesting()
421+
.getConsentInstanceGdprApplies()
422+
).to.eventually.be.true;
423+
});
424+
425+
it('never defaults to inline config when checkConsentHref is not defined', async () => {
426+
const inlineConfig = {
427+
'consentInstanceId': 'abc',
428+
'consentRequired': true,
429+
};
430+
ampConsent = getAmpConsent(doc, inlineConfig);
431+
await ampConsent.buildCallback();
432+
await macroTask();
433+
await expect(
434+
ampConsent
435+
.getConsentStateManagerForTesting()
436+
.getConsentInstanceGdprApplies()
437+
).to.eventually.be.null;
438+
});
439+
});
440+
391441
it('should not update local storage when response is false', async () => {
392442
const inlineConfig = {
393443
'consentInstanceId': 'abc',
@@ -414,7 +464,7 @@ describes.realWin(
414464
});
415465
});
416466

417-
it('should not update local storage when response is null', async () => {
467+
it('should not update local storage when consent value response is null', async () => {
418468
const inlineConfig = {
419469
'consentInstanceId': 'abc',
420470
'consentRequired': 'remote',

extensions/amp-consent/0.1/test/test-consent-policy-manager.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ describes.realWin(
6565
})
6666
);
6767
},
68+
getConsentInstanceGdprApplies: () => {
69+
return Promise.resolve(false);
70+
},
6871
});
6972
});
7073
});
@@ -572,5 +575,25 @@ describes.realWin(
572575
});
573576
});
574577
});
578+
579+
describe('getGdprApplies', () => {
580+
let manager;
581+
582+
beforeEach(() => {
583+
manager = new ConsentPolicyManager(ampdoc);
584+
manager.setLegacyConsentInstanceId('ABC');
585+
});
586+
587+
it('should return gdprApplies value', async () => {
588+
manager.registerConsentPolicyInstance('default', {
589+
'waitFor': {
590+
'ABC': undefined,
591+
},
592+
});
593+
await macroTask();
594+
// Set above in getConsentInstanceGdprApplies mock
595+
await expect(manager.getGdprApplies()).to.eventually.be.false;
596+
});
597+
});
575598
}
576599
);

extensions/amp-consent/0.1/test/test-consent-state-manager.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ describes.realWin('ConsentStateManager', {amp: 1}, (env) => {
215215
);
216216
});
217217
});
218+
219+
it('receives and sets gdprApplies', async () => {
220+
manager.registerConsentInstance('test', {});
221+
manager.setConsentInstanceGdprApplies(Promise.resolve(false));
222+
await expect(manager.getConsentInstanceGdprApplies()).to.eventually.be
223+
.false;
224+
});
218225
});
219226

220227
describe('ConsentInstance', () => {

src/consent.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ export function getConsentPolicySharedData(element, policyId) {
5858
}
5959

6060
/**
61-
* TODO(zhouyx): Combine with getConsentPolicyState and return a consentInfo
62-
* object.
61+
* TODO(micajuine-ho): Combine with getConsentPolicyGdprApplies
62+
* and (getConsentType in future) getGdprAppliesInfo to return
63+
* a consentInfo object.
6364
* @param {!Element|!ShadowRoot} element
6465
* @param {string} policyId
6566
* @return {!Promise<string>}
@@ -78,6 +79,23 @@ export function getConsentPolicyInfo(element, policyId) {
7879
);
7980
}
8081

82+
/**
83+
* @param {!Element|!ShadowRoot} element
84+
* @param {string} policyId
85+
* @return {!Promise<?boolean>}
86+
*/
87+
export function getConsentPolicyGdprApplies(element, policyId) {
88+
// Return the stored gdpr applies value.
89+
return Services.consentPolicyServiceForDocOrNull(element).then(
90+
(consentPolicy) => {
91+
if (!consentPolicy) {
92+
return null;
93+
}
94+
return consentPolicy.getGdprApplies(/** @type {string} */ (policyId));
95+
}
96+
);
97+
}
98+
8199
/**
82100
* Determine if an element needs to be blocked by consent based on meta tags.
83101
* @param {*} element

0 commit comments

Comments
 (0)