mirror of
https://github.com/QuiteAFancyEmerald/Holy-Unblocker.git
synced 2025-05-12 19:40:02 -04:00
270 lines
7.9 KiB
JavaScript
270 lines
7.9 KiB
JavaScript
import Fastify from 'fastify';
|
|
import { createServer } from 'node:http';
|
|
import wisp from 'wisp-server-node';
|
|
import createRammerhead from '../lib/rammerhead/src/server/index.js';
|
|
import { epoxyPath } from '@mercuryworkshop/epoxy-transport';
|
|
import { libcurlPath } from '@mercuryworkshop/libcurl-transport';
|
|
import { bareModulePath } from '@mercuryworkshop/bare-as-module3';
|
|
import { baremuxPath } from '@mercuryworkshop/bare-mux/node';
|
|
import { uvPath } from '@titaniumnetwork-dev/ultraviolet';
|
|
import fastifyHelmet from '@fastify/helmet';
|
|
import fastifyStatic from '@fastify/static';
|
|
import pageRoutes from './routes.mjs';
|
|
import { readFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { paintSource, preloaded404, tryReadFile } from './randomization.mjs';
|
|
import loadTemplates from './templates.mjs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { existsSync, unlinkSync } from 'node:fs';
|
|
import ecosystem from '../ecosystem.config.js';
|
|
|
|
const config = Object.freeze(
|
|
JSON.parse(await readFile(new URL('./config.json', import.meta.url)))
|
|
),
|
|
ecosystemConfig = Object.freeze(
|
|
ecosystem.apps.find((app) => app.name === 'HolyUB') || ecosystem.apps[0]
|
|
),
|
|
{ pages, externalPages } = pageRoutes,
|
|
__dirname = path.resolve();
|
|
|
|
/* Record the server's location as a URL object, including its host and port.
|
|
* The host can be modified at /src/config.json, whereas the ports can be modified
|
|
* at /ecosystem.config.js.
|
|
*/
|
|
const serverUrl = ((base) => {
|
|
try {
|
|
base = new URL(config.host);
|
|
} catch (e) {
|
|
base = new URL('http://a');
|
|
base.host = config.host;
|
|
}
|
|
base.port =
|
|
ecosystemConfig[config.production ? 'env_production' : 'env'].PORT;
|
|
return Object.freeze(base);
|
|
})();
|
|
console.log(serverUrl);
|
|
|
|
// The server will check for the existence of this file when a shutdown is requested.
|
|
// The shutdown script in run-command.js will temporarily produce this file.
|
|
const shutdown = fileURLToPath(new URL('./.shutdown', import.meta.url));
|
|
|
|
const rh = createRammerhead();
|
|
const rammerheadScopes = [
|
|
'/rammerhead.js',
|
|
'/hammerhead.js',
|
|
'/transport-worker.js',
|
|
'/task.js',
|
|
'/iframe-task.js',
|
|
'/worker-hammerhead.js',
|
|
'/messaging',
|
|
'/sessionexists',
|
|
'/deletesession',
|
|
'/newsession',
|
|
'/editsession',
|
|
'/needpassword',
|
|
'/syncLocalStorage',
|
|
'/api/shuffleDict',
|
|
'/mainport',
|
|
];
|
|
|
|
const rammerheadSession = /^\/[a-z0-9]{32}/,
|
|
shouldRouteRh = (req) => {
|
|
try {
|
|
const url = new URL(req.url, serverUrl);
|
|
return (
|
|
rammerheadScopes.includes(url.pathname) ||
|
|
rammerheadSession.test(url.pathname)
|
|
);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
},
|
|
routeRhRequest = (req, res) => {
|
|
rh.emit('request', req, res);
|
|
},
|
|
routeRhUpgrade = (req, socket, head) => {
|
|
rh.emit('upgrade', req, socket, head);
|
|
};
|
|
|
|
// Create a server factory for RH, and wisp (and bare if you please).
|
|
const serverFactory = (handler) => {
|
|
return createServer()
|
|
.on('request', (req, res) => {
|
|
if (shouldRouteRh(req)) routeRhRequest(req, res);
|
|
else handler(req, res);
|
|
})
|
|
.on('upgrade', (req, socket, head) => {
|
|
if (shouldRouteRh(req)) routeRhUpgrade(req, socket, head);
|
|
else if (req.url.endsWith('/wisp/')) wisp.routeRequest(req, socket, head);
|
|
});
|
|
};
|
|
|
|
// Set logger to true for logs.
|
|
const app = Fastify({
|
|
ignoreDuplicateSlashes: true,
|
|
ignoreTrailingSlash: true,
|
|
logger: false,
|
|
serverFactory: serverFactory,
|
|
});
|
|
|
|
// Apply Helmet middleware for security.
|
|
app.register(fastifyHelmet, {
|
|
contentSecurityPolicy: false, // Disable CSP
|
|
xPoweredBy: false,
|
|
});
|
|
|
|
// Assign server file paths to different paths, for serving content on the website.
|
|
app.register(fastifyStatic, {
|
|
root: fileURLToPath(new URL('../views/pages', import.meta.url)),
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: fileURLToPath(new URL('../views/assets', import.meta.url)),
|
|
prefix: '/assets/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: fileURLToPath(new URL('../views/archive', import.meta.url)),
|
|
prefix: '/arcade/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: fileURLToPath(
|
|
new URL(
|
|
// Use the pre-compiled, minified scripts instead, if enabled in config.
|
|
config.minifyScripts ? '../views/dist/assets/js' : '../views/assets/js',
|
|
import.meta.url
|
|
)
|
|
),
|
|
prefix: '/assets/js/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: fileURLToPath(
|
|
new URL(
|
|
// Use the pre-compiled, minified stylesheets instead, if enabled in config.
|
|
config.minifyScripts ? '../views/dist/assets/css' : '../views/assets/css',
|
|
import.meta.url
|
|
)
|
|
),
|
|
prefix: '/assets/css/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
// This combines scripts from the official UV repository with local UV scripts into
|
|
// one directory path. Local versions of files override the official versions.
|
|
app.register(fastifyStatic, {
|
|
root: [
|
|
fileURLToPath(
|
|
new URL(
|
|
// Use the pre-compiled, minified scripts instead, if enabled in config.
|
|
config.minifyScripts ? '../views/dist/uv' : '../views/uv',
|
|
import.meta.url
|
|
)
|
|
),
|
|
uvPath,
|
|
],
|
|
prefix: '/uv/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
// Register proxy paths to the website.
|
|
app.register(fastifyStatic, {
|
|
root: epoxyPath,
|
|
prefix: '/epoxy/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: libcurlPath,
|
|
prefix: '/libcurl/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: bareModulePath,
|
|
prefix: '/bareasmodule/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
app.register(fastifyStatic, {
|
|
root: baremuxPath,
|
|
prefix: '/baremux/',
|
|
decorateReply: false,
|
|
});
|
|
|
|
/* All website files are stored in the /views directory.
|
|
* This takes one of those files and displays it for a site visitor.
|
|
* Paths like /browsing are converted into paths like /views/pages/surf.html
|
|
* back here. Which path converts to what is defined in routes.mjs.
|
|
*/
|
|
app.get('/:path', (req, reply) => {
|
|
// Testing for future features that need cookies to deliver alternate source files.
|
|
if (req.raw.rawHeaders.includes('Cookie'))
|
|
console.log(req.raw.rawHeaders[req.raw.rawHeaders.indexOf('Cookie') + 1]);
|
|
|
|
const reqPath = req.params.path;
|
|
|
|
if (reqPath in externalPages) {
|
|
let externalRoute = externalPages[reqPath];
|
|
if (typeof externalRoute !== 'string')
|
|
externalRoute = externalRoute.default;
|
|
return reply.redirect(externalRoute);
|
|
}
|
|
|
|
// If a GET request is sent to /test-shutdown and a script-generated shutdown file
|
|
// is present, gracefully shut the server down.
|
|
if (reqPath === 'test-shutdown' && existsSync(shutdown)) {
|
|
console.log('Holy Unblocker is shutting down.');
|
|
app.close();
|
|
unlinkSync(shutdown);
|
|
process.exitCode = 0;
|
|
}
|
|
|
|
// Return the error page if the query is not found in routes.mjs.
|
|
if (reqPath && !(reqPath in pages))
|
|
return reply.code(404).type('text/html').send(preloaded404);
|
|
|
|
reply.type('text/html').send(
|
|
paintSource(
|
|
loadTemplates(
|
|
tryReadFile(
|
|
path.join(
|
|
__dirname,
|
|
'views',
|
|
// Set the index the as the default page.
|
|
reqPath ? pages[reqPath] : pages.index
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
});
|
|
|
|
app.get('/github/:redirect', (req, reply) => {
|
|
if (req.params.redirect in externalPages.github)
|
|
reply.redirect(externalPages.github[req.params.redirect]);
|
|
else reply.code(404).type('text/html').send(preloaded404);
|
|
});
|
|
|
|
/*
|
|
Testing for future restructuring of this config file.
|
|
|
|
app.get("/assets/js/uv/uv.config.js", (req, reply) => {
|
|
console.log(req.url);
|
|
reply.type("text/javascript");
|
|
reply.send(tryReadFile(path.join(__dirname, "views/assets/js/uv/uv.config.js")));
|
|
});
|
|
*/
|
|
|
|
// Set an error page for invalid paths outside the query string system.
|
|
app.setNotFoundHandler((req, reply) => {
|
|
reply.code(404).type('text/html').send(preloaded404);
|
|
});
|
|
|
|
app.listen({ port: serverUrl.port, host: serverUrl.hostname });
|
|
console.log(`Holy Unblocker is listening on port ${serverUrl.port}.`);
|