Server: switch to Fastify

Switch config to TOML over JSON (it's just nicer TBH)
This commit is contained in:
MotorTruck1221 2024-10-13 04:33:25 -06:00
parent deda273eda
commit 620ad25c61
No known key found for this signature in database
GPG key ID: 08F417E2B8B61EA4
20 changed files with 1373 additions and 683 deletions

6
.gitignore vendored
View file

@ -1,11 +1,13 @@
# build output
dist/
server/*.js
# generated types
.astro/
# dependencies
node_modules/
package-lock.json
# logs
npm-debug.log*
@ -26,5 +28,9 @@ pnpm-debug.log*
# nebula catalog database
database.sqlite
# YOUR config
config.toml
# Goofy PNPM problem
~/

View file

@ -191,9 +191,10 @@ docker compose -f ./docker-compose.build.yml build
```
---
## Environment
## Config
- There are a couple of environment variables for nebula. Most of the time, the defaults are fine, but there are instances where you may not want certain options enabled or certain things running.
- There are a couple of configuration options for nebula. Most of the time, the defaults are fine, but there are instances where you may not want certain options enabled or certain things running.
- An example config file is located [here](./config.example.toml). Config format is TOML
| Variable | Description | Default |
|------------------------|----------------------------------------------------------------------------------------------------------|---------|

View file

@ -75,6 +75,6 @@ export default defineConfig({
},
output: "server",
adapter: node({
mode: "hybrid"
mode: "middleware"
})
});

24
config.example.toml Normal file
View file

@ -0,0 +1,24 @@
[marketplace]
enabled = true # Turn on or off the marketplace entirely
psk = "CHANGEME" # Change this to something more secure.
level = 1
[db]
name = "database" # Your databsae name
username = "username" # The username of your DB (SQLITE just ignores this)
password = "password" # The password to your DB (SQLITE ignores this)
postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
[postgres] # Set the "domain" to either and ip address or a actual domain
domain = ""
port = 5432
[server.server]
port = 8080
wisp = true
logging = true # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)
[server.rammerhead] # Don't touch this section unless you KNOW what you are doing
reverseproxy = true
localstorage_sync = true
http2 = true

View file

@ -1,5 +0,0 @@
{
"marketplace_enabled": true,
"marketplace_psk": "CHANGE_THIS_THIS_IS_INSECURE",
"marketplace_level": "1"
}

View file

@ -3,10 +3,12 @@
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "concurrently \"astro dev\" \"node server.js\"",
"start": "node server.js",
"build": "astro check && astro build",
"bstart": "astro build && node server.js",
"dev": "concurrently \"astro dev\" \"tsx --watch server/server.ts\"",
"start": "node server/server.js",
"build:server": "tsc -p server",
"build:client": "astro check && astro build",
"build": "concurrently \"npm:build:server\" \"npm:build:client\"",
"bstart": "npm run build && npm run start",
"preview": "astro preview",
"astro": "astro",
"format:code": "biome format . --write",
@ -16,11 +18,13 @@
"dependencies": {
"@astrojs/check": "^0.8.3",
"@astrojs/node": "^8.3.4",
"@astrojs/svelte": "^5.7.1",
"@astrojs/tailwind": "^5.1.1",
"@fastify/compress": "^7.0.3",
"@fastify/static": "^7.0.4",
"@iconify-json/ph": "^1.2.0",
"@astrojs/svelte": "^5.7.2",
"@astrojs/tailwind": "^5.1.2",
"@fastify/compress": "^8.0.1",
"@fastify/middie": "^9.0.2",
"@fastify/multipart": "^9.0.1",
"@fastify/static": "^8.0.1",
"@iconify-json/ph": "^1.2.1",
"@mercuryworkshop/bare-mux": "1.1.1",
"@mercuryworkshop/epoxy-transport": "github:motortruck1221/epoxytransport",
"@mercuryworkshop/libcurl-transport": "^1.3.10",
@ -28,27 +32,34 @@
"@rubynetwork/rammerhead-browser": "^1.0.9",
"@svelte-drama/suspense": "0.5.1",
"@titaniumnetwork-dev/ultraviolet": "3.1.2",
"astro": "^4.15.11",
"@types/node": "^22.7.5",
"@types/sequelize": "^4.28.20",
"astro": "^4.16.2",
"astro-icon": "^1.1.1",
"chalk": "^5.3.0",
"concurrently": "^8.2.2",
"express": "^4.21.0",
"fastify": "^4.28.1",
"form-data": "^4.0.0",
"fastify": "^5.0.0",
"form-data": "^4.0.1",
"formdata-node": "^6.0.3",
"gradient-string": "^3.0.0",
"libcurl.js-new": "npm:libcurl.js@^0.6.16",
"multer": "1.4.5-lts.1",
"nanostores": "^0.10.3",
"pg": "^8.13.0",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.4",
"smol-toml": "^1.3.0",
"sqlite3": "^5.1.7",
"svelte": "^4.2.19",
"svelte-french-toast": "^1.2.0",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.2",
"typescript": "^5.6.3",
"vite-plugin-static-copy": "^1.0.6",
"wisp-server-node": "^1.1.7"
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
"bufferutil": "^4.0.8"
"bufferutil": "^4.0.8",
"tsx": "^4.19.1"
}
}

1352
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

82
server/config.ts Normal file
View file

@ -0,0 +1,82 @@
import { readFileSync } from 'node:fs';
import { parse, TomlPrimitive } from 'smol-toml';
import { fileURLToPath } from 'node:url';
import chalk from 'chalk';
interface TomlData {
marketplace: {
enabled: boolean;
psk: String
}
server: {
server: {
port: number;
wisp: boolean;
logging: boolean;
}
rammerhead: {
reverseproxy: boolean;
localstorage_sync: boolean;
http2: boolean;
}
},
db: {
name: string;
username: string;
password: string;
postgres: boolean;
},
postgres: {
domain: string;
port: number;
}
}
interface Verify {
name: string,
typeOF: any,
type: any
}
let doc = readFileSync(fileURLToPath(new URL('../config.toml', import.meta.url))).toString();
const parsedDoc = parse(doc) as unknown as TomlData;
function verify(t: Verify[]) {
for (let i: number = 0; i !== t.length; i++) {
if (typeof t[i].typeOF !== t[i].type) {
throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
}
}
}
verify([
{name: 'marketplace', typeOF: parsedDoc.marketplace, type: 'object'},
{name: 'marketplace.enabled', typeOF: parsedDoc.marketplace.enabled, type: 'boolean'},
{name: 'marketplace.psk', typeOF: parsedDoc.marketplace.psk, type: 'string'},
{name: 'server', typeOF: parsedDoc.server, type: 'object'},
{name: 'server.server', typeOF: parsedDoc.server.server, type: 'object'},
{name: 'server.rammerhead', typeOF: parsedDoc.server.rammerhead, type: 'object'},
{name: 'server.server.port', typeOF: parsedDoc.server.server.port, type: 'number'},
{name: 'server.server.wisp', typeOF: parsedDoc.server.server.wisp, type: 'boolean'},
{name: 'server.server.logging', typeOF: parsedDoc.server.server.logging, type: 'boolean'},
{name: 'server.rammerhead.reverseproxy', typeOF: parsedDoc.server.rammerhead.reverseproxy, type: 'boolean'},
{name: 'server.rammerhead.localstorage_sync', typeOF: parsedDoc.server.rammerhead.localstorage_sync, type: 'boolean'},
{name: 'server.rammerhead.http2', typeOF: parsedDoc.server.rammerhead.http2, type: 'boolean'},
{name: 'db', typeOF: parsedDoc.db, type: 'object'},
{name: 'db.name', typeOF: parsedDoc.db.name, type: 'string'},
{name: 'db.username', typeOF: parsedDoc.db.username, type: 'string'},
{name: 'db.password', typeOF: parsedDoc.db.password, type: 'string'},
{name: 'db.postgres', typeOF: parsedDoc.db.postgres, type: 'boolean'},
{name: 'postgres', typeOF: parsedDoc.postgres, type: 'object'},
{name: 'postgres.domain', typeOF: parsedDoc.postgres.domain, type: 'string'},
{name: 'postgres.port', typeOF: parsedDoc.postgres.port, type: 'number'}
]);
if (parsedDoc.marketplace.psk === "CHANGEME") {
console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
}
if (parsedDoc.db.password === "password") {
console.warn(chalk.red.bold('You should change your DB password!!'));
}
export { TomlData, parsedDoc }

23
server/dbSetup.ts Normal file
View file

@ -0,0 +1,23 @@
import chalk from "chalk";
import { CatalogModel } from "./server.js";
import { ModelCtor } from "sequelize";
function setupDB(db: ModelCtor<CatalogModel>) {
//We have some packages that need to be installed if they aren't.
//TODO: set this up
console.log(chalk.hex('#7967dd')('Performing DB setup...'));
//db.create({
// package_name: 'com.nebula.cybermonay',
// title: 'Cyber Monay',
// image: 'cyber_monay.jpg',
// author: 'Nebula Services',
// version: '1.0.0',
// description: 'A parody of the famous "Cyber Monay" hack!',
// tags: ["Hacking", "Animated", "Funny"],
// payload: "com.nebula.cybermonay.css",
// background_video: "cyber_monay_test.mp4",
// type: 'theme'
//});
}
export { setupDB }

1
server/env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module '@rubynetwork/rammerhead/src/server/index.js';

264
server/server.ts Normal file
View file

@ -0,0 +1,264 @@
import { fileURLToPath } from "node:url";
import fastifyCompress from "@fastify/compress";
import fastifyMiddie from "@fastify/middie";
import fastifyStatic from "@fastify/static";
import fastifyMultipart from "@fastify/multipart";
import Fastify, { FastifyReply, FastifyRequest } from 'fastify';
import chalk from 'chalk';
import { serverFactory } from "./serverFactory.js";
import { handler as ssrHandler } from "../dist/server/entry.mjs";
import gradient from "gradient-string";
import { parsedDoc } from "./config.js";
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
import { pipeline } from "node:stream/promises";
import { createWriteStream } from "node:fs";
import { setupDB } from "./dbSetup.js";
const app = Fastify({ logger: parsedDoc.server.server.logging, ignoreDuplicateSlashes: true, ignoreTrailingSlash: true, serverFactory: serverFactory });
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : 'localhost',
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
dialect: parsedDoc.db.postgres ? 'postgres': 'sqlite',
logging: parsedDoc.server.server.logging,
storage: 'database.sqlite' //this is sqlite only
});
interface CatalogModel extends Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {
package_name: string
title: string
description: string
author: string
image: string
tags: object
version: string
background_image: string
background_video: string
payload: string
type: string
}
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
package_name: { type: DataTypes.TEXT, unique: true },
title: { type: DataTypes.TEXT },
description: { type: DataTypes.TEXT },
author: { type: DataTypes.TEXT },
image: { type: DataTypes.TEXT },
tags: { type: DataTypes.JSON, allowNull: true },
version: { type: DataTypes.TEXT },
background_image: { type: DataTypes.TEXT, allowNull: true },
background_video: { type: DataTypes.TEXT, allowNull: true },
payload: { type: DataTypes.TEXT },
type: { type: DataTypes.TEXT }
});
await app.register(fastifyCompress, {
encodings: ['br', 'gzip', 'deflate']
});
await app.register(fastifyMultipart);
await app.register(fastifyStatic, {
root: fileURLToPath(new URL('../dist/client', import.meta.url)),
decorateReply: false,
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL('../database_assets/image', import.meta.url)),
prefix: '/images/',
decorateReply: false
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL('../database_assets/video', import.meta.url)),
prefix: '/videos/',
decorateReply: false
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL('../database_assets/styles', import.meta.url)),
prefix: '/styles/',
decorateReply: false
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL('../database_assets/scripts', import.meta.url)),
prefix: '/scripts/',
decorateReply: false
});
await app.register(fastifyMiddie);
app.get("/api", (request, reply) => {
reply.send({ Server: 'Active' });
});
// This API returns a list of the assets in the database (SW plugins and themes).
// It also returns the number of pages in the database.
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
type CatalogAssetsReq = FastifyRequest<{Querystring: { page: string } }>
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
try {
const { page } = request.query;
const pageNum: number = parseInt(page, 10) || 1;
if (pageNum < 1) {
reply.status(400).send({ error: "Page must be a positive number!" });
}
const offset = (pageNum - 1) * 20;
const totalItems = await catalogAssets.count();
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
const assets = dbAssets.reduce((acc, asset) => {
acc[asset.package_name] = {
title: asset.title,
description: asset.description,
author: asset.author,
image: asset.image,
tags: asset.tags,
version: asset.version,
background_image: asset.background_image,
background_video: asset.background_video,
payload: asset.payload,
type: asset.type
};
return acc;
}, {});
reply.send({ assets, pages: Math.ceil(totalItems / 20) });
}
catch (error) {
reply.status(500).send({ error: 'An error occured' });
}
});
type PackageReq = FastifyRequest<{Params: { package: string } }>
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
try {
const packageRow = await catalogAssets.findOne({ where: { package_name: request.params.package }});
if (!packageRow) return reply.status(404).send({ error: 'Package not found!' });
const details = {
title: packageRow.get("title"),
description: packageRow.get('description'),
image: packageRow.get('image'),
author: packageRow.get('author'),
tags: packageRow.get('tags'),
version: packageRow.get('version'),
background_image: packageRow.get('background_image'),
background_video: packageRow.get('background_video'),
payload: packageRow.get('payload'),
type: packageRow.get('type')
};
reply.send(details);
}
catch (error) {
reply.status(500).send({ error: 'An unexpected error occured' });
}
});
type UploadReq = FastifyRequest<{Headers: { psk: string }}>;
type CreateReq = FastifyRequest<{Headers: { psk: string}, Body: { uuid: string, title: string, image: string, author: string, version: string, description: string, tags: object | any, payload: string, background_video: string, background_image: string, type: string }}>;
async function verifyReq(request: UploadReq | CreateReq, reply: FastifyReply, upload: Boolean, data: any) {
return new Promise<void>((resolve, reject) => {
if (parsedDoc.marketplace.enabled === false) {
reply.status(500).send({ error: 'Marketplace is disabled!' });
reject();
}
else if (request.headers.psk !== parsedDoc.marketplace.psk) {
reply.status(403).send({ error: 'PSK not correct!' });
reject();
}
else if (upload && !data) {
reply.status(400).send({ error: 'No file uploaded!' });
reject();
}
else { resolve(); }
});
}
app.post("/api/upload-image", async (request: UploadReq, reply) => {
const data = await request.file();
verifyReq(request, reply, true, data)
.then(async () => {
await pipeline(data.file, createWriteStream(fileURLToPath(new URL(`../database_assets/image/${data.filename}`, import.meta.url))));
reply.send({ message: 'File uploaded successfully!', filename: data.filename });
})
.catch(() => {
if (parsedDoc.server.server.logging) { console.error(chalk.yellow.bold('Caught error from verify function. \n Error caused while uploading image')) };
});
});
app.post("/api/upload-video", async (request: UploadReq, reply) => {
const data = await request.file();
verifyReq(request, reply, true, data)
.then(async () => {
await pipeline(data.file, createWriteStream(fileURLToPath(new URL(`../database_assets/video/${data.filename}`, import.meta.url))));
reply.send({ message: 'File uploaded successfully!', filename: data.filename });
})
.catch(() => {
if (parsedDoc.server.server.logging) { console.error(chalk.yellow.bold('Caught error from verify function. \n Error caused while uploading video')) };
});
});
app.post("/api/upload-style", async (request: UploadReq, reply) => {
const data = await request.file();
verifyReq(request, reply, true, data)
.then(async () => {
await pipeline(data.file, createWriteStream(fileURLToPath(new URL(`../database_assets/styles/${data.filename}`, import.meta.url))));
reply.send({ message: 'File uploaded successfully!', filename: data.filename });
})
.catch(() => {
if (parsedDoc.server.server.logging) { console.error(chalk.yellow.bold('Caught error from verify function. \n Error caused while uploading style')) };
});
});
app.post("/api/upload-script", async (request: UploadReq, reply) => {
const data = await request.file();
verifyReq(request, reply, true, data)
.then(async () => {
await pipeline(data.file, createWriteStream(fileURLToPath(new URL(`../database_assets/video/${data.filename}`, import.meta.url))));
reply.send({ message: 'File uploaded successfully!', filename: data.filename });
})
.catch(() => {
if (parsedDoc.server.server.logging) { console.error(chalk.yellow.bold('Caught error from verify function. \n Error caused while uploading script')) };
});
});
app.post("/api/create-package", async (request: CreateReq, reply) => {
verifyReq(request, reply, false, undefined)
.then(async () => {
await catalogAssets.create({
package_name: request.body.uuid,
title: request.body.title,
image: request.body.image,
author: request.body.author,
version: request.body.version,
description: request.body.description,
tags: request.body.tags,
payload: request.body.payload,
background_video: request.body.background_video,
background_image: request.body.background_image,
type: request.body.type
});
})
.catch(async () => {
if (parsedDoc.server.server.logging) { console.error(chalk.yellow.bold('Caught error from verify function. \n Error caused while creating package')) };
});
});
app.use(ssrHandler);
const port: number = parseInt(process.env.PORT as string) || parsedDoc.server.server.port || parseInt('8080');
const titleText = `
_ _ _ _ ____ _
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
`
const titleColors = {
purple: "#7967dd",
pink: "#eb6f92"
};
console.log(gradient(Object.values(titleColors)).multiline(titleText as string));
app.listen({ port: port, host: '0.0.0.0' }).then(() => {
console.log(chalk.hex('#7967dd')(`Server listening on ${chalk.hex('#eb6f92').bold('http://localhost:' + port + '/')}`));
console.log(chalk.hex('#7967dd')(`Server also listening on ${chalk.hex('#eb6f92').bold('http://0.0.0.0:' + port + '/')}`));
catalogAssets.sync();
setupDB(catalogAssets);
});
export { CatalogModel }

37
server/serverFactory.ts Normal file
View file

@ -0,0 +1,37 @@
import { createServer } from 'node:http';
import wisp from 'wisp-server-node';
import rammerhead from '@rubynetwork/rammerhead';
import { FastifyServerFactory, FastifyServerFactoryHandler, RawServerDefault } from 'fastify';
import { parsedDoc } from './config.js';
const rh = rammerhead.createRammerhead({
logLevel: parsedDoc.server.server.logging ? 'debug' : 'disabled',
reverseProxy: parsedDoc.server.rammerhead.reverseproxy,
disableLocalStorageSync: parsedDoc.server.rammerhead.localstorage_sync ? false : true,
disableHttp2: parsedDoc.server.rammerhead.http2 ? false : true
});
const serverFactory: FastifyServerFactory = (handler: FastifyServerFactoryHandler): RawServerDefault => {
const httpServer = createServer();
httpServer.on('request', (req, res) => {
if (rammerhead.shouldRouteRh(req)) {
rammerhead.routeRhRequest(rh, req, res);
}
else {
handler(req, res);
}
});
httpServer.on('upgrade', (req, socket, head) => {
if (rammerhead.shouldRouteRh(req)) {
rammerhead.routeRhUpgrade(rh, req, socket, head);
}
else if (parsedDoc.server.server.wisp) {
if (req.url?.endsWith('/wisp/')) {
wisp.routeRequest(req, socket as any, head);
}
}
});
return httpServer;
}
export { serverFactory };

11
server/tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": false,
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {}
}
}

View file

@ -13,18 +13,15 @@ export const prerender = true;
<script>
import { initSw, setTransport } from "@utils/registerSW.ts"; //../../utils/registerSW.ts
import { Settings } from "@utils/settings/index";
document.addEventListener("astro:page-load", async function () {
try {
const iframe = document.getElementById("chango") as HTMLIFrameElement;
if (iframe) {
initSw().then(() => {
setTransport(localStorage.getItem(Settings.ProxySettings.transport) as string).then(async () => {
iframe.src = __uv$config!.prefix + __uv$config.encodeUrl!("https://radon.games");
});
import { pageLoad } from "@utils/events";
pageLoad(async () => {
const iframe = document.getElementById("chango") as HTMLIFrameElement;
if (iframe) {
initSw().then(() => {
setTransport(localStorage.getItem(Settings.ProxySettings.transport) as string).then(async () => {
iframe.src = __uv$config!.prefix + __uv$config.encodeUrl!("https://radon.games");
});
}
}
catch (_) {
});
}
});
</script>

View file

@ -47,6 +47,7 @@ const t = useTranslations(lang);
</Layout>
<script>
import { initSw, setTransport } from "@utils/registerSW.ts"; //../../utils/registerSW.ts
import { pageLoad } from "@utils/events";
import { RammerheadEncode } from "@rubynetwork/rammerhead-browser";
import { SupportedSites } from "@utils/siteSupport";
import {
@ -98,9 +99,7 @@ const t = useTranslations(lang);
}
}
//we need to rerun this on every page load
document.addEventListener("astro:page-load", async function () {
//wrap this in a try catch as sometimes this element will not be available on the page
try {
pageLoad(async () => {
const input = document.getElementById("nebula-input") as HTMLInputElement;
const iframe = document.getElementById("neb-iframe") as HTMLIFrameElement;
const omnibox = document.getElementById("omnibox") as HTMLDivElement;
@ -199,9 +198,5 @@ const t = useTranslations(lang);
}
}
});
} catch (err) {
//we purposely don't return anything
//console.debug(err);
}
});
</script>

View file

@ -133,6 +133,7 @@ export const prerender = true;
<script>
import { toast } from "@utils/toast.ts";
import { settings, Settings as SettingsEnum } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
function setup(proxySelectVal: HTMLSelectElement, openInVal: HTMLSelectElement, searchEngineVal: HTMLSelectElement, wispServerVal: HTMLSelectElement, transportVal: HTMLSelectElement) {
proxySelectVal.value = localStorage.getItem(SettingsEnum.ProxySettings.proxy) as string;
openInVal.value = localStorage.getItem(SettingsEnum.ProxySettings.openIn) as string;
@ -140,40 +141,37 @@ export const prerender = true;
wispServerVal.value = localStorage.getItem(SettingsEnum.ProxySettings.wispServerURL) as string;
transportVal.value = localStorage.getItem(SettingsEnum.ProxySettings.transport) as string;
}
document.addEventListener("astro:page-load", () => {
try {
const proxyButton = document.getElementById("setproxy") as HTMLButtonElement;
const proxySelectVal = document.getElementById('proxy') as HTMLSelectElement;
const openInButton = document.getElementById('setopenin') as HTMLButtonElement;
const openInVal = document.getElementById('openin') as HTMLSelectElement;
const searchEngineButton = document.getElementById('setsearchengine') as HTMLButtonElement;
const searchEngineVal = document.getElementById('searchengine') as HTMLSelectElement;
const wispServerButton = document.getElementById('setwispurl') as HTMLButtonElement;
const wispServerVal = document.getElementById('wispurl') as HTMLSelectElement;
const transportButton = document.getElementById('settransport') as HTMLButtonElement;
const transportVal = document.getElementById('transport') as HTMLSelectElement;
setup(proxySelectVal, openInVal, searchEngineVal, wispServerVal, transportVal);
proxyButton.addEventListener("click", () => {
settings.proxySettings.changeProxy(proxySelectVal.value);
toast('.proxyMessage');
});
openInButton.addEventListener("click", () => {
settings.proxySettings.openIn(openInVal.value)
toast('.openInMessage');
});
searchEngineButton.addEventListener('click', () => {
settings.proxySettings.setSearchEngine(searchEngineVal.value);
toast('.searchEngineMessage');
});
wispServerButton.addEventListener('click', () => {
settings.proxySettings.setWispURL(wispServerVal.value);
toast('.wispUrlMessage');
});
transportButton.addEventListener('click', () => {
settings.proxySettings.setTransport(transportVal.value);
toast('.transportMessage');
});
}
catch(_) {/* Don't return anything on purpose */}
pageLoad(() => {
const proxyButton = document.getElementById("setproxy") as HTMLButtonElement;
const proxySelectVal = document.getElementById('proxy') as HTMLSelectElement;
const openInButton = document.getElementById('setopenin') as HTMLButtonElement;
const openInVal = document.getElementById('openin') as HTMLSelectElement;
const searchEngineButton = document.getElementById('setsearchengine') as HTMLButtonElement;
const searchEngineVal = document.getElementById('searchengine') as HTMLSelectElement;
const wispServerButton = document.getElementById('setwispurl') as HTMLButtonElement;
const wispServerVal = document.getElementById('wispurl') as HTMLSelectElement;
const transportButton = document.getElementById('settransport') as HTMLButtonElement;
const transportVal = document.getElementById('transport') as HTMLSelectElement;
setup(proxySelectVal, openInVal, searchEngineVal, wispServerVal, transportVal);
proxyButton.addEventListener("click", () => {
settings.proxySettings.changeProxy(proxySelectVal.value);
toast('.proxyMessage');
});
openInButton.addEventListener("click", () => {
settings.proxySettings.openIn(openInVal.value)
toast('.openInMessage');
});
searchEngineButton.addEventListener('click', () => {
settings.proxySettings.setSearchEngine(searchEngineVal.value);
toast('.searchEngineMessage');
});
wispServerButton.addEventListener('click', () => {
settings.proxySettings.setWispURL(wispServerVal.value);
toast('.wispUrlMessage');
});
transportButton.addEventListener('click', () => {
settings.proxySettings.setTransport(transportVal.value);
toast('.transportMessage');
});
})
</script>

