diff options
| -rw-r--r-- | ext/js/templates/sandbox/template-renderer-frame-api.js | 51 | ||||
| -rw-r--r-- | ext/js/templates/template-renderer-proxy.js | 36 | ||||
| -rw-r--r-- | types/ext/template-renderer-frame-api.d.ts | 24 | ||||
| -rw-r--r-- | types/ext/template-renderer-proxy.d.ts | 102 | 
4 files changed, 138 insertions, 75 deletions
| diff --git a/ext/js/templates/sandbox/template-renderer-frame-api.js b/ext/js/templates/sandbox/template-renderer-frame-api.js index 56cedb97..28303e51 100644 --- a/ext/js/templates/sandbox/template-renderer-frame-api.js +++ b/ext/js/templates/sandbox/template-renderer-frame-api.js @@ -16,7 +16,7 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {ExtensionError} from '../../core/extension-error.js'; +import {createApiMap, invokeApiMapHandler} from '../../core/api-map.js';  import {parseJson} from '../../core/json.js';  export class TemplateRendererFrameApi { @@ -26,12 +26,12 @@ export class TemplateRendererFrameApi {      constructor(templateRenderer) {          /** @type {import('./template-renderer.js').TemplateRenderer} */          this._templateRenderer = templateRenderer; -        /** @type {import('core').MessageHandlerMap} */ -        this._windowMessageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ +        /** @type {import('template-renderer-proxy').FrontendApiMap} */ +        this._windowMessageHandlers = createApiMap([              ['render', this._onRender.bind(this)],              ['renderMulti', this._onRenderMulti.bind(this)],              ['getModifiedData', this._onGetModifiedData.bind(this)] -        ])); +        ]);      }      /** @@ -39,43 +39,19 @@ export class TemplateRendererFrameApi {       */      prepare() {          window.addEventListener('message', this._onWindowMessage.bind(this), false); -        this._postMessage(window.parent, 'ready', {}, null); +        this._postMessage(window.parent, 'ready', void 0, null);      }      // Private      /** -     * @param {MessageEvent<import('template-renderer-frame-api').MessageData>} e +     * @param {MessageEvent<import('template-renderer-proxy').FrontendMessageAny>} e       */      _onWindowMessage(e) {          const {source, data: {action, params, id}} = e; -        const messageHandler = this._windowMessageHandlers.get(action); -        if (typeof messageHandler === 'undefined') { return; } - -        this._onWindowMessageInner(messageHandler, action, params, /** @type {Window} */ (source), id); -    } - -    /** -     * @param {import('core').MessageHandler} handler -     * @param {string} action -     * @param {import('core').SerializableObject} params -     * @param {Window} source -     * @param {?string} id -     */ -    async _onWindowMessageInner(handler, action, params, source, id) { -        let response; -        try { -            let result = handler(params); -            if (result instanceof Promise) { -                result = await result; -            } -            response = {result}; -        } catch (error) { -            response = {error: ExtensionError.serialize(error)}; -        } - -        if (typeof id === 'undefined') { return; } -        this._postMessage(source, `${action}.response`, response, id); +        invokeApiMapHandler(this._windowMessageHandlers, action, params, [], (response) => { +            this._postMessage(/** @type {Window} */ (source), 'response', response, id); +        });      }      /** @@ -113,12 +89,15 @@ export class TemplateRendererFrameApi {      }      /** +     * @template {import('template-renderer-proxy').BackendApiNames} TName       * @param {Window} target -     * @param {string} action -     * @param {import('core').SerializableObject} params +     * @param {TName} action +     * @param {import('template-renderer-proxy').BackendApiParams<TName>} params       * @param {?string} id       */      _postMessage(target, action, params, id) { -        target.postMessage(/** @type {import('template-renderer-frame-api').MessageData} */ ({action, params, id}), '*'); +        /** @type {import('template-renderer-proxy').BackendMessageAny} */ +        const data = {action, params, id}; +        target.postMessage(data, '*');      }  } diff --git a/ext/js/templates/template-renderer-proxy.js b/ext/js/templates/template-renderer-proxy.js index 7cbab3c8..25fe8fb1 100644 --- a/ext/js/templates/template-renderer-proxy.js +++ b/ext/js/templates/template-renderer-proxy.js @@ -43,7 +43,7 @@ export class TemplateRendererProxy {       */      async render(template, data, type) {          await this._prepareFrame(); -        return /** @type {import('template-renderer').RenderResult} */ (await this._invoke('render', {template, data, type})); +        return await this._invoke('render', {template, data, type});      }      /** @@ -52,7 +52,7 @@ export class TemplateRendererProxy {       */      async renderMulti(items) {          await this._prepareFrame(); -        return /** @type {import('core').Response<import('template-renderer').RenderResult>[]} */ (await this._invoke('renderMulti', {items})); +        return await this._invoke('renderMulti', {items});      }      /** @@ -62,7 +62,7 @@ export class TemplateRendererProxy {       */      async getModifiedData(data, type) {          await this._prepareFrame(); -        return /** @type {import('anki-templates').NoteData} */ (await this._invoke('getModifiedData', {data, type})); +        return await this._invoke('getModifiedData', {data, type});      }      // Private @@ -124,14 +124,14 @@ export class TemplateRendererProxy {                  updateState(0x2);              };              /** -             * @param {MessageEvent<unknown>} e +             * @param {MessageEvent<import('template-renderer-proxy').BackendMessageAny>} e               */              const onWindowMessage = (e) => {                  if ((state & 0x5) !== 0x1) { return; }                  const frameWindow = frame.contentWindow;                  if (frameWindow === null || frameWindow !== e.source) { return; }                  const {data} = e; -                if (!(typeof data === 'object' && data !== null && /** @type {import('core').SerializableObject} */ (data).action === 'ready')) { return; } +                if (!(typeof data === 'object' && data !== null && data.action === 'ready')) { return; }                  updateState(0x4);              }; @@ -160,10 +160,11 @@ export class TemplateRendererProxy {      }      /** -     * @param {string} action -     * @param {import('core').SerializableObject} params +     * @template {import('template-renderer-proxy').FrontendApiNames} TName +     * @param {TName} action +     * @param {import('template-renderer-proxy').FrontendApiParams<TName>} params       * @param {?number} [timeout] -     * @returns {Promise<unknown>} +     * @returns {Promise<import('template-renderer-proxy').FrontendApiReturn<TName>>}       */      _invoke(action, params, timeout = null) {          return new Promise((resolve, reject) => { @@ -189,8 +190,9 @@ export class TemplateRendererProxy {                      timer = null;                  }              }; +              /** -             * @param {MessageEvent<unknown>} event +             * @param {MessageEvent<import('template-renderer-proxy').BackendMessageAny>} event               */              const onMessage = (event) => {                  if (event.source !== frameWindow) { return; } @@ -198,21 +200,23 @@ export class TemplateRendererProxy {                  if (                      typeof data !== 'object' ||                      data === null || -                    /** @type {import('core').SerializableObject} */ (data).id !== id || -                    /** @type {import('core').SerializableObject} */ (data).action !== `${action}.response` +                    data.id !== id || +                    data.action !== 'response'                  ) {                      return;                  } -                const response = /** @type {import('core').SerializableObject} */ (data).params; +                // This type should probably be able to be inferred without a cast, but for some reason it isn't. +                const responseData = /** @type {import('template-renderer-proxy').BackendMessage<'response'>} */ (data); +                const response = responseData.params;                  if (typeof response !== 'object' || response === null) { return; }                  cleanup(); -                const {error} = /** @type {import('core').Response} */ (response); +                const {error} = response;                  if (error) {                      reject(ExtensionError.deserialize(error));                  } else { -                    resolve(/** @type {import('core').Response} */ (response).result); +                    resolve(/** @type {import('template-renderer-proxy').FrontendApiReturn<TName>} */ (response.result));                  }              }; @@ -224,7 +228,9 @@ export class TemplateRendererProxy {              this._invocations.add(invocation);              window.addEventListener('message', onMessage, false); -            frameWindow.postMessage({action, params, id}, '*'); +            /** @type {import('template-renderer-proxy').FrontendMessage<TName>} */ +            const requestMessage = {action, params, id}; +            frameWindow.postMessage(requestMessage, '*');          });      } diff --git a/types/ext/template-renderer-frame-api.d.ts b/types/ext/template-renderer-frame-api.d.ts deleted file mode 100644 index e00a0711..00000000 --- a/types/ext/template-renderer-frame-api.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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/>. - */ - -import type * as Core from './core'; - -export type MessageData = { -    action: string; -    params: Core.SerializableObject; -    id: string; -}; diff --git a/types/ext/template-renderer-proxy.d.ts b/types/ext/template-renderer-proxy.d.ts new file mode 100644 index 00000000..74e95b54 --- /dev/null +++ b/types/ext/template-renderer-proxy.d.ts @@ -0,0 +1,102 @@ +/* + * 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/>. + */ + +import type {Response} from 'core'; +import type {RenderMode, NoteData} from 'anki-templates'; +import type {CompositeRenderData, PartialOrCompositeRenderData, RenderMultiItem, RenderResult} from 'template-renderer'; +import type { +    ApiMap as BaseApiMap, +    ApiMapInit as BaseApiMapInit, +    ApiHandler as BaseApiHandler, +    ApiParams as BaseApiParams, +    ApiNames as BaseApiNames, +    ApiReturn as BaseApiReturn, +    ApiReturnAny as BaseApiReturnAny, +} from './api-map'; + +// Frontend API + +type FrontendApiSurface = { +    render: { +        params: { +            template: string; +            data: PartialOrCompositeRenderData; +            type: RenderMode; +        }; +        return: RenderResult; +    }; +    renderMulti: { +        params: { +            items: RenderMultiItem[]; +        }; +        return: Response<RenderResult>[]; +    }; +    getModifiedData: { +        params: { +            data: CompositeRenderData; +            type: RenderMode; +        }; +        return: NoteData; +    }; +}; + +type FrontendApiParams<TName extends FrontendApiNames> = BaseApiParams<FrontendApiSurface[TName]>; + +type FrontendApiNames = BaseApiNames<FrontendApiSurface>; + +type FrontendApiReturnAny = BaseApiReturnAny<FrontendApiSurface>; + +export type FrontendMessage<TName extends FrontendApiNames> = { +    action: TName; +    params: FrontendApiParams<TName>; +    id: string; +}; + +export type FrontendMessageAny = FrontendMessage<FrontendApiNames>; + +export type FrontendApiReturn<TName extends FrontendApiNames> = BaseApiReturn<FrontendApiSurface[TName]>; + +export type FrontendApiMap = BaseApiMap<FrontendApiSurface>; + +export type FrontendApiMapInit = BaseApiMapInit<FrontendApiSurface>; + +export type FrontendApiHandler<TName extends FrontendApiNames> = BaseApiHandler<FrontendApiSurface[TName]>; + +// Backend API + +export type BackendApiSurface = { +    ready: { +        params: void; +        return: void; +    }; +    response: { +        params: Response<FrontendApiReturnAny>; +        return: void; +    }; +}; + +type BackendApiNames = BaseApiNames<BackendApiSurface>; + +type BackendApiParams<TName extends BackendApiNames> = BaseApiParams<BackendApiSurface[TName]>; + +export type BackendMessage<TName extends BackendApiNames> = { +    action: TName; +    params: BackendApiParams<TName>; +    id: string | null; +}; + +export type BackendMessageAny = BackendMessage<BackendApiNames>; |