badmfck-api-server 3.9.93 → 3.9.94

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.
@@ -29,7 +29,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.APIService = exports.Initializer = exports.REQ_DOC_USERS = exports.REQ_MONITOR_USERS = exports.REQ_INTERNAL_CALL = exports.REQ_HTTP_SERVER = exports.REQ_HTTP_REQUESTS_COUNT = exports.REQ_HTTP_LOG = exports.getDefaultOptions = void 0;
30
30
  const express_1 = __importDefault(require("express"));
31
31
  const BaseService_1 = require("./BaseService");
32
- const cors_1 = __importDefault(require("cors"));
33
32
  const BaseEndpoint_1 = require("./BaseEndpoint");
34
33
  const DefaultErrors_1 = __importStar(require("./structures/DefaultErrors"));
35
34
  const badmfck_signal_1 = require("badmfck-signal");
@@ -50,7 +49,7 @@ function defaultOptions() {
50
49
  return {
51
50
  port: 8080,
52
51
  baseEndPoint: "/api/",
53
- corsHostWhiteList: ["localhost:3000"],
52
+ corsHostWhiteList: ["http://localhost:3000"],
54
53
  endpoints: [],
55
54
  jsonLimit: "10mb",
56
55
  projectName: "Application project",
@@ -77,7 +76,7 @@ exports.REQ_HTTP_REQUESTS_COUNT = new badmfck_signal_1.Req(undefined, "REQ_HTTP_
77
76
  exports.REQ_HTTP_SERVER = new badmfck_signal_1.Req(undefined, "REQ_HTTP_SERVER");
78
77
  exports.REQ_INTERNAL_CALL = new badmfck_signal_1.Req(undefined, "REQ_INTERNAL_CALL");
79
78
  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");
79
+ exports.REQ_DOC_USERS = new badmfck_signal_1.Req(undefined, "REQ_DOC_USERS");
81
80
  const activeServices = [];
82
81
  async function Initializer(services) {
83
82
  services.push(new StatService_1.StatService());
@@ -105,7 +104,18 @@ class APIService extends BaseService_1.BaseService {
105
104
  this.options.corsHostWhiteList = [];
106
105
  const self = "http://localhost:" + this.options.port;
107
106
  if (!this.options.corsHostWhiteList.find(val => val === self))
108
- this.options.corsHostWhiteList.push();
107
+ this.options.corsHostWhiteList.push(self);
108
+ const list = [];
109
+ for (let h of this.options.corsHostWhiteList) {
110
+ h = h.replace(/\/$/, "");
111
+ if (!/^https?:\/\//i.test(h)) {
112
+ list.push(`http://${h}`, `https://${h}`);
113
+ }
114
+ else {
115
+ list.push(h);
116
+ }
117
+ }
118
+ this.options.corsHostWhiteList = Array.from(new Set(list));
109
119
  exports.REQ_MONITOR_USERS.listener = async () => this.options.access.monitor ?? [];
110
120
  exports.REQ_DOC_USERS.listener = async () => this.options.access.documentation ?? [];
111
121
  this.options.endpoints.push(new Monitor_1.Monitor(this.options));
@@ -142,8 +152,8 @@ class APIService extends BaseService_1.BaseService {
142
152
  exports.REQ_HTTP_SERVER.listener = async (_) => { return { express: app, http: server }; };
143
153
  if (this.options.isProductionEnvironment)
144
154
  app.set("env", 'production');
145
- app.use(express_1.default.json({ limit: '10mb' }));
146
- app.use(express_1.default.urlencoded({ limit: '10mb', extended: true }));
155
+ app.use(express_1.default.json({ limit: this.options.jsonLimit ?? '10mb' }));
156
+ app.use(express_1.default.urlencoded({ limit: this.options.jsonLimit ?? '10mb', extended: true }));
147
157
  app.use((0, express_fileupload_1.default)({
148
158
  limitHandler: (req, res, next) => {
149
159
  this.sendResponse(req.get("Referer") ?? "", res, {
@@ -161,6 +171,7 @@ class APIService extends BaseService_1.BaseService {
161
171
  response: undefined,
162
172
  files: undefined
163
173
  });
174
+ return;
164
175
  },
165
176
  limits: { fileSize: this.options.fileLimit },
166
177
  useTempFiles: true,
@@ -168,7 +179,7 @@ class APIService extends BaseService_1.BaseService {
168
179
  tempFileDir: this.options.fileTempDir,
169
180
  abortOnLimit: true
170
181
  }));
171
- app.use(async (err, req, resp, next) => {
182
+ app.use((err, req, resp, next) => {
172
183
  if (!err) {
173
184
  next();
174
185
  return;
@@ -193,15 +204,35 @@ class APIService extends BaseService_1.BaseService {
193
204
  response: undefined,
194
205
  files: undefined
195
206
  });
207
+ return;
208
+ });
209
+ const corsSet = new Set(this.options.corsHostWhiteList.map(x => String(x).replace(/\/$/, "")));
210
+ app.use((req, res, next) => {
211
+ const originHeader = req.headers.origin;
212
+ if (!originHeader)
213
+ return next();
214
+ let originNorm;
215
+ try {
216
+ originNorm = new URL(String(originHeader)).origin;
217
+ }
218
+ catch {
219
+ res.status(403).send({
220
+ error: { ...DefaultErrors_1.default.FORBIDDEN, details: "Invalid Origin header" },
221
+ data: null,
222
+ httpStatus: 403,
223
+ });
224
+ return;
225
+ }
226
+ if (!corsSet.has(originNorm)) {
227
+ res.status(403).send({
228
+ error: { ...DefaultErrors_1.default.FORBIDDEN, details: `Origin not allowed: ${originNorm}` },
229
+ data: null,
230
+ httpStatus: 403,
231
+ });
232
+ return;
233
+ }
234
+ return next();
196
235
  });
197
- const corsOptions = {
198
- origin: (origin, callback) => {
199
- const originIsWhitelisted = this.options.corsHostWhiteList.includes(origin);
200
- callback(null, originIsWhitelisted);
201
- },
202
- credentials: true
203
- };
204
- app.use((0, cors_1.default)(corsOptions));
205
236
  BaseEndpoint_1.BaseEndpoint.setEntryPoint(this.options.baseEndPoint);
206
237
  for (let i of this.options.endpoints) {
207
238
  await i.init();
@@ -214,17 +245,17 @@ class APIService extends BaseService_1.BaseService {
214
245
  this.requestsCount++;
215
246
  const tme = +new Date();
216
247
  const execute = async () => {
217
- const body = req.body;
248
+ const body = { ...(req.body ?? {}) };
218
249
  if (req.query && typeof req.query === "object") {
219
- for (let i in req.query) {
220
- body[i] = req.query[i];
221
- }
250
+ const keys = Object.keys(req.query);
251
+ for (let k of keys)
252
+ body[k] = req.query[k];
222
253
  }
223
254
  let httpRequest = {
224
255
  raw: req,
225
256
  response: res,
226
257
  method: req.method,
227
- data: req.body,
258
+ data: body,
228
259
  params: req.params,
229
260
  headers: req.headers,
230
261
  endpoint: ep,
@@ -324,7 +355,7 @@ class APIService extends BaseService_1.BaseService {
324
355
  }
325
356
  this.sendResponse(req.get("Referer") ?? "", res, result, tme, ep, httpRequest);
326
357
  };
327
- execute();
358
+ return execute();
328
359
  });
329
360
  }
330
361
  }
@@ -359,15 +390,17 @@ class APIService extends BaseService_1.BaseService {
359
390
  endpoint: req?.endpoint,
360
391
  headers: req?.headers,
361
392
  method: req?.method,
362
- data: req?.data,
393
+ data: this.checkDataLength(req?.data),
363
394
  params: req?.params,
364
395
  interceptorResult: req?.interceptorResult
365
396
  },
366
- response: data
397
+ response: this.checkDataLength(data),
367
398
  };
368
399
  this.netLog.push(logItem);
369
400
  if (this.netLog.length > 100)
370
401
  this.netLog.shift();
402
+ if (nextLogID > Number.MAX_SAFE_INTEGER - 1000)
403
+ nextLogID = 0;
371
404
  }
372
405
  async sendResponse(ref, res, data, requestTime, endpoint, req) {
373
406
  if (data.blockResponse) {
@@ -392,7 +425,7 @@ class APIService extends BaseService_1.BaseService {
392
425
  if (this.options.appVersion)
393
426
  data.version = this.options.appVersion;
394
427
  MonitorService_1.S_STAT_REGISTRATE_REQUEST.invoke(data);
395
- if (res.destroyed || res.closed) {
428
+ if (res.socket?.destroyed || res.writableEnded || res.headersSent || (res.destroyed !== undefined && res.destroyed)) {
396
429
  (0, LogService_1.logAPI)("Connection already closed, can't send response for: " + data.endpoint);
397
430
  }
398
431
  else {
@@ -401,11 +434,17 @@ class APIService extends BaseService_1.BaseService {
401
434
  res.sendFile(data.file, err => {
402
435
  if (err) {
403
436
  (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);
437
+ if (!res.headersSent && !res.writableEnded) {
438
+ res.status(500).send({ error: DefaultErrors_1.default.CANT_SEND_FILE, data: null });
439
+ return;
440
+ }
441
+ else {
442
+ try {
443
+ res.destroy?.(err);
444
+ }
445
+ catch { }
446
+ return;
447
+ }
409
448
  }
410
449
  else {
411
450
  if (req)
@@ -447,27 +486,18 @@ class APIService extends BaseService_1.BaseService {
447
486
  this.addNetlog(data, req, requestTime, 0);
448
487
  }
449
488
  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;
489
+ let json = "";
490
+ try {
491
+ json = JSON.stringify(data, (k, v) => {
492
+ if (typeof v === 'string' && v.length > 255)
493
+ return v.slice(0, 255) + `...(${v.length})`;
494
+ return v;
495
+ });
456
496
  }
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
- }
497
+ catch (e) {
498
+ json = "[unserializable data]";
469
499
  }
470
- return result ?? null;
500
+ return json.length > 500 ? json.slice(0, 500) + `...(total ${json.length})` : json;
471
501
  }
472
502
  }
473
503
  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.94",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",