Feat: Backend service - Implement WebSocket check in NetworkService
This commit is contained in:
1 parent
5093ca9f63
commit
52b838a9eb
6 files changed
+125
-46
No files matched your search
Generated
+15
-3
@@ -43,6 +43,7 @@
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0",
|
||||
"ws": "^8.19.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -63,6 +64,7 @@
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/ws": "^8.18.1",
|
||||
"c8": "10.1.3",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-mocha": "^10.5.0",
|
||||
@@ -4670,6 +4672,16 @@
|
||||
"@types/webidl-conversions": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
||||
@@ -15172,9 +15184,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"super-simple-scheduler": "1.4.5",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"winston": "^3.13.0",
|
||||
"ws": "^8.19.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,6 +79,7 @@
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/ping": "0.4.4",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/ws": "^8.18.1",
|
||||
"c8": "10.1.3",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-mocha": "^10.5.0",
|
||||
|
||||
@@ -45,6 +45,7 @@ import { games, GameDig } from "gamedig";
|
||||
import jmespath from "jmespath";
|
||||
import * as grpc from "@grpc/grpc-js";
|
||||
import * as protoLoader from "@grpc/proto-loader";
|
||||
import WebSocket from "ws";
|
||||
|
||||
// repositories
|
||||
import {
|
||||
@@ -145,7 +146,21 @@ export const initializeServices = async ({
|
||||
const teamsRepository = new MongoTeamsRepository();
|
||||
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
||||
|
||||
const networkService = new NetworkService(axios, got, https, jmespath, GameDig, ping, logger, Docker, net, settingsService, grpc, protoLoader);
|
||||
const networkService = new NetworkService(
|
||||
axios,
|
||||
got,
|
||||
https,
|
||||
jmespath,
|
||||
GameDig,
|
||||
ping,
|
||||
logger,
|
||||
Docker,
|
||||
net,
|
||||
settingsService,
|
||||
grpc,
|
||||
protoLoader,
|
||||
WebSocket
|
||||
);
|
||||
const emailService = new EmailService(settingsService, fs, path, compile, mjml2html, nodemailer, logger);
|
||||
|
||||
const notificationMessageBuilder = new NotificationMessageBuilder();
|
||||
|
||||
@@ -41,14 +41,7 @@ export interface INetworkService {
|
||||
class NetworkService implements INetworkService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private TYPE_PING: string;
|
||||
private TYPE_HTTP: string;
|
||||
private TYPE_PAGESPEED: string;
|
||||
private TYPE_HARDWARE: string;
|
||||
private TYPE_DOCKER: string;
|
||||
private TYPE_PORT: string;
|
||||
private TYPE_GAME: string;
|
||||
private TYPE_GRPC: string;
|
||||
private typeHandlers!: Record<string, (monitor: Monitor) => Promise<MonitorStatusResponse>>;
|
||||
private SERVICE_NAME: string;
|
||||
private NETWORK_ERROR: number;
|
||||
private PING_ERROR: number;
|
||||
@@ -65,6 +58,7 @@ class NetworkService implements INetworkService {
|
||||
private settingsService: ISettingsService;
|
||||
private grpc: any;
|
||||
private protoLoader: any;
|
||||
private WebSocket: any;
|
||||
|
||||
private buildStatusResponse = <T>({
|
||||
monitor,
|
||||
@@ -132,16 +126,20 @@ class NetworkService implements INetworkService {
|
||||
net: any,
|
||||
settingsService: ISettingsService,
|
||||
grpc: any,
|
||||
protoLoader: any
|
||||
protoLoader: any,
|
||||
WebSocket: any
|
||||
) {
|
||||
this.TYPE_PING = "ping";
|
||||
this.TYPE_HTTP = "http";
|
||||
this.TYPE_PAGESPEED = "pagespeed";
|
||||
this.TYPE_HARDWARE = "hardware";
|
||||
this.TYPE_DOCKER = "docker";
|
||||
this.TYPE_PORT = "port";
|
||||
this.TYPE_GAME = "game";
|
||||
this.TYPE_GRPC = "grpc";
|
||||
this.typeHandlers = {
|
||||
ping: (m) => this.requestPing(m),
|
||||
http: (m) => this.requestHttp(m),
|
||||
pagespeed: (m) => this.requestPageSpeed(m),
|
||||
hardware: (m) => this.requestHardware(m),
|
||||
docker: (m) => this.requestDocker(m),
|
||||
port: (m) => this.requestPort(m),
|
||||
game: (m) => this.requestGame(m),
|
||||
grpc: (m) => this.requestGrpc(m),
|
||||
websocket: (m) => this.requestWebSocket(m),
|
||||
};
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.NETWORK_ERROR = 5000;
|
||||
this.PING_ERROR = 5001;
|
||||
@@ -156,6 +154,7 @@ class NetworkService implements INetworkService {
|
||||
this.settingsService = settingsService;
|
||||
this.grpc = grpc;
|
||||
this.protoLoader = protoLoader;
|
||||
this.WebSocket = WebSocket;
|
||||
|
||||
const cacheable = new CacheableLookup();
|
||||
|
||||
@@ -188,26 +187,8 @@ class NetworkService implements INetworkService {
|
||||
// Main entry point
|
||||
async requestStatus(monitor: Monitor): Promise<MonitorStatusResponse> {
|
||||
const type = monitor?.type || "unknown";
|
||||
switch (type) {
|
||||
case this.TYPE_PING:
|
||||
return await this.requestPing(monitor);
|
||||
case this.TYPE_HTTP:
|
||||
return await this.requestHttp(monitor);
|
||||
case this.TYPE_PAGESPEED:
|
||||
return await this.requestPageSpeed(monitor);
|
||||
case this.TYPE_HARDWARE:
|
||||
return await this.requestHardware(monitor);
|
||||
case this.TYPE_DOCKER:
|
||||
return await this.requestDocker(monitor);
|
||||
case this.TYPE_PORT:
|
||||
return await this.requestPort(monitor);
|
||||
case this.TYPE_GAME:
|
||||
return await this.requestGame(monitor);
|
||||
case this.TYPE_GRPC:
|
||||
return await this.requestGrpc(monitor);
|
||||
default:
|
||||
return this.handleUnsupportedType(type);
|
||||
}
|
||||
const handler = this.typeHandlers[type];
|
||||
return handler ? handler(monitor) : this.handleUnsupportedType(type);
|
||||
}
|
||||
|
||||
private async requestPing(monitor: Monitor): Promise<MonitorStatusResponse> {
|
||||
@@ -763,6 +744,75 @@ class NetworkService implements INetworkService {
|
||||
return statusNames[code] || "UNKNOWN";
|
||||
}
|
||||
|
||||
private async requestWebSocket(monitor: Monitor): Promise<MonitorStatusResponse> {
|
||||
try {
|
||||
const { url, ignoreTlsErrors } = monitor;
|
||||
|
||||
if (!url) {
|
||||
throw new Error("Monitor URL is required");
|
||||
}
|
||||
|
||||
const TIMEOUT_MS = 10000;
|
||||
|
||||
const wsResponse = this.buildStatusResponse({
|
||||
monitor,
|
||||
overrides: {
|
||||
status: false,
|
||||
code: this.NETWORK_ERROR,
|
||||
message: "WebSocket connection not established",
|
||||
},
|
||||
});
|
||||
|
||||
const { response, responseTime, error } = await this.timeRequest<{ connected: boolean }>(() => {
|
||||
return new Promise<{ connected: boolean }>((resolve, reject) => {
|
||||
const wsOptions: Record<string, any> = {};
|
||||
if (ignoreTlsErrors) {
|
||||
wsOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
const ws = new this.WebSocket(url, wsOptions);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
ws.close();
|
||||
reject(new Error("WebSocket connection timeout"));
|
||||
}, TIMEOUT_MS);
|
||||
|
||||
ws.on("open", () => {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
resolve({ connected: true });
|
||||
});
|
||||
|
||||
ws.on("error", (err: Error) => {
|
||||
clearTimeout(timeout);
|
||||
ws.close();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (error) {
|
||||
wsResponse.status = false;
|
||||
wsResponse.code = this.NETWORK_ERROR;
|
||||
wsResponse.message = (error as Error).message || "WebSocket connection failed";
|
||||
wsResponse.responseTime = responseTime;
|
||||
return wsResponse;
|
||||
}
|
||||
|
||||
wsResponse.status = true;
|
||||
wsResponse.code = 200;
|
||||
wsResponse.message = "WebSocket connection successful";
|
||||
wsResponse.responseTime = responseTime;
|
||||
wsResponse.payload = response;
|
||||
|
||||
return wsResponse;
|
||||
} catch (err: any) {
|
||||
err.service = this.SERVICE_NAME;
|
||||
err.method = "requestWebSocket";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
|
||||
return {
|
||||
monitorId: "unknown",
|
||||
|
||||
@@ -11,7 +11,7 @@ export const getChecksParamValidation = z.object({
|
||||
});
|
||||
|
||||
export const getChecksQueryValidation = z.object({
|
||||
type: z.enum(["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc"]).optional(),
|
||||
type: z.enum(["http", "ping", "pagespeed", "hardware", "docker", "port", "game", "grpc", "websocket"]).optional(),
|
||||
sortOrder: z.enum(["asc", "desc"]).optional(),
|
||||
limit: z.coerce.number().optional(),
|
||||
dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]).optional(),
|
||||
|
||||
@@ -21,8 +21,8 @@ export const getMonitorsByTeamIdParamValidation = z.object({});
|
||||
export const getMonitorsByTeamIdQueryValidation = z.object({
|
||||
type: z
|
||||
.union([
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"])),
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"])),
|
||||
])
|
||||
.optional(),
|
||||
filter: z.union([z.string(), z.literal(""), z.null()]).optional(),
|
||||
@@ -37,8 +37,8 @@ export const getMonitorsWithChecksQueryValidation = z.object({
|
||||
order: z.enum(["asc", "desc"]).optional(),
|
||||
type: z
|
||||
.union([
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc"])),
|
||||
z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"]),
|
||||
z.array(z.enum(["http", "ping", "pagespeed", "docker", "hardware", "port", "game", "grpc", "websocket"])),
|
||||
])
|
||||
.optional(),
|
||||
explain: booleanCoercion.optional(),
|
||||
|
||||
Reference in new issue
Block a user