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: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
|
"/api/catalog-stats": {
|
||||||
|
target: "http://localhost:8080/api/catalog-stats",
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
|
||||||
|
},
|
||||||
"/api/catalog-assets": {
|
"/api/catalog-assets": {
|
||||||
target: "http://localhost:8080/api/catalog-assets",
|
target: "http://localhost:8080/api/catalog-assets",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|
59
public/sw.js
59
public/sw.js
|
@ -1,7 +1,9 @@
|
||||||
importScripts("/uv/uv.bundle.js");
|
importScripts("/uv/uv.bundle.js");
|
||||||
importScripts("/uv/uv.config.js");
|
importScripts("/uv/uv.config.js");
|
||||||
|
importScripts("/workerware/workerware.js");
|
||||||
importScripts(__uv$config.sw || "/uv/uv.sw.js");
|
importScripts(__uv$config.sw || "/uv/uv.sw.js");
|
||||||
const uv = new UVServiceWorker();
|
const uv = new UVServiceWorker();
|
||||||
|
const ww = new WorkerWare({ debug: false });
|
||||||
|
|
||||||
//where we handle our plugins!!!
|
//where we handle our plugins!!!
|
||||||
self.addEventListener("message", function(event) {
|
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)
|
//loop over the required data (we don't verify here as types will take care of us :D)
|
||||||
event.data.forEach((data) => {
|
event.data.forEach((data) => {
|
||||||
if (data.remove) {
|
if (data.remove) {
|
||||||
const idx = uv.config.inject.indexOf(data.host);
|
if (data.type === "page") {
|
||||||
uv.config.inject.splice(idx, 1);
|
const idx = uv.config.inject.indexOf(data.host);
|
||||||
|
uv.config.inject.splice(idx, 1);
|
||||||
|
}
|
||||||
|
else if (data.type === "serviceWorker") {
|
||||||
|
ww.deleteByName(data.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
uv.config.inject.push({
|
if (data.type === "page") {
|
||||||
host: data.host,
|
uv.config.inject.push({
|
||||||
html: data.html,
|
host: data.host,
|
||||||
injectTo: data.injectTo
|
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) {
|
self.addEventListener("fetch", function (event) {
|
||||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
|
event.respondWith(
|
||||||
event.respondWith(
|
(async () => {
|
||||||
(async function () {
|
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);
|
return await uv.fetch(event);
|
||||||
})()
|
}
|
||||||
);
|
else {
|
||||||
} else {
|
|
||||||
event.respondWith(
|
|
||||||
(async function () {
|
|
||||||
return await fetch(event.request);
|
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) {
|
function marketplaceAPI(app: FastifyInstance) {
|
||||||
app.get("/api", (request, reply) => {
|
app.get("/api/catalog-stats/", (request, reply) => {
|
||||||
reply.send({ Server: "Active" });
|
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).
|
// 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 chalk from "chalk";
|
||||||
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
|
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import gradient from "gradient-string";
|
import gradient from "gradient-string";
|
||||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
|
||||||
//@ts-ignore WHY would I want this typechecked AT ALL
|
//@ts-ignore WHY would I want this typechecked AT ALL
|
||||||
import { handler as ssrHandler } from "../dist/server/entry.mjs";
|
import { handler as ssrHandler } from "../dist/server/entry.mjs";
|
||||||
import { parsedDoc } from "./config.js";
|
import { parsedDoc } from "./config.js";
|
||||||
|
|
|
@ -195,5 +195,5 @@ import { VERSION } from "astro:env/client";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
}, true);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//marketplace code & handlers
|
//marketplace code & handlers
|
||||||
import { Settings } from ".";
|
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 = {
|
const AppearanceSettings = {
|
||||||
themes: "nebula||themes",
|
themes: "nebula||themes",
|
||||||
themeName: "nebula||themeName",
|
themeName: "nebula||themeName",
|
||||||
|
@ -53,8 +53,8 @@ const marketPlaceSettings = {
|
||||||
if (p === "theme") {
|
if (p === "theme") {
|
||||||
let items = localStorage.getItem(AppearanceSettings.themes) as any;
|
let items = localStorage.getItem(AppearanceSettings.themes) as any;
|
||||||
items ? (items = JSON.parse(items)) : (items = []);
|
items ? (items = JSON.parse(items)) : (items = []);
|
||||||
if (items.find((theme: any) => theme === packageName)) {
|
if (items.find((theme: any) => theme === packageName.toLowerCase())) {
|
||||||
const idx = items.indexOf(packageName);
|
const idx = items.indexOf(packageName.toLowerCase());
|
||||||
items.splice(idx, 1);
|
items.splice(idx, 1);
|
||||||
localStorage.setItem(AppearanceSettings.themes, JSON.stringify(items));
|
localStorage.setItem(AppearanceSettings.themes, JSON.stringify(items));
|
||||||
this.changeTheme(true);
|
this.changeTheme(true);
|
||||||
|
@ -65,7 +65,7 @@ const marketPlaceSettings = {
|
||||||
let plugins = localStorage.getItem(PluginSettings.plugins) as any;
|
let plugins = localStorage.getItem(PluginSettings.plugins) as any;
|
||||||
plugins ? (plugins = JSON.parse(plugins)) : (plugins = []);
|
plugins ? (plugins = JSON.parse(plugins)) : (plugins = []);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const plugin = plugins.find(({name}) => name === packageName);
|
const plugin = plugins.find(({name}) => name === packageName.toLowerCase());
|
||||||
if (plugin) {
|
if (plugin) {
|
||||||
plugin.remove = true;
|
plugin.remove = true;
|
||||||
localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins));
|
localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins));
|
||||||
|
@ -77,6 +77,7 @@ const marketPlaceSettings = {
|
||||||
handlePlugins: function(worker: never | ServiceWorkerRegistration) {
|
handlePlugins: function(worker: never | ServiceWorkerRegistration) {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
const plugins = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || [];
|
const plugins = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || [];
|
||||||
|
const swPagePlugins: SWPagePlugin[] = [];
|
||||||
const swPlugins: SWPlugin[] = [];
|
const swPlugins: SWPlugin[] = [];
|
||||||
if (plugins.length === 0) {
|
if (plugins.length === 0) {
|
||||||
console.log('Plugin length is not greater then 0. Resolving.');
|
console.log('Plugin length is not greater then 0. Resolving.');
|
||||||
|
@ -84,22 +85,37 @@ const marketPlaceSettings = {
|
||||||
}
|
}
|
||||||
plugins.forEach(async (plugin: Plugin) => {
|
plugins.forEach(async (plugin: Plugin) => {
|
||||||
if (plugin.type === "page") {
|
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 script = eval(pluginScript);
|
||||||
const inject = await script() as unknown as SWPlugin;
|
const inject = await script() as unknown as SWPagePlugin;
|
||||||
if (plugin.remove) {
|
if (plugin.remove) {
|
||||||
//@ts-ignore freaking types BRO
|
//@ts-ignore freaking types BRO
|
||||||
const plug = plugins.filter(({ name }) => name !== plugin.name);
|
const plug = plugins.filter(({ name }) => name !== plugin.name.toLowerCase());
|
||||||
swPlugins.push({remove: true, host: inject.host, html: inject.html, injectTo: inject.injectTo});
|
swPagePlugins.push({remove: true, host: inject.host, html: inject.html, injectTo: inject.injectTo, type: "page"});
|
||||||
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plug));
|
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plug));
|
||||||
}
|
}
|
||||||
else {
|
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.
|
//only resolve AFTER we have postMessaged to the SW.
|
||||||
worker.active?.postMessage(swPlugins);
|
worker.active?.postMessage(swPagePlugins);
|
||||||
resolve();
|
|
||||||
}
|
}
|
||||||
|
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;
|
type: PluginType;
|
||||||
remove?: boolean;
|
remove?: boolean;
|
||||||
}
|
}
|
||||||
interface SWPlugin {
|
interface SWPagePlugin extends Omit<Plugin, "name" | "src"> {
|
||||||
host: string;
|
host: string;
|
||||||
html: string;
|
html: string;
|
||||||
injectTo: "head" | "body";
|
injectTo: "head" | "body";
|
||||||
remove?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SWPlugin extends Omit<Plugin, "src"> {
|
||||||
|
function: any;
|
||||||
|
events: [];
|
||||||
|
}
|
||||||
|
|
||||||
interface Package {
|
interface Package {
|
||||||
theme?: {
|
theme?: {
|
||||||
payload: string;
|
payload: string;
|
||||||
|
@ -48,6 +53,7 @@ export {
|
||||||
type Package,
|
type Package,
|
||||||
type PluginType,
|
type PluginType,
|
||||||
type Plugin,
|
type Plugin,
|
||||||
|
type SWPagePlugin,
|
||||||
type SWPlugin,
|
type SWPlugin,
|
||||||
SearchEngines,
|
SearchEngines,
|
||||||
type SearchEngine,
|
type SearchEngine,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue