merge from local

This commit is contained in:
SrGooglo 2024-11-17 20:35:30 +00:00
parent 73c3ed8a2c
commit e897de6814
7 changed files with 229 additions and 36 deletions

View File

@ -18,12 +18,14 @@
"dependencies": {
"@loadable/component": "^5.16.4",
"@vitejs/plugin-react": "^4.3.3",
"comlink": "^4.4.1",
"history": "^5.3.0",
"less": "^4.2.0",
"million": "^3.1.11",
"object-observer": "^6.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-helmet": "^6.1.0"
"react-helmet": "^6.1.0",
"sucrase": "^3.35.0"
}
}

View 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 () => {
}
}

View File

@ -1,34 +1,22 @@
import { Observable } from "object-observer"
import InternalConsole from "../classes/InternalConsole"
import EventBus from "../classes/EventBus"
export default class Extension {
constructor(appContext, mainContext) {
this.appContext = appContext
this.mainContext = mainContext
this.promises = []
return this
constructor(params = {}) {
this.params = params
}
__initializer() {
return new Promise(async (resolve, reject) => {
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()
})
})
eventBus = new EventBus({
id: this.constructor.namespace ?? this.constructor.name,
})
this.promises.push(dependencyPromise)
console = new InternalConsole({
namespace: this.constructor.namespace ?? this.constructor.name,
})
}
await Promise.all(this.promises)
return resolve()
})
async _init() {
if (typeof this.onInitialize === "function") {
this.onInitialize()
}
}
}

View File

@ -11,6 +11,7 @@ import Extension from "./extension"
import EventBus from "./classes/EventBus"
import InternalConsole from "./classes/InternalConsole"
import ExtensionManager from "./classes/ExtensionsManager"
import isMobile from "./utils/isMobile"
@ -81,14 +82,7 @@ export default class EviteRuntime {
}
})
window.app.extensions = new Proxy(this.ExtensionsPublicContext, {
get: (target, key) => {
return target[key]
},
set: (target, key, value) => {
throw new Error("You can't set a extension value")
}
})
window.app.extensions = new ExtensionManager()
this.registerPublicMethod({ key: "isMobile", locked: true }, isMobile())
this.registerPublicMethod({ key: "__version", locked: true }, pkgJson.version)

View 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

View File

@ -12,12 +12,32 @@ export function withSuffix(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) {
return string.endsWith(suffix)
? string.slice(0, -1 * suffix.length)
: 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) {
if (urlLike instanceof URL) {
return urlLike

132
src/workers/extension.js Normal file
View 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)
}
}