import { Application, Context, Next, Router } from "jsr:@oak/oak@^17.1.4";
import State from "../state.ts";
import { randomUUID } from "node:crypto";
import { Entity } from "../entity.ts";
import { ContextState } from "./context_state.ts";
interface I_Host {
port?: number
}
class Host {
readonly port: number
#server: Deno.HttpServer<Deno.NetAddr> | undefined
#app: Application
#router: Router
get server() { return this.#server };
constructor({ port }: I_Host = {}) {
this.port = port || Number(Deno.env.get("PORT")) || 9090;
this.#app = new Application({});
this.#router = new Router();
this.#router.use(this.verify_request.bind(this));
this.#app.use(this.#router.routes());
State.HOSTS.push(this);
}
// #region Analytics
async RequestAnalytics(_ctx: Context<ContextState>, next: Next) {
try {
/*
* Do worker-side analytics and task monitoring here
*/
} catch(e: any) {
console.warn(`Host::RequestAnalytics() error! Continueing with request...`, e.message);
};
return next();
}
async ResponseAnalytics(ctx: Context<ContextState>, next: Next) {
const { state: { date } } = ctx;
const _request_total_time_ms = Date.now() - date.valueOf();
try {
/*
* Do worker-side analytics and task monitoring here
*/
} catch(e: any) {
console.warn(`Host::RequestAnalytics() error! Continueing with request...`, e.message);
};
return next();
}
listen(callback?: (port: number) => void) {
this.#server = Deno.serve({
port: this.port
}, async (request, info) => {
if(request.headers.get("upgrade") != "websocket") {
const app_response = await this.#app.handle(request, info.remoteAddr);
return app_response ?? new Response('', { status: 404 });
}
return this.handshake(request);
});
callback?.(this.port);
}
async verify_request(ctx: Context<ContextState>, next: Next) {
ctx.state.date = new Date();
ctx.state.id = randomUUID();
ctx.state.headers = new Headers({
'x-request-id': ctx.state.id
});
try {
ctx.state.body = await ctx.request.body.json();
} catch(_) { /**/ }
ctx.state.ip = ctx.request.headers.get('X-Forwarded-For') || ctx.request.ip;
return next();
}
handshake(request: Request) {
const { socket, response } = Deno.upgradeWebSocket(request);
const SIGNATURE = request.headers.get('sec-websocket-protocol')?.replace(/^SIGNATURE, (.+$)$/, '$1');
if(!SIGNATURE || SIGNATURE !== State.SIGNATURE) {
socket.close(1, 'Invalid signature');
return new Response('', { status: 403 });
}
const query: Record<string, string> = new URL(request.url).search
?.replace(/^\?/, '')
.split('&')
.reduce((p, query) => {
const [key, value] = query.split('=');
p[key] = value;
return p;
}, {} as Record<string, string>) ?? {};
const { uuid } = query;
if(!uuid) {
socket.close(1, 'No provided uuid');
return new Response('', { status: 403 });
}
socket.addEventListener('open', () => {
Entity.__AddWorker(uuid, socket);
});
return response;
}
get app() {
return this.#app;
}
get router() {
return this.#router;
}
}
export default Host;