Skip to content

Commit

Permalink
🐛 fix: clipcc support
Browse files Browse the repository at this point in the history
  • Loading branch information
FurryR committed Jan 22, 2024
1 parent 93807ef commit 6d1ceb3
Show file tree
Hide file tree
Showing 13 changed files with 6,909 additions and 4,609 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
"keyword-spacing": [2, {
"before": true,
"after": true
}]
}],
// disabled rules
"@typescript-eslint/triple-slash-reference": "off",
"@typescript-eslint/no-explicit-any": "off"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ npm-*

# Yarn
yarn-error.log
.yarn/
.pnp.cjs
.pnp.loader.mjs

# generated build files
/dist
Expand Down
2 changes: 1 addition & 1 deletion generate-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const includeURLs = [
'https://play.creaticode.com/projects/*',
'https://www.adacraft.org/*',
'https://studio.penguinmod.com/*',
'https://code.xueersi.com/home/project/detail?lang=scratch&pid=*&version=3.0&langType=scratch',
'https://code.xueersi.com/*',
'http://localhost:8601/*'
];

Expand Down
1 change: 1 addition & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line multiline-comment-style
/// <reference path="node_modules/@turbowarp/types/index.d.ts" />
/// <reference path="./loader/loader" />
/// <reference path="./loader/make-ctx" />
Expand Down
150 changes: 90 additions & 60 deletions src/injector/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import type VM from 'scratch-vm';
import type Blockly from 'scratch-blocks';
import * as l10n from '../l10n/l10n.json';
import formatMessage from 'format-message';
import { makeCtx } from '../loader/make-ctx';

interface EurekaCompatibleWorkspace extends Blockly.Workspace {
registerButtonCallback(key: string, callback: Function): void;
registerButtonCallback(key: string, callback: () => void): void;
}

