147 lines
3.4 KiB
JavaScript
147 lines
3.4 KiB
JavaScript
const DEFAULT_HTTPS_PORT = 443
|
|
const REDIRECT_STATUS_MIN = 300
|
|
const REDIRECT_STATUS_MAX = 399
|
|
|
|
const currentLocation = () => {
|
|
if (typeof window === 'undefined' || !window.location) {
|
|
return null
|
|
}
|
|
return window.location
|
|
}
|
|
|
|
const normalizeDomain = (value) => {
|
|
const domain = String(value || '').trim()
|
|
if (!domain) {
|
|
return ''
|
|
}
|
|
return domain.replace(/^[a-z][a-z\d+.-]*:\/\//i, '').replace(/\/.*$/, '')
|
|
}
|
|
|
|
const isNonDefaultPort = (protocol, port) => {
|
|
if (!port) {
|
|
return false
|
|
}
|
|
return protocol === 'https:' ? port !== String(DEFAULT_HTTPS_PORT) : port !== '80'
|
|
}
|
|
|
|
export const isProxyTLSMode = (sslData) => sslData?.tlsMode === 'proxy'
|
|
|
|
export const buildFinalURL = (sslData, webDomain, location = currentLocation()) => {
|
|
if (!location) {
|
|
return ''
|
|
}
|
|
|
|
const configuredDomain = normalizeDomain(webDomain)
|
|
if (!configuredDomain) {
|
|
return location.href
|
|
}
|
|
|
|
let target
|
|
try {
|
|
target = new URL(`https://${configuredDomain}`)
|
|
} catch (error) {
|
|
return location.href
|
|
}
|
|
|
|
const proxyMode = isProxyTLSMode(sslData)
|
|
const configuredPort = target.port
|
|
const targetPort = Number(sslData?.httpsPort)
|
|
if (!proxyMode) {
|
|
target.port = targetPort > 0 && targetPort !== DEFAULT_HTTPS_PORT ? String(targetPort) : ''
|
|
} else if (!isNonDefaultPort(target.protocol, configuredPort)) {
|
|
target.port = ''
|
|
}
|
|
|
|
target.pathname = location.pathname
|
|
target.search = location.search
|
|
target.hash = location.hash
|
|
return target.toString()
|
|
}
|
|
|
|
const createURL = (value, path) => {
|
|
try {
|
|
const url = new URL(path, value)
|
|
url.search = ''
|
|
url.hash = ''
|
|
return url.toString()
|
|
} catch (error) {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
export const getServiceProbeURLs = (sslData, webDomain, location = currentLocation()) => {
|
|
if (!location) {
|
|
return []
|
|
}
|
|
|
|
const urls = []
|
|
const add = (value, path) => {
|
|
const url = createURL(value, path)
|
|
if (url && !urls.includes(url)) {
|
|
urls.push(url)
|
|
}
|
|
}
|
|
|
|
add(location.href, '/api/ping')
|
|
add(location.href, '/api/v1/ping')
|
|
|
|
const finalURL = buildFinalURL(sslData, webDomain, location)
|
|
if (finalURL) {
|
|
add(finalURL, '/api/ping')
|
|
add(finalURL, '/api/v1/ping')
|
|
}
|
|
return urls
|
|
}
|
|
|
|
export const isServiceProbeSuccess = (response) => {
|
|
if (!response) {
|
|
return false
|
|
}
|
|
return response.status >= 200 && response.status < 300
|
|
}
|
|
|
|
export const isServiceRedirect = (response, location = currentLocation()) => {
|
|
if (!response || !location) {
|
|
return false
|
|
}
|
|
if (response.status < REDIRECT_STATUS_MIN || response.status > REDIRECT_STATUS_MAX) {
|
|
return false
|
|
}
|
|
|
|
try {
|
|
return new URL(response.url).origin === location.origin
|
|
} catch (error) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export const probeService = async (sslData, webDomain, location = currentLocation()) => {
|
|
if (!location || typeof fetch !== 'function') {
|
|
return false
|
|
}
|
|
|
|
const proxyMode = isProxyTLSMode(sslData)
|
|
const urls = getServiceProbeURLs(sslData, webDomain, location)
|
|
for (const url of urls) {
|
|
try {
|
|
const response = await fetch(url, {
|
|
method: 'GET',
|
|
credentials: 'include',
|
|
cache: 'no-store',
|
|
redirect: 'manual',
|
|
headers: {
|
|
Accept: 'application/json'
|
|
}
|
|
})
|
|
if (isServiceProbeSuccess(response)) {
|
|
return true
|
|
}
|
|
if (!proxyMode && isServiceRedirect(response, location)) {
|
|
return true
|
|
}
|
|
} catch (error) {
|
|
// 服务切换期间失败属于预期状态。
|
|
}
|
|
}
|
|
return false
|
|
} |