Skip to content

Commit

Permalink
Fix background page analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
aopell committed Apr 18, 2024
1 parent 2db8cb7 commit c5bf1b1
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 75 deletions.
1 change: 1 addition & 0 deletions src/html/offscreen.html
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
<!doctype html>
<script src="lib/google-analytics.js"></script>
<script src="offscreen.js"></script>
52 changes: 41 additions & 11 deletions src/scripts/background.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "webext-dynamic-content-scripts";
import addDomainPermissionToggle from "webext-permission-toggle";

import { trackEvent } from "./utils/analytics";
import { getAnalyticsUserId } from "./utils/analytics";
import { DISCORD_URL, EXTENSION_NAME, EXTENSION_WEBSITE } from "./utils/constants";
import { getBrowser } from "./utils/dom";
import { Logger } from "./utils/logger";
Expand Down Expand Up @@ -56,7 +56,7 @@ async function onActionClicked() {
let badgeText = await chrome.action.getBadgeText({});
let n = Number.parseInt(badgeText);

trackEvent("button_click", {
trackAnalyticsEvent("button_click", {
id: "main-browser-action-button",
context: "Browser Action",
value: String(n || 0),
Expand All @@ -73,7 +73,7 @@ async function onActionClicked() {

function onNotificationClicked(id: string) {
Logger.log("Notification clicked");
trackEvent("perform_action", {
trackAnalyticsEvent("perform_action", {
id: "click",
context: "Notifications",
value: id,
Expand Down Expand Up @@ -110,7 +110,7 @@ async function onAlarm(alarm: chrome.alarms.Alarm) {
}

function onInstalled(details: chrome.runtime.InstalledDetails) {
trackEvent("perform_action", {
trackAnalyticsEvent("perform_action", {
id: "runtime_oninstalled",
value: details.reason,
context: "Versions",
Expand Down Expand Up @@ -242,13 +242,7 @@ async function checkForNotifications() {
// Do below only in Chrome

// Create an offscreen document if one doesn't exist yet
if (!(await hasOffscreenDocument())) {
await chrome.offscreen.createDocument({
url: OFFSCREEN_DOCUMENT_PATH,
reasons: [chrome.offscreen.Reason.DOM_PARSER],
justification: "Parse Schoology notifications, which are returned from the API as HTML",
});
}
await createOffscreenDocument();
// Now that we have an offscreen document, we can dispatch the
// message.
chrome.runtime.sendMessage({
Expand All @@ -260,6 +254,21 @@ async function checkForNotifications() {

declare var clients: { matchAll: () => Promise<{ url: string }[]> };

async function createOffscreenDocument() {
try {
if (!(await hasOffscreenDocument())) {
await chrome.offscreen.createDocument({
url: OFFSCREEN_DOCUMENT_PATH,
reasons: [chrome.offscreen.Reason.DOM_PARSER],
justification:
"Parse Schoology notifications, which are returned from the API as HTML",
});
}
} catch (e) {
Logger.warn("Error creating offscreen document, it probably already exists", e);
}
}

async function hasOffscreenDocument() {
// Check all windows controlled by the service worker if one of them is the offscreen document
const matchedClients = await clients.matchAll();
Expand All @@ -271,4 +280,25 @@ async function hasOffscreenDocument() {
return false;
}

async function trackAnalyticsEvent(name: string, props: any) {
await createOffscreenDocument();
let storageContents = await chrome.storage.sync.get(null);
chrome.runtime.sendMessage({
type: "offscreen-analytics",
target: "offscreen",
data: {
name,
props,
settings: {
analytics: storageContents.analytics,
theme: storageContents.theme,
beta: storageContents.beta,
version: chrome.runtime.getManifest().version,
newVersion: storageContents.newVersion,
randomUserId: getAnalyticsUserId(),
},
},
});
}

load();
17 changes: 15 additions & 2 deletions src/scripts/content.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as pages from "./pages";
import * as utils from "./utils";
import { initializeAnalytics } from "./utils/analytics";
import { getAnalyticsUserId, initializeAnalytics } from "./utils/analytics";
import { getBrowser } from "./utils/dom";
import { Setting, generateDebugInfo } from "./utils/settings";

declare global {
Expand Down Expand Up @@ -31,8 +32,20 @@ function ready() {
}

async function load() {
await initializeAnalytics();
await pages.all.preload();

await initializeAnalytics({
documentContext: true,
isAnalyticsEnabled:
getBrowser() !== "Firefox" && Setting.getValue<string>("analytics") === "enabled",
selectedTheme: Setting.getValue<string>("theme", "<unset>"),
selectedBeta: Setting.getValue<string>("beta", "<unset>"),
currentVersion: chrome.runtime.getManifest().version,
newVersion: Setting.getValue<string>("newVersion", "<unset>"),
randomUserId: await getAnalyticsUserId(),
themeIsModern: document.documentElement.getAttribute("modern") ?? "false",
});

await ready();
await pages.all.load();

Expand Down
22 changes: 22 additions & 0 deletions src/scripts/offscreen.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { initializeAnalytics, trackEvent } from "./utils/analytics";
import { getBrowser } from "./utils/dom";
import { loadAssignmentNotifications } from "./utils/notifications";

// Registering this listener when the script is first executed ensures that the
// offscreen document will be able to receive messages when the promise returned
// by `offscreen.createDocument()` resolves.
chrome.runtime.onMessage.addListener(handleMessages);
let analyticsIsEnabled = false;

// This function performs basic filtering and error checking on messages before
// dispatching the message to a more specific message handler.
Expand Down Expand Up @@ -33,6 +36,25 @@ async function handleMessages(
},
});
break;
case "offscreen-analytics":
if (!analyticsIsEnabled) {
await initializeAnalytics({
documentContext: false,
isAnalyticsEnabled:
getBrowser() !== "Firefox" &&
message.data.settings.analytics !== "disabled",
selectedTheme: message.data.settings.theme ?? "<unset>",
selectedBeta: message.data.settings.beta ?? "<unset>",
currentVersion: message.data.settings.version,
newVersion: message.data.settings.newVersion ?? "<unset>",
randomUserId: message.data.settings.randomUserId,
themeIsModern: "<unset>",
});
analyticsIsEnabled = true;
}

trackEvent(message.data.name, message.data.props);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
return false;
Expand Down
18 changes: 14 additions & 4 deletions src/scripts/theme-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import $ from "jquery";
import M from "materialize-css";
import "spectrum-colorpicker";

import { initializeAnalytics, trackEvent } from "./utils/analytics";
import { getAnalyticsUserId, initializeAnalytics, trackEvent } from "./utils/analytics";
import { DEFAULT_THEME_NAME } from "./utils/constants";
import { DEFAULT_ICONS } from "./utils/default-icons";
import { CLASSIC_THEMES, DEFAULT_THEMES, LAUSD_THEMES } from "./utils/default-themes";
import { DeepPartial, createElement, setCSSVariable } from "./utils/dom";
import { DeepPartial, createElement, getBrowser, setCSSVariable } from "./utils/dom";
import { Logger } from "./utils/logger";
import {
CustomColorDefinition,
Expand Down Expand Up @@ -49,9 +49,19 @@ declare global {
}

async function load() {
initializeAnalytics();

__storage = await chrome.storage.sync.get(null);

await initializeAnalytics({
documentContext: true,
isAnalyticsEnabled: getBrowser() !== "Firefox" && __storage.analytics !== "disabled",
selectedTheme: __storage.theme ?? "<unset>",
selectedBeta: __storage.beta ?? "<unset>",
currentVersion: chrome.runtime.getManifest().version,
newVersion: __storage.newVersion ?? "<unset>",
randomUserId: await getAnalyticsUserId(),
themeIsModern: document.documentElement.getAttribute("modern") ?? "false",
});

defaultDomain = __storage.defaultDomain || "app.schoology.com";

if (isLAUSD()) {
Expand Down
132 changes: 74 additions & 58 deletions src/scripts/utils/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export var trackEvent = function (
console.debug("[S+] Tracking disabled by user", arguments);
};

export async function initializeAnalytics() {
export async function getAnalyticsUserId() {
function getRandomToken() {
// E.g. 8 * 32 = 256 bits token
var randomPool = new Uint8Array(32);
Expand All @@ -47,31 +47,41 @@ export async function initializeAnalytics() {
return hex;
}

let s = await chrome.storage.sync.get({
analytics: getBrowser() === "Firefox" ? "disabled" : "enabled",
theme: "<unset>",
beta: "<unset>",
newVersion: "<unset>",
});

if (s.analytics === "enabled") {
let l = await chrome.storage.local.get({ randomUserId: null });

if (!l.randomUserId) {
let randomToken = getRandomToken();
await chrome.storage.local.set({ randomUserId: randomToken });
enableAnalytics(s.theme, s.beta, s.newVersion, randomToken);
} else {
enableAnalytics(s.theme, s.beta, s.newVersion, l.randomUserId);
}
let l: { randomUserId?: string } = await chrome.storage.local.get({ randomUserId: null });

if (!l.randomUserId) {
let randomUserId = getRandomToken();
await chrome.storage.local.set({ randomUserId });
return randomUserId;
}

return l.randomUserId;
}

export async function initializeAnalytics({
documentContext,
isAnalyticsEnabled,
selectedTheme,
selectedBeta,
currentVersion,
newVersion,
randomUserId,
themeIsModern,
}: {
documentContext: boolean;
isAnalyticsEnabled: boolean;
selectedTheme: string | null;
selectedBeta: string | null;
currentVersion: string;
newVersion: string;
randomUserId: string;
themeIsModern: string;
}) {
if (isAnalyticsEnabled) {
enableAnalytics();
}

function enableAnalytics(
selectedTheme: string,
beta: string,
newVersion: string,
randomUserId: string
) {
function enableAnalytics() {
// Google Analytics v4

(globalThis as any).dataLayer = (globalThis as any).dataLayer || [];
Expand All @@ -82,20 +92,22 @@ export async function initializeAnalytics() {

gtag("js", new Date());

gtag("config", "G-YM6B00RDYC", {
const gtagConfig = {
page_location: location.href.replace(/\/\d{3,}\b/g, "/*"),
page_path: location.pathname.replace(/\/\d{3,}\b/g, "/*"),
page_title: null,
user_id: randomUserId,
user_properties: {
extensionVersion: chrome.runtime.getManifest().version,
domain: location.host,
theme: selectedTheme,
modernTheme: document.documentElement.getAttribute("modern"),
activeBeta: beta,
activeBeta: selectedBeta,
lastEnabledVersion: newVersion,
extensionVersion: currentVersion,
domain: location.host,
modernTheme: themeIsModern,
},
});
};

gtag("config", "G-YM6B00RDYC", gtagConfig);

trackEvent = function (
eventName,
Expand Down Expand Up @@ -135,37 +147,41 @@ export async function initializeAnalytics() {
});
}

let trackedElements = new Set();
let observer = new MutationObserver((mutations, mutationObserver) => {
for (let elem of document.querySelectorAll(".splus-track-clicks:not(.splus-tracked)")) {
if (!trackedElements.has(elem)) {
elem.addEventListener("click", trackClick);
elem.addEventListener("auxclick", trackClick);
elem.classList.add("splus-tracked");
trackedElements.add(elem);
if (documentContext) {
let trackedElements = new Set();
let observer = new MutationObserver((mutations, mutationObserver) => {
for (let elem of document.querySelectorAll(
".splus-track-clicks:not(.splus-tracked)"
)) {
if (!trackedElements.has(elem)) {
elem.addEventListener("click", trackClick);
elem.addEventListener("auxclick", trackClick);
elem.classList.add("splus-tracked");
trackedElements.add(elem);
}
}
}
});

var readyStateCheckInterval = setInterval(function () {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
init();
}
}, 10);

function init() {
observer.observe(document.body, {
childList: true,
subtree: true,
});

for (let elem of document.querySelectorAll(".splus-track-clicks")) {
if (!trackedElements.has(elem)) {
elem.addEventListener("click", trackClick);
elem.addEventListener("auxclick", trackClick);
elem.classList.add("splus-tracked");
trackedElements.add(elem);
var readyStateCheckInterval = setInterval(function () {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
init();
}
}, 10);

function init() {
observer.observe(document.body, {
childList: true,
subtree: true,
});

for (let elem of document.querySelectorAll(".splus-track-clicks")) {
if (!trackedElements.has(elem)) {
elem.addEventListener("click", trackClick);
elem.addEventListener("auxclick", trackClick);
elem.classList.add("splus-tracked");
trackedElements.add(elem);
}
}
}
}
Expand Down

0 comments on commit c5bf1b1

Please sign in to comment.