diff --git a/astro.config.ts b/astro.config.ts index ae3cf9b..bebc418 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -5,7 +5,7 @@ import { baremuxPath } from "@mercuryworkshop/bare-mux"; import { epoxyPath } from "@mercuryworkshop/epoxy-transport"; import { libcurlPath } from "@mercuryworkshop/libcurl-transport"; import playformCompress from "@playform/compress"; -import { uvPath } from "@titaniumnetwork-dev/ultraviolet"; +import { uvPath } from "@rubynetwork/ultraviolet"; import icon from "astro-icon"; import { defineConfig, envField } from "astro/config"; import { viteStaticCopy } from "vite-plugin-static-copy"; diff --git a/database_assets/com.nebula.darkMode/index.js b/database_assets/com.nebula.darkMode/index.js new file mode 100644 index 0000000..ca151ee --- /dev/null +++ b/database_assets/com.nebula.darkMode/index.js @@ -0,0 +1,11 @@ +const script = ` +console.error('GYATT') +` + +self.entryFunc = function(){ + return { + host: 'example.com', + html: ``, + injectTo: 'body' + } +} diff --git a/package.json b/package.json index 9525292..ed8d322 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "@playform/compress": "^0.1.4", "@rubynetwork/rammerhead": "^1.3.5", "@rubynetwork/rammerhead-browser": "^1.0.9", + "@rubynetwork/ultraviolet": "3.2.7-ruby.1", "@svelte-drama/suspense": "0.5.1", - "@titaniumnetwork-dev/ultraviolet": "3.1.2", "@types/node": "^22.7.5", "@types/sequelize": "^4.28.20", "astro": "^4.16.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d34332..25559e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,12 +56,12 @@ importers: '@rubynetwork/rammerhead-browser': specifier: ^1.0.9 version: 1.0.9 + '@rubynetwork/ultraviolet': + specifier: 3.2.7-ruby.1 + version: 3.2.7-ruby.1 '@svelte-drama/suspense': specifier: 0.5.1 version: 0.5.1(svelte@4.2.19) - '@titaniumnetwork-dev/ultraviolet': - specifier: 3.1.2 - version: 3.1.2 '@types/node': specifier: ^22.7.5 version: 22.7.5 @@ -871,6 +871,9 @@ packages: '@mercuryworkshop/bare-mux@1.1.1': resolution: {integrity: sha512-qKOnTsIjwv4wBvToek3Jm+y3F/BcFtjy6HOsdyzIUemCOw51kodzRsvWvU9Pf/JYDVPV8or0zbsg+qOKtasjhA==} + '@mercuryworkshop/bare-mux@1.1.4': + resolution: {integrity: sha512-mJPezqEpiKTCs+wu/3TowilnVXQgFs4SqoQnnbCbOc5cV6bDggaolKkyYjDnmOB9t/nptIIXDWDB31oS9vX9kA==} + '@mercuryworkshop/epoxy-tls@2.1.6-1': resolution: {integrity: sha512-drnffDo9Ls73Fpmcup2Ys1z+BjzK+WLnzyfS4APFfWr9cJ0gu7567tx4M06XH5PUZMOS1J1Z3wqnRaBh/RX5bQ==} @@ -1017,6 +1020,9 @@ packages: '@rubynetwork/rh@1.2.74': resolution: {integrity: sha512-qbVueaMHxSXF8dysQE3eL2Fj8x5L1cBMzk5G6iVokntQjYxCHcV+vWvZbykXspvPJjs4b5uYnZ+D/rMHmSVhrg==} + '@rubynetwork/ultraviolet@3.2.7-ruby.1': + resolution: {integrity: sha512-JaIlRFRObrXk5lWCfOs4CQ+caRm0aqRqZFMvFhXoRXK02YbhemF01/+JQEHwWgC1XeldyHf60LfL4JKhGqD3MA==} + '@shikijs/core@1.22.0': resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==} @@ -1052,9 +1058,6 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - '@titaniumnetwork-dev/ultraviolet@3.1.2': - resolution: {integrity: sha512-PvhyL9IQtSwHTVRRpNGn+YCWkSzP7JEk0wX7M5YfUSobBicoRLOJhCC4u6T9qh/vObDpLDE3TfP4GKqMTSa2rw==} - '@tootallnate/once@1.1.2': resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} @@ -1277,6 +1280,10 @@ packages: array-iterate@2.0.1: resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + astro-icon@1.1.1: resolution: {integrity: sha512-HKBesWk2Faw/0+klLX+epQVqdTfSzZz/9+5vxXUjTJaN/HnpDf608gRPgHh7ZtwBPNJMEFoU5GLegxoDcT56OQ==} @@ -2146,6 +2153,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + idb@8.0.0: + resolution: {integrity: sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2534,6 +2544,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meriyah@6.0.2: + resolution: {integrity: sha512-RHlo7GK3qrswKzVcMJ/2bMMkMChpn4L/JUOBfs81c3SKp/ZOS5u2aC/qY+sZCiE4qPkrC+a1yNPfGieh0uz19Q==} + engines: {node: '>=18.0.0'} + micromark-core-commonmark@2.0.1: resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} @@ -4834,6 +4848,11 @@ snapshots: '@types/uuid': 9.0.8 uuid: 9.0.1 + '@mercuryworkshop/bare-mux@1.1.4': + dependencies: + '@types/uuid': 9.0.8 + uuid: 9.0.1 + '@mercuryworkshop/epoxy-tls@2.1.6-1': {} '@mercuryworkshop/epoxy-transport@https://codeload.github.com/motortruck1221/epoxytransport/tar.gz/5e8205cdfef67e1cf73fbba7fb56cb181de070f7': @@ -5001,6 +5020,16 @@ snapshots: - supports-color - utf-8-validate + '@rubynetwork/ultraviolet@3.2.7-ruby.1': + dependencies: + '@mercuryworkshop/bare-mux': 1.1.4 + astring: 1.9.0 + events: 3.3.0 + idb: 8.0.0 + meriyah: 6.0.2 + parse5: 7.2.0 + set-cookie-parser: 2.7.0 + '@shikijs/core@1.22.0': dependencies: '@shikijs/engine-javascript': 1.22.0 @@ -5055,8 +5084,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@titaniumnetwork-dev/ultraviolet@3.1.2': {} - '@tootallnate/once@1.1.2': optional: true @@ -5309,6 +5336,8 @@ snapshots: array-iterate@2.0.1: {} + astring@1.9.0: {} + astro-icon@1.1.1: dependencies: '@iconify/tools': 4.0.7 @@ -6389,6 +6418,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb@8.0.0: {} + ieee754@1.2.1: {} import-meta-resolve@4.1.0: {} @@ -6810,6 +6841,8 @@ snapshots: merge2@1.4.1: {} + meriyah@6.0.2: {} + micromark-core-commonmark@2.0.1: dependencies: decode-named-character-reference: 1.0.2 diff --git a/public/sw.js b/public/sw.js index d48ffab..93feac4 100644 --- a/public/sw.js +++ b/public/sw.js @@ -4,6 +4,34 @@ importScripts("/uv/uv.bundle.js"); importScripts("/uv/uv.config.js"); importScripts(__uv$config.sw || "/uv/uv.sw.js"); const uv = new UVServiceWorker(); + +//where we handle our plugins!!! +self.addEventListener("message", function(event) { + //console.log(event.data); + if (uv.config.inject === undefined) { + uv.config.inject = []; + } + //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) { + if (uv.config.inject.find(({ host }) => host === data.host)) { + const idx = uv.config.inject.indexOf(data.host); + uv.config.inject.splice(idx, 1); + } + } + else { + if (!uv.config.inject.find(({ host }) => host === data.host)) { + uv.config.inject.push({ + host: data.host, + html: data.html, + injectTo: data.injectTo + }); + } + } + }); + console.log(uv.config.inject); +}); + self.addEventListener("fetch", function (event) { if (event.request.url.startsWith(location.origin + __uv$config.prefix)) { event.respondWith( diff --git a/public/workerware/WWError.js b/public/workerware/WWError.js new file mode 100644 index 0000000..0072ca4 --- /dev/null +++ b/public/workerware/WWError.js @@ -0,0 +1,6 @@ +class WWError extends Error { + constructor(message) { + super(message); + this.name = "[WorkerWare Exception]"; + } +} diff --git a/public/workerware/workerware.js b/public/workerware/workerware.js new file mode 100644 index 0000000..a1612b7 --- /dev/null +++ b/public/workerware/workerware.js @@ -0,0 +1,168 @@ +importScripts("./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, + }; + } +} diff --git a/server/dbSetup.ts b/server/dbSetup.ts index 3a6e047..25ebec8 100644 --- a/server/dbSetup.ts +++ b/server/dbSetup.ts @@ -22,7 +22,8 @@ async function installItems(db: ModelStatic, items: Items[]) { payload: item.payload, background_video: item.background_video, background_image: item.background_image, - type: item.type + type: item.type, + entryFunc: item.entryFunc }); }); } @@ -39,7 +40,8 @@ async function setupDB(db: ModelStatic) { description: "The gruvbox theme", tags: ["Theme", "Simple"], payload: "gruvbox.css", - type: "theme" + type: "theme", + entryFunc: null }, { package_name: "com.nebula.oled", @@ -50,7 +52,8 @@ async function setupDB(db: ModelStatic) { description: "A sleek & simple Oled theme for Nebula", tags: ["Theme", "Simple", "Sleek"], payload: "oled.css", - type: "theme" + type: "theme", + entryFunc: null }, { package_name: "com.nebula.lightTheme", @@ -61,7 +64,8 @@ async function setupDB(db: ModelStatic) { description: "A sleek light theme for Nebula", tags: ["Theme", "Simple", "Light"], payload: "light.css", - type: "theme" + type: "theme", + entryFunc: null }, { package_name: "com.nebula.retro", @@ -72,7 +76,20 @@ async function setupDB(db: ModelStatic) { description: "Give a retro look to Nebula", tags: ["Theme", "Simple", "Dark", "Retro"], payload: "retro.css", - type: "theme" + type: "theme", + entryFunc: null + }, + { + package_name: "com.nebula.darkMode", + title: "Dark Mode", + image: "dark.png", + author: "Nebula Services", + version: "1.0.0", + description: "Force dark mode on all websites", + tags: ["Plugin", "Dark Mode", "Noctura"], + payload: "index.js", + type: "plugin", + entryFunc: "entryFunc" } ]; const dbItems = await db.findAll(); diff --git a/server/marketplace.ts b/server/marketplace.ts index cfc0c9d..d525fb7 100644 --- a/server/marketplace.ts +++ b/server/marketplace.ts @@ -28,6 +28,7 @@ interface Catalog { background_video: string; payload: string; type: CatalogType; + entryFunc: string; } interface CatalogModel @@ -45,7 +46,8 @@ const catalogAssets = db.define("catalog_assets", { background_image: { type: DataTypes.TEXT, allowNull: true }, background_video: { type: DataTypes.TEXT, allowNull: true }, payload: { type: DataTypes.TEXT }, - type: { type: DataTypes.TEXT } + type: { type: DataTypes.TEXT }, + entryFunc: { type: DataTypes.STRING } }); function marketplaceAPI(app: FastifyInstance) { @@ -78,7 +80,8 @@ function marketplaceAPI(app: FastifyInstance) { background_image: asset.background_image, background_video: asset.background_video, payload: asset.payload, - type: asset.type + type: asset.type, + entryFunc: asset.entryFunc }; return acc; }, {}); @@ -105,7 +108,8 @@ function marketplaceAPI(app: FastifyInstance) { background_image: packageRow.get("background_image"), background_video: packageRow.get("background_video"), payload: packageRow.get("payload"), - type: packageRow.get("type") + type: packageRow.get("type"), + entryFunc: packageRow.get("entryFunc") }; reply.send(details); } catch (error) { @@ -128,6 +132,7 @@ function marketplaceAPI(app: FastifyInstance) { background_video: string; background_image: string; type: CatalogType; + entryFunc: string; }; }>; interface VerifyStatus { @@ -193,7 +198,8 @@ function marketplaceAPI(app: FastifyInstance) { payload: request.body.payload, background_video: request.body.background_video, background_image: request.body.background_image, - type: request.body.type as CatalogType + type: request.body.type as CatalogType, + entryFunc: request.body.entryFunc }; await catalogAssets.create({ package_name: body.package_name, @@ -206,7 +212,8 @@ function marketplaceAPI(app: FastifyInstance) { payload: body.payload, background_video: body.background_video, background_image: body.background_image, - type: body.type + type: body.type, + entryFunc: body.entryFunc }); const assets = fileURLToPath(new URL("../database_assets", import.meta.url)); try { diff --git a/src/components/catalog/InstalledPlugins.svelte b/src/components/catalog/InstalledPlugins.svelte new file mode 100644 index 0000000..f50b34f --- /dev/null +++ b/src/components/catalog/InstalledPlugins.svelte @@ -0,0 +1,56 @@ + + + {#await suspend(assets) then data} + {#each Object.entries(data) as [key, asset]} + + + {settings.marketPlaceSettings.changeTheme(false, asset.payload, asset.background_video, asset.background_image, asset.package_name)}}> + + + + {asset.title} + + {settings.marketPlaceSettings.uninstall("theme", asset.package_name); settings.marketPlaceSettings.changeTheme(true); compRef[key].$destroy()}}> + + + + + + + + + + + + + + {/each} + {/await} + diff --git a/src/env.d.ts b/src/env.d.ts index 7495309..f8f05ea 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,5 +1,5 @@ /// /// -/// +/// declare var BareMux: any; declare var EpxMod: any; diff --git a/src/pages/[lang]/catalog/package/[...packageName].astro b/src/pages/[lang]/catalog/package/[...packageName].astro index 9618d67..be16233 100644 --- a/src/pages/[lang]/catalog/package/[...packageName].astro +++ b/src/pages/[lang]/catalog/package/[...packageName].astro @@ -53,17 +53,24 @@ const assetsJson = await response.json(); } customElements.define('variable-define', VariableDefiner); const fn = () => { - const items = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes) as string) || []; - const itemExists = items.indexOf(packageName) !== -1; + const cssItems = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes) as string) || []; + const cssItemExists = cssItems.indexOf(packageName) !== -1; + const pluginItems = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || []; + //@ts-ignore + const pluginItemExists = pluginItems.find(({ name }) => name === packageName); const installButton = document.getElementById("install") as HTMLButtonElement; const uninstallButton = document.getElementById("uninstall") as HTMLButtonElement; const payload = assetsJson ? JSON.parse(assetsJson) : undefined; - if (itemExists) { + if (cssItemExists || pluginItemExists) { uninstallButton.classList.remove("hidden"); installButton.classList.add("hidden"); } installButton.addEventListener("click", () => { - settings.marketPlaceSettings.install({theme: {payload: payload.payload, video: payload.background_video, bgImage: payload.background_image}}, packageName, payload.payload).then(() => { + settings.marketPlaceSettings.install( + payload.type === "theme" ? {theme: {payload: payload.payload, video: payload.background_video, bgImage: payload.background_image}} : + {plugin: {name: packageName, src: payload.payload, type: "page", entryFunc: payload.entryFunc }}, + packageName, payload.payload + ).then(() => { installButton.classList.add("hidden"); uninstallButton.classList.remove("hidden"); }) diff --git a/src/pages/[lang]/index.astro b/src/pages/[lang]/index.astro index 3152e12..a49f82e 100644 --- a/src/pages/[lang]/index.astro +++ b/src/pages/[lang]/index.astro @@ -54,7 +54,8 @@ import { VERSION } from "astro:env/client"; WispServerURLS, SearchEngines, Settings, - cloak, + cloak, + settings } from "@utils/settings/index"; import { search } from "@utils/search.ts"; //../../utils/search.ts //@ts-expect-error No types, expected. See: https://github.com/ading2210/libcurl.js for docs on how to use. @@ -81,14 +82,16 @@ import { VERSION } from "astro:env/client"; } } function uv(iframe: HTMLIFrameElement, term: string) { - initSw().then(() => { - setTransport(localStorage.getItem(Settings.ProxySettings.transport) as string).then(async () => { - iframe.classList.remove("hidden"); - const url = await proxy(term, false); - if (url) { - iframe.src = url as string; - } - }); + initSw().then((reg) => { + settings.marketPlaceSettings.handlePlugins(reg).then(() => { + setTransport(localStorage.getItem(Settings.ProxySettings.transport) as string).then(async () => { + iframe.classList.remove("hidden"); + const url = await proxy(term, false); + if (url) { + iframe.src = url as string; + } + }); + }); }); } async function rh(iframe: HTMLIFrameElement, term: string) { diff --git a/src/pages/[lang]/settings/appearance.astro b/src/pages/[lang]/settings/appearance.astro index 4289c6c..185fec7 100644 --- a/src/pages/[lang]/settings/appearance.astro +++ b/src/pages/[lang]/settings/appearance.astro @@ -24,7 +24,7 @@ import { MARKETPLACE_ENABLED } from "astro:env/client"; {MARKETPLACE_ENABLED && - + diff --git a/src/pages/[lang]/settings/pr.astro b/src/pages/[lang]/settings/pr.astro index 030f1bf..b5235dc 100644 --- a/src/pages/[lang]/settings/pr.astro +++ b/src/pages/[lang]/settings/pr.astro @@ -6,6 +6,8 @@ import Layout from "@layouts/Layout.astro"; import SettingsLayout from "@layouts/SettingsLayout.astro"; import SettingsSection from "@layouts/SettingsSection.astro"; import { getLangFromUrl, useTranslations } from "../../../i18n/utils"; +import InstalledPlugins from "@components/catalog/InstalledPlugins.svelte"; +import { Icon } from "astro-icon/components"; const lang = getLangFromUrl(Astro.url); const t = useTranslations(lang); @@ -95,7 +97,20 @@ export const prerender = true; }} /> - + + + + + + + + + + Get more plugins in the Nebula Catalog! + + + + ((resolve) => { + return new Promise((resolve) => { if ("serviceWorker" in navigator) { - navigator.serviceWorker.ready.then(async () => { + navigator.serviceWorker.ready.then(async (reg) => { console.debug("Service worker ready!"); await loadProxyScripts(); - resolve(); + resolve(reg); }); navigator.serviceWorker.register("/sw.js", { scope: "/" }); } diff --git a/src/utils/settings/index.ts b/src/utils/settings/index.ts index ea16d34..bb51798 100644 --- a/src/utils/settings/index.ts +++ b/src/utils/settings/index.ts @@ -1,5 +1,5 @@ //Combine all of the other settings into one object. And export that (along with types and other things) -import { AppearanceSettings, marketPlaceSettings } from "./marketplace"; +import { AppearanceSettings, MarketPlaceExtras, PluginSettings, marketPlaceSettings } from "./marketplace"; import { ProxySettings, proxySettings } from "./proxy"; import { TabSettings, cloak, tabSettings } from "./tab"; import { @@ -19,7 +19,9 @@ import { const Settings = { AppearanceSettings, TabSettings, - ProxySettings + ProxySettings, + MarketPlaceExtras, + PluginSettings }; const settings = { diff --git a/src/utils/settings/marketplace.ts b/src/utils/settings/marketplace.ts index 0e5b5ae..9b1cab6 100644 --- a/src/utils/settings/marketplace.ts +++ b/src/utils/settings/marketplace.ts @@ -1,14 +1,22 @@ //marketplace code & handlers -import { type Package, type PackageType } from "./types"; +import { Settings } from "."; +import { type Package, type PackageType, type Plugin, type PluginType, type SWPlugin } from "./types"; const AppearanceSettings = { themes: "nebula||themes", themeName: "nebula||themeName", stylePayload: "nebula||stylepayload", video: "nebula||video", image: "nebula||image", +}; + +const PluginSettings = { + plugins: "nebula||plugins" +} + +const MarketPlaceExtras = { proxy: "nebula||marketplaceProxy", hostname: "nebula||marketplaceHostname" -}; +} const marketPlaceSettings = { install: function (p: Package, packageName: string, payload?: any) { @@ -23,6 +31,16 @@ const marketPlaceSettings = { } resolve(); } + if (p.plugin) { + let plugins = localStorage.getItem(PluginSettings.plugins) as any; + plugins ? (plugins = JSON.parse(plugins)) : (plugins = []); + //@ts-ignore + if (!plugins.find(({ name }) => name === packageName)) { + plugins.push({name: packageName, src: p.plugin.src, type: p.plugin.type, entryFunc: p.plugin.entryFunc} as unknown as Plugin) + localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins)); + } + resolve(); + } }); }, uninstall: function (p: PackageType, packageName: string) { @@ -38,6 +56,31 @@ const marketPlaceSettings = { } resolve(); } + if (p === "plugin") { + let plugins = localStorage.getItem(PluginSettings.plugins) as any; + plugins ? (plugins = JSON.parse(plugins)) : (plugins = []); + //@ts-ignore + if (plugins.find(({name}) => name === packageName)) { + const idx = plugins.indexOf(packageName); + plugins.splice(idx, 1); + localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins)); + } + resolve(); + } + }); + }, + handlePlugins: function(worker: never | ServiceWorkerRegistration) { + return new Promise((resolve) => { + const plugins = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || []; + plugins.forEach(async (plugin: Plugin) => { + if (plugin.type === "page") { + const pluginScript = await fetch(`/packages/${plugin.name}/${plugin.src}`).then((res) => res.text()); + const script = eval(pluginScript); + const inject = script() as unknown as SWPlugin; + worker.active?.postMessage([{host: inject.host, html: inject.html, injectTo: inject.injectTo}] as SWPlugin[]); + } + }); + resolve(); }); }, changeTheme: async function ( @@ -105,4 +148,4 @@ const marketPlaceSettings = { } }; -export { AppearanceSettings, marketPlaceSettings }; +export { AppearanceSettings, PluginSettings, MarketPlaceExtras, marketPlaceSettings }; diff --git a/src/utils/settings/types.ts b/src/utils/settings/types.ts index fe84dbb..d14b5db 100644 --- a/src/utils/settings/types.ts +++ b/src/utils/settings/types.ts @@ -5,14 +5,6 @@ type OpenIn = "a:b" | "blob" | "direct" | "embed"; type Proxy = "automatic" | "uv" | "rh"; type Transport = "epoxy" | "libcurl"; type PackageType = "theme" | "plugin"; -interface Package { - theme?: { - payload: string; - video?: string; - bgImage?: string; - }; - plugin?: {}; -} const SearchEngines: Record = { ddg: "https://duckduckgo.com/?q=%s", google: "https://google.com/search?q=%s", @@ -24,6 +16,27 @@ const WispServerURLS: Record = { ruby: "wss://ruby.rubynetwork.co/wisp/" }; +type PluginType = "page" | "serviceWorker" +interface Plugin { + name: string; + src: string; + type: PluginType; + entryFunc: () => unknown | unknown; +} +interface SWPlugin { + host: string; + html: string; + injectTo: "head" | "body"; +} +interface Package { + theme?: { + payload: string; + video?: string; + bgImage?: string; + }; + plugin?: Plugin; +} + export { type TabCloaks, type AbCloaks, @@ -32,6 +45,9 @@ export { type Transport, type PackageType, type Package, + type PluginType, + type Plugin, + type SWPlugin, SearchEngines, type SearchEngine, WispServerURLS,