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

Set a first-party cookie when user opts out of tracking #15184

Merged
merged 23 commits into from Dec 31, 2019

Conversation

katebutler
Copy link

Part of #14395 (allowing opt-out to work in Chrome on HTTP sites), also related to #6505

js/piwik.js Outdated
@@ -7706,6 +7712,32 @@ if (typeof window.Piwik !== 'object') {
// initialize the Piwik singleton
addEventListener(windowAlias, 'beforeunload', beforeUnloadHandler, false);

window.addEventListener('message', function(e) {
try {
var data = JSON.parse(e.data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't tested it yet... do we need to check if the message is for us just to avoid any issues with potentially other messages? like include some 'matomo_optout' in the string and then check if e.data && typeof e.data === 'string' && e.data.indexOf('matomo_optout') >= -1? (not tested that particular code... and would need to make sure we post this string in the opt out)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added some namespacing of the message attributes to make it less likely that we'll accidentally pick up messages intended for other scripts. The best practice would be to also check the message origin against the domain we expect to receive it from, but I can't find a way to do this since the tracker and the tracked site could be on any domain.
I don't think I need to explicitly check that e.data is defined, is a string etc as I am catching the exceptions from JSON.parse() in situations like this.

js/piwik.js Outdated
return;
}

if (typeof data.opted_in == 'undefined') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw we have some method for this isDefined(data.opt_in) or so is the name

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sweet, using this throughout now

js/piwik.js Outdated
return;
}

if (typeof data.loaded != 'undefined') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here be good to use our isDefined method for this maybe

js/piwik.js Outdated
* first-party opt-out cookie.
*/
updateOptOutForm: function() {
if (typeof window.postMessage === 'undefined') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here as well be good to use isDefined method

js/piwik.js Outdated
if (numAttempts > 1200) {
clearInterval(optOutTimer);
}
}, 100);
Copy link
Member

@tsteur tsteur Nov 24, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As DOM interactions are quite expensive in terms of performance, be good to check this maybe only about every second or so? Even every 2 seconds might be fine

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that the optout iframe might be displaying incorrectly to the user (e.g. saying they haven't opted out when they have) until it receives this message. The first time through the loop is usually unsuccessful because the iframe hasn't finished loading yet, and increasing the delay even to 200ms means there is a noticeable pause before the optout iframe updates itself. Maybe we need a new approach - calling postMessage on the iframe before it finishes loading is also causing an error message to be printed to the console which isn't ideal.

Copy link
Member

@tsteur tsteur Nov 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use setTimeout instead... and each time use something like interval = numAttempts * 100 or * 200 or

@@ -1,22 +1,73 @@
function submitForm(e, form) {
if (e.preventDefault) { // IE8 and below do not support preventDefault
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested the feature yet but would have expected the preventDefault code and also below the setInterval is still needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code was to prevent the checkbox state from toggling until after the API call had been made, and then refresh the iframe to show the new checkbox state. It seems a bit unnecessary when we are working with the first-party cookies, but if we do want to retain that workflow (i.e. sync the iframe form checkbox with the real world optout value after every change, to make sure that it reflects what will actually happen) I could put back the preventDefault and then fire a message back to the iframe after the first-party cookie has been set in the tracker JS.

@tsteur
Copy link
Member

tsteur commented Dec 7, 2019

@Findus23 @MichaelHeerklotz any chance you could give this PR a test as well?

js/piwik.js Outdated
var tracker = Piwik.getTracker();

var originHost = getHostName(e.origin);
var matomoHost = getHostName(tracker.getPiwikUrl());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would maybe need to adjust getHostName to check if url is set... otherwise if the tracking code is embedded on the page, but no async tracker is created (which some users do), then it might not return a string? Not sure though... looking at the code it seems getPiwikUrl always returns a string so it is likely fine

@tsteur
Copy link
Member

tsteur commented Dec 10, 2019

I was just testing the opt out. I was embedding the opt out into an html page and opted out. The third party ignore cookie was set. Then I added the tracking code to the same site. I then reloaded the page and it was suddenly saying

You are not opted out

Even though it should say

You are opted out

The problem is that it seems to only check for the first party cookie but not for the third party cookie status here:

image

This will be pretty much the behaviour for all Matomo sites as none of the visitors that previously unsubscribed would have the first party ignore cookie yet. So it is important to only show "You are not opted out" if neither the first nor the third party ignore cookie is set. If either of them is set, we assume the user is opted out.

I then opted out, and it was saying "opted out" after I clicked on it. However, when I reloaded the page, it was saying again "not opted out".

  • We should double check that the user was actually opted out before changing the status of the checkbox ideally. And if it didn't change the status, maybe we could show a message explaining the user it didn't work and to retry or to contact the website owner? Maybe could even adjust the message if the opt out is running on http? @mattab Any thoughts? It would basically not work when the site is embedded using http and the user does not have the tracking code on the same page or the opt out iframe domain does not match the tracker domain... In other cases either the first or the third party cookie should work... This message would be shown to actual visitors...
  • I noticed it was actually not changing the status because I had disableCookies in my tracking code (https://matomo.org/faq/general/faq_157/). I wonder if we need to still execute the methods forgetConsentGiven and rememberConsentGiven even when cookies are disabled? @Findus23 @mattab

Stopped testing afterwards.

@mattab
Copy link
Member

mattab commented Dec 11, 2019

We should double check that the user was actually opted out before changing the status of the checkbox ideally.

that'd be great.

And if it didn't change the status, maybe we could show a message explaining the user it didn't work and to retry or to contact the website owner? Maybe could even adjust the message if the opt out is running on http? @mattab Any thoughts? It would basically not work when the site is embedded using http and the user does not have the tracking code on the same page or the opt out iframe domain does not match the tracker domain...

If it's possible then be great to explain in the error message why it doesn't work eg Opt-out feature is unfortunately not working because this site is not using https and the tracking code cannot be found on this page. Please contact the website administrator for help. or Opt-out feature is unfortunately not working because this opt-out iframe domain $DOMAIN does not match the analytics service domain $DOMAIN2. Please contact the website administrator for help.

I noticed it was actually not changing the status because I had disableCookies in my tracking code (https://matomo.org/faq/general/faq_157/). I wonder if we need to still execute the methods forgetConsentGiven and rememberConsentGiven even when cookies are disabled? @Findus23 @mattab

not sure of the code details, but the idea would be that consent mechanism should be completely independant of cookies and work the same whether or not cookies are enabled in tracker.

@tsteur tsteur added this to the 3.13.1 milestone Dec 12, 2019
@katebutler katebutler added the Needs Review PRs that need a code review label Dec 12, 2019
@tsteur
Copy link
Member

tsteur commented Dec 22, 2019

Note: we still need to ignore disableCookies setting when opting out or in... will create a separate PR for this #15309

@tsteur
Copy link
Member

tsteur commented Dec 23, 2019

FYI made a few tweaks...

  • making sure it works with more browsers as eg not all browsers support startsWith etc.
  • Showing an error message if a user has popup blocker enabled (which it is by default on chrome) when changing the status
  • Showing an error message right away if cookies are disabled since it means it will most certainly not work I reckon
  • If when the popup closes, the optOut using postMessage did not work, then we may show a warning if we are on http. Generally we only show warnings if we are on HTTP as in all other cases things should work.
  • We can't fully detect if unsubscribe works because when the popup changes the ignore cookie, the cookies in the opt out frame are not updated and as such only a reload would help. However, we cannot really just reload since we don't really know if we will receive a confirmation message of the opt out through postMessage or not...

@diosmosis @Findus23 could you maybe have a look as well?

matches = e.exec(url);

return matches ? matches[1] : url;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would using:

document.createElement('a');
a.href = url;
return a.hostname;

be a bit safer here?

@diosmosis
Copy link
Member

@tsteur looks ok to me though I didn't test locally and it is a fairly complicated PR. I like the interesting use of postMessage here, that seems like it could potentially have other use cases in the tracker (not just opt out and overlay).

It would be nice to be able to unit test the opt out JS if possible (maybe by loading it in karma and testing w/ angular unit tests?). Might be a bit of work to make that possible though; if it's a good idea we could create an issue for now.

@tsteur tsteur merged commit 8aeaee6 into 3.x-dev Dec 31, 2019
@tsteur tsteur deleted the 14395-optout-clientside branch December 31, 2019 02:55
jonasgrilleres pushed a commit to 1024pix/pix-analytics that referenced this pull request Sep 22, 2020
jbuget pushed a commit to 1024pix/pix-analytics that referenced this pull request Sep 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Review PRs that need a code review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants