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' export const VERSION_RETRY_ROUNDS = 5 let versionFetchPromise: Promise | null = null let cachedVersionResult: string | null | undefined function getProtocol() { return window.location.protocol === 'https:' ? 'https:' : 'http:' } function getVersionUrls() { const protocol = getProtocol() return [ 'https://nerkh.gold/get_version', 'https://tablo.talasevin.ir/get_version', 'https://abshode-service.montgit.ir/server/get_version', `${protocol}//212.23.201.173:8381/get_version`, `${protocol}//91.212.174.137:4040/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 clearStaleVersionSuccessUrl(url: string) { if (localStorage.getItem(VERSION_SUCCESS_URL_KEY) === url) { localStorage.removeItem(VERSION_SUCCESS_URL_KEY) } } 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) { 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) if (!version) { throw new Error('Version response is empty') } 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) if (version) { return version } } catch (error) { clearStaleVersionSuccessUrl(url) 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 }