From 67f55226f744750152c8578e0653153dc1d71e2f Mon Sep 17 00:00:00 2001 From: FurryR Date: Fri, 26 Apr 2024 21:59:05 +0800 Subject: [PATCH] :bug: fix: eureka is not working on player page --- src/index.ts | 11 +++-- src/injector/inject.ts | 106 +++++++++++++++++++++++++---------------- 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/index.ts b/src/index.ts index 345fe91..335525d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,16 @@ -import { trap, injectVM, injectBlockly } from './injector/inject'; +import { getVMInstance, getBlocklyInstance, injectVM, injectBlockly, initalizeEureka } from './injector/inject'; import { log } from './util/log'; -const open = window.open; // @ts-expect-error defined in webpack define plugin log(`eureka-loader ${__EUREKA_VERSION__}`); -// Try injecting chibi into the page. -const [vm, blockly] = await trap(open); +// Initialize Eureka global object. +initalizeEureka(); +// Try injecting Eureka into the page. +const vm = await getVMInstance(); if (vm) { // Alright we got the virtual machine, start the injection. window.eureka.vm = vm; injectVM(vm); - injectBlockly(blockly); + getBlocklyInstance(vm).then(blockly => injectBlockly(blockly)); } else { // This is not a Scratch page, stop injecting. log(`Cannot find vm in this page, stop injecting.`); diff --git a/src/injector/inject.ts b/src/injector/inject.ts index 93a3340..6510b13 100644 --- a/src/injector/inject.ts +++ b/src/injector/inject.ts @@ -25,7 +25,11 @@ interface EurekaCompatibleVM extends VM { }; setLocale?: (locale: string, ...args: unknown[]) => unknown; getLocale?: () => string; - _loadExtensions?: (extensionIDs: Set, extensionURLs: Map, ...args: unknown[]) => Promise; + _loadExtensions?: ( + extensionIDs: Set, + extensionURLs: Map, + ...args: unknown[] + ) => Promise; } const MAX_LISTENING_MS = 30 * 1000; @@ -63,7 +67,7 @@ function getExtensionIdForOpcode (opcode: string) { * @param vm Virtual machine instance. For some reasons we cannot use VM here. * @returns Blockly instance. */ -async function getBlocklyInstance (vm: EurekaCompatibleVM): Promise { +export async function getBlocklyInstance (vm: EurekaCompatibleVM): Promise { function getBlocklyInstanceInternal (): any | null { // Hijack Function.prototype.apply to get React element instance. function hijack (fn: (...args: unknown[]) => unknown) { @@ -124,50 +128,54 @@ async function getBlocklyInstance (vm: EurekaCompatibleVM): Promise { +export function initalizeEureka () { window.eureka = { // @ts-expect-error defined in webpack define plugin version: __EUREKA_VERSION__, registeredExtension: {}, settings: settings, - openFrontend: openFrontend.bind(null, open) + openFrontend: openFrontend.bind(null, window.open) }; +} +/** + * Trap to get Virtual Machine instance. + * @return Callback promise. After that you could use window.eureka.vm to get the virtual machine. + */ +export async function getVMInstance (): Promise { log('Listening bind function...'); const oldBind = Function.prototype.bind; - // @ts-expect-error lazy to fix - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => { - log('Cannot find vm instance, stop listening.'); - Function.prototype.bind = oldBind; - reject(); - }, MAX_LISTENING_MS); - - Function.prototype.bind = function (...args) { - if (Function.prototype.bind === oldBind) { - return oldBind.apply(this, args); - } else if ( - args[0] && - Object.prototype.hasOwnProperty.call(args[0], 'editingTarget') && - Object.prototype.hasOwnProperty.call(args[0], 'runtime') - ) { - log('VM detected!'); + try { + const vm = await new Promise((resolve, reject) => { + const timeoutId = setTimeout(() => { + log('Cannot find vm instance, stop listening.'); Function.prototype.bind = oldBind; - clearTimeout(timeoutId); - resolve(args[0]); + reject(); + }, MAX_LISTENING_MS); + + Function.prototype.bind = function (...args) { + if (Function.prototype.bind === oldBind) { + return oldBind.apply(this, args); + } else if ( + args[0] && + Object.prototype.hasOwnProperty.call(args[0], 'editingTarget') && + Object.prototype.hasOwnProperty.call(args[0], 'runtime') + ) { + log('VM detected!'); + Function.prototype.bind = oldBind; + clearTimeout(timeoutId); + resolve(args[0]); + return oldBind.apply(this, args); + } return oldBind.apply(this, args); - } - return oldBind.apply(this, args); - }; - }).then(async (vm) => { - return [vm, await getBlocklyInstance(vm)]; - }).catch(() => { - return [null, null]; - }); + }; + }); + return vm; + } catch { + return null; + } } function setupFormat (vm: EurekaCompatibleVM) { const getLocale = vm.getLocale; @@ -311,7 +319,10 @@ export function injectVM (vm: EurekaCompatibleVM) { const originalDrserializeFunc = vm.deserializeProject; vm.deserializeProject = function (projectJSON: Record, ...args) { - if (typeof projectJSON.extensionURLs === 'object' || typeof projectJSON.sideloadExtensionURLs === 'object') { + if ( + typeof projectJSON.extensionURLs === 'object' || + typeof projectJSON.sideloadExtensionURLs === 'object' + ) { const extensionURLs: Record = typeof projectJSON.sideloadExtensionURLs === 'object' ? (projectJSON.sideloadExtensionURLs as Record) @@ -324,12 +335,18 @@ export function injectVM (vm: EurekaCompatibleVM) { // Migrate from old eureka if (projectJSON.extensionEnvs) { log('Old eureka-ify project detected, migrating...'); - extensionEnvs = projectJSON.sideloadExtensionEnvs = projectJSON.extensionEnvs as Record; + extensionEnvs = projectJSON.sideloadExtensionEnvs = + projectJSON.extensionEnvs as Record; delete projectJSON.extensionEnvs; - for (const extensionId in (projectJSON.sideloadExtensionEnvs as Record)) { + for (const extensionId in projectJSON.sideloadExtensionEnvs as Record< + string, + unknown + >) { if (extensionId in (projectJSON.extensionURLs as Record)) { - extensionURLs[extensionId] = (projectJSON.extensionURLs as Record)[extensionId]; + extensionURLs[extensionId] = ( + projectJSON.extensionURLs as Record + )[extensionId]; // @ts-expect-error lazy to fix types delete projectJSON.extensionURLs[extensionId]; } @@ -384,7 +401,11 @@ export function injectVM (vm: EurekaCompatibleVM) { // Turbowarp-specific patch, skip security manager check const originalTwLoadExtFunc = vm._loadExtensions; if (typeof originalTwLoadExtFunc === 'function') { - vm._loadExtensions = function (extensionIDs: Set, extensionURLs: Map, ...args: unknown[]) { + vm._loadExtensions = function ( + extensionIDs: Set, + extensionURLs: Map, + ...args: unknown[] + ) { const sideloadExtensionPromises: Promise[] = []; for (const extensionId of extensionIDs) { if (extensionId in window.eureka.registeredExtension) { @@ -395,7 +416,10 @@ export function injectVM (vm: EurekaCompatibleVM) { extensionIDs.delete(extensionId); } } - return Promise.all([originalTwLoadExtFunc.call(this, extensionIDs, extensionURLs, ...args), ...sideloadExtensionPromises]); + return Promise.all([ + originalTwLoadExtFunc.call(this, extensionIDs, extensionURLs, ...args), + ...sideloadExtensionPromises + ]); }; } @@ -482,7 +506,7 @@ export function injectBlockly (blockly: any) { } else { warn('Cannot find real blockly instance, try alternative method...'); const originalProcedureCallback = - window.Blockly?.getMainWorkspace()?.toolboxCategoryCallbacks_?.PROCEDURE; + window.Blockly?.getMainWorkspace()?.toolboxCategoryCallbacks_?.PROCEDURE; if (typeof originalProcedureCallback !== 'function') { error('alternative method failed, stop injecting'); return;