feat: support a public browser demo of the kiosk #88
10 changed files with 80 additions and 4 deletions
|
|
@ -73,6 +73,18 @@ VITE_SPIRE_SEED=
|
|||
# Show "Under Service" screen and block all transactions
|
||||
# VITE_MAINTENANCE_MODE=true
|
||||
|
||||
# =============================================================================
|
||||
# Public Web Demo
|
||||
# =============================================================================
|
||||
|
||||
# Set ONLY for the browser demo build (atm.demo.aiolabs.dev). Leave blank on
|
||||
# every real machine. When set it:
|
||||
# - keeps the mouse cursor visible (kiosk builds hide it)
|
||||
# - mints one extra, never-used LNbits wallet named with this exact string,
|
||||
# so the throwaway accounts the demo creates (one per page load, each with
|
||||
# its own ephemeral identity) can be swept by name instead of guessed at.
|
||||
# VITE_DEMO_TAG=bitspire-web-demo
|
||||
|
||||
# =============================================================================
|
||||
# Mock Fallback (Production Safety)
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"dev:vite": "vite",
|
||||
"electron:dev": "tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && electron dist-electron/main.js",
|
||||
"build": "vue-tsc --noEmit && vite build && tsc -p electron/tsconfig.json && tsc -p electron/tsconfig.preload.json && npx esbuild electron/fund-atm.ts --bundle --platform=node --format=cjs --external:better-sqlite3 --outfile=dist-electron/fund-atm.bundle.cjs",
|
||||
"build:web": "vite build",
|
||||
"build:electron": "pnpm build && electron-builder",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ const router = createRouter({
|
|||
],
|
||||
})
|
||||
|
||||
// Kiosk chrome (hidden cursor) is the default — every real machine is a
|
||||
// touchscreen. The public web demo (VITE_DEMO_TAG) runs in a normal browser,
|
||||
// where an invisible pointer just reads as broken.
|
||||
if (!import.meta.env.VITE_DEMO_TAG) {
|
||||
document.documentElement.classList.add('kiosk')
|
||||
}
|
||||
|
||||
// Create Pinia store
|
||||
const pinia = createPinia()
|
||||
|
||||
|
|
|
|||
|
|
@ -505,6 +505,31 @@ export async function initializeLightningServices(options?: {
|
|||
}
|
||||
console.log('[Lightning] LNbits wallet:', lnbitsWalletId)
|
||||
|
||||
// ── Public web demo: stamp the throwaway account so it can be swept ──────
|
||||
// The browser demo (atm.demo.aiolabs.dev) runs with an EPHEMERAL identity —
|
||||
// a fresh keypair per page load — so LNbits mints a new account + a fresh
|
||||
// auto-credited wallet for every visitor. That isolation is the point (a
|
||||
// single baked-in key would be credited exactly once and then drain), but it
|
||||
// leaves throwaway accounts behind, and nothing in an auto-created row says
|
||||
// "demo": pubkey-set/prvkey-NULL also describes a real ATM.
|
||||
//
|
||||
// A nostr pubkey can't carry a marker (you'd have to grind a vanity prefix,
|
||||
// far too slow to do on page load), and the account/wallet the server
|
||||
// auto-creates isn't nameable by the client. So we mint one extra,
|
||||
// never-used wallet whose NAME is the tag: sweeping is then an exact string
|
||||
// match on wallet name rather than a heuristic about what looks disposable.
|
||||
//
|
||||
// Unset on every real machine, so this is inert outside the demo build. The
|
||||
// call is fire-and-forget: losing the marker degrades cleanup, not the demo.
|
||||
const demoTag = (import.meta.env.VITE_DEMO_TAG as string | undefined)?.trim()
|
||||
if (demoTag) {
|
||||
void lnbits
|
||||
.createWallet(demoTag)
|
||||
// Never log the reply — create_wallet returns adminkey/inkey.
|
||||
.then(() => console.log('[Lightning] Demo marker wallet created:', demoTag))
|
||||
.catch((e) => console.warn('[Lightning] Demo marker wallet failed:', e))
|
||||
}
|
||||
|
||||
// #70 P1: pull operator pubkey + fee config from LNbits over the authenticated
|
||||
// transport (spirekeeper#41 `get_machine_config`). A seed-only machine has no
|
||||
// VITE_OPERATOR_PUBKEYS, so without this it can't trust its fee config and sits
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
@import 'tailwindcss';
|
||||
@import 'tw-animate-css';
|
||||
|
||||
/* Hide cursor completely on touchscreen kiosk */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
/* Hide cursor completely on touchscreen kiosk.
|
||||
Scoped to .kiosk (set on <html> by main.ts) so the public web demo, which
|
||||
runs in an ordinary browser with a mouse, keeps a visible pointer. */
|
||||
.kiosk,
|
||||
.kiosk *,
|
||||
.kiosk *::before,
|
||||
.kiosk *::after {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"scripts": {
|
||||
"dev": "turbo dev",
|
||||
"build": "turbo build",
|
||||
"build:web": "turbo build:web",
|
||||
"test": "turbo test",
|
||||
"lint": "turbo lint",
|
||||
"format": "prettier --write .",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import type {
|
|||
CreateInvoiceBody,
|
||||
PayInvoiceBody,
|
||||
WalletInfo,
|
||||
CreatedWallet,
|
||||
MachineConfigResponse,
|
||||
SubscribePaymentsBody,
|
||||
SubscribeAck,
|
||||
|
|
@ -211,6 +212,17 @@ export class LnbitsClient {
|
|||
return data ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an additional wallet on the calling account (`create_wallet`).
|
||||
*
|
||||
* Account-scoped (AUTH_ACCOUNT): the envelope carries no `wallet_id`, which
|
||||
* is what makes the server resolve auth to the Account rather than a Wallet.
|
||||
* NOT wrapped in `idempotent()` — a retry would mint a duplicate wallet.
|
||||
*/
|
||||
async createWallet(name: string): Promise<CreatedWallet> {
|
||||
return this.sendRpc<CreatedWallet>('create_wallet', { body: { name } })
|
||||
}
|
||||
|
||||
/** Pull server-delivered machine config (operator pubkey + fee config) over
|
||||
* the authenticated transport — spirekeeper's `get_machine_config` RPC
|
||||
* (bitspire#70 P1). Lets a seed-only ATM configure itself with no per-machine
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export type {
|
|||
CreateInvoiceBody,
|
||||
PayInvoiceBody,
|
||||
WalletInfo,
|
||||
CreatedWallet,
|
||||
SubscribePaymentsBody,
|
||||
SubscribeAck,
|
||||
SubscribePush,
|
||||
|
|
|
|||
|
|
@ -112,6 +112,15 @@ export interface WalletInfo {
|
|||
balance: number
|
||||
}
|
||||
|
||||
/** Reply shape of the `create_wallet` RPC — unlike WalletInfo it carries the
|
||||
* fresh wallet's keys, so never log it verbatim. */
|
||||
export interface CreatedWallet {
|
||||
id: string
|
||||
name: string
|
||||
adminkey: string
|
||||
inkey: string
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Subscriptions
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
|
||||
},
|
||||
"build:web": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**"],
|
||||
"env": ["VITE_*"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue