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}