138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import { WebhookConfig } from '../../types/config'
|
|
|
|
async function getWorkerLocation() {
|
|
const res = await fetch('https://cloudflare.com/cdn-cgi/trace')
|
|
const text = await res.text()
|
|
|
|
const colo = /^colo=(.*)$/m.exec(text)?.[1]
|
|
return colo
|
|
}
|
|
|
|
const fetchTimeout = (
|
|
url: string,
|
|
ms: number,
|
|
{ signal, ...options }: RequestInit<RequestInitCfProperties> | undefined = {}
|
|
): Promise<Response> => {
|
|
const controller = new AbortController()
|
|
const promise = fetch(url, { signal: controller.signal, ...options })
|
|
if (signal) signal.addEventListener('abort', () => controller.abort())
|
|
const timeout = setTimeout(() => controller.abort(), ms)
|
|
return promise.finally(() => clearTimeout(timeout))
|
|
}
|
|
|
|
function withTimeout<T>(millis: number, promise: Promise<T>): Promise<T> {
|
|
const timeout = new Promise<T>((resolve, reject) =>
|
|
setTimeout(() => reject(new Error(`Promise timed out after ${millis}ms`)), millis)
|
|
)
|
|
|
|
return Promise.race([promise, timeout])
|
|
}
|
|
|
|
function formatStatusChangeNotification(
|
|
monitor: any,
|
|
isUp: boolean,
|
|
timeIncidentStart: number,
|
|
timeNow: number,
|
|
reason: string,
|
|
timeZone: string
|
|
) {
|
|
const dateFormatter = new Intl.DateTimeFormat('en-US', {
|
|
month: 'numeric',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false,
|
|
timeZone: timeZone,
|
|
})
|
|
|
|
let downtimeDuration = Math.round((timeNow - timeIncidentStart) / 60)
|
|
const timeNowFormatted = dateFormatter.format(new Date(timeNow * 1000))
|
|
const timeIncidentStartFormatted = dateFormatter.format(new Date(timeIncidentStart * 1000))
|
|
|
|
if (isUp) {
|
|
return `✅ ${monitor.name} is up! \nThe service is up again after being down for ${downtimeDuration} minutes.`
|
|
} else if (timeNow == timeIncidentStart) {
|
|
return `🔴 ${
|
|
monitor.name
|
|
} is currently down. \nService is unavailable at ${timeNowFormatted}. \nIssue: ${
|
|
reason || 'unspecified'
|
|
}`
|
|
} else {
|
|
return `🔴 ${
|
|
monitor.name
|
|
} is still down. \nService is unavailable since ${timeIncidentStartFormatted} (${downtimeDuration} minutes). \nIssue: ${
|
|
reason || 'unspecified'
|
|
}`
|
|
}
|
|
}
|
|
|
|
async function webhookNotify(webhook: WebhookConfig, message: string) {
|
|
console.log(
|
|
'Sending webhook notification: ' + JSON.stringify(message) + ' to webhook ' + webhook.url
|
|
)
|
|
try {
|
|
let url = webhook.url
|
|
let method = webhook.method
|
|
let headers = new Headers(webhook.headers as any)
|
|
let payloadTemplated = webhook.payload
|
|
Object.keys(payloadTemplated).forEach((k) => {
|
|
if (payloadTemplated[k] === '$MSG') {
|
|
payloadTemplated[k] = message
|
|
}
|
|
})
|
|
let body = undefined
|
|
|
|
switch (webhook.payloadType) {
|
|
case 'param':
|
|
method = method ?? 'GET'
|
|
const urlTmp = new URL(url)
|
|
for (const [k, v] of Object.entries(payloadTemplated)) {
|
|
urlTmp.searchParams.append(k, v.toString())
|
|
}
|
|
url = urlTmp.toString()
|
|
break
|
|
case 'json':
|
|
method = method ?? 'POST'
|
|
if (headers.get('content-type') === null) {
|
|
headers.set('content-type', 'application/json')
|
|
}
|
|
body = JSON.stringify(payloadTemplated)
|
|
break
|
|
case 'x-www-form-urlencoded':
|
|
method = method ?? 'POST'
|
|
if (headers.get('content-type') === null) {
|
|
headers.set('content-type', 'application/x-www-form-urlencoded')
|
|
}
|
|
body = new URLSearchParams(payloadTemplated as any).toString()
|
|
break
|
|
default:
|
|
throw 'Unrecognized payload type: ' + webhook.payloadType
|
|
}
|
|
|
|
console.log(
|
|
`Webhook finalized parameters: ${method} ${url}, headers ${JSON.stringify(
|
|
Object.fromEntries(headers.entries())
|
|
)}, body ${JSON.stringify(body)}`
|
|
)
|
|
const resp = await fetchTimeout(url, webhook.timeout ?? 5000, { method, headers, body })
|
|
|
|
if (!resp.ok) {
|
|
console.log(
|
|
'Error calling webhook server, code: ' + resp.status + ', response: ' + (await resp.text())
|
|
)
|
|
} else {
|
|
console.log('Webhook notification sent successfully, code: ' + resp.status)
|
|
}
|
|
} catch (e) {
|
|
console.log('Error calling webhook server: ' + e)
|
|
}
|
|
}
|
|
|
|
export {
|
|
getWorkerLocation,
|
|
fetchTimeout,
|
|
withTimeout,
|
|
webhookNotify,
|
|
formatStatusChangeNotification,
|
|
}
|