fix: disable HTTP keep-alive and update nginx proxy config

- Set http1_keep_alive(false) on hyper server to prevent connection
  reuse issues with nginx reverse proxy
- Clean up nginx proxy config: remove upstream block, use direct
  proxy_pass to 127.0.0.1:5678
- Update AppLauncherOverlay and appLauncher store with UI fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 09:54:15 +00:00
parent 0cf71c4115
commit 0fb373273a
4 changed files with 123 additions and 35 deletions

View File

@@ -77,14 +77,39 @@
</Transition>
<iframe
ref="iframeRef"
v-if="store.url"
v-if="store.url && !iframeBlocked"
:key="iframeRefreshKey"
:src="store.url"
class="absolute inset-0 w-full h-full border-0 iframe-scrollbar-hide"
title="App content"
@load="onIframeLoad"
@error="onIframeError"
/>
<!-- Iframe blocked fallback -->
<Transition name="content-fade">
<div v-if="iframeBlocked && !iframeLoading" class="absolute inset-0 z-10 flex flex-col items-center justify-center">
<div class="text-center px-8">
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center">
<svg class="w-8 h-8 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8V6a2 2 0 012-2h14a2 2 0 012 2v12a2 2 0 01-2 2H5a2 2 0 01-2-2v-2m0-8h18M3 8v8m18-8v8" />
</svg>
</div>
<h3 class="text-lg font-semibold text-white mb-2">Can't display in frame</h3>
<p class="text-white/50 text-sm mb-6">This app doesn't support embedded viewing.<br>Please open it in a new tab instead.</p>
<button
@click="openInNewTabAndClose"
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>
</Transition>
<!-- Payment Confirmation Dialog -->
<Transition name="content-fade">
<div v-if="pendingPayment" class="absolute inset-0 z-20 flex items-center justify-center bg-black/70 backdrop-blur-sm">
@@ -158,6 +183,16 @@ const iframeRef = ref<HTMLIFrameElement | null>(null)
const iframeRefreshKey = ref(0)
const isRefreshing = ref(false)
const iframeLoading = ref(true)
const iframeBlocked = ref(false)
// Timers for iframe load detection
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
let contentCheckId: ReturnType<typeof setTimeout> | null = null
function clearTimers() {
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
if (contentCheckId) { clearTimeout(contentCheckId); contentCheckId = null }
}
// Wallet connect — payment request state
const pendingPayment = ref<PaymentRequest | null>(null)
@@ -168,7 +203,15 @@ const paymentOrigin = ref('')
function refreshIframe() {
isRefreshing.value = true
iframeLoading.value = true
iframeBlocked.value = false
clearTimers()
iframeRefreshKey.value++
loadTimeoutId = setTimeout(() => {
if (iframeLoading.value) {
iframeLoading.value = false
iframeBlocked.value = true
}
}, 15000)
}
function openInNewTab() {
@@ -177,11 +220,44 @@ function openInNewTab() {
}
}
function openInNewTabAndClose() {
openInNewTab()
store.close()
}
function onIframeLoad() {
injectScrollbarHideIfSameOrigin()
isRefreshing.value = false
iframeLoading.value = false
sendIdentityIfSupported()
// Clear the load timeout
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
// Check iframe content after a brief delay to let the app render
contentCheckId = setTimeout(checkIframeContent, 2000)
}
function onIframeError() {
clearTimers()
iframeLoading.value = false
iframeBlocked.value = true
}
/** Check if the iframe loaded meaningful content (same-origin only) */
function checkIframeContent() {
try {
const iframe = iframeRef.value
if (!iframe) return
const doc = iframe.contentDocument
if (!doc) return // Cross-origin — can't check, assume OK
const body = doc.body
if (!body || (body.children.length === 0 && body.innerText.trim() === '')) {
iframeBlocked.value = true
}
} catch {
// Cross-origin: can't access, assume working
}
}
/** Apps that support the Archipelago identity protocol (postMessage) */
@@ -379,10 +455,21 @@ watch(
(open) => {
if (open) {
iframeLoading.value = true
iframeBlocked.value = false
clearTimers()
// Set max load timeout — if iframe never fires load, show fallback
loadTimeoutId = setTimeout(() => {
if (iframeLoading.value) {
iframeLoading.value = false
iframeBlocked.value = true
}
}, 15000)
closeBtnRef.value?.focus()
} else {
isRefreshing.value = false
iframeLoading.value = true
iframeBlocked.value = false
clearTimers()
// Clear any pending payment when closing
if (pendingPayment.value) {
rejectPayment()
@@ -397,6 +484,7 @@ onMounted(() => {
})
onBeforeUnmount(() => {
clearTimers()
window.removeEventListener('keydown', onKeyDown, true)
window.removeEventListener('message', onMessage)
})