fix: iframe auto-retry for apps still starting + retry button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-19 14:52:16 +00:00
parent 9b3f9a3c4f
commit 64abb494d5

View File

@ -142,17 +142,29 @@
<h3 class="text-lg font-semibold text-white mb-2">{{ mustOpenNewTab ? 'This app opens in a new tab' : 'App not reachable' }}</h3>
<p class="text-white/50 text-sm mb-6">
<template v-if="mustOpenNewTab">{{ appTitle }} sets security headers that prevent iframe embedding.<br>Open it in a new browser tab instead.</template>
<template v-else>{{ appTitle }} may still be starting up or the container is stopped.<br>Try opening in a new tab or check the app status.</template>
<template v-else>{{ appTitle }} may still be starting up or the container is stopped.<br><span v-if="autoRetryCount > 0" class="text-yellow-400/70">Retrying automatically ({{ autoRetryCount }})...</span></template>
</p>
<button
@click="openNewTabAndBack"
class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
Open in new tab
</button>
<div class="flex items-center gap-3">
<button
v-if="!mustOpenNewTab"
@click="refresh"
class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Retry now
</button>
<button
@click="openNewTabAndBack"
class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
Open in new tab
</button>
</div>
</div>
</div>
</Transition>
@ -211,7 +223,9 @@ const iframeBlocked = ref(false)
const refreshKey = ref(0)
const showIdentityPicker = ref(false)
const showModeMenu = ref(false)
const autoRetryCount = ref(0)
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
let autoRetryId: ReturnType<typeof setTimeout> | null = null
/** Sites known to block iframes — skip the timeout and go straight to fallback */
const IFRAME_BLOCKED_APPS = new Set<string>([])
@ -483,8 +497,10 @@ async function sendIdentity(identity: SelectedIdentity) {
function onLoad() {
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
loading.value = false
isRefreshing.value = false
autoRetryCount.value = 0
// Check if iframe actually loaded content (same-origin only)
setTimeout(() => {
try {
@ -511,9 +527,17 @@ function onError() {
loading.value = false
isRefreshing.value = false
iframeBlocked.value = true
// Auto-retry up to 6 times (60s total) for apps that are still starting
if (!mustOpenNewTab.value && autoRetryCount.value < 6) {
autoRetryId = setTimeout(() => {
autoRetryCount.value++
refresh()
}, 10000)
}
}
function refresh() {
if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
isRefreshing.value = true
loading.value = true
iframeBlocked.value = false
@ -672,6 +696,7 @@ onMounted(() => {
onBeforeUnmount(() => {
if (loadTimeoutId) clearTimeout(loadTimeoutId)
if (autoRetryId) clearTimeout(autoRetryId)
window.removeEventListener('keydown', onKeyDown, true)
window.removeEventListener('message', onMessage)
document.removeEventListener('click', onClickOutside)