2024-10-15 11:57:37 +08:00
import { errorMessages , successMessages } from "../utils/messages.js" ;
2024-06-17 10:47:36 -07:00
2024-10-01 09:55:14 +08:00
/**
* NetworkService
*
* This service handles all network requests on the back end
* This includes pings, http requests, and pagespeed checks
*/
2024-06-17 10:47:36 -07:00
class NetworkService {
2024-10-18 11:12:18 +08:00
/**
* Creates an instance of NetworkService.
*
* @param {Object} db - The database service.
* @param {Object} emailService - The email service.
* @param {Object} axios - The axios HTTP client.
* @param {Object} ping - The ping service.
* @param {Object} logger - The logging service.
* @param {Object} http - The HTTP service.
*/
constructor ( db , emailService , axios , ping , logger , http ) {
2024-10-17 12:07:30 +08:00
this . db = db ;
this . emailService = emailService ;
this . TYPE_PING = "ping" ;
this . TYPE_HTTP = "http" ;
this . TYPE_PAGESPEED = "pagespeed" ;
2024-10-21 11:55:44 +08:00
this . TYPE_HARDWARE = "hardware" ;
2024-10-17 12:07:30 +08:00
this . SERVICE_NAME = "NetworkService" ;
this . NETWORK_ERROR = 5000 ;
2024-10-18 11:12:18 +08:00
this . axios = axios ;
this . ping = ping ;
this . logger = logger ;
this . http = http ;
2024-10-17 12:07:30 +08:00
}
2024-06-17 10:47:36 -07:00
2024-10-18 11:12:18 +08:00
/**
* Handles the notification process for a monitor.
*
* @param {Object} monitor - The monitor object containing monitor details.
* @param {boolean} isAlive - The status of the monitor (true if up, false if down).
* @returns {Promise<void>}
*/ async handleNotification ( monitor , isAlive ) {
2024-10-17 12:07:30 +08:00
try {
let template = isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate" ;
let status = isAlive === true ? "up" : "down" ;
2024-09-03 15:45:11 -07:00
2024-10-17 12:07:30 +08:00
const notifications = await this . db . getNotificationsByMonitorId ( monitor . _id );
for ( const notification of notifications ) {
if ( notification . type === "email" ) {
await this . emailService . buildAndSendEmail (
template ,
{ monitorName : monitor . name , monitorUrl : monitor . url },
notification . address ,
`Monitor ${ monitor . name } is ${ status } `
);
}
}
} catch ( error ) {
2024-10-24 15:28:13 +08:00
this . logger . error ({
message : error . message ,
2024-10-17 12:07:30 +08:00
service : this . SERVICE_NAME ,
2024-10-24 15:28:13 +08:00
method : "handleNotification" ,
details : `notification error for monitor: ${ monitor . _id } ` ,
stack : error . stack ,
2024-10-17 12:07:30 +08:00
});
}
}
2024-09-03 15:45:11 -07:00
2024-10-18 11:12:18 +08:00
/**
* Handles the status update for a monitor job.
*
* @param {Object} job - The job object containing job details.
* @param {boolean} isAlive - The status of the monitor (true if up, false if down).
* @returns {Promise<void>}
*/
2024-10-17 12:07:30 +08:00
async handleStatusUpdate ( job , isAlive ) {
let monitor ;
2024-10-18 13:36:39 +08:00
const { _id } = job . data ;
2024-10-17 12:07:30 +08:00
// Look up the monitor, if it doesn't exist, it's probably been removed, return
try {
monitor = await this . db . getMonitorById ( _id );
} catch ( error ) {
2024-10-24 15:28:13 +08:00
this . logger . error ({
message : error . message ,
service : this . SERVICE_NAME ,
method : "handleStatusUpdate" ,
stack : error . stack ,
details : `monitor lookup error for monitor: ${ _id } ` ,
});
2024-10-17 12:07:30 +08:00
return ;
}
2024-09-03 15:45:11 -07:00
2024-10-17 12:07:30 +08:00
// Otherwise, try to update monitor status
try {
if ( monitor . status === undefined || monitor . status !== isAlive ) {
const oldStatus = monitor . status ;
monitor . status = isAlive ;
await monitor . save ();
2024-09-02 11:00:59 -07:00
2024-10-17 12:07:30 +08:00
if ( oldStatus !== undefined && oldStatus !== isAlive ) {
this . handleNotification ( monitor , isAlive );
}
}
} catch ( error ) {
2024-10-24 15:28:13 +08:00
this . logger . error ({
message : error . message ,
2024-10-17 12:07:30 +08:00
service : this . SERVICE_NAME ,
2024-10-24 15:28:13 +08:00
method : "handleStatusUpdate" ,
stack : error . stack ,
details : `status update error for monitor: ${ _id } ` ,
2024-10-17 12:07:30 +08:00
});
}
}
2024-08-14 10:20:35 -07:00
2024-10-17 12:07:30 +08:00
/**
* Measures the response time of an asynchronous operation.
* @param {Function} operation - An asynchronous operation to measure.
* @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation.
* @throws {Error} The error object from the operation, contains response time.
*/
async measureResponseTime ( operation ) {
const startTime = Date . now ();
try {
const response = await operation ();
const endTime = Date . now ();
return { responseTime : endTime - startTime , response };
} catch ( error ) {
const endTime = Date . now ();
error . responseTime = endTime - startTime ;
2024-10-18 13:36:39 +08:00
error . service === undefined ? ( error . service = this . SERVICE_NAME ) : null ;
2024-10-17 12:07:30 +08:00
error . method === undefined ? ( error . method = "measureResponseTime" ) : null ;
throw error ;
}
}
2024-06-17 11:33:55 -07:00
2024-10-17 12:07:30 +08:00
/**
* Handles the ping operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handlePing ( job ) {
let isAlive ;
2024-08-14 10:20:35 -07:00
2024-10-18 14:21:22 +08:00
const operation = async () => {
const response = await this . ping . promise . probe ( job . data . url );
return response ;
};
2024-10-17 12:07:30 +08:00
try {
2024-10-18 14:21:22 +08:00
const { responseTime , response } = await this . measureResponseTime ( operation );
2024-10-17 12:07:30 +08:00
isAlive = response . alive ;
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
responseTime ,
message : isAlive
? successMessages . PING_SUCCESS
: errorMessages . PING_CANNOT_RESOLVE ,
};
2024-10-18 13:36:39 +08:00
await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-10-17 12:07:30 +08:00
} catch ( error ) {
isAlive = false ;
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
message : errorMessages . PING_CANNOT_RESOLVE ,
responseTime : error . responseTime ,
};
2024-10-18 13:36:39 +08:00
await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-10-17 12:07:30 +08:00
} finally {
this . handleStatusUpdate ( job , isAlive );
}
}
2024-06-17 10:47:36 -07:00
2024-10-17 12:07:30 +08:00
/**
* Handles the http operation for a given job, measures its response time, and logs the result.
* @param {Object} job - The job object containing data for the ping operation.
* @returns {Promise<{boolean}} The result of logging and storing the check
*/
async handleHttp ( job ) {
// Define operation for timing
const operation = async () => {
2024-10-18 11:12:18 +08:00
const response = await this . axios . get ( job . data . url );
2024-10-17 12:07:30 +08:00
return response ;
};
2024-06-18 11:42:25 -07:00
2024-10-17 12:07:30 +08:00
let isAlive ;
2024-08-14 10:20:35 -07:00
2024-10-17 12:07:30 +08:00
// attempt connection
try {
const { responseTime , response } = await this . measureResponseTime ( operation );
// check if response is in the 200 range, if so, service is up
isAlive = response . status >= 200 && response . status < 300 ;
2024-06-18 11:42:25 -07:00
2024-10-17 12:07:30 +08:00
//Create a check with relevant data
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
responseTime ,
statusCode : response . status ,
2024-10-18 11:12:18 +08:00
message : this . http . STATUS_CODES [ response . status ],
2024-10-17 12:07:30 +08:00
};
2024-10-18 13:36:39 +08:00
await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-10-17 12:07:30 +08:00
} catch ( error ) {
const statusCode = error . response ? . status || this . NETWORK_ERROR ;
2024-10-18 11:12:18 +08:00
let message = this . http . STATUS_CODES [ statusCode ] || "Network Error" ;
2024-10-17 12:07:30 +08:00
isAlive = false ;
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
statusCode ,
responseTime : error . responseTime ,
message ,
};
2024-10-18 14:21:22 +08:00
await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-10-17 12:07:30 +08:00
} finally {
this . handleStatusUpdate ( job , isAlive );
}
}
2024-06-18 11:42:25 -07:00
2024-10-17 12:07:30 +08:00
/**
* Handles PageSpeed job types by fetching and processing PageSpeed insights.
*
* This method sends a request to the Google PageSpeed Insights API to get performance metrics
* for the specified URL, then logs and stores the check results.
*
* @param {Object} job - The job object containing data related to the PageSpeed check.
* @param {string} job.data.url - The URL to be analyzed by the PageSpeed Insights API.
* @param {string} job.data._id - The unique identifier for the monitor associated with the check.
*
* @returns {Promise<void>} A promise that resolves when the check results have been logged and stored.
*
* @throws {Error} Throws an error if there is an issue with fetching or processing the PageSpeed insights.
*/
async handlePagespeed ( job ) {
let isAlive ;
try {
const url = job . data . url ;
2024-10-18 13:36:39 +08:00
2024-10-18 11:12:18 +08:00
const response = await this . axios . get (
2024-10-17 12:07:30 +08:00
`https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url= ${ url } &category=seo&category=accessibility&category=best-practices&category=performance`
);
const pageSpeedResults = response . data ;
const categories = pageSpeedResults . lighthouseResult ? . categories ;
const audits = pageSpeedResults . lighthouseResult ? . audits ;
const {
"cumulative-layout-shift" : cls ,
"speed-index" : si ,
"first-contentful-paint" : fcp ,
"largest-contentful-paint" : lcp ,
"total-blocking-time" : tbt ,
} = audits ;
// Weights
// First Contentful Paint 10%
// Speed Index 10%
// Largest Contentful Paint 25%
// Total Blocking Time 30%
// Cumulative Layout Shift 25%
2024-08-02 12:32:08 -07:00
2024-10-17 12:07:30 +08:00
isAlive = true ;
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
statusCode : response . status ,
2024-10-18 11:12:18 +08:00
message : this . http . STATUS_CODES [ response . status ],
2024-10-17 12:07:30 +08:00
accessibility : ( categories . accessibility ? . score || 0 ) * 100 ,
bestPractices : ( categories [ "best-practices" ] ? . score || 0 ) * 100 ,
seo : ( categories . seo ? . score || 0 ) * 100 ,
performance : ( categories . performance ? . score || 0 ) * 100 ,
audits : {
cls ,
si ,
fcp ,
lcp ,
tbt ,
},
};
this . logAndStoreCheck ( checkData , this . db . createPageSpeedCheck );
} catch ( error ) {
isAlive = false ;
const statusCode = error . response ? . status || this . NETWORK_ERROR ;
2024-10-18 11:12:18 +08:00
const message = this . http . STATUS_CODES [ statusCode ] || "Network Error" ;
2024-10-17 12:07:30 +08:00
const checkData = {
monitorId : job . data . _id ,
status : isAlive ,
statusCode ,
message ,
accessibility : 0 ,
bestPractices : 0 ,
seo : 0 ,
performance : 0 ,
};
this . logAndStoreCheck ( checkData , this . db . createPageSpeedCheck );
} finally {
this . handleStatusUpdate ( job , isAlive );
}
}
2024-07-25 14:59:31 -07:00
2024-10-21 11:55:44 +08:00
async handleHardware ( job ) {
const url = job . data . url ;
let isAlive ;
//TODO Fetch hardware data
//For now, fake hardware data:
const hardwareData = {
monitorId : job . data . _id ,
cpu : {
physical_core : 1 ,
logical_core : 1 ,
frequency : 266 ,
temperature : null ,
free_percent : null ,
usage_percent : null ,
},
memory : {
total_bytes : 4 ,
available_bytes : 4 ,
used_bytes : 2 ,
usage_percent : 0.5 ,
},
2024-10-22 10:11:02 +08:00
disk : [
{
read_speed_bytes : 3 ,
write_speed_bytes : 3 ,
total_bytes : 10 ,
free_bytes : 2 ,
usage_percent : 0.8 ,
},
],
2024-10-21 11:55:44 +08:00
host : {
os : "Linux" ,
platform : "Ubuntu" ,
kernel_version : "24.04" ,
},
};
try {
isAlive = true ;
this . logAndStoreCheck ( hardwareData , this . db . createHardwareCheck );
} catch ( error ) {
isAlive = false ;
const nullData = {
monitorId : job . data . _id ,
cpu : {
physical_core : 0 ,
logical_core : 0 ,
frequency : 0 ,
temperature : 0 ,
free_percent : 0 ,
usage_percent : 0 ,
},
memory : {
total_bytes : 0 ,
available_bytes : 0 ,
used_bytes : 0 ,
usage_percent : 0 ,
},
2024-10-22 10:11:02 +08:00
disk : [
{
read_speed_bytes : 0 ,
write_speed_bytes : 0 ,
total_bytes : 0 ,
free_bytes : 0 ,
usage_percent : 0 ,
},
],
2024-10-21 11:55:44 +08:00
host : {
os : "" ,
platform : "" ,
kernel_version : "" ,
},
};
this . logAndStoreCheck ( nullData , this . db . createHardwareCheck );
} finally {
this . handleStatusUpdate ( job , isAlive );
}
}
2024-10-17 12:07:30 +08:00
/**
* Retrieves the status of a given job based on its type.
* For unsupported job types, it logs an error and returns false.
*
* @param {Object} job - The job object containing data necessary for processing.
* @returns {Promise<boolean>} The status of the job if it is supported and processed successfully, otherwise false.
*/
async getStatus ( job ) {
switch ( job . data . type ) {
case this . TYPE_PING :
return await this . handlePing ( job );
case this . TYPE_HTTP :
return await this . handleHttp ( job );
case this . TYPE_PAGESPEED :
return await this . handlePagespeed ( job );
2024-10-21 11:55:44 +08:00
case this . TYPE_HARDWARE :
return await this . handleHardware ( job );
2024-10-17 12:07:30 +08:00
default :
2024-10-24 15:28:13 +08:00
this . logger . error ({
message : `Unsupported type: ${ job . data . type } ` ,
2024-10-17 12:07:30 +08:00
service : this . SERVICE_NAME ,
method : "getStatus" ,
});
2024-10-24 15:28:13 +08:00
2024-10-17 12:07:30 +08:00
return false ;
}
}
2024-06-17 10:47:36 -07:00
2024-10-17 12:07:30 +08:00
/**
* Logs and stores the result of a check for a specific job.
*
* @param {Object} data - Data to be written
* @param {function} writeToDB - DB write method
*
* @returns {Promise<boolean>} The status of the inserted check if successful, otherwise false.
*/
2024-07-25 14:59:31 -07:00
2024-10-17 12:07:30 +08:00
async logAndStoreCheck ( data , writeToDB ) {
try {
const insertedCheck = await writeToDB ( data );
if ( insertedCheck !== null && insertedCheck !== undefined ) {
return insertedCheck . status ;
}
2024-10-18 13:36:39 +08:00
throw new Error ();
2024-10-17 12:07:30 +08:00
} catch ( error ) {
2024-10-24 15:28:13 +08:00
this . logger . error ({
message : error . message ,
2024-10-17 12:07:30 +08:00
service : this . SERVICE_NAME ,
method : "logAndStoreCheck" ,
2024-10-24 15:28:13 +08:00
details : `Error writing check for ${ data . monitorId } ` ,
stack : error . stack ,
2024-10-17 12:07:30 +08:00
});
}
}
2024-06-17 10:47:36 -07:00
}
2024-10-15 11:57:37 +08:00
export default NetworkService ;