aboutsummaryrefslogtreecommitdiff
path: root/ext/js/background
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-22 20:23:12 -0500
committerGitHub <noreply@github.com>2023-12-23 01:23:12 +0000
commit33886cf39bd8c128296834a6825992f8b8341fe7 (patch)
tree1d5e8245f1d372d8ba89f93e4d0c9787c50d4468 /ext/js/background
parentf320e2dd5d7fe7c0ada7ec9634bd060c73cc3986 (diff)
API type safety (#425)
* Improve API type safety * Update init * Update type names * Simplify init
Diffstat (limited to 'ext/js/background')
-rw-r--r--ext/js/background/backend.js103
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;