summaryrefslogtreecommitdiff
path: root/ext/js/comm/mecab.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/comm/mecab.js')
-rw-r--r--ext/js/comm/mecab.js84
1 files changed, 60 insertions, 24 deletions
diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js
index c7314605..072b42f3 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: number}>} */
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;