badmfck-api-server 2.4.2 → 2.4.3
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/apiServer/BaseEndpoint.d.ts +1 -0
- package/dist/apiServer/ClusterService.d.ts +16 -0
- package/dist/apiServer/ClusterService.js +50 -0
- package/dist/apiServer/DocumentService.d.ts +42 -0
- package/dist/apiServer/DocumentService.js +182 -0
- package/dist/apiServer/HUBService.d.ts +29 -0
- package/dist/apiServer/HUBService.js +69 -0
- package/dist/apiServer/cluster/HUBConnection.d.ts +39 -0
- package/dist/apiServer/cluster/HUBConnection.js +230 -0
- package/dist/apiServer/structures/DefaultErrors.d.ts +4 -0
- package/dist/apiServer/structures/DefaultErrors.js +4 -0
- package/package.json +6 -5
@@ -12,6 +12,7 @@ export interface IEndpointHandler {
|
|
12
12
|
ignoreInterceptor?: boolean;
|
13
13
|
independed?: boolean;
|
14
14
|
allowInterceptorError?: boolean;
|
15
|
+
httpMethod?: "GET" | "POST" | "PUT" | "DELETE";
|
15
16
|
}
|
16
17
|
export declare class BaseEndpoint implements IBaseEndpoint {
|
17
18
|
private inializedEndpointNames;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { BaseService } from "./BaseService";
|
2
|
+
import { HUBConnection } from "./cluster/HUBConnection";
|
3
|
+
export interface ClusterServiceOptions {
|
4
|
+
id: string;
|
5
|
+
privateKey: string;
|
6
|
+
hubURL: string;
|
7
|
+
hubPublicKey: string;
|
8
|
+
nodes: string[];
|
9
|
+
}
|
10
|
+
export declare class ClusterService extends BaseService {
|
11
|
+
options: ClusterServiceOptions;
|
12
|
+
ws: HUBConnection | null;
|
13
|
+
constructor(opt: ClusterServiceOptions);
|
14
|
+
init(): Promise<void>;
|
15
|
+
connectToHUB(): Promise<void>;
|
16
|
+
}
|
@@ -0,0 +1,50 @@
|
|
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.ClusterService = void 0;
|
7
|
+
const ws_1 = __importDefault(require("ws"));
|
8
|
+
const BaseService_1 = require("./BaseService");
|
9
|
+
const LogService_1 = require("./LogService");
|
10
|
+
const HUBConnection_1 = require("./cluster/HUBConnection");
|
11
|
+
class ClusterService extends BaseService_1.BaseService {
|
12
|
+
options;
|
13
|
+
ws = null;
|
14
|
+
constructor(opt) {
|
15
|
+
super("ClusterService");
|
16
|
+
this.options = opt;
|
17
|
+
if (!opt.nodes.length)
|
18
|
+
throw new Error("ClusterService: No nodes provided");
|
19
|
+
}
|
20
|
+
async init() {
|
21
|
+
(0, LogService_1.logInfo)("ClusterService: initialized");
|
22
|
+
this.connectToHUB();
|
23
|
+
}
|
24
|
+
async connectToHUB() {
|
25
|
+
(0, LogService_1.logInfo)("ClusterService: connecting to HUB");
|
26
|
+
if (this.ws)
|
27
|
+
this.ws.close();
|
28
|
+
this.ws = new HUBConnection_1.HUBConnection({
|
29
|
+
ws: new ws_1.default(this.options.hubURL),
|
30
|
+
privateKey: this.options.privateKey,
|
31
|
+
hubID: this.options.id,
|
32
|
+
onAuthorized: () => {
|
33
|
+
this.ws?.send({
|
34
|
+
method: "auth",
|
35
|
+
data: {
|
36
|
+
id: this.options.id,
|
37
|
+
publicKey: this.options.hubPublicKey
|
38
|
+
},
|
39
|
+
callbackID: 0
|
40
|
+
});
|
41
|
+
},
|
42
|
+
onClose: () => {
|
43
|
+
console.log("ClusterService: connection to HUB closed");
|
44
|
+
this.connectToHUB();
|
45
|
+
},
|
46
|
+
handlers: []
|
47
|
+
});
|
48
|
+
}
|
49
|
+
}
|
50
|
+
exports.ClusterService = ClusterService;
|
@@ -0,0 +1,42 @@
|
|
1
|
+
declare type DocumentGeneratorConfig = {
|
2
|
+
sourcePath: string;
|
3
|
+
apiEndpoint: string;
|
4
|
+
savePath: string;
|
5
|
+
savePathFileName: string;
|
6
|
+
};
|
7
|
+
declare type DocumentGeneratorDocumentation = {
|
8
|
+
[baseEndpointName: string]: {
|
9
|
+
[endPointName: string]: DocumentGeneratorDocumentationEndpoint;
|
10
|
+
};
|
11
|
+
};
|
12
|
+
declare type DocumentGeneratorDocumentationEndpoint = {
|
13
|
+
handler: string;
|
14
|
+
fullPath: string;
|
15
|
+
ignoreInterceptor?: string;
|
16
|
+
description?: string;
|
17
|
+
parameters: DocumentGeneratorDocumentationEndpointParameter[];
|
18
|
+
returns?: Omit<DocumentGeneratorDocumentationEndpointParameter, "optional">;
|
19
|
+
example?: string[];
|
20
|
+
throws?: Omit<DocumentGeneratorDocumentationEndpointParameter, "optional">[];
|
21
|
+
};
|
22
|
+
declare type DocumentGeneratorDocumentationEndpointParameter = {
|
23
|
+
name: string;
|
24
|
+
type: string;
|
25
|
+
description: string;
|
26
|
+
optional?: boolean;
|
27
|
+
};
|
28
|
+
export declare class DocumentGenerator {
|
29
|
+
private documentation;
|
30
|
+
private config;
|
31
|
+
constructor(config: DocumentGeneratorConfig);
|
32
|
+
private getFiles;
|
33
|
+
private getFileContent;
|
34
|
+
private generateDocumentation;
|
35
|
+
private getCommentsByEndpoint;
|
36
|
+
private parseEndPointComments;
|
37
|
+
private tagToEndpoint;
|
38
|
+
private tagModifications;
|
39
|
+
private saveDocumentation;
|
40
|
+
getDocumentation(): DocumentGeneratorDocumentation;
|
41
|
+
}
|
42
|
+
export {};
|
@@ -0,0 +1,182 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
+
if (mod && mod.__esModule) return mod;
|
20
|
+
var result = {};
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
+
__setModuleDefault(result, mod);
|
23
|
+
return result;
|
24
|
+
};
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
+
exports.DocumentGenerator = void 0;
|
27
|
+
const fs = __importStar(require("fs"));
|
28
|
+
const path = __importStar(require("path"));
|
29
|
+
const comment_parser_1 = require("comment-parser");
|
30
|
+
class DocumentGenerator {
|
31
|
+
documentation = {};
|
32
|
+
config;
|
33
|
+
constructor(config) {
|
34
|
+
this.config = config;
|
35
|
+
this.generateDocumentation();
|
36
|
+
}
|
37
|
+
getFiles() {
|
38
|
+
return fs.readdirSync(this.config.sourcePath);
|
39
|
+
}
|
40
|
+
getFileContent(file) {
|
41
|
+
return fs.readFileSync(path.resolve(this.config.sourcePath, file), "utf-8");
|
42
|
+
}
|
43
|
+
generateDocumentation = () => {
|
44
|
+
for (const file of this.getFiles()) {
|
45
|
+
const fileContent = this.getFileContent(file);
|
46
|
+
const baseEndpoint = fileContent.match(/super\(['"](.*?)['"]\)/);
|
47
|
+
if (!baseEndpoint) {
|
48
|
+
console.error(`No base endpoint found in ${file}`);
|
49
|
+
continue;
|
50
|
+
}
|
51
|
+
const nodeEndpoint = this.config.apiEndpoint + "/" + baseEndpoint[1];
|
52
|
+
const regex = /{\s*endpoint:\s*"(.*?)",\s*handler:\s*this\.(.*?)(?:,\s*ignoreInterceptor:\s*(true|false))?\s*}/g;
|
53
|
+
const endpoints = fileContent.matchAll(regex);
|
54
|
+
for (const match of endpoints) {
|
55
|
+
const endpointName = match[1];
|
56
|
+
const handler = match[2];
|
57
|
+
const ignoreInterceptor = match[3];
|
58
|
+
if (!this.documentation.hasOwnProperty(nodeEndpoint)) {
|
59
|
+
this.documentation[nodeEndpoint] = {};
|
60
|
+
}
|
61
|
+
const endpoint = {
|
62
|
+
handler,
|
63
|
+
ignoreInterceptor,
|
64
|
+
fullPath: nodeEndpoint + "/" + endpointName,
|
65
|
+
description: "",
|
66
|
+
parameters: [],
|
67
|
+
};
|
68
|
+
this.documentation[nodeEndpoint][endpointName] = this.parseEndPointComments(this.getCommentsByEndpoint(fileContent, handler), endpoint);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
this.saveDocumentation();
|
72
|
+
console.log("Documentation generated and saved successfully");
|
73
|
+
};
|
74
|
+
getCommentsByEndpoint(fileContent, handler) {
|
75
|
+
let comments = "";
|
76
|
+
const regex = new RegExp(`(async )*${handler}(\\s*)\\(`, "g");
|
77
|
+
const match = fileContent.match(regex);
|
78
|
+
if (match) {
|
79
|
+
const indexHandler = fileContent.indexOf(`${match[0]}`);
|
80
|
+
if (indexHandler != -1) {
|
81
|
+
const indexCommentStart = fileContent.lastIndexOf("/**", indexHandler);
|
82
|
+
if (indexCommentStart != -1) {
|
83
|
+
const indexCommentEnd = fileContent.indexOf("*/", indexCommentStart);
|
84
|
+
if (indexCommentEnd != -1) {
|
85
|
+
const commentsSubstring = fileContent.substring(indexCommentStart, indexCommentEnd + 2);
|
86
|
+
if (commentsSubstring) {
|
87
|
+
const regexToCheckCommentWithMethod = new RegExp(`${commentsSubstring.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([\\s]*?)(async )*${handler}(\\s*)\\(`, "g");
|
88
|
+
const matchToCheckCommentWithMethod = fileContent.match(regexToCheckCommentWithMethod);
|
89
|
+
if (matchToCheckCommentWithMethod) {
|
90
|
+
comments = commentsSubstring;
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
}
|
97
|
+
return comments;
|
98
|
+
}
|
99
|
+
parseEndPointComments(comments, endpoint) {
|
100
|
+
if (comments) {
|
101
|
+
const parsed = (0, comment_parser_1.parse)(comments, { spacing: "preserve" });
|
102
|
+
if (parsed[0]) {
|
103
|
+
const firstParsed = parsed[0];
|
104
|
+
endpoint.description = this.tagModifications(firstParsed.description);
|
105
|
+
firstParsed.tags.forEach((tag) => {
|
106
|
+
endpoint = this.tagToEndpoint(tag, endpoint);
|
107
|
+
});
|
108
|
+
}
|
109
|
+
}
|
110
|
+
return endpoint;
|
111
|
+
}
|
112
|
+
tagToEndpoint(tag, endpoint) {
|
113
|
+
switch (tag.tag) {
|
114
|
+
case "param":
|
115
|
+
endpoint.parameters.push({
|
116
|
+
name: this.tagModifications(tag.name, "name"),
|
117
|
+
type: tag.type,
|
118
|
+
description: this.tagModifications(tag.description),
|
119
|
+
optional: tag.optional,
|
120
|
+
});
|
121
|
+
break;
|
122
|
+
case "description":
|
123
|
+
endpoint.description = this.tagModifications(tag.description);
|
124
|
+
break;
|
125
|
+
case "example":
|
126
|
+
if (!endpoint.example) {
|
127
|
+
endpoint.example = [];
|
128
|
+
}
|
129
|
+
endpoint.example.push(this.tagModifications(tag.description));
|
130
|
+
break;
|
131
|
+
case "returns":
|
132
|
+
endpoint.returns = {
|
133
|
+
name: this.tagModifications(tag.name, "name"),
|
134
|
+
type: tag.type,
|
135
|
+
description: this.tagModifications(tag.description),
|
136
|
+
};
|
137
|
+
break;
|
138
|
+
case "throws":
|
139
|
+
if (!endpoint.throws) {
|
140
|
+
endpoint.throws = [];
|
141
|
+
}
|
142
|
+
endpoint.throws.push({
|
143
|
+
name: this.tagModifications(tag.name, "name"),
|
144
|
+
type: tag.type,
|
145
|
+
description: this.tagModifications(tag.description),
|
146
|
+
});
|
147
|
+
break;
|
148
|
+
}
|
149
|
+
return endpoint;
|
150
|
+
}
|
151
|
+
tagModifications(text, field = "description") {
|
152
|
+
switch (field) {
|
153
|
+
case "description":
|
154
|
+
text = text
|
155
|
+
.trim()
|
156
|
+
.replace(/^(\-.)/, "")
|
157
|
+
.trim();
|
158
|
+
break;
|
159
|
+
case "name":
|
160
|
+
if (text === "-") {
|
161
|
+
text = "";
|
162
|
+
}
|
163
|
+
break;
|
164
|
+
}
|
165
|
+
return text;
|
166
|
+
}
|
167
|
+
saveDocumentation() {
|
168
|
+
const formatForSave = this.documentation;
|
169
|
+
const filePath = this.config.savePath + this.config.savePathFileName;
|
170
|
+
fs.writeFileSync(filePath, JSON.stringify(formatForSave, null, 2));
|
171
|
+
}
|
172
|
+
getDocumentation() {
|
173
|
+
return this.documentation;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
exports.DocumentGenerator = DocumentGenerator;
|
177
|
+
new DocumentGenerator({
|
178
|
+
sourcePath: "./src/api",
|
179
|
+
apiEndpoint: "api/v1",
|
180
|
+
savePath: "./bin/",
|
181
|
+
savePathFileName: "doc.json",
|
182
|
+
});
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { BaseService } from "./BaseService";
|
2
|
+
import { HUBConnection } from "./cluster/HUBConnection";
|
3
|
+
import Signal, { Req } from "badmfck-signal";
|
4
|
+
import { IError } from "./structures/Interfaces";
|
5
|
+
export interface HUBServiceOptions {
|
6
|
+
url: string;
|
7
|
+
privateKey: string;
|
8
|
+
hubID: string;
|
9
|
+
}
|
10
|
+
export interface IClusterNode {
|
11
|
+
clusterID: string;
|
12
|
+
url: string;
|
13
|
+
publicKey: string;
|
14
|
+
ws: HUBConnection[];
|
15
|
+
}
|
16
|
+
export declare const REQ_CLUSTER_NODE: Req<string, IError | {
|
17
|
+
url: string;
|
18
|
+
publicKey: string;
|
19
|
+
}>;
|
20
|
+
export declare class HUBService extends BaseService {
|
21
|
+
options: HUBServiceOptions;
|
22
|
+
nodes: Map<string, IClusterNode>;
|
23
|
+
handlers: (Signal<any> | Req<any, any>)[];
|
24
|
+
constructor(options: HUBServiceOptions);
|
25
|
+
init(): Promise<void>;
|
26
|
+
applicationReady(): Promise<void>;
|
27
|
+
onConnectionAuthorized(conn: HUBConnection): void;
|
28
|
+
onConnectionClosed(conn: HUBConnection): void;
|
29
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.HUBService = exports.REQ_CLUSTER_NODE = void 0;
|
4
|
+
const ws_1 = require("ws");
|
5
|
+
const APIService_1 = require("./APIService");
|
6
|
+
const BaseService_1 = require("./BaseService");
|
7
|
+
const HUBConnection_1 = require("./cluster/HUBConnection");
|
8
|
+
const LogService_1 = require("./LogService");
|
9
|
+
const badmfck_signal_1 = require("badmfck-signal");
|
10
|
+
exports.REQ_CLUSTER_NODE = new badmfck_signal_1.Req(undefined, "REQ_CLUSTER_NODE");
|
11
|
+
class HUBService extends BaseService_1.BaseService {
|
12
|
+
options;
|
13
|
+
nodes = new Map();
|
14
|
+
handlers = [];
|
15
|
+
constructor(options) {
|
16
|
+
super("HUBService");
|
17
|
+
this.options = options;
|
18
|
+
}
|
19
|
+
async init() {
|
20
|
+
super.init();
|
21
|
+
}
|
22
|
+
async applicationReady() {
|
23
|
+
const server = await APIService_1.REQ_HTTP_SERVER.request();
|
24
|
+
const wss = new ws_1.WebSocketServer({
|
25
|
+
server: server.http,
|
26
|
+
path: "/hub"
|
27
|
+
});
|
28
|
+
wss.on("connection", (ws) => {
|
29
|
+
const conn = new HUBConnection_1.HUBConnection({
|
30
|
+
ws: ws,
|
31
|
+
privateKey: this.options.privateKey,
|
32
|
+
hubID: this.options.hubID,
|
33
|
+
onAuthorized: () => this.onConnectionAuthorized(conn),
|
34
|
+
onClose: () => this.onConnectionClosed(conn),
|
35
|
+
handlers: this.handlers
|
36
|
+
});
|
37
|
+
});
|
38
|
+
wss.on("error", (err) => {
|
39
|
+
console.error("HUBService: error: ", err);
|
40
|
+
});
|
41
|
+
}
|
42
|
+
onConnectionAuthorized(conn) {
|
43
|
+
(0, LogService_1.logInfo)("HUBService: authorized connection");
|
44
|
+
let clusterNode = this.nodes.get(conn.clientID);
|
45
|
+
if (!clusterNode) {
|
46
|
+
(0, LogService_1.logInfo)("HUBService: new cluster node connected");
|
47
|
+
clusterNode = {
|
48
|
+
clusterID: conn.clientID,
|
49
|
+
url: conn.url,
|
50
|
+
publicKey: conn.publicKey,
|
51
|
+
ws: []
|
52
|
+
};
|
53
|
+
this.nodes.set(conn.clientID, clusterNode);
|
54
|
+
}
|
55
|
+
clusterNode.ws.push(conn);
|
56
|
+
}
|
57
|
+
onConnectionClosed(conn) {
|
58
|
+
(0, LogService_1.logInfo)("HUBService: connection closed");
|
59
|
+
const clusterNode = this.nodes.get(conn.clientID);
|
60
|
+
if (clusterNode) {
|
61
|
+
clusterNode.ws = clusterNode.ws.filter(x => x !== conn);
|
62
|
+
if (!clusterNode.ws.length) {
|
63
|
+
(0, LogService_1.logInfo)("HUBService: cluster node disconnected");
|
64
|
+
this.nodes.delete(conn.clientID);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
exports.HUBService = HUBService;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import { WebSocket } from "ws";
|
2
|
+
import { IError } from "../structures/Interfaces";
|
3
|
+
import Signal, { Req } from "badmfck-signal";
|
4
|
+
export interface IClusterPacket {
|
5
|
+
method: string;
|
6
|
+
data?: any | null;
|
7
|
+
error?: IError;
|
8
|
+
callbackID: number;
|
9
|
+
}
|
10
|
+
export declare class HUBConnection {
|
11
|
+
static callbackID: number;
|
12
|
+
callbacks: Map<number, {
|
13
|
+
time: number;
|
14
|
+
callback: (packet: IClusterPacket) => void;
|
15
|
+
}>;
|
16
|
+
authorized: boolean;
|
17
|
+
ws: WebSocket;
|
18
|
+
privateKey: string;
|
19
|
+
publicKey: string | null;
|
20
|
+
hubID: string;
|
21
|
+
clientID: string | null;
|
22
|
+
url: string | null;
|
23
|
+
onAuthorized: () => void;
|
24
|
+
intervalID: number;
|
25
|
+
constructor(options: {
|
26
|
+
ws: WebSocket;
|
27
|
+
privateKey: string;
|
28
|
+
hubID: string;
|
29
|
+
onAuthorized: () => void;
|
30
|
+
onClose: () => void;
|
31
|
+
handlers: (Req<any, any> | Signal<any>)[];
|
32
|
+
authorized?: boolean;
|
33
|
+
maxTimeout?: number;
|
34
|
+
});
|
35
|
+
authorize(data: any): Promise<boolean | IError>;
|
36
|
+
sendCallback(packet: IClusterPacket, data: IError | any): void;
|
37
|
+
send(packet: IClusterPacket): Promise<IClusterPacket | IError | null>;
|
38
|
+
close(): void;
|
39
|
+
}
|
@@ -0,0 +1,230 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
7
|
+
}
|
8
|
+
Object.defineProperty(o, k2, desc);
|
9
|
+
}) : (function(o, m, k, k2) {
|
10
|
+
if (k2 === undefined) k2 = k;
|
11
|
+
o[k2] = m[k];
|
12
|
+
}));
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15
|
+
}) : function(o, v) {
|
16
|
+
o["default"] = v;
|
17
|
+
});
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
19
|
+
if (mod && mod.__esModule) return mod;
|
20
|
+
var result = {};
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22
|
+
__setModuleDefault(result, mod);
|
23
|
+
return result;
|
24
|
+
};
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
26
|
+
exports.HUBConnection = void 0;
|
27
|
+
const ws_1 = require("ws");
|
28
|
+
const LogService_1 = require("../LogService");
|
29
|
+
const DefaultErrors_1 = __importStar(require("../structures/DefaultErrors"));
|
30
|
+
const crypto_1 = require("crypto");
|
31
|
+
class HUBConnection {
|
32
|
+
static callbackID = 1;
|
33
|
+
callbacks = new Map();
|
34
|
+
authorized = false;
|
35
|
+
ws;
|
36
|
+
privateKey;
|
37
|
+
publicKey;
|
38
|
+
hubID;
|
39
|
+
clientID = null;
|
40
|
+
url = null;
|
41
|
+
onAuthorized;
|
42
|
+
intervalID = 0;
|
43
|
+
constructor(options) {
|
44
|
+
(0, LogService_1.logInfo)("HUBConnection: new connection");
|
45
|
+
this.privateKey = options.privateKey;
|
46
|
+
this.publicKey = null;
|
47
|
+
this.hubID = options.hubID;
|
48
|
+
this.ws = options.ws;
|
49
|
+
this.onAuthorized = options.onAuthorized;
|
50
|
+
if (options.authorized)
|
51
|
+
this.authorized = true;
|
52
|
+
this.ws.on("close", () => {
|
53
|
+
if (!this.authorized)
|
54
|
+
return;
|
55
|
+
(0, LogService_1.logInfo)("HUBConnection: connection closed");
|
56
|
+
if (options.onClose)
|
57
|
+
options.onClose();
|
58
|
+
});
|
59
|
+
this.ws.on("error", (e) => {
|
60
|
+
(0, LogService_1.logError)("HUBConnection: connection error", e);
|
61
|
+
});
|
62
|
+
this.ws.on("open", () => {
|
63
|
+
(0, LogService_1.logInfo)("HUBConnection: connection opened");
|
64
|
+
if (options.authorized) {
|
65
|
+
if (this.onAuthorized)
|
66
|
+
this.onAuthorized();
|
67
|
+
}
|
68
|
+
});
|
69
|
+
this.ws.on("message", async (msg, isBinary) => {
|
70
|
+
if (!this.authorized && isBinary)
|
71
|
+
return;
|
72
|
+
let data = null;
|
73
|
+
try {
|
74
|
+
data = JSON.parse(msg.toString());
|
75
|
+
}
|
76
|
+
catch (e) { }
|
77
|
+
if (!data || !data.method || !data.callbackID) {
|
78
|
+
(0, LogService_1.logError)("HUBConnection: invalid packet structure", data);
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
if (typeof data.method !== "string" || typeof data.callbackID !== "number") {
|
82
|
+
(0, LogService_1.logError)("HUBConnection: invalid packet structure", data);
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
if (!this.authorized && data.method === "auth") {
|
86
|
+
const result = await this.authorize(data.data);
|
87
|
+
if (DefaultErrors_1.ErrorUtils.isError(result)) {
|
88
|
+
(0, LogService_1.logError)("HUBConnection: authorization failed", result);
|
89
|
+
this.sendCallback(data, result);
|
90
|
+
return;
|
91
|
+
}
|
92
|
+
this.authorized = true;
|
93
|
+
if (this.onAuthorized)
|
94
|
+
this.onAuthorized();
|
95
|
+
this.sendCallback(data, result);
|
96
|
+
return;
|
97
|
+
}
|
98
|
+
if (!this.authorized) {
|
99
|
+
(0, LogService_1.logError)("HUBConnection: not authorized");
|
100
|
+
this.sendCallback(data, DefaultErrors_1.default.UNAUTHORIZED);
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
if (typeof data.data !== "string")
|
104
|
+
return this.sendCallback(data, { ...DefaultErrors_1.default.BAD_REQUEST, details: "invalid data, must be a string" });
|
105
|
+
let packetData = null;
|
106
|
+
try {
|
107
|
+
const encryptedPacketData = (0, crypto_1.privateDecrypt)(this.privateKey, data.data).toString("utf8");
|
108
|
+
packetData = JSON.parse(encryptedPacketData);
|
109
|
+
}
|
110
|
+
catch (e) {
|
111
|
+
return this.sendCallback(data, { ...DefaultErrors_1.default.BAD_REQUEST, details: "invalid data type, must be a json" });
|
112
|
+
}
|
113
|
+
if (data.method === "callback") {
|
114
|
+
if (!this.callbacks.has(data.callbackID)) {
|
115
|
+
(0, LogService_1.logError)("HUBConnection: callback not found", data);
|
116
|
+
return;
|
117
|
+
}
|
118
|
+
const cb = this.callbacks.get(data.callbackID);
|
119
|
+
if (cb) {
|
120
|
+
cb.callback({
|
121
|
+
method: "callback",
|
122
|
+
data: packetData,
|
123
|
+
callbackID: data.callbackID,
|
124
|
+
error: data.error
|
125
|
+
});
|
126
|
+
this.callbacks.delete(data.callbackID);
|
127
|
+
}
|
128
|
+
return;
|
129
|
+
}
|
130
|
+
;
|
131
|
+
for (let i of options.handlers) {
|
132
|
+
if (i.name === data.method) {
|
133
|
+
if (i.type === "signal") {
|
134
|
+
i.invoke(packetData);
|
135
|
+
if (data.callbackID > 0)
|
136
|
+
this.sendCallback(data, {});
|
137
|
+
return;
|
138
|
+
}
|
139
|
+
else if (i.type === "request") {
|
140
|
+
const result = await i.request(packetData);
|
141
|
+
this.sendCallback(data, result);
|
142
|
+
return;
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
this.sendCallback(data, { ...DefaultErrors_1.default.NOT_IMPLEMENTED, details: "method not found: " + data.method });
|
147
|
+
});
|
148
|
+
if (!options.authorized) {
|
149
|
+
setTimeout(() => {
|
150
|
+
if (!this.authorized) {
|
151
|
+
this.close();
|
152
|
+
}
|
153
|
+
}, 2000);
|
154
|
+
}
|
155
|
+
setInterval(() => {
|
156
|
+
const now = +new Date();
|
157
|
+
for (let [key, value] of this.callbacks) {
|
158
|
+
if (now - value.time > (options.maxTimeout ?? 1000 * 60 * 15)) {
|
159
|
+
value.callback({ method: "callback", data: null, callbackID: key, error: { ...DefaultErrors_1.default.TIMEOUT } });
|
160
|
+
this.callbacks.delete(key);
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}, options.maxTimeout || 1000 * 60 * 15);
|
164
|
+
}
|
165
|
+
async authorize(data) {
|
166
|
+
if (typeof data !== "object" || !data || !data.signature || !data.publicKey || !data.clientID || !data.url)
|
167
|
+
return { ...DefaultErrors_1.default.BAD_REQUEST, details: "invalid data, expected {signature:encrypted hub key,publicKey:encrypted public key, clientID:clientID,url: service url}" };
|
168
|
+
let hubKey = null;
|
169
|
+
try {
|
170
|
+
hubKey = (0, crypto_1.privateDecrypt)(this.privateKey, data.signature).toString("utf8");
|
171
|
+
}
|
172
|
+
catch (e) {
|
173
|
+
return { ...DefaultErrors_1.default.UNAUTHORIZED, details: "cant check signature" };
|
174
|
+
}
|
175
|
+
if (hubKey !== this.hubID)
|
176
|
+
return { ...DefaultErrors_1.default.UNAUTHORIZED, details: "signature is invalid" };
|
177
|
+
try {
|
178
|
+
this.publicKey = (0, crypto_1.privateDecrypt)(this.privateKey, data.publicKey).toString("utf8");
|
179
|
+
}
|
180
|
+
catch (e) {
|
181
|
+
return { ...DefaultErrors_1.default.UNAUTHORIZED, details: "cant obtain public key" };
|
182
|
+
}
|
183
|
+
if (!this.publicKey)
|
184
|
+
return { ...DefaultErrors_1.default.UNAUTHORIZED, details: "wrong public key" };
|
185
|
+
return true;
|
186
|
+
}
|
187
|
+
sendCallback(packet, data) {
|
188
|
+
if (packet.callbackID === -1)
|
189
|
+
return;
|
190
|
+
if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN) {
|
191
|
+
this.ws.send(JSON.stringify({
|
192
|
+
method: "callback",
|
193
|
+
error: DefaultErrors_1.ErrorUtils.isError(data) ? data : null,
|
194
|
+
data: DefaultErrors_1.ErrorUtils.isError(data) ? null : data,
|
195
|
+
callbackID: packet.callbackID
|
196
|
+
}));
|
197
|
+
}
|
198
|
+
else {
|
199
|
+
(0, LogService_1.logError)("HUBConnection: ws not connected, cant send callback", packet);
|
200
|
+
}
|
201
|
+
}
|
202
|
+
async send(packet) {
|
203
|
+
if (!this.ws || this.ws.readyState !== ws_1.WebSocket.OPEN) {
|
204
|
+
(0, LogService_1.logError)("HUBConnection: ws not connected, cant send packet", packet);
|
205
|
+
return { ...DefaultErrors_1.default.SERVICE_NOT_WORKING, details: "ws not connected" };
|
206
|
+
}
|
207
|
+
let cb = null;
|
208
|
+
const promise = new Promise((resolve) => cb = resolve);
|
209
|
+
if (packet.callbackID > 0) {
|
210
|
+
const callbackID = HUBConnection.callbackID++;
|
211
|
+
this.callbacks.set(callbackID, { time: +new Date(), callback: cb });
|
212
|
+
packet.callbackID = callbackID;
|
213
|
+
}
|
214
|
+
this.ws.send(JSON.stringify(packet));
|
215
|
+
if (packet.callbackID === 0)
|
216
|
+
return null;
|
217
|
+
return promise;
|
218
|
+
}
|
219
|
+
close() {
|
220
|
+
this.callbacks.clear();
|
221
|
+
if (this.ws) {
|
222
|
+
try {
|
223
|
+
this.ws.removeAllListeners();
|
224
|
+
this.ws.close();
|
225
|
+
}
|
226
|
+
catch (e) { }
|
227
|
+
}
|
228
|
+
}
|
229
|
+
}
|
230
|
+
exports.HUBConnection = HUBConnection;
|
@@ -6,6 +6,10 @@ declare class DefaultErrors {
|
|
6
6
|
static FILE_TOO_LARGE: IError;
|
7
7
|
static CANT_SEND_FILE: IError;
|
8
8
|
static FILE_NOT_EXISTS: IError;
|
9
|
+
static UNAUTHORIZED: IError;
|
10
|
+
static BAD_REQUEST: IError;
|
11
|
+
static SERVICE_NOT_WORKING: IError;
|
12
|
+
static TIMEOUT: IError;
|
9
13
|
}
|
10
14
|
export declare class ErrorUtils {
|
11
15
|
static isError(obj: any): boolean;
|
@@ -9,6 +9,10 @@ class DefaultErrors {
|
|
9
9
|
static FILE_TOO_LARGE = { code: 4, message: "File is too large" };
|
10
10
|
static CANT_SEND_FILE = { code: 5, message: "Can't send file" };
|
11
11
|
static FILE_NOT_EXISTS = { code: 5, message: "File not exists" };
|
12
|
+
static UNAUTHORIZED = { code: 6, message: "Unauthorized" };
|
13
|
+
static BAD_REQUEST = { code: 7, message: "Bad request", httpStatus: 400 };
|
14
|
+
static SERVICE_NOT_WORKING = { code: 8, message: "Service not working" };
|
15
|
+
static TIMEOUT = { code: 9, message: "Timeout" };
|
12
16
|
}
|
13
17
|
class ErrorUtils {
|
14
18
|
static isError(obj) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "badmfck-api-server",
|
3
|
-
"version": "2.4.
|
3
|
+
"version": "2.4.3",
|
4
4
|
"description": "Simple API http server based on express",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"types": "dist/index.d.ts",
|
@@ -20,11 +20,12 @@
|
|
20
20
|
"@types/express-fileupload": "^1.5.0",
|
21
21
|
"@types/mysql": "^2.15.21",
|
22
22
|
"@types/ws": "^8.5.9",
|
23
|
-
"axios": "^1.7.
|
24
|
-
"badmfck-signal": "^1.4.
|
23
|
+
"axios": "^1.7.7",
|
24
|
+
"badmfck-signal": "^1.4.7",
|
25
|
+
"comment-parser": "^1.4.1",
|
25
26
|
"cors": "^2.8.5",
|
26
|
-
"express": "^4.
|
27
|
-
"express-fileupload": "^1.5.
|
27
|
+
"express": "^4.21.1",
|
28
|
+
"express-fileupload": "^1.5.1",
|
28
29
|
"mysql2": "^3.11.3",
|
29
30
|
"ws": "^8.18.0"
|
30
31
|
},
|