interface EurekaCompatibleVM extends VM {
Expand Down Expand Up @@ -49,38 +48,64 @@ function getExtensionIdForOpcode (opcode: string) {
* @param vm Virtual machine instance. For some reasons we cannot use VM here.
* @returns Blockly instance.
*/
function getBlocklyInstance (vm: EurekaCompatibleVM): any | null {
// Hijack Function.prototype.apply to get React element instance.
function hijack (fn: (...args: unknown[]) => unknown) {
const _orig = Function.prototype.apply;
Function.prototype.apply = function (thisArg: any) {
return thisArg;
};
const result = fn();
Function.prototype.apply = _orig;
return result;
}
async function getBlocklyInstance (vm: EurekaCompatibleVM): Promise<any> {
function getBlocklyInstanceInternal (): any | null {
// Hijack Function.prototype.apply to get React element instance.
function hijack (fn: (...args: unknown[]) => unknown) {
const _orig = Function.prototype.apply;
Function.prototype.apply = function (thisArg: any) {
return thisArg;
};
const result = fn();
Function.prototype.apply = _orig;
return result;
}

// @ts-expect-error lazy to extend VM interface
const events = vm._events?.EXTENSION_ADDED;
if (events) {
if (events instanceof Function) {
// It is a function, just hijack it.
const result = hijack(events);
if (result && typeof result === 'object' && 'ScratchBlocks' in result) {
return result.ScratchBlocks;
}
} else {
// It is an array, hijack every listeners.
for (const value of events) {
const result = hijack(value);
// @ts-expect-error lazy to extend VM interface
const events = vm._events?.EXTENSION_ADDED;
if (events) {
if (events instanceof Function) {
// It is a function, just hijack it.
const result = hijack(events);
if (result && typeof result === 'object' && 'ScratchBlocks' in result) {
return result.ScratchBlocks;
}
} else {
// It is an array, hijack every listeners.
for (const value of events) {
const result = hijack(value);
if (result && typeof result === 'object' && 'ScratchBlocks' in result) {
return result.ScratchBlocks;
}
}
}
}
return null;
}
return null;
let res = getBlocklyInstanceInternal();
return (
res ??
new Promise((resolve) => {
let state: any = undefined;
// @ts-expect-error lazy to extend VM interface
Reflect.defineProperty(vm._events, 'EXTENSION_ADDED', {
get: () => state,
set (v) {
state = v;
res = getBlocklyInstanceInternal();
if (res) {
// @ts-expect-error lazy to extend VM interface
Reflect.defineProperty(vm._events, 'EXTENSION_ADDED', {
value: state,
writable: true
});
resolve(res);
}
},
configurable: true
});
})
);
}

/**
Expand Down Expand Up @@ -160,14 +185,14 @@ export function inject (vm: EurekaCompatibleVM) {
env
})
)
: (window.eureka.settings.sideloadOnly ?
false :
confirm(
: window.eureka.settings.sideloadOnly
? false
: confirm(
format('eureka.tryLoad', {
extensionURL,
url
})
));
);
}
if (whetherSideload) {
await loader.load(
Expand All @@ -178,33 +203,31 @@ export function inject (vm: EurekaCompatibleVM) {
? 'sandboxed'
: 'unsandboxed') as 'unsandboxed' | 'sandboxed'
);
const extensionId = loader.getIdByUrl(url);
// @ts-expect-error internal hack
vm.extensionManager._loadedExtensions.set(extensionId, 'Eureka');
/*
* Const extensionId = loader.getIdByUrl(url);
* @ ts-expect-error internal hack
* vm.extensionManager._loadedExtensions.set(extensionId, 'Eureka');
*/
} else {
// @ts-expect-error internal hack
return originalLoadFunc.call(this, extensionURL, ...args);
}
} catch (e: unknown) {
error(format('eureka.errorIgnored'), e);
}
} else {
// @ts-expect-error internal hack
return originalLoadFunc.call(this, extensionURL, ...args);
}
};

const originalRefreshBlocksFunc = vm.extensionManager.refreshBlocks;
vm.extensionManager.refreshBlocks = async function (...args: unknown[]) {
// @ts-expect-error internal hack
const result = await originalRefreshBlocksFunc.call(this, ...args);
await window.eureka.loader.refreshBlocks();
return result;
};

const originalToJSONFunc = vm.toJSON;
vm.toJSON = function (optTargetId: string, ...args: unknown[]) {
// @ts-expect-error internal hack
const json = originalToJSONFunc.call(this, optTargetId, ...args);
const obj = JSON.parse(json);

Expand Down Expand Up @@ -296,7 +319,6 @@ export function inject (vm: EurekaCompatibleVM) {
delete projectJSON.sideloadMonitors;
}
}
// @ts-expect-error internal hack
return originalDrserializeFunc.call(this, projectJSON, ...args);
};

Expand Down Expand Up @@ -344,7 +366,7 @@ export function inject (vm: EurekaCompatibleVM) {
) {
for (const extensionId of extensions) {
if (
!vm.ccExtensionManager!.info.hasOwnProperty(extensionId) &&
!Object.prototype.hasOwnProperty.call(vm.ccExtensionManager!.info, extensionId) &&
extensionId in window.eureka.registeredExtension
) {
vm.ccExtensionManager!.info[extensionId] = {
Expand All @@ -358,16 +380,39 @@ export function inject (vm: EurekaCompatibleVM) {
}

// Blockly stuffs
vm.once('workspaceUpdate', () => {
const blockly = (window.eureka.blockly = getBlocklyInstance(vm));
if (!blockly) {
let initalized = false;
getBlocklyInstance(vm).then((blockly) => {
if (!initalized) {
window.eureka.blockly = blockly;
initalized = true;
const originalAddCreateButton_ = blockly.Procedures.addCreateButton_;
blockly.Procedures.addCreateButton_ = function (
workspace: EurekaCompatibleWorkspace,
xmlList: HTMLElement[],
...args: unknown[]
) {
originalAddCreateButton_.call(this, workspace, xmlList, ...args);
injectToolbox(xmlList, workspace, format);
};
const workspace = blockly.getMainWorkspace();
workspace.getToolbox().refreshSelection();
workspace.toolboxRefreshEnabled_ = true;
}
});
/*
* Const blockly = (window.eureka.blockly = getBlocklyInstance(vm));
* Deprecated: this method will be removed in the future.
*/
setTimeout(() => {
if (!initalized) {
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;
}
initalized = true;
window.Blockly.getMainWorkspace().toolboxCategoryCallbacks_.PROCEDURE = function (
workspace: EurekaCompatibleWorkspace,
...args: unknown[]
Expand All @@ -379,24 +424,9 @@ export function inject (vm: EurekaCompatibleVM) {
const workspace = window.Blockly.getMainWorkspace();
workspace.getToolbox().refreshSelection();
workspace.toolboxRefreshEnabled_ = true;
return;
}

const originalAddCreateButton_ = blockly.Procedures.addCreateButton_;
blockly.Procedures.addCreateButton_ = function (
workspace: EurekaCompatibleWorkspace,
xmlList: HTMLElement[],
...args: unknown[]
) {
originalAddCreateButton_.call(this, workspace, xmlList, ...args);
injectToolbox(xmlList, workspace, format);
};
const workspace = blockly.getMainWorkspace();
workspace.getToolbox().refreshSelection();
workspace.toolboxRefreshEnabled_ = true;
});
}, 3000);
}

function injectToolbox (xmlList: HTMLElement[], workspace: EurekaCompatibleWorkspace, format: typeof formatMessage) {
// Add separator and label
const sep = document.createElement('sep');
Expand Down Expand Up @@ -470,4 +500,4 @@ function injectToolbox (xmlList: HTMLElement[], workspace: EurekaCompatibleWorks
block.appendChild(mutation);
xmlList.push(block);
return xmlList;
}
}
4 changes: 2 additions & 2 deletions src/loader/dispatch/central-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class _CentralDispatch extends SharedDispatch {
if (isRemote) {
throw new Error(`Cannot use 'callSync' on remote provider for service ${service}.`);
}
return provider[method].apply(provider, args);
return provider[method](...args);
}
throw new Error(`Provider not found for service: ${service}`);
}
Expand All @@ -55,7 +55,7 @@ class _CentralDispatch extends SharedDispatch {
* @param {object} provider - a local object which provides this service.
*/
setServiceSync (service: string, provider: any) {
if (this.services.hasOwnProperty(service)) {
if (Object.prototype.hasOwnProperty.call(this.services, service)) {
console.warn(`Central dispatch replacing existing service provider for ${service}`);
}
this.services[service] = provider;
Expand Down
4 changes: 1 addition & 3 deletions src/loader/dispatch/shared-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,7 @@ class SharedDispatch {
return obj.map((item) => this._purifyObject(item, visited, depth + 1));
}
const result: Record<string, unknown> = {};
for (const key in obj) {
// @ts-expect-error
const value = obj[key];
for (const [key, value] of Object.entries(obj)) {
result[key] = this._purifyObject(value, visited, depth + 1);
}
return result;
Expand Down
8 changes: 4 additions & 4 deletions src/loader/dispatch/worker-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class _WorkerDispatch extends SharedDispatch {
*/
_connectionPromise: Promise<unknown>;
_onConnect!: (value?: unknown) => void;
// @ts-expect-error
_onMessage!: (worker: window & globalThis, event: MessageEvent) => void;
// @ts-expect-error no type description
_onMessage!: (worker: Window & globalThis, event: MessageEvent) => void;
constructor () {
super();

Expand All @@ -35,7 +35,7 @@ class _WorkerDispatch extends SharedDispatch {

this._onMessage = this._onMessage.bind(this, self);
if (typeof self !== 'undefined') {
// @ts-expect-error
// @ts-expect-error no type description
self.onmessage = this._onMessage;
}
}
Expand All @@ -60,7 +60,7 @@ class _WorkerDispatch extends SharedDispatch {
* @returns {Promise} - a promise which will resolve once the service is registered.
*/
setService (service: string, provider: unknown) {
if (this.services.hasOwnProperty(service)) {
if (Object.prototype.hasOwnProperty.call(this.services, service)) {
console.warn(`Worker dispatch replacing existing service provider for ${service}`);
}
this.services[service] = provider;
Expand Down
Loading

0 comments on commit 6d1ceb3

Please sign in to comment.