feat(demo): iframe asset-rewrite proxy, AIUI mockArchy, QR 2s, dummy mints
- IndeeHub + Mempool: nginx reverse-proxy + strip X-Frame-Options/CSP + sub_filter rewrite of absolute asset paths so the frame-busting SPAs load in the iframe (mempool.space remains best-effort — third-party CSP/ws may still limit it). - AIUI iframe gets ?mockArchy in demo → its built-in mock node data loads. - Pay-with-mobile QR: invoice settles after ~2s (backend gate keyed by payment_hash) and the poll tightened to 1s, so the QR is visible before auto-pay. - Wallet settings: dummy Cashu mints (4) + Fedimint federations (2, 222,500 sats), interactive per session (streaming.list/configure-mints, wallet.fedimint-list/ join/balance). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1b7335f4ac
commit
445f08a5c1
@ -94,6 +94,49 @@ http {
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# IndeeHub: reverse-proxy the real site same-origin, strip framing headers,
|
||||
# and rewrite its absolute asset paths (/assets, /, src, href) to the
|
||||
# /app/indeedhub/ prefix so the SPA loads inside the iframe.
|
||||
location /app/indeedhub/ {
|
||||
proxy_pass https://indee.tx1138.com/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host indee.tx1138.com;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_ssl_server_name on;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Content-Security-Policy-Report-Only;
|
||||
sub_filter_types text/html text/css application/javascript application/json;
|
||||
sub_filter_once off;
|
||||
sub_filter 'href="/' 'href="/app/indeedhub/';
|
||||
sub_filter 'src="/' 'src="/app/indeedhub/';
|
||||
sub_filter "href='/" "href='/app/indeedhub/";
|
||||
sub_filter "src='/" "src='/app/indeedhub/";
|
||||
sub_filter 'from"/' 'from"/app/indeedhub/';
|
||||
sub_filter 'url(/' 'url(/app/indeedhub/';
|
||||
}
|
||||
|
||||
# Mempool: same approach. NOTE mempool.space is a strict third-party app —
|
||||
# its data/websocket calls may still be blocked; iframe is best-effort.
|
||||
location /app/mempool/ {
|
||||
proxy_pass https://mempool.space/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host mempool.space;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_ssl_server_name on;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Content-Security-Policy-Report-Only;
|
||||
sub_filter_types text/html text/css application/javascript application/json;
|
||||
sub_filter_once off;
|
||||
sub_filter 'href="/' 'href="/app/mempool/';
|
||||
sub_filter 'src="/' 'src="/app/mempool/';
|
||||
sub_filter "href='/" "href='/app/mempool/";
|
||||
sub_filter "src='/" "src='/app/mempool/";
|
||||
sub_filter 'from"/' 'from"/app/mempool/';
|
||||
sub_filter 'url(/' 'url(/app/mempool/';
|
||||
}
|
||||
|
||||
# Proxy every other app UI (/app/<id>/) to the mock backend, which serves
|
||||
# the per-app mock UIs (bitcoin-ui, electrumx, lnd, fedimint) and the
|
||||
# generic "Not available in the demo" notice for the rest.
|
||||
|
||||
@ -149,6 +149,10 @@ const SEED_WALLET = {
|
||||
}
|
||||
function randomHex(bytes) { return Array.from({length: bytes}, () => Math.floor(Math.random()*256).toString(16).padStart(2,'0')).join('') }
|
||||
|
||||
// Tracks when a content invoice was requested, so the demo can leave the QR on
|
||||
// screen for a couple of seconds before reporting it paid.
|
||||
const invoiceRequestedAt = new Map()
|
||||
|
||||
const SEED_BTCRELAY = {
|
||||
settings: {
|
||||
enabled_for_peers: true,
|
||||
@ -216,7 +220,20 @@ function seedUserState() {
|
||||
}
|
||||
|
||||
function seedMockState() {
|
||||
return { analyticsEnabled: false, nodeVisibility: 'discoverable' }
|
||||
return {
|
||||
analyticsEnabled: false,
|
||||
nodeVisibility: 'discoverable',
|
||||
cashuMints: [
|
||||
'https://mint.minibits.cash/Bitcoin',
|
||||
'https://stablenut.umint.cash',
|
||||
'https://mint.coinos.io',
|
||||
'https://8333.space:3338',
|
||||
],
|
||||
federations: [
|
||||
{ federation_id: 'fed1' + randomHex(28), name: 'Archipelago Federation', balance_sats: 180_000 },
|
||||
{ federation_id: 'fed1' + randomHex(28), name: 'Bitcoin Park Mint', balance_sats: 42_500 },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Auth] Dev mode: ${DEV_MODE}${DEMO ? ' (DEMO multi-session)' : ''}`)
|
||||
@ -1981,17 +1998,42 @@ app.post('/rpc/v1', (req, res) => {
|
||||
}
|
||||
case 'content.request-invoice': {
|
||||
const price = params?.price_sats || 500
|
||||
const payment_hash = randomHex(32)
|
||||
invoiceRequestedAt.set(payment_hash, Date.now())
|
||||
return res.json({
|
||||
result: {
|
||||
bolt11: 'lntb' + price + '1p' + randomHex(50),
|
||||
payment_hash: randomHex(32),
|
||||
payment_hash,
|
||||
price_sats: price,
|
||||
},
|
||||
})
|
||||
}
|
||||
case 'content.invoice-status': {
|
||||
// Settle after the first poll so the QR flow shows a realistic wait.
|
||||
return res.json({ result: { paid: true } })
|
||||
// Demo: leave the QR on screen for ~2s before the invoice "settles".
|
||||
const at = invoiceRequestedAt.get(params?.payment_hash)
|
||||
const paid = !at || (Date.now() - at) >= 2000
|
||||
return res.json({ result: { paid } })
|
||||
}
|
||||
|
||||
// ── Wallet settings: Cashu mints + Fedimint federations (interactive) ──
|
||||
case 'streaming.list-mints': {
|
||||
return res.json({ result: { mints: mockState.cashuMints } })
|
||||
}
|
||||
case 'streaming.configure-mints': {
|
||||
if (Array.isArray(params?.mints)) mockState.cashuMints = params.mints
|
||||
return res.json({ result: { success: true, mints: mockState.cashuMints } })
|
||||
}
|
||||
case 'wallet.fedimint-list': {
|
||||
return res.json({ result: { federations: mockState.federations } })
|
||||
}
|
||||
case 'wallet.fedimint-join': {
|
||||
const fed = { federation_id: 'fed1' + randomHex(28), name: 'Joined Federation', balance_sats: 0 }
|
||||
mockState.federations.push(fed)
|
||||
return res.json({ result: { success: true, ...fed } })
|
||||
}
|
||||
case 'wallet.fedimint-balance': {
|
||||
const total = (mockState.federations || []).reduce((s, f) => s + (f.balance_sats || 0), 0)
|
||||
return res.json({ result: { balance_sats: total } })
|
||||
}
|
||||
case 'content.request-onchain': {
|
||||
const price = params?.price_sats || 500
|
||||
|
||||
@ -52,15 +52,15 @@ export function clearDemoIntroSeen(): void {
|
||||
// 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
|
||||
// and is not launchable.
|
||||
const DEMO_EXTERNAL_URLS: Record<string, string> = {
|
||||
// Real, public sites loaded directly in the in-app iframe.
|
||||
indeedhub: 'https://indee.tx1138.com/',
|
||||
mempool: 'https://mempool.space/testnet',
|
||||
'mempool-web': 'https://mempool.space/testnet',
|
||||
}
|
||||
const DEMO_EXTERNAL_URLS: Record<string, string> = {}
|
||||
|
||||
// Apps with a same-origin mock UI served by the demo backend.
|
||||
// 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.
|
||||
const DEMO_MOCK_UI: Record<string, string> = {
|
||||
indeedhub: '/app/indeedhub/',
|
||||
mempool: '/app/mempool/',
|
||||
'mempool-web': '/app/mempool/',
|
||||
'bitcoin-knots': '/app/bitcoin-knots/',
|
||||
'bitcoin-core': '/app/bitcoin-core/',
|
||||
bitcoin: '/app/bitcoin-core/',
|
||||
|
||||
@ -62,6 +62,7 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { ContextBroker } from '@/services/contextBroker'
|
||||
import { IS_DEMO } from '@/composables/useDemoIntro'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -71,9 +72,12 @@ const aiuiConnected = ref(false)
|
||||
let broker: ContextBroker | null = null
|
||||
|
||||
const aiuiUrl = computed(() => {
|
||||
// Demo: ?mockArchy makes AIUI use its built-in mock node data (apps, system,
|
||||
// network, wallet, bitcoin, files) and &seed pre-loads the example chats.
|
||||
const demo = IS_DEMO ? '&mockArchy=1&seed=1' : ''
|
||||
const envUrl = import.meta.env.VITE_AIUI_URL
|
||||
if (envUrl) return `${envUrl}?embedded=true&hideClose=true`
|
||||
if (import.meta.env.PROD) return '/aiui/?embedded=true&hideClose=true'
|
||||
if (envUrl) return `${envUrl}?embedded=true&hideClose=true${demo}`
|
||||
if (import.meta.env.PROD || IS_DEMO) return `/aiui/?embedded=true&hideClose=true${demo}`
|
||||
return ''
|
||||
})
|
||||
|
||||
|
||||
@ -1304,7 +1304,7 @@ async function payWithLightning() {
|
||||
|
||||
function scheduleInvoicePoll() {
|
||||
if (invoicePollTimer) clearTimeout(invoicePollTimer)
|
||||
invoicePollTimer = setTimeout(pollInvoice, 3000)
|
||||
invoicePollTimer = setTimeout(pollInvoice, 1000)
|
||||
}
|
||||
|
||||
async function pollInvoice() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user