diff options
Diffstat (limited to 'ext/js/comm/api.js')
-rw-r--r-- | ext/js/comm/api.js | 356 |
1 files changed, 309 insertions, 47 deletions
diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 05f95464..26218595 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -16,184 +16,428 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {deferPromise, deserializeError, isObject} from '../core.js'; +import {deferPromise} from '../core.js'; +import {ExtensionError} from '../core/extension-error.js'; export class API { + /** + * @param {import('../yomitan.js').Yomitan} yomitan + */ constructor(yomitan) { + /** @type {import('../yomitan.js').Yomitan} */ this._yomitan = yomitan; } + /** + * @param {import('api').OptionsGetDetails['optionsContext']} optionsContext + * @returns {Promise<import('api').OptionsGetResult>} + */ optionsGet(optionsContext) { - return this._invoke('optionsGet', {optionsContext}); + /** @type {import('api').OptionsGetDetails} */ + const details = {optionsContext}; + return this._invoke('optionsGet', details); } + /** + * @returns {Promise<import('api').OptionsGetFullResult>} + */ optionsGetFull() { return this._invoke('optionsGetFull'); } + /** + * @param {import('api').TermsFindDetails['text']} text + * @param {import('api').TermsFindDetails['details']} details + * @param {import('api').TermsFindDetails['optionsContext']} optionsContext + * @returns {Promise<import('api').TermsFindResult>} + */ termsFind(text, details, optionsContext) { - return this._invoke('termsFind', {text, details, optionsContext}); - } - + /** @type {import('api').TermsFindDetails} */ + const details2 = {text, details, optionsContext}; + return this._invoke('termsFind', details2); + } + + /** + * @param {import('api').ParseTextDetails['text']} text + * @param {import('api').ParseTextDetails['optionsContext']} optionsContext + * @param {import('api').ParseTextDetails['scanLength']} scanLength + * @param {import('api').ParseTextDetails['useInternalParser']} useInternalParser + * @param {import('api').ParseTextDetails['useMecabParser']} useMecabParser + * @returns {Promise<import('api').ParseTextResult>} + */ parseText(text, optionsContext, scanLength, useInternalParser, useMecabParser) { - return this._invoke('parseText', {text, optionsContext, scanLength, useInternalParser, useMecabParser}); + /** @type {import('api').ParseTextDetails} */ + const details = {text, optionsContext, scanLength, useInternalParser, useMecabParser}; + return this._invoke('parseText', details); } + /** + * @param {import('api').KanjiFindDetails['text']} text + * @param {import('api').KanjiFindDetails['optionsContext']} optionsContext + * @returns {Promise<import('api').KanjiFindResult>} + */ kanjiFind(text, optionsContext) { - return this._invoke('kanjiFind', {text, optionsContext}); + /** @type {import('api').KanjiFindDetails} */ + const details = {text, optionsContext}; + return this._invoke('kanjiFind', details); } + /** + * @returns {Promise<import('api').IsAnkiConnectedResult>} + */ isAnkiConnected() { return this._invoke('isAnkiConnected'); } + /** + * @returns {Promise<import('api').GetAnkiConnectVersionResult>} + */ getAnkiConnectVersion() { return this._invoke('getAnkiConnectVersion'); } + /** + * @param {import('api').AddAnkiNoteDetails['note']} note + * @returns {Promise<import('api').AddAnkiNoteResult>} + */ addAnkiNote(note) { - return this._invoke('addAnkiNote', {note}); + /** @type {import('api').AddAnkiNoteDetails} */ + const details = {note}; + return this._invoke('addAnkiNote', details); } + /** + * @param {import('api').GetAnkiNoteInfoDetails['notes']} notes + * @param {import('api').GetAnkiNoteInfoDetails['fetchAdditionalInfo']} fetchAdditionalInfo + * @returns {Promise<import('api').GetAnkiNoteInfoResult>} + */ getAnkiNoteInfo(notes, fetchAdditionalInfo) { - return this._invoke('getAnkiNoteInfo', {notes, fetchAdditionalInfo}); - } - + /** @type {import('api').GetAnkiNoteInfoDetails} */ + const details = {notes, fetchAdditionalInfo}; + return this._invoke('getAnkiNoteInfo', details); + } + + /** + * @param {import('api').InjectAnkiNoteMediaDetails['timestamp']} timestamp + * @param {import('api').InjectAnkiNoteMediaDetails['definitionDetails']} definitionDetails + * @param {import('api').InjectAnkiNoteMediaDetails['audioDetails']} audioDetails + * @param {import('api').InjectAnkiNoteMediaDetails['screenshotDetails']} screenshotDetails + * @param {import('api').InjectAnkiNoteMediaDetails['clipboardDetails']} clipboardDetails + * @param {import('api').InjectAnkiNoteMediaDetails['dictionaryMediaDetails']} dictionaryMediaDetails + * @returns {Promise<import('api').InjectAnkiNoteMediaResult>} + */ injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) { - return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}); + /** @type {import('api').InjectAnkiNoteMediaDetails} */ + const details = {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}; + return this._invoke('injectAnkiNoteMedia', details); } + /** + * @param {import('api').NoteViewDetails['noteId']} noteId + * @param {import('api').NoteViewDetails['mode']} mode + * @param {import('api').NoteViewDetails['allowFallback']} allowFallback + * @returns {Promise<import('api').NoteViewResult>} + */ noteView(noteId, mode, allowFallback) { - return this._invoke('noteView', {noteId, mode, allowFallback}); + /** @type {import('api').NoteViewDetails} */ + const details = {noteId, mode, allowFallback}; + return this._invoke('noteView', details); } + /** + * @param {import('api').SuspendAnkiCardsForNoteDetails['noteId']} noteId + * @returns {Promise<import('api').SuspendAnkiCardsForNoteResult>} + */ suspendAnkiCardsForNote(noteId) { - return this._invoke('suspendAnkiCardsForNote', {noteId}); + /** @type {import('api').SuspendAnkiCardsForNoteDetails} */ + const details = {noteId}; + return this._invoke('suspendAnkiCardsForNote', details); } + /** + * @param {import('api').GetTermAudioInfoListDetails['source']} source + * @param {import('api').GetTermAudioInfoListDetails['term']} term + * @param {import('api').GetTermAudioInfoListDetails['reading']} reading + * @returns {Promise<import('api').GetTermAudioInfoListResult>} + */ getTermAudioInfoList(source, term, reading) { - return this._invoke('getTermAudioInfoList', {source, term, reading}); + /** @type {import('api').GetTermAudioInfoListDetails} */ + const details = {source, term, reading}; + return this._invoke('getTermAudioInfoList', details); } + /** + * @param {import('api').CommandExecDetails['command']} command + * @param {import('api').CommandExecDetails['params']} [params] + * @returns {Promise<import('api').CommandExecResult>} + */ commandExec(command, params) { - return this._invoke('commandExec', {command, params}); + /** @type {import('api').CommandExecDetails} */ + const details = {command, params}; + return this._invoke('commandExec', details); } + /** + * @param {import('api').SendMessageToFrameDetails['frameId']} frameId + * @param {import('api').SendMessageToFrameDetails['action']} action + * @param {import('api').SendMessageToFrameDetails['params']} [params] + * @returns {Promise<import('api').SendMessageToFrameResult>} + */ sendMessageToFrame(frameId, action, params) { - return this._invoke('sendMessageToFrame', {frameId, action, params}); + /** @type {import('api').SendMessageToFrameDetails} */ + const details = {frameId, action, params}; + return this._invoke('sendMessageToFrame', details); } + /** + * @param {import('api').BroadcastTabDetails['action']} action + * @param {import('api').BroadcastTabDetails['params']} params + * @returns {Promise<import('api').BroadcastTabResult>} + */ broadcastTab(action, params) { - return this._invoke('broadcastTab', {action, params}); + /** @type {import('api').BroadcastTabDetails} */ + const details = {action, params}; + return this._invoke('broadcastTab', details); } + /** + * @returns {Promise<import('api').FrameInformationGetResult>} + */ frameInformationGet() { return this._invoke('frameInformationGet'); } + /** + * @param {import('api').InjectStylesheetDetails['type']} type + * @param {import('api').InjectStylesheetDetails['value']} value + * @returns {Promise<import('api').InjectStylesheetResult>} + */ injectStylesheet(type, value) { - return this._invoke('injectStylesheet', {type, value}); + /** @type {import('api').InjectStylesheetDetails} */ + const details = {type, value}; + return this._invoke('injectStylesheet', details); } + /** + * @param {import('api').GetStylesheetContentDetails['url']} url + * @returns {Promise<import('api').GetStylesheetContentResult>} + */ getStylesheetContent(url) { - return this._invoke('getStylesheetContent', {url}); + /** @type {import('api').GetStylesheetContentDetails} */ + const details = {url}; + return this._invoke('getStylesheetContent', details); } + /** + * @returns {Promise<import('api').GetEnvironmentInfoResult>} + */ getEnvironmentInfo() { return this._invoke('getEnvironmentInfo'); } + /** + * @returns {Promise<import('api').ClipboardGetResult>} + */ clipboardGet() { return this._invoke('clipboardGet'); } + /** + * @returns {Promise<import('api').GetDisplayTemplatesHtmlResult>} + */ getDisplayTemplatesHtml() { return this._invoke('getDisplayTemplatesHtml'); } + /** + * @returns {Promise<import('api').GetZoomResult>} + */ getZoom() { return this._invoke('getZoom'); } + /** + * @returns {Promise<import('api').GetDefaultAnkiFieldTemplatesResult>} + */ getDefaultAnkiFieldTemplates() { return this._invoke('getDefaultAnkiFieldTemplates'); } + /** + * @returns {Promise<import('api').GetDictionaryInfoResult>} + */ getDictionaryInfo() { return this._invoke('getDictionaryInfo'); } + /** + * @returns {Promise<import('api').PurgeDatabaseResult>} + */ purgeDatabase() { return this._invoke('purgeDatabase'); } + /** + * @param {import('api').GetMediaDetails['targets']} targets + * @returns {Promise<import('api').GetMediaResult>} + */ getMedia(targets) { - return this._invoke('getMedia', {targets}); + /** @type {import('api').GetMediaDetails} */ + const details = {targets}; + return this._invoke('getMedia', details); } + /** + * @param {import('api').LogDetails['error']} error + * @param {import('api').LogDetails['level']} level + * @param {import('api').LogDetails['context']} context + * @returns {Promise<import('api').LogResult>} + */ log(error, level, context) { - return this._invoke('log', {error, level, context}); + /** @type {import('api').LogDetails} */ + const details = {error, level, context}; + return this._invoke('log', details); } + /** + * @returns {Promise<import('api').LogIndicatorClearResult>} + */ logIndicatorClear() { return this._invoke('logIndicatorClear'); } + /** + * @param {import('api').ModifySettingsDetails['targets']} targets + * @param {import('api').ModifySettingsDetails['source']} source + * @returns {Promise<import('api').ModifySettingsResult>} + */ modifySettings(targets, source) { - return this._invoke('modifySettings', {targets, source}); + const details = {targets, source}; + return this._invoke('modifySettings', details); } + /** + * @param {import('api').GetSettingsDetails['targets']} targets + * @returns {Promise<import('api').GetSettingsResult>} + */ getSettings(targets) { - return this._invoke('getSettings', {targets}); + /** @type {import('api').GetSettingsDetails} */ + const details = {targets}; + return this._invoke('getSettings', details); } + /** + * @param {import('api').SetAllSettingsDetails['value']} value + * @param {import('api').SetAllSettingsDetails['source']} source + * @returns {Promise<import('api').SetAllSettingsResult>} + */ setAllSettings(value, source) { - return this._invoke('setAllSettings', {value, source}); + /** @type {import('api').SetAllSettingsDetails} */ + const details = {value, source}; + return this._invoke('setAllSettings', details); } + /** + * @param {import('api').GetOrCreateSearchPopupDetails} details + * @returns {Promise<import('api').GetOrCreateSearchPopupResult>} + */ getOrCreateSearchPopup(details) { - return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {}); + return this._invoke('getOrCreateSearchPopup', details); } + /** + * @param {import('api').IsTabSearchPopupDetails['tabId']} tabId + * @returns {Promise<import('api').IsTabSearchPopupResult>} + */ isTabSearchPopup(tabId) { - return this._invoke('isTabSearchPopup', {tabId}); + /** @type {import('api').IsTabSearchPopupDetails} */ + const details = {tabId}; + return this._invoke('isTabSearchPopup', details); } + /** + * @param {import('api').TriggerDatabaseUpdatedDetails['type']} type + * @param {import('api').TriggerDatabaseUpdatedDetails['cause']} cause + * @returns {Promise<import('api').TriggerDatabaseUpdatedResult>} + */ triggerDatabaseUpdated(type, cause) { - return this._invoke('triggerDatabaseUpdated', {type, cause}); + /** @type {import('api').TriggerDatabaseUpdatedDetails} */ + const details = {type, cause}; + return this._invoke('triggerDatabaseUpdated', details); } + /** + * @returns {Promise<import('api').TestMecabResult>} + */ testMecab() { - return this._invoke('testMecab', {}); + return this._invoke('testMecab'); } + /** + * @param {import('api').TextHasJapaneseCharactersDetails['text']} text + * @returns {Promise<import('api').TextHasJapaneseCharactersResult>} + */ textHasJapaneseCharacters(text) { - return this._invoke('textHasJapaneseCharacters', {text}); + /** @type {import('api').TextHasJapaneseCharactersDetails} */ + const details = {text}; + return this._invoke('textHasJapaneseCharacters', details); } + /** + * @param {import('api').GetTermFrequenciesDetails['termReadingList']} termReadingList + * @param {import('api').GetTermFrequenciesDetails['dictionaries']} dictionaries + * @returns {Promise<import('api').GetTermFrequenciesResult>} + */ getTermFrequencies(termReadingList, dictionaries) { - return this._invoke('getTermFrequencies', {termReadingList, dictionaries}); + /** @type {import('api').GetTermFrequenciesDetails} */ + const details = {termReadingList, dictionaries}; + return this._invoke('getTermFrequencies', details); } + /** + * @param {import('api').FindAnkiNotesDetails['query']} query + * @returns {Promise<import('api').FindAnkiNotesResult>} + */ findAnkiNotes(query) { - return this._invoke('findAnkiNotes', {query}); + /** @type {import('api').FindAnkiNotesDetails} */ + const details = {query}; + return this._invoke('findAnkiNotes', details); } + /** + * @param {import('api').LoadExtensionScriptsDetails['files']} files + * @returns {Promise<import('api').LoadExtensionScriptsResult>} + */ loadExtensionScripts(files) { - return this._invoke('loadExtensionScripts', {files}); + /** @type {import('api').LoadExtensionScriptsDetails} */ + const details = {files}; + return this._invoke('loadExtensionScripts', details); } + /** + * @param {import('api').OpenCrossFramePortDetails['targetTabId']} targetTabId + * @param {import('api').OpenCrossFramePortDetails['targetFrameId']} targetFrameId + * @returns {Promise<import('api').OpenCrossFramePortResult>} + */ openCrossFramePort(targetTabId, targetFrameId) { return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); } // Utilities - _createActionPort(timeout=5000) { + /** + * @param {number} timeout + * @returns {Promise<chrome.runtime.Port>} + */ + _createActionPort(timeout) { return new Promise((resolve, reject) => { + /** @type {?import('core').Timeout} */ let timer = null; const portDetails = deferPromise(); + /** + * @param {chrome.runtime.Port} port + */ const onConnect = async (port) => { try { const {name: expectedName, id: expectedId} = await portDetails.promise; @@ -210,6 +454,9 @@ export class API { resolve(port); }; + /** + * @param {Error} e + */ const onError = (e) => { if (timer !== null) { clearTimeout(timer); @@ -227,14 +474,24 @@ export class API { }); } - _invokeWithProgress(action, params, onProgress, timeout=5000) { + /** + * @template [TReturn=unknown] + * @param {string} action + * @param {import('core').SerializableObject} params + * @param {?(...args: unknown[]) => void} onProgress0 + * @param {number} [timeout] + * @returns {Promise<TReturn>} + */ + _invokeWithProgress(action, params, onProgress0, timeout=5000) { return new Promise((resolve, reject) => { + /** @type {?chrome.runtime.Port} */ let port = null; - if (typeof onProgress !== 'function') { - onProgress = () => {}; - } + const onProgress = typeof onProgress0 === 'function' ? onProgress0 : () => {}; + /** + * @param {import('backend').InvokeWithProgressResponseMessage<TReturn>} message + */ const onMessage = (message) => { switch (message.type) { case 'progress': @@ -250,7 +507,7 @@ export class API { break; case 'error': cleanup(); - reject(deserializeError(message.data)); + reject(ExtensionError.deserialize(message.data)); break; } }; @@ -267,7 +524,6 @@ export class API { port.disconnect(); port = null; } - onProgress = null; }; (async () => { @@ -281,20 +537,23 @@ export class API { const fragmentSize = 1e7; // 10 MB for (let i = 0, ii = messageString.length; i < ii; i += fragmentSize) { const data = messageString.substring(i, i + fragmentSize); - port.postMessage({action: 'fragment', data}); + port.postMessage(/** @type {import('backend').InvokeWithProgressRequestFragmentMessage} */ ({action: 'fragment', data})); } - port.postMessage({action: 'invoke'}); + port.postMessage(/** @type {import('backend').InvokeWithProgressRequestInvokeMessage} */ ({action: 'invoke'})); } catch (e) { cleanup(); reject(e); - } finally { - action = null; - params = null; } })(); }); } + /** + * @template [TReturn=unknown] + * @param {string} action + * @param {import('core').SerializableObject} [params] + * @returns {Promise<TReturn>} + */ _invoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { @@ -303,7 +562,7 @@ export class API { this._checkLastError(chrome.runtime.lastError); if (response !== null && typeof response === 'object') { if (typeof response.error !== 'undefined') { - reject(deserializeError(response.error)); + reject(ExtensionError.deserialize(response.error)); } else { resolve(response.result); } @@ -318,7 +577,10 @@ export class API { }); } - _checkLastError() { + /** + * @param {chrome.runtime.LastError|undefined} _ignore + */ + _checkLastError(_ignore) { // NOP } } |