Files
Checkmate/server/src/service/infrastructure/networkService.ts
T

199 lines
5.5 KiB
TypeScript
Raw Normal View History

2026-03-09 11:14:40 -07:00
import type { Monitor, MonitorStatusResponse, MonitorPayloadMap, MonitorType } from "@/types/index.js";
2026-02-24 19:36:45 +00:00
import type { AxiosStatic } from "axios";
2026-02-23 10:19:42 +05:30
import { AppError } from "@/utils/AppError.js";
2026-03-05 14:11:11 -08:00
import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js";
2026-02-24 19:19:32 +00:00
import { ILogger } from "@/utils/logger.js";
2026-03-04 23:24:48 +00:00
import { IStatusProvider } from "./network/IStatusProvider.js";
2025-04-20 11:29:53 -07:00
const SERVICE_NAME = "NetworkService";
2025-08-15 16:04:43 -07:00
2026-01-15 23:45:40 +00:00
export interface INetworkService {
readonly serviceName: string;
2026-03-09 11:14:40 -07:00
requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
2026-03-03 19:34:28 +00:00
requestWebhook(
type: string,
url: string,
body: unknown
): Promise<{ type: string; status: boolean; code: number; message: string; payload?: unknown }>;
2026-01-15 23:45:40 +00:00
requestPagerDuty(args: { message: string; routingKey: string; monitorUrl: string }): Promise<boolean>;
requestMatrix(args: { homeserverUrl: string; accessToken: string; roomId: string; message: string }): Promise<{
status: boolean;
code: number;
message: string;
payload?: unknown;
}>;
}
2026-03-05 14:46:28 -08:00
export class NetworkService implements INetworkService {
2025-04-20 11:29:53 -07:00
static SERVICE_NAME = SERVICE_NAME;
2026-02-24 19:36:45 +00:00
private axios: AxiosStatic;
private logger: ILogger;
2026-01-15 23:45:40 +00:00
2026-02-24 19:19:32 +00:00
constructor(
2026-02-24 19:36:45 +00:00
axios: AxiosStatic,
2026-02-24 19:19:32 +00:00
logger: ILogger,
2026-03-05 14:11:11 -08:00
private providers: IStatusProvider<unknown>[]
2026-02-24 19:19:32 +00:00
) {
2025-04-20 11:29:53 -07:00
this.axios = axios;
this.logger = logger;
}
2026-01-15 23:45:40 +00:00
get serviceName(): string {
return NetworkService.SERVICE_NAME;
}
2025-08-15 16:04:43 -07:00
// Main entry point
2026-03-09 11:14:40 -07:00
async requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>> {
2026-03-05 14:11:11 -08:00
const provider = this.providers.find((p) => p.supports(monitor.type));
if (!provider) {
2026-03-09 11:14:40 -07:00
return this.handleUnsupportedType(monitor.type) as Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
2025-04-20 11:29:53 -07:00
}
2026-03-09 11:14:40 -07:00
return provider.handle(monitor) as Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
2025-04-20 11:29:53 -07:00
}
2026-01-15 23:45:40 +00:00
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
return {
monitorId: "unknown",
teamId: "unknown",
type: "unknown",
status: false,
2026-03-05 14:11:11 -08:00
code: NETWORK_ERROR,
2026-01-15 23:45:40 +00:00
message: `Unsupported type: ${type}`,
};
2025-04-20 11:29:53 -07:00
}
2025-08-15 16:04:43 -07:00
// Other network requests unrelated to monitoring:
2026-03-03 19:34:28 +00:00
async requestWebhook(type: string, url: string, body: unknown) {
2025-04-20 11:29:53 -07:00
try {
2025-06-19 12:11:43 +08:00
const response = await this.axios.post(url, body, {
2025-04-20 11:29:53 -07:00
headers: {
"Content-Type": "application/json",
},
});
return {
type: "webhook",
status: true,
code: response.status,
2025-06-19 12:11:43 +08:00
message: `Successfully sent ${type} notification`,
2025-04-20 11:29:53 -07:00
payload: response.data,
};
2026-03-03 19:34:28 +00:00
} catch (err: unknown) {
2025-04-20 11:29:53 -07:00
this.logger.warn({
2026-03-03 19:34:28 +00:00
message: err instanceof Error ? err.message : String(err),
2026-03-05 14:11:11 -08:00
service: SERVICE_NAME,
2025-04-20 11:29:53 -07:00
method: "requestWebhook",
});
2026-03-03 19:34:28 +00:00
if (err && typeof err === "object" && "response" in err) {
const axiosError = err as { response?: { status?: number; data?: unknown } };
return {
type: "webhook",
status: false,
2026-03-05 14:11:11 -08:00
code: axiosError.response?.status ?? NETWORK_ERROR,
2026-03-03 19:34:28 +00:00
message: `Failed to send ${type} notification`,
payload: axiosError.response?.data,
};
}
2025-04-20 11:29:53 -07:00
return {
type: "webhook",
status: false,
2026-03-05 14:11:11 -08:00
code: NETWORK_ERROR,
2025-06-19 12:11:43 +08:00
message: `Failed to send ${type} notification`,
2025-04-20 11:29:53 -07:00
};
}
}
2026-01-15 22:33:25 +00:00
async requestPagerDuty({ message, routingKey, monitorUrl }: { message: string; routingKey: string; monitorUrl: string }) {
2025-06-10 13:44:15 +08:00
try {
const response = await this.axios.post(`https://events.pagerduty.com/v2/enqueue`, {
routing_key: routingKey,
event_action: "trigger",
payload: {
summary: message,
severity: "critical",
source: monitorUrl,
timestamp: new Date().toISOString(),
},
});
2026-04-09 09:27:38 -07:00
if (response.data?.status !== "success") return false;
2025-06-10 13:44:15 +08:00
return true;
2026-03-03 19:34:28 +00:00
} catch (err: unknown) {
const originalMessage = err instanceof Error ? err.message : String(err);
throw new AppError({
message: originalMessage || "Error sending PagerDuty notification",
2026-03-05 14:11:11 -08:00
service: SERVICE_NAME,
2026-03-03 19:34:28 +00:00
method: "requestPagerDuty",
details: {
responseData: err && typeof err === "object" && "response" in err ? (err as { response?: { data?: unknown } }).response?.data : undefined,
},
});
2025-06-10 13:44:15 +08:00
}
}
2026-01-15 22:33:25 +00:00
async requestMatrix({
homeserverUrl,
accessToken,
roomId,
message,
}: {
homeserverUrl: string;
accessToken: string;
roomId: string;
message: string;
}) {
try {
const url = `${homeserverUrl}/_matrix/client/v3/rooms/${roomId}/send/m.room.message?access_token=${accessToken}`;
const body = {
msgtype: "m.text",
body: message,
format: "org.matrix.custom.html",
formatted_body: message,
};
const response = await this.axios.post(url, body, {
headers: {
"Content-Type": "application/json",
},
});
return {
status: true,
code: response.status,
message: "Successfully sent Matrix notification",
};
2026-03-03 19:34:28 +00:00
} catch (err: unknown) {
if (err instanceof Error) {
this.logger.warn({
message: err.message,
2026-03-05 14:11:11 -08:00
service: SERVICE_NAME,
2026-03-03 19:34:28 +00:00
method: "requestMatrix",
});
if (err && typeof err === "object" && "response" in err) {
const axiosError = err as { response?: { status?: number; data?: unknown } };
return {
status: false,
2026-03-05 14:11:11 -08:00
code: axiosError.response?.status || NETWORK_ERROR,
2026-03-03 19:34:28 +00:00
message: "Failed to send Matrix notification",
payload: axiosError.response?.data,
};
}
}
this.logger.warn({
2026-03-03 19:34:28 +00:00
message: String(err),
2026-03-05 14:11:11 -08:00
service: SERVICE_NAME,
method: "requestMatrix",
});
return {
status: false,
2026-03-05 14:11:11 -08:00
code: NETWORK_ERROR,
message: "Failed to send Matrix notification",
};
}
}
2025-04-20 11:29:53 -07:00
}