Skip to content
This repository has been archived by the owner on Feb 7, 2019. It is now read-only.

Commit

Permalink
Initial implementation of telemetry
Browse files Browse the repository at this point in the history
This allows our webextension to record telemetry (events only at the moment)
by forwarding messages first through the background scripts (to pick up the
user's FxA user id) and then to the XPCOM extension.
  • Loading branch information
Jim Porter committed Oct 11, 2017
1 parent e8e545f commit ab58ea7
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 6 deletions.
2 changes: 2 additions & 0 deletions 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
}
29 changes: 28 additions & 1 deletion src/bootstrap.js
Expand Up @@ -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({});
}
});
});
}

Expand Down
8 changes: 7 additions & 1 deletion src/webextension/background/browser-action.js
Expand Up @@ -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;
Expand All @@ -13,6 +14,7 @@ function installPopup(path) {
popup,
});
}

function uninstallPopup() {
if (popup) {
browser.browserAction.setPopup({ popup: "" });
Expand All @@ -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);
Expand Down
10 changes: 8 additions & 2 deletions src/webextension/background/message-ports.js
Expand Up @@ -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";

Expand All @@ -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(() => ({}));
Expand Down Expand Up @@ -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;
}
});
}
16 changes: 16 additions & 0 deletions 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,
});
}
9 changes: 9 additions & 0 deletions 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,
});
}
5 changes: 3 additions & 2 deletions test/background/message-ports-test.js
Expand Up @@ -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() => {
Expand Down
93 changes: 93 additions & 0 deletions 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"},
});
});
});
});
38 changes: 38 additions & 0 deletions 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"},
});
});
});

0 comments on commit ab58ea7

Please sign in to comment.