nsite-ts/src/index.ts

110 lines
3.0 KiB
TypeScript
Raw Normal View History

2024-09-01 13:26:37 -05:00
#!/usr/bin/env node
import "./polyfill.js";
import Koa from "koa";
import serve from "koa-static";
import path from "node:path";
import cors from "@koa/cors";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import HttpErrors from "http-errors";
import logger from "./logger.js";
import { isHttpError } from "./helpers/error.js";
import { resolveNpubFromHostname } from "./helpers/dns.js";
import ndk from "./ndk.js";
import { NSITE_KIND } from "./const.js";
import { BLOSSOM_SERVERS } from "./env.js";
import { makeRequestWithAbort } from "./helpers/http.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const app = new Koa();
// set CORS headers
app.use(
cors({
origin: "*",
allowMethods: "*",
allowHeaders: "Authorization,*",
exposeHeaders: "*",
}),
);
// handle errors
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
if (isHttpError(err)) {
const status = (ctx.status = err.status || 500);
if (status >= 500) console.error(err.stack);
ctx.body = status > 500 ? { message: "Something went wrong" } : { message: err.message };
} else {
console.log(err);
ctx.status = 500;
ctx.body = { message: "Something went wrong" };
}
}
});
// serve nsite files
app.use(async (ctx, next) => {
const pubkey = (ctx.state.pubkey = await resolveNpubFromHostname(ctx.hostname));
if (pubkey) {
const event = await ndk.fetchEvent([
{ kinds: [NSITE_KIND], "#d": [ctx.path, ctx.path.replace(/^\//, "")], authors: [pubkey] },
]);
if (!event) throw new HttpErrors.NotFound("Failed to find event for path");
const sha256 = event.tags.find((t) => t[0] === "x" || t[0] === "sha256")?.[1];
if (!sha256) throw new HttpErrors.BadGateway("Failed to find file for path");
for (const server of BLOSSOM_SERVERS) {
try {
const { response } = await makeRequestWithAbort(new URL(sha256, server));
const { headers, statusCode } = response;
if (!headers || !statusCode) throw new Error("Missing headers or status code");
if (statusCode >= 200 && statusCode < 300) {
ctx.status = statusCode;
// @ts-expect-error
ctx.set(headers);
ctx.response.body = response;
} else {
// Consume response data to free up memory
response.resume();
}
} catch (error) {
// ignore error, try next server
}
}
// throw new HttpErrors.NotFound(`Unable to find ${sha256} on blossom servers`);
} else await next();
});
// serve static files from public
try {
const www = path.resolve(process.cwd(), "public");
fs.statSync(www);
app.use(serve(www));
} catch (error) {
const www = path.resolve(__dirname, "../public");
app.use(serve(www));
}
app.listen(process.env.PORT || 3000);
logger("Started on port", process.env.PORT || 3000);
async function shutdown() {
logger("Shutting down...");
process.exit(0);
}
process.addListener("SIGTERM", shutdown);
process.addListener("SIGINT", shutdown);