File size: 6,050 Bytes
9f207c1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>GPT-5</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; }
#loading {
position: fixed; inset: 0; background: rgba(255,255,255,0.9);
display: flex; justify-content: center; align-items: center;
font-size: 18px; z-index: 1000;
}
iframe { width: 100%; height: 100vh; border: none; }
</style>
</head>
<body>
<div id="loading">Please wait... Memilih server optimal...</div>
<iframe id="streamlit-frame" referrerpolicy="no-referrer"></iframe>
<script>
const VERSION = "v1";
const SERVERS = [
"https://gepatest-hwak3xkal5nltlveztf5nn.streamlit.app/?embed=true"
];
// Timeout definisi
const pingTimeoutMs = 5000; // timeout ping img
const iframeLoadTimeoutMs = 12000; // waktu nunggu iframe dianggap "siap"
const overallTryTimeoutMs = 20000; // batas keras setiap percobaan server
// Ping ringan via <img> untuk menghindari no-cors opaque fetch
function ping(url, timeout = pingTimeoutMs) {
return new Promise((resolve) => {
const start = performance.now();
const img = new Image();
let done = false;
const timer = setTimeout(() => {
if (done) return;
done = true;
// Timeout dianggap gagal (Infinity)
resolve({ url, ok: false, latency: Infinity });
img.src = ""; // abort
}, timeout);
const onComplete = (ok) => {
if (done) return;
done = true;
clearTimeout(timer);
const latency = performance.now() - start;
resolve({ url, ok, latency });
};
img.onload = () => onComplete(true);
img.onerror = () => onComplete(false);
// Gunakan endpoint root dengan cache buster (Streamlit akan render HTML, img akan gagal, tapi kita tetap dapat latency)
// Trik: walau img gagal (onerror), kita catat latency untuk RTT awal.
// Tambah cb agar tidak cached.
const u = new URL(url);
u.searchParams.set("cb", Date.now().toString());
img.src = u.toString();
});
}
async function rankServersByPing(urls) {
const results = await Promise.all(urls.map(u => ping(u)));
// Urutkan berdasarkan latency terkecil
return results
.sort((a,b) => a.latency - b.latency)
.map(r => r.url);
}
function setIframeSrc(iframe, baseUrl) {
const u = new URL(baseUrl);
u.searchParams.set("cb", Date.now().toString());
iframe.src = u.toString();
}
// Coba load satu server dengan mekanisme timeout. Kembalikan true jika dianggap siap, false jika gagal.
function tryLoadServer(iframe, url) {
return new Promise((resolve) => {
let settled = false;
// Hard timeout untuk satu percobaan
const hardTimer = setTimeout(() => {
if (settled) return;
settled = true;
resolve(false);
}, overallTryTimeoutMs);
// Timer "app-ready" (karena onload bisa sukses walau app error)
const readyTimer = setTimeout(() => {
if (settled) return;
// Lewati: tidak ada sinyal ready; anggap gagal
settled = true;
clearTimeout(hardTimer);
resolve(false);
}, iframeLoadTimeoutMs);
// onload: dokumen termuat. Namun belum tentu app sehat.
iframe.onload = () => {
// Tetap menunggu sampai readyTimer. Jika Anda punya postMessage dari app,
// di sini seharusnya menunggu pesan "ready" untuk resolve(true).
// Karena tidak ada, kita pakai heuristik: beri sedikit grace period lalu anggap OK.
// Grace singkat agar tidak menunggu lama kalau memang error.
setTimeout(() => {
if (settled) return;
settled = true;
clearTimeout(readyTimer);
clearTimeout(hardTimer);
resolve(true);
}, 1500); // grace 1.5s setelah onload
};
// onerror hanya terjadi untuk kegagalan jaringan keras
iframe.onerror = () => {
if (settled) return;
settled = true;
clearTimeout(readyTimer);
clearTimeout(hardTimer);
resolve(false);
};
setIframeSrc(iframe, url);
});
}
async function loadWithFallback() {
const iframe = document.getElementById("streamlit-frame");
const loading = document.getElementById("loading");
const storageKey = `streamlitUrl_${VERSION}`;
// Coba baca cache sebelumnya
const cached = sessionStorage.getItem(storageKey);
// Urutkan server berdasarkan ping (kasih prioritas yang lebih cepat).
let prioritized = await rankServersByPing(SERVERS);
// Jika ada cache, letakkan di depan daftar supaya dicoba dulu
if (cached && prioritized.includes(cached)) {
prioritized = [cached, ...prioritized.filter(u => u !== cached)];
}
for (const url of prioritized) {
const ok = await tryLoadServer(iframe, url);
if (ok) {
// Anggap server siap. Simpan ke sessionStorage.
sessionStorage.setItem(storageKey, url);
loading.style.display = "none";
// Pasang watchdog: jika nanti ada kegagalan reload (jarang), bersihkan cache.
iframe.addEventListener('error', () => {
sessionStorage.removeItem(storageKey);
});
return;
}
}
// Semua gagal: bersihkan cache dan tampilkan pesan sederhana
sessionStorage.removeItem(storageKey);
loading.textContent = "Semua server gagal dimuat. Coba refresh halaman.";
}
window.addEventListener("load", loadWithFallback);
</script>
</body>
</html> |