fix(system): prevent false offline reports when Cloudflare endpoint is unreachable
This commit is contained in:
parent
bbd62d8ed1
commit
9de6473f6a
|
|
@ -105,7 +105,7 @@ For answers to common questions about Project N.O.M.A.D., please see our [FAQ](F
|
|||
## About Internet Usage & Privacy
|
||||
Project N.O.M.A.D. is designed for offline usage. An internet connection is only required during the initial installation (to download dependencies) and if you (the user) decide to download additional tools and resources at a later time. Otherwise, N.O.M.A.D. does not require an internet connection and has ZERO built-in telemetry.
|
||||
|
||||
To test internet connectivity, N.O.M.A.D. attempts to make a request to Cloudflare's utility endpoint, `https://1.1.1.1/cdn-cgi/trace` and checks for a successful response.
|
||||
To test internet connectivity, N.O.M.A.D. first attempts to make a request to Cloudflare's utility endpoint, `https://1.1.1.1/cdn-cgi/trace`. If that endpoint is unreachable (for example, because your network blocks `1.1.1.1`), it falls back to other endpoints the application already contacts (the GitHub API and the Project N.O.M.A.D. API) and considers the connection online if any of them respond. You can override the endpoint used for this check with the `INTERNET_STATUS_TEST_URL` environment variable.
|
||||
|
||||
## About Security
|
||||
By design, Project N.O.M.A.D. is intended to be open and available without hurdles — it includes no authentication. If you decide to connect your device to a local network after install (e.g. for allowing other devices to access its resources), you can block/open ports to control which services are exposed.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
PORT=8080
|
||||
HOST=localhost
|
||||
LOG_LEVEL=info
|
||||
# Optional: override the URL used to test internet connectivity.
|
||||
# Defaults to https://1.1.1.1/cdn-cgi/trace with fallbacks to hosts the app
|
||||
# already contacts. Leave unset to use the defaults.
|
||||
# INTERNET_STATUS_TEST_URL=https://1.1.1.1/cdn-cgi/trace
|
||||
APP_KEY=some_random_key
|
||||
NODE_ENV=development
|
||||
SESSION_DRIVER=cookie
|
||||
|
|
|
|||
|
|
@ -35,29 +35,48 @@ export class SystemService {
|
|||
}
|
||||
|
||||
async getInternetStatus(): Promise<boolean> {
|
||||
const DEFAULT_TEST_URL = 'https://1.1.1.1/cdn-cgi/trace'
|
||||
// Primary endpoint stays Cloudflare's privacy-respecting utility endpoint.
|
||||
// The fallbacks are hosts the application already contacts elsewhere
|
||||
// (GitHub API for update checks, the Project N.O.M.A.D. API for release-note
|
||||
// subscriptions), so no new third-party services are introduced. They exist
|
||||
// to avoid false "offline" reports on networks that block 1.1.1.1.
|
||||
const DEFAULT_TEST_URLS = [
|
||||
'https://1.1.1.1/cdn-cgi/trace',
|
||||
'https://api.github.com',
|
||||
'https://api.projectnomad.us',
|
||||
]
|
||||
const MAX_ATTEMPTS = 3
|
||||
|
||||
let testUrl = DEFAULT_TEST_URL
|
||||
let customTestUrl = env.get('INTERNET_STATUS_TEST_URL')?.trim()
|
||||
let testUrls = DEFAULT_TEST_URLS
|
||||
const customTestUrl = env.get('INTERNET_STATUS_TEST_URL')?.trim()
|
||||
|
||||
// check that customTestUrl is a valid URL, if provided
|
||||
// If a custom test URL is provided and valid, use it exclusively. This
|
||||
// preserves the existing override behavior for operators who intentionally
|
||||
// point connectivity checks at a specific endpoint.
|
||||
if (customTestUrl && customTestUrl !== '') {
|
||||
try {
|
||||
new URL(customTestUrl)
|
||||
testUrl = customTestUrl
|
||||
testUrls = [customTestUrl]
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Invalid INTERNET_STATUS_TEST_URL: ${customTestUrl}. Falling back to default URL.`
|
||||
`Invalid INTERNET_STATUS_TEST_URL: ${customTestUrl}. Falling back to default URLs.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
const res = await axios.get(testUrl, { timeout: 5000 })
|
||||
return res.status === 200
|
||||
// Probe all endpoints in parallel and resolve as soon as the first one
|
||||
// responds. Any HTTP response (including non-2xx) means we reached the
|
||||
// internet, so accept all status codes rather than requiring a strict 200.
|
||||
await Promise.any(
|
||||
testUrls.map((testUrl) =>
|
||||
axios.get(testUrl, { timeout: 5000, validateStatus: () => true })
|
||||
)
|
||||
)
|
||||
return true
|
||||
} catch (error) {
|
||||
// Promise.any only rejects (with an AggregateError) when every endpoint failed.
|
||||
logger.warn(
|
||||
`Internet status check attempt ${attempt}/${MAX_ATTEMPTS} failed: ${error instanceof Error ? error.message : error}`
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue