feat(ui): auto-tab fallback — embed-refusing apps become tab apps
Demo images / Build & push demo images (push) Successful in 3m49s
Demo images / Build & push demo images (push) Successful in 3m49s
An app whose frame never loads while its backend reports Running (the embed-refusal signature: frame-busting JS, top-level-origin apps, SameSite=Strict logins — everything the gate's header stripping cannot fix) is remembered in localStorage; every later launch opens a tab straight from the click (user gesture, so no popup blocker), and opensInTab() gives it the tab-launch icon. A successful iframe load clears the memory and entries expire after 7 days, so nodes that gain embedding (gate improvements) get re-probed instead of being remembered broken forever. Dev guide updated; v1.8.2 changelog + What's New curated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
63cb9dd22c
commit
2399eeac66
@@ -105,6 +105,7 @@ import { useAppLauncherStore } from '@/stores/appLauncher'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useScreensaverStore } from '@/stores/screensaver'
|
||||
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
|
||||
import { isAutoTabApp, rememberAutoTabApp, forgetAutoTabApp } from '@/utils/autoTabApps'
|
||||
import AppSessionHeader from './appSession/AppSessionHeader.vue'
|
||||
import AppSessionFrame from './appSession/AppSessionFrame.vue'
|
||||
import MobileGamepad from './appSession/MobileGamepad.vue'
|
||||
@@ -199,9 +200,27 @@ function updateIsMobile() { isMobile.value = window.innerWidth < 768 }
|
||||
// same-origin by the mock backend, so the prod new-tab list doesn't apply.
|
||||
const mustOpenNewTab = computed(() =>
|
||||
(NEW_TAB_APPS.has(appId.value) && !(IS_DEMO && isDemoApp(appId.value))) ||
|
||||
// Remembered embed-refusers (frame-busting, top-level-origin apps): the
|
||||
// first blocked encounter records them, every later launch is a tab app.
|
||||
isAutoTabApp(appId.value) ||
|
||||
(IS_DEMO && isDemoExternal(appId.value))
|
||||
)
|
||||
|
||||
// The auto-tab detector: the load-timeout marked the frame blocked, the
|
||||
// warming-up retry loop has given up, and the backend says the app is
|
||||
// actually RUNNING — that combination is the embed-refusal signature (a
|
||||
// down app is "warming up" or shows a blocked reason instead). Remember it
|
||||
// so this app never shows the dead pane again; a later successful iframe
|
||||
// load (onLoad) clears the memory, and entries expire on their own.
|
||||
watch([iframeBlocked, warmingUp], ([blocked, warming]) => {
|
||||
if (
|
||||
blocked && !warming && !blockedReason.value &&
|
||||
(packageEntry.value?.state === PackageState.Running)
|
||||
) {
|
||||
rememberAutoTabApp(appId.value)
|
||||
}
|
||||
})
|
||||
|
||||
// ElectrumX shows a sync screen before its real UI (the Electrum server only
|
||||
// serves clients once its index is built). Poll /electrs-status while this is
|
||||
// the Electrum app; pass the status to the frame only while still syncing.
|
||||
@@ -344,6 +363,10 @@ function onLoad() {
|
||||
loading.value = false
|
||||
isRefreshing.value = false
|
||||
autoRetryCount.value = 0
|
||||
// A frame that loads embeds fine — heal any stale auto-tab memory (for
|
||||
// example an app remembered as blocked before the gate learned to strip
|
||||
// frame headers).
|
||||
forgetAutoTabApp(appId.value)
|
||||
// TV/keyboard: hand focus to the app so keys (incl. the gamepad bridge's
|
||||
// virtual keyboard) flow into the iframe without needing a pointer click.
|
||||
try { frameRef.value?.iframeRef?.focus() } catch { /* cross-origin is fine */ }
|
||||
|
||||
Reference in New Issue
Block a user