View file

@ -56,28 +56,26 @@ export const prerender = true;
<script>
import { toast } from "@utils/toast.ts"; //A helper function so we don't have to run this logic everytime.
import { settings, Settings as SettingsEnum } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
function setup(cloakValue: HTMLSelectElement, abValue: HTMLSelectElement) {
const cloakLocal = localStorage.getItem(SettingsEnum.TabSettings.tabCloak);
const abCloakLocal = localStorage.getItem(SettingsEnum.TabSettings.abblob);
cloakLocal === "default" ? cloakValue.value = "reset" : cloakValue.value = cloakLocal as string;
abValue.value = abCloakLocal as string || 'a:b';
}
document.addEventListener("astro:page-load", function() {
try {
const cloakButton = document.getElementById("cloak") as HTMLButtonElement;
const cloakValue = document.getElementById("cloakselect") as HTMLSelectElement;
const abButton = document.getElementById("aboutblankbutton") as HTMLButtonElement;
const abValue = document.getElementById("aboutblank") as HTMLSelectElement;
setup(cloakValue, abValue);
cloakButton.addEventListener("click", () => {
settings.tabSettings.cloakTab(cloakValue.value);
toast('.cloakMessageSuccess');
});
abButton.addEventListener("click", () => {
toast('.abCloakMessage');
settings.tabSettings.abCloak(abValue.value);
});
}
catch (_) { /* We don't want to return anything on purpose */ }
pageLoad(() => {
const cloakButton = document.getElementById("cloak") as HTMLButtonElement;
const cloakValue = document.getElementById("cloakselect") as HTMLSelectElement;
const abButton = document.getElementById("aboutblankbutton") as HTMLButtonElement;
const abValue = document.getElementById("aboutblank") as HTMLSelectElement;
setup(cloakValue, abValue);
cloakButton.addEventListener("click", () => {
settings.tabSettings.cloakTab(cloakValue.value);
toast('.cloakMessageSuccess');
});
abButton.addEventListener("click", () => {
toast('.abCloakMessage');
settings.tabSettings.abCloak(abValue.value);
});
})
</script>

View file

@ -37,6 +37,7 @@ const assetsJson = await response.json();
<variable-define data-assets={JSON.stringify(assetsJson)} data-name={packageName} data-type={assetsJson.type} />
</Layout>
<script>
import { pageLoad } from "@utils/events";
import { settings, Settings, type PackageType } from "@utils/settings/index";
let packageName: string;
let assetsJson: any;
@ -51,30 +52,28 @@ const assetsJson = await response.json();
}
}
customElements.define('variable-define', VariableDefiner);
document.addEventListener('astro:page-load', () => {
try {
const items = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes) as string) || [];
const itemExists = items.indexOf(packageName) !== -1;
const installButton = document.getElementById("install") as HTMLButtonElement;
const uninstallButton = document.getElementById("uninstall") as HTMLButtonElement;
const payload = assetsJson ? JSON.parse(assetsJson) : undefined;
if (itemExists) {
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(() => {
installButton.classList.add("hidden");
uninstallButton.classList.remove("hidden");
})
});
uninstallButton.addEventListener("click", () => {
settings.marketPlaceSettings.uninstall(packageType as PackageType, packageName).then(() => {
uninstallButton.classList.add("hidden")
installButton.classList.remove("hidden");
});
})
const fn = () => {
const items = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes) as string) || [];
const itemExists = items.indexOf(packageName) !== -1;
const installButton = document.getElementById("install") as HTMLButtonElement;
const uninstallButton = document.getElementById("uninstall") as HTMLButtonElement;
const payload = assetsJson ? JSON.parse(assetsJson) : undefined;
if (itemExists) {
uninstallButton.classList.remove("hidden");
installButton.classList.add("hidden");
}
catch (err) { /* DEBUGGING ONLY: console.error(err) */ }
});
installButton.addEventListener("click", () => {
settings.marketPlaceSettings.install({theme: {payload: payload.payload, video: payload.background_video, bgImage: payload.background_image}}, packageName, payload.payload).then(() => {
installButton.classList.add("hidden");
uninstallButton.classList.remove("hidden");
})
});
uninstallButton.addEventListener("click", () => {
settings.marketPlaceSettings.uninstall(packageType as PackageType, packageName).then(() => {
uninstallButton.classList.add("hidden")
installButton.classList.remove("hidden");
});
});
}
pageLoad(fn);
</script>

10
src/utils/events.ts Normal file
View file

@ -0,0 +1,10 @@
function pageLoad(fn: () => void) {
document.addEventListener("astro:page-load", () => {
try {
fn();
}
catch (err) { /* DEBUGGING ONLY: console.error(err) */ }
});
}
export { pageLoad };

16
test.js Normal file
View file

@ -0,0 +1,16 @@
import fs from "fs";
// This is a test file to upload files to the Nebula server
import { File, FormData } from "formdata-node";
import { fileFromPath } from "formdata-node/file-from-path";
import { parsedDoc } from "./server/config.js";
const form = new FormData();
const file = new File(["My hovercraft is full of eels"], "example.txt");
form.set("file", file);
console.log(form);
await fetch("http://localhost:8080/api/upload-image", {
headers: {
PSK: parsedDoc.marketplace.psk
},
method: "post",
body: form
}).then(async (res) => { console.log(await res.text()) });