@@ -5,17 +5,25 @@ const { log, UP, DOWN, evaluateJsonQuery } = require("../../src/util");
55const { checkStatusCode, getOidcTokenClientCredentials, encodeBase64, getDaysRemaining, checkCertExpiryNotifications } = require ( "../util-server" ) ;
66const { R } = require ( "redbean-node" ) ;
77
8+ /**
9+ * Globalping is a free and open-source tool that allows you to run network tests
10+ * and measurements from thousands of community hosted probes around the world.
11+ *
12+ * Library documentation: https://github.com/jsdelivr/globalping-typescript
13+ *
14+ * API documentation: https://globalping.io/docs/api.globalping.io
15+ */
816class GlobalpingMonitorType extends MonitorType {
917 name = "globalping" ;
1018
11- agent = "" ;
19+ httpUserAgent = "" ;
1220
1321 /**
1422 * @inheritdoc
1523 */
16- constructor ( agent ) {
24+ constructor ( httpUserAgent ) {
1725 super ( ) ;
18- this . agent = agent ;
26+ this . httpUserAgent = httpUserAgent ;
1927 }
2028
2129 /**
@@ -25,7 +33,7 @@ class GlobalpingMonitorType extends MonitorType {
2533 const apiKey = await Settings . get ( "globalpingApiToken" ) ;
2634 const client = new Globalping ( {
2735 auth : apiKey ,
28- agent : this . agent ,
36+ agent : this . httpUserAgent ,
2937 } ) ;
3038
3139 const hasAPIToken = ! ! apiKey ;
@@ -40,7 +48,12 @@ class GlobalpingMonitorType extends MonitorType {
4048 }
4149
4250 /**
43- * @inheritdoc
51+ * Handles ping monitors.
52+ * @param {Client } client - The client object.
53+ * @param {Monitor } monitor - The monitor object.
54+ * @param {Heartbeat } heartbeat - The heartbeat object.
55+ * @param {boolean } hasAPIToken - Whether the monitor has an API token.
56+ * @returns {Promise<void> } A promise that resolves when the ping monitor is handled.
4457 */
4558 async ping ( client , monitor , heartbeat , hasAPIToken ) {
4659 const opts = {
@@ -107,7 +120,12 @@ class GlobalpingMonitorType extends MonitorType {
107120 }
108121
109122 /**
110- * @inheritdoc
123+ * Handles HTTP monitors.
124+ * @param {Client } client - The client object.
125+ * @param {Monitor } monitor - The monitor object.
126+ * @param {Heartbeat } heartbeat - The heartbeat object.
127+ * @param {boolean } hasAPIToken - Whether the monitor has an API token.
128+ * @returns {Promise<void> } A promise that resolves when the HTTP monitor is handled.
111129 */
112130 async http ( client , monitor , heartbeat , hasAPIToken ) {
113131 const url = new URL ( monitor . url ) ;
@@ -142,6 +160,7 @@ class GlobalpingMonitorType extends MonitorType {
142160 host : url . hostname ,
143161 path : url . pathname ,
144162 query : url . search ? url . search . slice ( 1 ) : undefined ,
163+ method : monitor . method ,
145164 headers
146165 } ,
147166 protocol : protocol ,
@@ -204,34 +223,13 @@ class GlobalpingMonitorType extends MonitorType {
204223
205224 // keyword
206225 if ( monitor . keyword ) {
207- let data = result . rawOutput ;
208- let keywordFound = data . includes ( monitor . keyword ) ;
209-
210- if ( keywordFound !== ! Boolean ( monitor . invertKeyword ) ) {
211- data = data . replace ( / < [ ^ > ] * > ? | [ \n \r ] | \s + / gm, " " ) . trim ( ) ;
212- if ( data . length > 50 ) {
213- data = data . substring ( 0 , 47 ) + "..." ;
214- }
215- throw new Error ( heartbeat . msg + ", but keyword is " +
216- ( keywordFound ? "present" : "not" ) + " in [" + data + "]" ) ;
217-
218- }
219-
220- heartbeat . msg += this . formatResponse ( probe , ", keyword " + ( keywordFound ? "is" : "not" ) + " found" ) ;
221- heartbeat . status = UP ;
226+ await this . handleKeywordForHTTP ( monitor , heartbeat , result , probe ) ;
222227 return ;
223228 }
224229
225230 // json-query
226231 if ( monitor . expectedValue ) {
227- const { status, response } = await evaluateJsonQuery ( result . rawOutput , monitor . jsonPath , monitor . jsonPathOperator , monitor . expectedValue ) ;
228-
229- if ( ! status ) {
230- throw new Error ( this . formatResponse ( probe , `JSON query does not pass (comparing ${ response } ${ monitor . jsonPathOperator } ${ monitor . expectedValue } )` ) ) ;
231- }
232-
233- heartbeat . msg = this . formatResponse ( probe , `JSON query passes (comparing ${ response } ${ monitor . jsonPathOperator } ${ monitor . expectedValue } )` ) ;
234- heartbeat . status = UP ;
232+ await this . handleJSONQueryForHTTP ( monitor , heartbeat , result , probe ) ;
235233 return ;
236234 }
237235
@@ -242,99 +240,57 @@ class GlobalpingMonitorType extends MonitorType {
242240 }
243241
244242 /**
245- * @inheritdoc
243+ * Handles keyword for HTTP monitors.
244+ * @param {Monitor } monitor - The monitor object.
245+ * @param {Heartbeat } heartbeat - The heartbeat object.
246+ * @param {Result } result - The result object.
247+ * @param {Probe } probe - The probe object.
248+ * @returns {Promise<void> } A promise that resolves when the keyword is handled.
246249 */
247- formatApiError ( error ) {
248- let str = `${ error . type } ${ error . message } .` ;
249- if ( error . params ) {
250- for ( const key in error . params ) {
251- str += `\n${ key } : ${ error . params [ key ] } ` ;
250+ async handleKeywordForHTTP ( monitor , heartbeat , result , probe ) {
251+ let data = result . rawOutput ;
252+ let keywordFound = data . includes ( monitor . keyword ) ;
253+
254+ if ( keywordFound !== ! Boolean ( monitor . invertKeyword ) ) {
255+ data = data . replace ( / < [ ^ > ] * > ? | [ \n \r ] | \s + / gm, " " ) . trim ( ) ;
256+ if ( data . length > 50 ) {
257+ data = data . substring ( 0 , 47 ) + "..." ;
252258 }
253- }
254- return str ;
255- }
256-
257- /**
258- * @inheritdoc
259- */
260- formatTooManyRequestsError ( hasAPIToken ) {
261- const creditsHelpLink = "https://dash.globalping.io?view=add-credits" ;
262- if ( hasAPIToken ) {
263- return `You have run out of credits. Get higher limits by sponsoring us or hosting probes. Learn more at ${ creditsHelpLink } .` ;
264- }
265- return `You have run out of credits. Get higher limits by creating an account. Sign up at ${ creditsHelpLink } .` ;
266- }
259+ throw new Error ( heartbeat . msg + ", but keyword is " +
260+ ( keywordFound ? "present" : "not" ) + " in [" + data + "]" ) ;
267261
268- /**
269- * Returns the formatted probe location string. e.g "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1)"
270- * @param {object } probe - The probe object containing location information.
271- * @returns {string } The formatted probe location string.
272- */
273- formatProbeLocation ( probe ) {
274- let tag = "" ;
275-
276- for ( const t of probe . tags ) {
277- // If tag ends in a number, it's likely a region code and should be displayed
278- if ( Number . isInteger ( Number ( t . slice ( - 1 ) ) ) ) {
279- tag = t ;
280- break ;
281- }
282262 }
283- return `${ probe . city } ${ probe . state ? ` (${ probe . state } )` : ""
284- } , ${ probe . country } , ${ probe . continent } , ${ probe . network
285- } (AS${ probe . asn } )${ tag ? `, (${ tag } )` : "" } `;
286- }
287263
288- /**
289- * @inheritdoc
290- */
291- formatResponse ( probe , text ) {
292- return `${ this . formatProbeLocation ( probe ) } : ${ text } ` ;
264+ heartbeat . msg += ", keyword " + ( keywordFound ? "is" : "not" ) + " found" ;
265+ heartbeat . status = UP ;
293266 }
294267
295268 /**
296- * @inheritdoc
269+ * Handles JSON query for HTTP monitors.
270+ * @param {Monitor } monitor - The monitor object.
271+ * @param {Heartbeat } heartbeat - The heartbeat object.
272+ * @param {Result } result - The result object.
273+ * @param {Probe } probe - The probe object.
274+ * @returns {Promise<void> } A promise that resolves when the JSON query is handled.
297275 */
298- async getOauth2AuthHeader ( monitor ) {
299- if ( monitor . auth_method !== "oauth2-cc" ) {
300- return { } ;
301- }
302-
303- try {
304- if ( monitor . oauthAccessToken === undefined || new Date ( monitor . oauthAccessToken . expires_at * 1000 ) <= new Date ( ) ) {
305- log . debug ( "monitor" , `[${ monitor . name } ] The oauth access-token undefined or expired. Requesting a new token` ) ;
306- const oAuthAccessToken = await getOidcTokenClientCredentials ( monitor . oauth_token_url , monitor . oauth_client_id , monitor . oauth_client_secret , monitor . oauth_scopes , monitor . oauth_audience , monitor . oauth_auth_method ) ;
307- if ( monitor . oauthAccessToken ?. expires_at ) {
308- log . debug ( "monitor" , `[${ monitor . name } ] Obtained oauth access-token. Expires at ${ new Date ( monitor . oauthAccessToken ?. expires_at * 1000 ) } ` ) ;
309- } else {
310- log . debug ( "monitor" , `[${ monitor . name } ] Obtained oauth access-token. Time until expiry was not provided` ) ;
311- }
276+ async handleJSONQueryForHTTP ( monitor , heartbeat , result , probe ) {
277+ const { status, response } = await evaluateJsonQuery ( result . rawOutput , monitor . jsonPath , monitor . jsonPathOperator , monitor . expectedValue ) ;
312278
313- monitor . oauthAccessToken = oAuthAccessToken ;
314- }
315- return {
316- "Authorization" : monitor . oauthAccessToken . token_type + " " + monitor . oauthAccessToken . access_token ,
317- } ;
318- } catch ( e ) {
319- throw new Error ( "The oauth config is invalid. " + e . message ) ;
279+ if ( ! status ) {
280+ throw new Error ( this . formatResponse ( probe , `JSON query does not pass (comparing ${ response } ${ monitor . jsonPathOperator } ${ monitor . expectedValue } )` ) ) ;
320281 }
321- }
322282
323- /**
324- * @inheritdoc
325- */
326- getBasicAuthHeader ( monitor ) {
327- if ( monitor . auth_method !== "basic" ) {
328- return { } ;
329- }
330-
331- return {
332- "Authorization" : "Basic " + encodeBase64 ( monitor . basic_auth_user , monitor . basic_auth_pass ) ,
333- } ;
283+ heartbeat . msg = this . formatResponse ( probe , `JSON query passes (comparing ${ response } ${ monitor . jsonPathOperator } ${ monitor . expectedValue } )` ) ;
284+ heartbeat . status = UP ;
334285 }
335286
336287 /**
337- * @inheritdoc
288+ * Updates the TLS information for a monitor.
289+ * @param {object } monitor - The monitor object.
290+ * @param {string } protocol - The protocol used for the monitor.
291+ * @param {object } probe - The probe object containing location information.
292+ * @param {object } tlsInfo - The TLS information object.
293+ * @returns {Promise<void> }
338294 */
339295 async handleTLSInfo ( monitor , protocol , probe , tlsInfo ) {
340296 if ( ! tlsInfo ) {
@@ -390,6 +346,105 @@ class GlobalpingMonitorType extends MonitorType {
390346 await checkCertExpiryNotifications ( monitor , certResult ) ;
391347 }
392348 }
349+
350+ /**
351+ * Generates the OAuth2 authorization header for the monitor if it is enabled.
352+ * @param {object } monitor - The monitor object containing authentication information.
353+ * @returns {Promise<object> } The OAuth2 authorization header.
354+ */
355+ async getOauth2AuthHeader ( monitor ) {
356+ if ( monitor . auth_method !== "oauth2-cc" ) {
357+ return { } ;
358+ }
359+
360+ try {
361+ if ( new Date ( ( monitor . oauthAccessToken ?. expires_at || 0 ) * 1000 ) <= new Date ( ) ) {
362+ const oAuthAccessToken = await getOidcTokenClientCredentials ( monitor . oauth_token_url , monitor . oauth_client_id , monitor . oauth_client_secret , monitor . oauth_scopes , monitor . oauth_audience , monitor . oauth_auth_method ) ;
363+ log . debug ( "monitor" , `[${ monitor . name } ] Obtained oauth access-token. Expires at ${ new Date ( oAuthAccessToken . expires_at * 1000 ) } ` ) ;
364+
365+ monitor . oauthAccessToken = oAuthAccessToken ;
366+ }
367+ return {
368+ "Authorization" : monitor . oauthAccessToken . token_type + " " + monitor . oauthAccessToken . access_token ,
369+ } ;
370+ } catch ( e ) {
371+ throw new Error ( "The oauth config is invalid. " + e . message ) ;
372+ }
373+ }
374+
375+ /**
376+ * Generates the basic authentication header for a monitor if it is enabled.
377+ * @param {object } monitor - The monitor object.
378+ * @returns {object } The basic authentication header.
379+ */
380+ getBasicAuthHeader ( monitor ) {
381+ if ( monitor . auth_method !== "basic" ) {
382+ return { } ;
383+ }
384+
385+ return {
386+ "Authorization" : "Basic " + encodeBase64 ( monitor . basic_auth_user , monitor . basic_auth_pass ) ,
387+ } ;
388+ }
389+
390+ /**
391+ * Generates a formatted error message for API errors.
392+ * @param {Error } error - The API error object.
393+ * @returns {string } The formatted error message.
394+ */
395+ formatApiError ( error ) {
396+ let str = `${ error . type } ${ error . message } .` ;
397+ if ( error . params ) {
398+ for ( const key in error . params ) {
399+ str += `\n${ key } : ${ error . params [ key ] } ` ;
400+ }
401+ }
402+ return str ;
403+ }
404+
405+ /**
406+ * Generates a formatted error message for too many requests.
407+ * @param {boolean } hasAPIToken - Indicates whether an API token is available.
408+ * @returns {string } The formatted error message.
409+ */
410+ formatTooManyRequestsError ( hasAPIToken ) {
411+ const creditsHelpLink = "https://dash.globalping.io?view=add-credits" ;
412+ if ( hasAPIToken ) {
413+ return `You have run out of credits. Get higher limits by sponsoring us or hosting probes. Learn more at ${ creditsHelpLink } .` ;
414+ }
415+ return `You have run out of credits. Get higher limits by creating an account. Sign up at ${ creditsHelpLink } .` ;
416+ }
417+
418+ /**
419+ * Returns the formatted probe location string. e.g "Ashburn (VA), US, NA, Amazon.com (AS14618), (aws-us-east-1)"
420+ * @param {object } probe - The probe object containing location information.
421+ * @returns {string } The formatted probe location string.
422+ */
423+ formatProbeLocation ( probe ) {
424+ let tag = "" ;
425+
426+ for ( const t of probe . tags ) {
427+ // If tag ends in a number, it's likely a region code and should be displayed
428+ if ( Number . isInteger ( Number ( t . slice ( - 1 ) ) ) ) {
429+ tag = t ;
430+ break ;
431+ }
432+ }
433+ return `${ probe . city } ${ probe . state ? ` (${ probe . state } )` : ""
434+ } , ${ probe . country } , ${ probe . continent } , ${ probe . network
435+ } (AS${ probe . asn } )${ tag ? `, (${ tag } )` : "" } `;
436+ }
437+
438+ /**
439+ * Formats the response text with the probe location.
440+ * @param {object } probe - The probe object containing location information.
441+ * @param {string } text - The response text to append.
442+ * @returns {string } The formatted response text.
443+ */
444+ formatResponse ( probe , text ) {
445+ return `${ this . formatProbeLocation ( probe ) } : ${ text } ` ;
446+ }
447+
393448}
394449
395450module . exports = {
0 commit comments