Add signed node-scoped demo catalogs and retained app media sessions

This commit is contained in:
archipelago
2026-10-06 00:53:34 -04:00
parent 131c39cf74
commit e54f83df8f
18 changed files with 706 additions and 20 deletions
+10 -2
View File
@@ -131,6 +131,7 @@ import {
} from './appSession/appSessionConfig'
import { launchBlockedReason, resolveAppIcon } from './apps/appsConfig'
import { PackageState } from '@/types/api'
import { useAppMediaBridge } from '@/composables/useAppMediaBridge'
import { useAppIdentity } from './appSession/useAppIdentity'
import { useNostrBridge } from './appSession/useNostrBridge'
import { openExternalUrl, openInAppOrNewTab } from '@/utils/openExternal'
@@ -141,6 +142,7 @@ const props = defineProps<{
appIdProp?: string
/** Deep-link path inside the app (store-driven sessions), e.g. /tx/<hash> */
pathProp?: string
suspended?: boolean
}>()
const emit = defineEmits<{
@@ -179,8 +181,8 @@ const appId = computed(() => {
// Display mode -- per-app user choice → per-app default → last global → panel
const displayMode = ref<DisplayMode>(initialDisplayMode(appId.value))
const appTitle = computed(() => resolveAppTitle(appId.value))
const packageEntry = computed(() => store.data?.['package-data']?.[appId.value] || null)
const appTitle = computed(() => packageEntry.value?.manifest?.title || resolveAppTitle(appId.value))
const appIcon = computed(() =>
packageEntry.value
? resolveAppIcon(appId.value, packageEntry.value)
@@ -279,6 +281,7 @@ function closeRouteSession() {
// --- Identity & Nostr bridge ---
const iframeRef = computed(() => frameRef.value?.iframeRef ?? null)
const mediaBridge = useAppMediaBridge(appId, appUrl, iframeRef, computed(() => !props.suspended))
const identity = useAppIdentity(appId, iframeRef, showIdentityPicker)
const nostrBridge = useNostrBridge(identity.getStoredIdentity, {
@@ -342,6 +345,7 @@ function syncInlineRect() {
}
const inlineRectStyle = computed<Record<string, string> | undefined>(() => {
if (props.suspended) return { display: 'none' }
if (!inlinePanelMode.value) return undefined
const r = inlineRect.value
// Never paint the inline backdrop over the whole viewport while unmeasured.
@@ -393,6 +397,7 @@ watch(() => packageEntry.value?.['ui-ready'], (ready, previous) => {
// --- Lifecycle handlers ---
function onLoad() {
mediaBridge.connect()
if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
loading.value = false
@@ -404,7 +409,7 @@ function onLoad() {
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 */ }
try { if (!props.suspended) frameRef.value?.iframeRef?.focus() } catch { /* cross-origin is fine */ }
// Check if iframe actually loaded content (same-origin only)
iframeCheckId = setTimeout(() => {
try {
@@ -488,6 +493,7 @@ function closeSession() {
}
function onKeyDown(e: KeyboardEvent) {
if (props.suspended) return
if (e.key === 'Escape') {
if (nostrBridge.showConsent.value) {
nostrBridge.denyConsent()
@@ -508,6 +514,8 @@ function onFullscreenChange() {
}
function onMessage(e: MessageEvent) {
if (e.source !== iframeRef.value?.contentWindow) return
mediaBridge.handle(e)
if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e)
if (e.data?.type === 'archipelago:identity:request') identity.handleIdentityRequest(e.data?.force === true)
if (e.data?.type === 'archipelago:media:playing') screensaverStore.suppress(screensaverReason.value)