forked from EurekaScratch/eureka
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: SimonShiki <sinangentoo@gmail.com>
- Loading branch information
0 parents
commit b8cc9e2
Showing
31 changed files
with
6,178 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"presets": [ | ||
"@babel/preset-env", | ||
"@babel/preset-typescript", | ||
"babel-preset-solid" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
{ | ||
"parser": "@typescript-eslint/parser", | ||
"plugins": [ | ||
"@typescript-eslint" | ||
], | ||
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/eslint-recommended", | ||
"plugin:@typescript-eslint/recommended" | ||
], | ||
"parserOptions": { | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"rules": { | ||
"semi": "error", | ||
"indent": ["error", 4], | ||
"dot-notation": "error", | ||
"block-scoped-var": "error", | ||
"eqeqeq": "error", | ||
"no-confusing-arrow": ["error"], | ||
"no-else-return": "error", | ||
"no-lonely-if": "error", | ||
"no-useless-constructor": "error", | ||
"no-useless-return": "error", | ||
"no-var": "error", | ||
"comma-spacing": "error", | ||
"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 | ||
}] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Mac OS | ||
.DS_Store | ||
|
||
# NPM | ||
/node_modules | ||
npm-* | ||
|
||
# Yarn | ||
yarn-error.log | ||
|
||
# generated build files | ||
/dist |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<div align="center"> | ||
|
||
<img alt="logo" src="./assets/chibi.png"> | ||
|
||
# Chibi | ||
#### Load scratch extension everywhere. | ||
|
||
</div> | ||
|
||
--- | ||
|
||
Chibi is a userscript which can load 3rd-party extensions in any Scratch-based editors (theoretically). | ||
# ✨ Features | ||
- [x] Load Scratch standard extensions | ||
- [x] Unsandboxed extensions | ||
- [x] TurboWarp Extension API (very small part) | ||
- [ ] Fallback solution for visitors without script installation | ||
- [ ] Load from editor | ||
|
||
# 🔥 Usage | ||
*I haven’t written a method to load extensions in the editor yet, 你先别急* | ||
|
||
1. Install UserScript Manager like Tampermonkey or Greasymonkey. | ||
2. Open [release](https://github.com/SimonShiki/chibi/releases), Then click one release to install. | ||
3. Press 'F12' on your keyboard to open Developer Tools. | ||
4. Input ``chibi.loader.load([extensionURL], [load mode, like 'unsandboxed'])'`` In your console, then enter to execute. | ||
5. Your extension got loaded! | ||
|
||
# ⚓ License | ||
AGPL-3.0, see [LICENSE](./LICENSE). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "chibi", | ||
"displayName": "Chibi", | ||
"version": "0.0.1", | ||
"description": "Load scratch extension everywhere.", | ||
"repository": "https://github.com/SimonShiki/chibi", | ||
"author": "SimonShiki", | ||
"private": true, | ||
"scripts": { | ||
"build": "webpack --color --bail", | ||
"lint": "eslint ./src/ --ext .js,.ts", | ||
"typecheck": "tsc --watch --noEmit" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.22.17", | ||
"@babel/preset-env": "^7.22.15", | ||
"@babel/preset-typescript": "^7.22.15", | ||
"@turbowarp/types": "^0.0.11", | ||
"@typescript-eslint/eslint-plugin": "^6.7.0", | ||
"@typescript-eslint/parser": "^6.7.0", | ||
"babel-loader": "^9.1.3", | ||
"babel-preset-solid": "^1.7.7", | ||
"codingclip-worker-loader": "^3.0.9", | ||
"eslint": "^8.49.0", | ||
"mini-svg-data-uri": "^1.4.4", | ||
"webpack": "^5.88.2", | ||
"webpack-cli": "^5.1.4", | ||
"webpack-userscript": "^3.2.2" | ||
}, | ||
"dependencies": { | ||
"format-message": "^6.2.4", | ||
"solid-js": "^1.7.11", | ||
"typescript": "^5.2.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
/// <reference path="node_modules/@turbowarp/types/index.d.ts" /> | ||
// <reference path="./loader/loader" /> | ||
|
||
declare interface Window { | ||
chibi: { | ||
version: string; | ||
vm?: VM; | ||
loader?: ChibiLoader; | ||
registeredExtension: Record<string, string>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
declare module '*.svg'; | ||
declare module '*.png'; | ||
declare module '*.jpg'; | ||
declare module '*.gif'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { trap, inject } from './injector/inject'; | ||
import { log } from './util/log'; | ||
|
||
// @ts-expect-error defined in webpack define plugin | ||
log(`Chibi ${__CHIBI_VERSION__}`); | ||
await trap(); | ||
if (typeof window.chibi.vm !== 'undefined') { | ||
inject(window.chibi.vm); | ||
} else { | ||
log(`Cannot find vm in this page, stop injecting.`); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
/// <reference path="../global.d.ts" /> | ||
import {log, error} from '../util/log'; | ||
import { ChibiLoader } from '../loader/loader'; | ||
import type VM from 'scratch-vm'; | ||
|
||
const MAX_LISTENING_MS = 30 * 1000; | ||
|
||
export function trap () { | ||
window.chibi = { | ||
// @ts-expect-error defined in webpack define plugin | ||
version: __CHIBI_VERSION__, | ||
registeredExtension: {} | ||
}; | ||
|
||
log('Listening bind function...'); | ||
const oldBind = Function.prototype.bind; | ||
return new Promise<void>(resolve => { | ||
const timeoutId = setTimeout(() => { | ||
log('Cannot find vm instance, stop listening.'); | ||
Function.prototype.bind = oldBind; | ||
resolve(); | ||
}, 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!'); | ||
window.chibi.vm = args[0]; | ||
Function.prototype.bind = oldBind; | ||
clearTimeout(timeoutId); | ||
resolve(); | ||
return oldBind.apply(this, args); | ||
} | ||
return oldBind.apply(this, args); | ||
}; | ||
}); | ||
} | ||
|
||
function stringify (obj) { | ||
return JSON.stringify(obj, (_key, value) => { | ||
if (typeof value === 'number' && | ||
(value === Infinity || value === -Infinity || isNaN(value))){ | ||
return 0; | ||
} | ||
return value; | ||
}); | ||
} | ||
|
||
export function inject (vm: VM) { | ||
const loader = window.chibi.loader = new ChibiLoader(vm); | ||
const originalLoadFunc = vm.extensionManager.loadExtensionURL; | ||
vm.extensionManager.loadExtensionURL = async function (extensionURL: string, ...args: unknown[]) { | ||
if (extensionURL in window.chibi.registeredExtension) { | ||
const { url, env } = window.chibi.registeredExtension[extensionURL]; | ||
try { | ||
if (confirm(`🤨 Project is trying to sideloading ${extensionURL} from ${url} in ${env} mode. Do you want to load?`)) { | ||
await loader.load(url, env); | ||
} else { | ||
// @ts-expect-error internal hack | ||
return originalLoadFunc.apply(vm.extensionManager, [extensionURL, ...args]); | ||
} | ||
} catch (e: unknown) { | ||
error('Error occurred while sideloading extension. To avoid interrupting the loading process, we chose to ignore this error.', e); | ||
} | ||
} else { | ||
// @ts-expect-error internal hack | ||
return originalLoadFunc.apply(vm.extensionManager, [extensionURL, ...args]); | ||
} | ||
}; | ||
|
||
const originalToJSONFunc = vm.toJSON; | ||
vm.toJSON = function (optTargetId: string, ...args: unknown[]) { | ||
// @ts-expect-error internal hack | ||
const json = originalToJSONFunc.apply(vm, optTargetId, ...args); | ||
const obj = JSON.parse(json); | ||
const [urls, envs] = window.chibi.loader.getLoadedInfo(); | ||
obj.extensionURLs = Object.assign({}, urls); | ||
obj.extensionEnvs = Object.assign({}, envs); | ||
return stringify(obj); | ||
}; | ||
|
||
const originalDrserializeFunc = vm.deserializeProject; | ||
vm.deserializeProject = function (projectJSON: Record<string, unknown>, ...args: unknown[]) { | ||
if (typeof projectJSON.extensionURLs === 'object') { | ||
for (const id in projectJSON.extensionURLs) { | ||
window.chibi.registeredExtension[id] = { | ||
url: projectJSON.extensionURLs[id], | ||
env: typeof projectJSON.extensionEnvs === 'object' ? | ||
projectJSON.extensionEnvs[id] : 'sandboxed' | ||
}; | ||
} | ||
} | ||
// @ts-expect-error internal hack | ||
return originalDrserializeFunc.apply(vm, [projectJSON, ...args]); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { SharedDispatch, DispatchCallMessage } from './shared-dispatch'; | ||
/** | ||
* This class serves as the central broker for message dispatch. It expects to operate on the main thread / Window and | ||
* it must be informed of any Worker threads which will participate in the messaging system. From any context in the | ||
* messaging system, the dispatcher's "call" method can call any method on any "service" provided in any participating | ||
* context. The dispatch system will forward function arguments and return values across worker boundaries as needed. | ||
* @see {WorkerDispatch} | ||
*/ | ||
class _CentralDispatch extends SharedDispatch { | ||
services: Record<string, any>; | ||
/** | ||
* The constructor we will use to recognize workers. | ||
* @type {Worker | null} | ||
*/ | ||
workerClass: typeof Worker | null = (typeof Worker === 'undefined' ? null : Worker); | ||
/** | ||
* List of workers attached to this dispatcher. | ||
* @type {Array} | ||
*/ | ||
workers: Worker[] = []; | ||
_onMessage!: (worker: Worker, event: MessageEvent) => void; | ||
constructor () { | ||
super(); | ||
/** | ||
* Map of channel name to worker or local service provider. | ||
* If the entry is a Worker, the service is provided by an object on that worker. | ||
* Otherwise, the service is provided locally and methods on the service will be called directly. | ||
* @see {setService} | ||
* @type {object.<Worker|object>} | ||
*/ | ||
this.services = {}; | ||
} | ||
/** | ||
* Synchronously call a particular method on a particular service provided locally. | ||
* Calling this function on a remote service will fail. | ||
* @param {string} service - the name of the service. | ||
* @param {string} method - the name of the method. | ||
* @param {*} [args] - the arguments to be copied to the method, if any. | ||
* @returns {*} - the return value of the service method. | ||
*/ | ||
callSync (service: string, method: string, ...args: unknown[]) { | ||
const {provider, isRemote} = this._getServiceProvider(service); | ||
if (provider) { | ||
if (isRemote) { | ||
throw new Error(`Cannot use 'callSync' on remote provider for service ${service}.`); | ||
} | ||
return provider[method].apply(provider, args); | ||
} | ||
throw new Error(`Provider not found for service: ${service}`); | ||
} | ||
/** | ||
* Synchronously set a local object as the global provider of the specified service. | ||
* WARNING: Any method on the provider can be called from any worker within the dispatch system. | ||
* @param {string} service - a globally unique string identifying this service. Examples: 'vm', 'gui', 'extension9'. | ||
* @param {object} provider - a local object which provides this service. | ||
*/ | ||
setServiceSync (service: string, provider: any) { | ||
if (this.services.hasOwnProperty(service)) { | ||
console.warn(`Central dispatch replacing existing service provider for ${service}`); | ||
} | ||
this.services[service] = provider; | ||
} | ||
/** | ||
* Set a local object as the global provider of the specified service. | ||
* WARNING: Any method on the provider can be called from any worker within the dispatch system. | ||
* @param {string} service - a globally unique string identifying this service. Examples: 'vm', 'gui', 'extension9'. | ||
* @param {object} provider - a local object which provides this service. | ||
* @returns {Promise} - a promise which will resolve once the service is registered. | ||
*/ | ||
setService (service: string, provider: any) { | ||
/** Return a promise for consistency with {@link WorkerDispatch#setService} */ | ||
try { | ||
this.setServiceSync(service, provider); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
/** | ||
* Add a worker to the message dispatch system. The worker must implement a compatible message dispatch framework. | ||
* The dispatcher will immediately attempt to "handshake" with the worker. | ||
* @param {Worker} worker - the worker to add into the dispatch system. | ||
*/ | ||
addWorker (worker: Worker) { | ||
if (this.workers.indexOf(worker) === -1) { | ||
this.workers.push(worker); | ||
worker.onmessage = this._onMessage.bind(this, worker); | ||
this._remoteCall(worker, 'dispatch', 'handshake').catch(e => { | ||
console.error(`Could not handshake with worker: ${e}`); | ||
}); | ||
} else { | ||
console.warn('Central dispatch ignoring attempt to add duplicate worker'); | ||
} | ||
} | ||
/** | ||
* Fetch the service provider object for a particular service name. | ||
* @override | ||
* @param {string} service - the name of the service to look up | ||
* @returns {{provider:(object|Worker), isRemote:boolean}} - the means to contact the service, if found | ||
* @protected | ||
*/ | ||
_getServiceProvider (service: string) { | ||
const provider = this.services[service]; | ||
return provider && { | ||
provider, | ||
isRemote: Boolean((this.workerClass && provider instanceof this.workerClass) || provider.isRemote) | ||
}; | ||
} | ||
/** | ||
* Handle a call message sent to the dispatch service itself | ||
* @override | ||
* @param {Worker} worker - the worker which sent the message. | ||
* @param {DispatchCallMessage} message - the message to be handled. | ||
* @returns {Promise|undefined} - a promise for the results of this operation, if appropriate | ||
* @protected | ||
*/ | ||
_onDispatchMessage (worker: Worker, message: DispatchCallMessage) { | ||
let promise; | ||
switch (message.method) { | ||
case 'setService': | ||
if (!message.args) { | ||
console.error('setService received empty argument'); | ||
break; | ||
} | ||
promise = this.setService(String(message.args[0]), worker); | ||
break; | ||
default: | ||
console.error(`Central dispatch received message for unknown method: ${message.method}`); | ||
} | ||
return promise; | ||
} | ||
} | ||
|
||
export type CentralDispatch = _CentralDispatch; | ||
|
||
export const CentralDispatch = new _CentralDispatch(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './shared-dispatch'; | ||
export * from './central-dispatch'; | ||
export * from './worker-dispatch'; |
Oops, something went wrong.