UNPKG

@cordisjs/plugin-server

Version:
238 lines (234 loc) 8.12 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { Server: () => Server, WebSocketLayer: () => WebSocketLayer, default: () => index_default }); module.exports = __toCommonJS(index_exports); var import_cordis = require("cordis"); var import_cosmokit = require("cosmokit"); var import_node_http = require("node:http"); var import_path_to_regexp = require("path-to-regexp"); var import_koa_body = require("koa-body"); var import_parseurl = __toESM(require("parseurl"), 1); var import_ws = require("ws"); var import_schemastery = __toESM(require("schemastery"), 1); var import_router = __toESM(require("@koa/router"), 1); var import_koa = __toESM(require("koa"), 1); // src/listen.ts var import_net = __toESM(require("net"), 1); function listen({ host, port, maxPort = port }) { const server = import_net.default.createServer(); return new Promise((resolve, reject) => { function onListen() { server.off("error", onError); server.close((err) => { err ? reject(err) : resolve(port); }); } __name(onListen, "onListen"); function onError(err) { server.off("listening", onListen); if (!(err.code === "EADDRINUSE" || err.code === "EACCES")) { return reject(err); } port++; if (port > maxPort) { return reject(new Error("No open ports available")); } testPort(); } __name(onError, "onError"); function testPort() { server.once("error", onError); server.once("listening", onListen); server.listen(port, host); } __name(testPort, "testPort"); testPort(); }); } __name(listen, "listen"); // src/index.ts var WebSocketLayer = class { constructor(server, path, callback) { this.server = server; this.callback = callback; this.regexp = (0, import_path_to_regexp.pathToRegexp)(path); } static { __name(this, "WebSocketLayer"); } clients = /* @__PURE__ */ new Set(); regexp; accept(socket, request) { if (!this.regexp.test((0, import_parseurl.default)(request).pathname)) return; this.clients.add(socket); socket.addEventListener("close", () => { this.clients.delete(socket); }); this.callback?.(socket, request); return true; } close() { (0, import_cosmokit.remove)(this.server.wsStack, this); for (const socket of this.clients) { socket.close(); } } }; var Server = class extends import_router.default { constructor(ctx, config) { super(); this.ctx = ctx; this.config = config; ctx.runtime.name = "server"; ctx.provide("server"); ctx.alias("server", ["router"]); const body = (0, import_koa_body.koaBody)({ multipart: true, jsonLimit: "10mb", formLimit: "10mb", textLimit: "10mb", includeUnparsed: true }); this._body = async (c, next) => { if (c[/* @__PURE__ */ Symbol.for("isBodyParsed")]) return next(); c[/* @__PURE__ */ Symbol.for("isBodyParsed")] = true; await body(c, next); }; this._koa.use(this.routes()); this._koa.use(this.allowedMethods()); this._http = (0, import_node_http.createServer)(this._koa.callback()); this._ws = new import_ws.WebSocketServer({ server: this._http }); this._ws.on("connection", (socket, request) => { for (const manager of this.wsStack) { if (manager.accept(socket, request)) return; } socket.close(); }); ctx.decline(["selfUrl", "host", "port", "maxPort"]); if (config.selfUrl) { config.selfUrl = (0, import_cosmokit.trimSlash)(config.selfUrl); } ctx.on("ready", async () => { const { host = "127.0.0.1", port } = config; if (!port) return; this.host = host; this.port = await listen(config); this._http.listen(this.port, host); this.ctx.logger.info("server listening at %c", `http://${host}:${this.port}`); ctx.emit(this, "server/ready"); }, true); ctx.on("dispose", () => { if (config.port) { this.ctx.logger.info("server closing"); } this._ws?.close(); this._http?.close(); }); const self = import_cordis.Context.associate(this, "server"); ctx.set("server", self); ctx.on("internal/listener", function(name, listener) { if (name !== "server/ready" || !self[import_cordis.Context.filter](this) || !self.port) return; this.scope.ensure(async () => listener()); return () => false; }); return self; } static { __name(this, "Server"); } [import_cordis.Service.tracker] = { associate: "server", property: "ctx" }; _http; _ws; wsStack = []; _koa = new import_koa.default(); _body; host; port; [import_cordis.Context.filter](ctx) { return ctx[import_cordis.Context.isolate].server === this.ctx[import_cordis.Context.isolate].server; } get selfUrl() { const wildcard = ["0.0.0.0", "::"]; const host = wildcard.includes(this.host) ? "127.0.0.1" : this.host; if (this.port === 80) { return `http://${host}`; } else if (this.port === 443) { return `https://${host}`; } else { return `http://${host}:${this.port}`; } } /** * hack into router methods to make sure that koa middlewares are disposable */ register(...args) { args[2] = (0, import_cosmokit.makeArray)(args[2]); if (!args[2][0][/* @__PURE__ */ Symbol.for("noParseBody")]) { args[2].unshift(this._body); } const layer = super.register(...args); this.ctx.scope.disposables.push(() => { (0, import_cosmokit.remove)(this.stack, layer); }); return layer; } ws(path, callback) { const layer = new WebSocketLayer(this, path, callback); this.wsStack.push(layer); this.ctx.scope.disposables.push(() => layer.close()); return layer; } }; ((Server2) => { Server2.Config = import_schemastery.default.object({ host: import_schemastery.default.string().default("127.0.0.1").description("要监听的 IP 地址。如果将此设置为 `0.0.0.0` 将监听所有地址,包括局域网和公网地址。"), port: import_schemastery.default.natural().max(65535).description("要监听的初始端口号。"), maxPort: import_schemastery.default.natural().max(65535).description("允许监听的最大端口号。"), selfUrl: import_schemastery.default.string().role("link").description("应用暴露在公网的地址。") }); })(Server || (Server = {})); var index_default = Server; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Server, WebSocketLayer }); //# sourceMappingURL=index.cjs.map