diff options
Diffstat (limited to 'ext/js/comm/mecab.js')
-rw-r--r-- | ext/js/comm/mecab.js | 84 |
1 files changed, 60 insertions, 24 deletions
diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js index c7314605..0a87463b 100644 --- a/ext/js/comm/mecab.js +++ b/ext/js/comm/mecab.js @@ -24,32 +24,26 @@ import {EventListenerCollection} from '../core.js'; */ export class Mecab { /** - * The resulting data from an invocation of `parseText`. - * @typedef {object} ParseResult - * @property {string} name The dictionary name for the parsed result. - * @property {ParseTerm[]} lines The resulting parsed terms. - */ - - /** - * A fragment of the parsed text. - * @typedef {object} ParseFragment - * @property {string} term The term. - * @property {string} reading The reading of the term. - * @property {string} source The source text. - */ - - /** * Creates a new instance of the class. */ constructor() { + /** @type {?chrome.runtime.Port} */ this._port = null; + /** @type {number} */ this._sequence = 0; + /** @type {Map<number, {resolve: (value: unknown) => void, reject: (reason?: unknown) => void, timer: import('core').Timeout}>} */ this._invocations = new Map(); + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); + /** @type {number} */ this._timeout = 5000; + /** @type {number} */ this._version = 1; + /** @type {?number} */ this._remoteVersion = null; + /** @type {boolean} */ this._enabled = false; + /** @type {?Promise<void>} */ this._setupPortPromise = null; } @@ -107,7 +101,7 @@ export class Mecab { /** * Gets the version of the MeCab component. - * @returns {?number} The version of the MeCab component, or `null` if the component was not found. + * @returns {Promise<?number>} The version of the MeCab component, or `null` if the component was not found. */ async getVersion() { try { @@ -135,17 +129,26 @@ export class Mecab { * ] * ``` * @param {string} text The string to parse. - * @returns {ParseResult[]} A collection of parsing results of the text. + * @returns {Promise<import('mecab').ParseResult[]>} A collection of parsing results of the text. */ async parseText(text) { await this._setupPort(); const rawResults = await this._invoke('parse_text', {text}); - return this._convertParseTextResults(rawResults); + // Note: The format of rawResults is not validated + return this._convertParseTextResults(/** @type {import('mecab').ParseResultRaw} */ (rawResults)); } // Private - _onMessage({sequence, data}) { + /** + * @param {unknown} message + */ + _onMessage(message) { + if (typeof message !== 'object' || message === null) { return; } + + const {sequence, data} = /** @type {import('core').SerializableObject} */ (message); + if (typeof sequence !== 'number') { return; } + const invocation = this._invocations.get(sequence); if (typeof invocation === 'undefined') { return; } @@ -155,6 +158,9 @@ export class Mecab { this._invocations.delete(sequence); } + /** + * @returns {void} + */ _onDisconnect() { if (this._port === null) { return; } const e = chrome.runtime.lastError; @@ -166,10 +172,16 @@ export class Mecab { this._clearPort(); } + /** + * @param {string} action + * @param {import('core').SerializableObject} params + * @returns {Promise<unknown>} + */ _invoke(action, params) { return new Promise((resolve, reject) => { if (this._port === null) { reject(new Error('Port disconnected')); + return; } const sequence = this._sequence++; @@ -179,15 +191,21 @@ export class Mecab { reject(new Error(`MeCab invoke timed out after ${this._timeout}ms`)); }, this._timeout); - this._invocations.set(sequence, {resolve, reject, timer}, this._timeout); + this._invocations.set(sequence, {resolve, reject, timer}); this._port.postMessage({action, params, sequence}); }); } + /** + * @param {import('mecab').ParseResultRaw} rawResults + * @returns {import('mecab').ParseResult[]} + */ _convertParseTextResults(rawResults) { + /** @type {import('mecab').ParseResult[]} */ const results = []; for (const [name, rawLines] of Object.entries(rawResults)) { + /** @type {import('mecab').ParseFragment[][]} */ const lines = []; for (const rawLine of rawLines) { const line = []; @@ -204,6 +222,9 @@ export class Mecab { return results; } + /** + * @returns {Promise<void>} + */ async _setupPort() { if (!this._enabled) { throw new Error('MeCab not enabled'); @@ -214,10 +235,13 @@ export class Mecab { try { await this._setupPortPromise; } catch (e) { - throw new Error(e.message); + throw new Error(e instanceof Error ? e.message : `${e}`); } } + /** + * @returns {Promise<void>} + */ async _setupPort2() { const port = chrome.runtime.connectNative('yomitan_mecab'); this._eventListeners.addListener(port.onMessage, this._onMessage.bind(this)); @@ -225,7 +249,14 @@ export class Mecab { this._port = port; try { - const {version} = await this._invoke('get_version', {}); + const data = await this._invoke('get_version', {}); + if (typeof data !== 'object' || data === null) { + throw new Error('Invalid version'); + } + const {version} = /** @type {import('core').SerializableObject} */ (data); + if (typeof version !== 'number') { + throw new Error('Invalid version'); + } this._remoteVersion = version; if (version !== this._version) { throw new Error(`Unsupported MeCab native messenger version ${version}. Yomitan supports version ${this._version}.`); @@ -238,9 +269,14 @@ export class Mecab { } } + /** + * @returns {void} + */ _clearPort() { - this._port.disconnect(); - this._port = null; + if (this._port !== null) { + this._port.disconnect(); + this._port = null; + } this._invocations.clear(); this._eventListeners.removeAllEventListeners(); this._sequence = 0; |