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:
parent
9b3f9a3c4f
commit
64abb494d5
@ -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>
|
<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">
|
<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-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>
|
</p>
|
||||||
<button
|
<div class="flex items-center gap-3">
|
||||||
@click="openNewTabAndBack"
|
<button
|
||||||
class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
|
v-if="!mustOpenNewTab"
|
||||||
>
|
@click="refresh"
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
|
||||||
<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>
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
Open in new tab
|
<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" />
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
@ -211,7 +223,9 @@ const iframeBlocked = ref(false)
|
|||||||
const refreshKey = ref(0)
|
const refreshKey = ref(0)
|
||||||
const showIdentityPicker = ref(false)
|
const showIdentityPicker = ref(false)
|
||||||
const showModeMenu = ref(false)
|
const showModeMenu = ref(false)
|
||||||
|
const autoRetryCount = ref(0)
|
||||||
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
|
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 */
|
/** Sites known to block iframes — skip the timeout and go straight to fallback */
|
||||||
const IFRAME_BLOCKED_APPS = new Set<string>([])
|
const IFRAME_BLOCKED_APPS = new Set<string>([])
|
||||||
@ -483,8 +497,10 @@ async function sendIdentity(identity: SelectedIdentity) {
|
|||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
|
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
|
||||||
|
if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
|
||||||
loading.value = false
|
loading.value = false
|
||||||
isRefreshing.value = false
|
isRefreshing.value = false
|
||||||
|
autoRetryCount.value = 0
|
||||||
// Check if iframe actually loaded content (same-origin only)
|
// Check if iframe actually loaded content (same-origin only)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
@ -511,9 +527,17 @@ function onError() {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
isRefreshing.value = false
|
isRefreshing.value = false
|
||||||
iframeBlocked.value = true
|
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() {
|
function refresh() {
|
||||||
|
if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
|
||||||
isRefreshing.value = true
|
isRefreshing.value = true
|
||||||
loading.value = true
|
loading.value = true
|
||||||
iframeBlocked.value = false
|
iframeBlocked.value = false
|
||||||
@ -672,6 +696,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (loadTimeoutId) clearTimeout(loadTimeoutId)
|
if (loadTimeoutId) clearTimeout(loadTimeoutId)
|
||||||
|
if (autoRetryId) clearTimeout(autoRetryId)
|
||||||
window.removeEventListener('keydown', onKeyDown, true)
|
window.removeEventListener('keydown', onKeyDown, true)
|
||||||
window.removeEventListener('message', onMessage)
|
window.removeEventListener('message', onMessage)
|
||||||
document.removeEventListener('click', onClickOutside)
|
document.removeEventListener('click', onClickOutside)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user