diff options
| -rw-r--r-- | ext/js/background/offscreen.js | 2 | ||||
| -rw-r--r-- | ext/js/core/api-map.js | 58 | ||||
| -rw-r--r-- | types/ext/api-map.d.ts | 100 | ||||
| -rw-r--r-- | types/ext/offscreen.d.ts | 10 | 
4 files changed, 138 insertions, 32 deletions
| diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 44b0af77..1cab5929 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -138,7 +138,7 @@ export class Offscreen {      }      /** @type {import('offscreen').OffscreenApiHandler<'findTermsOffscreen'>} */ -    _findTermsHandler({mode, text, options}) { +    async _findTermsHandler({mode, text, options}) {          const enabledDictionaryMap = new Map(options.enabledDictionaryMap);          const excludeDictionaryDefinitions = (              options.excludeDictionaryDefinitions !== null ? diff --git a/ext/js/core/api-map.js b/ext/js/core/api-map.js index eb4abeea..09be8035 100644 --- a/ext/js/core/api-map.js +++ b/ext/js/core/api-map.js @@ -15,10 +15,13 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {ExtensionError} from './extension-error.js'; +  /**   * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMapInit<TApiSurface>} init - * @returns {import('api-map').ApiMap<TApiSurface>} + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMapInit<TApiSurface, TExtraParams>} init + * @returns {import('api-map').ApiMap<TApiSurface, TExtraParams>}   */  export function createApiMap(init) {      return new Map(init); @@ -26,8 +29,9 @@ export function createApiMap(init) {  /**   * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMap<TApiSurface>} map - * @param {import('api-map').ApiMapInit<TApiSurface>} init + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map + * @param {import('api-map').ApiMapInit<TApiSurface, TExtraParams>} init   * @throws {Error}   */  export function extendApiMap(map, init) { @@ -39,10 +43,52 @@ export function extendApiMap(map, init) {  /**   * @template {import('api-map').ApiSurface} [TApiSurface=never] - * @param {import('api-map').ApiMap<TApiSurface>} map + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map   * @param {string} name - * @returns {import('api-map').ApiHandlerAny<TApiSurface>|undefined} + * @returns {import('api-map').ApiHandlerAny<TApiSurface, TExtraParams>|undefined}   */  export function getApiMapHandler(map, name) {      return map.get(/** @type {import('api-map').ApiNames<TApiSurface>} */ (name));  } + +/** + * @template {import('api-map').ApiSurface} [TApiSurface=never] + * @template {unknown[]} [TExtraParams=[]] + * @param {import('api-map').ApiMap<TApiSurface, TExtraParams>} map + * @param {string} name + * @param {import('api-map').ApiParamsAny<TApiSurface>} params + * @param {TExtraParams} extraParams + * @param {(response: import('core').Response<import('api-map').ApiReturnAny<TApiSurface>>) => void} callback + * @param {() => void} [handlerNotFoundCallback] + * @returns {boolean} `true` if async, `false` otherwise. + */ +export function invokeApiMapHandler(map, name, params, extraParams, callback, handlerNotFoundCallback) { +    const handler = getApiMapHandler(map, name); +    if (typeof handler === 'undefined') { +        if (typeof handlerNotFoundCallback === 'function') { +            try { +                handlerNotFoundCallback(); +            } catch (error) { +                // NOP +            } +        } +        return false; +    } +    try { +        const promiseOrResult = handler(/** @type {import('core').SafeAny} */ (params), ...extraParams); +        if (promiseOrResult instanceof Promise) { +            /** @type {Promise<unknown>} */ (promiseOrResult).then( +                (result) => { callback({result}); }, +                (error) => { callback({error: ExtensionError.serialize(error)}); } +            ); +            return true; +        } else { +            callback({result: promiseOrResult}); +            return false; +        } +    } catch (error) { +        callback({error: ExtensionError.serialize(error)}); +        return false; +    } +} diff --git a/types/ext/api-map.d.ts b/types/ext/api-map.d.ts index eebc886a..4a4eb87c 100644 --- a/types/ext/api-map.d.ts +++ b/types/ext/api-map.d.ts @@ -15,41 +15,101 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +/** + * This type describes the structure of an API surface. + * It is effectively just an object containing a list of items which describe a basic API functionality. + */  type ApiSurface = { -    [name: string]: ApiItem; +    [name: string]: ApiDescriptor;  }; -type ApiItem = { +/** + * This type describes the structure of a single API function. + */ +type ApiDescriptor = { +    /** The parameters for the function. If there are no parameters, `void` should be used. */      params: void | {[name: string]: unknown}; +    /** The return type for the function. */      return: unknown;  }; -export type ApiHandler<TApiItem extends ApiItem> = (params: TApiItem['params']) => TApiItem['return'] | Promise<TApiItem['return']>; +/** + * This type represents a mapping of an entire API surface to its handlers. + */ +type ApiHandlerSurface<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams> = { +    [name in ApiNames<TSurface>]: ApiHandler<TSurface[name], TExtraParams>; +}; -type ApiHandlerSurface<TApiSurface extends ApiSurface> = {[name in ApiNames<TApiSurface>]: ApiHandler<TApiSurface[name]>}; +/** + * This type represents a single API map initializer. + * Type safety is enforced by ensuring that the name and handler signature are valid. + */ +type ApiMapInitItem<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams, TName extends ApiNames<TSurface>> = [ +    name: TName, +    handler: ApiHandler<TSurface[TName], TExtraParams>, +]; -export type ApiHandlerAny<TApiSurface extends ApiSurface> = ApiHandlerSurface<TApiSurface>[ApiNames<TApiSurface>]; +/** + * This type represents a union of all API map initializers for a given surface. + */ +type ApiMapInitItemAny<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams> = {[key in ApiNames<TSurface>]: ApiMapInitItem<TSurface, TExtraParams, key>}[ApiNames<TSurface>]; -export type ApiNames<TApiSurface extends ApiSurface> = keyof TApiSurface; +/** Base type for extra params, which is just a generic array. */ +type ApiTExtraParams = unknown[]; -export type ApiParams<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['params']; +/** Default type for extra params, which is an empty array. */ +type ApiExtraParamsDefault = []; -export type ApiReturn<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = TApiSurface[TName]['return']; +/** Type alias for the params member of a descriptor. */ +export type ApiParams<TDescriptor extends ApiDescriptor> = TDescriptor['params']; -export type ApiMap<TApiSurface extends ApiSurface> = Map<ApiNames<TApiSurface>, ApiHandlerAny<TApiSurface>>; +/** Type alias for a single param of a descriptor. */ +export type ApiParam<TDescriptor extends ApiDescriptor, TParamName extends ApiParamNames<TDescriptor>> = ApiParams<TDescriptor>[TParamName]; -export type ApiMapInit<TApiSurface extends ApiSurface> = ApiMapInitItemAny<TApiSurface>[]; +/** Type alias for the union of parameter names in a descriptor. */ +export type ApiParamNames<TDescriptor extends ApiDescriptor> = keyof ApiParams<TDescriptor>; -export type ApiMapInitLax<TApiSurface extends ApiSurface> = ApiMapInitLaxItem<TApiSurface>[]; +/** Type alias for a tuple of parameter types for a descriptor. */ +export type ApiOrderedParams<TDescriptor extends ApiDescriptor, TParamNames extends ApiParamNames<TDescriptor>[]> = { +    [index in keyof TParamNames]: ApiParams<TDescriptor>[TParamNames[index]]; +}; -export type ApiMapInitLaxItem<TApiSurface extends ApiSurface> = [ -    name: ApiNames<TApiSurface>, -    handler: ApiHandlerAny<TApiSurface>, -]; +/** Type alias for the return member of a descriptor. */ +export type ApiReturn<TDescriptor extends ApiDescriptor> = TDescriptor['return']; -type ApiMapInitItem<TApiSurface extends ApiSurface, TName extends ApiNames<TApiSurface>> = [ -    name: TName, -    handler: ApiHandler<TApiSurface[TName]>, -]; +/** A type representing a synchronous handler. */ +export type ApiHandlerSync<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => ApiReturn<TDescriptor>; + +/** A type representing an asynchronous handler. */ +export type ApiHandlerAsync<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => Promise<ApiReturn<TDescriptor>>; + +/** A type representing a generic handler. */ +export type ApiHandler<TDescriptor extends ApiDescriptor, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = (params: ApiParams<TDescriptor>, ...extraParams: TExtraParams) => ApiReturn<TDescriptor> | Promise<ApiReturn<TDescriptor>>; + +/** A union of all of the handlers for a given surface. */ +export type ApiHandlerAny<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = ApiHandlerSurface<TSurface, TExtraParams>[ApiNames<TSurface>]; + +/** A union of all of the names for a given surface. */ +export type ApiNames<TSurface extends ApiSurface> = keyof TSurface; + +/** A mapping of names to the corresponding handler function. */ +export type ApiMap<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = Map<ApiNames<TSurface>, ApiHandlerAny<TSurface, TExtraParams>>; + +/** The initialization array structure for populating an API map. */ +export type ApiMapInit<TSurface extends ApiSurface, TExtraParams extends ApiTExtraParams = ApiExtraParamsDefault> = ApiMapInitItemAny<TSurface, TExtraParams>[]; + +/** The type for a public API function, using a parameters object. */ +export type ApiFunction<TSurface extends ApiSurface, TName extends ApiNames<TSurface>> = ( +    params: ApiParams<TSurface[TName]>, +) => Promise<ApiReturn<TSurface[TName]>>; + +/** The type for a public API function, using ordered parameters. */ +export type ApiFunctionOrdered<TSurface extends ApiSurface, TName extends ApiNames<TSurface>, TParamNames extends ApiParamNames<TSurface[TName]>[]> = ( +    ...params: ApiOrderedParams<TSurface[TName], TParamNames>, +) => Promise<ApiReturn<TSurface[TName]>>; + +/** Type alias for a union of all params types. */ +export type ApiParamsAny<TSurface extends ApiSurface> = ApiParams<TSurface[keyof TSurface]>; -type ApiMapInitItemAny<TApiSurface extends ApiSurface> = {[key in ApiNames<TApiSurface>]: ApiMapInitItem<TApiSurface, key>}[ApiNames<TApiSurface>]; +/** Type alias for a union of all return types. */ +export type ApiReturnAny<TSurface extends ApiSurface> = ApiReturn<TSurface[keyof TSurface]>; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 451f5f9e..d67733bb 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -22,7 +22,7 @@ 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'; +import type {ApiMap, ApiMapInit, ApiHandler, ApiParams, ApiReturn, ApiNames} from './api-map';  type OffscreenApiSurface = {      databasePrepareOffscreen: { @@ -99,7 +99,7 @@ export type Message<TName extends MessageType> = (          {action: TName, params: OffscreenApiParams<TName>}  ); -export type MessageType = keyof OffscreenApiSurface; +export type MessageType = ApiNames<OffscreenApiSurface>;  export type FindKanjiOptionsOffscreen = Omit<Translation.FindKanjiOptions, 'enabledDictionaryMap'> & {      enabledDictionaryMap: [ @@ -126,8 +126,8 @@ export type OffscreenApiMap = ApiMap<OffscreenApiSurface>;  export type OffscreenApiMapInit = ApiMapInit<OffscreenApiSurface>; -export type OffscreenApiHandler<TName extends keyof OffscreenApiSurface> = ApiHandler<OffscreenApiSurface[TName]>; +export type OffscreenApiHandler<TName extends MessageType> = ApiHandler<OffscreenApiSurface[TName]>; -export type OffscreenApiParams<TName extends keyof OffscreenApiSurface> = ApiParams<OffscreenApiSurface, TName>; +export type OffscreenApiParams<TName extends MessageType> = ApiParams<OffscreenApiSurface[TName]>; -export type OffscreenApiReturn<TName extends keyof OffscreenApiSurface> = ApiReturn<OffscreenApiSurface, TName>; +export type OffscreenApiReturn<TName extends MessageType> = ApiReturn<OffscreenApiSurface[TName]>; |