This commit is contained in:
Dorian
2026-03-15 00:40:55 +00:00
parent 20883d8266
commit d52ebbb7a6
16 changed files with 1886 additions and 398 deletions

View File

@@ -1,14 +1,12 @@
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import router from '@/router'
/** Hostnames of external sites that block iframes via X-Frame-Options or CSP.
* These always open in a new tab. Other external sites load directly in the iframe. */
const IFRAME_BLOCKED_HOSTS: string[] = [
'484.kitchen',
'botfights.net',
'present.l484.com',
]
/** Legacy: these used to open in new tabs. Now all apps go through AppSession. */
const IFRAME_BLOCKED_HOSTS: string[] = []
/** External site proxy paths — disabled. External URLs load directly in the iframe
* via their standard https:// URL. The /ext/ subpath approach broke SPAs. */
@@ -66,7 +64,7 @@ const PORT_TO_PROXY: Record<string, string> = {
'8176': '/app/fedimint-gateway/',
'3100': '/app/dwn/',
'18081': '/app/nostr-rs-relay/',
'8190': '/app/indeedhub/',
'7777': '/app/indeedhub/',
}
/** Rewrite to same-origin proxy ONLY when needed for HTTPS mixed-content.
@@ -131,7 +129,19 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
const showConsent = ref(false)
let previousActiveElement: HTMLElement | null = null
/** Open app in full-page session view (preferred — no iframe subpath issues) */
function openSession(appId: string) {
router.push({ name: 'app-session', params: { appId } })
}
/** Legacy: open app in iframe overlay (kept for backward compat) */
function open(payload: { url: string; title: string; openInNewTab?: boolean }) {
// Route to full-page session if we can resolve an app ID from the URL
const resolvedId = resolveAppIdFromUrl(payload.url)
if (resolvedId) {
openSession(resolvedId)
return
}
if (payload.openInNewTab || mustOpenInNewTab(payload.url)) {
window.open(payload.url, '_blank', 'noopener,noreferrer')
return
@@ -143,6 +153,31 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
isOpen.value = true
}
/** Resolve an app ID from a URL (port or known external) */
function resolveAppIdFromUrl(urlStr: string): string | null {
try {
const u = new URL(urlStr)
// Check port-based apps
for (const [port, proxyPath] of Object.entries(PORT_TO_PROXY)) {
if (u.port === port) {
return proxyPath.replace('/app/', '').replace(/\/$/, '')
}
}
// Check external URLs
const EXTERNAL_APP_HOSTS: Record<string, string> = {
'botfights.net': 'botfights',
'nwnn.l484.com': 'nwnn',
'484.kitchen': '484-kitchen',
'cta.tx1138.com': 'call-the-operator',
'present.l484.com': 'arch-presentation',
'syntropy.institute': 'syntropy-institute',
'teeminuszero.net': 't-zero',
'nostrudel.ninja': 'nostrudel',
}
return EXTERNAL_APP_HOSTS[u.hostname] || null
} catch { return null }
}
function close() {
const toRestore = previousActiveElement
previousActiveElement = null
@@ -289,6 +324,7 @@ export const useAppLauncherStore = defineStore('appLauncher', () => {
url,
title,
open,
openSession,
close,
showConsent,
consentRequest,