mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-05-14 13:38:39 -05:00
geochecks iniital commit
This commit is contained in:
@@ -8,7 +8,7 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
const LogsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const [activeTab, setActiveTab] = useState<number>(2);
|
||||
const [activeTab, setActiveTab] = useState<number>(1);
|
||||
return (
|
||||
<BasePage>
|
||||
<Tabs
|
||||
|
||||
@@ -17,6 +17,8 @@ import SuperSimpleQueueHelper from "../service/infrastructure/SuperSimpleQueue/S
|
||||
import SuperSimpleQueue from "../service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import UserService from "../service/business/userService.js";
|
||||
import CheckService from "../service/business/checkService.js";
|
||||
import GeoChecksService from "../service/business/geoChecksService.js";
|
||||
import GlobalPingService from "../service/infrastructure/globalPingService.js";
|
||||
import DiagnosticService from "../service/business/diagnosticService.js";
|
||||
import InviteService from "../service/business/inviteService.js";
|
||||
import MaintenanceWindowService from "../service/business/maintenanceWindowService.js";
|
||||
@@ -83,6 +85,7 @@ export type InitializedServices = {
|
||||
jobQueue: any;
|
||||
userService: any;
|
||||
checkService: any;
|
||||
geoChecksService: any;
|
||||
diagnosticService: any;
|
||||
inviteService: any;
|
||||
maintenanceWindowService: any;
|
||||
@@ -171,7 +174,21 @@ export const initializeServices = async ({
|
||||
checksRepository,
|
||||
});
|
||||
|
||||
const bufferService = new BufferService({ logger, checkService, settingsService });
|
||||
const globalPingService = new GlobalPingService({ logger });
|
||||
|
||||
// Create geoChecksService with circular dependency workaround
|
||||
const geoChecksService = new GeoChecksService({
|
||||
logger,
|
||||
geoChecksRepository,
|
||||
globalPingService,
|
||||
bufferService: null as any,
|
||||
settingsService,
|
||||
});
|
||||
|
||||
const bufferService = new BufferService({ logger, checkService, geoChecksService, settingsService });
|
||||
|
||||
// Set bufferService reference
|
||||
(geoChecksService as any).bufferService = bufferService;
|
||||
|
||||
const statusService = new StatusService(logger, bufferService, monitorsRepository, monitorStatsRepository, checksRepository);
|
||||
|
||||
@@ -210,6 +227,7 @@ export const initializeServices = async ({
|
||||
monitorStatsRepository,
|
||||
checksRepository,
|
||||
incidentsRepository,
|
||||
geoChecksService,
|
||||
});
|
||||
|
||||
const superSimpleQueue = await SuperSimpleQueue.create({
|
||||
@@ -274,6 +292,7 @@ export const initializeServices = async ({
|
||||
maintenanceWindowService,
|
||||
monitorService,
|
||||
incidentService,
|
||||
geoChecksService,
|
||||
logger,
|
||||
notificationsService,
|
||||
statusPageService,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { GeoCheck, GroupedGeoCheck } from "@/types/geoCheck.js";
|
||||
|
||||
export interface IGeoChecksRepository {
|
||||
create(geoCheck: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">): Promise<GeoCheck>;
|
||||
create(geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]>;
|
||||
findByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date): Promise<GeoCheck[]>;
|
||||
findGroupedByMonitorIdAndDateRange(monitorId: string, startDate: Date, endDate: Date, dateFormat: string): Promise<GroupedGeoCheck[]>;
|
||||
deleteByMonitorId(monitorId: string): Promise<number>;
|
||||
|
||||
@@ -72,23 +72,26 @@ class MongoGeoChecksRepository implements IGeoChecksRepository {
|
||||
};
|
||||
};
|
||||
|
||||
create = async (geoCheck: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">): Promise<GeoCheck> => {
|
||||
create = async (geoChecks: Omit<GeoCheck, "id" | "__v" | "createdAt" | "updatedAt">[]): Promise<GeoCheck[]> => {
|
||||
try {
|
||||
const doc = await GeoCheckModel.create({
|
||||
metadata: {
|
||||
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
|
||||
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
|
||||
type: geoCheck.metadata.type,
|
||||
},
|
||||
results: geoCheck.results,
|
||||
expiry: new Date(geoCheck.expiry),
|
||||
});
|
||||
return this.toEntity(doc);
|
||||
const docs = await GeoCheckModel.insertMany(
|
||||
geoChecks.map((geoCheck) => ({
|
||||
metadata: {
|
||||
monitorId: new mongoose.Types.ObjectId(geoCheck.metadata.monitorId),
|
||||
teamId: new mongoose.Types.ObjectId(geoCheck.metadata.teamId),
|
||||
type: geoCheck.metadata.type,
|
||||
},
|
||||
results: geoCheck.results,
|
||||
expiry: new Date(geoCheck.expiry),
|
||||
}))
|
||||
);
|
||||
return docs.map((doc) => this.toEntity(doc));
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: `Error creating geo check: ${error.message}`,
|
||||
message: `Failed to create geo checks: ${error.message}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "create",
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import type { Monitor, GeoCheck } from "@/types/index.js";
|
||||
import { Types } from "mongoose";
|
||||
import type { IGeoChecksRepository } from "@/repositories/index.js";
|
||||
import type { IGlobalPingService } from "@/service/infrastructure/globalPingService.js";
|
||||
import type { IBufferService } from "@/service/infrastructure/bufferService.js";
|
||||
|
||||
const SERVICE_NAME = "GeoChecksService";
|
||||
|
||||
export interface IGeoChecksService {
|
||||
readonly serviceName: string;
|
||||
executeGeoCheck(monitor: Monitor): Promise<void>;
|
||||
}
|
||||
|
||||
class GeoChecksService implements IGeoChecksService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private logger: any;
|
||||
private geoChecksRepository: IGeoChecksRepository;
|
||||
private globalPingService: IGlobalPingService;
|
||||
private bufferService: IBufferService;
|
||||
private TTL_DAYS: number;
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
geoChecksRepository,
|
||||
globalPingService,
|
||||
bufferService,
|
||||
settingsService,
|
||||
}: {
|
||||
logger: any;
|
||||
geoChecksRepository: IGeoChecksRepository;
|
||||
globalPingService: IGlobalPingService;
|
||||
bufferService: IBufferService;
|
||||
settingsService: any;
|
||||
}) {
|
||||
this.logger = logger;
|
||||
this.geoChecksRepository = geoChecksRepository;
|
||||
this.globalPingService = globalPingService;
|
||||
this.bufferService = bufferService;
|
||||
this.TTL_DAYS = settingsService.getSettings().checksTTL || 90;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return GeoChecksService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a geo-distributed check for a monitor
|
||||
* 1. Create measurement request with GlobalPing API
|
||||
* 2. Poll for results (with 30s timeout)
|
||||
* 3. Transform and save results to buffer
|
||||
*/
|
||||
async executeGeoCheck(monitor: Monitor): Promise<void> {
|
||||
try {
|
||||
if (!monitor.url) {
|
||||
this.logger.warn({
|
||||
message: "Monitor missing URL for geo check",
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!monitor.geoCheckLocations || monitor.geoCheckLocations.length === 0) {
|
||||
this.logger.warn({
|
||||
message: "Monitor missing geo check locations",
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Create measurement request
|
||||
const measurementId = await this.globalPingService.createMeasurement(monitor.url, monitor.geoCheckLocations);
|
||||
|
||||
if (!measurementId) {
|
||||
// GlobalPing API is down, skip this check
|
||||
this.logger.debug({
|
||||
message: "Skipping geo check due to API unavailability",
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Poll for results
|
||||
const results = await this.globalPingService.pollForResults(measurementId);
|
||||
|
||||
if (results.length === 0) {
|
||||
// No successful results (all locations timed out or failed)
|
||||
this.logger.debug({
|
||||
message: "No successful geo check results",
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id, measurementId },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Build GeoCheck document
|
||||
const geoCheck = this.buildGeoCheck(monitor, results);
|
||||
|
||||
// Step 4: Add to buffer for batched insertion
|
||||
this.bufferService.addGeoCheckToBuffer(geoCheck);
|
||||
|
||||
this.logger.debug({
|
||||
message: `Geo check completed for monitor ${monitor.id}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id, resultsCount: results.length },
|
||||
});
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: "Error executing geo check",
|
||||
service: SERVICE_NAME,
|
||||
method: "executeGeoCheck",
|
||||
details: { monitorId: monitor.id, error: error.message },
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private buildGeoCheck(monitor: Monitor, results: any[]): GeoCheck {
|
||||
const now = new Date();
|
||||
const expiryDate = new Date(now.getTime() + this.TTL_DAYS * 24 * 60 * 60 * 1000);
|
||||
|
||||
return {
|
||||
id: new Types.ObjectId().toString(),
|
||||
metadata: {
|
||||
monitorId: monitor.id,
|
||||
teamId: monitor.teamId,
|
||||
type: monitor.type,
|
||||
},
|
||||
results,
|
||||
expiry: expiryDate.toISOString(),
|
||||
__v: 0,
|
||||
createdAt: now.toISOString(),
|
||||
updatedAt: now.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create geo checks (called by buffer service)
|
||||
*/
|
||||
createGeoChecks = async (geoChecks: GeoCheck[]) => {
|
||||
return this.geoChecksRepository.create(geoChecks);
|
||||
};
|
||||
}
|
||||
|
||||
export default GeoChecksService;
|
||||
@@ -4,6 +4,7 @@ import { AppError } from "@/utils/AppError.js";
|
||||
import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js";
|
||||
import type { StatusChangeResult, MonitorStatusResponse, HardwareStatusPayload, MonitorStatus } from "@/types/index.js";
|
||||
import IncidentService from "@/service/business/incidentService.js";
|
||||
import type { IGeoChecksService } from "@/service/business/geoChecksService.js";
|
||||
import {
|
||||
IMaintenanceWindowsRepository,
|
||||
IMonitorsRepository,
|
||||
@@ -43,6 +44,7 @@ class SuperSimpleQueueHelper {
|
||||
private monitorStatsRepository: IMonitorStatsRepository;
|
||||
private checksRepository: IChecksRepository;
|
||||
private incidentsRepository: IIncidentsRepository;
|
||||
private geoChecksService: IGeoChecksService;
|
||||
|
||||
constructor({
|
||||
logger,
|
||||
@@ -58,6 +60,7 @@ class SuperSimpleQueueHelper {
|
||||
monitorStatsRepository,
|
||||
checksRepository,
|
||||
incidentsRepository,
|
||||
geoChecksService,
|
||||
}: {
|
||||
logger: any;
|
||||
networkService: INetworkService;
|
||||
@@ -72,6 +75,7 @@ class SuperSimpleQueueHelper {
|
||||
monitorStatsRepository: IMonitorStatsRepository;
|
||||
checksRepository: IChecksRepository;
|
||||
incidentsRepository: IIncidentsRepository;
|
||||
geoChecksService: IGeoChecksService;
|
||||
}) {
|
||||
this.logger = logger;
|
||||
this.networkService = networkService;
|
||||
@@ -86,6 +90,7 @@ class SuperSimpleQueueHelper {
|
||||
this.monitorStatsRepository = monitorStatsRepository;
|
||||
this.checksRepository = checksRepository;
|
||||
this.incidentsRepository = incidentsRepository;
|
||||
this.geoChecksService = geoChecksService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -301,23 +306,8 @@ class SuperSimpleQueueHelper {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Call GlobalPing API
|
||||
// TODO: Implement GlobalPing API service
|
||||
// const measurementId = await this.globalPingService.createMeasurement(monitor.url, monitor.geoCheckLocations);
|
||||
// const geoResults = await this.globalPingService.pollForResults(measurementId);
|
||||
|
||||
// Step 4: Create GeoCheck document
|
||||
// TODO: Build and save GeoCheck
|
||||
// const geoCheck = {
|
||||
// metadata: {
|
||||
// monitorId,
|
||||
// teamId,
|
||||
// type: monitor.type,
|
||||
// },
|
||||
// results: geoResults,
|
||||
// expiry: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
// };
|
||||
// await this.geoChecksRepository.create(geoCheck);
|
||||
// Step 3: Execute geo check (handles API calls, polling, and saving)
|
||||
await this.geoChecksService.executeGeoCheck(monitor);
|
||||
|
||||
this.logger.debug({
|
||||
message: `Geo check job executed for monitor ${monitorId}`,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Check } from "@/types/index.js";
|
||||
import type { GeoCheck } from "@/types/index.js";
|
||||
|
||||
const SERVICE_NAME = "BufferService";
|
||||
|
||||
export interface IBufferService {
|
||||
addToBuffer(check: Check): void;
|
||||
addGeoCheckToBuffer(geoCheck: GeoCheck): void;
|
||||
removeCheckFromBuffer(check: Check): boolean;
|
||||
scheduleNextFlush(): void;
|
||||
flushBuffer(): Promise<void>;
|
||||
@@ -15,15 +17,29 @@ class BufferService implements IBufferService {
|
||||
private logger: any;
|
||||
private SERVICE_NAME: string;
|
||||
private buffer: any[];
|
||||
private geoBuffer: any[];
|
||||
private bufferTimer: NodeJS.Timeout | null = null;
|
||||
private checksService: any;
|
||||
private geoChecksService: any;
|
||||
|
||||
constructor({ logger, checkService, settingsService }: { logger: any; checkService: any; settingsService: any }) {
|
||||
constructor({
|
||||
logger,
|
||||
checkService,
|
||||
geoChecksService,
|
||||
settingsService,
|
||||
}: {
|
||||
logger: any;
|
||||
checkService: any;
|
||||
geoChecksService?: any;
|
||||
settingsService: any;
|
||||
}) {
|
||||
this.BUFFER_TIMEOUT = settingsService.getSettings().nodeEnv === "development" ? 10 : 1000 * 60 * 1; // 1 minute
|
||||
this.logger = logger;
|
||||
this.checksService = checkService;
|
||||
this.geoChecksService = geoChecksService;
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.buffer = [];
|
||||
this.geoBuffer = [];
|
||||
this.scheduleNextFlush();
|
||||
this.logger.info({
|
||||
message: `Buffer service initialized, flushing every ${this.BUFFER_TIMEOUT / 1000}s`,
|
||||
@@ -49,6 +65,19 @@ class BufferService implements IBufferService {
|
||||
}
|
||||
}
|
||||
|
||||
addGeoCheckToBuffer(geoCheck: GeoCheck) {
|
||||
try {
|
||||
this.geoBuffer.push(geoCheck);
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "addGeoCheckToBuffer",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
removeCheckFromBuffer(checkToRemove: Check) {
|
||||
try {
|
||||
if (!checkToRemove) {
|
||||
@@ -112,6 +141,10 @@ class BufferService implements IBufferService {
|
||||
if (this.buffer.length > 0) {
|
||||
await this.checksService.createChecks(this.buffer);
|
||||
}
|
||||
|
||||
if (this.geoBuffer.length > 0 && this.geoChecksService) {
|
||||
await this.geoChecksService.createGeoChecks(this.geoBuffer);
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
@@ -122,6 +155,7 @@ class BufferService implements IBufferService {
|
||||
}
|
||||
|
||||
this.buffer = [];
|
||||
this.geoBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
import type { GeoContinent, GeoCheckResult, GeoCheckTimings, GeoCheckLocation } from "@/types/geoCheck.js";
|
||||
import got from "got";
|
||||
|
||||
const SERVICE_NAME = "GlobalPingService";
|
||||
const GLOBAL_PING_API_BASE = "https://api.globalping.io/v1";
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const MAX_POLL_TIMEOUT_MS = 30000;
|
||||
|
||||
interface GlobalPingMeasurementRequest {
|
||||
type: "http";
|
||||
target: string;
|
||||
locations: Array<{ continent: GeoContinent }>;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
interface GlobalPingMeasurementResponse {
|
||||
id: string;
|
||||
type: string;
|
||||
status: "in-progress" | "finished" | "failed";
|
||||
probesCount: number;
|
||||
results?: GlobalPingProbeResult[];
|
||||
}
|
||||
|
||||
interface GlobalPingProbeResult {
|
||||
probe: {
|
||||
continent: GeoContinent;
|
||||
region: string;
|
||||
country: string;
|
||||
state: string | null;
|
||||
city: string;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
};
|
||||
result: {
|
||||
status: "finished" | "failed" | "timeout";
|
||||
statusCode?: number;
|
||||
statusCodeName?: string;
|
||||
timings?: {
|
||||
total: number;
|
||||
dns: number;
|
||||
tcp: number;
|
||||
tls: number;
|
||||
firstByte: number;
|
||||
download: number;
|
||||
};
|
||||
rawOutput?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IGlobalPingService {
|
||||
readonly serviceName: string;
|
||||
createMeasurement(url: string, locations: GeoContinent[]): Promise<string | null>;
|
||||
pollForResults(measurementId: string, timeoutMs?: number): Promise<GeoCheckResult[]>;
|
||||
}
|
||||
|
||||
class GlobalPingService implements IGlobalPingService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private logger: any;
|
||||
|
||||
constructor({ logger }: { logger: any }) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return GlobalPingService.SERVICE_NAME;
|
||||
}
|
||||
|
||||
async createMeasurement(url: string, locations: GeoContinent[]): Promise<string | null> {
|
||||
try {
|
||||
const requestBody: GlobalPingMeasurementRequest = {
|
||||
type: "http",
|
||||
target: url,
|
||||
locations: locations.map((continent) => ({ continent })),
|
||||
limit: locations.length,
|
||||
};
|
||||
|
||||
const response = await got.post<GlobalPingMeasurementResponse>(`${GLOBAL_PING_API_BASE}/measurements`, {
|
||||
json: requestBody,
|
||||
responseType: "json",
|
||||
timeout: { request: 10000 },
|
||||
});
|
||||
|
||||
const measurementId = response.body.id;
|
||||
|
||||
this.logger.debug({
|
||||
message: `Created GlobalPing measurement: ${measurementId}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "createMeasurement",
|
||||
details: { measurementId, url, locations },
|
||||
});
|
||||
|
||||
return measurementId;
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: "GlobalPing API unavailable, skipping geo check",
|
||||
service: SERVICE_NAME,
|
||||
method: "createMeasurement",
|
||||
details: error.message,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async pollForResults(measurementId: string, timeoutMs: number = MAX_POLL_TIMEOUT_MS): Promise<GeoCheckResult[]> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
try {
|
||||
const response = await got.get<GlobalPingMeasurementResponse>(`${GLOBAL_PING_API_BASE}/measurements/${measurementId}`, {
|
||||
responseType: "json",
|
||||
timeout: { request: 5000 },
|
||||
});
|
||||
|
||||
const measurement = response.body;
|
||||
|
||||
if (measurement.status === "finished") {
|
||||
const results = this.transformResults(measurement.results || []);
|
||||
this.logger.debug({
|
||||
message: `GlobalPing measurement completed: ${measurementId}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "pollForResults",
|
||||
details: { measurementId, resultsCount: results.length },
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
if (measurement.status === "failed") {
|
||||
this.logger.warn({
|
||||
message: `GlobalPing measurement failed: ${measurementId}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "pollForResults",
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
// Still in-progress, wait and poll again
|
||||
await this.sleep(POLL_INTERVAL_MS);
|
||||
} catch (error: any) {
|
||||
this.logger.error({
|
||||
message: "Error polling GlobalPing API",
|
||||
service: SERVICE_NAME,
|
||||
method: "pollForResults",
|
||||
details: error.message,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Timeout reached
|
||||
this.logger.warn({
|
||||
message: `GlobalPing measurement polling timeout: ${measurementId}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "pollForResults",
|
||||
details: { measurementId, timeoutMs },
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
private transformResults(probeResults: GlobalPingProbeResult[]): GeoCheckResult[] {
|
||||
const successfulResults: GeoCheckResult[] = [];
|
||||
|
||||
for (const probeResult of probeResults) {
|
||||
// Skip failed or timeout results
|
||||
if (probeResult.result.status !== "finished" || !probeResult.result.statusCode || !probeResult.result.timings) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const location: GeoCheckLocation = {
|
||||
continent: probeResult.probe.continent,
|
||||
region: probeResult.probe.region,
|
||||
country: probeResult.probe.country,
|
||||
state: probeResult.probe.state || "",
|
||||
city: probeResult.probe.city,
|
||||
longitude: probeResult.probe.longitude,
|
||||
latitude: probeResult.probe.latitude,
|
||||
};
|
||||
|
||||
const timings: GeoCheckTimings = {
|
||||
total: probeResult.result.timings.total,
|
||||
dns: probeResult.result.timings.dns,
|
||||
tcp: probeResult.result.timings.tcp,
|
||||
tls: probeResult.result.timings.tls,
|
||||
firstByte: probeResult.result.timings.firstByte,
|
||||
download: probeResult.result.timings.download,
|
||||
};
|
||||
|
||||
const result: GeoCheckResult = {
|
||||
location,
|
||||
status: probeResult.result.statusCode >= 200 && probeResult.result.statusCode < 300,
|
||||
statusCode: probeResult.result.statusCode,
|
||||
timings,
|
||||
};
|
||||
|
||||
successfulResults.push(result);
|
||||
}
|
||||
|
||||
return successfulResults;
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalPingService;
|
||||
Reference in New Issue
Block a user