1 | "use strict";
|
2 | var __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 | };
|
11 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
12 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
13 | };
|
14 | Object.defineProperty(exports, "__esModule", { value: true });
|
15 |
|
16 |
|
17 | const express_1 = __importDefault(require("express"));
|
18 | const fs_1 = __importDefault(require("fs"));
|
19 | const multer_1 = __importDefault(require("multer"));
|
20 | const path_1 = __importDefault(require("path"));
|
21 | const log_1 = __importDefault(require("./log"));
|
22 | const provideRoutes_1 = __importDefault(require("./provideRoutes"));
|
23 | let app;
|
24 | let server;
|
25 | const sockets = new Set();
|
26 | let previousPort = 0;
|
27 | function 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 |
|
43 | function requireDefault(file) {
|
44 |
|
45 |
|
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 | }
|
62 | let isFirstStart = true;
|
63 | function mockServer(attachedFileNames, options, listenCallback) {
|
64 | return __awaiter(this, void 0, void 0, function* () {
|
65 | log_1.default.debug(!server ? "starting" : "re-starting...");
|
66 |
|
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);
|
80 | app.use(express_1.default.json());
|
81 | app.use(express_1.default.urlencoded({ extended: true }));
|
82 | app.use(express_1.default.text());
|
83 | app.use((0, multer_1.default)().any());
|
84 |
|
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 |
|
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);
|
97 | }
|
98 | const lastModified = Date.now();
|
99 |
|
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 |
|
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 |
|
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 |
|
145 | res.write = function hookWrite(chunk) {
|
146 | chunks.push(chunk);
|
147 |
|
148 | return oldWrite.apply(res, arguments);
|
149 | };
|
150 |
|
151 | res.end = function hookEnd(chunk) {
|
152 | if (chunk) {
|
153 | chunks.push(chunk);
|
154 | }
|
155 |
|
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 |
|
164 | let body;
|
165 | try {
|
166 | if (chunks.length) {
|
167 | const isBufferArray = !chunks.some((v) => !v || !(v instanceof Buffer || v instanceof Uint8Array));
|
168 |
|
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 |
|
218 | app.get("/favicon.ico", (req, res, next) => {
|
219 | const hostHeader = req.header("host");
|
220 |
|
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 | }
|
271 | exports.default = mockServer;
|