Workerware

This commit is contained in:
MotorTruck1221 2024-10-26 04:13:33 -06:00
parent 5dc222a0df
commit 0fae05e33a
No known key found for this signature in database
GPG key ID: 08F417E2B8B61EA4
8 changed files with 255 additions and 36 deletions

View file

@ -79,6 +79,11 @@ export default defineConfig({
],
server: {
proxy: {
"/api/catalog-stats": {
target: "http://localhost:8080/api/catalog-stats",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
},
"/api/catalog-assets": {
target: "http://localhost:8080/api/catalog-assets",
changeOrigin: true,

View file

@ -1,7 +1,9 @@
importScripts("/uv/uv.bundle.js");
importScripts("/uv/uv.config.js");
importScripts("/workerware/workerware.js");
importScripts(__uv$config.sw || "/uv/uv.sw.js");
const uv = new UVServiceWorker();
const ww = new WorkerWare({ debug: false });
//where we handle our plugins!!!
self.addEventListener("message", function(event) {
@ -10,32 +12,51 @@ self.addEventListener("message", function(event) {
//loop over the required data (we don't verify here as types will take care of us :D)
event.data.forEach((data) => {
if (data.remove) {
const idx = uv.config.inject.indexOf(data.host);
uv.config.inject.splice(idx, 1);
if (data.type === "page") {
const idx = uv.config.inject.indexOf(data.host);
uv.config.inject.splice(idx, 1);
}
else if (data.type === "serviceWorker") {
ww.deleteByName(data.name);
}
}
else {
uv.config.inject.push({
host: data.host,
html: data.html,
injectTo: data.injectTo
});
if (data.type === "page") {
uv.config.inject.push({
host: data.host,
html: data.html,
injectTo: data.injectTo
});
}
else if (data.type === "serviceWorker") {
const wwFunction = eval(data.function);
ww.use({
function: wwFunction ? wwFunction : new Function(data.function),
name: data.name,
events: data.events
})
}
else {
console.error('NO type exists for that. Only serviceWorker & page exist.');
return;
}
}
});
console.log(uv.config.inject);
});
self.addEventListener("fetch", function (event) {
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
event.respondWith(
(async function () {
event.respondWith(
(async () => {
const wwRes = await ww.run(event)();
if (wwRes.includes(null)) {
return;
}
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
return await uv.fetch(event);
})()
);
} else {
event.respondWith(
(async function () {
}
else {
return await fetch(event.request);
})()
);
}
}
})()
);
});

View file

@ -0,0 +1,168 @@
importScripts("/workerware/WWError.js");
const dbg = console.log.bind(console, "[WorkerWare]");
const time = console.time.bind(console, "[WorkerWare]");
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
/*
OPTS:
debug - Enables debug logging.
randomNames - Generate random names for middlewares.
timing - Logs timing for each middleware.
*/
const defaultOpt = {
debug: false,
randomNames: false,
timing: false,
};
const validEvents = [
"abortpayment",
"activate",
"backgroundfetchabort",
"backgroundfetchclick",
"backgroundfetchfail",
"backgroundfetchsuccess",
"canmakepayment",
"contentdelete",
"cookiechange",
"fetch",
"install",
"message",
"messageerror",
"notificationclick",
"notificationclose",
"paymentrequest",
"periodicsync",
"push",
"pushsubscriptionchange",
"sync",
];
class WorkerWare {
constructor(opt) {
this._opt = Object.assign({}, defaultOpt, opt);
this._middlewares = [];
}
info() {
return {
version: "0.1.0",
middlewares: this._middlewares,
options: this._opt,
};
}
use(middleware) {
let validateMW = this.validateMiddleware(middleware);
if (validateMW.error) throw new WWError(validateMW.error);
// This means the middleware is an anonymous function, or the user is silly and named their function "function"
if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
if (!middleware.name) middleware.name = middleware.function.name;
if (this._opt.randomNames) middleware.name = crypto.randomUUID();
if (this._opt.debug) dbg("Adding middleware:", middleware.name);
this._middlewares.push(middleware);
}
// Run all middlewares for the event type passed in.
run(event) {
const middlewares = this._middlewares;
const returnList = [];
let fn = async () => {
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].events.includes(event.type)) {
if (this._opt.timing) console.time(middlewares[i].name);
// Add the configuration to the event object.
event.workerware = {
config: middlewares[i].configuration || {},
};
if (!middlewares[i].explicitCall) {
let res = await middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
returnList.push(res);
}
}
}
return returnList;
};
return fn;
}
deleteByName(middlewareID) {
if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
}
deleteByEvent(middlewareEvent) {
if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
}
get() {
return this._middlewares;
}
/*
Run a single middleware by ID.
This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
*/
runMW(name, event) {
const middlewares = this._middlewares;
if (this._opt.debug) dbg("Running middleware:", name);
// if (middlewares.includes(name)) {
// return middlewares[name](event);
// } else {
// throw new WWError("Middleware not found!");
// }
let didCall = false;
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].name == name) {
didCall = true;
event.workerware = {
config: middlewares[i].configuration || {},
}
if (this._opt.timing) console.time(middlewares[i].name);
let call = middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
return call;
}
}
if (!didCall) {
throw new WWError("Middleware not found!");
}
}
// type middlewareManifest = {
// function: Function,
// name?: string,
// events: string[], // Should be a union of validEvents.
// configuration?: Object // Optional configuration for the middleware.
// }
validateMiddleware(middleware) {
if (!middleware.function)
return {
error: "middleware.function is required",
};
if (typeof middleware.function !== "function")
return {
error: "middleware.function must be typeof function",
};
if (typeof middleware.configuration !== "object" && middleware.configuration !== undefined) {
return {
error: "middleware.configuration must be typeof object",
};
}
if (!middleware.events)
return {
error: "middleware.events is required",
};
if (!Array.isArray(middleware.events))
return {
error: "middleware.events must be an array",
};
if (middleware.events.some((ev) => !validEvents.includes(ev)))
return {
error: "Invalid event type! Must be one of the following: " + validEvents.join(", "),
};
if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
return {
error: "middleware.explicitCall must be typeof boolean",
};
}
return {
error: undefined,
};
}
}

