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
get/set local/session storage/cookies through cmp #866
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 0f8b4bf The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
src/cmpStorage.ts
Outdated
console.log(`consentState.canTarget: ${consentState.canTarget}`); | ||
*/ | ||
|
||
switch(useCase) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there might be one other use-case for permissions 1 and 2 and maybe 7 - "Limited data advertising" - I'm going to ask Data Privacy if Article Count can switch to use [1, 2 and 7] rather than [1, 3 and 7].
apparently getraw and setraw are also used and hence will also have to wrap those: https://github.com/search?q=org%3Aguardian%20storage.local.getraw&type=code |
[beta] @guardian/consent-management-platform |
Coverage report
Show new covered files 🐣
Test suite run success339 tests passing in 18 suites. Report generated by 🧪jest coverage report action from 0f8b4bf |
🚀 0.0.0-beta-20240118125826 published to npm as a beta release |
After some further thought following our conversation with @akinsola-guardian, @sookburt and @mxdvl on friday, I think this is how i see things... Whatever we do, these need to be true:
Current proposalThe current proposal provides extended versions the existing This achieves 1, but I think breaks 3:
Other optionsKeep the helpers in libs, import the CMPAs you mention that creates a cyclical peer-dependency. This cannot work, because it creates an eternally recurring peerDep bumping cycle, among other good other reasons not to. Keep libs and CMP separate, offer self-certification in helpere.g. import { checkConsent, onConsentChange } from '@guardian/consent-management-platform'
onConsentChange(consentState => {
storage.local.set('a', 'b', {
consent: checkConsent(consentState) // `true` or `false`
})
}) I think this bends 3.i and breaks 3.ii – it passes much of the responsibility onto consumers, and any changes to the CMP implementation will need updates to consumer code. I think the fact you could pass a simple Dependency injectione.g. import { checkConsent, onConsentChange } from '@guardian/consent-management-platform'
onConsentChange(consentState => {
storage.local.set('a', 'b', { checkConsent, consentState })
}) This is safer, and while you could pass a function of the same signature as But it does still pass a lot of work onto consumers, requiring them to compose consent checking and storage access with little benefit to them. I'd say it bends 3.i and 3.ii? Ideal API?My feeling is we want an API that's something like this: import { cookie, storage } from '@guardian/libs'
await cookie.set('a', 'b', { useCase: 'good' })
await storage.local.set('a', 'b', { useCase: 'evil' }) It abstracts all consent logic away to the CMP (and so the consent team), and allows consumers to focus on their job at hand (saving something to storage) while making it easy for them provide the reason. It does mean the function becomes async, but we can keep, deprecate and lint against the current sync signature, while adding the consent options object to make async: storage.local.set('a', 'b') // current, no consent
await storage.local.set('a', 'b', { useCase: 'evil' }) // new, with consent How do we get there?By moving the CMP modules to The only reason they are different codebases is because that's how we're organised internally. Until now, it was annoying (the waterfall of package bumps between projects) but it didn't impede functionality. But given all the above, I think it's the best eventual outcome. What about now?It obviously not the quickest solution. In the meantime, if we agree that would be a good eventuality, we could still inject an instance/methods of the CMP into import { checkConsent } from '@guardian/consent-management-platform'
import { provideConsentCheck, storage } from '@guardian/libs'
provideConsentCheck(checkConsent)
cmp.init({})
// `storage.local.set` uses an async `checkConsent` internally
await storage.local.set('a', 'b', { useCase }) Once the CMP code has been integrated into libs, the final API would be something like: import { cmp, storage } from '@guardian/libs'
cmp.init({})
await storage.local.set('a', 'b', { useCase }) |
[beta] @guardian/consent-management-platform |
🚀 0.0.0-beta-20240122213831 published to npm as a beta release |
What does this change?
Adds a wrapper around get/set local/session strorage/cookies. Provides as list of available consent use cases (e.g. 'Targeted Advertising' or 'Essential') that users can specify and ensure that the right consent for the use-case has been granted by our reader.
Why?
To ensure compliance with consent and avoid many different locations in our code that check for consent for their use-cases which makes it hard to ensure compliance for example in case of a framework change.
Need to check consent on 'get' as the same cookie/storage item could be used for several different use-cases and require different consent. Obviously need to check on 'set'.
Privacy by Design: https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/accountability-and-governance/guide-to-accountability-and-governance/accountability-and-governance/data-protection-by-design-and-default/
https://ico.org.uk/for-organisations/uk-gdpr-guidance-and-resources/designing-products-that-protect-privacy/privacy-in-the-product-design-lifecycle/
Requirements and possible options
Consent requirements:
Use-cases for storage/cookies:
How is consent obtained/checked:
? What for server-side applications? --> Perhaps out of scope for now and next steps.
? Is there a use-case where consent is obtained not through the cmp? Could not currently identify such a use-case.
Architectural considerations
Possible architectural options:
? Where could this be located? Perhaps in the @guardian/libs: Would required libs to import cmp. cmp depends on libs (circular dependency?) Could it be in a separate package, something like @guardian/storage?
2a) "Self-certification" of consent and one function to interact with cookies/storage: Users would first check if there is consent for their use case and then certify that they have done so by passing in a boolean.
Reminder: all reader-facing functions have to be asynchronous as consent is required.
? Where could this be located? The consent check could live in cmp and the cookie/storage interaction in
@guardian/journalism-tools-stream
2b) We also discussed that instead of passing a self-certify boolean to pass a function that would do the consent check and return a boolean.
Reminder: all reader-facing functions have to be asynchronous as consent is required.
? Is that really different from passing a boolean? Perhaps it makes it easier to use the cmp in a timely way while still allowing use-cases that do not require cmp to pass in 'true'.
? Where could this be located? Perhaps in the @guardian/libs: Would required libs to import cmp. cmp depends on libs (risk of circular dependency would be broken through dependency injection)
-> One async functions that does all in one: get consent and the data.
-> One sync function that requires self-certification.
? Where could this be located? Perhaps in the @guardian/libs: Would required libs to import cmp. cmp depends on libs (circular dependency?) Could it be in a separate package, something like @guardian/storage?
cmp does the consentState magic and users call a one-liner from cmp as an async call: await storage.local.get(useCase, key)
users get the consentState from cmp (async) and pass it into a storage/cookie interaction function in cmp so that @guardian/libs do not depend on cmp: const consentState = await onConsent(); storage.local.get(useCase, consentState, key)
How do we get there?
The path will depend on the architectural choice. Using the linter to flag to users the correct usage is probably going to be helpful. Give a grace period to migrate with linter warnings, then upgrade to linter errors.