Port modular price, product, and PWA fixes into boursecu
Sync live quote handling, SSE product images, store hours, file URL normalization, PWA icon cache, TV monitor, about software, SuperAdmin menu gates, price alerts, and related UX from modular while preserving boursecu branding and config constraints. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+52
-42
@@ -1,3 +1,11 @@
|
||||
import {
|
||||
allowSuperAdminFallback,
|
||||
getSuperAdminBases,
|
||||
isSuperAdminUrl,
|
||||
resolveTenantDomain,
|
||||
SA_RETRY_ROUNDS,
|
||||
} from '@/config/superadmin'
|
||||
|
||||
type VersionResponse =
|
||||
| string
|
||||
| number
|
||||
@@ -9,36 +17,25 @@ type VersionResponse =
|
||||
}
|
||||
|
||||
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL'
|
||||
export const VERSION_RETRY_ROUNDS = 5
|
||||
export const VERSION_RETRY_ROUNDS = SA_RETRY_ROUNDS
|
||||
|
||||
let versionFetchPromise: Promise<string | null> | null = null
|
||||
let cachedVersionResult: string | null | undefined
|
||||
|
||||
function getProtocol() {
|
||||
return window.location.protocol === 'https:' ? 'https:' : 'http:'
|
||||
}
|
||||
|
||||
/** فقط SuperAdmin — بدون رجیستری قدیمی */
|
||||
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`
|
||||
]
|
||||
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) {
|
||||
return urls
|
||||
if (!lastSuccessUrl || !urls.includes(lastSuccessUrl) || !isSuperAdminUrl(lastSuccessUrl)) {
|
||||
if (lastSuccessUrl && !isSuperAdminUrl(lastSuccessUrl)) {
|
||||
localStorage.removeItem(VERSION_SUCCESS_URL_KEY)
|
||||
}
|
||||
return [...urls]
|
||||
}
|
||||
|
||||
const others = urls.filter((url) => url !== lastSuccessUrl)
|
||||
return [lastSuccessUrl, ...others]
|
||||
return [lastSuccessUrl, ...urls.filter((url) => url !== lastSuccessUrl)]
|
||||
}
|
||||
|
||||
function clearStaleVersionSuccessUrl(url: string) {
|
||||
@@ -89,10 +86,10 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
Accept: 'application/json',
|
||||
},
|
||||
cache: 'no-store',
|
||||
signal: controller.signal
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -100,10 +97,11 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') ?? ''
|
||||
if (!contentType.includes('application/json')) {
|
||||
throw new Error('Version response is not JSON')
|
||||
}
|
||||
|
||||
const data = contentType.includes('application/json')
|
||||
? ((await response.json()) as VersionResponse)
|
||||
: ((await response.text()) as VersionResponse)
|
||||
const data = (await response.json()) as VersionResponse
|
||||
|
||||
const version = normalizeVersionResponse(data)
|
||||
|
||||
@@ -112,15 +110,17 @@ async function fetchVersion(url: string, timeout = 3500): Promise<string | null>
|
||||
}
|
||||
|
||||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url)
|
||||
|
||||
return version
|
||||
} finally {
|
||||
window.clearTimeout(timeoutId)
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise<string | null> {
|
||||
const urls = sortVersionUrls(getVersionUrls())
|
||||
async function tryFetchVersionFromUrls(
|
||||
urls: string[],
|
||||
rounds: number,
|
||||
): Promise<string | null> {
|
||||
if (!urls.length) return null
|
||||
|
||||
for (let round = 1; round <= rounds; round++) {
|
||||
for (let index = 0; index < urls.length; index++) {
|
||||
@@ -142,8 +142,27 @@ async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise<str
|
||||
return null
|
||||
}
|
||||
|
||||
async function fetchVersionWithRetry(rounds = VERSION_RETRY_ROUNDS): Promise<string | null> {
|
||||
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() {
|
||||
cachedVersionResult = undefined
|
||||
versionFetchPromise = null
|
||||
}
|
||||
|
||||
@@ -158,22 +177,13 @@ export async function loadRemoteAppVersion(options?: {
|
||||
resetVersionCheckCache()
|
||||
}
|
||||
|
||||
if (cachedVersionResult !== undefined) {
|
||||
return cachedVersionResult
|
||||
}
|
||||
|
||||
if (versionFetchPromise) {
|
||||
return versionFetchPromise
|
||||
}
|
||||
|
||||
versionFetchPromise = fetchVersionWithRetry(rounds)
|
||||
.then((version) => {
|
||||
cachedVersionResult = version
|
||||
return version
|
||||
})
|
||||
.finally(() => {
|
||||
versionFetchPromise = null
|
||||
})
|
||||
versionFetchPromise = fetchVersionWithRetry(rounds).finally(() => {
|
||||
versionFetchPromise = null
|
||||
})
|
||||
|
||||
return versionFetchPromise
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user