1
0
silverbullet/plugos/hooks/endpoint.ts
2023-08-11 20:37:13 +02:00

138 lines
4.1 KiB
TypeScript

import { Hook, Manifest } from "../types.ts";
import { System } from "../system.ts";
import { Application } from "../../server/deps.ts";
export type EndpointRequest = {
method: string;
path: string;
query: { [key: string]: string };
headers: { [key: string]: string };
body: any;
};
export type EndpointResponse = {
status: number;
headers?: { [key: string]: string };
body: any;
};
export type EndpointHookT = {
http?: EndPointDef | EndPointDef[];
};
export type EndPointDef = {
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "OPTIONS" | "ANY";
path: string;
};
export class EndpointHook implements Hook<EndpointHookT> {
private app: Application;
readonly prefix: string;
constructor(app: Application, prefix: string) {
this.app = app;
this.prefix = prefix;
}
apply(system: System<EndpointHookT>): void {
this.app.use(async (ctx, next) => {
const req = ctx.request;
const requestPath = ctx.request.url.pathname;
if (!requestPath.startsWith(this.prefix)) {
return next();
}
console.log("Endpoint request", requestPath);
// Iterate over all loaded plugins
for (const [plugName, plug] of system.loadedPlugs.entries()) {
const manifest = plug.manifest;
if (!manifest) {
continue;
}
const functions = manifest.functions;
console.log("Checking plug", plugName);
const prefix = `${this.prefix}/${plugName}`;
if (!requestPath.startsWith(prefix)) {
continue;
}
for (const [name, functionDef] of Object.entries(functions)) {
if (!functionDef.http) {
continue;
}
console.log("Got config", functionDef);
const endpoints = Array.isArray(functionDef.http)
? functionDef.http
: [functionDef.http];
console.log(endpoints);
for (const { path, method } of endpoints) {
const prefixedPath = `${prefix}${path}`;
if (
prefixedPath === requestPath &&
((method || "GET") === req.method || method === "ANY")
) {
try {
const response: EndpointResponse = await plug.invoke(name, [
{
path: req.url.pathname,
method: req.method,
body: req.body(),
query: Object.fromEntries(
req.url.searchParams.entries(),
),
headers: Object.fromEntries(req.headers.entries()),
} as EndpointRequest,
]);
if (response.headers) {
for (
const [key, value] of Object.entries(
response.headers,
)
) {
ctx.response.headers.set(key, value);
}
}
ctx.response.status = response.status;
ctx.response.body = response.body;
console.log("Sent result");
return;
} catch (e: any) {
console.error("Error executing function", e);
ctx.response.status = 500;
ctx.response.body = e.message;
return;
}
}
}
}
}
// console.log("Shouldn't get here");
next();
});
}
validateManifest(manifest: Manifest<EndpointHookT>): string[] {
const errors = [];
for (const functionDef of Object.values(manifest.functions)) {
if (!functionDef.http) {
continue;
}
const endpoints = Array.isArray(functionDef.http)
? functionDef.http
: [functionDef.http];
for (const { path, method } of endpoints) {
if (!path) {
errors.push("Path not defined for endpoint");
}
if (
method &&
["GET", "POST", "PUT", "DELETE", "ANY"].indexOf(method) === -1
) {
errors.push(
`Invalid method ${method} for end point with with ${path}`,
);
}
}
}
return errors;
}
}