View file

@ -49,8 +49,12 @@ const catalogAssets = db.define<CatalogModel>("catalog_assets", {
});
function marketplaceAPI(app: FastifyInstance) {
app.get("/api", (request, reply) => {
reply.send({ Server: "Active" });
app.get("/api/catalog-stats/", (request, reply) => {
reply.send({
version: '1.0.0',
spec: 'Nebula Services',
enabled: true
});
});
// This API returns a list of the assets in the database (SW plugins and themes).

View file

@ -10,7 +10,6 @@ import fastifyStatic from "@fastify/static";
import chalk from "chalk";
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
import gradient from "gradient-string";
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
//@ts-ignore WHY would I want this typechecked AT ALL
import { handler as ssrHandler } from "../dist/server/entry.mjs";
import { parsedDoc } from "./config.js";

View file

@ -195,5 +195,5 @@ import { VERSION } from "astro:env/client";
}
}
});
});
}, true);
</script>

View file

@ -1,6 +1,6 @@
//marketplace code & handlers
import { Settings } from ".";
import { type Package, type PackageType, type Plugin, type PluginType, type SWPlugin } from "./types";
import { type Package, type PackageType, type Plugin, type PluginType, type SWPagePlugin, type SWPlugin } from "./types";
const AppearanceSettings = {
themes: "nebula||themes",
themeName: "nebula||themeName",
@ -53,8 +53,8 @@ const marketPlaceSettings = {
if (p === "theme") {
let items = localStorage.getItem(AppearanceSettings.themes) as any;
items ? (items = JSON.parse(items)) : (items = []);
if (items.find((theme: any) => theme === packageName)) {
const idx = items.indexOf(packageName);
if (items.find((theme: any) => theme === packageName.toLowerCase())) {
const idx = items.indexOf(packageName.toLowerCase());
items.splice(idx, 1);
localStorage.setItem(AppearanceSettings.themes, JSON.stringify(items));
this.changeTheme(true);
@ -65,7 +65,7 @@ const marketPlaceSettings = {
let plugins = localStorage.getItem(PluginSettings.plugins) as any;
plugins ? (plugins = JSON.parse(plugins)) : (plugins = []);
//@ts-ignore
const plugin = plugins.find(({name}) => name === packageName);
const plugin = plugins.find(({name}) => name === packageName.toLowerCase());
if (plugin) {
plugin.remove = true;
localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins));
@ -77,6 +77,7 @@ const marketPlaceSettings = {
handlePlugins: function(worker: never | ServiceWorkerRegistration) {
return new Promise<void>((resolve) => {
const plugins = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || [];
const swPagePlugins: SWPagePlugin[] = [];
const swPlugins: SWPlugin[] = [];
if (plugins.length === 0) {
console.log('Plugin length is not greater then 0. Resolving.');
@ -84,22 +85,37 @@ const marketPlaceSettings = {
}
plugins.forEach(async (plugin: Plugin) => {
if (plugin.type === "page") {
const pluginScript = await fetch(`/packages/${plugin.name}/${plugin.src}`).then((res) => res.text());
const pluginScript = await fetch(`/packages/${plugin.name.toLowerCase()}/${plugin.src}`).then((res) => res.text());
const script = eval(pluginScript);
const inject = await script() as unknown as SWPlugin;
const inject = await script() as unknown as SWPagePlugin;
if (plugin.remove) {
//@ts-ignore freaking types BRO
const plug = plugins.filter(({ name }) => name !== plugin.name);
swPlugins.push({remove: true, host: inject.host, html: inject.html, injectTo: inject.injectTo});
const plug = plugins.filter(({ name }) => name !== plugin.name.toLowerCase());
swPagePlugins.push({remove: true, host: inject.host, html: inject.html, injectTo: inject.injectTo, type: "page"});
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plug));
}
else {
swPlugins.push({host: inject.host, html: inject.html, injectTo: inject.injectTo});
swPagePlugins.push({host: inject.host, html: inject.html, injectTo: inject.injectTo, type: "page"});
}
//only resolve AFTER we have postMessaged to the SW.
worker.active?.postMessage(swPlugins);
resolve();
worker.active?.postMessage(swPagePlugins);
}
else if (plugin.type === "serviceWorker") {
const pluginScript = await fetch(`/packages/${plugin.name.toLowerCase()}/${plugin.src}`).then((res) => res.text());
const script = eval(pluginScript);
const inject = await script() as unknown as SWPlugin;
if (plugin.remove) {
//@ts-ignore
const plug = plugins.filter(({ name }) => name !== plugin.name.toLowerCase());
swPlugins.push({remove: true, function: inject.function.toString(), name: plugin.name, events: inject.events, type: "serviceWorker"});
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plug));
}
else {
swPlugins.push({function: inject.function.toString(), name: plugin.name, events: inject.events, type: "serviceWorker"});
}
worker.active?.postMessage(swPlugins);
}
resolve();
});
});
},

View file

@ -23,12 +23,17 @@ interface Plugin {
type: PluginType;
remove?: boolean;
}
interface SWPlugin {
interface SWPagePlugin extends Omit<Plugin, "name" | "src"> {
host: string;
html: string;
injectTo: "head" | "body";
remove?: boolean;
}
interface SWPlugin extends Omit<Plugin, "src"> {
function: any;
events: [];
}
interface Package {
theme?: {
payload: string;
@ -48,6 +53,7 @@ export {
type Package,
type PluginType,
type Plugin,
type SWPagePlugin,
type SWPlugin,
SearchEngines,
type SearchEngine,