mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-02 16:16:02 +00:00
124 lines
3.5 KiB
TypeScript
124 lines
3.5 KiB
TypeScript
import { Exception } from "core/errors";
|
|
import { isDebug } from "core/env";
|
|
import { $console, mcpLogLevels, s } from "bknd/utils";
|
|
import { $object } from "modules/mcp";
|
|
import { cors } from "hono/cors";
|
|
import { Module } from "modules/Module";
|
|
import { AuthException } from "auth/errors";
|
|
|
|
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"] as const;
|
|
|
|
export const serverConfigSchema = $object(
|
|
"config_server",
|
|
{
|
|
cors: s.strictObject({
|
|
origin: s.string({ default: "*" }),
|
|
allow_methods: s.array(s.string({ enum: serverMethods }), {
|
|
default: serverMethods,
|
|
uniqueItems: true,
|
|
}),
|
|
allow_headers: s.array(s.string(), {
|
|
default: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
|
}),
|
|
allow_credentials: s.boolean({ default: true }),
|
|
}),
|
|
mcp: s.strictObject({
|
|
enabled: s.boolean({ default: false }),
|
|
path: s.string({ default: "/api/system/mcp" }),
|
|
logLevel: s.string({
|
|
enum: mcpLogLevels,
|
|
default: "warning",
|
|
}),
|
|
}),
|
|
},
|
|
{
|
|
description: "Server configuration",
|
|
},
|
|
).strict();
|
|
|
|
export type AppServerConfig = s.Static<typeof serverConfigSchema>;
|
|
|
|
export class AppServer extends Module<AppServerConfig> {
|
|
override getRestrictedPaths() {
|
|
return [];
|
|
}
|
|
|
|
get client() {
|
|
return this.ctx.server;
|
|
}
|
|
|
|
getSchema() {
|
|
return serverConfigSchema;
|
|
}
|
|
|
|
override async build() {
|
|
const origin = this.config.cors.origin ?? "*";
|
|
const origins = origin.includes(",") ? origin.split(",").map((o) => o.trim()) : [origin];
|
|
const all_origins = origins.includes("*");
|
|
this.client.use(
|
|
"*",
|
|
cors({
|
|
origin: (origin: string) => {
|
|
if (all_origins) return origin;
|
|
return origins.includes(origin) ? origin : undefined;
|
|
},
|
|
allowMethods: this.config.cors.allow_methods,
|
|
allowHeaders: this.config.cors.allow_headers,
|
|
credentials: this.config.cors.allow_credentials,
|
|
}),
|
|
);
|
|
|
|
// add an initial fallback route
|
|
this.client.use("/", async (c, next) => {
|
|
await next();
|
|
// if not finalized or giving a 404
|
|
if (!c.finalized || c.res.status === 404) {
|
|
// double check it's root
|
|
if (new URL(c.req.url).pathname === "/") {
|
|
c.res = undefined;
|
|
c.res = Response.json({
|
|
bknd: "hello world!",
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
this.client.onError((err, c) => {
|
|
//throw err;
|
|
$console.error("[AppServer:onError]", err);
|
|
|
|
if (err instanceof Response) {
|
|
return err;
|
|
}
|
|
|
|
if (err instanceof AuthException) {
|
|
if (isDebug()) {
|
|
return c.json(err.toJSON(), err.code);
|
|
}
|
|
|
|
return c.json(err.toJSON(), err.getSafeErrorAndCode().code);
|
|
}
|
|
|
|
if (err instanceof Exception) {
|
|
return c.json(err.toJSON(), err.code as any);
|
|
}
|
|
|
|
if (err instanceof Error) {
|
|
if (isDebug()) {
|
|
return c.json(
|
|
{ error: err.message, stack: err.stack?.split("\n").map((line) => line.trim()) },
|
|
500,
|
|
);
|
|
}
|
|
}
|
|
|
|
return c.json({ error: err.message }, 500);
|
|
});
|
|
this.setBuilt();
|
|
}
|
|
|
|
override toJSON(secrets?: boolean) {
|
|
return this.config;
|
|
}
|
|
}
|