badmfck-api-server 3.9.89 → 3.9.93
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.
- package/dist/apiServer/APIService.d.ts +2 -2
- package/dist/apiServer/APIService.js +2 -2
- package/dist/apiServer/BaseEndpoint.d.ts +2 -0
- package/dist/apiServer/DBService.js +2 -0
- package/dist/apiServer/LocalRequest.js +0 -26
- package/dist/apiServer/external/ExternalService.js +0 -24
- package/dist/apiServer/helper/Validator.js +14 -0
- package/dist/apiServer/http/HReq.d.ts +0 -0
- package/dist/apiServer/http/HReq.js +1 -0
- package/dist/apiServer/http/HTTP.d.ts +97 -0
- package/dist/apiServer/http/HTTP.js +356 -0
- package/dist/apiServer/monitor/Monitor.js +2 -34
- package/dist/apiServer/structures/Interfaces.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/package.json +3 -2
|
@@ -23,7 +23,7 @@ export interface APIServiceNetworkLogItem {
|
|
|
23
23
|
error?: string | null;
|
|
24
24
|
}
|
|
25
25
|
export interface IInterceptor<T> {
|
|
26
|
-
intercept(req: HTTPRequestVO<any
|
|
26
|
+
intercept(req: HTTPRequestVO<any>, internalCallParams: any): Promise<T | IError>;
|
|
27
27
|
}
|
|
28
28
|
export interface APIServiceOptions<TInterceptor = unknown> {
|
|
29
29
|
port: number;
|
|
@@ -58,7 +58,7 @@ export declare const REQ_INTERNAL_CALL: Req<HTTPRequestVO, IError | any>;
|
|
|
58
58
|
export declare const REQ_MONITOR_USERS: Req<void, IMonitorUser[]>;
|
|
59
59
|
export declare const REQ_DOC_USERS: Req<void, IMonitorUser[]>;
|
|
60
60
|
export declare function Initializer(services: IBaseService[]): Promise<void>;
|
|
61
|
-
export declare class APIService extends BaseService {
|
|
61
|
+
export declare class APIService<TInterceptorResult = any, TInternalCallParams = any> extends BaseService {
|
|
62
62
|
private version;
|
|
63
63
|
private options;
|
|
64
64
|
private monitor;
|
|
@@ -92,7 +92,7 @@ async function Initializer(services) {
|
|
|
92
92
|
}
|
|
93
93
|
exports.Initializer = Initializer;
|
|
94
94
|
class APIService extends BaseService_1.BaseService {
|
|
95
|
-
version = "3.9.
|
|
95
|
+
version = "3.9.94";
|
|
96
96
|
options;
|
|
97
97
|
monitor = null;
|
|
98
98
|
started = new Date();
|
|
@@ -273,7 +273,7 @@ class APIService extends BaseService_1.BaseService {
|
|
|
273
273
|
if (!ignoreInterceptor) {
|
|
274
274
|
let interceptorResult;
|
|
275
275
|
if (this.options.interceptor) {
|
|
276
|
-
interceptorResult = await this.options.interceptor.intercept(httpRequest);
|
|
276
|
+
interceptorResult = await this.options.interceptor.intercept(httpRequest, j.internalCallParams);
|
|
277
277
|
if (DefaultErrors_1.ErrorUtils.isError(interceptorResult) && !allowInterceptorError) {
|
|
278
278
|
this.sendResponse(req.get("Referer") ?? "", res, interceptorResult, tme, ep, httpRequest);
|
|
279
279
|
return;
|
|
@@ -11,6 +11,7 @@ type HTTPMethod = typeof httpMethods[number];
|
|
|
11
11
|
export type IHandler = Record<HTTPMethod, ((req: HTTPRequestVO) => Promise<TransferPacketVO<any>>) | {
|
|
12
12
|
controller: (req: HTTPRequestVO) => Promise<TransferPacketVO<any>>;
|
|
13
13
|
ignoreInterceptor?: boolean;
|
|
14
|
+
internalCallParams?: any;
|
|
14
15
|
allowInterceptorError?: boolean;
|
|
15
16
|
validationModel?: any;
|
|
16
17
|
resultModel?: any;
|
|
@@ -24,6 +25,7 @@ export interface IEndpointHandler {
|
|
|
24
25
|
endpoint: string;
|
|
25
26
|
handler?: ((req: HTTPRequestVO) => Promise<TransferPacketVO<any>>) | IHandler;
|
|
26
27
|
ignoreInterceptor?: boolean;
|
|
28
|
+
internalCallParams?: any;
|
|
27
29
|
ignoreInDocumentation?: boolean;
|
|
28
30
|
ignoreHttpLogging?: boolean;
|
|
29
31
|
independed?: boolean;
|
|
@@ -204,6 +204,8 @@ class DBService extends BaseService_1.BaseService {
|
|
|
204
204
|
};
|
|
205
205
|
};
|
|
206
206
|
exports.REQ_DB.listener = async (req) => {
|
|
207
|
+
if (req.fields)
|
|
208
|
+
req.fields = JSON.parse(JSON.stringify(req.fields));
|
|
207
209
|
const executionStartTime = Date.now();
|
|
208
210
|
if (!req.dbid && DBService.allInstances.length === 1 && DBService.allInstances[0].adapter) {
|
|
209
211
|
const result = await DBService.allInstances[0].adapter.query(req);
|
|
@@ -1,32 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.LocalRequest = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
4
|
const LocalRequest = async (ep, data, method, headers) => {
|
|
9
|
-
let m = "get";
|
|
10
|
-
if (method)
|
|
11
|
-
m = method.toLowerCase();
|
|
12
|
-
if (data && !method)
|
|
13
|
-
m = "post";
|
|
14
|
-
if (ep.startsWith("/"))
|
|
15
|
-
ep = ep.substring(1);
|
|
16
|
-
let url = "http://localhost:8091/" + ep;
|
|
17
|
-
if (ep.toLowerCase().startsWith("http"))
|
|
18
|
-
url = ep;
|
|
19
|
-
let resp = null;
|
|
20
|
-
console.log("open url: ", url);
|
|
21
|
-
try {
|
|
22
|
-
if (m === "get")
|
|
23
|
-
resp = await axios_1.default.get(url, { headers: headers });
|
|
24
|
-
if (m === "post")
|
|
25
|
-
resp = await axios_1.default.post(url, data, { headers: headers });
|
|
26
|
-
}
|
|
27
|
-
catch (e) {
|
|
28
|
-
console.log("ERROR! cant open url", e);
|
|
29
|
-
}
|
|
30
|
-
console.log(resp);
|
|
31
5
|
};
|
|
32
6
|
exports.LocalRequest = LocalRequest;
|
|
@@ -33,7 +33,6 @@ 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 axios_1 = __importDefault(require("axios"));
|
|
37
36
|
exports.REQ_EXTERNAL_CALL = new badmfck_signal_1.Req(undefined, "REQ_EXTERNAL_CALL");
|
|
38
37
|
exports.REQ_XT = new badmfck_signal_1.Req(undefined, "REQ_XT");
|
|
39
38
|
exports.REQ_XT.listener = async ({ id, req }) => {
|
|
@@ -90,31 +89,8 @@ class ExternalService extends BaseService_1.BaseService {
|
|
|
90
89
|
if (this.options.url.endsWith("/"))
|
|
91
90
|
this.options.url = this.options.url.substring(0, this.options.url.length - 1);
|
|
92
91
|
try {
|
|
93
|
-
const resp = await axios_1.default.post(this.options.url + "/external/call/" + this.options.id, encrypted.data, {
|
|
94
|
-
headers: {
|
|
95
|
-
"authorization": encrypted.authorization,
|
|
96
|
-
"Content-Type": "application/json",
|
|
97
|
-
"Accept": "application/json"
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
const data = resp.data;
|
|
101
|
-
if (typeof data === "object" && "error" in data)
|
|
102
|
-
return data.error;
|
|
103
|
-
if (typeof data === "object" && "data" in data)
|
|
104
|
-
return data.data;
|
|
105
|
-
return data;
|
|
106
92
|
}
|
|
107
93
|
catch (e) {
|
|
108
|
-
const err = e;
|
|
109
|
-
if (err.response && err.response.data)
|
|
110
|
-
console.log("ExternalService.requestExternalCall", err.response.data);
|
|
111
|
-
else
|
|
112
|
-
console.error("ExternalService.requestExternalCall", err.cause);
|
|
113
|
-
if (err.response) {
|
|
114
|
-
if (err.response.data && typeof err.response.data === "object" && "error" in err.response.data)
|
|
115
|
-
return err.response.data.error;
|
|
116
|
-
}
|
|
117
|
-
return { ...DefaultErrors_1.default.BAD_REQUEST, details: err.cause?.message };
|
|
118
94
|
}
|
|
119
95
|
}
|
|
120
96
|
async onExternalCall(req) {
|
|
@@ -153,6 +153,20 @@ class Validator {
|
|
|
153
153
|
else
|
|
154
154
|
object[i] = num;
|
|
155
155
|
}
|
|
156
|
+
if (typeof structure[i] === "boolean") {
|
|
157
|
+
if (typeof object[i] === "number") {
|
|
158
|
+
if (object[i] === 1)
|
|
159
|
+
object[i] = true;
|
|
160
|
+
if (object[i] === 0)
|
|
161
|
+
object[i] = false;
|
|
162
|
+
}
|
|
163
|
+
if (typeof object[i] === "string") {
|
|
164
|
+
if (object[i].trim().toLowerCase() === "true")
|
|
165
|
+
object[i] = true;
|
|
166
|
+
if (object[i].trim().toLowerCase() === "false")
|
|
167
|
+
object[i] = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
156
170
|
if (typeof object[i] !== typeof structure[i])
|
|
157
171
|
errors.push("wrong datatype for field '" + i + "' expected " + typeof structure[i] + " got " + typeof object[i]);
|
|
158
172
|
if (typeof structure[i] === "object" || typeof structure[i] === "function") {
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Dispatcher, Agent } from "undici";
|
|
2
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
3
|
+
export type THttpOk<T> = {
|
|
4
|
+
ok: true;
|
|
5
|
+
status: number;
|
|
6
|
+
data: T;
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
};
|
|
9
|
+
export type THttpErr<E = any> = {
|
|
10
|
+
ok: false;
|
|
11
|
+
status?: number;
|
|
12
|
+
error: E;
|
|
13
|
+
details?: any;
|
|
14
|
+
};
|
|
15
|
+
export type THttpResult<T, E = any> = THttpOk<T> | THttpErr<E>;
|
|
16
|
+
export type TRetryPolicy = {
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
maxAttempts?: number;
|
|
19
|
+
baseDelayMs?: number;
|
|
20
|
+
maxDelayMs?: number;
|
|
21
|
+
retryOnStatuses?: number[];
|
|
22
|
+
retryOnNetworkErrors?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type THttpConfig = {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
dispatcher?: Dispatcher;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
retry?: TRetryPolicy;
|
|
30
|
+
onRequest?: (ctx: {
|
|
31
|
+
method: HttpMethod;
|
|
32
|
+
url: string;
|
|
33
|
+
attempt: number;
|
|
34
|
+
}) => void;
|
|
35
|
+
onResponse?: (ctx: {
|
|
36
|
+
method: HttpMethod;
|
|
37
|
+
url: string;
|
|
38
|
+
attempt: number;
|
|
39
|
+
status: number;
|
|
40
|
+
ms: number;
|
|
41
|
+
}) => void;
|
|
42
|
+
onError?: (ctx: {
|
|
43
|
+
method: HttpMethod;
|
|
44
|
+
url: string;
|
|
45
|
+
attempt: number;
|
|
46
|
+
ms: number;
|
|
47
|
+
err: unknown;
|
|
48
|
+
}) => void;
|
|
49
|
+
};
|
|
50
|
+
export type TRequestOptions = {
|
|
51
|
+
method: HttpMethod;
|
|
52
|
+
url: string;
|
|
53
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
54
|
+
headers?: Record<string, string>;
|
|
55
|
+
body?: any;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
dispatcher?: Dispatcher;
|
|
58
|
+
responseType?: "json" | "text" | "raw";
|
|
59
|
+
throwOnJsonParseError?: boolean;
|
|
60
|
+
retry?: TRetryPolicy;
|
|
61
|
+
allowBody?: boolean;
|
|
62
|
+
};
|
|
63
|
+
export declare class Http {
|
|
64
|
+
private static _inited;
|
|
65
|
+
private static config;
|
|
66
|
+
private static _sharedAgent;
|
|
67
|
+
private static _dcEnabled;
|
|
68
|
+
private static _dcConnectedChannel;
|
|
69
|
+
private static _dcListener;
|
|
70
|
+
private static _activeConnectionsTotal;
|
|
71
|
+
private static _activeConnectionsByHost;
|
|
72
|
+
private static _countedSockets;
|
|
73
|
+
private static _socketHostKey;
|
|
74
|
+
private static ensureInit;
|
|
75
|
+
static configure(partial: Partial<THttpConfig>): void;
|
|
76
|
+
static createAgent(opts?: ConstructorParameters<typeof Agent>[0]): Agent;
|
|
77
|
+
static closeSharedAgent(): void;
|
|
78
|
+
static basicAuth(username: string, password: string): string;
|
|
79
|
+
static bearer(token: string): string;
|
|
80
|
+
static enableConnectionCounterByHost(): void;
|
|
81
|
+
static disableConnectionCounterByHost(options?: {
|
|
82
|
+
reset?: boolean;
|
|
83
|
+
}): void;
|
|
84
|
+
private static _makeHostKey;
|
|
85
|
+
static getActiveConnectionsTotal(): number;
|
|
86
|
+
static getActiveConnectionsByHost(): Map<string, number>;
|
|
87
|
+
static getActiveConnectionsSnapshot(): {
|
|
88
|
+
total: number;
|
|
89
|
+
byHost: Record<string, number>;
|
|
90
|
+
};
|
|
91
|
+
static request<T = any, E = any>(opts: TRequestOptions): Promise<THttpResult<T, E>>;
|
|
92
|
+
static get<T = any, E = any>(url: string, opts?: Omit<TRequestOptions, "method" | "url">): Promise<THttpResult<T, E>>;
|
|
93
|
+
static post<T = any, E = any>(url: string, body?: any, opts?: Omit<TRequestOptions, "method" | "url" | "body">): Promise<THttpResult<T, E>>;
|
|
94
|
+
static put<T = any, E = any>(url: string, body?: any, opts?: Omit<TRequestOptions, "method" | "url" | "body">): Promise<THttpResult<T, E>>;
|
|
95
|
+
static patch<T = any, E = any>(url: string, body?: any, opts?: Omit<TRequestOptions, "method" | "url" | "body">): Promise<THttpResult<T, E>>;
|
|
96
|
+
static delete<T = any, E = any>(url: string, opts?: Omit<TRequestOptions, "method" | "url">): Promise<THttpResult<T, E>>;
|
|
97
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
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.Http = void 0;
|
|
7
|
+
const undici_1 = require("undici");
|
|
8
|
+
const node_diagnostics_channel_1 = __importDefault(require("node:diagnostics_channel"));
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
11
|
+
}
|
|
12
|
+
function clamp(n, min, max) {
|
|
13
|
+
return Math.max(min, Math.min(max, n));
|
|
14
|
+
}
|
|
15
|
+
function buildQuery(query) {
|
|
16
|
+
if (!query)
|
|
17
|
+
return "";
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
for (const [k, v] of Object.entries(query)) {
|
|
20
|
+
if (v === null || v === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
params.set(k, String(v));
|
|
23
|
+
}
|
|
24
|
+
const s = params.toString();
|
|
25
|
+
return s ? `?${s}` : "";
|
|
26
|
+
}
|
|
27
|
+
function mergeHeaders(a, b) {
|
|
28
|
+
return { ...(a || {}), ...(b || {}) };
|
|
29
|
+
}
|
|
30
|
+
function normalizeHeaders(headers) {
|
|
31
|
+
const out = {};
|
|
32
|
+
if (!headers || typeof headers !== "object")
|
|
33
|
+
return out;
|
|
34
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
35
|
+
if (Array.isArray(v))
|
|
36
|
+
out[k.toLowerCase()] = v.join(", ");
|
|
37
|
+
else if (v !== undefined && v !== null)
|
|
38
|
+
out[k.toLowerCase()] = String(v);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function isRetryableStatus(status, retryOnStatuses) {
|
|
43
|
+
return retryOnStatuses.includes(status);
|
|
44
|
+
}
|
|
45
|
+
function isLikelyNetworkError(err) {
|
|
46
|
+
if (!err || typeof err !== "object")
|
|
47
|
+
return false;
|
|
48
|
+
const anyErr = err;
|
|
49
|
+
if (anyErr?.name === "AbortError")
|
|
50
|
+
return true;
|
|
51
|
+
const code = anyErr?.code;
|
|
52
|
+
const networkCodes = new Set(["ECONNRESET", "ETIMEDOUT", "ECONNREFUSED", "EAI_AGAIN", "ENOTFOUND", "EPIPE"]);
|
|
53
|
+
if (code && networkCodes.has(code))
|
|
54
|
+
return true;
|
|
55
|
+
if (err instanceof undici_1.errors.ConnectTimeoutError)
|
|
56
|
+
return true;
|
|
57
|
+
if (err instanceof undici_1.errors.HeadersTimeoutError)
|
|
58
|
+
return true;
|
|
59
|
+
if (err instanceof undici_1.errors.BodyTimeoutError)
|
|
60
|
+
return true;
|
|
61
|
+
if (err instanceof undici_1.errors.SocketError)
|
|
62
|
+
return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
class Http {
|
|
66
|
+
static _inited = false;
|
|
67
|
+
static config = {
|
|
68
|
+
baseUrl: "",
|
|
69
|
+
timeoutMs: 5000,
|
|
70
|
+
headers: {
|
|
71
|
+
accept: "application/json",
|
|
72
|
+
},
|
|
73
|
+
retry: {
|
|
74
|
+
enabled: true,
|
|
75
|
+
maxAttempts: 2,
|
|
76
|
+
baseDelayMs: 200,
|
|
77
|
+
maxDelayMs: 1500,
|
|
78
|
+
retryOnStatuses: [429, 502, 503, 504],
|
|
79
|
+
retryOnNetworkErrors: true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
static _sharedAgent = null;
|
|
83
|
+
static _dcEnabled = false;
|
|
84
|
+
static _dcConnectedChannel = null;
|
|
85
|
+
static _dcListener = null;
|
|
86
|
+
static _activeConnectionsTotal = 0;
|
|
87
|
+
static _activeConnectionsByHost = new Map();
|
|
88
|
+
static _countedSockets = new WeakSet();
|
|
89
|
+
static _socketHostKey = new WeakMap();
|
|
90
|
+
static ensureInit() {
|
|
91
|
+
if (this._inited)
|
|
92
|
+
return;
|
|
93
|
+
this._inited = true;
|
|
94
|
+
this.enableConnectionCounterByHost();
|
|
95
|
+
if (!this.config.dispatcher) {
|
|
96
|
+
this._sharedAgent = this.createAgent({
|
|
97
|
+
connections: 100,
|
|
98
|
+
keepAliveTimeout: 60_000,
|
|
99
|
+
keepAliveMaxTimeout: 60_000,
|
|
100
|
+
});
|
|
101
|
+
this.config.dispatcher = this._sharedAgent;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
static configure(partial) {
|
|
105
|
+
this.config = {
|
|
106
|
+
...this.config,
|
|
107
|
+
...partial,
|
|
108
|
+
headers: mergeHeaders(this.config.headers, partial.headers),
|
|
109
|
+
retry: { ...(this.config.retry || {}), ...(partial.retry || {}) },
|
|
110
|
+
};
|
|
111
|
+
if (partial.dispatcher && this._sharedAgent) {
|
|
112
|
+
try {
|
|
113
|
+
this._sharedAgent.close();
|
|
114
|
+
}
|
|
115
|
+
catch { }
|
|
116
|
+
this._sharedAgent = null;
|
|
117
|
+
}
|
|
118
|
+
this.ensureInit();
|
|
119
|
+
}
|
|
120
|
+
static createAgent(opts) {
|
|
121
|
+
return new undici_1.Agent({
|
|
122
|
+
keepAliveTimeout: 60_000,
|
|
123
|
+
keepAliveMaxTimeout: 60_000,
|
|
124
|
+
...opts,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
static closeSharedAgent() {
|
|
128
|
+
if (!this._sharedAgent)
|
|
129
|
+
return;
|
|
130
|
+
try {
|
|
131
|
+
this._sharedAgent.close();
|
|
132
|
+
}
|
|
133
|
+
catch { }
|
|
134
|
+
this._sharedAgent = null;
|
|
135
|
+
if (this.config.dispatcher === this._sharedAgent) {
|
|
136
|
+
this.config.dispatcher = undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
static basicAuth(username, password) {
|
|
140
|
+
const token = Buffer.from(`${username}:${password}`).toString("base64");
|
|
141
|
+
return `Basic ${token}`;
|
|
142
|
+
}
|
|
143
|
+
static bearer(token) {
|
|
144
|
+
return `Bearer ${token}`;
|
|
145
|
+
}
|
|
146
|
+
static enableConnectionCounterByHost() {
|
|
147
|
+
if (this._dcEnabled)
|
|
148
|
+
return;
|
|
149
|
+
this._dcEnabled = true;
|
|
150
|
+
const ch = node_diagnostics_channel_1.default.channel("undici:client:connected");
|
|
151
|
+
const listener = (message) => {
|
|
152
|
+
const msg = message;
|
|
153
|
+
const socket = msg?.socket;
|
|
154
|
+
if (!socket)
|
|
155
|
+
return;
|
|
156
|
+
if (this._countedSockets.has(socket))
|
|
157
|
+
return;
|
|
158
|
+
this._countedSockets.add(socket);
|
|
159
|
+
const key = this._makeHostKey(msg?.connectParams, socket);
|
|
160
|
+
this._socketHostKey.set(socket, key);
|
|
161
|
+
this._activeConnectionsTotal += 1;
|
|
162
|
+
this._activeConnectionsByHost.set(key, (this._activeConnectionsByHost.get(key) ?? 0) + 1);
|
|
163
|
+
const onClose = () => {
|
|
164
|
+
this._activeConnectionsTotal = Math.max(0, this._activeConnectionsTotal - 1);
|
|
165
|
+
const k = this._socketHostKey.get(socket) ?? "unknown://unknown:0";
|
|
166
|
+
const prev = this._activeConnectionsByHost.get(k) ?? 0;
|
|
167
|
+
const next = Math.max(0, prev - 1);
|
|
168
|
+
if (next === 0)
|
|
169
|
+
this._activeConnectionsByHost.delete(k);
|
|
170
|
+
else
|
|
171
|
+
this._activeConnectionsByHost.set(k, next);
|
|
172
|
+
socket.off("close", onClose);
|
|
173
|
+
};
|
|
174
|
+
socket.on("close", onClose);
|
|
175
|
+
};
|
|
176
|
+
ch.subscribe(listener);
|
|
177
|
+
this._dcConnectedChannel = ch;
|
|
178
|
+
this._dcListener = listener;
|
|
179
|
+
}
|
|
180
|
+
static disableConnectionCounterByHost(options) {
|
|
181
|
+
if (!this._dcEnabled)
|
|
182
|
+
return;
|
|
183
|
+
const ch = this._dcConnectedChannel;
|
|
184
|
+
const listener = this._dcListener;
|
|
185
|
+
if (ch && listener) {
|
|
186
|
+
ch.unsubscribe(listener);
|
|
187
|
+
}
|
|
188
|
+
this._dcConnectedChannel = null;
|
|
189
|
+
this._dcListener = null;
|
|
190
|
+
this._dcEnabled = false;
|
|
191
|
+
if (options?.reset) {
|
|
192
|
+
this._activeConnectionsTotal = 0;
|
|
193
|
+
this._activeConnectionsByHost.clear();
|
|
194
|
+
this._countedSockets = new WeakSet();
|
|
195
|
+
this._socketHostKey = new WeakMap();
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
static _makeHostKey(connectParams, socket) {
|
|
199
|
+
const protocolRaw = connectParams?.protocol || "unknown:";
|
|
200
|
+
const protocol = protocolRaw.replace(/\/+$/g, "");
|
|
201
|
+
const hostname = connectParams?.hostname || connectParams?.host;
|
|
202
|
+
let port = connectParams?.port !== undefined && connectParams?.port !== null ? String(connectParams.port) : undefined;
|
|
203
|
+
if (!port) {
|
|
204
|
+
if (protocol === "https:")
|
|
205
|
+
port = "443";
|
|
206
|
+
else if (protocol === "http:")
|
|
207
|
+
port = "80";
|
|
208
|
+
else
|
|
209
|
+
port = "?";
|
|
210
|
+
}
|
|
211
|
+
if (hostname)
|
|
212
|
+
return `${protocol}//${hostname}:${port}`;
|
|
213
|
+
const addr = socket?.remoteAddress ?? "unknown";
|
|
214
|
+
const p = socket?.remotePort ?? "?";
|
|
215
|
+
return `${protocol}//${addr}:${p}`;
|
|
216
|
+
}
|
|
217
|
+
static getActiveConnectionsTotal() {
|
|
218
|
+
return this._activeConnectionsTotal;
|
|
219
|
+
}
|
|
220
|
+
static getActiveConnectionsByHost() {
|
|
221
|
+
return new Map(this._activeConnectionsByHost);
|
|
222
|
+
}
|
|
223
|
+
static getActiveConnectionsSnapshot() {
|
|
224
|
+
return {
|
|
225
|
+
total: this._activeConnectionsTotal,
|
|
226
|
+
byHost: Object.fromEntries(this._activeConnectionsByHost.entries()),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
static async request(opts) {
|
|
230
|
+
this.ensureInit();
|
|
231
|
+
const cfg = this.config;
|
|
232
|
+
const retryCfg = {
|
|
233
|
+
enabled: opts.retry?.enabled ?? cfg.retry?.enabled ?? true,
|
|
234
|
+
maxAttempts: opts.retry?.maxAttempts ?? cfg.retry?.maxAttempts ?? 2,
|
|
235
|
+
baseDelayMs: opts.retry?.baseDelayMs ?? cfg.retry?.baseDelayMs ?? 200,
|
|
236
|
+
maxDelayMs: opts.retry?.maxDelayMs ?? cfg.retry?.maxDelayMs ?? 1500,
|
|
237
|
+
retryOnStatuses: opts.retry?.retryOnStatuses ?? cfg.retry?.retryOnStatuses ?? [429, 502, 503, 504],
|
|
238
|
+
retryOnNetworkErrors: opts.retry?.retryOnNetworkErrors ?? cfg.retry?.retryOnNetworkErrors ?? true,
|
|
239
|
+
};
|
|
240
|
+
const timeoutMs = opts.timeoutMs ?? cfg.timeoutMs ?? 5000;
|
|
241
|
+
const dispatcher = opts.dispatcher ?? cfg.dispatcher;
|
|
242
|
+
const baseUrl = cfg.baseUrl || "";
|
|
243
|
+
const isAbsolute = /^https?:\/\//i.test(opts.url);
|
|
244
|
+
const query = buildQuery(opts.query);
|
|
245
|
+
const url = (isAbsolute ? opts.url : baseUrl + opts.url) + query;
|
|
246
|
+
const headers = mergeHeaders(cfg.headers, opts.headers);
|
|
247
|
+
let body = undefined;
|
|
248
|
+
const hasBody = opts.body !== undefined && opts.body !== null;
|
|
249
|
+
const allowBody = opts.allowBody ?? (opts.method !== "GET" && opts.method !== "DELETE");
|
|
250
|
+
if (hasBody && allowBody) {
|
|
251
|
+
const ct = (headers["content-type"] || headers["Content-Type"] || "").toLowerCase();
|
|
252
|
+
if (!ct)
|
|
253
|
+
headers["content-type"] = "application/json";
|
|
254
|
+
const ct2 = (headers["content-type"] || headers["Content-Type"] || "").toLowerCase();
|
|
255
|
+
if (ct2.includes("application/json") && typeof opts.body !== "string" && !(opts.body instanceof Buffer)) {
|
|
256
|
+
body = JSON.stringify(opts.body);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
body = opts.body;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const responseType = opts.responseType ?? "json";
|
|
263
|
+
const throwOnJsonParseError = opts.throwOnJsonParseError ?? false;
|
|
264
|
+
for (let attempt = 1; attempt <= retryCfg.maxAttempts; attempt++) {
|
|
265
|
+
const started = Date.now();
|
|
266
|
+
cfg.onRequest?.({ method: opts.method, url, attempt });
|
|
267
|
+
const controller = new AbortController();
|
|
268
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
269
|
+
try {
|
|
270
|
+
const res = await (0, undici_1.request)(url, {
|
|
271
|
+
method: opts.method,
|
|
272
|
+
headers,
|
|
273
|
+
body,
|
|
274
|
+
signal: controller.signal,
|
|
275
|
+
dispatcher,
|
|
276
|
+
});
|
|
277
|
+
const ms = Date.now() - started;
|
|
278
|
+
const status = res.statusCode;
|
|
279
|
+
cfg.onResponse?.({ method: opts.method, url, attempt, status, ms });
|
|
280
|
+
const hdrs = normalizeHeaders(res.headers);
|
|
281
|
+
let parsed = undefined;
|
|
282
|
+
let rawText;
|
|
283
|
+
if (responseType === "raw") {
|
|
284
|
+
await res.body.dump();
|
|
285
|
+
parsed = undefined;
|
|
286
|
+
}
|
|
287
|
+
else if (responseType === "text") {
|
|
288
|
+
rawText = await res.body.text();
|
|
289
|
+
parsed = rawText;
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
rawText = await res.body.text();
|
|
293
|
+
if (rawText.length === 0) {
|
|
294
|
+
parsed = null;
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
try {
|
|
298
|
+
parsed = JSON.parse(rawText);
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
if (throwOnJsonParseError)
|
|
302
|
+
throw e;
|
|
303
|
+
parsed = rawText;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (status >= 200 && status < 300) {
|
|
308
|
+
return { ok: true, status, data: parsed, headers: hdrs };
|
|
309
|
+
}
|
|
310
|
+
const retryable = retryCfg.enabled &&
|
|
311
|
+
isRetryableStatus(status, retryCfg.retryOnStatuses) &&
|
|
312
|
+
attempt < retryCfg.maxAttempts;
|
|
313
|
+
if (retryable) {
|
|
314
|
+
const backoff = clamp(retryCfg.baseDelayMs * Math.pow(2, attempt - 1), retryCfg.baseDelayMs, retryCfg.maxDelayMs);
|
|
315
|
+
await sleep(backoff);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
return { ok: false, status, error: parsed, details: { headers: hdrs } };
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
const ms = Date.now() - started;
|
|
322
|
+
cfg.onError?.({ method: opts.method, url, attempt, ms, err });
|
|
323
|
+
const retryable = retryCfg.enabled &&
|
|
324
|
+
retryCfg.retryOnNetworkErrors &&
|
|
325
|
+
isLikelyNetworkError(err) &&
|
|
326
|
+
attempt < retryCfg.maxAttempts;
|
|
327
|
+
if (retryable) {
|
|
328
|
+
const backoff = clamp(retryCfg.baseDelayMs * Math.pow(2, attempt - 1), retryCfg.baseDelayMs, retryCfg.maxDelayMs);
|
|
329
|
+
await sleep(backoff);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
return { ok: false, error: err, details: { network: true } };
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
clearTimeout(t);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { ok: false, error: "UNKNOWN" };
|
|
339
|
+
}
|
|
340
|
+
static get(url, opts) {
|
|
341
|
+
return this.request({ ...(opts || {}), method: "GET", url });
|
|
342
|
+
}
|
|
343
|
+
static post(url, body, opts) {
|
|
344
|
+
return this.request({ ...(opts || {}), method: "POST", url, body });
|
|
345
|
+
}
|
|
346
|
+
static put(url, body, opts) {
|
|
347
|
+
return this.request({ ...(opts || {}), method: "PUT", url, body });
|
|
348
|
+
}
|
|
349
|
+
static patch(url, body, opts) {
|
|
350
|
+
return this.request({ ...(opts || {}), method: "PATCH", url, body });
|
|
351
|
+
}
|
|
352
|
+
static delete(url, opts) {
|
|
353
|
+
return this.request({ ...(opts || {}), method: "DELETE", url });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
exports.Http = Http;
|
|
@@ -13,7 +13,7 @@ const DefaultErrors_1 = __importDefault(require("../structures/DefaultErrors"));
|
|
|
13
13
|
const MonitorService_1 = require("../MonitorService");
|
|
14
14
|
const fs_1 = __importDefault(require("fs"));
|
|
15
15
|
const path_1 = __importDefault(require("path"));
|
|
16
|
-
const
|
|
16
|
+
const Http_1 = require("../http/Http");
|
|
17
17
|
exports.S_MONITOR_REGISTRATE_ACTION = new badmfck_signal_1.Signal();
|
|
18
18
|
const logMap = {
|
|
19
19
|
ALL: 10,
|
|
@@ -54,43 +54,11 @@ class Monitor extends BaseEndpoint_1.BaseEndpoint {
|
|
|
54
54
|
this.lastTimeCacheChecked = +new Date();
|
|
55
55
|
let response = null;
|
|
56
56
|
try {
|
|
57
|
-
response = await
|
|
57
|
+
response = await Http_1.Http.get("https://igorbloom.com/dist/monitor.html");
|
|
58
58
|
}
|
|
59
59
|
catch (e) {
|
|
60
60
|
(0, LogService_1.logError)("Error loading monitor.html from remote server", e);
|
|
61
61
|
}
|
|
62
|
-
if (response && response.status === 200 && response.data && response.data.length > 0) {
|
|
63
|
-
if (typeof response.data !== "string")
|
|
64
|
-
(0, LogService_1.logError)("Monitor HTML file is not a string", response.data);
|
|
65
|
-
else {
|
|
66
|
-
const hash = crypto_1.default.createHash("sha256").update(response.data).digest("hex");
|
|
67
|
-
const oldHash = crypto_1.default.createHash("sha256").update(this.indexHtmlFile).digest("hex");
|
|
68
|
-
if (hash !== oldHash) {
|
|
69
|
-
if (response.data.length < 1000) {
|
|
70
|
-
(0, LogService_1.logError)("Monitor HTML file is too small, probably not loaded correctly", response.data);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
if (response.data.substring(0, 24).toLowerCase().indexOf("<!doctype html>") === -1) {
|
|
74
|
-
(0, LogService_1.logError)("Monitor HTML file is not a valid HTML file, <!doctype html> tag not found");
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
try {
|
|
78
|
-
fs_1.default.writeFileSync(path_1.default.resolve(__dirname, "index.html"), response.data, "utf-8");
|
|
79
|
-
this.fileParsed = false;
|
|
80
|
-
this.indexHtmlFile = response.data;
|
|
81
|
-
(0, LogService_1.logInfo)("Monitor HTML file updated from remote server");
|
|
82
|
-
}
|
|
83
|
-
catch (e) {
|
|
84
|
-
(0, LogService_1.logError)("Error writing monitor HTML file to local disk", e);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
(0, LogService_1.logInfo)("Remote monitor HTML file has the same hash");
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
62
|
}
|
|
95
63
|
return this.indexHtmlFile;
|
|
96
64
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,4 +13,5 @@ import { YYYYMMDDHH } from "./apiServer/helper/YYYYMMDDHH";
|
|
|
13
13
|
import { TimeframeService } from "./apiServer/TimeframeService";
|
|
14
14
|
import { JSONStableStringify } from "./apiServer/helper/JSONStableStringify";
|
|
15
15
|
import { ZipUtils } from "./apiServer/helper/ZipUtils";
|
|
16
|
-
|
|
16
|
+
import { Http } from "./apiServer/http/Http";
|
|
17
|
+
export { Http, ZipUtils, UID, YYYYMMDDHH, JSONStableStringify, APIService, Initializer, LocalRequest, ValidationModel, MysqlService, TimeframeService, Validator, LogService, DataProvider, ErrorUtils, ExternalService, DBService, S_MONITOR_REGISTRATE_ACTION };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.S_MONITOR_REGISTRATE_ACTION = exports.DBService = exports.ExternalService = exports.ErrorUtils = exports.DataProvider = exports.LogService = exports.Validator = exports.TimeframeService = exports.MysqlService = exports.LocalRequest = exports.Initializer = exports.APIService = exports.JSONStableStringify = exports.YYYYMMDDHH = exports.UID = exports.ZipUtils = void 0;
|
|
3
|
+
exports.S_MONITOR_REGISTRATE_ACTION = exports.DBService = exports.ExternalService = exports.ErrorUtils = exports.DataProvider = exports.LogService = exports.Validator = exports.TimeframeService = exports.MysqlService = exports.LocalRequest = exports.Initializer = exports.APIService = exports.JSONStableStringify = exports.YYYYMMDDHH = exports.UID = exports.ZipUtils = exports.Http = void 0;
|
|
4
4
|
const APIService_1 = require("./apiServer/APIService");
|
|
5
5
|
Object.defineProperty(exports, "APIService", { enumerable: true, get: function () { return APIService_1.APIService; } });
|
|
6
6
|
Object.defineProperty(exports, "Initializer", { enumerable: true, get: function () { return APIService_1.Initializer; } });
|
|
@@ -32,3 +32,5 @@ const JSONStableStringify_1 = require("./apiServer/helper/JSONStableStringify");
|
|
|
32
32
|
Object.defineProperty(exports, "JSONStableStringify", { enumerable: true, get: function () { return JSONStableStringify_1.JSONStableStringify; } });
|
|
33
33
|
const ZipUtils_1 = require("./apiServer/helper/ZipUtils");
|
|
34
34
|
Object.defineProperty(exports, "ZipUtils", { enumerable: true, get: function () { return ZipUtils_1.ZipUtils; } });
|
|
35
|
+
const Http_1 = require("./apiServer/http/Http");
|
|
36
|
+
Object.defineProperty(exports, "Http", { enumerable: true, get: function () { return Http_1.Http; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "badmfck-api-server",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.93",
|
|
4
4
|
"description": "Simple API http server based on express",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@types/express-fileupload": "^1.5.0",
|
|
21
21
|
"@types/mysql": "^2.15.21",
|
|
22
|
+
"@types/node": "^25.0.3",
|
|
22
23
|
"@types/pg": "^8.15.5",
|
|
23
24
|
"@types/ws": "^8.5.9",
|
|
24
|
-
"axios": "^1.10.0",
|
|
25
25
|
"badmfck-signal": "^1.4.9",
|
|
26
26
|
"cors": "^2.8.5",
|
|
27
27
|
"express": "^4.21.2",
|
|
28
28
|
"express-fileupload": "^1.5.2",
|
|
29
29
|
"mysql2": "^3.14.2",
|
|
30
30
|
"pg": "^8.16.3",
|
|
31
|
+
"undici": "^7.18.0",
|
|
31
32
|
"ws": "^8.18.3"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|