nose-pluto/src/webapp.ts
Chris Wanstrath 74a101db93 try it
2025-09-16 21:56:30 -07:00

91 lines
2.5 KiB
TypeScript

import { type Context, Hono } from "hono"
import type { Child } from "hono/jsx"
import { renderToString } from "hono/jsx/dom/server"
import { join } from "node:path"
import { readdirSync } from "node:fs"
import { NOSE_WWW } from "./config"
import { isFile } from "./utils"
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
export type App = Hono | Handler
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
const app = await findApp(subdomain)
if (!app) return c.text(`App Not Found: ${subdomain}`, 404)
if (app instanceof Hono)
return app.fetch(c.req.raw)
else
return toResponse(await app(c))
}
export function apps(): string[] {
const apps: string[] = []
for (const entry of readdirSync(NOSE_WWW))
apps.push(entry.replace(/\.tsx?/, ""))
return apps
}
async function findApp(name: string): Promise<App | undefined> {
let path = join(NOSE_WWW, `${name}.ts`)
let app = await loadApp(path)
if (app) return app
path = join(NOSE_WWW, `${name}.tsx`)
app = await loadApp(path)
if (app) return app
path = join(NOSE_WWW, name, "index.ts")
app = await loadApp(path)
if (app) return app
path = join(NOSE_WWW, name, "index.tsx")
app = await loadApp(path)
if (app) return app
console.error("can't find app:", name)
}
async function loadApp(path: string): Promise<App | undefined> {
if (!isFile(path)) return
const mod = await import(path + `?t=${Date.now()}`)
if (mod?.default)
return mod.default as App
}
export function toResponse(source: string | Child | Response): Response {
if (source instanceof Response)
return source
else if (typeof source === "string")
return new Response(source)
else
return new Response(renderToString(source), {
headers: {
"Content-Type": "text/html; charset=utf-8"
}
})
}
export async function publishDNS() {
const { stdout: ipRaw } = await Bun.$`hostname -I | awk '{print $1}'`.quiet()
const { stdout: hostRaw } = await Bun.$`hostname`.quiet()
const ip = ipRaw.toString().trim()
const host = hostRaw.toString().trim()
const procs = apps().map(app =>
Bun.spawn(["avahi-publish", "-a", `${app}.${host}.local`, "-R", ip])
)
const signals = ["SIGINT", "SIGTERM"]
signals.forEach(sig =>
process.on(sig, () => {
procs.forEach(p => p.kill("SIGTERM"))
process.exit(0)
})
)
}