aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-21 09:46:50 -0500
committerGitHub <noreply@github.com>2023-12-21 14:46:50 +0000
commitab847b124d418b13037b59f446b288ff435e66a4 (patch)
treee481d9abb8a3a5b7887cfc570b3d3df85f124d14
parentb83ca2f37d6bb1007f62216cebf96a1177e556dc (diff)
API maps (#413)
* Add API map type descriptions * Remove unused ApiMapInitLax * Add createApiMap function * Add extendApiMap * Support promises * Update Offscreen to use API map * Add ApiNames<> template * Add getApiMapHandler * Use getApiMapHandler in offscreen
-rw-r--r--ext/js/background/offscreen-proxy.js2
-rw-r--r--ext/js/background/offscreen.js38
-rw-r--r--ext/js/core/api-map.js48
-rw-r--r--types/ext/api-map.d.ts55
-rw-r--r--types/ext/offscreen.d.ts116
5 files changed, 191 insertions, 68 deletions
diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js
index dfd342b4..99dc0741 100644
--- a/ext/js/background/offscreen-proxy.js
+++ b/ext/js/background/offscreen-proxy.js
@@ -74,7 +74,7 @@ export class OffscreenProxy {
/**
* @template {import('offscreen').MessageType} TMessageType
* @param {import('offscreen').Message<TMessageType>} message
- * @returns {Promise<import('offscreen').MessageReturn<TMessageType>>}
+ * @returns {Promise<import('offscreen').OffscreenApiReturn<TMessageType>>}
*/
sendMessagePromise(message) {
return new Promise((resolve, reject) => {
diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js
index d1cf3384..44b0af77 100644
--- a/ext/js/background/offscreen.js
+++ b/ext/js/background/offscreen.js
@@ -19,6 +19,7 @@
import * as wanakana from '../../lib/wanakana.js';
import {ClipboardReader} from '../comm/clipboard-reader.js';
import {invokeMessageHandler} from '../core.js';
+import {createApiMap, getApiMapHandler} from '../core/api-map.js';
import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
import {DictionaryDatabase} from '../language/dictionary-database.js';
import {JapaneseUtil} from '../language/sandbox/japanese-util.js';
@@ -50,9 +51,10 @@ export class Offscreen {
richContentPasteTargetSelector: '#clipboard-rich-content-paste-target'
});
+
/* eslint-disable no-multi-spaces */
- /** @type {import('offscreen').MessageHandlerMap} */
- this._messageHandlers = new Map(/** @type {import('offscreen').MessageHandlerMapInit} */ ([
+ /** @type {import('offscreen').OffscreenApiMapInit} */
+ const messageHandlersInit = [
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
['clipboardSetBrowserOffscreen', this._setClipboardBrowser.bind(this)],
@@ -65,8 +67,10 @@ export class Offscreen {
['findTermsOffscreen', this._findTermsHandler.bind(this)],
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)]
- ]));
- /* eslint-enable no-multi-spaces */
+ ];
+
+ /** @type {import('offscreen').OffscreenApiMap} */
+ this._messageHandlers = createApiMap(messageHandlersInit);
/** @type {?Promise<void>} */
this._prepareDatabasePromise = null;
@@ -77,22 +81,22 @@ export class Offscreen {
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
}
- /** @type {import('offscreen').MessageHandler<'clipboardGetTextOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardGetTextOffscreen'>} */
async _getTextHandler({useRichText}) {
return await this._clipboardReader.getText(useRichText);
}
- /** @type {import('offscreen').MessageHandler<'clipboardGetImageOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardGetImageOffscreen'>} */
async _getImageHandler() {
return await this._clipboardReader.getImage();
}
- /** @type {import('offscreen').MessageHandler<'clipboardSetBrowserOffscreen', false>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardSetBrowserOffscreen'>} */
_setClipboardBrowser({value}) {
this._clipboardReader.browser = value;
}
- /** @type {import('offscreen').MessageHandler<'databasePrepareOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databasePrepareOffscreen'>} */
_prepareDatabaseHandler() {
if (this._prepareDatabasePromise !== null) {
return this._prepareDatabasePromise;
@@ -101,29 +105,29 @@ export class Offscreen {
return this._prepareDatabasePromise;
}
- /** @type {import('offscreen').MessageHandler<'getDictionaryInfoOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'getDictionaryInfoOffscreen'>} */
async _getDictionaryInfoHandler() {
return await this._dictionaryDatabase.getDictionaryInfo();
}
- /** @type {import('offscreen').MessageHandler<'databasePurgeOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databasePurgeOffscreen'>} */
async _purgeDatabaseHandler() {
return await this._dictionaryDatabase.purge();
}
- /** @type {import('offscreen').MessageHandler<'databaseGetMediaOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databaseGetMediaOffscreen'>} */
async _getMediaHandler({targets}) {
const media = await this._dictionaryDatabase.getMedia(targets);
const serializedMedia = media.map((m) => ({...m, content: ArrayBufferUtil.arrayBufferToBase64(m.content)}));
return serializedMedia;
}
- /** @type {import('offscreen').MessageHandler<'translatorPrepareOffscreen', false>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'translatorPrepareOffscreen'>} */
_prepareTranslatorHandler({deinflectionReasons}) {
this._translator.prepare(deinflectionReasons);
}
- /** @type {import('offscreen').MessageHandler<'findKanjiOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'findKanjiOffscreen'>} */
async _findKanjiHandler({text, options}) {
/** @type {import('translation').FindKanjiOptions} */
const modifiedOptions = {
@@ -133,7 +137,7 @@ export class Offscreen {
return await this._translator.findKanji(text, modifiedOptions);
}
- /** @type {import('offscreen').MessageHandler<'findTermsOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'findTermsOffscreen'>} */
_findTermsHandler({mode, text, options}) {
const enabledDictionaryMap = new Map(options.enabledDictionaryMap);
const excludeDictionaryDefinitions = (
@@ -160,19 +164,19 @@ export class Offscreen {
return this._translator.findTerms(mode, text, modifiedOptions);
}
- /** @type {import('offscreen').MessageHandler<'getTermFrequenciesOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'getTermFrequenciesOffscreen'>} */
_getTermFrequenciesHandler({termReadingList, dictionaries}) {
return this._translator.getTermFrequencies(termReadingList, dictionaries);
}
- /** @type {import('offscreen').MessageHandler<'clearDatabaseCachesOffscreen', false>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clearDatabaseCachesOffscreen'>} */
_clearDatabaseCachesHandler() {
this._translator.clearDatabaseCaches();
}
/** @type {import('extension').ChromeRuntimeOnMessageCallback} */
_onMessage({action, params}, sender, callback) {
- const messageHandler = this._messageHandlers.get(/** @type {import('offscreen').MessageType} */ (action));
+ const messageHandler = getApiMapHandler(this._messageHandlers, action);
if (typeof messageHandler === 'undefined') { return false; }
return invokeMessageHandler(messageHandler, params, callback, sender);
}
diff --git a/ext/js/core/api-map.js b/ext/js/core/api-map.js
new file mode 100644
index 00000000..eb4abeea
--- /dev/null
+++ b/ext/js/core/api-map.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 Yomitan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMapInit<TApiSurface>} init
+ * @returns {import('api-map').ApiMap<TApiSurface>}
+ */
+export function createApiMap(init) {
+ return new Map(init);
+}
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMap<TApiSurface>} map
+ * @param {import('api-map').ApiMapInit<TApiSurface>} init
+ * @throws {Error}
+ */
+export function extendApiMap(map, init) {
+ for (const [key, value] of init) {
+ if (map.has(key)) { throw new Error(`The handler for ${String(key)} has already been registered`); }
+ map.set(key, value);
+ }
+}
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMap<TApiSurface>} map
+ * @param {string} name
+ * @returns {import('api-map').ApiHandlerAny<TApiSurface>|undefined}
+ */
+export function getApiMapHandler(map, name) {
+ return map.get(/** @type {import('api-map').ApiNames<TApiSurface>} */ (name));
+}
diff --git a/types/ext/api-map.d.ts b/types/ext/api-map.d.ts
new file mode 100644
index 00000000..eebc886a
--- /dev/null
+++ b/types/ext/api-map.d.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 Yomitan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+type ApiSurface = {
+ [name: string]: ApiItem;
+};
+
+type ApiItem = {
+ params: void | {[name: string]: unknown};
+ return: unknown;
+};
+
+export type ApiHandler<TApiItem extends ApiItem> = (params: TApiItem['params']) => TApiItem['return'] | Promise<TApiItem['return']>;
+
+type ApiHandlerSurface<TApiSurface extends ApiSurface> = {[name in ApiNames<TApiSurface>]: ApiHandler<TApiSurface[name]>};
+
+export type ApiHandlerAny<TApiSurface extends ApiSurface> = ApiHandlerSurface<TApiSurface>[ApiNames<TApiSurface>];
+
+export type ApiNames<TApiSurface extends ApiSurface> = keyof TApiSurface;
+
+export type ApiParams<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['params'];
+
+export type ApiReturn<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['return'];
+
+export type ApiMap<TApiSurface extends ApiSurface> = Map<ApiNames<TApiSurface>, ApiHandlerAny<TApiSurface>>;
+
+export type ApiMapInit<TApiSurface extends ApiSurface> = ApiMapInitItemAny<TApiSurface>[];
+
+export type ApiMapInitLax<TApiSurface extends ApiSurface> = ApiMapInitLaxItem<TApiSurface>[];
+
+export type ApiMapInitLaxItem<TApiSurface extends ApiSurface> = [
+ name: ApiNames<TApiSurface>,
+ handler: ApiHandlerAny<TApiSurface>,
+];
+
+type ApiMapInitItem<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = [
+ name: TName,
+ handler: ApiHandler<TApiSurface[TName]>,
+];
+
+type ApiMapInitItemAny<TApiSurface extends ApiSurface> = {[key in ApiNames<TApiSurface>]: ApiMapInitItem<TApiSurface, key>}[ApiNames<TApiSurface>];
diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts
index c741ac99..451f5f9e 100644
--- a/types/ext/offscreen.d.ts
+++ b/types/ext/offscreen.d.ts
@@ -15,7 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import type * as Core from './core';
import type * as Deinflector from './deinflector';
import type * as Dictionary from './dictionary';
import type * as DictionaryDatabase from './dictionary-database';
@@ -23,64 +22,84 @@ import type * as DictionaryImporter from './dictionary-importer';
import type * as Environment from './environment';
import type * as Translation from './translation';
import type * as Translator from './translator';
+import type {ApiMap, ApiMapInit, ApiHandler, ApiParams, ApiReturn} from './api-map';
-export type Message<T extends MessageType> = (
- MessageDetailsMap[T] extends undefined ?
- {action: T} :
- {action: T, params: MessageDetailsMap[T]}
-);
-
-export type MessageReturn<T extends MessageType> = MessageReturnMap[T];
-
-type MessageDetailsMap = {
- databasePrepareOffscreen: undefined;
- getDictionaryInfoOffscreen: undefined;
- databasePurgeOffscreen: undefined;
+type OffscreenApiSurface = {
+ databasePrepareOffscreen: {
+ params: void;
+ return: void;
+ };
+ getDictionaryInfoOffscreen: {
+ params: void;
+ return: DictionaryImporter.Summary[];
+ };
+ databasePurgeOffscreen: {
+ params: void;
+ return: boolean;
+ };
databaseGetMediaOffscreen: {
- targets: DictionaryDatabase.MediaRequest[];
+ params: {
+ targets: DictionaryDatabase.MediaRequest[];
+ };
+ return: DictionaryDatabase.Media<string>[];
};
translatorPrepareOffscreen: {
- deinflectionReasons: Deinflector.ReasonsRaw;
+ params: {
+ deinflectionReasons: Deinflector.ReasonsRaw;
+ };
+ return: void;
};
findKanjiOffscreen: {
- text: string;
- options: FindKanjiOptionsOffscreen;
+ params: {
+ text: string;
+ options: FindKanjiOptionsOffscreen;
+ };
+ return: Dictionary.KanjiDictionaryEntry[];
};
findTermsOffscreen: {
- mode: Translator.FindTermsMode;
- text: string;
- options: FindTermsOptionsOffscreen;
+ params: {
+ mode: Translator.FindTermsMode;
+ text: string;
+ options: FindTermsOptionsOffscreen;
+ };
+ return: Translator.FindTermsResult;
};
getTermFrequenciesOffscreen: {
- termReadingList: Translator.TermReadingList;
- dictionaries: string[];
+ params: {
+ termReadingList: Translator.TermReadingList;
+ dictionaries: string[];
+ };
+ return: Translator.TermFrequencySimple[];
+ };
+ clearDatabaseCachesOffscreen: {
+ params: void;
+ return: void;
};
- clearDatabaseCachesOffscreen: undefined;
clipboardSetBrowserOffscreen: {
- value: Environment.Browser | null;
+ params: {
+ value: Environment.Browser | null;
+ };
+ return: void;
};
clipboardGetTextOffscreen: {
- useRichText: boolean;
+ params: {
+ useRichText: boolean;
+ };
+ return: string;
+ };
+ clipboardGetImageOffscreen: {
+ params: void;
+ return: string | null;
};
- clipboardGetImageOffscreen: undefined;
};
-type MessageReturnMap = {
- databasePrepareOffscreen: void;
- getDictionaryInfoOffscreen: DictionaryImporter.Summary[];
- databasePurgeOffscreen: boolean;
- databaseGetMediaOffscreen: DictionaryDatabase.Media<string>[];
- translatorPrepareOffscreen: void;
- findKanjiOffscreen: Dictionary.KanjiDictionaryEntry[];
- findTermsOffscreen: Translator.FindTermsResult;
- getTermFrequenciesOffscreen: Translator.TermFrequencySimple[];
- clearDatabaseCachesOffscreen: void;
- clipboardSetBrowserOffscreen: void;
- clipboardGetTextOffscreen: string;
- clipboardGetImageOffscreen: string | null;
-};
+export type Message<TName extends MessageType> = (
+ OffscreenApiParams<TName> extends void ?
+ {action: TName} :
+ {action: TName, params: OffscreenApiParams<TName>}
+);
-export type MessageType = keyof MessageDetailsMap;
+export type MessageType = keyof OffscreenApiSurface;
export type FindKanjiOptionsOffscreen = Omit<Translation.FindKanjiOptions, 'enabledDictionaryMap'> & {
enabledDictionaryMap: [
@@ -103,15 +122,12 @@ export type FindTermsTextReplacementOffscreen = Omit<Translation.FindTermsTextRe
pattern: string;
};
-export type MessageHandler<
- TMessage extends MessageType,
- TIsAsync extends boolean,
-> = (
- details: MessageDetailsMap[TMessage],
-) => (TIsAsync extends true ? Promise<MessageReturn<TMessage>> : MessageReturn<TMessage>);
+export type OffscreenApiMap = ApiMap<OffscreenApiSurface>;
+
+export type OffscreenApiMapInit = ApiMapInit<OffscreenApiSurface>;
-export type MessageHandlerMap = Map<MessageType, Core.MessageHandler>;
+export type OffscreenApiHandler<TName extends keyof OffscreenApiSurface> = ApiHandler<OffscreenApiSurface[TName]>;
-export type MessageHandlerMapInit = MessageHandlerMapInitItem[];
+export type OffscreenApiParams<TName extends keyof OffscreenApiSurface> = ApiParams<OffscreenApiSurface, TName>;
-export type MessageHandlerMapInitItem = [messageType: MessageType, handler: Core.MessageHandler];
+export type OffscreenApiReturn<TName extends keyof OffscreenApiSurface> = ApiReturn<OffscreenApiSurface, TName>;