2024-06-17 10:47:36 -07:00
const axios = require ( "axios" );
const ping = require ( "ping" );
const logger = require ( "../utils/logger" );
2024-08-28 09:42:02 -07:00
const http = require ( "http" );
const { errorMessages , successMessages } = require ( "../utils/messages" );
2024-06-17 10:47:36 -07:00
class NetworkService {
2024-08-14 10:20:35 -07:00
constructor ( db , emailService ) {
2024-06-17 10:47:36 -07:00
this . db = db ;
2024-08-14 10:20:35 -07:00
this . emailService = emailService ;
2024-06-17 10:47:36 -07:00
this . TYPE_PING = "ping" ;
this . TYPE_HTTP = "http" ;
2024-07-25 14:59:31 -07:00
this . TYPE_PAGESPEED = "pagespeed" ;
2024-06-17 10:47:36 -07:00
this . SERVICE_NAME = "NetworkService" ;
2024-06-18 11:42:25 -07:00
this . NETWORK_ERROR = 5000 ;
2024-06-17 10:47:36 -07:00
}
2024-09-03 15:45:11 -07:00
async handleNotification ( monitor , isAlive ) {
try {
let template =
isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate" ;
let status = isAlive === true ? "up" : "down" ;
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 ) {
logger . error ( error . message , {
method : "handleNotification" ,
service : this . SERVICE_NAME ,
monitorId : monitor . _id ,
});
}
}
async handleStatusUpdate ( job , isAlive ) {
2024-08-31 16:48:02 -07:00
try {
const { _id } = job . data ;
const monitor = await this . db . getMonitorById ( _id );
2024-09-03 15:45:11 -07:00
2024-08-31 16:48:02 -07:00
if ( monitor === null || monitor === undefined ) {
logger . error ( `Null Monitor: ${ _id } ` , {
2024-09-03 15:45:11 -07:00
method : "handleStatusUpdate" ,
2024-08-31 16:48:02 -07:00
service : this . SERVICE_NAME ,
jobId : job . id ,
});
return ;
}
2024-09-03 15:45:11 -07:00
if ( monitor . status === undefined || monitor . status !== isAlive ) {
const oldStatus = monitor . status ;
monitor . status = isAlive ;
2024-09-02 10:45:07 -07:00
await monitor . save ();
2024-09-02 11:00:59 -07:00
2024-09-03 15:45:11 -07:00
if ( oldStatus !== undefined && oldStatus !== isAlive ) {
this . handleNotification ( monitor , isAlive );
2024-08-14 10:20:35 -07:00
}
}
2024-08-31 16:48:02 -07:00
} catch ( error ) {
logger . error ( error . message , {
2024-09-03 15:45:11 -07:00
method : "handleStatusUpdate" ,
2024-08-31 16:48:02 -07:00
service : this . SERVICE_NAME ,
jobId : job . id ,
});
2024-08-14 10:20:35 -07:00
}
}
2024-06-17 13:52:20 -07: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.
*/
2024-06-17 11:33:55 -07:00
async measureResponseTime ( operation ) {
2024-06-17 11:05:22 -07:00
const startTime = Date . now ();
2024-06-17 10:47:36 -07:00
try {
2024-06-17 11:33:55 -07:00
const response = await operation ();
const endTime = Date . now ();
return { responseTime : endTime - startTime , response };
} catch ( error ) {
2024-06-17 13:52:20 -07:00
const endTime = Date . now ();
error . responseTime = endTime - startTime ;
2024-06-17 11:33:55 -07:00
throw error ;
}
}
2024-06-17 13:52:20 -07: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
*/
2024-06-17 11:33:55 -07:00
async handlePing ( job ) {
const operation = async () => {
2024-06-17 10:47:36 -07:00
const response = await ping . promise . probe ( job . data . url );
2024-06-17 11:33:55 -07:00
return response ;
};
2024-08-14 10:20:35 -07:00
let isAlive ;
2024-06-17 11:33:55 -07:00
try {
2024-07-25 14:59:31 -07:00
const { responseTime , response } =
await this . measureResponseTime ( operation );
2024-08-14 10:20:35 -07:00
isAlive = response . alive ;
2024-07-25 14:59:31 -07:00
const checkData = {
2024-06-18 11:42:25 -07:00
monitorId : job . data . _id ,
status : isAlive ,
responseTime ,
2024-08-28 09:42:02 -07:00
message : isAlive
? successMessages . PING_SUCCESS
: errorMessages . PING_CANNOT_RESOLVE ,
2024-07-25 14:59:31 -07:00
};
return await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-06-17 10:47:36 -07:00
} catch ( error ) {
2024-08-14 10:20:35 -07:00
isAlive = false ;
2024-07-25 14:59:31 -07:00
const checkData = {
2024-06-18 11:42:25 -07:00
monitorId : job . data . _id ,
2024-08-14 10:20:35 -07:00
status : isAlive ,
2024-08-28 09:42:02 -07:00
message : errorMessages . PING_CANNOT_RESOLVE ,
2024-06-18 11:42:25 -07:00
responseTime : error . responseTime ,
2024-07-25 14:59:31 -07:00
};
return await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-08-14 10:20:35 -07:00
} finally {
2024-09-03 15:45:11 -07:00
this . handleStatusUpdate ( job , isAlive );
2024-06-17 10:47:36 -07:00
}
}
2024-06-17 13:52:20 -07: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
*/
2024-06-17 10:47:36 -07:00
async handleHttp ( job ) {
2024-06-18 11:42:25 -07:00
// Define operation for timing
2024-06-17 11:33:55 -07:00
const operation = async () => {
2024-06-17 10:47:36 -07:00
const response = await axios . get ( job . data . url );
2024-06-17 11:33:55 -07:00
return response ;
};
2024-06-18 11:42:25 -07:00
2024-08-14 10:20:35 -07:00
let isAlive ;
2024-06-18 11:42:25 -07:00
// attempt connection
2024-06-17 11:33:55 -07:00
try {
2024-07-25 14:59:31 -07:00
const { responseTime , response } =
await this . measureResponseTime ( operation );
2024-06-18 11:42:25 -07:00
// check if response is in the 200 range, if so, service is up
2024-08-14 10:20:35 -07:00
isAlive = response . status >= 200 && response . status < 300 ;
2024-06-18 11:42:25 -07:00
//Create a check with relevant data
2024-07-25 14:59:31 -07:00
const checkData = {
2024-06-18 11:42:25 -07:00
monitorId : job . data . _id ,
status : isAlive ,
responseTime ,
statusCode : response . status ,
2024-08-28 09:42:02 -07:00
message : http . STATUS_CODES [ response . status ],
2024-07-25 14:59:31 -07:00
};
return await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-06-17 10:47:36 -07:00
} catch ( error ) {
2024-08-28 09:42:02 -07:00
const statusCode = error . response ? . status || this . NETWORK_ERROR ;
let message = http . STATUS_CODES [ statusCode ] || "Network Error" ;
2024-08-14 10:20:35 -07:00
isAlive = false ;
2024-07-25 14:59:31 -07:00
const checkData = {
2024-06-18 11:42:25 -07:00
monitorId : job . data . _id ,
2024-08-14 10:20:35 -07:00
status : isAlive ,
2024-08-28 09:42:02 -07:00
statusCode ,
2024-06-18 11:42:25 -07:00
responseTime : error . responseTime ,
2024-08-28 09:42:02 -07:00
message ,
2024-07-25 14:59:31 -07:00
};
2024-08-28 09:42:02 -07:00
2024-07-25 14:59:31 -07:00
return await this . logAndStoreCheck ( checkData , this . db . createCheck );
2024-08-14 10:20:35 -07:00
} finally {
2024-09-03 15:45:11 -07:00
this . handleStatusUpdate ( job , isAlive );
2024-06-17 10:47:36 -07:00
}
}
2024-06-18 11:42:25 -07:00
2024-07-25 23:10:30 -07: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 ) {
2024-08-14 10:20:35 -07:00
let isAlive ;
2024-07-25 23:10:30 -07:00
try {
const url = job . data . url ;
const response = await axios . get (
`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 ;
2024-08-02 12:32:08 -07:00
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-14 10:20:35 -07:00
isAlive = true ;
2024-07-25 23:10:30 -07:00
const checkData = {
monitorId : job . data . _id ,
2024-08-14 10:20:35 -07:00
status : isAlive ,
2024-08-28 09:42:02 -07:00
statusCode : response . status ,
message : http . STATUS_CODES [ response . status ],
2024-07-25 23:10:30 -07: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 ,
2024-08-02 12:32:08 -07:00
audits : {
cls ,
si ,
fcp ,
lcp ,
tbt ,
},
2024-07-25 23:10:30 -07:00
};
2024-08-02 12:32:08 -07:00
2024-07-25 23:10:30 -07:00
this . logAndStoreCheck ( checkData , this . db . createPageSpeedCheck );
} catch ( error ) {
2024-08-14 10:20:35 -07:00
isAlive = false ;
2024-08-28 09:42:02 -07:00
const statusCode = error . response ? . status || this . NETWORK_ERROR ;
const message = http . STATUS_CODES [ statusCode ] || "Network Error" ;
2024-07-25 23:10:30 -07:00
const checkData = {
monitorId : job . data . _id ,
2024-08-14 10:20:35 -07:00
status : isAlive ,
2024-08-28 09:42:02 -07:00
statusCode ,
message ,
2024-07-25 23:10:30 -07:00
accessibility : 0 ,
bestPractices : 0 ,
seo : 0 ,
performance : 0 ,
};
this . logAndStoreCheck ( checkData , this . db . createPageSpeedCheck );
2024-08-14 10:20:35 -07:00
} finally {
2024-09-03 15:45:11 -07:00
this . handleStatusUpdate ( job , isAlive );
2024-07-25 23:10:30 -07:00
}
2024-07-25 19:11:24 -07:00
}
2024-07-25 14:59:31 -07:00
2024-06-17 13:52:20 -07: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.
*/
2024-06-17 10:47:36 -07:00
async getStatus ( job ) {
2024-06-17 11:05:22 -07:00
switch ( job . data . type ) {
case this . TYPE_PING :
return await this . handlePing ( job );
case this . TYPE_HTTP :
return await this . handleHttp ( job );
2024-07-25 14:59:31 -07:00
case this . TYPE_PAGESPEED :
return await this . handlePagespeed ( job );
2024-06-17 11:05:22 -07:00
default :
2024-06-17 13:52:20 -07:00
logger . error ( `Unsupported type: ${ job . data . type } ` , {
2024-06-17 11:05:22 -07:00
service : this . SERVICE_NAME ,
2024-06-17 13:52:20 -07:00
jobId : job . id ,
2024-06-17 11:05:22 -07:00
});
return false ;
2024-06-17 10:47:36 -07:00
}
}
2024-06-17 13:52:20 -07:00
/**
* Logs and stores the result of a check for a specific job.
*
2024-07-25 14:59:31 -07:00
* @param {Object} data - Data to be written
* @param {function} writeToDB - DB write method
*
2024-06-17 13:52:20 -07:00
* @returns {Promise<boolean>} The status of the inserted check if successful, otherwise false.
*/
2024-07-25 14:59:31 -07:00
async logAndStoreCheck ( data , writeToDB ) {
2024-06-17 13:52:20 -07:00
try {
2024-07-25 14:59:31 -07:00
const insertedCheck = await writeToDB ( data );
2024-06-17 13:52:20 -07:00
return insertedCheck . status ;
} catch ( error ) {
2024-07-26 18:56:39 -07:00
console . log ( error );
2024-07-25 14:59:31 -07:00
logger . error ( `Error wrtiting check for ${ data . monitorId } ` , {
2024-06-17 13:52:20 -07:00
service : this . SERVICE_NAME ,
2024-07-25 14:59:31 -07:00
monitorId : data . monitorId ,
2024-06-17 13:52:20 -07:00
error : error ,
});
2024-06-17 10:47:36 -07:00
}
}
}
module . exports = NetworkService ;