import { allowSuperAdminFallback, getSuperAdminBases, isSuperAdminUrl, resolveTenantDomain, SA_RETRY_ROUNDS, } from '@/config/superadmin' 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 = SA_RETRY_ROUNDS let versionFetchPromise: Promise | null = null /** فقط SuperAdmin — بدون رجیستری قدیمی */ function getVersionUrls() { const domain = encodeURIComponent(resolveTenantDomain()) return getSuperAdminBases().map((base) => `${base}/get_version?domain=${domain}`) } function sortVersionUrls(urls: string[]) { const lastSuccessUrl = localStorage.getItem(VERSION_SUCCESS_URL_KEY) if (!lastSuccessUrl || !urls.includes(lastSuccessUrl) || !isSuperAdminUrl(lastSuccessUrl)) { if (lastSuccessUrl && !isSuperAdminUrl(lastSuccessUrl)) { localStorage.removeItem(VERSION_SUCCESS_URL_KEY) } return [...urls] } return [lastSuccessUrl, ...urls.filter((url) => url !== lastSuccessUrl)] } 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') ?? '' if (!contentType.includes('application/json')) { throw new Error('Version response is not JSON') } const data = (await response.json()) 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 tryFetchVersionFromUrls( urls: string[], rounds: number, ): Promise { if (!urls.length) return null 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 } async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise { const urls = sortVersionUrls(getVersionUrls()) const saUrls = urls.filter(isSuperAdminUrl) const otherUrls = urls.filter((url) => !isSuperAdminUrl(url)) const lists = saUrls.length > 0 ? allowSuperAdminFallback() ? [saUrls, otherUrls] : [saUrls] : [urls] for (const list of lists) { const version = await tryFetchVersionFromUrls(list, rounds) if (version) return version } return null } export function resetVersionCheckCache() { 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 (versionFetchPromise) { return versionFetchPromise } versionFetchPromise = fetchVersionWithRetry(rounds).finally(() => { versionFetchPromise = null }) return versionFetchPromise }