import Events from "./events.ts"; import { serveFile } from "./utils/serve_file.ts"; import { cwd } from "../services/process.ts"; import { merge } from "./utils/object.ts"; import { serve } from "../services/net.ts"; import type { Event, EventListener, EventOptions } from "./events.ts"; import type { HTTPHandlerInfo, HTTPServer, NetAddress, } from "../services/net.ts"; import type { Merge } from "./utils/object.ts"; /** The options to configure the local server */ export interface Options { /** The root path */ root?: string; port?: number; hostname?: string; signal?: AbortSignal; serveFile?: (root: string, request: Request) => Promise; } export const defaults = { root: `${cwd()}/_site`, port: 8000, serveFile, } satisfies Options; export type RequestHandler = (req: Request) => Promise; export type Middleware = ( req: Request, next: RequestHandler, info: HTTPHandlerInfo, ) => Promise; export default class Server { // deno-lint-ignore no-explicit-any events: Events = new Events(); options: Merge; middlewares: Middleware[] = []; fetch: (request: Request, info: HTTPHandlerInfo) => Promise; #server?: HTTPServer; #waiting = false; constructor(options?: Options) { this.options = merge(defaults, options); if (this.options.hostname === "localhost") { this.options.hostname = "0.0.0.0"; } // Create the fetch function for `deno serve` this.fetch = (request: Request, info: HTTPHandlerInfo) => { return this.handle(request, info); }; } /** The local address this server is listening on. */ get addr(): NetAddress | undefined { return this.#server?.addr; } /** The port this server is listening on */ get port(): number { return this.options.port; } /** The hostname this server is listening on */ get hostname(): string { const { hostname } = this.options; return (hostname === "0.0.0.0" || hostname === "127.0.0.1") ? "localhost" : hostname ?? "localhost"; } /** Register one or more middlewares */ use(...middleware: Middleware[]) { this.middlewares.push(...middleware); return this; } /** Register one or more middlewares at the beginning of the list */ useFirst(...middleware: Middleware[]) { this.middlewares.unshift(...middleware); return this; } /** Add a listener to an event */ addEventListener( type: K, listener: EventListener>, options?: EventOptions, ): this { this.events.addEventListener(type, listener, options); return this; } /** Dispatch an event */ dispatchEvent(event: ServerEvent) { return this.events.dispatchEvent(event); } /** Start the server in waiting mode */ wait() { this.#waiting = true; this.start(); } /** Start the server */ start(signal?: AbortSignal) { if (!this.#server) { this.#server = serve({ ...this.options, handler: this.handle.bind(this), signal, onListen: () => { if (!this.#waiting) { this.dispatchEvent({ type: "start" }); } }, onOpenSocket: (socket) => { this.dispatchEvent({ type: "openSocket", socket }); }, }); } else if (this.#waiting) { this.#waiting = false; this.dispatchEvent({ type: "start" }); } } /** Stops the server */ stop() { try { this.#server?.shutdown(); } catch (err) { this.dispatchEvent({ type: "error", error: err as Error, }); } } /** Handle a http request event */ async handle( request: Request, info: HTTPHandlerInfo, ): Promise { if (this.#waiting) { return this.handleWait(); } const middlewares = [...this.middlewares]; const next: RequestHandler = async ( request: Request, ): Promise => { const middleware = middlewares.shift(); if (middleware) { return await middleware(request, next, info); } return await this.options.serveFile(this.options.root, request); }; return await next(request); } handleWait(url?: string): Response { return new Response( ` Por favor, agarde - Please wait
Por favor, agarde - Please wait\n
`, { status: 200, headers: { "Content-Type": "text/html", "X-Lume-CMS": "wait", }, }, ); } } export type ServerEventMap = { // deno-lint-ignore ban-types start: {}; openSocket: { socket: WebSocket; }; error: { error: Error; }; }; /** Custom events for site build */ export type ServerEvent = & Event & ServerEventMap[T] & { type: T }; /** The available event types */ export type ServerEventType = keyof ServerEventMap;