UNPKG

webpack-mock-server

Version:

Mocks api requests for webpack-dev-server with hot-replacement

241 lines (240 loc) 10 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = mockServer; const express_1 = __importDefault(require("express")); const fs_1 = __importDefault(require("fs")); const multer_1 = __importDefault(require("multer")); const path_1 = __importDefault(require("path")); const log_1 = __importDefault(require("./log")); const provideRoutes_1 = __importDefault(require("./provideRoutes")); const helpers_1 = require("./helpers"); let app; let server; const sockets = new Set(); let previousPort = 0; function close() { return new Promise((resolve) => { if (server) { sockets.forEach((v) => v.destroy()); sockets.clear(); server.close(() => { resolve(); server = undefined; }); } else { resolve(); } }); } let isFirstStart = true; function mockServer(attachedFileNames, options, listenCallback) { return __awaiter(this, void 0, void 0, function* () { log_1.default.debug(!server ? "starting" : "re-starting..."); if (isFirstStart) { const signals = ["SIGINT", "SIGTERM"]; signals.forEach((s) => { process.on(s, () => __awaiter(this, void 0, void 0, function* () { yield close(); process.exit(); })); }); isFirstStart = false; } yield close(); app = (0, express_1.default)(); app.set("json spaces", 2); app.use(express_1.default.json({ strict: false })); app.use(express_1.default.urlencoded({ extended: true })); app.use(express_1.default.text()); app.use((0, multer_1.default)().any()); app.use((req, _res, next) => { var _a, _b; if (req.file || ((_a = req.files) === null || _a === void 0 ? void 0 : _a.length)) { const fileDownloadUrls = []; function assignFile(file) { if (!file) { return; } let name = file.originalname; if (encodeURI(name) !== name) { name = path_1.default.extname(name); } const lastModified = Date.now(); file.downloadUrl = `/_file/${lastModified}_${name}`; fileDownloadUrls.push(file.downloadUrl); app.get(file.downloadUrl, (_req, res) => { res.writeHead(200, { "Content-Type": file.mimetype, "Last-Modified": new Date(lastModified).toUTCString(), }); res.end(file.buffer); }); } assignFile(req.file); const { files } = req; if (files) { if (Array.isArray(files)) { files.forEach((v) => assignFile(v)); } else { Object.keys(files).forEach((k) => files[k].forEach((v) => assignFile(v))); } } req.fileDownloadUrls = fileDownloadUrls; } if ((_b = req.rawHeaders) === null || _b === void 0 ? void 0 : _b.some((h) => h.startsWith("multipart/form-data"))) { req.body = (0, helpers_1.parseFormDataObject)(req.body); } else { req.body = (0, helpers_1.tryParseJSONDate)(req.body); } next(); }); options.before && app.use(options.before); if (options.logRequests || options.logResponses) { app.use((req, res, next) => { if (options.logRequests) { log_1.default.info(`Got request: ${req.method}`, req.url, { httpVersion: req.httpVersion, headers: req.headers, params: req.params, cookies: req.cookies, }); } if (!options.logResponses) { next(); return; } const oldWrite = res.write; const oldEnd = res.end; const chunks = []; res.write = function hookWrite(chunk) { chunks.push(chunk); return oldWrite.apply(res, arguments); }; res.end = function hookEnd(chunk) { if (chunk) { chunks.push(chunk); } oldEnd.apply(res, arguments); }; res.once("finish", () => { const headers = res.getHeaders(); const contentType = headers["content-type"] || ""; const isJson = contentType.startsWith("application/json"); const isText = contentType.startsWith("text/"); let body; try { if (chunks.length) { const isBufferArray = !chunks.some((v) => !v || !(v instanceof Buffer || v instanceof Uint8Array)); if (isJson) { let str = ""; if (isBufferArray) { str = Buffer.concat(chunks).toString("utf8"); } else { str = chunks.join("\n"); } body = str && JSON.parse(str); } else if (isText) { body = chunks.join("\n"); } else if (isBufferArray) { body = "[byteArray]"; } else { body = chunks; } } } catch (ex) { log_1.default.error("", ex); } log_1.default.info(`Sent response for ${req.method}`, req.url, { headers: Object.assign({}, headers), statusCode: res.statusCode, statusMessage: res.statusMessage, body, }); }); next(); }); } const mockedInfoPath = "/"; app.get(mockedInfoPath, (_req, res) => { try { const routes = (0, provideRoutes_1.default)(app, mockedInfoPath); const html = fs_1.default.readFileSync(path_1.default.resolve(__dirname, "../public/index.html"), "utf8").replace("{routes}", JSON.stringify(routes)); res.send(html); } catch (ex) { res.send("Mock server is ready"); log_1.default.error("Exception in provideRoutes()", ex); } }); app.get("/favicon.ico", (req, res, next) => { const hostHeader = req.header("host"); if (hostHeader && hostHeader.includes(`:${previousPort}`)) { res.sendFile(require.resolve("../public/favicon.ico")); } else { next(); } }); function listen(port) { server = app .listen(port, () => { if (port !== previousPort) { log_1.default.info("Started at", `http://localhost:${port}/`); previousPort = port; } else { log_1.default.debug("Started at", `http://localhost:${port}/`); } listenCallback && listenCallback(port, server); }) .on("error", (err) => { if (err.code === "EADDRINUSE" || err.code === "EACCES") { listen(port + 1); } else { log_1.default.error("", err); } }) .on("connection", (socket) => { sockets.add(socket); socket.once("close", () => { sockets.delete(socket); }); }); } try { if (attachedFileNames.length === 0) { log_1.default.error("There are no rootFiles"); } else { log_1.default.debug("import rootFiles:", "", attachedFileNames.map((v) => v.path)); attachedFileNames.forEach((v) => (0, helpers_1.requireDefault)(v)(app)); } listen(options.port); } catch (ex) { log_1.default.error("Exception during attaching node-modules", ex); } return app; }); }