126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
import { MonitorTarget } from '../../uptime.types'
|
|
import { withTimeout, fetchTimeout } from './util'
|
|
|
|
export async function getStatus(
|
|
monitor: MonitorTarget
|
|
): Promise<{ ping: number; up: boolean; err: string }> {
|
|
let status = {
|
|
ping: 0,
|
|
up: false,
|
|
err: 'Unknown',
|
|
}
|
|
|
|
const startTime = Date.now()
|
|
|
|
if (monitor.method === 'TCP_PING') {
|
|
// TCP port endpoint monitor
|
|
try {
|
|
const connect = await import(/* webpackIgnore: true */ 'cloudflare:sockets').then(
|
|
(sockets) => sockets.connect
|
|
)
|
|
// This is not a real https connection, but we need to add a dummy `https://` to parse the hostname & port
|
|
const parsed = new URL('https://' + monitor.target)
|
|
const socket = connect({ hostname: parsed.hostname, port: Number(parsed.port) })
|
|
|
|
// Now we have an `opened` promise!
|
|
await withTimeout(monitor.timeout || 10000, socket.opened)
|
|
await socket.close()
|
|
|
|
console.log(`${monitor.name} connected to ${monitor.target}`)
|
|
|
|
status.ping = Date.now() - startTime
|
|
status.up = true
|
|
status.err = ''
|
|
} catch (e: Error | any) {
|
|
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`)
|
|
if (e.message.includes('timed out')) {
|
|
status.ping = monitor.timeout || 10000
|
|
}
|
|
status.up = false
|
|
status.err = e.name + ': ' + e.message
|
|
}
|
|
} else {
|
|
// HTTP endpoint monitor
|
|
try {
|
|
const response = await fetchTimeout(monitor.target, monitor.timeout || 10000, {
|
|
method: monitor.method,
|
|
headers: monitor.headers as any,
|
|
body: monitor.body,
|
|
cf: {
|
|
cacheTtlByStatus: {
|
|
'100-599': -1, // Don't cache any status code, from https://developers.cloudflare.com/workers/runtime-apis/request/#requestinitcfproperties
|
|
},
|
|
},
|
|
})
|
|
|
|
console.log(`${monitor.name} responded with ${response.status}`)
|
|
status.ping = Date.now() - startTime
|
|
|
|
if (monitor.expectedCodes) {
|
|
if (!monitor.expectedCodes.includes(response.status)) {
|
|
console.log(`${monitor.name} expected ${monitor.expectedCodes}, got ${response.status}`)
|
|
status.up = false
|
|
status.err = `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${
|
|
response.status
|
|
}`
|
|
return status
|
|
}
|
|
} else {
|
|
if (response.status < 200 || response.status > 299) {
|
|
console.log(`${monitor.name} expected 2xx, got ${response.status}`)
|
|
status.up = false
|
|
status.err = `Expected codes: 2xx, Got: ${response.status}`
|
|
return status
|
|
}
|
|
}
|
|
|
|
if (monitor.responseKeyword || monitor.responseForbiddenKeyword) {
|
|
// Only read response body if we have a keyword to check
|
|
const responseBody = await response.text()
|
|
|
|
// MUST contain responseKeyword
|
|
if (monitor.responseKeyword && !responseBody.includes(monitor.responseKeyword)) {
|
|
console.log(
|
|
`${monitor.name} expected keyword ${
|
|
monitor.responseKeyword
|
|
}, not found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
|
|
)
|
|
status.up = false
|
|
status.err = "HTTP response doesn't contain the configured keyword"
|
|
return status
|
|
}
|
|
|
|
// MUST NOT contain responseForbiddenKeyword
|
|
if (
|
|
monitor.responseForbiddenKeyword &&
|
|
responseBody.includes(monitor.responseForbiddenKeyword)
|
|
) {
|
|
console.log(
|
|
`${monitor.name} forbidden keyword ${
|
|
monitor.responseForbiddenKeyword
|
|
}, found in response (truncated to 100 chars): ${responseBody.slice(0, 100)}`
|
|
)
|
|
status.up = false
|
|
status.err = 'HTTP response contains the configured forbidden keyword'
|
|
return status
|
|
}
|
|
}
|
|
|
|
status.up = true
|
|
status.err = ''
|
|
} catch (e: any) {
|
|
console.log(`${monitor.name} errored with ${e.name}: ${e.message}`)
|
|
if (e.name === 'AbortError') {
|
|
status.ping = monitor.timeout || 10000
|
|
status.up = false
|
|
status.err = `Timeout after ${status.ping}ms`
|
|
} else {
|
|
status.up = false
|
|
status.err = e.name + ': ' + e.message
|
|
}
|
|
}
|
|
}
|
|
|
|
return status
|
|
}
|