From 92afea0a484adbaad8968fe4b34a66eac8373bf4 Mon Sep 17 00:00:00 2001 From: FurryR Date: Sun, 29 Oct 2023 21:33:25 +0800 Subject: [PATCH] :bug: Fix several bugs --- .eslintrc.json | 3 +- .prettierrc.json | 6 +- README-ja_JP.md | 2 +- src/frontend/index.ts | 20 ++- src/injector/inject.ts | 159 ++++++++++++++++-------- src/l10n/l10n.json | 29 +++++ src/l10n/lang.ts | 60 --------- src/loader/dispatch/central-dispatch.ts | 21 +--- src/loader/dispatch/shared-dispatch.ts | 55 ++------ src/loader/dispatch/worker-dispatch.ts | 6 +- src/loader/loader.ts | 130 ++++++------------- src/loader/make-ctx.ts | 12 +- src/loader/sandbox.worker.ts | 10 +- src/typings/argument-type.ts | 2 +- src/typings/block-type.ts | 2 +- src/typings/reporter-scope.ts | 2 +- src/typings/target-type.ts | 2 +- src/util/cast.ts | 15 +-- src/util/color.ts | 8 +- src/util/maybe-format-message.ts | 6 +- tsconfig.json | 2 +- 21 files changed, 228 insertions(+), 324 deletions(-) create mode 100644 src/l10n/l10n.json delete mode 100644 src/l10n/lang.ts diff --git a/.eslintrc.json b/.eslintrc.json index 727b058..d9a14f0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,7 @@ }, "rules": { "semi": "error", - "indent": ["error", 4], + "indent": ["error", 4, {"SwitchCase": 1}], "dot-notation": "error", "block-scoped-var": "error", "capitalized-comments": "warn", @@ -30,7 +30,6 @@ "func-call-spacing": "error", "dot-location": ["error", "property"], "no-whitespace-before-property": "error", - "space-before-function-paren": "error", "space-unary-ops": ["error", { "words": true, "nonwords": false diff --git a/.prettierrc.json b/.prettierrc.json index 2f3f1d9..5855764 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -2,9 +2,9 @@ "semi": true, "tabWidth": 4, "singleQuote": true, - "trailingComma": "es5", + "trailingComma": "none", "arrowParens": "always", "bracketSameLine": true, "bracketSpacing": true, - "printWidth": 80 -} \ No newline at end of file + "printWidth": 100 +} diff --git a/README-ja_JP.md b/README-ja_JP.md index a636a48..6b331de 100644 --- a/README-ja_JP.md +++ b/README-ja_JP.md @@ -26,7 +26,7 @@ Chibi は Tampermonkey/GreasyMonkey に対応するユーザースクリプト - [X] CodingClip - [X] Cocrea - [X] Aerfaying -- [X] Cocrea 中国版 +- [X] Co-Create World - [X] XMW - [X] CodeLab - [X] 40code diff --git a/src/frontend/index.ts b/src/frontend/index.ts index b3b3256..75349ca 100644 --- a/src/frontend/index.ts +++ b/src/frontend/index.ts @@ -25,13 +25,10 @@ type MothDispatched = MothDispatchedAllocate | MothDispatchedLoad; */ function getExtensionInfo() { const processedExtInfo: MothExtensionInfo[] = []; - for (const [ - extId, - ext, - ] of window.chibi.loader.loadedScratchExtension.entries()) { + for (const [extId, ext] of window.chibi.loader.loadedScratchExtension.entries()) { processedExtInfo.push({ name: extId, - sandboxed: ext.env === 'sandboxed', + sandboxed: ext.env === 'sandboxed' }); } return processedExtInfo; @@ -44,29 +41,29 @@ async function messageHandler(event: MessageEvent) { if (event.origin !== 'https://chibi.codingclip.cc') return; if (!('type' in event.data)) return; switch ((event.data as MothDispatched).type) { + // Handshake: send current extension info in order to prepare frontend. case 'allocate': - // Handshake: send current extension info in order to prepare frontend. console.log('handshake with frontend'); dashboardWindow?.postMessage( { type: 'handshake', clientInfo: { version: Number(window.chibi.version), - url: window.location.host, - }, + url: window.location.host + } }, '*' ); dashboardWindow?.postMessage( { type: 'extension', - extensions: getExtensionInfo(), + extensions: getExtensionInfo() }, '*' ); break; case 'load': - // Load a extension. + // Load an extension. await window.chibi.loader.load( event.data.info.url, event.data.info.sandboxed ? 'sandboxed' : 'unsandboxed' @@ -74,7 +71,7 @@ async function messageHandler(event: MessageEvent) { dashboardWindow?.postMessage( { type: 'extension', - extensions: getExtensionInfo(), + extensions: getExtensionInfo() }, '*' ); @@ -96,4 +93,5 @@ function openFrontend(open: typeof window.open) { 'popup=yes,status=no,location=no,toolbar=no,menubar=no' ); } + export default openFrontend; diff --git a/src/injector/inject.ts b/src/injector/inject.ts index 74b71e5..2302879 100644 --- a/src/injector/inject.ts +++ b/src/injector/inject.ts @@ -4,7 +4,8 @@ import { ChibiLoader } from '../loader/loader'; import openFrontend from '../frontend'; import type VM from 'scratch-vm'; import type Blockly from 'scratch-blocks'; -import setup from '../l10n/lang'; +import * as l10n from '../l10n/l10n.json'; +import formatMessage from 'format-message'; interface ChibiCompatibleWorkspace extends Blockly.Workspace { registerButtonCallback(key: string, callback: Function): void; @@ -58,6 +59,7 @@ function getBlocklyInstance(vm: any): any | undefined { } return undefined; // Method failed. } + /** * Trap to get Virtual Machine instance. * @param open window.open function (compatible with ccw). @@ -68,7 +70,7 @@ export function trap(open: typeof window.open): Promise { // @ts-expect-error defined in webpack define plugin version: __CHIBI_VERSION__, registeredExtension: {}, - openFrontend: openFrontend.bind(null, open), + openFrontend: openFrontend.bind(null, open) }; log('Listening bind function...'); @@ -85,10 +87,7 @@ export function trap(open: typeof window.open): Promise { return oldBind.apply(this, args); } else if ( args[0] && - Object.prototype.hasOwnProperty.call( - args[0], - 'editingTarget' - ) && + Object.prototype.hasOwnProperty.call(args[0], 'editingTarget') && Object.prototype.hasOwnProperty.call(args[0], 'runtime') ) { log('VM detected!'); @@ -102,6 +101,7 @@ export function trap(open: typeof window.open): Promise { }; }); } + /** * Inject into the original virtual machine. * @param vm {ChibiCompatibleVM} Original virtual machine instance. @@ -110,7 +110,13 @@ export function inject(vm: ChibiCompatibleVM) { const loader = (window.chibi.loader = new ChibiLoader(vm)); const originalLoadFunc = vm.extensionManager.loadExtensionURL; const getLocale = vm.getLocale; - let lang = setup(getLocale ? getLocale.call(vm) : 'en'); + let format = formatMessage.namespace(); + format.setup({ + locale: getLocale ? getLocale.call(vm) : 'en', + missingTranslation: 'ignore', + generateId: (defaultMessage: string) => `${defaultMessage}`, + translations: l10n + }); vm.extensionManager.loadExtensionURL = async function ( extensionURL: string, ...args: unknown[] @@ -120,39 +126,36 @@ export function inject(vm: ChibiCompatibleVM) { try { const res = env ? confirm( - lang - .format('chibi.tryLoadInEnv') - .replace('{EXT_URL}', extensionURL) - .replace('{URL}', url) - .replace('{ENV}', env) + format('chibi.tryLoadInEnv', { + extensionURL, + url, + env + }) ) : confirm( - lang - .format('chibi.tryLoad') - .replace('{EXT_URL}', extensionURL) - .replace('{URL}', url) + format('chibi.tryLoadInEnv', { + extensionURL, + url + }) ); if (res) { await loader.load( url, (env ? env - : confirm(lang.format('chibi.loadInSandbox')) + : confirm(format('chibi.loadInSandbox')) ? 'sandboxed' : 'unsandboxed') as 'unsandboxed' | 'sandboxed' ); const extensionId = loader.getIdByUrl(url); // @ts-expect-error internal hack - vm.extensionManager._loadedExtensions.set( - extensionId, - 'Chibi' - ); + vm.extensionManager._loadedExtensions.set(extensionId, 'Chibi'); } else { // @ts-expect-error internal hack return originalLoadFunc.call(this, extensionURL, ...args); } } catch (e: unknown) { - error(lang.format('chibi.errorIgnored'), e); + error(format('chibi.errorIgnored'), e); } } else { // @ts-expect-error internal hack @@ -180,10 +183,7 @@ export function inject(vm: ChibiCompatibleVM) { }; const originalDrserializeFunc = vm.deserializeProject; - vm.deserializeProject = function ( - projectJSON: Record, - ...args: unknown[] - ) { + vm.deserializeProject = function (projectJSON: Record, ...args: unknown[]) { if (typeof projectJSON.extensionURLs === 'object') { for (const id in projectJSON.extensionURLs) { window.chibi.registeredExtension[id] = { @@ -191,7 +191,7 @@ export function inject(vm: ChibiCompatibleVM) { env: typeof projectJSON.extensionEnvs === 'object' ? projectJSON.extensionEnvs[id] - : 'sandboxed', + : 'sandboxed' }; } } @@ -201,7 +201,12 @@ export function inject(vm: ChibiCompatibleVM) { const originSetLocaleFunc = vm.setLocale; vm.setLocale = function (locale: string, ...args: unknown[]) { - lang = setup(locale); + format.setup({ + locale, + missingTranslation: 'ignore', + generateId: (defaultMessage: string) => `${defaultMessage}`, + translations: l10n + }); // @ts-expect-error internal hack const result = originSetLocaleFunc.call(this, locale, ...args); // @ts-expect-error lazy to extend VM interface @@ -209,29 +214,26 @@ export function inject(vm: ChibiCompatibleVM) { return result; }; // TODO: compiler support - const originalArgReporterBooleanFunc = - vm.runtime._primitives['argument_reporter_boolean']; + const originalArgReporterBooleanFunc = vm.runtime._primitives['argument_reporter_boolean']; vm.runtime._primitives['argument_reporter_boolean'] = function ( args: Record, ...otherArgs: unknown[] ) { const chibiFlag = args.VALUE; switch (chibiFlag) { + case '🧐 Chibi Installed?': + warn("'🧐 Chibi Installed?' is deprecated, use '🧐 Chibi?' instead."); + return true; case '🧐 Chibi?': return true; default: - return originalArgReporterBooleanFunc.call( - this, - args, - ...otherArgs - ); + return originalArgReporterBooleanFunc.call(this, args, ...otherArgs); } }; // Hack for ClipCC 3.2- versions if (typeof vm.ccExtensionManager === 'object') { - const originalGetOrderFunc = - vm.ccExtensionManager.getExtensionLoadOrder; + const originalGetOrderFunc = vm.ccExtensionManager.getExtensionLoadOrder; vm.ccExtensionManager.getExtensionLoadOrder = function ( extensions: string[], ...args: unknown[] @@ -242,7 +244,7 @@ export function inject(vm: ChibiCompatibleVM) { extensionId in window.chibi.registeredExtension ) { vm.ccExtensionManager!.info[extensionId] = { - api: 0, + api: 0 }; } } @@ -254,7 +256,70 @@ export function inject(vm: ChibiCompatibleVM) { // Blockly stuffs setTimeout(() => { const blockly = (window.chibi.blockly = getBlocklyInstance(vm)); + // deprecated: this method will be removed in the future. + if (!blockly) { + warn('Cannot find real blockly instance, try alternative method...'); + const originalProcedureCallback = + window.Blockly?.getMainWorkspace().toolboxCategoryCallbacks_.PROCEDURE; + if (!originalProcedureCallback) { + error('alternative method failed, stop injecting'); + return; + } + window.Blockly.getMainWorkspace().toolboxCategoryCallbacks_.PROCEDURE = function ( + workspace: ChibiCompatibleWorkspace, + ...args: unknown[] + ) { + const xmlList = originalProcedureCallback.call(this, workspace, ...args); + // Add separator and label + const sep = document.createElement('sep'); + sep.setAttribute('gap', '36'); + xmlList.push(sep); + const label = document.createElement('label'); + label.setAttribute('text', '😎 Chibi'); + xmlList.push(label); + // Add dashboard button + const dashboardButton = document.createElement('button'); + dashboardButton.setAttribute('text', format('chibi.openFrontend')); + dashboardButton.setAttribute('callbackKey', 'CHIBI_FRONTEND'); + workspace.registerButtonCallback('CHIBI_FRONTEND', () => { + window.chibi.openFrontend(); + }); + xmlList.push(dashboardButton); + + // Add load from url button + const sideloadButton = document.createElement('button'); + sideloadButton.setAttribute('text', format('chibi.sideload')); + sideloadButton.setAttribute('callbackKey', 'CHIBI_SIDELOAD_FROM_URL'); + workspace.registerButtonCallback('CHIBI_SIDELOAD_FROM_URL', () => { + const url = prompt(format('chibi.enterURL')); + if (!url) return; + const mode = confirm(format('chibi.loadInSandbox')) + ? 'sandboxed' + : 'unsandboxed'; + window.chibi.loader.load(url, mode); + }); + xmlList.push(sideloadButton); + + // Add chibi detection + const mutation = document.createElement('mutation'); + mutation.setAttribute('chibi', 'installed'); + const field = document.createElement('field'); + field.setAttribute('name', 'VALUE'); + field.innerHTML = '🧐 Chibi?'; + const block = document.createElement('block'); + block.setAttribute('type', 'argument_reporter_boolean'); + block.setAttribute('gap', '16'); + block.appendChild(field); + block.appendChild(mutation); + xmlList.push(block); + return xmlList; + }; + const workspace = window.Blockly.getMainWorkspace(); + workspace.getToolbox().refreshSelection(); + workspace.toolboxRefreshEnabled_ = true; + return; + } const originalAddCreateButton_ = blockly.Procedures.addCreateButton_; blockly.Procedures.addCreateButton_ = function ( workspace: ChibiCompatibleWorkspace, @@ -272,10 +337,7 @@ export function inject(vm: ChibiCompatibleVM) { // Add dashboard button const dashboardButton = document.createElement('button'); - dashboardButton.setAttribute( - 'text', - lang.format('chibi.openFrontend') - ); + dashboardButton.setAttribute('text', format('chibi.openFrontend')); dashboardButton.setAttribute('callbackKey', 'CHIBI_FRONTEND'); workspace.registerButtonCallback('CHIBI_FRONTEND', () => { window.chibi.openFrontend(); @@ -284,17 +346,12 @@ export function inject(vm: ChibiCompatibleVM) { // Add load from url button const sideloadButton = document.createElement('button'); - sideloadButton.setAttribute('text', lang.format('chibi.sideload')); - sideloadButton.setAttribute( - 'callbackKey', - 'CHIBI_SIDELOAD_FROM_URL' - ); + sideloadButton.setAttribute('text', format('chibi.sideload')); + sideloadButton.setAttribute('callbackKey', 'CHIBI_SIDELOAD_FROM_URL'); workspace.registerButtonCallback('CHIBI_SIDELOAD_FROM_URL', () => { - const url = prompt('Enter URL'); + const url = prompt(format('chibi.enterURL')); if (!url) return; - const mode = confirm(lang.format('chibi.loadInSandbox')) - ? 'sandboxed' - : 'unsandboxed'; + const mode = confirm(format('chibi.loadInSandbox')) ? 'sandboxed' : 'unsandboxed'; window.chibi.loader.load(url, mode); }); xmlList.push(sideloadButton); diff --git a/src/l10n/l10n.json b/src/l10n/l10n.json new file mode 100644 index 0000000..8388f31 --- /dev/null +++ b/src/l10n/l10n.json @@ -0,0 +1,29 @@ +{ + "zh-cn": { + "chibi.openFrontend": "打开面板", + "chibi.sideload": "从 URL 侧载扩展", + "chibi.errorIgnored": "在加载扩展扩展时出现错误。为了避免加载进程的中断,此错误已被忽略。", + "chibi.tryLoad": "🤨 项目正从 [url] 加载扩展 [extensionURL]。要加载么?", + "chibi.tryLoadInEnv": "🤨 项目正以 [env] 模式从 [url] 加载扩展 [extensionURL]。要加载么?", + "chibi.loadInSandbox": "🤨 要在沙箱模式中加载扩展么?", + "chibi.enterURL": "🌐 输入" + }, + "en": { + "chibi.openFrontend": "Open Frontend", + "chibi.sideload": "Sideload from URL", + "chibi.errorIgnored": "Error occurred while sideloading extension. To avoid interrupting the loading process, we chose to ignore this error.", + "chibi.tryLoad": "🤨 Project is trying to sideloading [extensionURL] from [url]. Do you want to load?", + "chibi.tryLoadInEnv": "🤨 Project is trying to sideloading [extensionURL] from [url] in [env] mode. Do you want to load?", + "chibi.loadInSandbox": "🤨 Do you want to load it in the sandbox?", + "chibi.enterURL": "🌐 Enter URL" + }, + "ja": { + "chibi.openFrontend": "ダッシュボードを開く", + "chibi.sideload": "URL から拡張機能を導入", + "chibi.errorIgnored": "拡張機能のサイドロード中でエラーが発生しました。ロードの中断を防ぐために、このエラーは無視しました。", + "chibi.tryLoad": "🤨 プロジェクトは [url] から [extensionURL] をサイドロードしています。ロードしますか?", + "chibi.tryLoadInEnv": "🤨 プロジェクトは [env] モードで、[url] から [extensionURL] をサイドロードしています。ロードしますか?", + "chibi.loadInSandbox": "🤨 サンドボックス環境でロードしますか?", + "chibi.enterURL": "🌐 URL を入力してください。" + } +} diff --git a/src/l10n/lang.ts b/src/l10n/lang.ts deleted file mode 100644 index c3d9ea7..0000000 --- a/src/l10n/lang.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { warn } from '../util/log'; -const l10n: Record> = { - 'zh-cn': { - 'chibi.openFrontend': '打开面板', - 'chibi.sideload': '从 URL 侧载扩展', - 'chibi.errorIgnored': - '在加载扩展扩展时出现错误。为了避免加载进程的中断,此错误已被忽略。', - 'chibi.tryLoad': '🤨 项目正从 {URL} 加载扩展 {EXT_URL}。要加载么?', - 'chibi.tryLoadInEnv': - '🤨 项目正以 {ENV} 模式从 {URL} 加载扩展 {EXT_URL}。要加载么?', - 'chibi.loadInSandbox': '🤨 要在沙箱模式中加载扩展么?', - 'chibi.enterURL': '🌐 输入', - }, - en: { - 'chibi.openFrontend': 'Open Frontend', - 'chibi.sideload': 'Sideload from URL', - 'chibi.errorIgnored': - 'Error occurred while sideloading extension. To avoid interrupting the loading process, we chose to ignore this error.', - 'chibi.tryLoad': - '🤨 Project is trying to sideloading {EXT_URL} from {URL}. Do you want to load?', - 'chibi.tryLoadInEnv': - '🤨 Project is trying to sideloading {EXT_URL} from {URL} in {ENV} mode. Do you want to load?', - 'chibi.loadInSandbox': '🤨 Do you want to load it in the sandbox?', - 'chibi.enterURL': '🌐 Enter URL', - }, - ja: { - 'chibi.openFrontend': 'ダッシュボードを開く', - 'chibi.sideload': 'URL から拡張機能を導入', - 'chibi.errorIgnored': - '拡張機能のサイドロード中でエラーが発生しました。ロードの中断を防ぐために、このエラーは無視しました。', - 'chibi.tryLoad': - '🤨 プロジェクトは {URL} から {EXT_URL} をサイドロードしています。ロードしますか?', - 'chibi.tryLoadInEnv': - '🤨 プロジェクトは {ENV} モードで、{URL} から {EXT_URL} をサイドロードしています。ロードしますか?', - 'chibi.loadInSandbox': '🤨 サンドボックス環境でロードしますか?', - 'chibi.enterURL': '🌐 URL を入力してください。', - }, -}; -class Language { - lang: string; - constructor(lang: string) { - this.lang = lang; - } - format(id: string) { - if (l10n[this.lang] && id in l10n[this.lang]) { - return l10n[this.lang][id]; - } else if (l10n['en'] && id in l10n['en']) { - return l10n['en'][id]; - } - return id; - } -} -export default function setup(lang: string): Language { - if (!(lang in l10n)) { - warn( - '🥺 This language is currently not supported. Falling back to English.' - ); - } - return new Language(lang); -} diff --git a/src/loader/dispatch/central-dispatch.ts b/src/loader/dispatch/central-dispatch.ts index 40e5406..db3b74b 100644 --- a/src/loader/dispatch/central-dispatch.ts +++ b/src/loader/dispatch/central-dispatch.ts @@ -12,8 +12,7 @@ class _CentralDispatch extends SharedDispatch { * The constructor we will use to recognize workers. * @type {Worker | null} */ - workerClass: typeof Worker | null = - typeof Worker === 'undefined' ? null : Worker; + workerClass: typeof Worker | null = typeof Worker === 'undefined' ? null : Worker; /** * List of workers attached to this dispatcher. * @type {Array} @@ -43,9 +42,7 @@ class _CentralDispatch extends SharedDispatch { const { provider, isRemote } = this._getServiceProvider(service); if (provider) { if (isRemote) { - throw new Error( - `Cannot use 'callSync' on remote provider for service ${service}.` - ); + throw new Error(`Cannot use 'callSync' on remote provider for service ${service}.`); } return provider[method].apply(provider, args); } @@ -59,9 +56,7 @@ class _CentralDispatch extends SharedDispatch { */ setServiceSync(service: string, provider: any) { if (this.services.hasOwnProperty(service)) { - console.warn( - `Central dispatch replacing existing service provider for ${service}` - ); + console.warn(`Central dispatch replacing existing service provider for ${service}`); } this.services[service] = provider; } @@ -94,9 +89,7 @@ class _CentralDispatch extends SharedDispatch { console.error(`Could not handshake with worker: ${e}`); }); } else { - console.warn( - 'Central dispatch ignoring attempt to add duplicate worker' - ); + console.warn('Central dispatch ignoring attempt to add duplicate worker'); } } /** @@ -112,10 +105,8 @@ class _CentralDispatch extends SharedDispatch { provider && { provider, isRemote: Boolean( - (this.workerClass && - provider instanceof this.workerClass) || - provider.isRemote - ), + (this.workerClass && provider instanceof this.workerClass) || provider.isRemote + ) } ); } diff --git a/src/loader/dispatch/shared-dispatch.ts b/src/loader/dispatch/shared-dispatch.ts index 3ac76ca..305734f 100644 --- a/src/loader/dispatch/shared-dispatch.ts +++ b/src/loader/dispatch/shared-dispatch.ts @@ -91,24 +91,13 @@ class SharedDispatch { * @param {*} [args] - the arguments to be copied to the method, if any. * @returns {Promise} - a promise for the return value of the service method. */ - transferCall( - service: string, - method: string, - transfer: unknown, - ...args: unknown[] - ) { + transferCall(service: string, method: string, transfer: unknown, ...args: unknown[]) { try { // @ts-expect-error TS(2339): It's not implemented. const { provider, isRemote } = this._getServiceProvider(service); if (provider) { if (isRemote) { - return this._remoteTransferCall( - provider, - service, - method, - transfer, - ...args - ); + return this._remoteTransferCall(provider, service, method, transfer, ...args); } const result = provider[method](...args); return Promise.resolve(result); @@ -142,13 +131,7 @@ class SharedDispatch { method: string, ...args: unknown[] ): Promise { - return this._remoteTransferCall( - provider, - service, - method, - null, - ...args - ); + return this._remoteTransferCall(provider, service, method, null, ...args); } /** * Like {@link transferCall}, but force the call to be posted through a particular communication channel. @@ -172,10 +155,7 @@ class SharedDispatch { args = this._purifyObject(args) as unknown[]; } if (transfer) { - provider.postMessage( - { service, method, responseId, args }, - transfer - ); + provider.postMessage({ service, method, responseId, args }, transfer); } else { provider.postMessage({ service, method, responseId, args }); } @@ -188,10 +168,7 @@ class SharedDispatch { * @returns {*} - a unique response ID for this set of callbacks. See {@link _deliverResponse}. * @protected */ - _storeCallbacks( - resolve: (value: unknown) => void, - reject: (value: unknown) => void - ) { + _storeCallbacks(resolve: (value: unknown) => void, reject: (value: unknown) => void) { const responseId = this.nextResponseId++; this.callbacks[responseId] = [resolve, reject]; return responseId; @@ -230,17 +207,11 @@ class SharedDispatch { if (message.service === 'dispatch') { promise = this._onDispatchMessage(worker, message); } else { - promise = this.call( - message.service, - message.method, - ...message.args - ); + promise = this.call(message.service, message.method, ...message.args); } } else if (typeof message.responseId === 'undefined') { console.error( - `Dispatch caught malformed message from a worker: ${JSON.stringify( - event - )}` + `Dispatch caught malformed message from a worker: ${JSON.stringify(event)}` ); } else { this._deliverResponse(message.responseId, message); @@ -248,21 +219,19 @@ class SharedDispatch { if (promise) { if (typeof message.responseId === 'undefined') { console.error( - `Dispatch message missing required response ID: ${JSON.stringify( - event - )}` + `Dispatch message missing required response ID: ${JSON.stringify(event)}` ); } else { promise.then( (result) => worker.postMessage({ responseId: message.responseId, - result, + result }), (error) => worker.postMessage({ responseId: message.responseId, - error: `${error}`, + error: `${error}` }) ); } @@ -309,9 +278,7 @@ class SharedDispatch { visited.add(obj); if (Array.isArray(obj)) { - return obj.map((item) => - this._purifyObject(item, visited, depth + 1) - ); + return obj.map((item) => this._purifyObject(item, visited, depth + 1)); } const result: Record = {}; for (const key in obj) { diff --git a/src/loader/dispatch/worker-dispatch.ts b/src/loader/dispatch/worker-dispatch.ts index 6f85e19..a246a95 100644 --- a/src/loader/dispatch/worker-dispatch.ts +++ b/src/loader/dispatch/worker-dispatch.ts @@ -61,9 +61,7 @@ class _WorkerDispatch extends SharedDispatch { */ setService(service: string, provider: unknown) { if (this.services.hasOwnProperty(service)) { - console.warn( - `Worker dispatch replacing existing service provider for ${service}` - ); + console.warn(`Worker dispatch replacing existing service provider for ${service}`); } this.services[service] = provider; return this.waitForConnection.then(() => @@ -83,7 +81,7 @@ class _WorkerDispatch extends SharedDispatch { const provider = this.services[service]; return { provider: provider || self, - isRemote: !provider, + isRemote: !provider }; } diff --git a/src/loader/loader.ts b/src/loader/loader.ts index 31c2625..f9c2ed1 100644 --- a/src/loader/loader.ts +++ b/src/loader/loader.ts @@ -7,7 +7,7 @@ import { ExtensionBlockMetadata, BlockType, MenuItems, - BlockArgs, + BlockArgs } from '../typings'; import { maybeFormatMessage } from '../util/maybe-format-message'; import { CentralDispatch as dispatch } from './dispatch/central-dispatch'; @@ -80,11 +80,7 @@ class ChibiLoader { ); } dispatch.setService('loader', this).catch((e: Error) => { - error( - `ChibiLoader was unable to register extension service: ${JSON.stringify( - e - )}` - ); + error(`ChibiLoader was unable to register extension service: ${JSON.stringify(e)}`); }); } @@ -93,10 +89,7 @@ class ChibiLoader { * @param {ExtensionClass | string} ext - Extension's data. * @param {'sandboxed' | 'unsandboxed'} env - Extension's running environment. */ - async load( - ext: string | ExtensionClass, - env: 'sandboxed' | 'unsandboxed' = 'sandboxed' - ) { + async load(ext: string | ExtensionClass, env: 'sandboxed' | 'unsandboxed' = 'sandboxed') { if (typeof ext === 'string') { switch (env) { case 'sandboxed': @@ -106,7 +99,7 @@ class ChibiLoader { this.pendingExtensions.push({ extensionURL: ext, resolve, - reject, + reject }); dispatch.addWorker(ExtensionWorker); }); @@ -115,15 +108,9 @@ class ChibiLoader { const originalScript = await response.text(); const closureFunc = new Function('Scratch', originalScript); const ctx = makeCtx(this.vm); - ctx.extensions.register = ( - extensionObj: ExtensionClass - ) => { + ctx.extensions.register = (extensionObj: ExtensionClass) => { const extensionInfo = extensionObj.getInfo(); - this._registerExtensionInfo( - extensionObj, - extensionInfo, - ext - ); + this._registerExtensionInfo(extensionObj, extensionInfo, ext); }; closureFunc(ctx); return; @@ -136,11 +123,7 @@ class ChibiLoader { // @ts-expect-error Load as builtin extension. const extensionObject = new ext(this.vm.runtime); const extensionInfo = extensionObject.getInfo() as ExtensionMetadata; - this._registerExtensionInfo( - extensionObject, - extensionInfo, - extensionInfo.id - ); + this._registerExtensionInfo(extensionObject, extensionInfo, extensionInfo.id); return extensionInfo; } @@ -156,11 +139,7 @@ class ChibiLoader { // It's running in worker if (typeof targetExt.instance === 'string') { const info = await dispatch.call(targetExt.instance, 'getInfo'); - const processedInfo = this._prepareExtensionInfo( - null, - info, - targetExt.instance - ); + const processedInfo = this._prepareExtensionInfo(null, info, targetExt.instance); // @ts-expect-error private method this.vm.runtime._refreshExtensionPrimitives(processedInfo); return processedInfo; @@ -230,17 +209,11 @@ class ChibiLoader { id: extensionInfo.id, url: extensionURL, info: extensionInfo, - instance: (extensionObject ?? serviceName) as - | ExtensionClass - | string, - env: serviceName ? 'sandboxed' : 'unsandboxed', + instance: (extensionObject ?? serviceName) as ExtensionClass | string, + env: serviceName ? 'sandboxed' : 'unsandboxed' } as ScratchExtension); } - extensionInfo = this._prepareExtensionInfo( - extensionObject, - extensionInfo, - serviceName - ); + extensionInfo = this._prepareExtensionInfo(extensionObject, extensionInfo, serviceName); // @ts-expect-error private method this.vm.runtime._registerExtensionPrimitives(extensionInfo); @@ -297,9 +270,9 @@ class ChibiLoader { } catch (e: unknown) { // TODO: more meaningful error reporting error( - `Error processing block: ${ - (e as Error).message - }, Block:\n${JSON.stringify(blockInfo)}` + `Error processing block: ${(e as Error).message}, Block:\n${JSON.stringify( + blockInfo + )}` ); } return results; @@ -340,7 +313,7 @@ class ChibiLoader { if (!menuInfo.items) { menuInfo = { // @ts-expect-error - items: menuInfo, + items: menuInfo }; menus[menuName] = menuInfo; } @@ -381,41 +354,29 @@ class ChibiLoader { */ const editingTarget = - this.vm.runtime.getEditingTarget() || - this.vm.runtime.getTargetForStage(); + this.vm.runtime.getEditingTarget() || this.vm.runtime.getTargetForStage(); const editingTargetID = editingTarget ? editingTarget.id : null; // @ts-expect-error private method - const extensionMessageContext = - this.vm.runtime.makeMessageContextForTarget(editingTarget); + const extensionMessageContext = this.vm.runtime.makeMessageContextForTarget(editingTarget); // TODO: Fix this to use dispatch.call when extensions are running in workers. const menuFunc = extensionObject[menuItemFunctionName] as ( editingTargetID: string | null ) => MenuItems; - const menuItems = menuFunc - .call(extensionObject, editingTargetID) - .map((item) => { - item = maybeFormatMessage(item, extensionMessageContext); - switch (typeof item) { - case 'object': - return [ - maybeFormatMessage( - item.text, - extensionMessageContext - ), - item.value, - ]; - case 'string': - return [item, item]; - default: - return item; - } - }); + const menuItems = menuFunc.call(extensionObject, editingTargetID).map((item) => { + item = maybeFormatMessage(item, extensionMessageContext); + switch (typeof item) { + case 'object': + return [maybeFormatMessage(item.text, extensionMessageContext), item.value]; + case 'string': + return [item, item]; + default: + return item; + } + }); if (!menuItems || menuItems.length < 1) { - throw new Error( - `Extension menu returned no items: ${menuItemFunctionName}` - ); + throw new Error(`Extension menu returned no items: ${menuItemFunctionName}`); } return menuItems; } @@ -439,12 +400,11 @@ class ChibiLoader { blockType: BlockType.COMMAND, terminal: false, blockAllThreads: false, - arguments: {}, + arguments: {} }, blockInfo ); - blockInfo.opcode = - blockInfo.opcode && this._sanitizeID(blockInfo.opcode); + blockInfo.opcode = blockInfo.opcode && this._sanitizeID(blockInfo.opcode); blockInfo.text = blockInfo.text || blockInfo.opcode; switch (blockInfo.blockType) { @@ -472,21 +432,13 @@ class ChibiLoader { : blockInfo.opcode; const getBlockInfo = blockInfo.isDynamic - ? (args: BlockArgs) => - args && args.mutation && args.mutation.blockInfo + ? (args: BlockArgs) => args && args.mutation && args.mutation.blockInfo : () => blockInfo; const callBlockFunc = (() => { // Maybe there's a worker if (extensionObject === null) { - if ( - serviceName && - dispatch._isRemoteService(serviceName) - ) { - return ( - args: BlockArgs, - _util: unknown, - realBlockInfo: unknown - ) => + if (serviceName && dispatch._isRemoteService(serviceName)) { + return (args: BlockArgs, _util: unknown, realBlockInfo: unknown) => dispatch.call( serviceName, funcName, @@ -495,24 +447,16 @@ class ChibiLoader { realBlockInfo ); } - warn( - `Could not find extension block function called ${funcName}` - ); + warn(`Could not find extension block function called ${funcName}`); // eslint-disable-next-line @typescript-eslint/no-empty-function return () => {}; } if (!extensionObject[funcName]) { // The function might show up later as a dynamic property of the service object - warn( - `Could not find extension block function called ${funcName}` - ); + warn(`Could not find extension block function called ${funcName}`); } - return ( - args: BlockArgs, - util: unknown, - realBlockInfo: unknown - ) => + return (args: BlockArgs, util: unknown, realBlockInfo: unknown) => // @ts-expect-error extensionObject[funcName](args, util, realBlockInfo); })(); diff --git a/src/loader/make-ctx.ts b/src/loader/make-ctx.ts index 906d1ae..298db22 100644 --- a/src/loader/make-ctx.ts +++ b/src/loader/make-ctx.ts @@ -3,7 +3,7 @@ import { TargetType, ArgumentType, ReporterScope, - StandardScratchExtensionClass as ExtensionClass, + StandardScratchExtensionClass as ExtensionClass } from '../typings'; import { Cast } from '../util/cast'; import formatMessage, { Message } from 'format-message'; @@ -26,7 +26,7 @@ export interface Context { renderer?: Renderer; } /** - * i10n support for Chibi extensions. + * I10n support for Chibi extensions. * @param vm Virtual machine instance. Optional. * @returns Something like Scratch.translate. */ @@ -38,7 +38,7 @@ function createTranslate(vm?: VM) { // Already in the expected format } else if (typeof message === 'string') { message = { - default: message, + default: message }; } else { throw new Error('unsupported data type in translate()'); @@ -64,7 +64,7 @@ function createTranslate(vm?: VM) { locale: getLocale(), missingTranslation: 'ignore', generateId, - translations: storedTranslations, + translations: storedTranslations }); }; @@ -96,9 +96,9 @@ export function makeCtx(vm?: VM) { throw new Error('not implemented'); }, unsandboxed: !!vm, - chibi: true, + chibi: true }, - translate: createTranslate(vm), + translate: createTranslate(vm) }; if (vm) { ctx.vm = vm; diff --git a/src/loader/sandbox.worker.ts b/src/loader/sandbox.worker.ts index 6330f3d..d1a7378 100644 --- a/src/loader/sandbox.worker.ts +++ b/src/loader/sandbox.worker.ts @@ -47,12 +47,7 @@ class ExtensionWorker { const promise = dispatch .setService(serviceName, extensionObject) .then(() => - dispatch.call( - 'loader', - 'registerExtensionService', - this.extensionURL, - serviceName - ) + dispatch.call('loader', 'registerExtensionService', this.extensionURL, serviceName) ); if (this.initialRegistrations) { this.initialRegistrations.push(promise); @@ -67,7 +62,6 @@ globalThis.Scratch = makeCtx(); * Expose only specific parts of the worker to extensions. */ const extensionWorker = new ExtensionWorker(); -globalThis.Scratch.extensions.register = - extensionWorker.register.bind(extensionWorker); +globalThis.Scratch.extensions.register = extensionWorker.register.bind(extensionWorker); export default null as unknown as any; diff --git a/src/typings/argument-type.ts b/src/typings/argument-type.ts index 9b79979..a931780 100644 --- a/src/typings/argument-type.ts +++ b/src/typings/argument-type.ts @@ -34,7 +34,7 @@ enum ArgumentType { /** * Inline image on block (as part of the label) */ - IMAGE = 'image', + IMAGE = 'image' } export { ArgumentType }; diff --git a/src/typings/block-type.ts b/src/typings/block-type.ts index a810959..76382c1 100644 --- a/src/typings/block-type.ts +++ b/src/typings/block-type.ts @@ -41,7 +41,7 @@ enum BlockType { /** * Lambda reporter with callable function. */ - Lambda = 'Lambda', + Lambda = 'Lambda' } export { BlockType }; diff --git a/src/typings/reporter-scope.ts b/src/typings/reporter-scope.ts index 2364721..5a2fa1a 100644 --- a/src/typings/reporter-scope.ts +++ b/src/typings/reporter-scope.ts @@ -12,7 +12,7 @@ enum ReporterScope { * This reporter's value is specific to a particular target/sprite. * Another target may have a different value or may not even have a value. */ - TARGET = 'target', + TARGET = 'target' } export { ReporterScope }; diff --git a/src/typings/target-type.ts b/src/typings/target-type.ts index 0009163..bc18e15 100644 --- a/src/typings/target-type.ts +++ b/src/typings/target-type.ts @@ -10,6 +10,6 @@ enum TargetType { /** * Rendered target which cannot move but can change backdrops */ - STAGE = 'stage', + STAGE = 'stage' } export { TargetType }; diff --git a/src/util/cast.ts b/src/util/cast.ts index 103d0d8..c91141f 100644 --- a/src/util/cast.ts +++ b/src/util/cast.ts @@ -84,11 +84,7 @@ class Cast { } if (typeof value === 'string') { // These specific strings are treated as false in Scratch. - if ( - value === '' || - value === '0' || - value.toLowerCase() === 'false' - ) { + if (value === '' || value === '0' || value.toLowerCase() === 'false') { return false; } // All other strings treated as true. @@ -141,9 +137,7 @@ class Cast { * @return {boolean} True if the argument is all white spaces or null / empty. */ static isWhiteSpace(val: unknown) { - return ( - val === null || (typeof val === 'string' && val.trim().length === 0) - ); + return val === null || (typeof val === 'string' && val.trim().length === 0); } /** @@ -176,10 +170,7 @@ class Cast { return 0; } // Handle the special case of Infinity - if ( - (n1 === Infinity && n2 === Infinity) || - (n1 === -Infinity && n2 === -Infinity) - ) { + if ((n1 === Infinity && n2 === Infinity) || (n1 === -Infinity && n2 === -Infinity)) { return 0; } // Compare as numbers. diff --git a/src/util/color.ts b/src/util/color.ts index b8249fb..3ca1df8 100644 --- a/src/util/color.ts +++ b/src/util/color.ts @@ -79,7 +79,7 @@ class Color { return { r: (parsed >> 16) & 0xff, g: (parsed >> 8) & 0xff, - b: parsed & 0xff, + b: parsed & 0xff }; } else if (hex.length === 3) { const r = (parsed >> 8) & 0xf; @@ -88,7 +88,7 @@ class Color { return { r: (r << 4) | r, g: (g << 4) | g, - b: (b << 4) | b, + b: (b << 4) | b }; } return null; @@ -180,7 +180,7 @@ class Color { return { r: Math.floor(r * 255), g: Math.floor(g * 255), - b: Math.floor(b * 255), + b: Math.floor(b * 255) }; } @@ -223,7 +223,7 @@ class Color { return { r: fraction0 * rgb0.r + fraction1 * rgb1.r, g: fraction0 * rgb0.g + fraction1 * rgb1.g, - b: fraction0 * rgb0.b + fraction1 * rgb1.b, + b: fraction0 * rgb0.b + fraction1 * rgb1.b }; } } diff --git a/src/util/maybe-format-message.ts b/src/util/maybe-format-message.ts index fbedb31..e060710 100644 --- a/src/util/maybe-format-message.ts +++ b/src/util/maybe-format-message.ts @@ -7,11 +7,7 @@ import formatMessage from 'format-message'; * @param {string} [locale] - the locale to pass to `formatMessage` if it gets called. * @return {string|*} - the formatted message OR the original `maybeMessage` input. */ -export const maybeFormatMessage = function ( - maybeMessage?: any, - args?: any, - locale?: any -) { +export const maybeFormatMessage = function (maybeMessage?: any, args?: any, locale?: any) { if (maybeMessage && maybeMessage.id && maybeMessage.default) { return formatMessage(maybeMessage, args, locale); } diff --git a/tsconfig.json b/tsconfig.json index eff8b88..aba4843 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,7 +42,7 @@ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true, /* Enable importing .json files. */ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */