type VersionResponse = | string | number | { version?: string | number data?: string | number | { version?: string | number; value?: string | number } value?: string | number user?: unknown } const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL' const VERSION_RETRY_ROUNDS = 5 let versionFetchPromise: Promise | null = null let cachedVersionResult: string | null | undefined function getVersionUrls() { return [ 'https://nerkh.gold/get_version', 'https://tablo.talasevin.ir/get_version', 'https://abshode-service.montgit.ir/server/get_version' ] } function sortVersionUrls(urls: string[]) { const lastSuccessUrl = localStorage.getItem(VERSION_SUCCESS_URL_KEY) if (!lastSuccessUrl) { return urls } const others = urls.filter((url) => url !== lastSuccessUrl) return [lastSuccessUrl, ...others] } function readUserVersion(user: unknown) { if (!user || typeof user !== 'object' || Array.isArray(user)) { return '' } const record = user as { version?: string | number; value?: string | number } const version = record.version ?? record.value return version === undefined || version === null ? '' : String(version).trim() } function normalizeVersionResponse(response: VersionResponse) { if (typeof response === 'string' || typeof response === 'number') { return String(response).trim() } if (!response) return '' if (typeof response.data === 'string' || typeof response.data === 'number') { return String(response.data).trim() } const directVersion = response.version ?? response.value ?? (typeof response.data === 'object' ? response.data?.version ?? response.data?.value : undefined) if (directVersion !== undefined && directVersion !== null) { return String(directVersion).trim() } return readUserVersion(response.user) } async function fetchVersion(url: string, timeout = 3500): Promise { const controller = new AbortController() const timeoutId = window.setTimeout(() => controller.abort(), timeout) try { const response = await fetch(url, { method: 'GET', headers: { Accept: 'application/json' }, cache: 'no-store', signal: controller.signal }) if (!response.ok) { // Non-2xx/timeout should be treated as "request failed" and we may try the next URL. throw new Error(`Version request failed with status ${response.status}`) } const contentType = response.headers.get('content-type') ?? '' const data = contentType.includes('application/json') ? ((await response.json()) as VersionResponse) : ((await response.text()) as VersionResponse) const version = normalizeVersionResponse(data) // IMPORTANT: if the request returned 200/ok, we stop here even if we couldn't extract a version. // This prevents unnecessary looping when the endpoint is "reachable" but returns an empty payload. if (!version) { return null } localStorage.setItem(VERSION_SUCCESS_URL_KEY, url) return version } finally { window.clearTimeout(timeoutId) } } async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise { const urls = sortVersionUrls(getVersionUrls()) for (let round = 1; round <= rounds; round++) { for (let index = 0; index < urls.length; index++) { const url = urls[index] const timeout = index === urls.length - 1 ? 6000 : 3500 try { const version = await fetchVersion(url, timeout) // fetchVersion only throws on real request failures (non-ok / timeout). // If it returns null, we got an HTTP 200 but empty body => stop immediately. return version } catch (error) { console.warn(`Version failed from ${url}. Round: ${round}`, error) } } } return null } export function resetVersionCheckCache() { cachedVersionResult = undefined versionFetchPromise = null } export async function loadRemoteAppVersion(options?: { rounds?: number force?: boolean }): Promise { const rounds = options?.rounds ?? VERSION_RETRY_ROUNDS const force = options?.force ?? false if (force) { resetVersionCheckCache() } if (cachedVersionResult !== undefined) { return cachedVersionResult } if (versionFetchPromise) { return versionFetchPromise } versionFetchPromise = fetchVersionWithRetry(rounds) .then((version) => { cachedVersionResult = version return version }) .finally(() => { versionFetchPromise = null }) return versionFetchPromise }