diff options
Diffstat (limited to 'ext/js/background')
| -rw-r--r-- | ext/js/background/backend.js | 103 | 
1 files changed, 51 insertions, 52 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index c62685b2..f7cad1e6 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -22,7 +22,8 @@ import {AnkiConnect} from '../comm/anki-connect.js';  import {ClipboardMonitor} from '../comm/clipboard-monitor.js';  import {ClipboardReader} from '../comm/clipboard-reader.js';  import {Mecab} from '../comm/mecab.js'; -import {clone, deferPromise, invokeMessageHandler, isObject, log, promiseTimeout} from '../core.js'; +import {clone, deferPromise, isObject, log, promiseTimeout} from '../core.js'; +import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';  import {ExtensionError} from '../core/extension-error.js';  import {readResponseJson} from '../core/json.js';  import {AnkiUtil} from '../data/anki-util.js'; @@ -146,8 +147,8 @@ export class Backend {          this._permissionsUtil = new PermissionsUtil();          /* eslint-disable no-multi-spaces */ -        /** @type {import('core').MessageHandlerMap} */ -        this._messageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ +        /** @type {import('api').ApiMap} */ +        this._apiMap = createApiMap([              ['requestBackendReadySignal',    this._onApiRequestBackendReadySignal.bind(this)],              ['optionsGet',                   this._onApiOptionsGet.bind(this)],              ['optionsGetFull',               this._onApiOptionsGetFull.bind(this)], @@ -190,7 +191,7 @@ export class Backend {              ['findAnkiNotes',                this._onApiFindAnkiNotes.bind(this)],              ['loadExtensionScripts',         this._onApiLoadExtensionScripts.bind(this)],              ['openCrossFramePort',           this._onApiOpenCrossFramePort.bind(this)] -        ])); +        ]);          /* eslint-enable no-multi-spaces */          /** @type {Map<string, (params?: import('core').SerializableObject) => void>} */ @@ -369,7 +370,7 @@ export class Backend {          });      } -    /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ +    /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('api').MessageAny>} */      _onMessageWrapper(message, sender, sendResponse) {          if (this._isPrepared) {              return this._onMessage(message, sender, sendResponse); @@ -392,15 +393,13 @@ export class Backend {      }      /** -     * @param {{action: string, params?: import('core').SerializableObject}} message +     * @param {import('api').MessageAny} message       * @param {chrome.runtime.MessageSender} sender       * @param {(response?: unknown) => void} callback       * @returns {boolean}       */      _onMessage({action, params}, sender, callback) { -        const messageHandler = this._messageHandlers.get(action); -        if (typeof messageHandler === 'undefined') { return false; } -        return invokeMessageHandler(messageHandler, params, callback, sender); +        return invokeApiMapHandler(this._apiMap, action, params, [sender], callback);      }      /** @@ -427,7 +426,7 @@ export class Backend {      // Message handlers -    /** @type {import('api').Handler<import('api').RequestBackendReadySignalDetails, import('api').RequestBackendReadySignalResult, true>} */ +    /** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */      _onApiRequestBackendReadySignal(_params, sender) {          // tab ID isn't set in background (e.g. browser_action)          const data = {action: 'Yomitan.backendReady', params: {}}; @@ -443,17 +442,17 @@ export class Backend {          }      } -    /** @type {import('api').Handler<import('api').OptionsGetDetails, import('api').OptionsGetResult>} */ +    /** @type {import('api').ApiHandler<'optionsGet'>} */      _onApiOptionsGet({optionsContext}) {          return this._getProfileOptions(optionsContext, false);      } -    /** @type {import('api').Handler<import('api').OptionsGetFullDetails, import('api').OptionsGetFullResult>} */ +    /** @type {import('api').ApiHandler<'optionsGetFull'>} */      _onApiOptionsGetFull() {          return this._getOptionsFull(false);      } -    /** @type {import('api').Handler<import('api').KanjiFindDetails, import('api').KanjiFindResult>} */ +    /** @type {import('api').ApiHandler<'kanjiFind'>} */      async _onApiKanjiFind({text, optionsContext}) {          const options = this._getProfileOptions(optionsContext, false);          const {general: {maxResults}} = options; @@ -463,7 +462,7 @@ export class Backend {          return dictionaryEntries;      } -    /** @type {import('api').Handler<import('api').TermsFindDetails, import('api').TermsFindResult>} */ +    /** @type {import('api').ApiHandler<'termsFind'>} */      async _onApiTermsFind({text, details, optionsContext}) {          const options = this._getProfileOptions(optionsContext, false);          const {general: {resultOutputMode: mode, maxResults}} = options; @@ -473,7 +472,7 @@ export class Backend {          return {dictionaryEntries, originalTextLength};      } -    /** @type {import('api').Handler<import('api').ParseTextDetails, import('api').ParseTextResult>} */ +    /** @type {import('api').ApiHandler<'parseText'>} */      async _onApiParseText({text, optionsContext, scanLength, useInternalParser, useMecabParser}) {          const [internalResults, mecabResults] = await Promise.all([              (useInternalParser ? this._textParseScanning(text, scanLength, optionsContext) : null), @@ -506,22 +505,22 @@ export class Backend {          return results;      } -    /** @type {import('api').Handler<import('api').GetAnkiConnectVersionDetails, import('api').GetAnkiConnectVersionResult>} */ +    /** @type {import('api').ApiHandler<'getAnkiConnectVersion'>} */      async _onApiGetAnkiConnectVersion() {          return await this._anki.getVersion();      } -    /** @type {import('api').Handler<import('api').IsAnkiConnectedDetails, import('api').IsAnkiConnectedResult>} */ +    /** @type {import('api').ApiHandler<'isAnkiConnected'>} */      async _onApiIsAnkiConnected() {          return await this._anki.isConnected();      } -    /** @type {import('api').Handler<import('api').AddAnkiNoteDetails, import('api').AddAnkiNoteResult>} */ +    /** @type {import('api').ApiHandler<'addAnkiNote'>} */      async _onApiAddAnkiNote({note}) {          return await this._anki.addNote(note);      } -    /** @type {import('api').Handler<import('api').GetAnkiNoteInfoDetails, import('api').GetAnkiNoteInfoResult>} */ +    /** @type {import('api').ApiHandler<'getAnkiNoteInfo'>} */      async _onApiGetAnkiNoteInfo({notes, fetchAdditionalInfo}) {          /** @type {import('anki').NoteInfoWrapper[]} */          const results = []; @@ -558,7 +557,7 @@ export class Backend {          return results;      } -    /** @type {import('api').Handler<import('api').InjectAnkiNoteMediaDetails, import('api').InjectAnkiNoteMediaResult>} */ +    /** @type {import('api').ApiHandler<'injectAnkiNoteMedia'>} */      async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) {          return await this._injectAnkNoteMedia(              this._anki, @@ -571,7 +570,7 @@ export class Backend {          );      } -    /** @type {import('api').Handler<import('api').NoteViewDetails, import('api').NoteViewResult>} */ +    /** @type {import('api').ApiHandler<'noteView'>} */      async _onApiNoteView({noteId, mode, allowFallback}) {          if (mode === 'edit') {              try { @@ -590,7 +589,7 @@ export class Backend {          return 'browse';      } -    /** @type {import('api').Handler<import('api').SuspendAnkiCardsForNoteDetails, import('api').SuspendAnkiCardsForNoteResult>} */ +    /** @type {import('api').ApiHandler<'suspendAnkiCardsForNote'>} */      async _onApiSuspendAnkiCardsForNote({noteId}) {          const cardIds = await this._anki.findCardsForNote(noteId);          const count = cardIds.length; @@ -601,17 +600,17 @@ export class Backend {          return count;      } -    /** @type {import('api').Handler<import('api').CommandExecDetails, import('api').CommandExecResult>} */ +    /** @type {import('api').ApiHandler<'commandExec'>} */      _onApiCommandExec({command, params}) {          return this._runCommand(command, params);      } -    /** @type {import('api').Handler<import('api').GetTermAudioInfoListDetails, import('api').GetTermAudioInfoListResult>} */ +    /** @type {import('api').ApiHandler<'getTermAudioInfoList'>} */      async _onApiGetTermAudioInfoList({source, term, reading}) {          return await this._audioDownloader.getTermAudioInfoList(source, term, reading);      } -    /** @type {import('api').Handler<import('api').SendMessageToFrameDetails, import('api').SendMessageToFrameResult, true>} */ +    /** @type {import('api').ApiHandler<'sendMessageToFrame'>} */      _onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {          if (!sender) { return false; }          const {tab} = sender; @@ -625,7 +624,7 @@ export class Backend {          return true;      } -    /** @type {import('api').Handler<import('api').BroadcastTabDetails, import('api').BroadcastTabResult, true>} */ +    /** @type {import('api').ApiHandler<'broadcastTab'>} */      _onApiBroadcastTab({action, params}, sender) {          if (!sender) { return false; }          const {tab} = sender; @@ -639,7 +638,7 @@ export class Backend {          return true;      } -    /** @type {import('api').Handler<import('api').FrameInformationGetDetails, import('api').FrameInformationGetResult, true>} */ +    /** @type {import('api').ApiHandler<'frameInformationGet'>} */      _onApiFrameInformationGet(_params, sender) {          const tab = sender.tab;          const tabId = tab ? tab.id : void 0; @@ -647,14 +646,14 @@ export class Backend {          return Promise.resolve({tabId, frameId});      } -    /** @type {import('api').Handler<import('api').InjectStylesheetDetails, import('api').InjectStylesheetResult, true>} */ +    /** @type {import('api').ApiHandler<'injectStylesheet'>} */      async _onApiInjectStylesheet({type, value}, sender) {          const {frameId, tab} = sender;          if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); }          return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false);      } -    /** @type {import('api').Handler<import('api').GetStylesheetContentDetails, import('api').GetStylesheetContentResult>} */ +    /** @type {import('api').ApiHandler<'getStylesheetContent'>} */      async _onApiGetStylesheetContent({url}) {          if (!url.startsWith('/') || url.startsWith('//') || !url.endsWith('.css')) {              throw new Error('Invalid URL'); @@ -662,22 +661,22 @@ export class Backend {          return await this._fetchText(url);      } -    /** @type {import('api').Handler<import('api').GetEnvironmentInfoDetails, import('api').GetEnvironmentInfoResult>} */ +    /** @type {import('api').ApiHandler<'getEnvironmentInfo'>} */      _onApiGetEnvironmentInfo() {          return this._environment.getInfo();      } -    /** @type {import('api').Handler<import('api').ClipboardGetDetails, import('api').ClipboardGetResult>} */ +    /** @type {import('api').ApiHandler<'clipboardGet'>} */      async _onApiClipboardGet() {          return this._clipboardReader.getText(false);      } -    /** @type {import('api').Handler<import('api').GetDisplayTemplatesHtmlDetails, import('api').GetDisplayTemplatesHtmlResult>} */ +    /** @type {import('api').ApiHandler<'getDisplayTemplatesHtml'>} */      async _onApiGetDisplayTemplatesHtml() {          return await this._fetchText('/display-templates.html');      } -    /** @type {import('api').Handler<import('api').GetZoomDetails, import('api').GetZoomResult, true>} */ +    /** @type {import('api').ApiHandler<'getZoom'>} */      _onApiGetZoom(_params, sender) {          return new Promise((resolve, reject) => {              if (!sender || !sender.tab) { @@ -707,45 +706,45 @@ export class Backend {          });      } -    /** @type {import('api').Handler<import('api').GetDefaultAnkiFieldTemplatesDetails, import('api').GetDefaultAnkiFieldTemplatesResult>} */ +    /** @type {import('api').ApiHandler<'getDefaultAnkiFieldTemplates'>} */      _onApiGetDefaultAnkiFieldTemplates() {          return /** @type {string} */ (this._defaultAnkiFieldTemplates);      } -    /** @type {import('api').Handler<import('api').GetDictionaryInfoDetails, import('api').GetDictionaryInfoResult>} */ +    /** @type {import('api').ApiHandler<'getDictionaryInfo'>} */      async _onApiGetDictionaryInfo() {          return await this._dictionaryDatabase.getDictionaryInfo();      } -    /** @type {import('api').Handler<import('api').PurgeDatabaseDetails, import('api').PurgeDatabaseResult>} */ +    /** @type {import('api').ApiHandler<'purgeDatabase'>} */      async _onApiPurgeDatabase() {          await this._dictionaryDatabase.purge();          this._triggerDatabaseUpdated('dictionary', 'purge');      } -    /** @type {import('api').Handler<import('api').GetMediaDetails, import('api').GetMediaResult>} */ +    /** @type {import('api').ApiHandler<'getMedia'>} */      async _onApiGetMedia({targets}) {          return await this._getNormalizedDictionaryDatabaseMedia(targets);      } -    /** @type {import('api').Handler<import('api').LogDetails, import('api').LogResult>} */ +    /** @type {import('api').ApiHandler<'log'>} */      _onApiLog({error, level, context}) {          log.log(ExtensionError.deserialize(error), level, context);      } -    /** @type {import('api').Handler<import('api').LogIndicatorClearDetails, import('api').LogIndicatorClearResult>} */ +    /** @type {import('api').ApiHandler<'logIndicatorClear'>} */      _onApiLogIndicatorClear() {          if (this._logErrorLevel === null) { return; }          this._logErrorLevel = null;          this._updateBadge();      } -    /** @type {import('api').Handler<import('api').ModifySettingsDetails, import('api').ModifySettingsResult>} */ +    /** @type {import('api').ApiHandler<'modifySettings'>} */      _onApiModifySettings({targets, source}) {          return this._modifySettings(targets, source);      } -    /** @type {import('api').Handler<import('api').GetSettingsDetails, import('api').GetSettingsResult>} */ +    /** @type {import('api').ApiHandler<'getSettings'>} */      _onApiGetSettings({targets}) {          const results = [];          for (const target of targets) { @@ -759,14 +758,14 @@ export class Backend {          return results;      } -    /** @type {import('api').Handler<import('api').SetAllSettingsDetails, import('api').SetAllSettingsResult>} */ +    /** @type {import('api').ApiHandler<'setAllSettings'>} */      async _onApiSetAllSettings({value, source}) {          this._optionsUtil.validate(value);          this._options = clone(value);          await this._saveOptions(source);      } -    /** @type {import('api').Handler<import('api').GetOrCreateSearchPopupDetails, import('api').GetOrCreateSearchPopupResult>} */ +    /** @type {import('api').ApiHandlerNoExtraArgs<'getOrCreateSearchPopup'>} */      async _onApiGetOrCreateSearchPopup({focus = false, text}) {          const {tab, created} = await this._getOrCreateSearchPopupWrapper();          if (focus === true || (focus === 'ifCreated' && created)) { @@ -782,19 +781,19 @@ export class Backend {          return {tabId: typeof id === 'number' ? id : null, windowId: tab.windowId};      } -    /** @type {import('api').Handler<import('api').IsTabSearchPopupDetails, import('api').IsTabSearchPopupResult>} */ +    /** @type {import('api').ApiHandler<'isTabSearchPopup'>} */      async _onApiIsTabSearchPopup({tabId}) {          const baseUrl = chrome.runtime.getURL('/search.html');          const tab = typeof tabId === 'number' ? await this._checkTabUrl(tabId, (url) => url !== null && url.startsWith(baseUrl)) : null;          return (tab !== null);      } -    /** @type {import('api').Handler<import('api').TriggerDatabaseUpdatedDetails, import('api').TriggerDatabaseUpdatedResult>} */ +    /** @type {import('api').ApiHandler<'triggerDatabaseUpdated'>} */      _onApiTriggerDatabaseUpdated({type, cause}) {          this._triggerDatabaseUpdated(type, cause);      } -    /** @type {import('api').Handler<import('api').TestMecabDetails, import('api').TestMecabResult>} */ +    /** @type {import('api').ApiHandler<'testMecab'>} */      async _onApiTestMecab() {          if (!this._mecab.isEnabled()) {              throw new Error('MeCab not enabled'); @@ -831,22 +830,22 @@ export class Backend {          return true;      } -    /** @type {import('api').Handler<import('api').TextHasJapaneseCharactersDetails, import('api').TextHasJapaneseCharactersResult>} */ +    /** @type {import('api').ApiHandler<'textHasJapaneseCharacters'>} */      _onApiTextHasJapaneseCharacters({text}) {          return this._japaneseUtil.isStringPartiallyJapanese(text);      } -    /** @type {import('api').Handler<import('api').GetTermFrequenciesDetails, import('api').GetTermFrequenciesResult>} */ +    /** @type {import('api').ApiHandler<'getTermFrequencies'>} */      async _onApiGetTermFrequencies({termReadingList, dictionaries}) {          return await this._translator.getTermFrequencies(termReadingList, dictionaries);      } -    /** @type {import('api').Handler<import('api').FindAnkiNotesDetails, import('api').FindAnkiNotesResult>} */ +    /** @type {import('api').ApiHandler<'findAnkiNotes'>} */      async _onApiFindAnkiNotes({query}) {          return await this._anki.findNotes(query);      } -    /** @type {import('api').Handler<import('api').LoadExtensionScriptsDetails, import('api').LoadExtensionScriptsResult, true>} */ +    /** @type {import('api').ApiHandler<'loadExtensionScripts'>} */      async _onApiLoadExtensionScripts({files}, sender) {          if (!sender || !sender.tab) { throw new Error('Invalid sender'); }          const tabId = sender.tab.id; @@ -857,7 +856,7 @@ export class Backend {          }      } -    /** @type {import('api').Handler<import('api').OpenCrossFramePortDetails, import('api').OpenCrossFramePortResult, true>} */ +    /** @type {import('api').ApiHandler<'openCrossFramePort'>} */      _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) {          const sourceTabId = (sender && sender.tab ? sender.tab.id : null);          if (typeof sourceTabId !== 'number') { @@ -2062,7 +2061,7 @@ export class Backend {       * @param {?import('api').InjectAnkiNoteMediaScreenshotDetails} screenshotDetails       * @param {?import('api').InjectAnkiNoteMediaClipboardDetails} clipboardDetails       * @param {import('api').InjectAnkiNoteMediaDictionaryMediaDetails[]} dictionaryMediaDetails -     * @returns {Promise<import('api').InjectAnkiNoteMediaResult>} +     * @returns {Promise<import('api').ApiReturn<'injectAnkiNoteMedia'>>}       */      async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {          let screenshotFileName = null;  |