Skip to content

Commit 5757c70

Browse files
committed
Add telemetry opt-in/out buttons to consent page
1 parent 9f645e8 commit 5757c70

File tree

10 files changed

+131
-52
lines changed

10 files changed

+131
-52
lines changed

public/consent.html

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646

4747
button {
4848
width: 214px;
49-
height: 49px;
49+
height: 75px;
5050
border-radius: 4px;
5151
border-width: 1px;
5252
padding-top: 4px;
@@ -67,11 +67,14 @@
6767
cursor: pointer;
6868
}
6969

70+
button + button {
71+
margin-left: 10px;
72+
}
73+
7074
button#codecov-uninstall {
7175
background: white;
7276
border: 1px solid black;
7377
color: black;
74-
margin-left: 10px;
7578
}
7679
</style>
7780
</head>
@@ -102,15 +105,23 @@ <h1>Codecov Browser Extension</h1>
102105
The Codecov extension may collect and use information from your browser
103106
such as your IP address and the URLs of the pages you access on
104107
https://github.com or on an enterprise version or self-hosted
105-
installation of Github you explicitly grant the extension access to.
108+
installation of GitHub to which you explicitly grant the extension
109+
access.
110+
</p>
111+
<p>
112+
Additionally, you may choose to opt-in or opt-out of non-personal
113+
telemetry collection (error reporting and performance data).
106114
</p>
107115
<p>
108116
By clicking 'Accept', you confirm that you understand and agree to the
109117
privacy policy of the Codecov Browser Extension. Should you decide not
110118
to accept, please note that the extension will not work.
111119
</p>
112120
<br />
113-
<button id="codecov-consent-accept">Accept</button>
121+
<button id="codecov-consent-accept">Accept and allow telemetry</button>
122+
<button id="codecov-consent-accept-essential">
123+
Accept and opt-out of telemetry
124+
</button>
114125
<button id="codecov-uninstall">Uninstall</button>
115126
</div>
116127
</body>

