diff --git a/src/html/offscreen.html b/src/html/offscreen.html
index 177e040d..a07f4aaa 100644
--- a/src/html/offscreen.html
+++ b/src/html/offscreen.html
@@ -1,2 +1,3 @@
+
\ No newline at end of file
diff --git a/src/scripts/background.ts b/src/scripts/background.ts
index 22ebd5ad..df155696 100644
--- a/src/scripts/background.ts
+++ b/src/scripts/background.ts
@@ -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";
@@ -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),
@@ -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,
@@ -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",
@@ -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({
@@ -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();
@@ -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();
diff --git a/src/scripts/content.ts b/src/scripts/content.ts
index 91fbfa32..3f97b079 100644
--- a/src/scripts/content.ts
+++ b/src/scripts/content.ts
@@ -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 {
@@ -31,8 +32,20 @@ function ready() {
}
async function load() {
- await initializeAnalytics();
await pages.all.preload();
+
+ await initializeAnalytics({
+ documentContext: true,
+ isAnalyticsEnabled:
+ getBrowser() !== "Firefox" && Setting.getValue("analytics") === "enabled",
+ selectedTheme: Setting.getValue("theme", ""),
+ selectedBeta: Setting.getValue("beta", ""),
+ currentVersion: chrome.runtime.getManifest().version,
+ newVersion: Setting.getValue("newVersion", ""),
+ randomUserId: await getAnalyticsUserId(),
+ themeIsModern: document.documentElement.getAttribute("modern") ?? "false",
+ });
+
await ready();
await pages.all.load();
diff --git a/src/scripts/offscreen.ts b/src/scripts/offscreen.ts
index ad39444a..51db83b9 100644
--- a/src/scripts/offscreen.ts
+++ b/src/scripts/offscreen.ts
@@ -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.
@@ -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 ?? "",
+ selectedBeta: message.data.settings.beta ?? "",
+ currentVersion: message.data.settings.version,
+ newVersion: message.data.settings.newVersion ?? "",
+ randomUserId: message.data.settings.randomUserId,
+ themeIsModern: "",
+ });
+ analyticsIsEnabled = true;
+ }
+
+ trackEvent(message.data.name, message.data.props);
+ break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
return false;
diff --git a/src/scripts/theme-editor.ts b/src/scripts/theme-editor.ts
index 04e64798..d8c28bc5 100644
--- a/src/scripts/theme-editor.ts
+++ b/src/scripts/theme-editor.ts
@@ -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,
@@ -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 ?? "",
+ selectedBeta: __storage.beta ?? "",
+ currentVersion: chrome.runtime.getManifest().version,
+ newVersion: __storage.newVersion ?? "",
+ randomUserId: await getAnalyticsUserId(),
+ themeIsModern: document.documentElement.getAttribute("modern") ?? "false",
+ });
+
defaultDomain = __storage.defaultDomain || "app.schoology.com";
if (isLAUSD()) {
diff --git a/src/scripts/utils/analytics.ts b/src/scripts/utils/analytics.ts
index 460011e4..82ef5cd9 100644
--- a/src/scripts/utils/analytics.ts
+++ b/src/scripts/utils/analytics.ts
@@ -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);
@@ -47,31 +47,41 @@ export async function initializeAnalytics() {
return hex;
}
- let s = await chrome.storage.sync.get({
- analytics: getBrowser() === "Firefox" ? "disabled" : "enabled",
- theme: "",
- beta: "",
- newVersion: "",
- });
-
- 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 || [];
@@ -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,
@@ -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);
+ }
}
}
}