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}