742 lines
20 KiB
TypeScript
742 lines
20 KiB
TypeScript
import { HTTPError, RequestError } from "got";
|
|
import type { Got, Response } from "got";
|
|
import type { Monitor, MonitorStatusResponse } from "@/types/index.js";
|
|
|
|
import CacheableLookup from "cacheable-lookup";
|
|
const SERVICE_NAME = "NetworkService";
|
|
|
|
type MonitorStatusResponseOverrides<T> = Partial<Omit<MonitorStatusResponse<T>, "monitorId" | "teamId" | "type">>;
|
|
|
|
interface BuildStatusResponseArgs<T> {
|
|
monitor: Monitor;
|
|
response?: Response<T> | null;
|
|
error?: Error | RequestError | HTTPError | null;
|
|
payload?: T | null;
|
|
jsonPath?: string;
|
|
matchMethod?: MonitorStatusResponse["matchMethod"];
|
|
expectedValue?: string;
|
|
extracted?: unknown;
|
|
overrides?: MonitorStatusResponseOverrides<T>;
|
|
}
|
|
|
|
export interface INetworkService {
|
|
readonly serviceName: string;
|
|
requestStatus(monitor: Monitor): Promise<MonitorStatusResponse>;
|
|
requestWebhook(type: string, url: string, body: any): Promise<{ type: string; status: boolean; code: number; message: string; payload?: unknown }>;
|
|
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;
|
|
}>;
|
|
}
|
|
|
|
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 SERVICE_NAME: string;
|
|
private NETWORK_ERROR: number;
|
|
private PING_ERROR: number;
|
|
|
|
private axios: any;
|
|
private got: Got;
|
|
private https: any;
|
|
private jmespath: any;
|
|
private GameDig: any;
|
|
private ping: any;
|
|
private logger: any;
|
|
private Docker: any;
|
|
private net: any;
|
|
private settingsService: any;
|
|
|
|
private buildStatusResponse = <T>({
|
|
monitor,
|
|
response,
|
|
error,
|
|
payload,
|
|
jsonPath,
|
|
matchMethod,
|
|
expectedValue,
|
|
extracted,
|
|
overrides,
|
|
}: BuildStatusResponseArgs<T>): MonitorStatusResponse<T> => {
|
|
if (error) {
|
|
const statusResponse: MonitorStatusResponse<T> = {
|
|
monitorId: monitor.id,
|
|
teamId: monitor.teamId,
|
|
type: monitor.type,
|
|
status: false,
|
|
code: this.NETWORK_ERROR,
|
|
message: error.message ?? "Network error",
|
|
responseTime: 0,
|
|
timings: undefined,
|
|
jsonPath,
|
|
matchMethod,
|
|
expectedValue,
|
|
extracted,
|
|
payload,
|
|
};
|
|
if (error instanceof HTTPError || error instanceof RequestError) {
|
|
statusResponse.code = error?.response?.statusCode ?? this.NETWORK_ERROR;
|
|
statusResponse.message = error.message;
|
|
statusResponse.responseTime = error.timings?.phases?.total ?? 0;
|
|
statusResponse.timings = error.timings;
|
|
}
|
|
return { ...statusResponse, ...(overrides ?? {}) };
|
|
}
|
|
|
|
return {
|
|
monitorId: monitor.id,
|
|
teamId: monitor.teamId,
|
|
type: monitor.type,
|
|
status: response?.ok ?? false,
|
|
code: response?.statusCode ?? this.NETWORK_ERROR,
|
|
message: response?.statusMessage ?? "",
|
|
responseTime: response?.timings?.phases?.total ?? 0,
|
|
timings: response?.timings,
|
|
payload: payload ?? response?.body,
|
|
jsonPath,
|
|
matchMethod,
|
|
expectedValue,
|
|
extracted,
|
|
...(overrides ?? {}),
|
|
};
|
|
};
|
|
|
|
constructor({
|
|
axios,
|
|
got,
|
|
https,
|
|
jmespath,
|
|
GameDig,
|
|
ping,
|
|
logger,
|
|
http,
|
|
Docker,
|
|
net,
|
|
settingsService,
|
|
}: {
|
|
axios: any;
|
|
got: Got;
|
|
https: any;
|
|
jmespath: any;
|
|
GameDig: any;
|
|
ping: any;
|
|
logger: any;
|
|
http: any;
|
|
Docker: any;
|
|
net: any;
|
|
settingsService: 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.SERVICE_NAME = SERVICE_NAME;
|
|
this.NETWORK_ERROR = 5000;
|
|
this.PING_ERROR = 5001;
|
|
this.axios = axios;
|
|
this.https = https;
|
|
this.jmespath = jmespath;
|
|
this.GameDig = GameDig;
|
|
this.ping = ping;
|
|
this.logger = logger;
|
|
this.Docker = Docker;
|
|
this.net = net;
|
|
this.settingsService = settingsService;
|
|
|
|
const cacheable = new CacheableLookup();
|
|
|
|
this.got = got.extend({
|
|
dnsCache: cacheable,
|
|
timeout: {
|
|
request: 30000,
|
|
},
|
|
retry: { limit: 1 },
|
|
});
|
|
}
|
|
|
|
get serviceName(): string {
|
|
return NetworkService.SERVICE_NAME;
|
|
}
|
|
|
|
// Helper functions
|
|
private async timeRequest<T>(operation: () => Promise<T>): Promise<{ response: T | null; responseTime: number; error: unknown }> {
|
|
const start = process.hrtime.bigint();
|
|
try {
|
|
const response = await operation();
|
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
|
return { response, responseTime: elapsedMs, error: null };
|
|
} catch (error) {
|
|
const elapsedMs = Math.round(Number(process.hrtime.bigint() - start) / 1_000_000);
|
|
return { response: null, responseTime: elapsedMs, error };
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
default:
|
|
return this.handleUnsupportedType(type);
|
|
}
|
|
}
|
|
|
|
private async requestPing(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
if (!monitor?.url) {
|
|
throw new Error("Monitor URL is required");
|
|
}
|
|
|
|
const rawUrl = monitor.url;
|
|
const sanitizedHost = rawUrl
|
|
.replace(/^https?:\/\//, "")
|
|
.replace(/\/.*$/, "")
|
|
.replace(/:.*/, "");
|
|
const { response, error } = await this.timeRequest(() => this.ping.promise.probe(sanitizedHost));
|
|
|
|
if (!response) {
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
throw new Error("Ping failed - no result returned");
|
|
}
|
|
|
|
const pingResponse = this.buildStatusResponse({
|
|
monitor,
|
|
payload: response,
|
|
overrides: {
|
|
status: (response as { alive?: boolean })?.alive ?? false,
|
|
code: 200,
|
|
message: "Success",
|
|
responseTime: (response as { time?: number })?.time ?? 0,
|
|
payload: response,
|
|
},
|
|
});
|
|
|
|
if (error) {
|
|
pingResponse.status = false;
|
|
pingResponse.code = this.PING_ERROR;
|
|
pingResponse.message = "Ping failed";
|
|
return pingResponse;
|
|
}
|
|
|
|
return pingResponse;
|
|
} catch (err: any) {
|
|
err.service = this.SERVICE_NAME;
|
|
err.method = "requestPing";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
private async requestHttp(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
const { url, secret, id, teamId, type, ignoreTlsErrors, useAdvancedMatching, jsonPath, matchMethod, expectedValue } = monitor;
|
|
const httpResponse = this.buildStatusResponse({
|
|
monitor,
|
|
overrides: {
|
|
status: false,
|
|
code: this.NETWORK_ERROR,
|
|
message: "Request not executed",
|
|
},
|
|
});
|
|
|
|
try {
|
|
if (!url) {
|
|
throw new Error("Monitor URL is required");
|
|
}
|
|
const config: Record<string, any> = {
|
|
headers: secret ? { Authorization: `Bearer ${secret}` } : undefined,
|
|
};
|
|
|
|
if (ignoreTlsErrors) {
|
|
config.agent = {
|
|
https: new this.https.Agent({
|
|
rejectUnauthorized: false,
|
|
}),
|
|
};
|
|
}
|
|
|
|
const response = await this.got(url, config);
|
|
|
|
let payload: any;
|
|
const contentType = response.headers["content-type"];
|
|
|
|
if (contentType && contentType.includes("application/json")) {
|
|
try {
|
|
payload = JSON.parse(response.body);
|
|
} catch {
|
|
payload = response.body;
|
|
}
|
|
} else {
|
|
payload = response.body;
|
|
}
|
|
|
|
Object.assign(httpResponse, {
|
|
code: response.statusCode,
|
|
status: response.ok,
|
|
message: response.statusMessage ?? "",
|
|
responseTime: response.timings.phases.total || 0,
|
|
payload,
|
|
timings: response.timings,
|
|
});
|
|
|
|
if (!useAdvancedMatching) {
|
|
return httpResponse;
|
|
}
|
|
|
|
if (expectedValue && !jsonPath) {
|
|
let ok = false;
|
|
if (matchMethod === "equal") ok = payload === expectedValue;
|
|
if (matchMethod === "include" && typeof payload === "string") ok = payload.includes(expectedValue);
|
|
if (matchMethod === "regex" && typeof payload === "string") ok = new RegExp(expectedValue).test(payload);
|
|
|
|
if (ok === true) {
|
|
return httpResponse;
|
|
} else {
|
|
httpResponse.code = 500;
|
|
httpResponse.status = false;
|
|
httpResponse.message = "Expected value did not match";
|
|
return httpResponse;
|
|
}
|
|
}
|
|
|
|
if (jsonPath) {
|
|
const contentType = response.headers["content-type"];
|
|
const isJson = contentType?.includes("application/json");
|
|
if (!isJson) {
|
|
httpResponse.status = false;
|
|
httpResponse.message = "Response is not JSON";
|
|
return httpResponse;
|
|
}
|
|
try {
|
|
const extracted = this.jmespath.search(payload, jsonPath);
|
|
if (expectedValue) {
|
|
let ok = false;
|
|
if (matchMethod === "equal") ok = extracted === expectedValue;
|
|
if (matchMethod === "include" && typeof extracted === "string") ok = extracted.includes(expectedValue);
|
|
if (matchMethod === "regex" && typeof extracted === "string") ok = new RegExp(expectedValue).test(extracted);
|
|
|
|
if (ok) {
|
|
httpResponse.extracted = extracted;
|
|
return httpResponse;
|
|
} else {
|
|
httpResponse.status = false;
|
|
httpResponse.code = 500;
|
|
httpResponse.message = "Expected value did not match";
|
|
httpResponse.extracted = extracted;
|
|
return httpResponse;
|
|
}
|
|
} else {
|
|
const isFalsey = extracted === false || extracted === "false" || extracted === undefined || extracted === null;
|
|
if (!isFalsey) {
|
|
httpResponse.extracted = extracted;
|
|
return httpResponse;
|
|
} else {
|
|
httpResponse.status = false;
|
|
httpResponse.code = 500;
|
|
httpResponse.message = "Expected value did not match";
|
|
httpResponse.extracted = extracted;
|
|
return httpResponse;
|
|
}
|
|
}
|
|
} catch {
|
|
httpResponse.status = false;
|
|
httpResponse.message = "Error evaluating JSON path";
|
|
return httpResponse;
|
|
}
|
|
}
|
|
return httpResponse;
|
|
} catch (err: any) {
|
|
if (err.name === "HTTPError" || err.name === "RequestError") {
|
|
httpResponse.code = err?.response?.statusCode || this.NETWORK_ERROR;
|
|
httpResponse.status = false;
|
|
httpResponse.message = err?.response?.statusCode || err.message;
|
|
httpResponse.responseTime = err?.timings?.phases?.total || 0;
|
|
httpResponse.payload = null;
|
|
httpResponse.timings = err?.timings || {};
|
|
return httpResponse;
|
|
}
|
|
err.service = this.SERVICE_NAME;
|
|
err.method = "requestHttp";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
private async requestPageSpeed(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
const url = monitor.url;
|
|
if (!url) {
|
|
throw new Error("Monitor URL is required");
|
|
}
|
|
let pageSpeedUrl = `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance`;
|
|
const dbSettings = await this.settingsService.getDBSettings();
|
|
if (dbSettings?.pagespeedApiKey) {
|
|
pageSpeedUrl += `&key=${dbSettings.pagespeedApiKey}`;
|
|
} else {
|
|
this.logger.warn({
|
|
message: "PageSpeed API key not found, job not executed",
|
|
service: this.SERVICE_NAME,
|
|
method: "requestPagespeed",
|
|
details: { url },
|
|
});
|
|
}
|
|
return await this.requestHttp({
|
|
...monitor,
|
|
url: pageSpeedUrl,
|
|
});
|
|
} catch (err: any) {
|
|
err.service = this.SERVICE_NAME;
|
|
err.method = "requestPageSpeed";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
private async requestHardware(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
return await this.requestHttp(monitor);
|
|
} catch (err: any) {
|
|
err.service = this.SERVICE_NAME;
|
|
err.method = "requestHardware";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
private async requestDocker(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
if (!monitor.url) {
|
|
throw new Error("Monitor URL is required");
|
|
}
|
|
|
|
const docker = new this.Docker({
|
|
socketPath: "/var/run/docker.sock",
|
|
handleError: true, // Enable error handling
|
|
});
|
|
|
|
const dockerResponse = this.buildStatusResponse({
|
|
monitor,
|
|
overrides: {
|
|
status: false,
|
|
code: this.NETWORK_ERROR,
|
|
message: "No response",
|
|
},
|
|
});
|
|
|
|
const containers = await docker.listContainers({ all: true });
|
|
|
|
// Normalize input: strip leading slashes and convert to lowercase for comparison
|
|
const normalizedInput = monitor.url.replace(/^\/+/, "").toLowerCase();
|
|
|
|
// Priority-based matching to avoid ambiguity:
|
|
// 1. Exact full ID match (64-char)
|
|
let exactIdMatch = containers.find((c: any) => c.Id.toLowerCase() === normalizedInput);
|
|
|
|
// 2. Exact container name match (case-insensitive)
|
|
let exactNameMatch = containers.find((c: any) =>
|
|
c.Names.some((name: string) => {
|
|
const cleanName = name.replace(/^\/+/, "").toLowerCase();
|
|
return cleanName === normalizedInput;
|
|
})
|
|
);
|
|
|
|
// 3. Partial ID match (fallback for backwards compatibility)
|
|
let partialIdMatch = containers.find((c: any) => c.Id.toLowerCase().startsWith(normalizedInput));
|
|
|
|
// Select container based on priority
|
|
let targetContainer = exactIdMatch || exactNameMatch || partialIdMatch;
|
|
|
|
// Return negative response if no container
|
|
if (!targetContainer) {
|
|
this.logger.warn({
|
|
message: `No container found for "${monitor.url}".`,
|
|
service: this.SERVICE_NAME,
|
|
method: "requestDocker",
|
|
details: { url: monitor.url },
|
|
});
|
|
|
|
dockerResponse.code = 404;
|
|
dockerResponse.status = false;
|
|
dockerResponse.message = "Docker container not found";
|
|
return dockerResponse;
|
|
}
|
|
|
|
// Return negative response if ambiguous matches exist
|
|
const matchTypes = [];
|
|
if (exactIdMatch) matchTypes.push("exact ID");
|
|
if (exactNameMatch) matchTypes.push("exact name");
|
|
if (partialIdMatch && !exactIdMatch) matchTypes.push("partial ID");
|
|
|
|
if (matchTypes.length > 1) {
|
|
this.logger.warn({
|
|
message: `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`,
|
|
service: this.SERVICE_NAME,
|
|
method: "requestDocker",
|
|
details: { url: monitor.url },
|
|
});
|
|
dockerResponse.status = false;
|
|
dockerResponse.message = `Ambiguous container match for "${monitor.url}". Matched by: ${matchTypes.join(", ")}. Using ${exactIdMatch ? "exact ID" : exactNameMatch ? "exact name" : "partial ID"} match.`;
|
|
return dockerResponse;
|
|
}
|
|
|
|
const container = docker.getContainer(targetContainer.Id);
|
|
const { response, responseTime, error }: { response?: any; responseTime?: number; error?: any } = await this.timeRequest(() =>
|
|
container.inspect()
|
|
);
|
|
|
|
dockerResponse.responseTime = responseTime;
|
|
dockerResponse.status = response?.State?.Status === "running" ? true : false;
|
|
dockerResponse.code = 200;
|
|
dockerResponse.message = "Docker container status fetched successfully";
|
|
|
|
if (error) {
|
|
dockerResponse.status = false;
|
|
dockerResponse.code = error.statusCode || this.NETWORK_ERROR;
|
|
dockerResponse.message = error.reason || "Failed to fetch Docker container information";
|
|
return dockerResponse;
|
|
}
|
|
|
|
return dockerResponse;
|
|
} catch (err: any) {
|
|
err.service = this.SERVICE_NAME;
|
|
err.method = "requestDocker";
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
private async requestPort(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
const { url, port } = monitor;
|
|
const { response, responseTime, error } = await this.timeRequest(async () => {
|
|
return new Promise((resolve, reject) => {
|
|
const socket = this.net.createConnection(
|
|
{
|
|
host: url,
|
|
port,
|
|
},
|
|
() => {
|
|
socket.end();
|
|
socket.destroy();
|
|
resolve({ success: true });
|
|
}
|
|
);
|
|
|
|
socket.setTimeout(5000);
|
|
socket.on("timeout", () => {
|
|
socket.destroy();
|
|
reject(new Error("Connection timeout"));
|
|
});
|
|
|
|
socket.on("error", (err: any) => {
|
|
socket.destroy();
|
|
reject(err);
|
|
});
|
|
});
|
|
});
|
|
|
|
const portResponse = this.buildStatusResponse({
|
|
monitor,
|
|
overrides: {
|
|
code: 200,
|
|
status: (response as { success?: boolean })?.success ?? false,
|
|
message: "Port check successful",
|
|
responseTime,
|
|
},
|
|
});
|
|
|
|
if (error) {
|
|
portResponse.code = this.NETWORK_ERROR;
|
|
portResponse.status = false;
|
|
portResponse.message = "Port check failed";
|
|
return portResponse;
|
|
}
|
|
|
|
return portResponse;
|
|
} catch (error: any) {
|
|
error.service = this.SERVICE_NAME;
|
|
error.method = "requestTCP";
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async requestGame(monitor: Monitor): Promise<MonitorStatusResponse> {
|
|
try {
|
|
const { url, port, gameId } = monitor;
|
|
|
|
const gameResponse = this.buildStatusResponse({
|
|
monitor,
|
|
overrides: {
|
|
code: 200,
|
|
status: true,
|
|
message: "Success",
|
|
},
|
|
});
|
|
|
|
const state = await this.GameDig.query({
|
|
type: gameId,
|
|
host: url,
|
|
port: port,
|
|
}).catch((error: any) => {
|
|
this.logger.warn({
|
|
message: error.message,
|
|
service: this.SERVICE_NAME,
|
|
method: "requestGame",
|
|
details: { url, port, gameId },
|
|
});
|
|
});
|
|
|
|
if (!state) {
|
|
gameResponse.code = this.NETWORK_ERROR;
|
|
gameResponse.status = false;
|
|
gameResponse.message = "No response";
|
|
return gameResponse;
|
|
}
|
|
|
|
gameResponse.responseTime = state.ping;
|
|
gameResponse.payload = state;
|
|
return gameResponse;
|
|
} catch (error: any) {
|
|
error.service = this.SERVICE_NAME;
|
|
error.method = "requestPing";
|
|
throw error;
|
|
}
|
|
}
|
|
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
|
|
return {
|
|
monitorId: "unknown",
|
|
teamId: "unknown",
|
|
type: "unknown",
|
|
status: false,
|
|
code: this.NETWORK_ERROR,
|
|
message: `Unsupported type: ${type}`,
|
|
};
|
|
}
|
|
|
|
// Other network requests unrelated to monitoring:
|
|
async requestWebhook(type: string, url: string, body: any) {
|
|
try {
|
|
const response = await this.axios.post(url, body, {
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
return {
|
|
type: "webhook",
|
|
status: true,
|
|
code: response.status,
|
|
message: `Successfully sent ${type} notification`,
|
|
payload: response.data,
|
|
};
|
|
} catch (error: any) {
|
|
this.logger.warn({
|
|
message: error.message,
|
|
service: this.SERVICE_NAME,
|
|
method: "requestWebhook",
|
|
});
|
|
|
|
return {
|
|
type: "webhook",
|
|
status: false,
|
|
code: error.response?.status || this.NETWORK_ERROR,
|
|
message: `Failed to send ${type} notification`,
|
|
payload: error.response?.data,
|
|
};
|
|
}
|
|
}
|
|
|
|
async requestPagerDuty({ message, routingKey, monitorUrl }: { message: string; routingKey: string; monitorUrl: string }) {
|
|
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(),
|
|
},
|
|
});
|
|
|
|
if (response?.data?.status !== "success") return false;
|
|
return true;
|
|
} catch (error: any) {
|
|
error.details = error.response?.data;
|
|
error.service = this.SERVICE_NAME;
|
|
error.method = "requestPagerDuty";
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
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",
|
|
};
|
|
} catch (error: any) {
|
|
this.logger.warn({
|
|
message: error.message,
|
|
service: this.SERVICE_NAME,
|
|
method: "requestMatrix",
|
|
});
|
|
|
|
return {
|
|
status: false,
|
|
code: error.response?.status || this.NETWORK_ERROR,
|
|
message: "Failed to send Matrix notification",
|
|
payload: error.response?.data,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export default NetworkService;
|