feat(demo): launch IndeeHub in the in-app iframe via the :2101 proxy

- useDemoIntro: replace DEMO_EXTERNAL_URLS (external-tab workaround) with
  DEMO_PROXY_PORTS; demoAppUrl('indeedhub') now resolves to
  <protocol>//<current-hostname>:2101/ at runtime (no hardcoded host/IP);
  isDemoExternal returns false (kept exported so call sites compile
  unchanged); isDemoApp still true for indeedhub so the NEW_TAB bypass
  keeps it in the in-app session
- useAppIdentity: suppress the identity-picker modal under IS_DEMO — the
  embedded IndeeHub is already signed in via the seeded throwaway demo
  account; real-node picker behavior untouched (IS_DEMO compile-time false)

Verified: 195 unit tests green (IS_DEMO=false path); VITE_DEMO=1 build
bundle contains the :2101 launch logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-29 12:28:21 -04:00
parent 69bc3d3f27
commit 66d540f8b5
2 changed files with 29 additions and 14 deletions

View File

@ -24,18 +24,19 @@ export function clearDemoIntroSeen(): void {
// ── Demoable apps ───────────────────────────────────────────────────────────
// Only these apps actually do something in the demo (a mock UI or a real
// external site). Everything else shows "No demo" on a disabled install button
// proxied site). Everything else shows "No demo" on a disabled install button
// and is not launchable.
// IndeeHub's real site sends X-Frame-Options: SAMEORIGIN, and the old
// same-origin nginx sub_filter proxy broke its runtime-built asset URLs —
// so the demo opens the real site directly instead.
const DEMO_EXTERNAL_URLS: Record<string, string> = {
indeedhub: 'https://indee.tx1138.com/',
// IndeeHub's real site sends X-Frame-Options: SAMEORIGIN and its SPA uses
// absolute-root asset paths, so a /app/indeedhub/ path-prefix proxy broke it.
// Instead nginx-demo.conf runs a WHOLE-ORIGIN reverse proxy of
// https://indee.tx1138.com on a dedicated port (framing headers stripped,
// demo sign-in seeded) and the iframe loads http://<demo-host>:<port>/.
const DEMO_PROXY_PORTS: Record<string, number> = {
indeedhub: 2101,
}
// Apps loaded in the in-app iframe via a same-origin path. IndeeHub and Mempool
// are reverse-proxied by nginx (X-Frame-Options/CSP stripped + asset paths
// rewritten) so the frame-busting real sites can be embedded.
// Apps loaded in the in-app iframe via a same-origin path served by the mock
// backend (per-app mock UIs and placeholder dashboards).
const DEMO_MOCK_UI: Record<string, string> = {
mempool: '/app/mempool/',
'mempool-web': '/app/mempool/',
@ -66,18 +67,25 @@ const DEMO_MOCK_UI: Record<string, string> = {
/**
* Whether a demo app opens externally (new tab / in-app browser) because its
* real site blocks iframing (X-Frame-Options).
* real site blocks iframing. Nothing does anymore frame-busting sites are
* whole-origin proxied (DEMO_PROXY_PORTS) instead. Kept exported so call
* sites in appLauncher.ts / AppSession.vue compile unchanged.
*/
export function isDemoExternal(appId: string): boolean {
return appId in DEMO_EXTERNAL_URLS
export function isDemoExternal(_appId: string): boolean {
return false
}
/** Can this app be launched/installed in the demo? */
export function isDemoApp(appId: string): boolean {
return appId in DEMO_EXTERNAL_URLS || appId in DEMO_MOCK_UI
return appId in DEMO_PROXY_PORTS || appId in DEMO_MOCK_UI
}
/** Resolve the demo launch URL for an app, or null if it isn't demoable. */
export function demoAppUrl(appId: string): string | null {
return DEMO_EXTERNAL_URLS[appId] ?? DEMO_MOCK_UI[appId] ?? null
const proxyPort = DEMO_PROXY_PORTS[appId]
if (proxyPort !== undefined && typeof window !== 'undefined') {
// Same host the demo itself is served from — never a hardcoded host/IP.
return `${window.location.protocol}//${window.location.hostname}:${proxyPort}/`
}
return DEMO_MOCK_UI[appId] ?? null
}

View File

@ -2,6 +2,7 @@
import { type Ref } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import { IS_DEMO } from '@/composables/useDemoIntro'
const IDENTITY_KEY = 'archipelago_app_identity_'
@ -54,6 +55,11 @@ export function useAppIdentity(
/** Called on iframe load to inject identity if the app supports it */
function onIframeLoadIdentity() {
// Public demo: never interrupt the visitor with the identity picker —
// the embedded IndeeHub is already signed in via the seeded throwaway
// demo account (see docker/indee-demo-signin.js). Real-node behavior is
// untouched (IS_DEMO is compile-time false there).
if (IS_DEMO) return
if (isIdentityAwareApp(appId.value)) {
const stored = getStoredIdentity()
if (stored) sendIdentity(stored)
@ -63,6 +69,7 @@ export function useAppIdentity(
/** Handle identity request messages from iframe */
function handleIdentityRequest() {
if (IS_DEMO) return
const stored = getStoredIdentity()
if (stored) sendIdentity(stored)
else showIdentityPicker.value = true