diff --git a/src/components/settings/Loader.astro b/src/components/settings/Loader.astro index 82ba805..4f31232 100644 --- a/src/components/settings/Loader.astro +++ b/src/components/settings/Loader.astro @@ -27,6 +27,7 @@ import { SW, createProxyScripts, checkProxyScripts, createBareMuxConn, setTransport } from "@utils/serviceWorker"; import { Settings } from "@utils/settings"; import { log } from "@utils/index"; + import { Marketplace } from "@utils/marketplace"; const titleText = ` _ _ _ _ ____ _ | \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___ @@ -46,9 +47,11 @@ } await checkProxyScripts(); const conn = await createBareMuxConn("/baremux/worker.js"); - window.sw = new SW(conn); - for await (const _ of Settings.initDefaults()) {}; - const { serviceWorker, bareMuxConn, sj } = await window.sw.getSWInfo(); + const sw = new SW(conn); + new Marketplace(); + // We don't do anything with the values (they aren't even any), so we just are iterating. + for await (const _ of Settings.initDefaults()); + const { serviceWorker, bareMuxConn, sj } = await sw.getSWInfo(); log({ type: 'info', bg: true, prefix: true }, `General init completed! \n\nServiceWorker: ${serviceWorker.active?.state} \nBareMuxConn: ${bareMuxConn ? 'Active': 'Not active'} \nScramjetController: ${sj ? 'Active' : 'Not active'}`); } const initSettings = async () => { diff --git a/src/global.d.ts b/src/global.d.ts index 17fb87e..159a5e6 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,10 +1,10 @@ // LEGIT here for ONE global. +import type { Marketplace } from "@utils/marketplace"; import type { SW } from "@utils/serviceWorker"; declare global { interface Window { - sw: SW; } }; diff --git a/src/pages/[lang]/index.astro b/src/pages/[lang]/index.astro index 7f25b04..ddae9a7 100644 --- a/src/pages/[lang]/index.astro +++ b/src/pages/[lang]/index.astro @@ -49,7 +49,7 @@ import { VERSION } from "astro:env/client"; import { BareClient } from "@mercuryworkshop/bare-mux"; import { defaultStore } from "@utils/storage"; import { Settings } from "@utils/settings"; - import { setTransport } from "@utils/serviceWorker"; + import { setTransport, SW } from "@utils/serviceWorker"; type Suggestion = { phrase: string; @@ -81,7 +81,8 @@ import { VERSION } from "astro:env/client"; iframe.src = `${__uv$config.prefix}${__uv$config.encodeUrl!(val)}`; break; case "sj": - const { sj } = await window.sw.getSWInfo(); + const sw = SW.getInstances().next().value as SW; + const { sj } = await sw.getSWInfo(); iframe.src = sj.encodeUrl(val); break; } diff --git a/src/utils/marketplace.ts b/src/utils/marketplace.ts index 9fe597b..993cba3 100644 --- a/src/utils/marketplace.ts +++ b/src/utils/marketplace.ts @@ -1,4 +1,4 @@ -import { log } from "./index"; +import { Elements, log } from "./index"; import { StoreManager } from "./storage"; import { SettingsVals } from "./values"; @@ -54,25 +54,71 @@ interface Theme { bgImage?: string; }; +/** + * A class where for all of the Marketplace handlers. + * It creates it's own StoreManager where all of it's values will live. + * And it has 2 static items. instances and getInstances. + * + * @example + * //Create a new Marketplace instance + * const mp = new Marketplace(); + * //Use one of the very many methods available. + * + * //Get all instances of a Marketplace is as easy as: + * // const mp = Marketplace.getInstances.next().value; + * // Consume and use the class. +*/ class Marketplace { //create our own subsidized StoreManager with it's own prefix so the marketplace stuff NEVER touches the other data #storage: StoreManager<"nebula||marketplace">; + static #instances = new Set(); constructor() { this.#storage = new StoreManager("nebula||marketplace"); + log({ type: 'info', bg: true, prefix: true }, 'Marketplace instance created and ready!'); + Marketplace.#instances.add(this); } + + /** + * A static method to aquire an instance of a marketplace object. + * + * @example + * //Get the first insatnce available. + * const mp = Marketplace.getInstances.next().value + * + * @example + * // Iterate over every instance + * for (const instance of Marketplace.getInstances()) { + * // Do some work + * } + */ + static *getInstances() { + //Marketplace.instances.forEach((val) => yield val); + for (const item of Marketplace.#instances.keys()) { + yield item; + } + } + + /** + * Install a theme into both localstorage AND set the theme. + * + * @example + * const mp = new Marketplace() // OR get an instances from getInstances() + * mp.installTheme({ + * name: "testTheme", + * payload: "/packages/testTheme/index.css", + * // video: if you have a bg video, pass it here. + * //bgImage: pass the BG image here if you have one + * }); + */ async installTheme(theme: Theme) { - const themes = this.#storage.getVal(SettingsVals.marketPlace.themes) - ? JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.themes)) - : []; + const themes = JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.themes)) || []; if (themes.find((t: any) => t === theme.name)) return log({ type: 'error', bg: false, prefix: false, throw: true }, `${theme.name} is already installed!`) themes.push(theme.name); this.#storage.setVal(SettingsVals.marketPlace.themes, JSON.stringify(themes)); } async installPlugin(plugin: Plug) { - const plugins = this.#storage.getVal(SettingsVals.marketPlace.plugins) - ? JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.plugins)) - : []; + const plugins = JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.plugins)) || []; const plug = plugins.find(({ name }: { name: string }) => name === plugin.name); if (plug && plug.remove === false) return log({ type: 'error', bg: false, prefix: false, throw: true }, `${plugin.name} is already installed!`); @@ -80,8 +126,140 @@ class Marketplace { plugins.push({ name: plugin.name, src: plugin.src, type: plugin.type } as unknown as Plug); this.#storage.setVal(SettingsVals.marketPlace.plugins, JSON.stringify(plugins)); } + + async uninstallTheme(theme: Theme) { + const items = JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.plugins)) || []; + if (!items.find((th: string) => th === theme.name)) { + return log({ type: 'error', bg: false, prefix: false, throw: true }, `Theme: ${theme.name} is not installed!`); + } + const idx = items.indexOf(theme.name.toLowerCase()); + items.splice(idx, 1); + this.#storage.setVal(SettingsVals.marketPlace.themes, JSON.stringify(items)); + } + + async uninstallPlugin(plug: Plug) { + const items = JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.plugins)) || []; + const plugin = items.find(({ name }: { name: string }) => name === plug.name.toLowerCase()); + if (!plugin) return log({ type: 'error', bg: false, prefix: false, throw: true }, `Plugin: ${plug.name} is not installed!`); + plugin.remove = true; + this.#storage.setVal(SettingsVals.marketPlace.plugins, JSON.stringify(items)); + } + + async handlePlugins(worker: ServiceWorkerRegistration) { + let plugins = JSON.parse(this.#storage.getVal(SettingsVals.marketPlace.plugins)) || []; + const pagePlugins: SWPagePlugin[] = []; + const swPlugins: SWPlugin[] = []; + if (plugins.length === 0) return log({ type: 'info', bg: false, prefix: true }, 'No plugins to add! Exiting.'); + plugins.map(async (plugin: Plug) => { + if (plugin.type === "page") { + const script = await fetch(`/packages/${plugin.name.toLowerCase()}/${plugin.src}`); + const scriptRes = await script.text(); + const evaledScript = eval(scriptRes); + const inject = (await evaledScript()) as unknown as SWPagePlugin; + if (!plugin.remove) { + pagePlugins.push({ + host: inject.host, + html: inject.html, + injectTo: inject.injectTo, + type: 'page' + }); + } + else { + plugins = plugins.filter(({ name }: { name: string }) => name !== plugin.name.toLowerCase()); + pagePlugins.push({ + remove: true, + host: inject.host, + html: inject.html, + injectTo: inject.injectTo, + type: 'page' + }); + } + worker.active?.postMessage(pagePlugins); + } + + if (plugin.type === "serviceWorker") { + const s = await fetch(`/packages/${plugin.name.toLowerCase()}/${plugin.src}`); + const sRes = await s.text(); + const eScript = eval(sRes); + const inject = (await eScript()) as unknown as SWPlugin; + if (!plugin.remove) { + swPlugins.push({ + function: inject.function.toString(), + name: plugin.name, + events: inject.events, + type: 'serviceWorker' + }); + } + else { + plugins = plugins.filter(({ name }: { name: string }) => name !== plugin.name.toLowerCase()); + swPlugins.push({ + remove: true, + function: inject.function.toString(), + name: plugin.name, + events: inject.events, + type: 'serviceWorker' + }); + } + worker.active?.postMessage(swPlugins); + } + this.#storage.setVal(SettingsVals.marketPlace.plugins, JSON.stringify(plugins)); + }); + } + + async theme(opts: { type: 'normal', payload: string, sources?: { video?: string, bg?: string }, name: string } | { type: 'remove', payload?: string, sources?: { video?: string, bg?: string }, name?: string }) { + const elems = Elements.select([ + { type: 'id', val: 'stylesheet' }, + { type: 'id', val: 'nebulaVideo' }, + { type: 'id', val: 'nebulaImage' } + ]); + const s = Elements.exists(await elems.next()); + const nv = Elements.exists(await elems.next()); + const ni = Elements.exists(await elems.next()); + + const nvl = this.#storage.getVal(SettingsVals.marketPlace.appearance.video); + const nil = this.#storage.getVal(SettingsVals.marketPlace.appearance.image); + const tsp = this.#storage.getVal(SettingsVals.marketPlace.appearance.theme.payload); + const tsn = this.#storage.getVal(SettingsVals.marketPlace.appearance.theme.name); + + const reset = (style: boolean) => { + const st = this.#storage; + if (style) { + st.removeVal(SettingsVals.marketPlace.appearance.theme.name); + st.removeVal(SettingsVals.marketPlace.appearance.theme.payload); + s.href = "/nebula.css"; + } + st.removeVal(SettingsVals.marketPlace.appearance.video); + nv.src = ""; + st.removeVal(SettingsVals.marketPlace.appearance.image); + ni.style.display = "none"; + ni.src = ""; + } + + if (opts.type === 'remove') return reset(true); + + if (opts.sources?.video || nvl) { + reset(false); + if (!nvl) this.#storage.setVal(SettingsVals.marketPlace.appearance.video, opts.sources?.video || nvl); + nv.src = `/packages/${opts.name}/${opts.sources?.video ? opts.sources.video : nvl}` + } + if (opts.sources?.bg || nil) { + reset(false); + if (!nil) this.#storage.setVal(SettingsVals.marketPlace.appearance.image, opts.sources?.bg || nil); + ni.style.display = "block"; + ni.src = `/packages/${opts.name}/${opts.sources?.bg ? opts.sources.bg : nil}` + } + + if (opts.payload) { + if (tsp !== opts.payload) { + this.#storage.setVal(SettingsVals.marketPlace.appearance.theme.payload, opts.payload); + this.#storage.setVal(SettingsVals.marketPlace.appearance.theme.name, opts.name); + s.href = `/packages/${opts.name}/${opts.payload}`; + } + } + else { + if (tsp) return s.href = `/packages/${tsn}/${tsp}`; + } + } } -window.mp = Marketplace - export { Marketplace } diff --git a/src/utils/serviceWorker.ts b/src/utils/serviceWorker.ts index 7a6ed1a..2e9bf3c 100644 --- a/src/utils/serviceWorker.ts +++ b/src/utils/serviceWorker.ts @@ -101,7 +101,9 @@ type SWInit = { class SW { #init!: SWInit; #ready: boolean = false; + static #instances = new Set(); constructor(conn: BareMuxConnection) { + SW.#instances.add(this); const sj = (): ScramjetController => { const sj = new ScramjetController({ prefix: '/~/scramjet', @@ -133,7 +135,25 @@ class SW { throw new Error('Your browser is not supported! This website uses Service Workers heavily.'); } } - + + /** + * Static method to get an already existing SW class + * + * + * @example + * SW.getInstances.next().value // Get the first instance. + * + * @example + * // Loop through every instance + * for (const sw of SW.getInstances()) { + * console.log(sw) // DO some real work + * } + */ + static *getInstances() { + for (const value of SW.#instances.keys()) { + yield value as SW; + } + } /** * Allows you to overrid the items set. Should be used sparingly or never. */ diff --git a/src/utils/settings.ts b/src/utils/settings.ts index d8b69a4..ce7f323 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -1,7 +1,7 @@ import { defaultStore } from "./storage"; import { SettingsVals, WispServers } from "./values"; import { Marketplace } from "./marketplace"; -import { setTransport } from "./serviceWorker"; +import { setTransport, SW } from "./serviceWorker"; const tab = { ab: (redirect: string) => { @@ -86,7 +86,8 @@ const proxy = { defaultStore.setVal(SettingsVals.proxy.wispServer, s); }, transport: async (t: "libcurl" | "epoxy") => { - const { bareMuxConn } = await window.sw.getSWInfo(); + const sw = SW.getInstances().next().value!; + const { bareMuxConn } = await sw.getSWInfo(); await setTransport(bareMuxConn, t as "libcurl" | "epoxy"); defaultStore.setVal(SettingsVals.proxy.transport.key, t); } diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 7766546..fd9fc19 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -25,9 +25,13 @@ class StoreManager