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

Feature: Privacy Notice Consent popup for Firefox #99

Merged
Merged
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ Install for your browser:
[last-xpi]: https://github.com/ActivityWatch/aw-watcher-web/releases/download/v0.4.3/aw-watcher-web-v0.4.3.xpi
[818]: https://github.com/orgs/ActivityWatch/discussions/818#discussioncomment-4017528

### Firefox Enterprise Policy

Due to the issue mentioned above, a privacy notice has to be displayed to follow the Mozilla add-on policy. This can be pre-accepted by setting the following Firefox Enterprise Policy:
```json
{
"policies": {
"3rdparty": {
"Extensions": {
"{ef87d84c-2127-493f-b952-5b4e744245bc}": {
"consentOfflineDataCollection": true
}
}
}
}
}
```

## Building

Expand Down
1 change: 1 addition & 0 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

"background": {
"scripts": [
"static/installed.js",
"out/app.js"
],
"persistent": false
Expand Down
44 changes: 41 additions & 3 deletions src/eventPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,48 @@ function popupRequestReceived(msg) {
}
}

async function askConsentNeeded() {
// Source for compatibility check: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension#handling_api_differences
try {
if (typeof browser.runtime.getBrowserInfo != "function") {
return false
}
} catch (e) {
return false
}
let browserInfo;
await browser.runtime.getBrowserInfo().then((info) => {browserInfo = info})
if (browserInfo.name != "Firefox") {
return false
}
try {
if (await browser.storage.managed.get("consentOfflineDataCollection")) {
return false
}
} catch (e) {
console.error('managed storage error: ', e)
return true
}
return true
}

function startPopupListener() {
chrome.storage.local.get(["enabled"], function(obj) {
chrome.storage.local.get(["enabled"], async function(obj) {
if (obj.enabled == undefined) {
chrome.storage.local.set({"enabled": true});
if(await askConsentNeeded()) {
chrome.storage.local.set({"enabled": false}); // TODO: replace with storage.managed
chrome.storage.local.set({"noConsentGiven": true});
chrome.storage.local.get("askConsent", (obj) => {
if(obj.askConsent) {
const url = chrome.runtime.getURL("../static/consent.html");
chrome.windows.create({ url, type: "popup", height: 420, width: 416, });
freshInstall = false;
}
})
} else {
chrome.storage.local.set({"enabled": true});
startWatcher();
}
}
});
chrome.runtime.onMessage.addListener(popupRequestReceived);
Expand All @@ -163,5 +201,5 @@ function startPopupListener() {

(function() {
startPopupListener();
startWatcher();
// startWatcher() moved to startPopupListener
})();
28 changes: 28 additions & 0 deletions static/consent.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>ActivityWatch Consent Dialog</title>

<link href="./style.css" rel="stylesheet" type="text/css">

<script src="./consent.js"></script>
</head>

<body class="consent">
<img src="/media/banners/banner.png" style="width: 100%">

<hr>

<h1>Privacy Notice</h1>
<p>
This extension by nature collects personal identifiable information in the form of URLs. All collected information never leaves your device because all data is only forwarded to localhost. Since this is the core functionality of this extension, the only option to not consent, is to uninstall.
</p>
<div class="action-container">
<div class="action"><button class="button" id="consent-refused">Remove ActivityWatch</button></div>
<div class="action"><button class="button accept" id="consent-given">Consent to offline data collection</button></div>
</div>

</body>

</html>
19 changes: 19 additions & 0 deletions static/consent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use strict";

function consentListeners() {
let consent_refused = document.getElementById('consent-refused');
let consent_given = document.getElementById('consent-given');
consent_refused.addEventListener("click", (obj) => {
browser.management.uninstallSelf()
window.close()
Copy link
Member

@ErikBjare ErikBjare Jan 11, 2023

Choose a reason for hiding this comment

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

I think this should prompt the user to uninstall/disable the extension, if we can, as per Mozilla guidelines: https://extensionworkshop.com/documentation/develop/best-practices-for-collecting-user-data-consents/

image

});
consent_given.addEventListener("click", (obj) => {
chrome.runtime.sendMessage({enabled: true}, function(response) {});
chrome.storage.local.set({"noConsentGiven": false});
window.close()
})
}

document.addEventListener('DOMContentLoaded', function() {
consentListeners();
})
11 changes: 11 additions & 0 deletions static/installed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";

chrome.runtime.onInstalled.addListener(async ({ reason, temporary }) => {
switch (reason) {
case "install":
{
chrome.storage.local.set({"askConsent": true});
}
break;
}
});
3 changes: 3 additions & 0 deletions static/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
<!-- Filled by JS -->
</input>
</td>
<td>
<button id="status-consent-btn">Consent to Privacy Notice</button>
</td>
</tr>

<tr>
Expand Down
21 changes: 20 additions & 1 deletion static/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,21 @@
function renderStatus() {
chrome.storage.local.get(["lastSync", "lastSyncSuccess", "testing", "baseURL", "enabled"], function(obj) {
// Enabled checkbox
document.getElementById('status-enabled-checkbox').checked = obj.enabled;
let enabledCheckbox = document.getElementById('status-enabled-checkbox');
enabledCheckbox.checked = obj.enabled;

// Consent Button
let showConsentBtn = document.getElementById('status-consent-btn');
chrome.storage.local.get("noConsentGiven", (obj) => {
console.log('noConsentGiven: ', obj.noConsentGiven)
if (obj.noConsentGiven) {
enabledCheckbox.setAttribute('disabled', '');
showConsentBtn.style.display = 'inline-block';
} else {
enabledCheckbox.removeAttribute('disabled');
showConsentBtn.style.display = 'none';
}
});

// Connected
let connectedColor = obj.lastSyncSuccess ? "#00AA00" : "#FF0000";
Expand Down Expand Up @@ -34,6 +48,11 @@ function domListeners() {
let enabled = obj.srcElement.checked;
chrome.runtime.sendMessage({enabled: enabled}, function(response) {});
});
let consent_button = document.getElementById('status-consent-btn');
consent_button.addEventListener('click', () => {
const url = chrome.runtime.getURL("../static/consent.html");
chrome.windows.create({ url, type: "popup", height: 420, width: 416, });
});
}

document.addEventListener('DOMContentLoaded', function() {
Expand Down
31 changes: 31 additions & 0 deletions static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ body {
width: 25em;
}

.consent {
width: 400px;
}

hr {
border: 1px solid #DDD;
}
Expand All @@ -12,6 +16,15 @@ a {
text-decoration: none;
}

h1 {
margin-bottom: 0px;
}

button {
cursor: pointer;
font-size: 100%;
}

.button {
display: inline-block;
color: #333333;
Expand All @@ -22,10 +35,28 @@ a {
border: 1px solid #DDD;
}

#status-consent-btn {
display: none;
}

.accept {
color: #FFF;
background-color: #007ef8;
border: 1px solid #0073e6;
}

#status {
/* avoid an excessively wide status text */
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
max-width: 400px;
}

.action-container {
display: flex;
}

/* .action {
flex: 1;
} */