mirror of
https://github.com/ragestudio/vessel.git
synced 2025-06-09 02:24:17 +00:00
merge from local
This commit is contained in:
parent
73c3ed8a2c
commit
e897de6814
@ -18,12 +18,14 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@loadable/component": "^5.16.4",
|
"@loadable/component": "^5.16.4",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
|
"comlink": "^4.4.1",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"million": "^3.1.11",
|
"million": "^3.1.11",
|
||||||
"object-observer": "^6.0.0",
|
"object-observer": "^6.0.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-helmet": "^6.1.0"
|
"react-helmet": "^6.1.0",
|
||||||
|
"sucrase": "^3.35.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
50
src/classes/ExtensionsManager/index.js
Normal file
50
src/classes/ExtensionsManager/index.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import InternalConsole from "../InternalConsole"
|
||||||
|
import { isUrl } from "../../utils/url"
|
||||||
|
import * as Comlink from "comlink"
|
||||||
|
|
||||||
|
import ExtensionWorker from "../../workers/extension.js?worker"
|
||||||
|
|
||||||
|
export default class ExtensionManager {
|
||||||
|
logger = new InternalConsole({
|
||||||
|
namespace: "ExtensionsManager",
|
||||||
|
bgColor: "bgMagenta",
|
||||||
|
})
|
||||||
|
|
||||||
|
extensions = new Map()
|
||||||
|
|
||||||
|
loadExtension = async (manifest) => {
|
||||||
|
if (isUrl(manifest)) {
|
||||||
|
manifest = await fetch(manifest)
|
||||||
|
manifest = await manifest.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
const worker = new ExtensionWorker()
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
event: "load",
|
||||||
|
manifest: manifest,
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
worker.onmessage = ({data}) => {
|
||||||
|
if (data.event === "loaded") {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(Comlink.wrap(worker))
|
||||||
|
|
||||||
|
// if (typeof main.events === "object") {
|
||||||
|
// Object.entries(main.events).forEach(([event, handler]) => {
|
||||||
|
// main.event.on(event, handler)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.extensions.set(manifest.registryId,main.public)
|
||||||
|
}
|
||||||
|
|
||||||
|
installExtension = async () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,34 +1,22 @@
|
|||||||
import { Observable } from "object-observer"
|
import InternalConsole from "../classes/InternalConsole"
|
||||||
|
import EventBus from "../classes/EventBus"
|
||||||
|
|
||||||
export default class Extension {
|
export default class Extension {
|
||||||
constructor(appContext, mainContext) {
|
constructor(params = {}) {
|
||||||
this.appContext = appContext
|
this.params = params
|
||||||
this.mainContext = mainContext
|
|
||||||
|
|
||||||
this.promises = []
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__initializer() {
|
eventBus = new EventBus({
|
||||||
return new Promise(async (resolve, reject) => {
|
id: this.constructor.namespace ?? this.constructor.name,
|
||||||
if (Array.isArray(this.depends)) {
|
|
||||||
this.depends.forEach((dependency) => {
|
|
||||||
const dependencyPromise = new Promise((resolve, reject) => {
|
|
||||||
Observable.observe(this.mainContext.ATTACHED_EXTENSIONS, (changes) => {
|
|
||||||
changes.forEach((change) => {
|
|
||||||
Array.from(change.object).includes(dependency) && resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.promises.push(dependencyPromise)
|
console = new InternalConsole({
|
||||||
|
namespace: this.constructor.namespace ?? this.constructor.name,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async _init() {
|
||||||
|
if (typeof this.onInitialize === "function") {
|
||||||
|
this.onInitialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(this.promises)
|
|
||||||
|
|
||||||
return resolve()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ import Extension from "./extension"
|
|||||||
|
|
||||||
import EventBus from "./classes/EventBus"
|
import EventBus from "./classes/EventBus"
|
||||||
import InternalConsole from "./classes/InternalConsole"
|
import InternalConsole from "./classes/InternalConsole"
|
||||||
|
import ExtensionManager from "./classes/ExtensionsManager"
|
||||||
|
|
||||||
import isMobile from "./utils/isMobile"
|
import isMobile from "./utils/isMobile"
|
||||||
|
|
||||||
@ -81,14 +82,7 @@ export default class EviteRuntime {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.extensions = new Proxy(this.ExtensionsPublicContext, {
|
window.app.extensions = new ExtensionManager()
|
||||||
get: (target, key) => {
|
|
||||||
return target[key]
|
|
||||||
},
|
|
||||||
set: (target, key, value) => {
|
|
||||||
throw new Error("You can't set a extension value")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.registerPublicMethod({ key: "isMobile", locked: true }, isMobile())
|
this.registerPublicMethod({ key: "isMobile", locked: true }, isMobile())
|
||||||
this.registerPublicMethod({ key: "__version", locked: true }, pkgJson.version)
|
this.registerPublicMethod({ key: "__version", locked: true }, pkgJson.version)
|
||||||
|
7
src/utils/isJsxCode/index.js
Normal file
7
src/utils/isJsxCode/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function isJsxCode(code) {
|
||||||
|
const jsxPattern = /<([A-Za-z][A-Za-z0-9]*)(\s+[^>]*)?(\/?>).*<\/\1>|<([A-Za-z][A-Za-z0-9]*)(\s+[^>]*)?\/>/s;
|
||||||
|
|
||||||
|
return jsxPattern.test(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default isJsxCode
|
@ -12,12 +12,32 @@ export function withSuffix(string, suffix) {
|
|||||||
return string.endsWith(suffix) ? string : string + suffix
|
return string.endsWith(suffix) ? string : string + suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isUrl(string) {
|
||||||
|
return string.startsWith("http://") || string.startsWith("https://")
|
||||||
|
}
|
||||||
|
|
||||||
export function withoutSuffix(string, suffix) {
|
export function withoutSuffix(string, suffix) {
|
||||||
return string.endsWith(suffix)
|
return string.endsWith(suffix)
|
||||||
? string.slice(0, -1 * suffix.length)
|
? string.slice(0, -1 * suffix.length)
|
||||||
: string + suffix
|
: string + suffix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function URLResolve(url, ...paths) {
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
|
||||||
|
paths.forEach((path) => {
|
||||||
|
if (typeof path !== "string") {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path.replace(/^\//, '')
|
||||||
|
|
||||||
|
urlObj.pathname = urlObj.pathname.replace(/\/$/, '') + '/' + path
|
||||||
|
})
|
||||||
|
|
||||||
|
return urlObj.toString()
|
||||||
|
}
|
||||||
|
|
||||||
export function createUrl(urlLike) {
|
export function createUrl(urlLike) {
|
||||||
if (urlLike instanceof URL) {
|
if (urlLike instanceof URL) {
|
||||||
return urlLike
|
return urlLike
|
||||||
|
132
src/workers/extension.js
Normal file
132
src/workers/extension.js
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import * as Comlink from "comlink"
|
||||||
|
import { isUrl, URLResolve } from "../utils/url"
|
||||||
|
import classAggregation from "../utils/classAggregation"
|
||||||
|
import ExtensionClass from "../extension"
|
||||||
|
|
||||||
|
let main = null
|
||||||
|
let manifest = null
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
static log(...args) {
|
||||||
|
console.log(`[ExtensionWorker]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(...args) {
|
||||||
|
console.error(`[ExtensionWorker]`, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
static warn(...args) {
|
||||||
|
console.warn(`[ExtensionWorker]`, ...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceImportsWithRemoteURL(codeStr, assetsUrl) {
|
||||||
|
const importRegex = /from\s+["']([^"'\s]+)["']|import\s+["']([^"'\s]+)["']/g
|
||||||
|
|
||||||
|
const matches = [...codeStr.matchAll(importRegex)]
|
||||||
|
|
||||||
|
let replacements = matches.map(async ([match, fromImportStr, simpleImportStr]) => {
|
||||||
|
if (fromImportStr) {
|
||||||
|
if (fromImportStr.startsWith("./")) {
|
||||||
|
fromImportStr = await URLResolve(assetsUrl, fromImportStr)
|
||||||
|
fromImportStr = await createCodeResource(fromImportStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return `from "${fromImportStr}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (simpleImportStr) {
|
||||||
|
if (simpleImportStr.startsWith("./")) {
|
||||||
|
simpleImportStr = await URLResolve(assetsUrl, simpleImportStr)
|
||||||
|
simpleImportStr = await createCodeResource(simpleImportStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return `import "${simpleImportStr}"`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
replacements = await Promise.all(replacements)
|
||||||
|
|
||||||
|
matches.forEach((match, i) => {
|
||||||
|
codeStr = codeStr.replace(match[0], replacements[i])
|
||||||
|
})
|
||||||
|
|
||||||
|
return codeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createCodeResource(code) {
|
||||||
|
if (!code) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifest || !manifest.srcUrl) {
|
||||||
|
throw new Error(`Manifest does not have srcUrl`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUrl(code)) {
|
||||||
|
code = await fetch(code)
|
||||||
|
code = await code.text()
|
||||||
|
}
|
||||||
|
|
||||||
|
code = await replaceImportsWithRemoteURL(code, manifest.srcUrl)
|
||||||
|
|
||||||
|
const blob = new Blob([code], { type: "application/javascript" })
|
||||||
|
return URL.createObjectURL(blob)
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage = async ({ data }) => {
|
||||||
|
if (data.event === "load") {
|
||||||
|
manifest = data.manifest
|
||||||
|
|
||||||
|
Logger.log(`Loading extension [${manifest.registryId}] >`, data.manifest)
|
||||||
|
|
||||||
|
// try to fetch package.json
|
||||||
|
if (!manifest.packageUrl) {
|
||||||
|
throw new Error(`Extension ${manifest.registryId} is missing packageUrl`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set packageJson
|
||||||
|
const packageJson = await fetch(manifest.packageUrl)
|
||||||
|
|
||||||
|
manifest.packageJson = await packageJson.json()
|
||||||
|
|
||||||
|
// try to fetch main file
|
||||||
|
if (!manifest.packageJson.main) {
|
||||||
|
throw new Error(`Extension ${manifest.registryId} is missing main`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve url with the assetsUrl
|
||||||
|
manifest.packageJson.main = URLResolve(manifest.assetsUrl, manifest.packageJson.main)
|
||||||
|
|
||||||
|
// load main file as a module
|
||||||
|
main = await createCodeResource(manifest.packageJson.main)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const module = await import(main)
|
||||||
|
|
||||||
|
main = module.default
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to import main module >`, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
main = classAggregation(ExtensionClass, main)
|
||||||
|
|
||||||
|
main = new main()
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to instantiate main module >`, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await main._init()
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`Failed to initialize main module >`, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
postMessage({ event: "loaded" })
|
||||||
|
|
||||||
|
Logger.log("Exposing main class >", main, main.public)
|
||||||
|
|
||||||
|
Comlink.expose(main.public)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user