Skip to content

Commit

Permalink
feat: privacy notice consent popup for Firefox (ActivityWatch#99)
Browse files Browse the repository at this point in the history
* created consent page with style and js

* implemented ask consent popup

* renamed refuse button, since an extension can't uninstall itself

* implemented extra consent button, if first popup was denied

* extra script for installed listener needed to execute in time

* implemented button actions

* various bug fixes and made compatible with chrome

* cleanup

* added explicit semicolon

Co-authored-by: Erik Bjäreholt <[email protected]>

* implemented remove extension on consent refused

* implemented giving consent using enterprise policy

* added firefox enterprise policy example

* fixed typo

Co-authored-by: Erik Bjäreholt <[email protected]>
  • Loading branch information
Morpheus0x and ErikBjare authored Jan 18, 2023
1 parent b47fa73 commit 5379254
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 4 deletions.
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()
});
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;
} */

0 comments on commit 5379254

Please sign in to comment.