UNPKG

11.6 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11var __importDefault = (this && this.__importDefault) || function (mod) {
12 return (mod && mod.__esModule) ? mod : { "default": mod };
13};
14Object.defineProperty(exports, "__esModule", { value: true });
15/* eslint-disable no-shadow */
16/* eslint-disable @typescript-eslint/no-namespace */
17const express_1 = __importDefault(require("express"));
18const fs_1 = __importDefault(require("fs"));
19const multer_1 = __importDefault(require("multer"));
20const path_1 = __importDefault(require("path"));
21const log_1 = __importDefault(require("./log"));
22const provideRoutes_1 = __importDefault(require("./provideRoutes"));
23let app;
24let server;
25const sockets = new Set();
26let previousPort = 0;
27function close() {
28 return new Promise((resolve) => {
29 if (server) {
30 sockets.forEach((v) => v.destroy());
31 sockets.clear();
32 server.close(() => {
33 resolve();
34 server = undefined;
35 });
36 }
37 else {
38 resolve();
39 }
40 });
41}
42// eslint-disable-next-line @typescript-eslint/no-explicit-any
43function requireDefault(file) {
44 // todo it doesn't work with ES6 imports in packages
45 // eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
46 const m = require(file.path);
47 const arr = [];
48 function moduleEachWrapper(usedApp) {
49 arr.forEach((f) => f(usedApp));
50 }
51 Object.keys(m).forEach((key) => {
52 const f = m[key];
53 if (typeof f !== "function") {
54 log_1.default.error(`Wrong 'export ${key} = ${f}' from ${file.rootName}: expected exporting only functions`);
55 }
56 else {
57 arr.push(f);
58 }
59 });
60 return moduleEachWrapper;
61}
62let isFirstStart = true;
63function mockServer(attachedFileNames, options, listenCallback) {
64 return __awaiter(this, void 0, void 0, function* () {
65 log_1.default.debug(!server ? "starting" : "re-starting...");
66 // self-destroying
67 if (isFirstStart) {
68 const signals = ["SIGINT", "SIGTERM"];
69 signals.forEach((s) => {
70 process.on(s, () => __awaiter(this, void 0, void 0, function* () {
71 yield close();
72 process.exit();
73 }));
74 });
75 isFirstStart = false;
76 }
77 yield close();
78 app = (0, express_1.default)();
79 app.set("json spaces", 2); // prettify json-response
80 app.use(express_1.default.json());
81 app.use(express_1.default.urlencoded({ extended: true })); // support form-urlencoded
82 app.use(express_1.default.text()); // support ordinary text
83 app.use((0, multer_1.default)().any()); // support multipart/form-data
84 // uploadImage middleware - storing in memory
85 app.use((req, _res, next) => {
86 var _a;
87 if (req.file || ((_a = req.files) === null || _a === void 0 ? void 0 : _a.length)) {
88 const fileDownloadUrls = [];
89 // eslint-disable-next-line no-inner-declarations
90 function assignFile(file) {
91 if (!file) {
92 return;
93 }
94 let name = file.originalname;
95 if (encodeURI(name) !== name) {
96 name = path_1.default.extname(name); // extract only extension file if fileName isn't normalized
97 }
98 const lastModified = Date.now();
99 // eslint-disable-next-line no-param-reassign
100 file.downloadUrl = `/_file/${lastModified}_${name}`;
101 fileDownloadUrls.push(file.downloadUrl);
102 app.get(file.downloadUrl, (_req, res) => {
103 res.writeHead(200, {
104 "Content-Type": file.mimetype,
105 "Last-Modified": new Date(lastModified).toUTCString(),
106 });
107 res.end(file.buffer);
108 });
109 }
110 assignFile(req.file);
111 // eslint-disable-next-line no-unused-expressions
112 const { files } = req;
113 if (files) {
114 if (Array.isArray(files)) {
115 files.forEach((v) => assignFile(v));
116 }
117 else {
118 Object.keys(files).forEach((k) => files[k].forEach((v) => assignFile(v)));
119 }
120 }
121 req.fileDownloadUrls = fileDownloadUrls;
122 }
123 next();
124 });
125 options.before && app.use(options.before);
126 // logMiddleware
127 if (options.logRequests || options.logResponses) {
128 app.use((req, res, next) => {
129 if (options.logRequests) {
130 log_1.default.info(`Got request: ${req.method}`, req.url, {
131 httpVersion: req.httpVersion,
132 headers: req.headers,
133 params: req.params,
134 cookies: req.cookies,
135 });
136 }
137 if (!options.logResponses) {
138 next();
139 return;
140 }
141 const oldWrite = res.write;
142 const oldEnd = res.end;
143 const chunks = [];
144 // @ts-ignore
145 res.write = function hookWrite(chunk) {
146 chunks.push(chunk);
147 // @ts-ignore
148 return oldWrite.apply(res, arguments);
149 };
150 // @ts-ignore
151 res.end = function hookEnd(chunk) {
152 if (chunk) {
153 chunks.push(chunk);
154 }
155 // @ts-ignore
156 oldEnd.apply(res, arguments);
157 };
158 res.once("finish", () => {
159 const headers = res.getHeaders();
160 const contentType = headers["content-type"] || "";
161 const isJson = contentType.startsWith("application/json");
162 const isText = contentType.startsWith("text/");
163 // eslint-disable-next-line @typescript-eslint/ban-types
164 let body;
165 try {
166 if (chunks.length) {
167 const isBufferArray = !chunks.some((v) => !v || !(v instanceof Buffer || v instanceof Uint8Array));
168 // @ts-ignore
169 if (isJson) {
170 let str = "";
171 if (isBufferArray) {
172 str = Buffer.concat(chunks).toString("utf8");
173 }
174 else {
175 str = chunks.join("\n");
176 }
177 body = str && JSON.parse(str);
178 }
179 else if (isText) {
180 body = chunks.join("\n");
181 }
182 else if (isBufferArray) {
183 body = "[byteArray]";
184 }
185 else {
186 body = chunks;
187 }
188 }
189 }
190 catch (ex) {
191 log_1.default.error("", ex);
192 }
193 log_1.default.info(`Sent response for ${req.method}`, req.url, {
194 headers: Object.assign({}, headers),
195 statusCode: res.statusCode,
196 statusMessage: res.statusMessage,
197 body,
198 });
199 });
200 next();
201 });
202 }
203 const mockedInfoPath = "/";
204 app.get(mockedInfoPath, (_req, res) => {
205 try {
206 const routes = (0, provideRoutes_1.default)(app, mockedInfoPath);
207 const html = fs_1.default
208 .readFileSync(path_1.default.resolve(__dirname, "../public/index.html"), "utf8")
209 .replace("{routes}", JSON.stringify(routes));
210 res.send(html);
211 }
212 catch (ex) {
213 res.send("Mock server is ready");
214 log_1.default.error("Exception in provideRoutes()", ex);
215 }
216 });
217 // provides favicon only in case if request to this server directly but not to via ParentProxyServer
218 app.get("/favicon.ico", (req, res, next) => {
219 const hostHeader = req.header("host");
220 // in this case previousPort == currentPort
221 if (hostHeader && hostHeader.includes(`:${previousPort}`)) {
222 res.sendFile(require.resolve("../public/favicon.ico"));
223 }
224 else {
225 next();
226 }
227 });
228 function listen(port) {
229 server = app
230 .listen(port, () => {
231 if (port !== previousPort) {
232 log_1.default.info("Started at", `http://localhost:${port}/`);
233 previousPort = port;
234 }
235 else {
236 log_1.default.debug("Started at", `http://localhost:${port}/`);
237 }
238 listenCallback && listenCallback(port, server);
239 })
240 .on("error", (err) => {
241 if (err.code === "EADDRINUSE" || err.code === "EACCES") {
242 listen(port + 1);
243 }
244 else {
245 log_1.default.error("", err);
246 }
247 })
248 .on("connection", (socket) => {
249 sockets.add(socket);
250 socket.once("close", () => {
251 sockets.delete(socket);
252 });
253 });
254 }
255 try {
256 if (attachedFileNames.length === 0) {
257 log_1.default.error("There are no rootFiles");
258 }
259 else {
260 log_1.default.debug("import rootFiles:", "", attachedFileNames.map((v) => v.path));
261 attachedFileNames.forEach((v) => requireDefault(v)(app));
262 }
263 listen(options.port);
264 }
265 catch (ex) {
266 log_1.default.error("Exception during attaching node-modules", ex);
267 }
268 return app;
269 });
270}
271exports.default = mockServer;