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