mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-13 12:00:01 -04:00
Workerware
This commit is contained in:
parent
5dc222a0df
commit
0fae05e33a
8 changed files with 255 additions and 36 deletions
|
@ -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,
|
||||
|
|
59
public/sw.js
59
public/sw.js
|
@ -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);
|
||||
})()
|
||||
);
|
||||
}
|
||||
}
|
||||
})()
|
||||
);
|
||||
});
|
||||
|
|
168
public/workerware/workerware.js
Normal file
168
public/workerware/workerware.js
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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).
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -195,5 +195,5 @@ import { VERSION } from "astro:env/client";
|
|||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}, true);
|
||||
</script>
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue