webpack-mock-server
Version:
Mocks api requests for webpack-dev-server with hot-replacement
241 lines (240 loc) • 10 kB
JavaScript
;
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;
});
}