badmfck-api-server 3.9.93 → 3.9.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,7 +50,7 @@ function defaultOptions() {
50
50
  return {
51
51
  port: 8080,
52
52
  baseEndPoint: "/api/",
53
- corsHostWhiteList: ["localhost:3000"],
53
+ corsHostWhiteList: ["http://localhost:3000"],
54
54
  endpoints: [],
55
55
  jsonLimit: "10mb",
56
56
  projectName: "Application project",
@@ -77,7 +77,7 @@ exports.REQ_HTTP_REQUESTS_COUNT = new badmfck_signal_1.Req(undefined, "REQ_HTTP_
77
77
  exports.REQ_HTTP_SERVER = new badmfck_signal_1.Req(undefined, "REQ_HTTP_SERVER");
78
78
  exports.REQ_INTERNAL_CALL = new badmfck_signal_1.Req(undefined, "REQ_INTERNAL_CALL");
79
79
  exports.REQ_MONITOR_USERS = new badmfck_signal_1.Req(undefined, "REQ_MONITOR_USERS");
80
- exports.REQ_DOC_USERS = new badmfck_signal_1.Req(undefined, "REQ_MONITOR_USERS");
80
+ exports.REQ_DOC_USERS = new badmfck_signal_1.Req(undefined, "REQ_DOC_USERS");
81
81
  const activeServices = [];
82
82
  async function Initializer(services) {
83
83
  services.push(new StatService_1.StatService());
@@ -105,7 +105,18 @@ class APIService extends BaseService_1.BaseService {
105
105
  this.options.corsHostWhiteList = [];
106
106
  const self = "http://localhost:" + this.options.port;
107
107
  if (!this.options.corsHostWhiteList.find(val => val === self))
108
- this.options.corsHostWhiteList.push();
108
+ this.options.corsHostWhiteList.push(self);
109
+ const list = [];
110
+ for (let h of this.options.corsHostWhiteList) {
111
+ h = h.replace(/\/$/, "");
112
+ if (!/^https?:\/\//i.test(h)) {
113
+ list.push(`http://${h}`, `https://${h}`);
114
+ }
115
+ else {
116
+ list.push(h);
117
+ }
118
+ }
119
+ this.options.corsHostWhiteList = Array.from(new Set(list));
109
120
  exports.REQ_MONITOR_USERS.listener = async () => this.options.access.monitor ?? [];
110
121
  exports.REQ_DOC_USERS.listener = async () => this.options.access.documentation ?? [];
111
122
  this.options.endpoints.push(new Monitor_1.Monitor(this.options));
@@ -142,8 +153,8 @@ class APIService extends BaseService_1.BaseService {
142
153
  exports.REQ_HTTP_SERVER.listener = async (_) => { return { express: app, http: server }; };
143
154
  if (this.options.isProductionEnvironment)
144
155
  app.set("env", 'production');
145
- app.use(express_1.default.json({ limit: '10mb' }));
146
- app.use(express_1.default.urlencoded({ limit: '10mb', extended: true }));
156
+ app.use(express_1.default.json({ limit: this.options.jsonLimit ?? '10mb' }));
157
+ app.use(express_1.default.urlencoded({ limit: this.options.jsonLimit ?? '10mb', extended: true }));
147
158
  app.use((0, express_fileupload_1.default)({
148
159
  limitHandler: (req, res, next) => {
149
160
  this.sendResponse(req.get("Referer") ?? "", res, {
@@ -161,6 +172,7 @@ class APIService extends BaseService_1.BaseService {
161
172
  response: undefined,
162
173
  files: undefined
163
174
  });
175
+ return;
164
176
  },
165
177
  limits: { fileSize: this.options.fileLimit },
166
178
  useTempFiles: true,
@@ -168,7 +180,7 @@ class APIService extends BaseService_1.BaseService {
168
180
  tempFileDir: this.options.fileTempDir,
169
181
  abortOnLimit: true
170
182
  }));
171
- app.use(async (err, req, resp, next) => {
183
+ app.use((err, req, resp, next) => {
172
184
  if (!err) {
173
185
  next();
174
186
  return;
@@ -193,15 +205,53 @@ class APIService extends BaseService_1.BaseService {
193
205
  response: undefined,
194
206
  files: undefined
195
207
  });
208
+ return;
196
209
  });
210
+ const corsSet = new Set(this.options.corsHostWhiteList.map(x => String(x).replace(/\/$/, "")));
197
211
  const corsOptions = {
198
212
  origin: (origin, callback) => {
199
- const originIsWhitelisted = this.options.corsHostWhiteList.includes(origin);
200
- callback(null, originIsWhitelisted);
213
+ if (!origin)
214
+ return callback(null, true);
215
+ try {
216
+ const o = new URL(String(origin));
217
+ const originNorm = o.origin.replace(/\/$/, "");
218
+ const hostNorm = o.host.replace(/\/$/, "");
219
+ const ok = corsSet.has(originNorm);
220
+ return callback(null, ok);
221
+ }
222
+ catch {
223
+ return callback(null, false);
224
+ }
201
225
  },
202
226
  credentials: true
203
227
  };
204
228
  app.use((0, cors_1.default)(corsOptions));
229
+ app.use((req, res, next) => {
230
+ const originHeader = req.headers.origin;
231
+ if (!originHeader)
232
+ return next();
233
+ let originNorm;
234
+ try {
235
+ originNorm = new URL(String(originHeader)).origin.replace(/\/$/, "");
236
+ }
237
+ catch {
238
+ res.status(403).send({
239
+ error: { ...DefaultErrors_1.default.FORBIDDEN, details: "Invalid Origin header" },
240
+ data: null,
241
+ httpStatus: 403,
242
+ });
243
+ return;
244
+ }
245
+ if (!corsSet.has(originNorm)) {
246
+ res.status(403).send({
247
+ error: { ...DefaultErrors_1.default.FORBIDDEN, details: `Origin not allowed: ${originNorm}` },
248
+ data: null,
249
+ httpStatus: 403,
250
+ });
251
+ return;
252
+ }
253
+ return next();
254
+ });
205
255
  BaseEndpoint_1.BaseEndpoint.setEntryPoint(this.options.baseEndPoint);
206
256
  for (let i of this.options.endpoints) {
207
257
  await i.init();
@@ -214,17 +264,17 @@ class APIService extends BaseService_1.BaseService {
214
264
  this.requestsCount++;
215
265
  const tme = +new Date();
216
266
  const execute = async () => {
217
- const body = req.body;
267
+ const body = { ...(req.body ?? {}) };
218
268
  if (req.query && typeof req.query === "object") {
219
- for (let i in req.query) {
220
- body[i] = req.query[i];
221
- }
269
+ const keys = Object.keys(req.query);
270
+ for (let k of keys)
271
+ body[k] = req.query[k];
222
272
  }
223
273
  let httpRequest = {
224
274
  raw: req,
225
275
  response: res,
226
276
  method: req.method,
227
- data: req.body,
277
+ data: body,
228
278
  params: req.params,
229
279
  headers: req.headers,
230
280
  endpoint: ep,
@@ -324,7 +374,7 @@ class APIService extends BaseService_1.BaseService {
324
374
  }
325
375
  this.sendResponse(req.get("Referer") ?? "", res, result, tme, ep, httpRequest);
326
376
  };
327
- execute();
377
+ return execute();
328
378
  });
329
379
  }
330
380
  }
@@ -359,15 +409,17 @@ class APIService extends BaseService_1.BaseService {
359
409
  endpoint: req?.endpoint,
360
410
  headers: req?.headers,
361
411
  method: req?.method,
362
- data: req?.data,
412
+ data: this.checkDataLength(req?.data),
363
413
  params: req?.params,
364
414
  interceptorResult: req?.interceptorResult
365
415
  },
366
- response: data
416
+ response: this.checkDataLength(data),
367
417
  };
368
418
  this.netLog.push(logItem);
369
419
  if (this.netLog.length > 100)
370
420
  this.netLog.shift();
421
+ if (nextLogID > Number.MAX_SAFE_INTEGER - 1000)
422
+ nextLogID = 0;
371
423
  }
372
424
  async sendResponse(ref, res, data, requestTime, endpoint, req) {
373
425
  if (data.blockResponse) {
@@ -392,7 +444,7 @@ class APIService extends BaseService_1.BaseService {
392
444
  if (this.options.appVersion)
393
445
  data.version = this.options.appVersion;
394
446
  MonitorService_1.S_STAT_REGISTRATE_REQUEST.invoke(data);
395
- if (res.destroyed || res.closed) {
447
+ if (res.socket?.destroyed || res.writableEnded || res.headersSent || (res.destroyed !== undefined && res.destroyed)) {
396
448
  (0, LogService_1.logAPI)("Connection already closed, can't send response for: " + data.endpoint);
397
449
  }
398
450
  else {
@@ -401,11 +453,17 @@ class APIService extends BaseService_1.BaseService {
401
453
  res.sendFile(data.file, err => {
402
454
  if (err) {
403
455
  (0, LogService_1.logError)("Can't send file: " + data.file);
404
- this.sendResponse(ref, res, {
405
- error: DefaultErrors_1.default.CANT_SEND_FILE,
406
- data: null,
407
- httpStatus: 500
408
- }, requestTime, data.endpoint, req);
456
+ if (!res.headersSent && !res.writableEnded) {
457
+ res.status(500).send({ error: DefaultErrors_1.default.CANT_SEND_FILE, data: null });
458
+ return;
459
+ }
460
+ else {
461
+ try {
462
+ res.destroy?.(err);
463
+ }
464
+ catch { }
465
+ return;
466
+ }
409
467
  }
410
468
  else {
411
469
  if (req)
@@ -447,27 +505,18 @@ class APIService extends BaseService_1.BaseService {
447
505
  this.addNetlog(data, req, requestTime, 0);
448
506
  }
449
507
  checkDataLength(data, result, lvl) {
450
- if (!lvl)
451
- lvl = 0;
452
- if (typeof data !== "object") {
453
- if (typeof data === "string" && data.length > 1024)
454
- return data.substring(0, 1000) + "... (total:" + data.length + ")";
455
- return data;
508
+ let json = "";
509
+ try {
510
+ json = JSON.stringify(data, (k, v) => {
511
+ if (typeof v === 'string' && v.length > 255)
512
+ return v.slice(0, 255) + `...(${v.length})`;
513
+ return v;
514
+ });
456
515
  }
457
- if (typeof data === "object") {
458
- if (!result)
459
- result = {};
460
- let arrcnt = 100;
461
- for (let i in data) {
462
- result[i] = this.checkDataLength(data[i], result[i], lvl + 1);
463
- arrcnt--;
464
- if (arrcnt <= 0) {
465
- result["..."] = "... total: " + Array.isArray(data) ? data.length : Object.keys(data).length;
466
- break;
467
- }
468
- }
516
+ catch (e) {
517
+ json = "[unserializable data]";
469
518
  }
470
- return result ?? null;
519
+ return json.length > 500 ? json.slice(0, 500) + `...(total ${json.length})` : json;
471
520
  }
472
521
  }
473
522
  exports.APIService = APIService;
@@ -44,6 +44,7 @@ export declare class BaseEndpoint implements IBaseEndpoint {
44
44
  ignoreInDocumentation: boolean;
45
45
  private static entrypoint;
46
46
  endpoint: string;
47
+ protected streamOptions: Record<string, any>;
47
48
  static setEntryPoint: (ep: string) => void;
48
49
  static getEntryPoint: () => string;
49
50
  constructor(endpoint: string);
@@ -53,6 +54,11 @@ export declare class BaseEndpoint implements IBaseEndpoint {
53
54
  __precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
54
55
  __execute(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
55
56
  protected validateStructure(structure: any, req: HTTPRequestVO): Promise<void>;
56
- protected createStream(req: HTTPRequestVO): HTTPRequestVO;
57
+ protected createStream(req: HTTPRequestVO, opts: {
58
+ contentType?: string;
59
+ headers?: {
60
+ [key: string]: string;
61
+ };
62
+ }): HTTPRequestVO;
57
63
  }
58
64
  export {};
@@ -17,6 +17,7 @@ class BaseEndpoint {
17
17
  ignoreInDocumentation = false;
18
18
  static entrypoint = "/";
19
19
  endpoint = "";
20
+ streamOptions = {};
20
21
  static setEntryPoint = (ep) => {
21
22
  this.entrypoint = ep;
22
23
  if (!this.entrypoint.endsWith("/")) {
@@ -73,43 +74,65 @@ class BaseEndpoint {
73
74
  for (let i of this.endpoints) {
74
75
  let targetEP = BaseEndpoint.entrypoint + i.endpoint;
75
76
  targetEP = targetEP.replaceAll("//", "/");
76
- if (targetEP === req.endpoint) {
77
- if (i.handler && typeof i.handler === "function") {
78
- if (req.params && req.params.validationModel && req.params.validationModel === "1")
79
- return { data: i.validationModel };
80
- if (i.validationModel)
81
- await this.validateStructure(i.validationModel, req);
82
- return i.handler.call(this, req);
77
+ if (targetEP !== req.endpoint)
78
+ continue;
79
+ const httpMethod = req.method.toUpperCase();
80
+ const resolveAsStream = () => {
81
+ if (i.asStream)
82
+ return true;
83
+ if (i.handler && typeof i.handler === "object") {
84
+ const h = i.handler[httpMethod];
85
+ if (h && typeof h === "object" && "asStream" in h && h.asStream)
86
+ return true;
83
87
  }
84
- const httpMethod = req.method.toUpperCase();
85
- if (i.handler && i.handler[httpMethod] && typeof i.handler[httpMethod] === "function") {
86
- if (req.params && req.params.validationModel && req.params.validationModel === "1")
87
- return { data: i.validationModel };
88
- if (i.validationModel)
89
- await this.validateStructure(i.validationModel, req);
90
- if (i.asStream)
91
- req = this.createStream(req);
92
- return i.handler[httpMethod].call(this, req);
93
- }
94
- if (i.handler
95
- && i.handler[httpMethod]
96
- && typeof i.handler[httpMethod] === "object"
97
- && "controller" in i.handler[httpMethod]
98
- && typeof i.handler[httpMethod].controller === "function") {
99
- const vmodel = i.handler[httpMethod].validationModel;
100
- if (req.params && req.params.validationModel && req.params.validationModel === "1")
101
- return { data: vmodel };
102
- if (vmodel)
103
- await this.validateStructure(vmodel, req);
104
- if (i.asStream)
105
- req = this.createStream(req);
106
- return i.handler[httpMethod].controller.call(this, req);
107
- }
108
- return {
109
- error: { ...DefaultErrors_1.default.METHOD_NOT_ALLOWED, details: "No handler for " + httpMethod },
110
- data: null
111
- };
88
+ return false;
89
+ };
90
+ const asStream = resolveAsStream();
91
+ if (i.handler && typeof i.handler === "function") {
92
+ if (req.data && req.data.validationModel && req.data.validationModel === "1")
93
+ return { data: i.validationModel };
94
+ if (i.validationModel)
95
+ await this.validateStructure(i.validationModel, req);
96
+ if (asStream)
97
+ req = this.createStream(req, this.streamOptions[i.endpoint] ?? {});
98
+ const result = await i.handler.call(this, req);
99
+ if (asStream)
100
+ return { blockResponse: true, data: null };
101
+ return result;
102
+ }
103
+ if (i.handler && i.handler[httpMethod] && typeof i.handler[httpMethod] === "function") {
104
+ if (req.data && req.data.validationModel && req.data.validationModel === "1")
105
+ return { data: i.validationModel };
106
+ if (i.validationModel)
107
+ await this.validateStructure(i.validationModel, req);
108
+ if (asStream)
109
+ req = this.createStream(req, this.streamOptions[i.endpoint] ?? {});
110
+ const result = await i.handler[httpMethod].call(this, req);
111
+ if (asStream)
112
+ return { blockResponse: true, data: null };
113
+ return result;
114
+ }
115
+ if (i.handler
116
+ && i.handler[httpMethod]
117
+ && typeof i.handler[httpMethod] === "object"
118
+ && "controller" in i.handler[httpMethod]
119
+ && typeof i.handler[httpMethod].controller === "function") {
120
+ const vmodel = i.handler[httpMethod].validationModel;
121
+ if (req.data && req.data.validationModel && req.data.validationModel === "1")
122
+ return { data: vmodel };
123
+ if (vmodel)
124
+ await this.validateStructure(vmodel, req);
125
+ if (asStream)
126
+ req = this.createStream(req, this.streamOptions[i.endpoint] ?? {});
127
+ const result = await i.handler[httpMethod].controller.call(this, req);
128
+ if (asStream)
129
+ return { blockResponse: true, data: null };
130
+ return result;
112
131
  }
132
+ return {
133
+ error: { ...DefaultErrors_1.default.METHOD_NOT_ALLOWED, details: "No handler for " + httpMethod },
134
+ data: null
135
+ };
113
136
  }
114
137
  }
115
138
  (0, LogService_1.logWarn)("${BaseEndpoint.js}", "Unhandled entrypoint: " + this.endpoint);
@@ -128,17 +151,101 @@ class BaseEndpoint {
128
151
  throw { ...DefaultErrors_1.default.WRONG_PARAMS, details: report };
129
152
  Validator_1.Validator.filterStructure(structure, req.data);
130
153
  }
131
- createStream(req) {
154
+ createStream(req, opts) {
132
155
  const res = req.response;
133
- res.setHeader('Content-Type', 'text/plain');
134
- res.setHeader('Transfer-Encoding', 'chunked');
135
- const stream = new stream_1.Readable();
136
- stream.pipe(res);
156
+ if (res.headersSent)
157
+ throw { ...DefaultErrors_1.default.INTERNAL_SERVER_ERROR, details: "Headers already sent, can't create stream" };
158
+ res.statusCode = 200;
159
+ res.setHeader("Content-Type", opts?.contentType ?? "application/octet-stream");
160
+ res.setHeader("Transfer-Encoding", "chunked");
161
+ res.setHeader("Cache-Control", "no-cache");
162
+ if (opts?.headers) {
163
+ for (let h in opts.headers) {
164
+ res.setHeader(h, opts.headers[h]);
165
+ }
166
+ }
167
+ const stream = new stream_1.Readable({
168
+ read() {
169
+ }
170
+ });
171
+ let finished = false;
172
+ let timer;
173
+ const cleanup = () => {
174
+ if (finished)
175
+ return;
176
+ finished = true;
177
+ if (timer)
178
+ clearTimeout(timer);
179
+ try {
180
+ if (!stream.destroyed) {
181
+ stream.destroy();
182
+ }
183
+ }
184
+ catch (e) {
185
+ console.error("Error during stream cleanup:", e);
186
+ }
187
+ try {
188
+ if (!res.writableEnded)
189
+ res.end();
190
+ }
191
+ catch { }
192
+ };
193
+ const endOk = () => {
194
+ if (finished)
195
+ return;
196
+ finished = true;
197
+ if (timer)
198
+ clearTimeout(timer);
199
+ try {
200
+ if (!stream.destroyed)
201
+ stream.push(null);
202
+ }
203
+ catch (e) {
204
+ cleanup();
205
+ }
206
+ };
207
+ res.on('close', () => {
208
+ cleanup();
209
+ });
210
+ const setupTimer = () => {
211
+ if (finished)
212
+ return;
213
+ if (timer)
214
+ clearTimeout(timer);
215
+ timer = setTimeout(() => cleanup(), 60_000);
216
+ };
217
+ setupTimer();
218
+ const origPush = stream.push.bind(stream);
219
+ stream.push = (chunk, encoding) => {
220
+ if (chunk === null)
221
+ return origPush(null);
222
+ if (finished)
223
+ return false;
224
+ setupTimer();
225
+ return origPush(chunk, encoding);
226
+ };
227
+ req.raw?.on?.("aborted", () => {
228
+ cleanup();
229
+ try {
230
+ res.destroy?.();
231
+ }
232
+ catch { }
233
+ });
234
+ req.raw?.on?.("close", cleanup);
137
235
  stream.on('error', (err) => {
138
- console.error('Stream error:', err);
139
- res.status(500).send('Stream error');
236
+ console.error("Stream error:", err);
237
+ cleanup();
238
+ try {
239
+ res.destroy?.(err);
240
+ }
241
+ catch {
242
+ res.end();
243
+ }
140
244
  });
245
+ res.on("finish", cleanup);
246
+ stream.pipe(res);
141
247
  req.stream = stream;
248
+ req.endStream = (force) => force ? cleanup() : endOk();
142
249
  return req;
143
250
  }
144
251
  }
@@ -33,6 +33,7 @@ const DefaultErrors_1 = __importStar(require("../structures/DefaultErrors"));
33
33
  const Validator_1 = require("../helper/Validator");
34
34
  const UID_1 = require("../helper/UID");
35
35
  const crypto_1 = __importDefault(require("crypto"));
36
+ const __1 = require("../..");
36
37
  exports.REQ_EXTERNAL_CALL = new badmfck_signal_1.Req(undefined, "REQ_EXTERNAL_CALL");
37
38
  exports.REQ_XT = new badmfck_signal_1.Req(undefined, "REQ_XT");
38
39
  exports.REQ_XT.listener = async ({ id, req }) => {
@@ -88,10 +89,21 @@ class ExternalService extends BaseService_1.BaseService {
88
89
  const encrypted = this.encrypt({ requestName, requestData });
89
90
  if (this.options.url.endsWith("/"))
90
91
  this.options.url = this.options.url.substring(0, this.options.url.length - 1);
91
- try {
92
- }
93
- catch (e) {
92
+ const resp = await __1.Http.post(this.options.url + "/external/call/" + this.options.id, encrypted.data, {
93
+ headers: {
94
+ "authorization": encrypted.authorization,
95
+ "Content-Type": "application/json",
96
+ "Accept": "application/json"
97
+ }
98
+ });
99
+ if (!resp.ok) {
100
+ return { ...DefaultErrors_1.default.BAD_REQUEST, details: resp.details, stack: resp.error };
94
101
  }
102
+ const json = await resp.data.json();
103
+ if (typeof json === "object" && "error" in json)
104
+ return json.error;
105
+ if (typeof json === "object" && "data" in json)
106
+ return json.data;
95
107
  }
96
108
  async onExternalCall(req) {
97
109
  const data = this.decrypt(req);
@@ -0,0 +1,4 @@
1
+ import { BaseService } from "../BaseService";
2
+ export declare class MicroserviceClient extends BaseService {
3
+ constructor();
4
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MicroserviceClient = void 0;
4
+ const BaseService_1 = require("../BaseService");
5
+ class MicroserviceClient extends BaseService_1.BaseService {
6
+ constructor() {
7
+ super("MicroserviceClient");
8
+ }
9
+ }
10
+ exports.MicroserviceClient = MicroserviceClient;
@@ -0,0 +1,4 @@
1
+ import { BaseService } from "../BaseService";
2
+ export declare class MicroserviceHost extends BaseService {
3
+ constructor();
4
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MicroserviceHost = void 0;
4
+ const BaseService_1 = require("../BaseService");
5
+ class MicroserviceHost extends BaseService_1.BaseService {
6
+ constructor() {
7
+ super("MicroserviceHost");
8
+ }
9
+ }
10
+ exports.MicroserviceHost = MicroserviceHost;
@@ -36,6 +36,7 @@ export interface HTTPRequestVO<T = any, TInterceport = any> {
36
36
  unlocked?: boolean;
37
37
  internalCall?: boolean;
38
38
  stream?: Readable;
39
+ endStream?: (force?: boolean) => void;
39
40
  internalCallParams?: any;
40
41
  }
41
42
  export interface IError {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "3.9.93",
3
+ "version": "3.9.95",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",