public/consent.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,30 @@ document.addEventListener("DOMContentLoaded", function () {
33
consentButton.addEventListener("click", async function () {
44
const result = await browser.runtime.sendMessage({
55
type: "set_consent",
6-
payload: true,
6+
payload: "all",
77
});
88

99
if (result) {
10-
console.log("Consent granted, closing tab");
10+
console.log("All consent granted, closing tab");
11+
close();
12+
} else {
13+
console.error(
14+
"Something went wrong saving consent. Please report this at https://github.com/codecov/codecov-browser-extension/issues"
15+
);
16+
}
17+
});
18+
19+
var essentialConsentButton = document.getElementById(
20+
"codecov-consent-accept-essential"
21+
);
22+
essentialConsentButton.addEventListener("click", async function () {
23+
const result = await browser.runtime.sendMessage({
24+
type: "set_consent",
25+
payload: "essential",
26+
});
27+
28+
if (result) {
29+
console.log("Essential consent granted, closing tab");
1130
close();
1231
} else {
1332
console.error(

src/background/main.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414

1515
async function handleConsent(): Promise<void> {
1616
const consent = await new Codecov().getConsent();
17-
if (!consent) {
17+
if (consent === "none") {
1818
const url = browser.runtime.getURL("consent.html");
1919
await browser.tabs.create({ url, active: true });
2020
}
@@ -41,8 +41,8 @@ async function handleMessages(message: {
4141
referrer?: string;
4242
}) {
4343
const codecov = new Codecov();
44-
if (await codecov.getConsent()) {
45-
console.log("Have data consent, initializing Sentry");
44+
if ((await codecov.getConsent()) === "all") {
45+
console.log("Have full data consent, initializing Sentry");
4646
sentryInit({
4747
dsn: process.env.SENTRY_DSN,
4848

src/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// The version number here should represent the last version where consent requirement was updated.
22
// We must update the key's version to re-request consent whenever the data we collect changes.
3-
export const consentStorageKey = "codecov-consent-0.5.9";
3+
export const allConsentStorageKey = "codecov-consent-0.6.3";
4+
export const onlyEssentialConsentStorageKey = "codecov-essential-consent-0.6.3";
45

56
export const codecovApiTokenStorageKey = "self_hosted_codecov_api_token";
67
export const selfHostedCodecovURLStorageKey = "self_hosted_codecov_url";

src/content/common/sentry.ts

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,34 @@ import {
88
dedupeIntegration,
99
Scope,
1010
} from "@sentry/browser";
11+
import { Consent } from "src/types";
1112

1213
// Sentry config
1314
// Browser extensions must initialize Sentry a bit differently to avoid
1415
// conflicts between Sentry instances should the site the extension is running
1516
// on also use Sentry. Read more here:
1617
// https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/
1718

18-
const sentryClient = new BrowserClient({
19-
dsn: process.env.SENTRY_DSN,
20-
transport: makeFetchTransport,
21-
stackParser: defaultStackParser,
22-
integrations: [
23-
breadcrumbsIntegration,
24-
browserApiErrorsIntegration,
25-
globalHandlersIntegration,
26-
dedupeIntegration,
27-
],
28-
});
19+
export function initSentry(consent: Consent): Scope | undefined {
20+
// Only enable Sentry if have "all" data consent.
21+
if (consent !== "all") {
22+
return undefined;
23+
}
2924

30-
const Sentry = new Scope();
31-
Sentry.setClient(sentryClient);
32-
sentryClient.init();
25+
const sentryClient = new BrowserClient({
26+
dsn: process.env.SENTRY_DSN,
27+
transport: makeFetchTransport,
28+
stackParser: defaultStackParser,
29+
integrations: [
30+
breadcrumbsIntegration,
31+
browserApiErrorsIntegration,
32+
globalHandlersIntegration,
33+
dedupeIntegration,
34+
],
35+
});
3336

34-
export default Sentry;
37+
const Sentry = new Scope();
38+
Sentry.setClient(sentryClient);
39+
sentryClient.init();
40+
return Sentry;
41+
}

src/content/github/common/fetchers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import browser from "webextension-polyfill";
22
import {
3+
Consent,
34
FileCoverageReportResponse,
45
FileMetadata,
56
MessageType,
@@ -102,7 +103,7 @@ export async function getPRReport(url: any) {
102103
return response.data;
103104
}
104105

105-
export async function getConsent() {
106+
export async function getConsent(): Promise<Consent> {
106107
const response = await browser.runtime.sendMessage({
107108
type: MessageType.GET_CONSENT,
108109
});

src/content/github/file/main.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "tether-drop/dist/css/drop-theme-arrows.css";
66
import "src/basscss.css";
77
import "./style.css";
88
import {
9+
Consent,
910
CoverageStatus,
1011
FileCoverageReport,
1112
FileCoverageReportResponse,
@@ -32,7 +33,7 @@ import {
3233
getConsent,
3334
} from "../common/fetchers";
3435
import { print } from "src/utils";
35-
import Sentry from "../../common/sentry";
36+
import { initSentry } from "../../common/sentry";
3637

3738
const globals: {
3839
coverageReport?: FileCoverageReport;
@@ -44,23 +45,31 @@ const globals: {
4445
prompt?: HTMLElement;
4546
} = {};
4647

47-
init();
48+
let Sentry: ReturnType<typeof initSentry> = undefined;
49+
let consent: Consent = "none";
50+
51+
await init();
52+
53+
async function init(): Promise<void> {
54+
consent = await getConsent();
55+
56+
Sentry = initSentry(consent);
4857

49-
function init(): Promise<void> {
5058
// this event discovered by "reverse-engineering GitHub"
5159
// https://github.com/refined-github/refined-github/blob/main/contributing.md#reverse-engineering-github
5260
// TODO: this event is not fired when navigating using the browser's back and forward buttons
5361
document.addEventListener("soft-nav:end", () => {
5462
clear();
55-
main();
63+
main(consent);
5664
});
5765

58-
return main();
66+
return main(consent);
5967
}
6068

61-
async function main(): Promise<void> {
69+
async function main(consent: Consent): Promise<void> {
6270
try {
63-
if (!(await getConsent())) {
71+
if (consent === "none") {
72+
// No data consent, do nothing.
6473
return;
6574
}
6675

@@ -72,7 +81,9 @@ async function main(): Promise<void> {
7281
globals.coverageButton = createCoverageButton();
7382
await process(urlMetadata);
7483
} catch (e) {
75-
Sentry.captureException(e);
84+
if (Sentry) {
85+
Sentry.captureException(e);
86+
}
7687
throw e;
7788
}
7889
}
@@ -314,7 +325,7 @@ async function handleFlagClick(selectedFlags: string[]) {
314325
[flagsStorageKey]: selectedFlags,
315326
});
316327
clear();
317-
await main();
328+
await main(consent);
318329
}
319330

320331
async function handleComponentClick(selectedComponents: string[]) {
@@ -325,7 +336,7 @@ async function handleComponentClick(selectedComponents: string[]) {
325336
[componentsStorageKey]: selectedComponents,
326337
});
327338
clear();
328-
await main();
339+
await main(consent);
329340
}
330341

331342
function calculateCoveragePct(coverageReport: FileCoverageReport): number {

src/content/github/pr/main.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import _ from "lodash";
33

44
import "src/basscss.css";
55
import { displayChange } from "src/utils";
6-
import { CoverageStatus, PullCoverageReport } from "src/types";
6+
import { Consent, CoverageStatus, PullCoverageReport } from "src/types";
77
import {
88
animateAndAnnotateLines,
99
clearAnimation,
@@ -14,22 +14,38 @@ import { colors } from "../common/constants";
1414
import { print } from "src/utils";
1515
import { getConsent, getPRReport } from "../common/fetchers";
1616
import { isPrUrl } from "../common/utils";
17-
import Sentry from "src/content/common/sentry";
17+
import { initSentry } from "src/content/common/sentry";
1818

1919
const globals: {
2020
coverageReport?: PullCoverageReport;
2121
} = {};
2222

23-
async function main() {
23+
let Sentry: ReturnType<typeof initSentry> = undefined;
24+
let consent: Consent = "none";
25+
26+
await init();
27+
28+
async function init(): Promise<void> {
29+
consent = await getConsent();
30+
31+
Sentry = initSentry(consent);
32+
33+
return main(consent);
34+
}
35+
36+
async function main(consent: Consent) {
2437
try {
25-
if (!(await getConsent())) {
38+
if (consent === "none") {
39+
// No data consent, do nothing.
2640
return;
2741
}
2842

2943
document.addEventListener("soft-nav:end", execute);
3044
await execute();
3145
} catch (e) {
32-
Sentry.captureException(e);
46+
if (Sentry) {
47+
Sentry.captureException(e);
48+
}
3349
throw e;
3450
}
3551
}
@@ -193,5 +209,3 @@ function clearAnimationAndAnnotations() {
193209
clearAnimation(lineSelector, annotateLine);
194210
clearAnnotations((line: HTMLElement) => (line.style.boxShadow = "inherit"));
195211
}
196-
197-
main();

src/service.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import {
99
selfHostedGitHubURLStorageKey,
1010
providers,
1111
cacheTtlMs,
12-
consentStorageKey,
12+
allConsentStorageKey,
13+
onlyEssentialConsentStorageKey,
1314
} from "src/constants";
15+
import { Consent } from "./types";
1416

1517
export class Codecov {
1618
apiToken: string = "";
@@ -251,23 +253,34 @@ export class Codecov {
251253
};
252254
}
253255

254-
async getConsent(): Promise<boolean> {
256+
async getConsent(): Promise<Consent> {
255257
// We only need to get consent for firefox
256258
// @ts-ignore IS_FIREFOX is populated by Webpack at build time
257259
if (!IS_FIREFOX) {
258-
return true;
260+
return "all";
259261
}
260262

261-
const consent: boolean | undefined = await browser.storage.local
262-
.get(consentStorageKey)
263-
.then((res) => res[consentStorageKey]);
263+
const consents = await browser.storage.local.get([
264+
allConsentStorageKey,
265+
onlyEssentialConsentStorageKey,
266+
]);
264267

265-
return !!consent;
268+
if (consents[allConsentStorageKey]) {
269+
return "all";
270+
} else if (consents[onlyEssentialConsentStorageKey]) {
271+
return "essential";
272+
} else {
273+
return "none";
274+
}
266275
}
267276

268-
async setConsent(consent: boolean): Promise<boolean> {
277+
async setConsent(consent: Consent): Promise<Consent> {
278+
const allConsent = consent === "all";
279+
const essentialConsent = consent === "essential";
280+
269281
const storageObject: { [id: string]: boolean } = {};
270-
storageObject[consentStorageKey] = consent;
282+
storageObject[allConsentStorageKey] = allConsent;
283+
storageObject[onlyEssentialConsentStorageKey] = essentialConsent;
271284

272285
await browser.storage.local.set(storageObject);
273286

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ export enum MessageType {
4141
GET_CONSENT = "get_consent",
4242
SET_CONSENT = "set_consent",
4343
}
44+
45+
export type Consent = "all" | "essential" | "none";

0 commit comments

Comments
 (0)