Files
Checkmate/server/src/service/infrastructure/network/HttpProvider.ts
T
harsh-aghara dd16ac060a fix: run advanced matcher on HTTP errors with custom up codes
When customUpCodes mark an HTTPError as up, parse the response body, apply advancedMatcher.validate, and set status to statusUp && matchResult.ok. Return parsed payload and extracted values to match the success path.
2026-06-05 00:35:30 +05:30

174 lines
4.8 KiB
TypeScript

import { type Got, HTTPError, RequestError } from "got";
import { IAdvancedMatcher } from "@/service/infrastructure/network/AdvancedMatcher.js";
import { IStatusProvider } from "@/service/infrastructure/network/IStatusProvider.js";
import { HttpStatusPayload } from "@/types/network.js";
import { MonitorStatusResponse } from "@/types/network.js";
import { Agent as HttpsAgent } from "https";
import { Monitor, MonitorType } from "@/types/monitor.js";
import { NETWORK_ERROR, isStatusUp } from "@/service/infrastructure/network/utils.js";
import CacheableLookup from "cacheable-lookup";
export class HttpProvider implements IStatusProvider<HttpStatusPayload> {
readonly type = "http";
constructor(
private got: Got,
private advancedMatcher: IAdvancedMatcher
) {
const cacheable = new CacheableLookup({ maxTtl: 300, errorTtl: 30 });
this.got = got.extend({
dnsCache: cacheable,
timeout: {
request: 30000,
},
retry: { limit: 1 },
});
}
supports(type: MonitorType) {
return type === "http";
}
private handleHttpError<T>(error: unknown, monitor: Monitor): MonitorStatusResponse<T> {
if (error instanceof HTTPError || error instanceof RequestError) {
const statusCode = error.response?.statusCode;
const statusUp = isStatusUp(statusCode, monitor.customUpCodes);
const responseTime = error.timings?.phases?.total ?? 0;
if (!statusUp) {
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: false,
code: statusCode ?? NETWORK_ERROR,
message: error.message,
responseTime,
timings: error.timings,
payload: null as T,
};
}
const { jsonPath } = monitor;
const body = error.response?.body ?? "";
const contentType = error.response?.headers?.["content-type"] || "";
const isJson = contentType.includes("application/json");
if (jsonPath && !isJson) {
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: false,
code: statusCode ?? NETWORK_ERROR,
message: "Response is not JSON",
responseTime,
timings: error.timings,
payload: body as unknown as T,
};
}
let payload: T;
if (isJson) {
try {
payload = JSON.parse(body) as T;
} catch {
payload = body as unknown as T;
}
} else {
payload = body as unknown as T;
}
const matchResult = this.advancedMatcher.validate<T>(payload, monitor);
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: statusUp && matchResult.ok,
code: statusCode ?? NETWORK_ERROR,
message: matchResult.ok ? error.message : matchResult.message,
responseTime,
timings: error.timings,
payload,
extracted: matchResult.extracted,
};
}
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: false,
code: NETWORK_ERROR,
message: error instanceof Error ? error.message : String(error),
responseTime: 0,
payload: null as T,
};
}
async handle<T>(monitor: Monitor): Promise<MonitorStatusResponse<T>> {
const { url, secret, jsonPath, ignoreTlsErrors } = monitor;
if (!url) {
throw new Error("URL is required for HTTP monitor");
}
const options: Record<string, unknown> = {
headers: monitor.secret ? { Authorization: `Bearer ${secret}` } : undefined,
};
options.agent = {
https: new HttpsAgent({ rejectUnauthorized: !ignoreTlsErrors }),
};
try {
const response = await this.got<string>(url, options);
const contentType = response.headers["content-type"] || "";
const isJson = contentType.includes("application/json");
if (jsonPath && !isJson) {
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: false,
code: response.statusCode,
message: "Response is not JSON",
responseTime: response.timings.phases.total ?? 0,
timings: response.timings,
payload: response.body as unknown as T,
};
}
let payload: T;
if (isJson) {
try {
payload = JSON.parse(response.body) as T;
} catch {
payload = response.body as unknown as T;
}
} else {
payload = response.body as unknown as T;
}
const matchResult = this.advancedMatcher.validate<T>(payload, monitor);
const statusUp = isStatusUp(response.statusCode, monitor.customUpCodes);
return {
monitorId: monitor.id,
teamId: monitor.teamId,
type: monitor.type,
status: statusUp && matchResult.ok,
code: response.statusCode,
message: matchResult.ok ? (response.statusMessage ?? "OK") : matchResult.message,
responseTime: response.timings.phases.total ?? 0,
timings: response.timings,
payload,
extracted: matchResult.extracted,
};
} catch (error: unknown) {
return this.handleHttpError(error, monitor);
}
}
}