442 lines
13 KiB
JavaScript
442 lines
13 KiB
JavaScript
(function() {
|
||
'use strict';
|
||
|
||
window.__APP_CONFIG__ = window.__APP_CONFIG__ || {};
|
||
window.__CONFIG_READY__ = false;
|
||
|
||
let configResolve;
|
||
let configReject;
|
||
window.__CONFIG_PROMISE__ = new Promise((resolve, reject) => {
|
||
configResolve = resolve;
|
||
configReject = reject;
|
||
});
|
||
|
||
const VERSION_SUCCESS_URL_KEY = 'VERSION_Success_URL';
|
||
const CONFIG_SUCCESS_URL_KEY = 'CONFIG_Success_URL';
|
||
const CONFIG_CACHE_KEY = 'APP_CONFIG_CACHE';
|
||
const VERSION_STORAGE_KEY = 'version';
|
||
const VERSION_UPDATE_LOCK_KEY = 'version_update_lock';
|
||
const VERSION_LAST_RELOAD_KEY = 'version_last_reload';
|
||
const AUTH_TOKEN_KEY = 'peransa_token';
|
||
const VERSION_RETRY_ROUNDS = 5;
|
||
const CONFIG_RETRY_ROUNDS = 5;
|
||
const CONFIG_TIMEOUT = 12000;
|
||
|
||
let configTimeoutId;
|
||
let mainScriptLoaded = false;
|
||
|
||
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 getConfigUrls() {
|
||
const protocol = getProtocol();
|
||
|
||
return [
|
||
protocol + '//nerkh.gold/get_user',
|
||
protocol + '//tablo.talasevin.ir/get_user',
|
||
protocol + '//abshode-service.montgit.ir/server/get_user',
|
||
protocol + '//212.23.201.173:8381/get_user',
|
||
protocol + '//91.212.174.137:4040/server/get_user'
|
||
];
|
||
}
|
||
|
||
function sortUrls(urls, successKey) {
|
||
const lastSuccessUrl = localStorage.getItem(successKey);
|
||
if (!lastSuccessUrl) return urls.slice();
|
||
return [lastSuccessUrl].concat(urls.filter((url) => url !== lastSuccessUrl));
|
||
}
|
||
|
||
function clearStaleSuccessUrl(key, url) {
|
||
if (localStorage.getItem(key) === url) {
|
||
localStorage.removeItem(key);
|
||
}
|
||
}
|
||
|
||
function normalizeVersion(value) {
|
||
if (value === null || value === undefined) return '';
|
||
return String(value).trim();
|
||
}
|
||
|
||
function readUserVersion(user) {
|
||
if (!user || typeof user !== 'object' || Array.isArray(user)) return '';
|
||
const version = user.version ?? user.value;
|
||
return version === undefined || version === null ? '' : String(version).trim();
|
||
}
|
||
|
||
function normalizeVersionResponse(response) {
|
||
if (typeof response === 'string' || typeof response === 'number') {
|
||
return String(response).trim();
|
||
}
|
||
if (!response || typeof response !== 'object') return '';
|
||
|
||
if (typeof response.data === 'string' || typeof response.data === 'number') {
|
||
return String(response.data).trim();
|
||
}
|
||
|
||
const directVersion =
|
||
response.version ??
|
||
response.value ??
|
||
(response.data && typeof response.data === 'object'
|
||
? response.data.version ?? response.data.value
|
||
: undefined);
|
||
|
||
if (directVersion !== undefined && directVersion !== null) {
|
||
return String(directVersion).trim();
|
||
}
|
||
|
||
return readUserVersion(response.user);
|
||
}
|
||
|
||
function readCachedAppConfig() {
|
||
try {
|
||
const raw = localStorage.getItem(CONFIG_CACHE_KEY);
|
||
if (!raw) return null;
|
||
const payload = JSON.parse(raw);
|
||
if (!payload || typeof payload !== 'object' || !payload.config) return null;
|
||
return payload.config;
|
||
} catch (error) {
|
||
console.warn('Failed to read cached get_user:', error);
|
||
localStorage.removeItem(CONFIG_CACHE_KEY);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function writeCachedAppConfig(config) {
|
||
try {
|
||
localStorage.setItem(
|
||
CONFIG_CACHE_KEY,
|
||
JSON.stringify({
|
||
cached_at: Date.now(),
|
||
config: config
|
||
})
|
||
);
|
||
} catch (error) {
|
||
console.warn('Failed to write get_user cache:', error);
|
||
}
|
||
}
|
||
|
||
function getStoredAppVersion() {
|
||
return localStorage.getItem(VERSION_STORAGE_KEY);
|
||
}
|
||
|
||
function writeStoredAppVersion(version) {
|
||
localStorage.setItem(VERSION_STORAGE_KEY, normalizeVersion(version));
|
||
}
|
||
|
||
function shouldUpdateForVersion(serverVersion) {
|
||
const normalized = normalizeVersion(serverVersion);
|
||
if (!normalized) return false;
|
||
|
||
const stored = getStoredAppVersion();
|
||
if (!stored) {
|
||
writeStoredAppVersion(normalized);
|
||
return false;
|
||
}
|
||
|
||
return stored !== normalized;
|
||
}
|
||
|
||
function canStartVersionUpdate(serverVersion) {
|
||
const version = normalizeVersion(serverVersion);
|
||
if (!version) return false;
|
||
|
||
if (sessionStorage.getItem(VERSION_LAST_RELOAD_KEY) === version) {
|
||
return false;
|
||
}
|
||
|
||
const rawLock = sessionStorage.getItem(VERSION_UPDATE_LOCK_KEY);
|
||
if (rawLock) {
|
||
try {
|
||
const lock = JSON.parse(rawLock);
|
||
const isSameVersion = lock.version === version;
|
||
const isFreshLock = Date.now() - lock.started_at < 60_000;
|
||
if (isSameVersion && isFreshLock) return false;
|
||
} catch {
|
||
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
function startVersionUpdateLock(serverVersion) {
|
||
sessionStorage.setItem(
|
||
VERSION_UPDATE_LOCK_KEY,
|
||
JSON.stringify({
|
||
version: normalizeVersion(serverVersion),
|
||
started_at: Date.now()
|
||
})
|
||
);
|
||
}
|
||
|
||
function finishVersionUpdateBeforeReload(serverVersion) {
|
||
const version = normalizeVersion(serverVersion);
|
||
writeStoredAppVersion(version);
|
||
sessionStorage.setItem(VERSION_LAST_RELOAD_KEY, version);
|
||
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||
}
|
||
|
||
function cancelVersionUpdateLock() {
|
||
sessionStorage.removeItem(VERSION_UPDATE_LOCK_KEY);
|
||
}
|
||
|
||
function preserveAuthToken() {
|
||
return localStorage.getItem(AUTH_TOKEN_KEY);
|
||
}
|
||
|
||
function restoreAuthToken(token) {
|
||
if (token) {
|
||
localStorage.setItem(AUTH_TOKEN_KEY, token);
|
||
}
|
||
}
|
||
|
||
function clearBootstrapCachesKeepToken() {
|
||
const token = preserveAuthToken();
|
||
localStorage.removeItem(CONFIG_CACHE_KEY);
|
||
localStorage.removeItem(CONFIG_SUCCESS_URL_KEY);
|
||
localStorage.removeItem(VERSION_SUCCESS_URL_KEY);
|
||
restoreAuthToken(token);
|
||
}
|
||
|
||
function hardReloadApp() {
|
||
const refreshUrl =
|
||
window.location.origin +
|
||
window.location.pathname +
|
||
'?__app_refresh=' +
|
||
Date.now() +
|
||
(window.location.hash || '');
|
||
window.location.replace(refreshUrl);
|
||
}
|
||
|
||
async function fetchJson(url, timeout) {
|
||
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('Request failed: ' + response.status);
|
||
}
|
||
|
||
const contentType = response.headers.get('content-type') || '';
|
||
|
||
if (!contentType.includes('application/json')) {
|
||
const text = await response.text();
|
||
throw new Error('Response is not JSON. Received: ' + text.substring(0, 100));
|
||
}
|
||
|
||
return await response.json();
|
||
} finally {
|
||
window.clearTimeout(timeoutId);
|
||
}
|
||
}
|
||
|
||
async function loadVersion(rounds) {
|
||
rounds = rounds || VERSION_RETRY_ROUNDS;
|
||
const urls = sortUrls(getVersionUrls(), VERSION_SUCCESS_URL_KEY);
|
||
|
||
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 data = await fetchJson(url, timeout);
|
||
const version = normalizeVersionResponse(data);
|
||
if (!version) {
|
||
throw new Error('Version response is empty');
|
||
}
|
||
localStorage.setItem(VERSION_SUCCESS_URL_KEY, url);
|
||
console.log('Version loaded from:', url, version);
|
||
return version;
|
||
} catch (error) {
|
||
clearStaleSuccessUrl(VERSION_SUCCESS_URL_KEY, url);
|
||
console.warn('Version failed from', url, 'round:', round, error);
|
||
}
|
||
}
|
||
}
|
||
|
||
console.warn('Version check failed from all endpoints');
|
||
return null;
|
||
}
|
||
|
||
async function fetchConfig(rounds) {
|
||
rounds = rounds || CONFIG_RETRY_ROUNDS;
|
||
const urls = sortUrls(getConfigUrls(), CONFIG_SUCCESS_URL_KEY);
|
||
|
||
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 ? 7000 : 4000;
|
||
|
||
try {
|
||
const config = await fetchJson(url, timeout);
|
||
|
||
if (!config || typeof config !== 'object') {
|
||
throw new Error('Config response is invalid');
|
||
}
|
||
|
||
localStorage.setItem(CONFIG_SUCCESS_URL_KEY, url);
|
||
writeCachedAppConfig(config);
|
||
return { config: config, url: url };
|
||
} catch (error) {
|
||
clearStaleSuccessUrl(CONFIG_SUCCESS_URL_KEY, url);
|
||
console.warn('Config failed from', url, 'round:', round, error);
|
||
}
|
||
}
|
||
}
|
||
|
||
throw new Error('Cannot load config from any server');
|
||
}
|
||
|
||
function applyConfig(config) {
|
||
window.__APP_CONFIG__ = config || {};
|
||
window.__CONFIG_READY__ = true;
|
||
|
||
if (config && config.user && config.user.name_fa) {
|
||
document.title = 'پنل مدیریت ' + '-' + config.user.name_fa;
|
||
}
|
||
|
||
if (config && config.user && config.user.avatar) {
|
||
const faviconUrl = config.user.avatar;
|
||
const existingIcons = document.querySelectorAll('link[rel*="icon"]');
|
||
existingIcons.forEach((icon) => icon.remove());
|
||
|
||
const link = document.createElement('link');
|
||
link.rel = 'icon';
|
||
link.type = 'image/png';
|
||
link.href = faviconUrl + '?v=' + Date.now();
|
||
document.head.appendChild(link);
|
||
}
|
||
}
|
||
|
||
function finishConfigLoad(config, error) {
|
||
if (configResolve) {
|
||
configResolve(config);
|
||
}
|
||
|
||
if (error) {
|
||
window.dispatchEvent(new CustomEvent('configError', { detail: error }));
|
||
} else {
|
||
window.dispatchEvent(new CustomEvent('configReady', { detail: config }));
|
||
}
|
||
|
||
if (configTimeoutId) {
|
||
clearTimeout(configTimeoutId);
|
||
configTimeoutId = null;
|
||
}
|
||
|
||
loadMainScript();
|
||
}
|
||
|
||
async function applyVersionUpdate(serverVersion) {
|
||
startVersionUpdateLock(serverVersion);
|
||
|
||
try {
|
||
const { config } = await fetchConfig();
|
||
clearBootstrapCachesKeepToken();
|
||
writeCachedAppConfig(config);
|
||
applyConfig(config);
|
||
finishVersionUpdateBeforeReload(serverVersion);
|
||
console.log('App version changed — hard refresh', serverVersion);
|
||
hardReloadApp();
|
||
return true;
|
||
} catch (error) {
|
||
console.error('Version update failed:', error);
|
||
cancelVersionUpdateLock();
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function loadConfig() {
|
||
try {
|
||
// ۱) کش get_user — اگر بود سریع اعمال شود
|
||
const cachedConfig = readCachedAppConfig();
|
||
if (cachedConfig) {
|
||
applyConfig(cachedConfig);
|
||
console.log('Applied cached get_user');
|
||
}
|
||
|
||
// ۲) get_version — اگر تغییر کرده hard refresh
|
||
const remoteVersion = await loadVersion();
|
||
if (remoteVersion && shouldUpdateForVersion(remoteVersion) && canStartVersionUpdate(remoteVersion)) {
|
||
const updating = await applyVersionUpdate(remoteVersion);
|
||
if (updating) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
// ۳) تازه کردن get_user و کش
|
||
const { config, url } = await fetchConfig();
|
||
applyConfig(config);
|
||
console.log('Config loaded successfully from', url, config);
|
||
finishConfigLoad(config);
|
||
} catch (error) {
|
||
console.warn('Config loader: Could not fetch config from any endpoint');
|
||
console.warn('Error details:', error && error.message ? error.message : error);
|
||
console.warn('App will continue with empty/cached config.');
|
||
|
||
const cachedConfig = readCachedAppConfig();
|
||
if (cachedConfig) {
|
||
applyConfig(cachedConfig);
|
||
finishConfigLoad(cachedConfig, error);
|
||
return;
|
||
}
|
||
|
||
window.__APP_CONFIG__ = {};
|
||
window.__CONFIG_READY__ = true;
|
||
finishConfigLoad({}, error);
|
||
}
|
||
}
|
||
|
||
function loadMainScript() {
|
||
if (mainScriptLoaded || document.querySelector('script[src="/src/main.ts"]')) {
|
||
return;
|
||
}
|
||
|
||
mainScriptLoaded = true;
|
||
|
||
import('/src/main.ts').then(() => {
|
||
console.log('Main script loaded and executed');
|
||
});
|
||
}
|
||
|
||
configTimeoutId = setTimeout(function() {
|
||
if (!mainScriptLoaded) {
|
||
console.warn('Config loader: Timeout reached. Loading main.ts with empty/cached config.');
|
||
const cachedConfig = readCachedAppConfig();
|
||
window.__APP_CONFIG__ = cachedConfig || window.__APP_CONFIG__ || {};
|
||
window.__CONFIG_READY__ = true;
|
||
|
||
if (configResolve) {
|
||
configResolve(window.__APP_CONFIG__);
|
||
}
|
||
|
||
loadMainScript();
|
||
}
|
||
}, CONFIG_TIMEOUT);
|
||
|
||
loadConfig();
|
||
})();
|