diff --git a/jpm-prefs.json b/jpm-prefs.json index 96455277..eadb8985 100644 --- a/jpm-prefs.json +++ b/jpm-prefs.json @@ -1,4 +1,6 @@ { + "toolkit.telemetry.enabled": true, + "toolkit.telemetry.server": "https://127.0.0.1/telemetry-dummy/", "xpinstall.signatures.required": false, "extensions.legacy.enabled": true } diff --git a/src/bootstrap.js b/src/bootstrap.js index 78fc0e25..3489a99e 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -4,9 +4,36 @@ /* eslint-disable no-unused-vars */ +const { utils: Cu } = Components; + +Cu.import("resource://gre/modules/Services.jsm"); + function startup({webExtension}, reason) { - webExtension.startup().then(() => { + Services.telemetry.registerEvents("lockbox", { + "startup": { + methods: ["startup"], + objects: ["addon", "webextension"], + }, + "click": { + methods: ["click"], + objects: ["browser_action"], + extra_keys: ["fxauid"], + }, + }); + + webExtension.startup().then(({browser}) => { console.log("embedded webextension has started"); + Services.telemetry.recordEvent("lockbox", "startup", "webextension"); + browser.runtime.onMessage.addListener((message, sender, respond) => { + switch (message.type) { + case "telemetry_event": + Services.telemetry.recordEvent( + message.category, message.method, message.object, null, + message.extra || null + ); + respond({}); + } + }); }); } diff --git a/src/webextension/background/browser-action.js b/src/webextension/background/browser-action.js index 30524714..ed633d88 100644 --- a/src/webextension/background/browser-action.js +++ b/src/webextension/background/browser-action.js @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { openView } from "./views"; +import * as telemetry from "./telemetry"; let listener; let popup; @@ -13,6 +14,7 @@ function installPopup(path) { popup, }); } + function uninstallPopup() { if (popup) { browser.browserAction.setPopup({ popup: "" }); @@ -21,9 +23,13 @@ function uninstallPopup() { } function installListener(name) { - listener = () => { openView(name); }; + listener = () => { + telemetry.recordEvent("lockbox", "click", "browser_action"); + openView(name); + }; browser.browserAction.onClicked.addListener(listener); } + function uninstallListener() { if (listener) { browser.browserAction.onClicked.removeListener(listener); diff --git a/src/webextension/background/message-ports.js b/src/webextension/background/message-ports.js index 37cfc203..2d5792df 100644 --- a/src/webextension/background/message-ports.js +++ b/src/webextension/background/message-ports.js @@ -5,6 +5,7 @@ import openDataStore from "./datastore"; import getAuthorization, { saveAuthorization } from "./authorization/index"; import updateBrowserAction from "./browser-action"; +import * as telemetry from "./telemetry"; import { openView, closeView } from "./views"; import { makeItemSummary } from "../common"; @@ -24,7 +25,7 @@ export default function initializeMessagePorts() { port.onDisconnect.addListener(() => ports.delete(port)); }); - browser.runtime.onMessage.addListener(async function(message, sender) { + browser.runtime.onMessage.addListener(async(message, sender) => { switch (message.type) { case "open_view": return openView(message.name).then(() => ({})); @@ -90,8 +91,13 @@ export default function initializeMessagePorts() { return openDataStore().then(async(ds) => { return {item: await ds.get(message.id)}; }); + + case "proxy_telemetry_event": + return telemetry.recordEvent( + message.category, message.method, message.object, message.extra + ); default: - throw new Error(`unknown message type "${message.type}`); + return null; } }); } diff --git a/src/webextension/background/telemetry.js b/src/webextension/background/telemetry.js new file mode 100644 index 00000000..a1be3cd0 --- /dev/null +++ b/src/webextension/background/telemetry.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import getAuthorization from "./authorization/index"; + +export async function recordEvent(category, method, object, extra) { + const fxauid = getAuthorization().uid; + if (fxauid) { + extra = {...(extra || {}), fxauid}; + } + + return browser.runtime.sendMessage({ + type: "telemetry_event", category, method, object, extra, + }); +} diff --git a/src/webextension/telemetry.js b/src/webextension/telemetry.js new file mode 100644 index 00000000..884268b6 --- /dev/null +++ b/src/webextension/telemetry.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +export async function recordEvent(category, method, object, extra) { + return browser.runtime.sendMessage({ + type: "proxy_telemetry_event", category, method, object, extra, + }); +} diff --git a/test/background/message-ports-test.js b/test/background/message-ports-test.js index 46f0ba7d..b66e6927 100644 --- a/test/background/message-ports-test.js +++ b/test/background/message-ports-test.js @@ -199,9 +199,10 @@ describe("message ports (background side)", () => { }); it("handle unknown message type", async() => { - await expect(browser.runtime.sendMessage({ + const result = await browser.runtime.sendMessage({ type: "nonexist", - })).to.be.rejectedWith(Error); + }); + expect(result).to.equal(null); }); it("handle message port disconnect", async() => { diff --git a/test/background/telemetry-test.js b/test/background/telemetry-test.js new file mode 100644 index 00000000..59835339 --- /dev/null +++ b/test/background/telemetry-test.js @@ -0,0 +1,93 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +require("babel-polyfill"); + +import chai, { expect } from "chai"; +import sinon from "sinon"; +import sinonChai from "sinon-chai"; + +chai.use(sinonChai); + +import getAuthorization from "../../src/webextension/background/authorization"; +import * as telemetry from "../../src/webextension/background/telemetry"; + +describe("background - telemetry", () => { + let onMessage; + + beforeEach(() => { + browser.runtime.onMessage.addListener(onMessage = sinon.stub().returns({})); + }); + + afterEach(() => { + browser.runtime.onMessage.mockClearListener(); + }); + + describe("no fxa uid", () => { + it("recordEvent()", async() => { + const result = await telemetry.recordEvent("category", "method", + "object"); + expect(result).to.deep.equal({}); + expect(onMessage).to.have.been.calledWith({ + type: "telemetry_event", + category: "category", + method: "method", + object: "object", + extra: undefined, + }); + }); + + it("recordEvent() with extra", async() => { + const result = await telemetry.recordEvent("category", "method", "object", + {extra: "value"}); + expect(result).to.deep.equal({}); + expect(onMessage).to.have.been.calledWith({ + type: "telemetry_event", + category: "category", + method: "method", + object: "object", + extra: {extra: "value"}, + }); + }); + }); + + describe("with fxa uid", () => { + before(() => { + // Pretend we're signed in. + getAuthorization().info = { + uid: "1234", + }; + }); + + after(() => { + getAuthorization().info = undefined; + }); + + it("recordEvent() (with fxa uid)", async() => { + const result = await telemetry.recordEvent("category", "method", + "object"); + expect(result).to.deep.equal({}); + expect(onMessage).to.have.been.calledWith({ + type: "telemetry_event", + category: "category", + method: "method", + object: "object", + extra: {fxauid: "1234"}, + }); + }); + + it("recordEvent() (with fxa uid and extras)", async() => { + const result = await telemetry.recordEvent("category", "method", "object", + {extra: "value"}); + expect(result).to.deep.equal({}); + expect(onMessage).to.have.been.calledWith({ + type: "telemetry_event", + category: "category", + method: "method", + object: "object", + extra: {extra: "value", fxauid: "1234"}, + }); + }); + }); +}); diff --git a/test/telemetry-test.js b/test/telemetry-test.js new file mode 100644 index 00000000..17d3d0f4 --- /dev/null +++ b/test/telemetry-test.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +require("babel-polyfill"); + +import chai, { expect } from "chai"; +import sinon from "sinon"; +import sinonChai from "sinon-chai"; + +chai.use(sinonChai); + +import * as telemetry from "../src/webextension/telemetry"; + +describe("telemetry", () => { + let onMessage; + + beforeEach(() => { + browser.runtime.onMessage.addListener(onMessage = sinon.stub().returns({})); + }); + + afterEach(() => { + browser.runtime.onMessage.mockClearListener(); + }); + + it("recordEvent()", async() => { + const result = await telemetry.recordEvent("category", "method", "object", + {extra: "value"}); + expect(result).to.deep.equal({}); + expect(onMessage).to.have.been.calledWith({ + type: "proxy_telemetry_event", + category: "category", + method: "method", + object: "object", + extra: {extra: "value"}, + }); + }); +});