badmfck-api-server 1.5.2 → 1.5.4

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.
@@ -25,6 +25,10 @@ export interface APIServiceOptions {
25
25
  onError?: ((...rest: any[]) => void) | null;
26
26
  isProductionEnvironment: boolean;
27
27
  interceptor?: IBaseEndpoint;
28
+ monitor?: {
29
+ login: string;
30
+ password: string;
31
+ }[];
28
32
  }
29
33
  export declare function getDefaultOptions(): APIServiceOptions;
30
34
  export declare const REQ_CREATE_NET_LOG: Req<void, APIServiceNetworkLogItem>;
@@ -34,6 +38,8 @@ export declare class APIService extends BaseService {
34
38
  private static nextLogID;
35
39
  private version;
36
40
  private options;
41
+ private monitor?;
42
+ private monitorIndexFile?;
37
43
  netLog: APIServiceNetworkLogItem[];
38
44
  constructor(options?: APIServiceOptions | null);
39
45
  init(): Promise<void>;
@@ -11,6 +11,10 @@ const BaseEndpoint_1 = require("./BaseEndpoint");
11
11
  const DefaultErrors_1 = __importDefault(require("./structures/DefaultErrors"));
12
12
  const badmfck_signal_1 = require("badmfck-signal");
13
13
  const LogService_1 = require("./LogService");
14
+ const Monitor_1 = require("./monitor/Monitor");
15
+ const path_1 = __importDefault(require("path"));
16
+ const crypto_1 = __importDefault(require("crypto"));
17
+ const fs_1 = __importDefault(require("fs"));
14
18
  function getDefaultOptions() {
15
19
  return {
16
20
  port: 8091,
@@ -43,8 +47,10 @@ async function Initializer(services) {
43
47
  exports.Initializer = Initializer;
44
48
  class APIService extends BaseService_1.BaseService {
45
49
  static nextLogID = 0;
46
- version = "1.5.2";
50
+ version = "1.5.4";
47
51
  options;
52
+ monitor;
53
+ monitorIndexFile;
48
54
  netLog = [];
49
55
  constructor(options) {
50
56
  super('HTTP Service');
@@ -54,6 +60,16 @@ class APIService extends BaseService_1.BaseService {
54
60
  const self = "http://localhost:" + this.options.port;
55
61
  if (!this.options.corsHostWhiteList.find(val => val === self))
56
62
  this.options.corsHostWhiteList.push();
63
+ if (this.options.monitor && this.options.monitor.length > 0) {
64
+ this.monitor = new Monitor_1.Monitor();
65
+ this.options.endpoints.push(this.monitor);
66
+ console.log("Service Monitor initialized");
67
+ console.log("monitor links:");
68
+ for (let i of this.options.monitor) {
69
+ const hash = crypto_1.default.createHash("sha256").update(i.login + i.password).digest().toString("hex");
70
+ console.log("/sm-" + hash);
71
+ }
72
+ }
57
73
  }
58
74
  async init() {
59
75
  exports.REQ_HTTP_LOG.listener = async (ignore) => this.netLog;
@@ -148,12 +164,22 @@ class APIService extends BaseService_1.BaseService {
148
164
  httpRequest.interceptorResult = interceptorResult;
149
165
  }
150
166
  }
167
+ if (i === this.monitor)
168
+ httpRequest.precheck = { data: this.options.monitor };
169
+ const precheck = await i.precheck(httpRequest);
170
+ if (precheck && precheck.error) {
171
+ this.sendResponse(res, precheck, tme, ep, log);
172
+ return;
173
+ }
174
+ httpRequest.precheck = precheck;
151
175
  result = await i.execute(httpRequest);
152
176
  }
153
177
  catch (e) {
154
178
  console.error(e);
155
179
  if (this.options.onError)
156
180
  this.options.onError(e);
181
+ if (this.monitor)
182
+ this.monitor.registrateFatalError(ep);
157
183
  this.sendResponse(res, {
158
184
  httpStatus: 500,
159
185
  error: {
@@ -172,6 +198,40 @@ class APIService extends BaseService_1.BaseService {
172
198
  }
173
199
  app.use((req, res, next) => {
174
200
  const tme = +new Date();
201
+ if (this.monitor && this.options && this.options.monitor) {
202
+ if (req.method === "GET") {
203
+ let monitorRequest = false;
204
+ for (let i of this.options.monitor) {
205
+ const hash = crypto_1.default.createHash("sha256").update(i.login + i.password).digest().toString("hex");
206
+ if (req.originalUrl.endsWith("/sm-" + hash)) {
207
+ monitorRequest = true;
208
+ break;
209
+ }
210
+ }
211
+ if (monitorRequest) {
212
+ const date = new Date();
213
+ try {
214
+ if (!this.monitorIndexFile)
215
+ this.monitorIndexFile = fs_1.default.readFileSync(path_1.default.resolve(__dirname, "monitor", "index.html"));
216
+ res.setHeader("Content-Type", "text/html");
217
+ res.setHeader("Content-Length", this.monitorIndexFile.byteLength);
218
+ res.status(200).send(this.monitorIndexFile);
219
+ }
220
+ catch (e) {
221
+ this.sendResponse(res, {
222
+ error: {
223
+ code: 10002,
224
+ message: "Internal server error",
225
+ details: `${e}`
226
+ },
227
+ data: null,
228
+ httpStatus: 500
229
+ }, tme, req.path);
230
+ }
231
+ return;
232
+ }
233
+ }
234
+ }
175
235
  this.sendResponse(res, {
176
236
  error: DefaultErrors_1.default.UNKNOWN_REQUEST,
177
237
  data: null,
@@ -186,6 +246,8 @@ class APIService extends BaseService_1.BaseService {
186
246
  data.responseTime = (+new Date()) - requestTime;
187
247
  data.version = this.version;
188
248
  data.endpoint = endpoint ?? "no_endpoint";
249
+ if (this.monitor)
250
+ this.monitor.registrateResponse(data.endpoint, data.responseTime);
189
251
  if (log)
190
252
  log.time = data.responseTime;
191
253
  if (res.destroyed || res.closed) {
@@ -193,6 +255,8 @@ class APIService extends BaseService_1.BaseService {
193
255
  log.error = "Connection already closed, can't send response";
194
256
  if (this.options.onError)
195
257
  this.options.onError("Connection already closed, can't send response", data);
258
+ if (this.monitor)
259
+ this.monitor.registrateError(data.endpoint);
196
260
  }
197
261
  else {
198
262
  try {
@@ -219,6 +283,8 @@ class APIService extends BaseService_1.BaseService {
219
283
  res.send(data);
220
284
  if (log)
221
285
  log.response = data;
286
+ if (data.error && this.monitor)
287
+ this.monitor.registrateAPIError(data.endpoint);
222
288
  }
223
289
  }
224
290
  }
@@ -227,6 +293,8 @@ class APIService extends BaseService_1.BaseService {
227
293
  this.options.onError("Can't send response", e);
228
294
  if (log)
229
295
  log.error = "Can't send response";
296
+ if (this.monitor)
297
+ this.monitor.registrateError(data.endpoint);
230
298
  }
231
299
  }
232
300
  if (this.options.onNetworkLog && log)
@@ -2,6 +2,7 @@ import { HTTPRequestVO, TransferPacketVO } from "./structures/Interfaces";
2
2
  export interface IBaseEndpoint {
3
3
  endpoints?: IEndpointHandler[];
4
4
  execute: (req: HTTPRequestVO) => Promise<TransferPacketVO<any>>;
5
+ precheck: (req: HTTPRequestVO) => Promise<TransferPacketVO<any> | null>;
5
6
  init: () => Promise<void>;
6
7
  ignoreHttpLogging: boolean;
7
8
  }
@@ -22,5 +23,6 @@ export declare class BaseEndpoint implements IBaseEndpoint {
22
23
  constructor(endpoint: string);
23
24
  registerEndpoints(endpoints: IEndpointHandler[]): void;
24
25
  init(): Promise<void>;
26
+ precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
25
27
  execute(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
26
28
  }
@@ -56,6 +56,7 @@ class BaseEndpoint {
56
56
  (0, LogService_1.logInfo)("${BaseEndpoint.js}", "endpoint: " + i.endpoint + " initalized, ignoreInterceptor: " + (i.ignoreInterceptor ?? "false"));
57
57
  }
58
58
  ;
59
+ async precheck(req) { return null; }
59
60
  async execute(req) {
60
61
  if (this.endpoints && this.endpoints.length > 0) {
61
62
  for (let i of this.endpoints) {
@@ -0,0 +1,33 @@
1
+ import { BaseEndpoint } from "../BaseEndpoint";
2
+ import { HTTPRequestVO, TransferPacketVO } from "../structures/Interfaces";
3
+ export declare const indexFilePath: string;
4
+ interface IStatObject {
5
+ requests: Map<string, number>;
6
+ errors: Map<string, number>;
7
+ fatalErrors: Map<string, number>;
8
+ apiErrors: Map<string, number>;
9
+ }
10
+ export declare class Monitor extends BaseEndpoint {
11
+ ignoreHttpLogging: boolean;
12
+ private startedAt;
13
+ private httpRequests;
14
+ constructor();
15
+ registrateError(endpoint: string): void;
16
+ registrateFatalError(endpoint: string): void;
17
+ registrateAPIError(endpoint: string): void;
18
+ registrateResponse(endpoint: string, responseTime: number): void;
19
+ increaseStat(statObject: Map<string, number>, endpoint: string): void;
20
+ createStatObj(): IStatObject;
21
+ getDateIndex(d: Date): number;
22
+ getHourMinuteIndex(d: Date): string;
23
+ leadZero(i: number): string;
24
+ precheck(req: HTTPRequestVO): Promise<TransferPacketVO<any> | null>;
25
+ logs(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
26
+ netlog(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
27
+ metrics(req: HTTPRequestVO): Promise<TransferPacketVO<any>>;
28
+ mapToKeyValue(map: Map<string, number>): {
29
+ name: string;
30
+ value: number;
31
+ }[];
32
+ }
33
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Monitor = exports.indexFilePath = void 0;
7
+ const APIService_1 = require("../APIService");
8
+ const BaseEndpoint_1 = require("../BaseEndpoint");
9
+ const LogService_1 = require("../LogService");
10
+ const crypto_1 = __importDefault(require("crypto"));
11
+ const path_1 = __importDefault(require("path"));
12
+ exports.indexFilePath = path_1.default.resolve(__dirname, "index.html");
13
+ class Monitor extends BaseEndpoint_1.BaseEndpoint {
14
+ ignoreHttpLogging = true;
15
+ startedAt = new Date();
16
+ httpRequests = new Map();
17
+ constructor() {
18
+ super("sys-monitor");
19
+ this.registerEndpoints([
20
+ { ignoreInterceptor: true, endpoint: "log", handler: this.logs },
21
+ { ignoreInterceptor: true, endpoint: "netlog", handler: this.netlog },
22
+ { ignoreInterceptor: true, endpoint: "metrics", handler: this.metrics }
23
+ ]);
24
+ }
25
+ registrateError(endpoint) {
26
+ const so = this.createStatObj();
27
+ this.increaseStat(so.errors, endpoint);
28
+ }
29
+ registrateFatalError(endpoint) {
30
+ const so = this.createStatObj();
31
+ this.increaseStat(so.fatalErrors, endpoint);
32
+ }
33
+ registrateAPIError(endpoint) {
34
+ const so = this.createStatObj();
35
+ this.increaseStat(so.apiErrors, endpoint);
36
+ }
37
+ registrateResponse(endpoint, responseTime) {
38
+ const so = this.createStatObj();
39
+ this.increaseStat(so.requests, endpoint);
40
+ }
41
+ increaseStat(statObject, endpoint) {
42
+ let reqep = statObject.get(endpoint);
43
+ if (!reqep)
44
+ reqep = 0;
45
+ reqep += 1;
46
+ statObject.set(endpoint, reqep);
47
+ }
48
+ createStatObj() {
49
+ const d = new Date();
50
+ const dtm = this.getDateIndex(d);
51
+ let day = this.httpRequests.get(dtm);
52
+ if (!day) {
53
+ day = new Map();
54
+ this.httpRequests.set(dtm, day);
55
+ if (this.httpRequests.size > 14) {
56
+ for (let i of this.httpRequests) {
57
+ this.httpRequests.delete(i[0]);
58
+ break;
59
+ }
60
+ }
61
+ }
62
+ const hourMinute = this.getHourMinuteIndex(d);
63
+ let hm = day.get(hourMinute);
64
+ if (!hm) {
65
+ hm = {
66
+ errors: new Map(),
67
+ requests: new Map(),
68
+ fatalErrors: new Map(),
69
+ apiErrors: new Map()
70
+ };
71
+ }
72
+ return hm;
73
+ }
74
+ getDateIndex(d) {
75
+ const dtm = d;
76
+ return parseInt(dtm.getFullYear().toString().substring(2) + this.leadZero(dtm.getMonth() + 1) + this.leadZero(dtm.getDate()));
77
+ }
78
+ getHourMinuteIndex(d) {
79
+ const dtm = d;
80
+ return this.leadZero(dtm.getHours()) + this.leadZero(dtm.getMinutes());
81
+ }
82
+ leadZero(i) {
83
+ if (i > 9)
84
+ return i + "";
85
+ return "0" + i;
86
+ }
87
+ async precheck(req) {
88
+ if (!req.headers)
89
+ return { error: { code: 10001, message: "No authorization", httpStatus: 400 } };
90
+ if (!req.headers['authorization'])
91
+ return { error: { code: 10002, message: "No authorization found", httpStatus: 400 } };
92
+ const auth = req.headers['authorization'];
93
+ if (!Array.isArray(req.precheck)) {
94
+ return { error: { code: 10003, message: "No authorization records found", httpStatus: 400 } };
95
+ }
96
+ let authorized = false;
97
+ for (let i of req.precheck) {
98
+ let expectationStr = "";
99
+ expectationStr += JSON.stringify(i);
100
+ expectationStr += JSON.stringify(req.method);
101
+ expectationStr += JSON.stringify(req.data);
102
+ expectationStr += JSON.stringify(req.params);
103
+ const expectation = "sha256 " + crypto_1.default.createHash("sha256").update(expectationStr).digest().toString("hex");
104
+ if (auth === expectation) {
105
+ authorized = true;
106
+ break;
107
+ }
108
+ }
109
+ if (!authorized) {
110
+ console.error("Wrong token: " + auth);
111
+ return { error: { code: 10004, message: "Unauthorized access", httpStatus: 401 } };
112
+ }
113
+ return null;
114
+ }
115
+ async logs(req) {
116
+ const log = await LogService_1.REQ_LOG.request(null);
117
+ return { data: log };
118
+ }
119
+ async netlog(req) {
120
+ const log = await APIService_1.REQ_HTTP_LOG.request();
121
+ return { data: log };
122
+ }
123
+ async metrics(req) {
124
+ const date = new Date();
125
+ const di = this.getDateIndex(date);
126
+ const stat = this.httpRequests.get(di);
127
+ if (!stat)
128
+ return { data: [] };
129
+ let result = [];
130
+ for (let minutes of stat) {
131
+ result.push({
132
+ m: minutes[0],
133
+ ae: this.mapToKeyValue(minutes[1].apiErrors),
134
+ e: this.mapToKeyValue(minutes[1].errors),
135
+ r: this.mapToKeyValue(minutes[1].requests),
136
+ fe: this.mapToKeyValue(minutes[1].fatalErrors),
137
+ });
138
+ }
139
+ return { data: { stat: result, date: +date } };
140
+ }
141
+ mapToKeyValue(map) {
142
+ const res = [];
143
+ for (let i of map)
144
+ res.push({ name: i[0], value: i[1] });
145
+ return res;
146
+ }
147
+ }
148
+ exports.Monitor = Monitor;
@@ -21,6 +21,7 @@ export interface HTTPRequestVO {
21
21
  headers: any;
22
22
  endpoint: string;
23
23
  interceptorResult?: TransferPacketVO<any>;
24
+ precheck?: TransferPacketVO<any> | null;
24
25
  }
25
26
  export interface IError {
26
27
  code: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "badmfck-api-server",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "Simple API http server based on express",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",