diff --git a/src/background/background.ts b/src/background/background.ts index 02aa096..27b4c8f 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1,20 +1,21 @@ import { browser, Runtime } from "webextension-polyfill-ts"; import { GTranslateScraper } from "./gtranslate_scraper"; import { Flashcards } from "./database"; -import { Communicator, commandKinds } from "../communication"; +import { BackgroundMessenger } from "../background_frontend_commands"; import content_script from "../frontend/content_script/content_script.ts?file"; -let com = new Communicator(); +let con = new BackgroundMessenger(); const scraper = new GTranslateScraper(); -com.translateCallback = async (toTranslate, sender) => - scraper.translate(toTranslate, await getCurrentLanguages(), sender.tab?.id); +con.addMessageListener("translate", async (toTranslate, sender) => + scraper.translate(toTranslate, await getCurrentLanguages(), sender.tab?.id) +); -com.getLanguagesCallback = () => scraper.languages; +con.addMessageListener("getLanguages", () => scraper.languages); const db = new Flashcards(); -com.addFlashcardCallback = (c) => db.addFlashcard(c); -com.removeFlashcardCallback = (c) => db.removeFlashcard(c); +con.addMessageListener("addFlashcard", (c) => db.addFlashcard(c)); +con.addMessageListener("removeFlashcard", (c) => db.removeFlashcard(c)); const getCurrentLanguages = async () => { const langs = await scraper.languages; @@ -33,8 +34,8 @@ const setCurrentLanguages = async (l: Partial) => { if (l.dstLang) await browser.storage.local.set({ dstLang: l.dstLang.code }); }; -com.getCurrentLanguagesCallback = getCurrentLanguages; -com.setCurrentLanguagesCallback = setCurrentLanguages; +con.addMessageListener("getCurrentLanguages", getCurrentLanguages); +con.addMessageListener("setCurrentLanguages", setCurrentLanguages); const getTabID = async (s: Runtime.MessageSender): Promise => { if (s.tab?.id) return s.tab.id; @@ -56,24 +57,24 @@ const injectScript = async (tabID: number, enabled?: boolean) => { file: content_script, }); if (enabled === undefined) enabled = await isEnabledSession(tabID); - browser.tabs.sendMessage(tabID, { - commandKind: commandKinds.setEnabled, - value: enabled, - }); + con.runCommand("setEnabled", enabled, tabID); }; -com.setEnabledCallback = async (v: boolean, s: Runtime.MessageSender) => { - const tab = await getTabID(s); - if ((await isEnabledSession(tab)) == v) return; +con.addMessageListener( + "setEnabled", + async (v: boolean, s: Runtime.MessageSender) => { + const tab = await getTabID(s); + if ((await isEnabledSession(tab)) == v) return; - injectScript(tab); - await setEnabledSession(tab, v); -}; + injectScript(tab); + await setEnabledSession(tab, v); + } +); -com.getEnabledCallback = async (s: Runtime.MessageSender): Promise => { +con.addMessageListener("getEnabled", async (s: Runtime.MessageSender) => { const tab = await getTabID(s); return isEnabledSession(tab); -}; +}); browser.tabs.onUpdated.addListener(async (tabID, changeInfo) => { if (changeInfo.status == "complete") { diff --git a/src/background_frontend_commands.ts b/src/background_frontend_commands.ts new file mode 100644 index 0000000..750e233 --- /dev/null +++ b/src/background_frontend_commands.ts @@ -0,0 +1,77 @@ +import { Communicator, commandFunction, commandList } from "./communicator"; +import { browser } from "webextension-polyfill-ts"; + +export interface commands extends commandList { + setEnabled: setEnabled; + getEnabled: getEnabled; + translate: translate; + addFlashcard: addFlashcard; + removeFlashcard: removeFlashcard; + getLanguages: getLanguages; + getCurrentLanguages: getCurrentLanguages; + setCurrentLanguages: setCurrentLanguages; +} + +interface setEnabled extends commandFunction { + args: boolean; +} + +interface getEnabled extends commandFunction { + return: boolean; +} + +interface getLanguages extends commandFunction { + return: Array; +} + +interface translate extends commandFunction { + args: string; + return: Translation; +} + +interface getCurrentLanguages extends commandFunction { + return: LanguagePair; +} + +interface addFlashcard extends commandFunction { + args: Translation | Flashcard; + return: Flashcard; +} + +interface removeFlashcard extends commandFunction { + args: Flashcard; +} + +interface setCurrentLanguages extends commandFunction { + args: Partial; +} + +export class BackgroundMessenger extends Communicator { + constructor() { + super(); + browser.runtime.onMessage.addListener((m, s) => this.onMessage(m, s)); + } + runCommand( + command: K, + args: commands[K]["args"], + tabID: number + ) { + const msg = this.getCommandMessage(command, args); + return browser.tabs.sendMessage(tabID, msg); + } +} + +export class FrontendMessenger extends Communicator { + constructor() { + super(); + browser.runtime.onMessage.addListener((m, s) => this.onMessage(m, s)); + } + + runCommand( + command: K, + args: commands[K]["args"] + ): commands[K]["return"] { + const message = this.getCommandMessage(command, args); + return browser.runtime.sendMessage(message); + } +} diff --git a/src/communication.ts b/src/communication.ts deleted file mode 100644 index 6d5170d..0000000 --- a/src/communication.ts +++ /dev/null @@ -1,138 +0,0 @@ -//There has to be a better way to do this while remaining type safe... -import { browser, Runtime } from "webextension-polyfill-ts"; - -export class Communicator { - setEnabledCallback?: (value: boolean, sender: Runtime.MessageSender) => void; - getEnabledCallback?: (sender: Runtime.MessageSender) => Promise; - translateCallback?: ( - toTranslate: string, - sender: Runtime.MessageSender - ) => Promise; - addFlashcardCallback?: ( - value: Translation | Flashcard, - sender: Runtime.MessageSender - ) => Promise; - removeFlashcardCallback?: ( - value: Flashcard, - sender: Runtime.MessageSender - ) => Promise; - getLanguagesCallback?: ( - sender: Runtime.MessageSender - ) => Promise>; - getCurrentLanguagesCallback?: ( - sender: Runtime.MessageSender - ) => Promise; - setCurrentLanguagesCallback?: ( - value: Partial, - sender: Runtime.MessageSender - ) => void; - - constructor() { - browser.runtime.onMessage.addListener( - (c: command, s: Runtime.MessageSender) => { - switch (c.commandKind) { - case commandKinds.setEnabled: - return this.setEnabledCallback!(c.value, s); - case commandKinds.getEnabled: - return this.getEnabledCallback!(s); - case commandKinds.translate: - return this.translateCallback!(c.toTranslate, s); - case commandKinds.addFlashcard: - return this.addFlashcardCallback!(c.card, s); - case commandKinds.removeFlashcard: - return this.removeFlashcardCallback!(c.card, s); - case commandKinds.getLanguages: - return this.getLanguagesCallback!(s); - case commandKinds.getCurrentLanguages: - return this.getCurrentLanguagesCallback!(s); - case commandKinds.setCurrentLanguages: - return this.setCurrentLanguagesCallback!(c.value, s); - default: - console.warn(`Unimplemented command ${c}`); - } - } - ); - } - - static setEnabled = (v: boolean) => - sendMessage({ commandKind: commandKinds.setEnabled, value: v }); - static getEnabled = (): Promise => - sendMessage({ commandKind: commandKinds.getEnabled }); - static translate = (toTranslate: string): Promise => - sendMessage({ - commandKind: commandKinds.translate, - toTranslate: toTranslate, - }); - static addFlashcard = (card: Flashcard | Translation): Promise => - sendMessage({ - commandKind: commandKinds.addFlashcard, - card: card, - }); - static removeFlashcard = (card: Flashcard) => - sendMessage({ - commandKind: commandKinds.removeFlashcard, - card: card, - }); - static getLanguages = (): Promise> => - sendMessage({ - commandKind: commandKinds.getLanguages, - }); - static getCurrentLanguages = (): Promise => - sendMessage({ - commandKind: commandKinds.getCurrentLanguages, - }); - static setCurrentLanguages = (v: Partial) => - sendMessage({ commandKind: commandKinds.setCurrentLanguages, value: v }); -} - -const sendMessage = (m: command) => browser.runtime.sendMessage(m); - -export enum commandKinds { - setEnabled = "setEnabled", - getEnabled = "getEnabled", - translate = "translate", - addFlashcard = "addFlashcard", - removeFlashcard = "removeFlashcard", - getLanguages = "getLanguages", - getCurrentLanguages = "getCurrentLanguage", - setCurrentLanguages = "setCurrentLanguages", -} -interface setEnabled { - commandKind: commandKinds.setEnabled; - value: boolean; -} - -interface getCommand { - commandKind: - | commandKinds.getEnabled - | commandKinds.getLanguages - | commandKinds.getCurrentLanguages; -} - -interface translateCommand { - commandKind: commandKinds.translate; - toTranslate: string; -} - -interface addFlashcard { - commandKind: commandKinds.addFlashcard; - card: Translation | Flashcard; -} - -interface removeFlashcard { - commandKind: commandKinds.removeFlashcard; - card: Flashcard; -} - -interface setCurrentLanguages { - commandKind: commandKinds.setCurrentLanguages; - value: Partial; -} - -export type command = - | setEnabled - | getCommand - | translateCommand - | addFlashcard - | removeFlashcard - | setCurrentLanguages; diff --git a/src/communicator.ts b/src/communicator.ts new file mode 100644 index 0000000..9ad68ab --- /dev/null +++ b/src/communicator.ts @@ -0,0 +1,52 @@ +import type { Runtime } from "webextension-polyfill-ts"; + +export interface commandFunction { + args: any; + return: any; +} + +export interface commandList { + [functionName: string]: commandFunction; +} + +interface commandMessage { + name: K; + args: T[K]["args"]; +} + +declare type listener = ( + args: T[K]["args"], + s: Runtime.MessageSender +) => Promise; + +export abstract class Communicator { + private listeners = new Map>(); + + abstract runCommand( + command: K, + args: T[K]["args"], + ...rest: any[] + ): Promise; + + addMessageListener(command: K, callback: listener) { + this.listeners.set(command, callback); + } + + getCommandMessage( + command: K, + ...args: T[K]["args"] + ): commandMessage { + return { name: command, args: args }; + } + + onMessage( + m: commandMessage, + s: Runtime.MessageSender + ) { + let listener = this.listeners.get(m.name); + let args: [T[keyof T]["args"], Runtime.MessageSender] = m.args; + if (args[0] === undefined) args.pop(); + args.push(s); + if (listener) return listener.apply(null, args); + } +} diff --git a/src/frontend/content_script/Translate.svelte b/src/frontend/content_script/Translate.svelte index 19fe1eb..a71fedb 100644 --- a/src/frontend/content_script/Translate.svelte +++ b/src/frontend/content_script/Translate.svelte @@ -1,19 +1,23 @@ @@ -21,4 +25,6 @@ {:then trans} +{:catch} + Error {/await} diff --git a/src/frontend/content_script/Translated.svelte b/src/frontend/content_script/Translated.svelte index df8aed5..f875e6c 100644 --- a/src/frontend/content_script/Translated.svelte +++ b/src/frontend/content_script/Translated.svelte @@ -1,15 +1,17 @@ diff --git a/src/frontend/content_script/content_script.ts b/src/frontend/content_script/content_script.ts index 5f80bce..3752830 100644 --- a/src/frontend/content_script/content_script.ts +++ b/src/frontend/content_script/content_script.ts @@ -1,13 +1,15 @@ -import { Communicator } from "../../communication"; import tippy, { roundArrow } from "tippy.js"; import App from "./Translate.svelte"; import "./tippy.scss"; +import { FrontendMessenger } from "../../background_frontend_commands"; (() => { //Make sure our script only runs once if (window.hasRun) return; window.hasRun = true; + let con = new FrontendMessenger(); + const tippyInstance = setupTippy(); let timeoutID: number; @@ -18,20 +20,19 @@ import "./tippy.scss"; timeoutID = window.setTimeout(tippyInstance.show, 500); }; - let con = new Communicator(); - con.setEnabledCallback = (v: boolean) => { + con.addMessageListener("setEnabled", async (v: boolean) => { document.removeEventListener("selectionchange", onSelectionChange); //Always remove it avoid duplicate event listeners if (v) { onSelectionChange(); // Call it since selection may have changed since it was enabled document.addEventListener("selectionchange", onSelectionChange); } else tippyInstance.hide(); - }; + }); function setupTippy() { const svelteElement = document.createElement("div"); const sveleteApp = new App({ target: svelteElement, - props: { toTranslate: "" }, + props: { toTranslate: "", connection: con }, }); return tippy(document.body, { content: svelteElement, diff --git a/src/frontend/popup/Popup.svelte b/src/frontend/popup/Popup.svelte index 3b4361b..8717cfc 100644 --- a/src/frontend/popup/Popup.svelte +++ b/src/frontend/popup/Popup.svelte @@ -1,17 +1,18 @@