2022-10-10 12:50:21 +00:00
|
|
|
import { Hook, Manifest } from "../types.ts";
|
|
|
|
import { System } from "../system.ts";
|
2024-01-13 17:07:02 +00:00
|
|
|
import { Context, Next } from "../../server/deps.ts";
|
2022-10-10 12:50:21 +00:00
|
|
|
|
|
|
|
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> {
|
|
|
|
readonly prefix: string;
|
|
|
|
|
2023-12-10 12:23:42 +00:00
|
|
|
constructor(prefix: string) {
|
2022-10-10 12:50:21 +00:00
|
|
|
this.prefix = prefix;
|
|
|
|
}
|
|
|
|
|
2023-12-10 12:23:42 +00:00
|
|
|
public async handleRequest(
|
|
|
|
system: System<EndpointHookT>,
|
|
|
|
ctx: Context,
|
|
|
|
next: Next,
|
|
|
|
) {
|
2024-01-13 17:07:02 +00:00
|
|
|
const req = ctx.req;
|
|
|
|
const url = new URL(req.url);
|
|
|
|
const requestPath = url.pathname;
|
2023-12-10 12:23:42 +00:00
|
|
|
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;
|
2022-10-10 12:50:21 +00:00
|
|
|
}
|
2023-12-10 12:23:42 +00:00
|
|
|
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) {
|
2022-10-10 12:50:21 +00:00
|
|
|
continue;
|
|
|
|
}
|
2023-12-10 12:23:42 +00:00
|
|
|
// 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, [
|
|
|
|
{
|
2024-01-13 17:07:02 +00:00
|
|
|
path: url.pathname,
|
2023-12-10 12:23:42 +00:00
|
|
|
method: req.method,
|
2024-01-13 17:07:02 +00:00
|
|
|
body: await req.text(),
|
2023-12-10 12:23:42 +00:00
|
|
|
query: Object.fromEntries(
|
2024-01-13 17:07:02 +00:00
|
|
|
url.searchParams.entries(),
|
2023-12-10 12:23:42 +00:00
|
|
|
),
|
2024-01-13 17:07:02 +00:00
|
|
|
headers: req.header(),
|
2023-12-10 12:23:42 +00:00
|
|
|
} as EndpointRequest,
|
|
|
|
]);
|
|
|
|
if (response.headers) {
|
|
|
|
for (
|
|
|
|
const [key, value] of Object.entries(
|
|
|
|
response.headers,
|
|
|
|
)
|
|
|
|
) {
|
2024-01-13 17:07:02 +00:00
|
|
|
ctx.header(key, value);
|
2022-10-10 12:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
2024-01-13 17:07:02 +00:00
|
|
|
ctx.status(response.status);
|
|
|
|
console.log("Going to return", response.body);
|
|
|
|
if (typeof response.body === "string") {
|
|
|
|
return ctx.text(response.body);
|
|
|
|
} else if (response.body instanceof Uint8Array) {
|
|
|
|
return ctx.body(response.body);
|
|
|
|
} else {
|
|
|
|
return ctx.json(response.body);
|
|
|
|
}
|
2023-12-10 12:23:42 +00:00
|
|
|
} catch (e: any) {
|
|
|
|
console.error("Error executing function", e);
|
2024-01-13 17:07:02 +00:00
|
|
|
return ctx.body(e.message, 500);
|
2022-10-10 12:50:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-10 12:23:42 +00:00
|
|
|
}
|
|
|
|
// console.log("Shouldn't get here");
|
|
|
|
await next();
|
|
|
|
}
|
|
|
|
|
|
|
|
apply(): void {
|
2022-10-10 12:50:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
validateManifest(manifest: Manifest<EndpointHookT>): string[] {
|
2022-10-15 17:02:56 +00:00
|
|
|
const errors = [];
|
|
|
|
for (const functionDef of Object.values(manifest.functions)) {
|
2022-10-10 12:50:21 +00:00
|
|
|
if (!functionDef.http) {
|
|
|
|
continue;
|
|
|
|
}
|
2022-10-15 17:02:56 +00:00
|
|
|
const endpoints = Array.isArray(functionDef.http)
|
2022-10-10 12:50:21 +00:00
|
|
|
? functionDef.http
|
|
|
|
: [functionDef.http];
|
2022-10-15 17:02:56 +00:00
|
|
|
for (const { path, method } of endpoints) {
|
2022-10-10 12:50:21 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|