aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-22 20:27:10 -0500
committerGitHub <noreply@github.com>2023-12-23 01:27:10 +0000
commita72b53de011d35602c0f68577c30e28386046583 (patch)
tree0fd3f2e38b36b81c773bd518c99ce5f01cae32eb
parentcb4499fd8ab0322fa3ab706adfb46caf21c57eec (diff)
Type safety for TemplateRendererProxy (#428)
* Type safety for TemplateRendererProxy * Simplify
-rw-r--r--ext/js/templates/sandbox/template-renderer-frame-api.js51
-rw-r--r--ext/js/templates/template-renderer-proxy.js36
-rw-r--r--types/ext/template-renderer-frame-api.d.ts24
-rw-r--r--types/ext/template-renderer-proxy.d.ts102
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>;