mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-12 11:30:01 -04:00
Server: switch to Fastify
Switch config to TOML over JSON (it's just nicer TBH)
This commit is contained in:
parent
deda273eda
commit
620ad25c61
20 changed files with 1373 additions and 683 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -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
|
||||
~/
|
||||
|
|
|
@ -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 |
|
||||
|------------------------|----------------------------------------------------------------------------------------------------------|---------|
|
||||
|
|
|
@ -75,6 +75,6 @@ export default defineConfig({
|
|||
},
|
||||
output: "server",
|
||||
adapter: node({
|
||||
mode: "hybrid"
|
||||
mode: "middleware"
|
||||
})
|
||||
});
|
||||
|
|
24
config.example.toml
Normal file
24
config.example.toml
Normal 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
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"marketplace_enabled": true,
|
||||
"marketplace_psk": "CHANGE_THIS_THIS_IS_INSECURE",
|
||||
"marketplace_level": "1"
|
||||
}
|
41
package.json
41
package.json
|
@ -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
1352
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
82
server/config.ts
Normal file
82
server/config.ts
Normal 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
23
server/dbSetup.ts
Normal 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
1
server/env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
declare module '@rubynetwork/rammerhead/src/server/index.js';
|
264
server/server.ts
Normal file
264
server/server.ts
Normal 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
37
server/serverFactory.ts
Normal 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
11
server/tsconfig.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
10
src/utils/events.ts
Normal 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
16
test.js
Normal 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()) });
|
Loading…
Add table
Add a link
Reference in a new issue