schoolbox web extension :)
1import type { Browser } from "#imports";
2import { browser, defineBackground, storage } from "#imports";
3import { logger } from "@/utils/logger";
4import { globalSettings, updated } from "@/utils/storage";
5import semver from "semver";
6
7export default defineBackground(() => {
8 browser.runtime.onInstalled.addListener(async ({ reason, previousVersion }) => {
9 if (reason === "install") {
10 logger.info("[background] Opening wiki page after install");
11 browser.tabs.create({ url: "https://schooltape.github.io/installed" });
12 if (import.meta.env.DEV) {
13 logger.info("[background] Opening development URLs");
14 browser.tabs.create({ url: "https://help.schoolbox.com.au/account/anonymous.php?" });
15 browser.tabs.create({ url: browser.runtime.getURL("/popup.html") });
16 }
17 } else if (reason === "update") {
18 logger.info("[background] Showing update badge");
19
20 await updated.set({ icon: true, changelog: true });
21 updateIcon();
22
23 const manifest = browser.runtime.getManifest();
24 const newVersion = manifest.version_name || manifest.version;
25
26 // hacky way of resetting the extension to fix migration issues
27 // new version is greater than or equal to v4.0.5 AND previous version was less than v4.0.5
28 if (previousVersion && semver.gte(newVersion, "4.0.5") && semver.lt(previousVersion, "4.0.5")) {
29 logger.info("[background] Clearing storage (v4.0.5 migration)");
30 await storage.clear("local");
31 }
32
33 if (import.meta.env.DEV) {
34 logger.info("[background] Opening development URLs");
35 browser.tabs.create({ url: browser.runtime.getURL("/popup.html"), active: false });
36 }
37 }
38 });
39
40 // update icon when toggle or update is changed
41 globalSettings.storage.watch(() => {
42 updateIcon();
43 });
44
45 // listen for messages
46 interface Message {
47 resetSettings?: boolean;
48 inject?: string;
49 toTab?: string;
50 updateIcon?: boolean;
51 }
52
53 // eslint-disable-next-line @typescript-eslint/no-explicit-any
54 browser.runtime.onMessage.addListener(async (msg: any, sender: any) => {
55 const message = msg as Message;
56 logger.child({ message, sender }).info("[background] Received message");
57
58 if (message.resetSettings) {
59 resetSettings();
60 } else if (message.toTab) {
61 const tabs = await browser.tabs.query({ url: message.toTab });
62 if (tabs.length > 0) {
63 // @ts-expect-error - tab will exist
64 browser.tabs.update(tabs[0].id, { active: true });
65 } else if (sender.tab?.id) {
66 browser.tabs.update(sender.tab.id, { url: message.toTab });
67 }
68 } else if (message.updateIcon) {
69 updateIcon();
70 }
71
72 return true; // return success
73 });
74
75 // context menus
76 let contexts: Browser.contextMenus.CreateProperties["contexts"];
77 logger.info(`[background] Manifest version: ${import.meta.env.MANIFEST_VERSION}`);
78 if (import.meta.env.MANIFEST_VERSION === 2) {
79 contexts = ["browser_action"];
80 } else {
81 contexts = ["action"];
82 }
83 browser.contextMenus.create({
84 id: "report-bug",
85 title: "Report a bug...",
86 contexts: contexts,
87 });
88 browser.contextMenus.create({
89 id: "feature-request",
90 title: "Request a feature...",
91 contexts: contexts,
92 });
93 browser.contextMenus.create({
94 id: "github",
95 title: "GitHub",
96 contexts: contexts,
97 });
98 browser.contextMenus.onClicked.addListener((info) => {
99 const manifest = browser.runtime.getManifest();
100 const version = manifest.version_name || manifest.version;
101
102 switch (info.menuItemId) {
103 case "report-bug":
104 browser.tabs.create({
105 url: `https://github.com/schooltape/schooltape/issues/new?template=bug.yml&version=v${version}`,
106 });
107 break;
108 case "feature-request":
109 browser.tabs.create({
110 url: "https://github.com/schooltape/schooltape/issues/new?template=feature.yml",
111 });
112 break;
113 case "github":
114 browser.tabs.create({ url: "https://github.com/schooltape/schooltape" });
115 break;
116 }
117 });
118});
119
120async function resetSettings(): Promise<void> {
121 logger.info("[background] Clearing local storage");
122 await storage.clear("local");
123}
124
125async function updateIcon() {
126 logger.info("[background] Updating icon...");
127
128 let iconSuffix = "";
129
130 // if it's june
131 if (new Date().getMonth() === 5) {
132 iconSuffix += "-ctp";
133 }
134 if ((await globalSettings.storage.getValue()).global === false) {
135 iconSuffix += "-disabled";
136 }
137 if ((await updated.storage.getValue()).icon === true) {
138 iconSuffix += "-badge";
139 }
140
141 if (import.meta.env.MANIFEST_VERSION === 2) {
142 browser.browserAction.setIcon({
143 path: {
144 16: `/icon/16${iconSuffix}.png`,
145 32: `/icon/32${iconSuffix}.png`,
146 48: `/icon/48${iconSuffix}.png`,
147 128: `/icon/128${iconSuffix}.png`,
148 },
149 });
150 } else {
151 browser.action.setIcon({
152 path: {
153 16: `/icon/16${iconSuffix}.png`,
154 32: `/icon/32${iconSuffix}.png`,
155 48: `/icon/48${iconSuffix}.png`,
156 128: `/icon/128${iconSuffix}.png`,
157 },
158 });
159 }
160}