update config template

pull/147/head
lyc8503 2025-10-29 22:13:19 +08:00
parent ca455d32bb
commit b7f1abf1c0
7 changed files with 646 additions and 1 deletions

1
deploy Submodule

@ -0,0 +1 @@
Subproject commit 5172b6658695610184d26a318ec29c5af137da82

391
gp.patch Normal file

File diff suppressed because one or more lines are too long

1
pr113 Submodule

@ -0,0 +1 @@
Subproject commit e368c267942dd8b8bced824292e0fc12604c02fe

1
pr142 Submodule

@ -0,0 +1 @@
Subproject commit af6476d7baa7da38ee29363d6ea427bdcdfabdba

1
pr143 Submodule

@ -0,0 +1 @@
Subproject commit 6179edc0e3ebe8408363092a83db103232dc58a5

250
test.js Normal file
View File

@ -0,0 +1,250 @@
const fetchTimeout = (url, ms, options = {}) => {
const controller = new AbortController()
const promise = fetch(url, { signal: controller.signal, ...options })
if (options.signal) {
options.signal.addEventListener('abort', () => controller.abort())
}
const timeout = setTimeout(() => controller.abort(), ms)
return promise.finally(() => clearTimeout(timeout))
}
async function httpResponseBasicCheck(monitor, code, bodyReader) {
if (monitor.expectedCodes) {
if (!monitor.expectedCodes.includes(code)) {
return `Expected codes: ${JSON.stringify(monitor.expectedCodes)}, Got: ${code}`
}
} else {
if (code < 200 || code > 299) {
return `Expected codes: 2xx, Got: ${code}`
}
}
if (monitor.responseKeyword || monitor.responseForbiddenKeyword) {
// Only read response body if we have a keyword to check
const responseBody = await bodyReader()
// 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)}`
)
return "HTTP response doesn't contain the configured keyword"
}
// 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)}`
)
return 'HTTP response contains the configured forbidden keyword'
}
}
return null
}
async function getStatusWithGlobalPing(monitor) {
try {
if (monitor.checkProxy === undefined) {
throw "empty check proxy for globalping, shouldn't call this method"
}
const gpUrl = new URL(monitor.checkProxy)
if (gpUrl.protocol !== 'globalping:') {
throw 'incorrect check proxy protocol for globalping, got: ' + gpUrl.protocol
}
const token = gpUrl.hostname
let globalPingRequest = {}
if (monitor.method === 'TCP_PING') {
const targetUrl = new URL('https://' + monitor.target) // dummy https:// to parse hostname & port
globalPingRequest = {
type: 'ping',
target: targetUrl.hostname,
locations: [
{
magic: gpUrl.searchParams.get('magic') || '',
},
],
measurementOptions: {
port: targetUrl.port,
packets: 1,
protocol: 'tcp',
ipVersion: Number(gpUrl.searchParams.get('ipVersion') || 4),
},
}
} else {
const targetUrl = new URL(monitor.target)
if (monitor.body !== undefined) {
throw 'custom body not supported'
}
if (monitor.method && !['GET', 'HEAD', 'OPTIONS'].includes(monitor.method.toUpperCase())) {
throw 'only GET, HEAD, OPTIONS methods are supported'
}
globalPingRequest = {
type: 'http',
target: targetUrl.hostname,
locations: [
{
magic: gpUrl.searchParams.get('magic') || '',
},
],
measurementOptions: {
request: {
method: monitor.method,
path: targetUrl.pathname,
query: targetUrl.search === '' ? undefined : targetUrl.search,
headers: Object.fromEntries(
Object.entries(monitor.headers ?? {}).map(([key, value]) => [key, String(value)])
), // TODO: headers
},
port:
targetUrl.port === ''
? targetUrl.protocol === 'http:'
? 80
: 443
: Number(targetUrl.port),
protocol: targetUrl.protocol.replace(':', ''),
ipVersion: Number(gpUrl.searchParams.get('ipVersion') || 4),
},
}
}
const startTime = Date.now()
console.log(`Requesting the Global Ping API, payload: ${JSON.stringify(globalPingRequest)}`)
const measurement = await fetchTimeout('https://api.globalping.io/v1/measurements', 5000, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
body: JSON.stringify(globalPingRequest),
})
const measurementResponse = await measurement.json()
if (measurement.status !== 202) {
throw measurementResponse.error.message
}
const measurementId = measurementResponse.id
console.log(
`Measurement created successfully, id: ${measurementId}, time elapsed: ${
Date.now() - startTime
}ms`
)
const pollStart = Date.now()
let measurementResult
while (true) {
if (Date.now() - pollStart > (monitor.timeout ?? 10000) + 2000) {
// 2s extra buffer
throw 'api polling timeout'
}
measurementResult = await (
await fetchTimeout(`https://api.globalping.io/v1/measurements/${measurementId}`, 5000)
).json()
if (measurementResult.status !== 'in-progress') {
break
}
await new Promise((resolve) => setTimeout(resolve, 1000))
}
console.log(
`Measurement ${measurementId} finished with response: ${JSON.stringify(
measurementResult
)}, time elapsed: ${Date.now() - pollStart}ms`
)
if (
measurementResult.status !== 'finished' ||
measurementResult.results[0].result.status !== 'finished'
) {
console.log(
`measurement failed with status: ${measurementResult.status}, result status: ${measurementResult.results[0].result.status}`
)
// Truncate raw output to avoid huge error messages
throw `status [${measurementResult.status}|${
measurementResult.results[0].result.status
}]: ${measurementResult.results?.[0].result?.rawOutput?.slice(0, 64)}`
}
const country = measurementResult.results[0].probe.country
if (monitor.method === 'TCP_PING') {
const time = Math.round(measurementResult.results[0].result.stats.avg)
return {
location: country,
status: {
ping: time,
up: true,
err: '',
},
}
} else {
const time = measurementResult.results[0].result.timings.total
const code = measurementResult.results[0].result.statusCode
const body = measurementResult.results[0].result.rawBody
let err = await httpResponseBasicCheck(monitor, code, () => body)
if (err !== null) {
console.log(`${monitor.name} didn't pass response check: ${err}`)
}
if (
monitor.target.toLowerCase().startsWith('https') &&
!measurementResult.results[0].result.tls.authorized
) {
console.log(
`${monitor.name} TLS certificate not trusted: ${measurementResult.results[0].result.tls.error}`
)
err = 'TLS certificate not trusted: ' + measurementResult.results[0].result.tls.error
}
return {
location: country,
status: {
ping: time,
up: err === null,
err: err ?? '',
},
}
}
} catch (e) {
console.log(`Globalping ${monitor.name} errored with ${e}`)
// throw e
return {
location: 'ERROR',
status: {
ping: e.toString().toLowerCase().includes('timeout') ? monitor.timeout ?? 10000 : 0,
up: false,
err: 'Globalping error: ' + e.toString(),
},
}
}
}
let ret = await getStatusWithGlobalPing({
name: 'Test',
method: 'GET',
target: 'https://blog.lyc8503.net/',
// headers: {
// "Hello": 1,
// "World": "test",
// },
checkProxy: 'globalping://2grt6l4wovwrwr4aytl3fovdcbjdem4k/?magic=Beijing',
// timeout: 2000,
// expectedCodes: [301],
// responseForbiddenKeyword: 'cloudflare',
})
console.log(ret)

View File

@ -66,7 +66,7 @@ const workerConfig: WorkerConfig = {
responseForbiddenKeyword: 'bad gateway',
// [OPTIONAL] if specified, will call the check proxy to check the monitor, mainly for geo-specific checks
// refer to docs https://github.com/lyc8503/UptimeFlare/wiki/Check-proxy-setup before setting this value
// currently supports `worker://` and `http(s)://` proxies
// currently supports `worker://`, `globalping://` and `http(s)://` proxies
checkProxy: 'https://xxx.example.com OR worker://weur',
// [OPTIONAL] if true, the check will fallback to local if the specified proxy is down
checkProxyFallback: true,