@bwalletx/connect 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +4 -0
- package/dist/bwalletx-connect.global.js +8 -8
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{chunk-IUDD7YTQ.js → chunk-EXRQJTXF.js} +91 -20
- package/dist/chunk-EXRQJTXF.js.map +1 -0
- package/dist/{chunk-PI42H3XY.js → chunk-RIOHG5JH.js} +4 -4
- package/dist/chunk-RIOHG5JH.js.map +1 -0
- package/dist/element.cjs +86 -18
- package/dist/element.cjs.map +1 -1
- package/dist/element.js +1 -1
- package/dist/index.cjs +97 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -7
- package/dist/index.d.ts +31 -7
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +90 -22
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/shell-react.cjs.map +1 -1
- package/dist/shell-react.js.map +1 -1
- package/dist/{wallets-VOKIKBbO.d.cts → wallets-CbzMtxrf.d.cts} +1 -1
- package/dist/{wallets-VOKIKBbO.d.ts → wallets-CbzMtxrf.d.ts} +1 -1
- package/package.json +9 -10
- package/dist/chunk-IUDD7YTQ.js.map +0 -1
- package/dist/chunk-PI42H3XY.js.map +0 -1
package/dist/react.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode, ReactElement, CSSProperties } from 'react';
|
|
2
2
|
import { WalletInterface } from '@bsv/sdk';
|
|
3
|
-
import { C as ConnectedWallet, W as WalletOption, P as PayRequest, a as PayResult, B as BuyTokenRequest, b as PayOptions } from './wallets-
|
|
4
|
-
export { H as HandCashAdapterOptions, c as PaymentError, d as brc100Option, e as discoverWallets, h as handcashAdapter, p as pairingOption } from './wallets-
|
|
3
|
+
import { C as ConnectedWallet, W as WalletOption, P as PayRequest, a as PayResult, B as BuyTokenRequest, b as PayOptions } from './wallets-CbzMtxrf.cjs';
|
|
4
|
+
export { H as HandCashAdapterOptions, c as PaymentError, d as brc100Option, e as discoverWallets, h as handcashAdapter, p as pairingOption } from './wallets-CbzMtxrf.cjs';
|
|
5
5
|
import { SigninSize, SigninTheme, SigninLabel, SigninBrand } from './element.cjs';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode, ReactElement, CSSProperties } from 'react';
|
|
2
2
|
import { WalletInterface } from '@bsv/sdk';
|
|
3
|
-
import { C as ConnectedWallet, W as WalletOption, P as PayRequest, a as PayResult, B as BuyTokenRequest, b as PayOptions } from './wallets-
|
|
4
|
-
export { H as HandCashAdapterOptions, c as PaymentError, d as brc100Option, e as discoverWallets, h as handcashAdapter, p as pairingOption } from './wallets-
|
|
3
|
+
import { C as ConnectedWallet, W as WalletOption, P as PayRequest, a as PayResult, B as BuyTokenRequest, b as PayOptions } from './wallets-CbzMtxrf.js';
|
|
4
|
+
export { H as HandCashAdapterOptions, c as PaymentError, d as brc100Option, e as discoverWallets, h as handcashAdapter, p as pairingOption } from './wallets-CbzMtxrf.js';
|
|
5
5
|
import { SigninSize, SigninTheme, SigninLabel, SigninBrand } from './element.js';
|
|
6
6
|
|
|
7
7
|
/**
|
package/dist/react.js
CHANGED
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
pay,
|
|
11
11
|
pickWallet,
|
|
12
12
|
tip
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RIOHG5JH.js";
|
|
14
14
|
import {
|
|
15
15
|
atStage,
|
|
16
16
|
clearPairing,
|
|
17
17
|
defineBwalletxSignin,
|
|
18
18
|
signInWithBwalletX
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EXRQJTXF.js";
|
|
20
20
|
import "./chunk-MWD4BCO5.js";
|
|
21
21
|
|
|
22
22
|
// src/react.ts
|
package/dist/shell-react.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/react.ts","../src/shell/manifest.ts","../src/shell/wallet.ts","../src/shell/elements.ts"],"sourcesContent":["/**\n * React wrappers for the bApp shell. The elements do the work; these set the manifest property\n * and wire the events.\n *\n * import { BappBar, BappTopbar, BappDrawer } from '@bwalletx/connect/shell/react';\n * <BappTopbar manifest={m} drawer=\"sections\" />\n * <BappDrawer id=\"sections\" manifest={m} />\n * <BappBar manifest={m} active=\"feed\" onNavigate={(d, e) => { e.preventDefault(); router.push(d.path) }} />\n */\nimport { createElement, useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\nimport { defineBappShell } from './elements.js';\nimport type { BappManifest, SlotId } from './manifest.js';\n\nexport interface NavigateDetail { slot?: SlotId; path: string | null; source: 'web' | 'wallet' }\ntype Handler<D> = (detail: D, event: CustomEvent<D>) => void;\n\ninterface Common {\n manifest: BappManifest | null;\n /** \"auto\" (default) detects bWallet; true/false forces. */\n inWallet?: boolean | 'auto';\n /** true = never call location.assign; route in onNavigate. */\n manualNavigation?: boolean;\n onNavigate?: Handler<NavigateDetail>;\n id?: string;\n className?: string;\n style?: CSSProperties;\n /** Slotted content, e.g. <button slot=\"tools\"> or <span slot=\"strip-left\">. */\n children?: ReactNode;\n}\n\nfunction useShell(tag: string, props: Common, attrs: Record<string, string | undefined>, events: Record<string, Handler<any> | undefined>) {\n const ref = useRef<HTMLElement & { manifest: BappManifest | null }>(null);\n useEffect(() => { defineBappShell(); }, []);\n useEffect(() => { if (ref.current) ref.current.manifest = props.manifest; }, [props.manifest]);\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const all = { 'bapp-navigate': props.onNavigate, ...events };\n const subs = Object.entries(all).filter(([, h]) => h).map(([type, h]) => {\n const fn = (e: Event) => h!((e as CustomEvent).detail, e as CustomEvent);\n el.addEventListener(type, fn);\n return () => el.removeEventListener(type, fn);\n });\n return () => subs.forEach((u) => u());\n });\n const a: Record<string, unknown> = { ref, id: props.id, class: props.className, style: props.style };\n if (props.inWallet !== undefined && props.inWallet !== 'auto') a['in-wallet'] = String(props.inWallet);\n if (props.manualNavigation) a.navigate = 'manual';\n for (const [k, v] of Object.entries(attrs)) if (v !== undefined) a[k] = v;\n return createElement(tag, a, props.children);\n}\n\nexport interface BappBarProps extends Common {\n active?: SlotId | null;\n onActive?: Handler<{ slot: SlotId }>;\n onBPress?: Handler<{ path: string | null }>;\n onBHold?: Handler<{ phase: 'start' | 'end' }>;\n}\nexport function BappBar(p: BappBarProps) {\n return useShell('bwalletx-bar', p, { active: p.active ?? undefined }, { 'bapp-active': p.onActive, 'bapp-b-press': p.onBPress, 'bapp-b-hold': p.onBHold });\n}\n\nexport interface BappTopbarProps extends Common {\n title?: string;\n subtitle?: string;\n /** id of the <bwalletx-drawer> the app icon opens. */\n drawer?: string;\n /** Show ☰ on the web too (it always shows in the wallet). */\n menu?: boolean;\n strip?: boolean;\n onMenu?: Handler<object>;\n onClose?: Handler<object>;\n onDrawerToggle?: Handler<object>;\n}\nexport function BappTopbar(p: BappTopbarProps) {\n return useShell('bwalletx-topbar', p, {\n title: p.title, subtitle: p.subtitle, drawer: p.drawer, menu: p.menu ? 'true' : undefined, strip: p.strip === false ? 'off' : undefined,\n }, { 'bapp-menu': p.onMenu, 'bapp-close': p.onClose, 'bapp-drawer-toggle': p.onDrawerToggle });\n}\n\nexport interface BappDrawerProps extends Common {\n open?: boolean;\n /** Path to highlight; default location.pathname. */\n current?: string;\n onClose?: Handler<object>;\n}\nexport function BappDrawer(p: BappDrawerProps) {\n return useShell('bwalletx-drawer', p, { open: p.open ? '' : undefined, current: p.current }, { 'bapp-close': p.onClose });\n}\n\nexport type { BappManifest, SlotId } from './manifest.js';\n","/**\n * bApp manifest: /.well-known/bapp.json. One file declares the app's five scoped slots, its drawer\n * (Home + sections) and its like-to-fund config. The web bar draws from it; inside bWalletX the\n * wallet's native dock reads the same file. Every path is same-origin: no URLs, no schemes.\n */\n\nexport const SLOT_IDS = ['wallet', 'exchange', 'b', 'feed', 'chat'] as const;\nexport type SlotId = (typeof SLOT_IDS)[number];\n\nexport interface BappSlot {\n /** Same-origin path, e.g. \"/feed\". Required when enabled, except for \"b\" (the agent). */\n path?: string;\n /** Optional rename (\"Market\"); the icon and position never change. */\n label?: string;\n /** false = shown greyed out and not clickable. Slots are never hidden. */\n enabled: boolean;\n}\n\nexport interface BappDrawerItem {\n label: string;\n path: string;\n /** Optional same-origin icon path. */\n icon?: string;\n}\n\nexport interface BappLikeConfig {\n /** Which fields of a feed item hold the id, token (BSV-21 id / $TICKER), payee, title and media. */\n fields: { id: string; token: string; payee?: string; title?: string; media?: string };\n /** Price of one Like in US cents. Default 1 (one PNEE). */\n amountCents: number;\n /** Pay in PNEE (the wallet's USD-cent token) when held, else BSV. Default \"PNEE\". */\n currency: 'PNEE' | 'BSV';\n /** Daily cap in US cents. Default 100 ($1), never more. */\n dailyCapCents: number;\n}\n\nexport interface BappTheme {\n /** Active/accent colour, #RRGGBB. Default #FFD24D. */\n accent?: string;\n}\n\nexport interface BappManifest {\n version: 1;\n name: string;\n /** Same-origin path to a square icon. */\n icon: string;\n /** The app's Home (first drawer item). Default \"/\". */\n home: string;\n theme?: BappTheme;\n slots: Record<SlotId, BappSlot>;\n drawer: BappDrawerItem[];\n like?: BappLikeConfig;\n}\n\nexport type ValidationResult =\n | { ok: true; manifest: BappManifest; errors: [] }\n | { ok: false; errors: string[]; manifest?: undefined };\n\nexport const WELL_KNOWN_PATH = '/.well-known/bapp.json';\nexport const LIKE_DEFAULT_CENTS = 1;\nexport const LIKE_MAX_DAILY_CENTS = 100;\n\n/**\n * A same-origin path: starts with one \"/\", no \"//\" (protocol-relative), no backslashes, no control\n * characters, no scheme. Query and hash are allowed.\n */\nexport function isSameOriginPath(p: unknown): p is string {\n if (typeof p !== 'string' || p.length === 0 || p.length > 512) return false;\n if (p[0] !== '/' || p[1] === '/' || p[1] === '\\\\') return false;\n if (/[\\\\\\u0000-\\u001f\\u007f\\s]/.test(p)) return false;\n // Resolving against a dummy origin must not change origin.\n try {\n return new URL(p, 'https://bapp.invalid').origin === 'https://bapp.invalid';\n } catch {\n return false;\n }\n}\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);\nconst str = (v: unknown, max = 64): v is string => typeof v === 'string' && v.trim().length > 0 && v.length <= max;\n\n/** Validate and normalise a parsed bapp.json. Never throws. */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = [];\n if (!isObj(input)) return { ok: false, errors: ['manifest must be a JSON object'] };\n const m = input;\n const known = new Set(['$schema', 'version', 'name', 'icon', 'home', 'theme', 'slots', 'drawer', 'like']);\n for (const k of Object.keys(m)) if (!known.has(k)) errors.push(`unknown field \"${k}\"`);\n\n if (m.version !== 1) errors.push('version must be 1');\n if (!str(m.name, 40)) errors.push('name must be a non-empty string (max 40)');\n if (!isSameOriginPath(m.icon)) errors.push('icon must be a same-origin path starting with \"/\"');\n const home = m.home === undefined ? '/' : m.home;\n if (!isSameOriginPath(home)) errors.push('home must be a same-origin path starting with \"/\"');\n\n let theme: BappTheme | undefined;\n if (m.theme !== undefined) {\n if (!isObj(m.theme)) errors.push('theme must be an object');\n else {\n for (const k of Object.keys(m.theme)) if (k !== 'accent') errors.push(`unknown field \"theme.${k}\"`);\n if (m.theme.accent !== undefined && !(typeof m.theme.accent === 'string' && /^#[0-9a-fA-F]{6}$/.test(m.theme.accent)))\n errors.push('theme.accent must be #RRGGBB');\n theme = m.theme.accent ? { accent: m.theme.accent as string } : {};\n }\n }\n\n const slots = {} as Record<SlotId, BappSlot>;\n if (!isObj(m.slots)) errors.push('slots must be an object with wallet, exchange, b, feed, chat');\n else {\n for (const k of Object.keys(m.slots)) if (!(SLOT_IDS as readonly string[]).includes(k)) errors.push(`unknown slot \"${k}\"`);\n for (const id of SLOT_IDS) {\n const s = m.slots[id];\n if (!isObj(s)) { errors.push(`slots.${id} is required (use \"enabled\": false for an unused slot)`); continue; }\n for (const k of Object.keys(s)) if (!['path', 'label', 'enabled'].includes(k)) errors.push(`unknown field \"slots.${id}.${k}\"`);\n if (typeof s.enabled !== 'boolean') errors.push(`slots.${id}.enabled must be true or false`);\n if (s.path !== undefined && !isSameOriginPath(s.path)) errors.push(`slots.${id}.path must be a same-origin path starting with \"/\"`);\n if (s.enabled === true && s.path === undefined && id !== 'b') errors.push(`slots.${id}.path is required when enabled`);\n if (s.label !== undefined && !str(s.label, 12)) errors.push(`slots.${id}.label must be a short string (max 12)`);\n slots[id] = { enabled: s.enabled === true, ...(s.path !== undefined ? { path: s.path as string } : {}), ...(s.label !== undefined ? { label: s.label as string } : {}) };\n }\n }\n\n const drawer: BappDrawerItem[] = [];\n if (m.drawer !== undefined) {\n if (!Array.isArray(m.drawer)) errors.push('drawer must be an array');\n else if (m.drawer.length > 24) errors.push('drawer has more than 24 items');\n else m.drawer.forEach((d, i) => {\n if (!isObj(d)) { errors.push(`drawer[${i}] must be an object`); return; }\n for (const k of Object.keys(d)) if (!['label', 'path', 'icon'].includes(k)) errors.push(`unknown field \"drawer[${i}].${k}\"`);\n if (!str(d.label, 40)) errors.push(`drawer[${i}].label must be a non-empty string (max 40)`);\n if (!isSameOriginPath(d.path)) errors.push(`drawer[${i}].path must be a same-origin path starting with \"/\"`);\n if (d.icon !== undefined && !isSameOriginPath(d.icon)) errors.push(`drawer[${i}].icon must be a same-origin path starting with \"/\"`);\n drawer.push({ label: d.label as string, path: d.path as string, ...(d.icon ? { icon: d.icon as string } : {}) });\n });\n }\n\n let like: BappLikeConfig | undefined;\n if (m.like !== undefined) {\n const l = m.like;\n if (!isObj(l)) errors.push('like must be an object');\n else {\n for (const k of Object.keys(l)) if (!['fields', 'amountCents', 'currency', 'dailyCapCents'].includes(k)) errors.push(`unknown field \"like.${k}\"`);\n const f = l.fields;\n if (!isObj(f) || !str(f.id) || !str(f.token)) errors.push('like.fields needs \"id\" and \"token\" field names');\n else for (const k of ['payee', 'title', 'media']) if (f[k] !== undefined && !str(f[k])) errors.push(`like.fields.${k} must be a field name`);\n const amount = l.amountCents ?? LIKE_DEFAULT_CENTS;\n const cap = l.dailyCapCents ?? LIKE_MAX_DAILY_CENTS;\n const currency = l.currency ?? 'PNEE';\n if (!Number.isInteger(amount) || (amount as number) < 1) errors.push('like.amountCents must be a whole number of cents, at least 1');\n if (!Number.isInteger(cap) || (cap as number) < 1 || (cap as number) > LIKE_MAX_DAILY_CENTS) errors.push('like.dailyCapCents must be 1..100 ($1 max)');\n else if (Number.isInteger(amount) && (amount as number) > (cap as number)) errors.push('like.amountCents cannot exceed like.dailyCapCents');\n if (currency !== 'PNEE' && currency !== 'BSV') errors.push('like.currency must be \"PNEE\" or \"BSV\"');\n if (isObj(f)) {\n const fields: BappLikeConfig['fields'] = { id: f.id as string, token: f.token as string };\n for (const k of ['payee', 'title', 'media'] as const) if (typeof f[k] === 'string') fields[k] = f[k] as string;\n like = { fields, amountCents: amount as number, currency: currency as 'PNEE' | 'BSV', dailyCapCents: cap as number };\n }\n }\n }\n\n if (errors.length) return { ok: false, errors };\n return {\n ok: true,\n errors: [],\n manifest: {\n version: 1, name: (m.name as string).trim(), icon: m.icon as string, home: home as string,\n ...(theme ? { theme } : {}), slots, drawer, ...(like ? { like } : {}),\n },\n };\n}\n\n/** Same as validateManifest but throws with every error listed. */\nexport function parseManifest(input: unknown): BappManifest {\n const r = validateManifest(input);\n if (!r.ok) throw new Error(`Invalid bapp.json:\\n- ${r.errors.join('\\n- ')}`);\n return r.manifest;\n}\n\n/** Fetch and validate /.well-known/bapp.json (same origin). */\nexport async function loadManifest(path: string = WELL_KNOWN_PATH, fetchImpl: typeof fetch = fetch): Promise<BappManifest> {\n if (!isSameOriginPath(path)) throw new Error('loadManifest: path must be same-origin');\n const res = await fetchImpl(path, { credentials: 'same-origin', headers: { accept: 'application/json' } });\n if (!res.ok) throw new Error(`loadManifest: ${path} returned ${res.status}`);\n return parseManifest(await res.json());\n}\n\n/** The path a slot points at, or null when disabled / no path. */\nexport function slotPath(m: BappManifest, slot: SlotId): string | null {\n const s = m.slots[slot];\n return s && s.enabled && s.path ? s.path : null;\n}\n\nexport const isSlotId = (v: unknown): v is SlotId => typeof v === 'string' && (SLOT_IDS as readonly string[]).includes(v);\n","/**\n * In-wallet mode. Inside bWalletX the wallet draws the dock natively; the app hides its web bar,\n * follows `bapp:navigate {slot}` from the wallet, and reports `bapp:active {slot}` back.\n * Protocol: docs/SHELL-PROTOCOL.md.\n */\nimport { isSlotId, slotPath, type BappManifest, type SlotId } from './manifest.js';\n\n/** Wallet web origins allowed to drive a framed bApp. Extension origins are passed in by the app. */\nexport const DEFAULT_WALLET_ORIGINS: readonly string[] = ['https://web.bwalletx.com', 'https://bwalletx.com'];\n\nexport type WalletVia = 'ua' | 'frame' | null;\nexport interface WalletDetection {\n inWallet: boolean;\n via: WalletVia;\n /** Bottom inset (px) the wallet reserves, from `bWalletInset/<n>`. */\n inset?: number;\n}\n\nconst UA_RE = /\\b(?:bWallet|YoursWalletMobile)\\//;\nconst INSET_RE = /\\bbWalletInset\\/(\\d{1,4})\\b/;\n\n/** Synchronous detection from the user agent (native app webviews). */\nexport function detectWalletUA(ua: string = typeof navigator !== 'undefined' ? navigator.userAgent : ''): WalletDetection {\n const inset = INSET_RE.exec(ua);\n if (inset || UA_RE.test(ua)) return { inWallet: true, via: 'ua', ...(inset ? { inset: Number(inset[1]) } : {}) };\n return { inWallet: false, via: null };\n}\n\nlet state: WalletDetection = { inWallet: false, via: null };\n/** Current detection (UA, or a completed frame handshake). */\nexport function walletState(): WalletDetection { return state; }\n\nfunction setState(next: WalletDetection, win: Window): void {\n state = next;\n if (next.inWallet) win.document?.documentElement?.setAttribute('data-bwx-in-wallet', next.via ?? '');\n win.dispatchEvent(new CustomEvent('bapp-wallet', { detail: next }));\n}\n\nexport interface ShellBridgeOptions {\n manifest: BappManifest;\n /** Allowed wallet origins for framed mode (exact match). Default DEFAULT_WALLET_ORIGINS. */\n walletOrigins?: readonly string[];\n /** Default: window. */\n win?: Window;\n /** Default: navigator.userAgent. */\n userAgent?: string;\n}\n\ntype Msg = { type?: unknown; v?: unknown; slot?: unknown };\n\n/**\n * Listens for the wallet and keeps it in sync. Navigation is dispatched as a cancelable\n * `bapp-navigate` event on window ({slot, path, source:'wallet'}); if nobody calls\n * preventDefault() the page goes to the slot's path with location.assign.\n */\nexport class ShellBridge {\n readonly manifest: BappManifest;\n private readonly origins: readonly string[];\n private readonly win: Window;\n private target: { win: Window; origin: string } | null = null;\n private active: SlotId | null = null;\n private readonly onMessage = (e: MessageEvent) => this.handle(e);\n private readonly onActive = (e: Event) => { const s = (e as CustomEvent).detail?.slot; if (isSlotId(s)) this.setActive(s); };\n private readonly onMenu = () => this.post({ type: 'bapp:menu' });\n private readonly onClose = () => this.post({ type: 'bapp:close' });\n\n constructor(opts: ShellBridgeOptions) {\n this.manifest = opts.manifest;\n this.origins = opts.walletOrigins ?? DEFAULT_WALLET_ORIGINS;\n this.win = opts.win ?? window;\n const ua = detectWalletUA(opts.userAgent);\n if (ua.inWallet) {\n // Native webview: the wallet injects messages into the page itself.\n this.target = { win: this.win, origin: this.win.location.origin };\n setState(ua, this.win);\n }\n }\n\n start(): this {\n this.win.addEventListener('message', this.onMessage);\n this.win.addEventListener('bapp-active', this.onActive);\n this.win.addEventListener('bapp-menu', this.onMenu);\n this.win.addEventListener('bapp-close', this.onClose);\n return this;\n }\n\n stop(): void {\n this.win.removeEventListener('message', this.onMessage);\n this.win.removeEventListener('bapp-active', this.onActive);\n this.win.removeEventListener('bapp-menu', this.onMenu);\n this.win.removeEventListener('bapp-close', this.onClose);\n }\n\n get inWallet(): boolean { return this.target !== null; }\n\n /** Tell the wallet which slot is showing (lights up the native dock). */\n setActive(slot: SlotId): void {\n this.active = slot;\n this.post({ type: 'bapp:active', v: 1, slot });\n }\n\n /** Is this message from a trusted wallet? */\n trusted(e: MessageEvent): boolean {\n const parent = this.win.parent;\n // Framed: only our direct parent, only an allowlisted origin.\n if (parent && parent !== this.win && e.source === parent) return this.origins.includes(e.origin);\n // Native webview (UA detected): injected into this page, so same window and same origin.\n if (this.target?.win === this.win && (e.source === this.win || e.source === null)) return e.origin === this.win.location.origin;\n return false;\n }\n\n private handle(e: MessageEvent): void {\n const d = e.data as Msg;\n if (!d || typeof d !== 'object' || typeof d.type !== 'string' || !d.type.startsWith('bapp:')) return;\n if (!this.trusted(e)) return;\n if (d.type === 'bapp:hello') {\n if (!this.target) {\n this.target = { win: e.source as Window, origin: e.origin };\n setState({ inWallet: true, via: 'frame' }, this.win);\n }\n this.post({ type: 'bapp:ready', v: 1, manifest: '/.well-known/bapp.json', ...(this.active ? { slot: this.active } : {}) });\n return;\n }\n if (d.type === 'bapp:navigate') {\n if (!isSlotId(d.slot)) return; // never a URL, only a slot id\n const path = slotPath(this.manifest, d.slot);\n if (d.slot !== 'b' && !path) return; // disabled or not in the manifest\n if (d.slot === 'b' && !this.manifest.slots.b.enabled) return;\n const ev = new CustomEvent('bapp-navigate', { cancelable: true, detail: { slot: d.slot, path, source: 'wallet' } });\n this.win.dispatchEvent(ev);\n if (!ev.defaultPrevented && path) this.win.location.assign(path);\n this.setActive(d.slot);\n }\n }\n\n private post(msg: Record<string, unknown>): void {\n if (!this.target) return;\n const { win, origin } = this.target;\n // Native webviews expose ReactNativeWebView; fall back to postMessage to self.\n const rn = (this.win as unknown as { ReactNativeWebView?: { postMessage(s: string): void } }).ReactNativeWebView;\n if (win === this.win && rn) rn.postMessage(JSON.stringify(msg));\n else win.postMessage(msg, origin);\n }\n}\n\n/** Convenience: create and start a bridge. */\nexport function startShellBridge(opts: ShellBridgeOptions): ShellBridge {\n return new ShellBridge(opts).start();\n}\n\n/** Test hook. */\nexport function _resetWalletState(): void { state = { inWallet: false, via: null }; }\n","/**\n * <bwalletx-bar>, <bwalletx-topbar>, <bwalletx-drawer>: the bApp shell drawn on the web.\n * Values match the bWalletX Dock exactly (see docs/BAPP-SHELL in the wallet repo).\n * Framework-free; React wrappers in ./react.ts.\n */\nimport { SLOT_IDS, isSlotId, slotPath, type BappManifest, type SlotId } from './manifest.js';\nimport { detectWalletUA, walletState } from './wallet.js';\n\nexport const TOKENS = {\n barHeight: 88, barBg: '#0A0B0D', hairline: '#1C1C1E', icon: 20, active: '#FFD24D', inactive: '#F2F2F0',\n label: '#98A2B3', bSize: 52, stripHeight: 28, rowHeight: 56, tool: 36, toolRing: '#2A2A2C', toolIcon: '#F5B800',\n} as const;\n\nconst DEFAULT_LABELS: Record<SlotId, string> = { wallet: 'Wallet', exchange: 'Exchange', b: 'b', feed: 'Feed', chat: 'Chat' };\nconst svg = (d: string) => `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${d}</svg>`;\nexport const ICONS = {\n wallet: svg('<rect x=\"3\" y=\"6\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M3 10h18\"/>'),\n exchange: svg('<path d=\"M7 4v16M3 8l4-4 4 4M17 20V4M13 16l4 4 4-4\"/>'),\n feed: svg('<rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M8 9h8M8 13h8M8 17h5\"/>'),\n chat: svg('<path d=\"M21 12a8 8 0 0 1-12 7l-5 1 1-4a8 8 0 1 1 16-4z\"/>'),\n menu: svg('<path d=\"M4 6h16M4 12h16M4 18h16\"/>'),\n close: svg('<path d=\"M6 6l12 12M18 6L6 18\"/>'),\n};\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\nconst Base: typeof HTMLElement = typeof HTMLElement === 'undefined' ? (class {} as unknown as typeof HTMLElement) : HTMLElement;\n\n/** in-wallet attribute: \"true\" / \"false\" force it; anything else = auto-detect. */\nfunction inWallet(el: HTMLElement): boolean {\n const a = el.getAttribute('in-wallet');\n if (a === 'true' || a === '') return true;\n if (a === 'false') return false;\n return walletState().inWallet || detectWalletUA().inWallet;\n}\n\nfunction emit(el: HTMLElement, type: string, detail: unknown, cancelable = false): boolean {\n return el.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));\n}\n\n/** Fire bapp-navigate; unless prevented (or navigate=\"manual\"), go to the path. */\nfunction navigate(el: HTMLElement, detail: { slot?: SlotId; path: string | null }): void {\n const ok = emit(el, 'bapp-navigate', { ...detail, source: 'web' }, true);\n if (ok && detail.path && el.getAttribute('navigate') !== 'manual') location.assign(detail.path);\n}\n\nabstract class ShellElement extends Base {\n protected _manifest: BappManifest | null = null;\n private readonly onWallet = () => this.render();\n get manifest(): BappManifest | null { return this._manifest; }\n set manifest(m: BappManifest | null) { this._manifest = m; this.render(); }\n connectedCallback(): void {\n if (!this.shadowRoot) this.attachShadow({ mode: 'open' });\n window.addEventListener('bapp-wallet', this.onWallet);\n this.render();\n }\n disconnectedCallback(): void { window.removeEventListener('bapp-wallet', this.onWallet); }\n attributeChangedCallback(): void { if (this.shadowRoot) this.render(); }\n protected accent(): string { return this._manifest?.theme?.accent ?? TOKENS.active; }\n abstract render(): void;\n}\n\n// ---------------------------------------------------------------- <bwalletx-bar>\nconst BAR_CSS = `\n:host{display:block;--bwx-accent:${TOKENS.active}}\n:host([hidden]),:host([data-in-wallet]){display:none!important}\nnav{height:${TOKENS.barHeight}px;box-sizing:border-box;background:${TOKENS.barBg};border-top:1px solid ${TOKENS.hairline};\n display:flex;align-items:flex-start;padding:10px 0 env(safe-area-inset-bottom,0);font:10px/1.2 -apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\nbutton{all:unset;box-sizing:border-box;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;\n color:${TOKENS.inactive};-webkit-tap-highlight-color:transparent;touch-action:manipulation}\nbutton:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px;border-radius:10px}\nbutton svg{width:${TOKENS.icon}px;height:${TOKENS.icon}px}\n.lbl{font-size:10px;color:${TOKENS.label};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}\nbutton[aria-current=\"page\"]{color:var(--bwx-accent)}\nbutton[aria-current=\"page\"] .lbl{color:var(--bwx-accent);font-weight:700}\nbutton:disabled{opacity:.35;cursor:default;pointer-events:none}\n.b{width:${TOKENS.bSize}px;height:${TOKENS.bSize}px;border-radius:50%;background:#000;border:2px solid var(--bwx-accent);box-sizing:border-box;\n display:grid;place-items:center;font:800 24px/1 Georgia,\"Times New Roman\",serif;color:var(--bwx-accent);margin-top:-4px}\nbutton[data-slot=\"b\"][aria-current=\"page\"] .b{box-shadow:0 0 0 3px #FFD24D33}\n`;\n\nexport class BwalletxBarElement extends ShellElement {\n static observedAttributes = ['active', 'in-wallet', 'hidden'];\n private holdTimer: ReturnType<typeof setTimeout> | null = null;\n private held = false;\n get active(): SlotId | null { const a = this.getAttribute('active'); return isSlotId(a) ? a : null; }\n set active(s: SlotId | null) {\n if (s) this.setAttribute('active', s); else this.removeAttribute('active');\n }\n attributeChangedCallback(name?: string, old?: string | null, val?: string | null): void {\n super.attributeChangedCallback();\n if (name === 'active' && val !== old && isSlotId(val)) emit(this, 'bapp-active', { slot: val });\n }\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n if (inWallet(this)) this.setAttribute('data-in-wallet', ''); else this.removeAttribute('data-in-wallet');\n const m = this._manifest;\n const active = this.active;\n const slots = SLOT_IDS.map((id) => {\n const s = m?.slots[id];\n const enabled = !!s?.enabled;\n const label = s?.label ?? DEFAULT_LABELS[id];\n const cur = active === id ? ' aria-current=\"page\"' : '';\n const dis = enabled ? '' : ' disabled aria-disabled=\"true\"';\n if (id === 'b') return `<button type=\"button\" data-slot=\"b\" aria-label=\"b agent${m ? ` in ${esc(m.name)}` : ''}\"${cur}${dis}><span class=\"b\">b</span></button>`;\n return `<button type=\"button\" data-slot=\"${id}\" aria-label=\"${esc(label)}\"${cur}${dis}>${ICONS[id]}<span class=\"lbl\">${esc(label)}</span></button>`;\n }).join('');\n root.innerHTML = `<style>${BAR_CSS}</style><nav part=\"bar\" aria-label=\"${m ? esc(m.name) : 'App'} navigation\" style=\"--bwx-accent:${this.accent()}\">${slots}</nav>`;\n root.querySelectorAll<HTMLButtonElement>('button[data-slot]').forEach((b) => {\n const id = b.dataset.slot as SlotId;\n if (id === 'b') this.wireB(b);\n else b.addEventListener('click', () => this.go(id));\n });\n }\n private go(id: SlotId): void {\n const m = this._manifest;\n if (!m || !m.slots[id]?.enabled) return;\n this.active = id;\n navigate(this, { slot: id, path: slotPath(m, id) });\n }\n /** (b): tap = bapp-b-press, hold (>=450ms) = bapp-b-hold {phase:'start'|'end'}. Hold-to-talk is a later phase. */\n private wireB(b: HTMLButtonElement): void {\n const clear = () => { if (this.holdTimer) clearTimeout(this.holdTimer); this.holdTimer = null; };\n b.addEventListener('pointerdown', () => {\n this.held = false; clear();\n this.holdTimer = setTimeout(() => { this.held = true; emit(this, 'bapp-b-hold', { phase: 'start' }); }, 450);\n });\n const up = () => { clear(); if (this.held) emit(this, 'bapp-b-hold', { phase: 'end' }); };\n b.addEventListener('pointerup', up);\n b.addEventListener('pointercancel', up);\n b.addEventListener('pointerleave', up);\n b.addEventListener('click', () => {\n if (this.held) { this.held = false; return; }\n if (!this._manifest?.slots.b.enabled) return;\n emit(this, 'bapp-b-press', { path: slotPath(this._manifest, 'b') });\n const p = slotPath(this._manifest, 'b');\n if (p) this.go('b');\n });\n }\n}\n\n// ---------------------------------------------------------------- <bwalletx-topbar>\nconst TOP_CSS = `\n:host{display:block;--bwx-accent:${TOKENS.active};font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\n:host([hidden]){display:none!important}\n.strip{height:${TOKENS.stripHeight}px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-size:11px;color:#9a9a9a;background:${TOKENS.barBg}}\n::slotted([slot^=\"strip\"]){color:inherit}\n.row{height:${TOKENS.rowHeight}px;box-sizing:border-box;display:flex;align-items:center;gap:8px;padding:0 12px;background:${TOKENS.barBg};border-bottom:1px solid ${TOKENS.hairline}}\n.btn,::slotted([slot=\"tools\"]){all:unset;box-sizing:border-box;width:${TOKENS.tool}px;height:${TOKENS.tool}px;flex:none;border-radius:50%;border:1px solid ${TOKENS.toolRing};\n display:grid;place-items:center;color:${TOKENS.toolIcon};cursor:pointer;-webkit-tap-highlight-color:transparent}\n.btn:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px}\n.btn svg{width:18px;height:18px}\n.app{border-color:${TOKENS.toolIcon};background:#1a1405;font-weight:800;font-size:13px;line-height:1;overflow:hidden}\n.app img{width:100%;height:100%;object-fit:cover}\n.title{flex:1;min-width:0;color:#F2F2F0;font-weight:700;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.title small{display:block;color:#777;font-weight:500;font-size:11px}\n.tools{display:flex;gap:8px}\n`;\n\nexport class BwalletxTopbarElement extends ShellElement {\n static observedAttributes = ['title', 'subtitle', 'in-wallet', 'drawer', 'menu', 'strip', 'hidden'];\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n const m = this._manifest;\n const wallet = inWallet(this);\n const menu = wallet || this.getAttribute('menu') === 'true';\n const name = this.getAttribute('title') ?? m?.name ?? '';\n const sub = this.getAttribute('subtitle');\n const initials = name.replace(/[^A-Za-z0-9]/g, '').slice(0, 2) || 'b';\n const icon = m?.icon ? `<img src=\"${esc(m.icon)}\" alt=\"\">` : esc(initials);\n const strip = this.getAttribute('strip') !== 'off';\n root.innerHTML = `<style>${TOP_CSS}</style><header part=\"topbar\" style=\"--bwx-accent:${this.accent()}\">\n${strip ? `<div class=\"strip\" part=\"strip\"><slot name=\"strip-left\"></slot><slot name=\"strip-right\"></slot></div>` : ''}\n<div class=\"row\" part=\"row\">\n${menu ? `<button type=\"button\" class=\"btn\" data-act=\"menu\" aria-label=\"bWalletX menu\">${ICONS.menu}</button>` : ''}\n<button type=\"button\" class=\"btn app\" data-act=\"drawer\" aria-label=\"${esc(name || 'App')} home and sections\" aria-haspopup=\"true\">${icon}</button>\n<div class=\"title\" part=\"title\">${esc(name)}${sub ? `<small>${esc(sub)}</small>` : ''}</div>\n<div class=\"tools\"><slot name=\"tools\"></slot>${wallet ? `<button type=\"button\" class=\"btn\" data-act=\"close\" aria-label=\"Close ${esc(name || 'app')}\">${ICONS.close}</button>` : ''}</div>\n</div></header>`;\n root.querySelector('[data-act=\"menu\"]')?.addEventListener('click', () => emit(this, 'bapp-menu', {}));\n root.querySelector('[data-act=\"close\"]')?.addEventListener('click', () => emit(this, 'bapp-close', {}));\n root.querySelector('[data-act=\"drawer\"]')?.addEventListener('click', () => {\n if (!emit(this, 'bapp-drawer-toggle', {}, true)) return;\n const id = this.getAttribute('drawer');\n const d = (id ? document.getElementById(id) : document.querySelector('bwalletx-drawer')) as BwalletxDrawerElement | null;\n d?.toggle();\n });\n }\n}\n\n// ---------------------------------------------------------------- <bwalletx-drawer>\nconst DRAWER_CSS = `\n:host{position:fixed;inset:var(--bwx-drawer-top,84px) 0 var(--bwx-drawer-bottom,88px) 0;z-index:50;display:none;font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\n:host([open]){display:block}\n.scrim{position:absolute;inset:0;background:#0009}\nnav{position:absolute;top:0;left:0;bottom:0;width:min(78%,320px);box-sizing:border-box;background:#0d0e10;border-right:1px solid ${TOKENS.hairline};padding:14px;overflow:auto}\nh4{margin:10px 0 6px;font-size:11px;color:#777;letter-spacing:.08em;text-transform:uppercase;font-weight:600}\na{display:flex;align-items:center;gap:10px;padding:11px 10px;border-radius:10px;color:#F2F2F0;text-decoration:none;font-size:15px}\na img{width:20px;height:20px}\na[aria-current=\"page\"]{background:#1a1405;color:var(--bwx-accent)}\na:focus-visible{outline:2px solid var(--bwx-accent)}\nhr{border:0;border-top:1px solid ${TOKENS.hairline};margin:10px 0}\n.back{color:${TOKENS.toolIcon}}\n`;\n\nexport class BwalletxDrawerElement extends ShellElement {\n static observedAttributes = ['open', 'current', 'in-wallet'];\n get open(): boolean { return this.hasAttribute('open'); }\n set open(v: boolean) { this.toggleAttribute('open', v); }\n toggle(force?: boolean): void { this.open = force ?? !this.open; }\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n const m = this._manifest;\n const cur = this.getAttribute('current') ?? (typeof location !== 'undefined' ? location.pathname : '/');\n const items = m ? [{ label: 'Home', path: m.home }, ...m.drawer] : [];\n const links = items.map((it, i) => `<a href=\"${esc(it.path)}\" data-i=\"${i}\"${it.path === cur ? ' aria-current=\"page\"' : ''}>${'icon' in it && it.icon ? `<img src=\"${esc(it.icon)}\" alt=\"\">` : ''}${esc(it.label)}</a>`).join('');\n const back = inWallet(this) ? `<hr><a href=\"#\" class=\"back\" data-act=\"close\">← Back to bWalletX</a>` : '';\n root.innerHTML = `<style>${DRAWER_CSS}</style><div class=\"scrim\" part=\"scrim\"></div><nav part=\"drawer\" aria-label=\"${m ? esc(m.name) : 'App'} sections\" style=\"--bwx-accent:${this.accent()}\"><h4>${m ? esc(m.name) : ''}</h4>${links}${back}</nav>`;\n root.querySelector('.scrim')!.addEventListener('click', () => this.toggle(false));\n root.querySelectorAll<HTMLAnchorElement>('a[data-i]').forEach((a) => a.addEventListener('click', (e) => {\n e.preventDefault();\n const it = items[Number(a.dataset.i)]!;\n this.toggle(false);\n navigate(this, { path: it.path });\n }));\n root.querySelector('[data-act=\"close\"]')?.addEventListener('click', (e) => { e.preventDefault(); this.toggle(false); emit(this, 'bapp-close', {}); });\n }\n}\n\n/** Register the three elements (idempotent). */\nexport function defineBappShell(): void {\n if (typeof customElements === 'undefined') return;\n if (!customElements.get('bwalletx-bar')) customElements.define('bwalletx-bar', BwalletxBarElement);\n if (!customElements.get('bwalletx-topbar')) customElements.define('bwalletx-topbar', BwalletxTopbarElement);\n if (!customElements.get('bwalletx-drawer')) customElements.define('bwalletx-drawer', BwalletxDrawerElement);\n}\n\n/** Give every shell element on the page the same manifest. */\nexport function applyManifest(m: BappManifest, root: ParentNode = document): void {\n root.querySelectorAll<HTMLElement & { manifest: BappManifest | null }>('bwalletx-bar,bwalletx-topbar,bwalletx-drawer').forEach((el) => { el.manifest = m; });\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/shell/react.ts","../src/shell/manifest.ts","../src/shell/wallet.ts","../src/shell/elements.ts"],"sourcesContent":["/**\n * React wrappers for the bApp shell. The elements do the work; these set the manifest property\n * and wire the events.\n *\n * import { BappBar, BappTopbar, BappDrawer } from '@bwalletx/connect/shell/react';\n * <BappTopbar manifest={m} drawer=\"sections\" />\n * <BappDrawer id=\"sections\" manifest={m} />\n * <BappBar manifest={m} active=\"feed\" onNavigate={(d, e) => { e.preventDefault(); router.push(d.path) }} />\n */\nimport { createElement, useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\nimport { defineBappShell } from './elements.js';\nimport type { BappManifest, SlotId } from './manifest.js';\n\nexport interface NavigateDetail { slot?: SlotId; path: string | null; source: 'web' | 'wallet' }\ntype Handler<D> = (detail: D, event: CustomEvent<D>) => void;\n\ninterface Common {\n manifest: BappManifest | null;\n /** \"auto\" (default) detects bWallet; true/false forces. */\n inWallet?: boolean | 'auto';\n /** true = never call location.assign; route in onNavigate. */\n manualNavigation?: boolean;\n onNavigate?: Handler<NavigateDetail>;\n id?: string;\n className?: string;\n style?: CSSProperties;\n /** Slotted content, e.g. <button slot=\"tools\"> or <span slot=\"strip-left\">. */\n children?: ReactNode;\n}\n\nfunction useShell(tag: string, props: Common, attrs: Record<string, string | undefined>, events: Record<string, Handler<never> | undefined>) {\n const ref = useRef<HTMLElement & { manifest: BappManifest | null }>(null);\n useEffect(() => { defineBappShell(); }, []);\n useEffect(() => { if (ref.current) ref.current.manifest = props.manifest; }, [props.manifest]);\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const all = { 'bapp-navigate': props.onNavigate, ...events };\n const subs = Object.entries(all).filter(([, h]) => h).map(([type, h]) => {\n const fn = (e: Event) => h!((e as CustomEvent).detail, e as CustomEvent);\n el.addEventListener(type, fn);\n return () => el.removeEventListener(type, fn);\n });\n return () => subs.forEach((u) => u());\n });\n const a: Record<string, unknown> = { ref, id: props.id, class: props.className, style: props.style };\n if (props.inWallet !== undefined && props.inWallet !== 'auto') a['in-wallet'] = String(props.inWallet);\n if (props.manualNavigation) a.navigate = 'manual';\n for (const [k, v] of Object.entries(attrs)) if (v !== undefined) a[k] = v;\n return createElement(tag, a, props.children);\n}\n\nexport interface BappBarProps extends Common {\n active?: SlotId | null;\n onActive?: Handler<{ slot: SlotId }>;\n onBPress?: Handler<{ path: string | null }>;\n onBHold?: Handler<{ phase: 'start' | 'end' }>;\n}\nexport function BappBar(p: BappBarProps) {\n return useShell('bwalletx-bar', p, { active: p.active ?? undefined }, { 'bapp-active': p.onActive, 'bapp-b-press': p.onBPress, 'bapp-b-hold': p.onBHold });\n}\n\nexport interface BappTopbarProps extends Common {\n title?: string;\n subtitle?: string;\n /** id of the <bwalletx-drawer> the app icon opens. */\n drawer?: string;\n /** Show ☰ on the web too (it always shows in the wallet). */\n menu?: boolean;\n strip?: boolean;\n onMenu?: Handler<object>;\n onClose?: Handler<object>;\n onDrawerToggle?: Handler<object>;\n}\nexport function BappTopbar(p: BappTopbarProps) {\n return useShell('bwalletx-topbar', p, {\n title: p.title, subtitle: p.subtitle, drawer: p.drawer, menu: p.menu ? 'true' : undefined, strip: p.strip === false ? 'off' : undefined,\n }, { 'bapp-menu': p.onMenu, 'bapp-close': p.onClose, 'bapp-drawer-toggle': p.onDrawerToggle });\n}\n\nexport interface BappDrawerProps extends Common {\n open?: boolean;\n /** Path to highlight; default location.pathname. */\n current?: string;\n onClose?: Handler<object>;\n}\nexport function BappDrawer(p: BappDrawerProps) {\n return useShell('bwalletx-drawer', p, { open: p.open ? '' : undefined, current: p.current }, { 'bapp-close': p.onClose });\n}\n\nexport type { BappManifest, SlotId } from './manifest.js';\n","/**\n * bApp manifest: /.well-known/bapp.json. One file declares the app's five scoped slots, its drawer\n * (Home + sections) and its like-to-fund config. The web bar draws from it; inside bWalletX the\n * wallet's native dock reads the same file. Every path is same-origin: no URLs, no schemes.\n */\n\nexport const SLOT_IDS = ['wallet', 'exchange', 'b', 'feed', 'chat'] as const;\nexport type SlotId = (typeof SLOT_IDS)[number];\n\nexport interface BappSlot {\n /** Same-origin path, e.g. \"/feed\". Required when enabled, except for \"b\" (the agent). */\n path?: string;\n /** Optional rename (\"Market\"); the icon and position never change. */\n label?: string;\n /** false = shown greyed out and not clickable. Slots are never hidden. */\n enabled: boolean;\n}\n\nexport interface BappDrawerItem {\n label: string;\n path: string;\n /** Optional same-origin icon path. */\n icon?: string;\n}\n\nexport interface BappLikeConfig {\n /** Which fields of a feed item hold the id, token (BSV-21 id / $TICKER), payee, title and media. */\n fields: { id: string; token: string; payee?: string; title?: string; media?: string };\n /** Price of one Like in US cents. Default 1 (one PNEE). */\n amountCents: number;\n /** Pay in PNEE (the wallet's USD-cent token) when held, else BSV. Default \"PNEE\". */\n currency: 'PNEE' | 'BSV';\n /** Daily cap in US cents. Default 100 ($1), never more. */\n dailyCapCents: number;\n}\n\nexport interface BappTheme {\n /** Active/accent colour, #RRGGBB. Default #FFD24D. */\n accent?: string;\n}\n\nexport interface BappManifest {\n version: 1;\n name: string;\n /** Same-origin path to a square icon. */\n icon: string;\n /** The app's Home (first drawer item). Default \"/\". */\n home: string;\n theme?: BappTheme;\n slots: Record<SlotId, BappSlot>;\n drawer: BappDrawerItem[];\n like?: BappLikeConfig;\n}\n\nexport type ValidationResult =\n | { ok: true; manifest: BappManifest; errors: [] }\n | { ok: false; errors: string[]; manifest?: undefined };\n\nexport const WELL_KNOWN_PATH = '/.well-known/bapp.json';\nexport const LIKE_DEFAULT_CENTS = 1;\nexport const LIKE_MAX_DAILY_CENTS = 100;\n\n/**\n * A same-origin path: starts with one \"/\", no \"//\" (protocol-relative), no backslashes, no control\n * characters, no scheme. Query and hash are allowed.\n */\nexport function isSameOriginPath(p: unknown): p is string {\n if (typeof p !== 'string' || p.length === 0 || p.length > 512) return false;\n if (p[0] !== '/' || p[1] === '/' || p[1] === '\\\\') return false;\n if (/[\\\\\\u0000-\\u001f\\u007f\\s]/.test(p)) return false;\n // Resolving against a dummy origin must not change origin.\n try {\n return new URL(p, 'https://bapp.invalid').origin === 'https://bapp.invalid';\n } catch {\n return false;\n }\n}\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);\nconst str = (v: unknown, max = 64): v is string => typeof v === 'string' && v.trim().length > 0 && v.length <= max;\n\n/** Validate and normalise a parsed bapp.json. Never throws. */\nexport function validateManifest(input: unknown): ValidationResult {\n const errors: string[] = [];\n if (!isObj(input)) return { ok: false, errors: ['manifest must be a JSON object'] };\n const m = input;\n const known = new Set(['$schema', 'version', 'name', 'icon', 'home', 'theme', 'slots', 'drawer', 'like']);\n for (const k of Object.keys(m)) if (!known.has(k)) errors.push(`unknown field \"${k}\"`);\n\n if (m.version !== 1) errors.push('version must be 1');\n if (!str(m.name, 40)) errors.push('name must be a non-empty string (max 40)');\n if (!isSameOriginPath(m.icon)) errors.push('icon must be a same-origin path starting with \"/\"');\n const home = m.home === undefined ? '/' : m.home;\n if (!isSameOriginPath(home)) errors.push('home must be a same-origin path starting with \"/\"');\n\n let theme: BappTheme | undefined;\n if (m.theme !== undefined) {\n if (!isObj(m.theme)) errors.push('theme must be an object');\n else {\n for (const k of Object.keys(m.theme)) if (k !== 'accent') errors.push(`unknown field \"theme.${k}\"`);\n if (m.theme.accent !== undefined && !(typeof m.theme.accent === 'string' && /^#[0-9a-fA-F]{6}$/.test(m.theme.accent)))\n errors.push('theme.accent must be #RRGGBB');\n theme = m.theme.accent ? { accent: m.theme.accent as string } : {};\n }\n }\n\n const slots = {} as Record<SlotId, BappSlot>;\n if (!isObj(m.slots)) errors.push('slots must be an object with wallet, exchange, b, feed, chat');\n else {\n for (const k of Object.keys(m.slots)) if (!(SLOT_IDS as readonly string[]).includes(k)) errors.push(`unknown slot \"${k}\"`);\n for (const id of SLOT_IDS) {\n const s = m.slots[id];\n if (!isObj(s)) { errors.push(`slots.${id} is required (use \"enabled\": false for an unused slot)`); continue; }\n for (const k of Object.keys(s)) if (!['path', 'label', 'enabled'].includes(k)) errors.push(`unknown field \"slots.${id}.${k}\"`);\n if (typeof s.enabled !== 'boolean') errors.push(`slots.${id}.enabled must be true or false`);\n if (s.path !== undefined && !isSameOriginPath(s.path)) errors.push(`slots.${id}.path must be a same-origin path starting with \"/\"`);\n if (s.enabled === true && s.path === undefined && id !== 'b') errors.push(`slots.${id}.path is required when enabled`);\n if (s.label !== undefined && !str(s.label, 12)) errors.push(`slots.${id}.label must be a short string (max 12)`);\n slots[id] = { enabled: s.enabled === true, ...(s.path !== undefined ? { path: s.path as string } : {}), ...(s.label !== undefined ? { label: s.label as string } : {}) };\n }\n }\n\n const drawer: BappDrawerItem[] = [];\n if (m.drawer !== undefined) {\n if (!Array.isArray(m.drawer)) errors.push('drawer must be an array');\n else if (m.drawer.length > 24) errors.push('drawer has more than 24 items');\n else m.drawer.forEach((d, i) => {\n if (!isObj(d)) { errors.push(`drawer[${i}] must be an object`); return; }\n for (const k of Object.keys(d)) if (!['label', 'path', 'icon'].includes(k)) errors.push(`unknown field \"drawer[${i}].${k}\"`);\n if (!str(d.label, 40)) errors.push(`drawer[${i}].label must be a non-empty string (max 40)`);\n if (!isSameOriginPath(d.path)) errors.push(`drawer[${i}].path must be a same-origin path starting with \"/\"`);\n if (d.icon !== undefined && !isSameOriginPath(d.icon)) errors.push(`drawer[${i}].icon must be a same-origin path starting with \"/\"`);\n drawer.push({ label: d.label as string, path: d.path as string, ...(d.icon ? { icon: d.icon as string } : {}) });\n });\n }\n\n let like: BappLikeConfig | undefined;\n if (m.like !== undefined) {\n const l = m.like;\n if (!isObj(l)) errors.push('like must be an object');\n else {\n for (const k of Object.keys(l)) if (!['fields', 'amountCents', 'currency', 'dailyCapCents'].includes(k)) errors.push(`unknown field \"like.${k}\"`);\n const f = l.fields;\n if (!isObj(f) || !str(f.id) || !str(f.token)) errors.push('like.fields needs \"id\" and \"token\" field names');\n else for (const k of ['payee', 'title', 'media']) if (f[k] !== undefined && !str(f[k])) errors.push(`like.fields.${k} must be a field name`);\n const amount = l.amountCents ?? LIKE_DEFAULT_CENTS;\n const cap = l.dailyCapCents ?? LIKE_MAX_DAILY_CENTS;\n const currency = l.currency ?? 'PNEE';\n if (!Number.isInteger(amount) || (amount as number) < 1) errors.push('like.amountCents must be a whole number of cents, at least 1');\n if (!Number.isInteger(cap) || (cap as number) < 1 || (cap as number) > LIKE_MAX_DAILY_CENTS) errors.push('like.dailyCapCents must be 1..100 ($1 max)');\n else if (Number.isInteger(amount) && (amount as number) > (cap as number)) errors.push('like.amountCents cannot exceed like.dailyCapCents');\n if (currency !== 'PNEE' && currency !== 'BSV') errors.push('like.currency must be \"PNEE\" or \"BSV\"');\n if (isObj(f)) {\n const fields: BappLikeConfig['fields'] = { id: f.id as string, token: f.token as string };\n for (const k of ['payee', 'title', 'media'] as const) if (typeof f[k] === 'string') fields[k] = f[k] as string;\n like = { fields, amountCents: amount as number, currency: currency as 'PNEE' | 'BSV', dailyCapCents: cap as number };\n }\n }\n }\n\n if (errors.length) return { ok: false, errors };\n return {\n ok: true,\n errors: [],\n manifest: {\n version: 1, name: (m.name as string).trim(), icon: m.icon as string, home: home as string,\n ...(theme ? { theme } : {}), slots, drawer, ...(like ? { like } : {}),\n },\n };\n}\n\n/** Same as validateManifest but throws with every error listed. */\nexport function parseManifest(input: unknown): BappManifest {\n const r = validateManifest(input);\n if (!r.ok) throw new Error(`Invalid bapp.json:\\n- ${r.errors.join('\\n- ')}`);\n return r.manifest;\n}\n\n/** Fetch and validate /.well-known/bapp.json (same origin). */\nexport async function loadManifest(path: string = WELL_KNOWN_PATH, fetchImpl: typeof fetch = fetch): Promise<BappManifest> {\n if (!isSameOriginPath(path)) throw new Error('loadManifest: path must be same-origin');\n const res = await fetchImpl(path, { credentials: 'same-origin', headers: { accept: 'application/json' } });\n if (!res.ok) throw new Error(`loadManifest: ${path} returned ${res.status}`);\n return parseManifest(await res.json());\n}\n\n/** The path a slot points at, or null when disabled / no path. */\nexport function slotPath(m: BappManifest, slot: SlotId): string | null {\n const s = m.slots[slot];\n return s && s.enabled && s.path ? s.path : null;\n}\n\nexport const isSlotId = (v: unknown): v is SlotId => typeof v === 'string' && (SLOT_IDS as readonly string[]).includes(v);\n","/**\n * In-wallet mode. Inside bWalletX the wallet draws the dock natively; the app hides its web bar,\n * follows `bapp:navigate {slot}` from the wallet, and reports `bapp:active {slot}` back.\n * Protocol: docs/SHELL-PROTOCOL.md.\n */\nimport { isSlotId, slotPath, type BappManifest, type SlotId } from './manifest.js';\n\n/** Wallet web origins allowed to drive a framed bApp. Extension origins are passed in by the app. */\nexport const DEFAULT_WALLET_ORIGINS: readonly string[] = ['https://web.bwalletx.com', 'https://bwalletx.com'];\n\nexport type WalletVia = 'ua' | 'frame' | null;\nexport interface WalletDetection {\n inWallet: boolean;\n via: WalletVia;\n /** Bottom inset (px) the wallet reserves, from `bWalletInset/<n>`. */\n inset?: number;\n}\n\nconst UA_RE = /\\b(?:bWallet|YoursWalletMobile)\\//;\nconst INSET_RE = /\\bbWalletInset\\/(\\d{1,4})\\b/;\n\n/** Synchronous detection from the user agent (native app webviews). */\nexport function detectWalletUA(ua: string = typeof navigator !== 'undefined' ? navigator.userAgent : ''): WalletDetection {\n const inset = INSET_RE.exec(ua);\n if (inset || UA_RE.test(ua)) return { inWallet: true, via: 'ua', ...(inset ? { inset: Number(inset[1]) } : {}) };\n return { inWallet: false, via: null };\n}\n\nlet state: WalletDetection = { inWallet: false, via: null };\n/** Current detection (UA, or a completed frame handshake). */\nexport function walletState(): WalletDetection { return state; }\n\nfunction setState(next: WalletDetection, win: Window): void {\n state = next;\n if (next.inWallet) win.document?.documentElement?.setAttribute('data-bwx-in-wallet', next.via ?? '');\n win.dispatchEvent(new CustomEvent('bapp-wallet', { detail: next }));\n}\n\nexport interface ShellBridgeOptions {\n manifest: BappManifest;\n /** Allowed wallet origins for framed mode (exact match). Default DEFAULT_WALLET_ORIGINS. */\n walletOrigins?: readonly string[];\n /** Default: window. */\n win?: Window;\n /** Default: navigator.userAgent. */\n userAgent?: string;\n}\n\ntype Msg = { type?: unknown; v?: unknown; slot?: unknown };\n\n/**\n * Listens for the wallet and keeps it in sync. Navigation is dispatched as a cancelable\n * `bapp-navigate` event on window ({slot, path, source:'wallet'}); if nobody calls\n * preventDefault() the page goes to the slot's path with location.assign.\n */\nexport class ShellBridge {\n readonly manifest: BappManifest;\n private readonly origins: readonly string[];\n private readonly win: Window;\n private target: { win: Window; origin: string } | null = null;\n private active: SlotId | null = null;\n private readonly onMessage = (e: MessageEvent) => this.handle(e);\n private readonly onActive = (e: Event) => { const s = (e as CustomEvent).detail?.slot; if (isSlotId(s)) this.setActive(s); };\n private readonly onMenu = () => this.post({ type: 'bapp:menu' });\n private readonly onClose = () => this.post({ type: 'bapp:close' });\n\n constructor(opts: ShellBridgeOptions) {\n this.manifest = opts.manifest;\n this.origins = opts.walletOrigins ?? DEFAULT_WALLET_ORIGINS;\n this.win = opts.win ?? window;\n const ua = detectWalletUA(opts.userAgent);\n if (ua.inWallet) {\n // Native webview: the wallet injects messages into the page itself.\n this.target = { win: this.win, origin: this.win.location.origin };\n setState(ua, this.win);\n }\n }\n\n start(): this {\n this.win.addEventListener('message', this.onMessage);\n this.win.addEventListener('bapp-active', this.onActive);\n this.win.addEventListener('bapp-menu', this.onMenu);\n this.win.addEventListener('bapp-close', this.onClose);\n return this;\n }\n\n stop(): void {\n this.win.removeEventListener('message', this.onMessage);\n this.win.removeEventListener('bapp-active', this.onActive);\n this.win.removeEventListener('bapp-menu', this.onMenu);\n this.win.removeEventListener('bapp-close', this.onClose);\n }\n\n get inWallet(): boolean { return this.target !== null; }\n\n /** Tell the wallet which slot is showing (lights up the native dock). */\n setActive(slot: SlotId): void {\n this.active = slot;\n this.post({ type: 'bapp:active', v: 1, slot });\n }\n\n /** Is this message from a trusted wallet? */\n trusted(e: MessageEvent): boolean {\n const parent = this.win.parent;\n // Framed: only our direct parent, only an allowlisted origin.\n if (parent && parent !== this.win && e.source === parent) return this.origins.includes(e.origin);\n // Native webview (UA detected): injected into this page, so same window and same origin.\n if (this.target?.win === this.win && (e.source === this.win || e.source === null)) return e.origin === this.win.location.origin;\n return false;\n }\n\n private handle(e: MessageEvent): void {\n const d = e.data as Msg;\n if (!d || typeof d !== 'object' || typeof d.type !== 'string' || !d.type.startsWith('bapp:')) return;\n if (!this.trusted(e)) return;\n if (d.type === 'bapp:hello') {\n if (!this.target) {\n this.target = { win: e.source as Window, origin: e.origin };\n setState({ inWallet: true, via: 'frame' }, this.win);\n }\n this.post({ type: 'bapp:ready', v: 1, manifest: '/.well-known/bapp.json', ...(this.active ? { slot: this.active } : {}) });\n return;\n }\n if (d.type === 'bapp:navigate') {\n if (!isSlotId(d.slot)) return; // never a URL, only a slot id\n const path = slotPath(this.manifest, d.slot);\n if (d.slot !== 'b' && !path) return; // disabled or not in the manifest\n if (d.slot === 'b' && !this.manifest.slots.b.enabled) return;\n const ev = new CustomEvent('bapp-navigate', { cancelable: true, detail: { slot: d.slot, path, source: 'wallet' } });\n this.win.dispatchEvent(ev);\n if (!ev.defaultPrevented && path) this.win.location.assign(path);\n this.setActive(d.slot);\n }\n }\n\n private post(msg: Record<string, unknown>): void {\n if (!this.target) return;\n const { win, origin } = this.target;\n // Native webviews expose ReactNativeWebView; fall back to postMessage to self.\n const rn = (this.win as unknown as { ReactNativeWebView?: { postMessage(s: string): void } }).ReactNativeWebView;\n if (win === this.win && rn) rn.postMessage(JSON.stringify(msg));\n else win.postMessage(msg, origin);\n }\n}\n\n/** Convenience: create and start a bridge. */\nexport function startShellBridge(opts: ShellBridgeOptions): ShellBridge {\n return new ShellBridge(opts).start();\n}\n\n/** Test hook. */\nexport function _resetWalletState(): void { state = { inWallet: false, via: null }; }\n","/**\n * <bwalletx-bar>, <bwalletx-topbar>, <bwalletx-drawer>: the bApp shell drawn on the web.\n * Values match the bWalletX Dock exactly (see docs/BAPP-SHELL in the wallet repo).\n * Framework-free; React wrappers in ./react.ts.\n */\nimport { SLOT_IDS, isSlotId, slotPath, type BappManifest, type SlotId } from './manifest.js';\nimport { detectWalletUA, walletState } from './wallet.js';\n\nexport const TOKENS = {\n barHeight: 88, barBg: '#0A0B0D', hairline: '#1C1C1E', icon: 20, active: '#FFD24D', inactive: '#F2F2F0',\n label: '#98A2B3', bSize: 52, stripHeight: 28, rowHeight: 56, tool: 36, toolRing: '#2A2A2C', toolIcon: '#F5B800',\n} as const;\n\nconst DEFAULT_LABELS: Record<SlotId, string> = { wallet: 'Wallet', exchange: 'Exchange', b: 'b', feed: 'Feed', chat: 'Chat' };\nconst svg = (d: string) => `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">${d}</svg>`;\nexport const ICONS = {\n wallet: svg('<rect x=\"3\" y=\"6\" width=\"18\" height=\"13\" rx=\"2\"/><path d=\"M3 10h18\"/>'),\n exchange: svg('<path d=\"M7 4v16M3 8l4-4 4 4M17 20V4M13 16l4 4 4-4\"/>'),\n feed: svg('<rect x=\"4\" y=\"4\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M8 9h8M8 13h8M8 17h5\"/>'),\n chat: svg('<path d=\"M21 12a8 8 0 0 1-12 7l-5 1 1-4a8 8 0 1 1 16-4z\"/>'),\n menu: svg('<path d=\"M4 6h16M4 12h16M4 18h16\"/>'),\n close: svg('<path d=\"M6 6l12 12M18 6L6 18\"/>'),\n};\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\nconst Base: typeof HTMLElement = typeof HTMLElement === 'undefined' ? (class {} as unknown as typeof HTMLElement) : HTMLElement;\n\n/** in-wallet attribute: \"true\" / \"false\" force it; anything else = auto-detect. */\nfunction inWallet(el: HTMLElement): boolean {\n const a = el.getAttribute('in-wallet');\n if (a === 'true' || a === '') return true;\n if (a === 'false') return false;\n return walletState().inWallet || detectWalletUA().inWallet;\n}\n\nfunction emit(el: HTMLElement, type: string, detail: unknown, cancelable = false): boolean {\n return el.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));\n}\n\n/** Fire bapp-navigate; unless prevented (or navigate=\"manual\"), go to the path. */\nfunction navigate(el: HTMLElement, detail: { slot?: SlotId; path: string | null }): void {\n const ok = emit(el, 'bapp-navigate', { ...detail, source: 'web' }, true);\n if (ok && detail.path && el.getAttribute('navigate') !== 'manual') location.assign(detail.path);\n}\n\nabstract class ShellElement extends Base {\n protected _manifest: BappManifest | null = null;\n private readonly onWallet = () => this.render();\n get manifest(): BappManifest | null { return this._manifest; }\n set manifest(m: BappManifest | null) { this._manifest = m; this.render(); }\n connectedCallback(): void {\n if (!this.shadowRoot) this.attachShadow({ mode: 'open' });\n window.addEventListener('bapp-wallet', this.onWallet);\n this.render();\n }\n disconnectedCallback(): void { window.removeEventListener('bapp-wallet', this.onWallet); }\n attributeChangedCallback(): void { if (this.shadowRoot) this.render(); }\n protected accent(): string { return this._manifest?.theme?.accent ?? TOKENS.active; }\n abstract render(): void;\n}\n\n// ---------------------------------------------------------------- <bwalletx-bar>\nconst BAR_CSS = `\n:host{display:block;--bwx-accent:${TOKENS.active}}\n:host([hidden]),:host([data-in-wallet]){display:none!important}\nnav{height:${TOKENS.barHeight}px;box-sizing:border-box;background:${TOKENS.barBg};border-top:1px solid ${TOKENS.hairline};\n display:flex;align-items:flex-start;padding:10px 0 env(safe-area-inset-bottom,0);font:10px/1.2 -apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\nbutton{all:unset;box-sizing:border-box;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;\n color:${TOKENS.inactive};-webkit-tap-highlight-color:transparent;touch-action:manipulation}\nbutton:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px;border-radius:10px}\nbutton svg{width:${TOKENS.icon}px;height:${TOKENS.icon}px}\n.lbl{font-size:10px;color:${TOKENS.label};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}\nbutton[aria-current=\"page\"]{color:var(--bwx-accent)}\nbutton[aria-current=\"page\"] .lbl{color:var(--bwx-accent);font-weight:700}\nbutton:disabled{opacity:.35;cursor:default;pointer-events:none}\n.b{width:${TOKENS.bSize}px;height:${TOKENS.bSize}px;border-radius:50%;background:#000;border:2px solid var(--bwx-accent);box-sizing:border-box;\n display:grid;place-items:center;font:800 24px/1 Georgia,\"Times New Roman\",serif;color:var(--bwx-accent);margin-top:-4px}\nbutton[data-slot=\"b\"][aria-current=\"page\"] .b{box-shadow:0 0 0 3px #FFD24D33}\n`;\n\nexport class BwalletxBarElement extends ShellElement {\n static observedAttributes = ['active', 'in-wallet', 'hidden'];\n private holdTimer: ReturnType<typeof setTimeout> | null = null;\n private held = false;\n get active(): SlotId | null { const a = this.getAttribute('active'); return isSlotId(a) ? a : null; }\n set active(s: SlotId | null) {\n if (s) this.setAttribute('active', s); else this.removeAttribute('active');\n }\n attributeChangedCallback(name?: string, old?: string | null, val?: string | null): void {\n super.attributeChangedCallback();\n if (name === 'active' && val !== old && isSlotId(val)) emit(this, 'bapp-active', { slot: val });\n }\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n if (inWallet(this)) this.setAttribute('data-in-wallet', ''); else this.removeAttribute('data-in-wallet');\n const m = this._manifest;\n const active = this.active;\n const slots = SLOT_IDS.map((id) => {\n const s = m?.slots[id];\n const enabled = !!s?.enabled;\n const label = s?.label ?? DEFAULT_LABELS[id];\n const cur = active === id ? ' aria-current=\"page\"' : '';\n const dis = enabled ? '' : ' disabled aria-disabled=\"true\"';\n if (id === 'b') return `<button type=\"button\" data-slot=\"b\" aria-label=\"b agent${m ? ` in ${esc(m.name)}` : ''}\"${cur}${dis}><span class=\"b\">b</span></button>`;\n return `<button type=\"button\" data-slot=\"${id}\" aria-label=\"${esc(label)}\"${cur}${dis}>${ICONS[id]}<span class=\"lbl\">${esc(label)}</span></button>`;\n }).join('');\n root.innerHTML = `<style>${BAR_CSS}</style><nav part=\"bar\" aria-label=\"${m ? esc(m.name) : 'App'} navigation\" style=\"--bwx-accent:${this.accent()}\">${slots}</nav>`;\n root.querySelectorAll<HTMLButtonElement>('button[data-slot]').forEach((b) => {\n const id = b.dataset.slot as SlotId;\n if (id === 'b') this.wireB(b);\n else b.addEventListener('click', () => this.go(id));\n });\n }\n private go(id: SlotId): void {\n const m = this._manifest;\n if (!m || !m.slots[id]?.enabled) return;\n this.active = id;\n navigate(this, { slot: id, path: slotPath(m, id) });\n }\n /** (b): tap = bapp-b-press, hold (>=450ms) = bapp-b-hold {phase:'start'|'end'}. Hold-to-talk is a later phase. */\n private wireB(b: HTMLButtonElement): void {\n const clear = () => { if (this.holdTimer) clearTimeout(this.holdTimer); this.holdTimer = null; };\n b.addEventListener('pointerdown', () => {\n this.held = false; clear();\n this.holdTimer = setTimeout(() => { this.held = true; emit(this, 'bapp-b-hold', { phase: 'start' }); }, 450);\n });\n const up = () => { clear(); if (this.held) emit(this, 'bapp-b-hold', { phase: 'end' }); };\n b.addEventListener('pointerup', up);\n b.addEventListener('pointercancel', up);\n b.addEventListener('pointerleave', up);\n b.addEventListener('click', () => {\n if (this.held) { this.held = false; return; }\n if (!this._manifest?.slots.b.enabled) return;\n emit(this, 'bapp-b-press', { path: slotPath(this._manifest, 'b') });\n const p = slotPath(this._manifest, 'b');\n if (p) this.go('b');\n });\n }\n}\n\n// ---------------------------------------------------------------- <bwalletx-topbar>\nconst TOP_CSS = `\n:host{display:block;--bwx-accent:${TOKENS.active};font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\n:host([hidden]){display:none!important}\n.strip{height:${TOKENS.stripHeight}px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;font-size:11px;color:#9a9a9a;background:${TOKENS.barBg}}\n::slotted([slot^=\"strip\"]){color:inherit}\n.row{height:${TOKENS.rowHeight}px;box-sizing:border-box;display:flex;align-items:center;gap:8px;padding:0 12px;background:${TOKENS.barBg};border-bottom:1px solid ${TOKENS.hairline}}\n.btn,::slotted([slot=\"tools\"]){all:unset;box-sizing:border-box;width:${TOKENS.tool}px;height:${TOKENS.tool}px;flex:none;border-radius:50%;border:1px solid ${TOKENS.toolRing};\n display:grid;place-items:center;color:${TOKENS.toolIcon};cursor:pointer;-webkit-tap-highlight-color:transparent}\n.btn:focus-visible{outline:2px solid var(--bwx-accent);outline-offset:2px}\n.btn svg{width:18px;height:18px}\n.app{border-color:${TOKENS.toolIcon};background:#1a1405;font-weight:800;font-size:13px;line-height:1;overflow:hidden}\n.app img{width:100%;height:100%;object-fit:cover}\n.title{flex:1;min-width:0;color:#F2F2F0;font-weight:700;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.title small{display:block;color:#777;font-weight:500;font-size:11px}\n.tools{display:flex;gap:8px}\n`;\n\nexport class BwalletxTopbarElement extends ShellElement {\n static observedAttributes = ['title', 'subtitle', 'in-wallet', 'drawer', 'menu', 'strip', 'hidden'];\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n const m = this._manifest;\n const wallet = inWallet(this);\n const menu = wallet || this.getAttribute('menu') === 'true';\n const name = this.getAttribute('title') ?? m?.name ?? '';\n const sub = this.getAttribute('subtitle');\n const initials = name.replace(/[^A-Za-z0-9]/g, '').slice(0, 2) || 'b';\n const icon = m?.icon ? `<img src=\"${esc(m.icon)}\" alt=\"\">` : esc(initials);\n const strip = this.getAttribute('strip') !== 'off';\n root.innerHTML = `<style>${TOP_CSS}</style><header part=\"topbar\" style=\"--bwx-accent:${this.accent()}\">\n${strip ? `<div class=\"strip\" part=\"strip\"><slot name=\"strip-left\"></slot><slot name=\"strip-right\"></slot></div>` : ''}\n<div class=\"row\" part=\"row\">\n${menu ? `<button type=\"button\" class=\"btn\" data-act=\"menu\" aria-label=\"bWalletX menu\">${ICONS.menu}</button>` : ''}\n<button type=\"button\" class=\"btn app\" data-act=\"drawer\" aria-label=\"${esc(name || 'App')} home and sections\" aria-haspopup=\"true\">${icon}</button>\n<div class=\"title\" part=\"title\">${esc(name)}${sub ? `<small>${esc(sub)}</small>` : ''}</div>\n<div class=\"tools\"><slot name=\"tools\"></slot>${wallet ? `<button type=\"button\" class=\"btn\" data-act=\"close\" aria-label=\"Close ${esc(name || 'app')}\">${ICONS.close}</button>` : ''}</div>\n</div></header>`;\n root.querySelector('[data-act=\"menu\"]')?.addEventListener('click', () => emit(this, 'bapp-menu', {}));\n root.querySelector('[data-act=\"close\"]')?.addEventListener('click', () => emit(this, 'bapp-close', {}));\n root.querySelector('[data-act=\"drawer\"]')?.addEventListener('click', () => {\n if (!emit(this, 'bapp-drawer-toggle', {}, true)) return;\n const id = this.getAttribute('drawer');\n const d = (id ? document.getElementById(id) : document.querySelector('bwalletx-drawer')) as BwalletxDrawerElement | null;\n d?.toggle();\n });\n }\n}\n\n// ---------------------------------------------------------------- <bwalletx-drawer>\nconst DRAWER_CSS = `\n:host{position:fixed;inset:var(--bwx-drawer-top,84px) 0 var(--bwx-drawer-bottom,88px) 0;z-index:50;display:none;font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",system-ui,sans-serif}\n:host([open]){display:block}\n.scrim{position:absolute;inset:0;background:#0009}\nnav{position:absolute;top:0;left:0;bottom:0;width:min(78%,320px);box-sizing:border-box;background:#0d0e10;border-right:1px solid ${TOKENS.hairline};padding:14px;overflow:auto}\nh4{margin:10px 0 6px;font-size:11px;color:#777;letter-spacing:.08em;text-transform:uppercase;font-weight:600}\na{display:flex;align-items:center;gap:10px;padding:11px 10px;border-radius:10px;color:#F2F2F0;text-decoration:none;font-size:15px}\na img{width:20px;height:20px}\na[aria-current=\"page\"]{background:#1a1405;color:var(--bwx-accent)}\na:focus-visible{outline:2px solid var(--bwx-accent)}\nhr{border:0;border-top:1px solid ${TOKENS.hairline};margin:10px 0}\n.back{color:${TOKENS.toolIcon}}\n`;\n\nexport class BwalletxDrawerElement extends ShellElement {\n static observedAttributes = ['open', 'current', 'in-wallet'];\n get open(): boolean { return this.hasAttribute('open'); }\n set open(v: boolean) { this.toggleAttribute('open', v); }\n toggle(force?: boolean): void { this.open = force ?? !this.open; }\n render(): void {\n const root = this.shadowRoot;\n if (!root) return;\n const m = this._manifest;\n const cur = this.getAttribute('current') ?? (typeof location !== 'undefined' ? location.pathname : '/');\n const items = m ? [{ label: 'Home', path: m.home }, ...m.drawer] : [];\n const links = items.map((it, i) => `<a href=\"${esc(it.path)}\" data-i=\"${i}\"${it.path === cur ? ' aria-current=\"page\"' : ''}>${'icon' in it && it.icon ? `<img src=\"${esc(it.icon)}\" alt=\"\">` : ''}${esc(it.label)}</a>`).join('');\n const back = inWallet(this) ? `<hr><a href=\"#\" class=\"back\" data-act=\"close\">← Back to bWalletX</a>` : '';\n root.innerHTML = `<style>${DRAWER_CSS}</style><div class=\"scrim\" part=\"scrim\"></div><nav part=\"drawer\" aria-label=\"${m ? esc(m.name) : 'App'} sections\" style=\"--bwx-accent:${this.accent()}\"><h4>${m ? esc(m.name) : ''}</h4>${links}${back}</nav>`;\n root.querySelector('.scrim')!.addEventListener('click', () => this.toggle(false));\n root.querySelectorAll<HTMLAnchorElement>('a[data-i]').forEach((a) => a.addEventListener('click', (e) => {\n e.preventDefault();\n const it = items[Number(a.dataset.i)]!;\n this.toggle(false);\n navigate(this, { path: it.path });\n }));\n root.querySelector('[data-act=\"close\"]')?.addEventListener('click', (e) => { e.preventDefault(); this.toggle(false); emit(this, 'bapp-close', {}); });\n }\n}\n\n/** Register the three elements (idempotent). */\nexport function defineBappShell(): void {\n if (typeof customElements === 'undefined') return;\n if (!customElements.get('bwalletx-bar')) customElements.define('bwalletx-bar', BwalletxBarElement);\n if (!customElements.get('bwalletx-topbar')) customElements.define('bwalletx-topbar', BwalletxTopbarElement);\n if (!customElements.get('bwalletx-drawer')) customElements.define('bwalletx-drawer', BwalletxDrawerElement);\n}\n\n/** Give every shell element on the page the same manifest. */\nexport function applyManifest(m: BappManifest, root: ParentNode = document): void {\n root.querySelectorAll<HTMLElement & { manifest: BappManifest | null }>('bwalletx-bar,bwalletx-topbar,bwalletx-drawer').forEach((el) => { el.manifest = m; });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,mBAAqF;;;ACH9E,IAAM,WAAW,CAAC,UAAU,YAAY,KAAK,QAAQ,MAAM;AAqL3D,SAAS,SAAS,GAAiB,MAA6B;AACrE,QAAM,IAAI,EAAE,MAAM,IAAI;AACtB,SAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO;AAC7C;AAEO,IAAM,WAAW,CAAC,MAA4B,OAAO,MAAM,YAAa,SAA+B,SAAS,CAAC;;;AC9KxH,IAAM,QAAQ;AACd,IAAM,WAAW;AAGV,SAAS,eAAe,KAAa,OAAO,cAAc,cAAc,UAAU,YAAY,IAAqB;AACxH,QAAM,QAAQ,SAAS,KAAK,EAAE;AAC9B,MAAI,SAAS,MAAM,KAAK,EAAE,EAAG,QAAO,EAAE,UAAU,MAAM,KAAK,MAAM,GAAI,QAAQ,EAAE,OAAO,OAAO,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,EAAG;AAC/G,SAAO,EAAE,UAAU,OAAO,KAAK,KAAK;AACtC;AAEA,IAAI,QAAyB,EAAE,UAAU,OAAO,KAAK,KAAK;AAEnD,SAAS,cAA+B;AAAE,SAAO;AAAO;;;ACtBxD,IAAM,SAAS;AAAA,EACpB,WAAW;AAAA,EAAI,OAAO;AAAA,EAAW,UAAU;AAAA,EAAW,MAAM;AAAA,EAAI,QAAQ;AAAA,EAAW,UAAU;AAAA,EAC7F,OAAO;AAAA,EAAW,OAAO;AAAA,EAAI,aAAa;AAAA,EAAI,WAAW;AAAA,EAAI,MAAM;AAAA,EAAI,UAAU;AAAA,EAAW,UAAU;AACxG;AAEA,IAAM,iBAAyC,EAAE,QAAQ,UAAU,UAAU,YAAY,GAAG,KAAK,MAAM,QAAQ,MAAM,OAAO;AAC5H,IAAM,MAAM,CAAC,MAAc,iJAAiJ,CAAC;AACtK,IAAM,QAAQ;AAAA,EACnB,QAAQ,IAAI,uEAAuE;AAAA,EACnF,UAAU,IAAI,uDAAuD;AAAA,EACrE,MAAM,IAAI,mFAAmF;AAAA,EAC7F,MAAM,IAAI,4DAA4D;AAAA,EACtE,MAAM,IAAI,qCAAqC;AAAA,EAC/C,OAAO,IAAI,kCAAkC;AAC/C;AAEA,IAAM,MAAM,CAAC,MAAc,EAAE,QAAQ,YAAY,CAAC,MAAM,KAAK,EAAE,WAAW,CAAC,CAAC,GAAG;AAC/E,IAAM,OAA2B,OAAO,gBAAgB,cAAe,MAAM;AAAC,IAAsC;AAGpH,SAAS,SAAS,IAA0B;AAC1C,QAAM,IAAI,GAAG,aAAa,WAAW;AACrC,MAAI,MAAM,UAAU,MAAM,GAAI,QAAO;AACrC,MAAI,MAAM,QAAS,QAAO;AAC1B,SAAO,YAAY,EAAE,YAAY,eAAe,EAAE;AACpD;AAEA,SAAS,KAAK,IAAiB,MAAc,QAAiB,aAAa,OAAgB;AACzF,SAAO,GAAG,cAAc,IAAI,YAAY,MAAM,EAAE,QAAQ,SAAS,MAAM,UAAU,MAAM,WAAW,CAAC,CAAC;AACtG;AAGA,SAAS,SAAS,IAAiB,QAAsD;AACvF,QAAM,KAAK,KAAK,IAAI,iBAAiB,EAAE,GAAG,QAAQ,QAAQ,MAAM,GAAG,IAAI;AACvE,MAAI,MAAM,OAAO,QAAQ,GAAG,aAAa,UAAU,MAAM,SAAU,UAAS,OAAO,OAAO,IAAI;AAChG;AAEA,IAAe,eAAf,cAAoC,KAAK;AAAA,EAC7B,YAAiC;AAAA,EAC1B,WAAW,MAAM,KAAK,OAAO;AAAA,EAC9C,IAAI,WAAgC;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EAC7D,IAAI,SAAS,GAAwB;AAAE,SAAK,YAAY;AAAG,SAAK,OAAO;AAAA,EAAG;AAAA,EAC1E,oBAA0B;AACxB,QAAI,CAAC,KAAK,WAAY,MAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AACxD,WAAO,iBAAiB,eAAe,KAAK,QAAQ;AACpD,SAAK,OAAO;AAAA,EACd;AAAA,EACA,uBAA6B;AAAE,WAAO,oBAAoB,eAAe,KAAK,QAAQ;AAAA,EAAG;AAAA,EACzF,2BAAiC;AAAE,QAAI,KAAK,WAAY,MAAK,OAAO;AAAA,EAAG;AAAA,EAC7D,SAAiB;AAAE,WAAO,KAAK,WAAW,OAAO,UAAU,OAAO;AAAA,EAAQ;AAEtF;AAGA,IAAM,UAAU;AAAA,mCACmB,OAAO,MAAM;AAAA;AAAA,aAEnC,OAAO,SAAS,uCAAuC,OAAO,KAAK,yBAAyB,OAAO,QAAQ;AAAA;AAAA;AAAA,SAG/G,OAAO,QAAQ;AAAA;AAAA,mBAEL,OAAO,IAAI,aAAa,OAAO,IAAI;AAAA,4BAC1B,OAAO,KAAK;AAAA;AAAA;AAAA;AAAA,WAI7B,OAAO,KAAK,aAAa,OAAO,KAAK;AAAA;AAAA;AAAA;AAKzC,IAAM,qBAAN,cAAiC,aAAa;AAAA,EACnD,OAAO,qBAAqB,CAAC,UAAU,aAAa,QAAQ;AAAA,EACpD,YAAkD;AAAA,EAClD,OAAO;AAAA,EACf,IAAI,SAAwB;AAAE,UAAM,IAAI,KAAK,aAAa,QAAQ;AAAG,WAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAAM;AAAA,EACpG,IAAI,OAAO,GAAkB;AAC3B,QAAI,EAAG,MAAK,aAAa,UAAU,CAAC;AAAA,QAAQ,MAAK,gBAAgB,QAAQ;AAAA,EAC3E;AAAA,EACA,yBAAyB,MAAe,KAAqB,KAA2B;AACtF,UAAM,yBAAyB;AAC/B,QAAI,SAAS,YAAY,QAAQ,OAAO,SAAS,GAAG,EAAG,MAAK,MAAM,eAAe,EAAE,MAAM,IAAI,CAAC;AAAA,EAChG;AAAA,EACA,SAAe;AACb,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AACX,QAAI,SAAS,IAAI,EAAG,MAAK,aAAa,kBAAkB,EAAE;AAAA,QAAQ,MAAK,gBAAgB,gBAAgB;AACvG,UAAM,IAAI,KAAK;AACf,UAAM,SAAS,KAAK;AACpB,UAAM,QAAQ,SAAS,IAAI,CAAC,OAAO;AACjC,YAAM,IAAI,GAAG,MAAM,EAAE;AACrB,YAAM,UAAU,CAAC,CAAC,GAAG;AACrB,YAAM,QAAQ,GAAG,SAAS,eAAe,EAAE;AAC3C,YAAM,MAAM,WAAW,KAAK,yBAAyB;AACrD,YAAM,MAAM,UAAU,KAAK;AAC3B,UAAI,OAAO,IAAK,QAAO,0DAA0D,IAAI,OAAO,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,GAAG,GAAG;AAC3H,aAAO,oCAAoC,EAAE,iBAAiB,IAAI,KAAK,CAAC,IAAI,GAAG,GAAG,GAAG,IAAI,MAAM,EAAE,CAAC,qBAAqB,IAAI,KAAK,CAAC;AAAA,IACnI,CAAC,EAAE,KAAK,EAAE;AACV,SAAK,YAAY,UAAU,OAAO,uCAAuC,IAAI,IAAI,EAAE,IAAI,IAAI,KAAK,oCAAoC,KAAK,OAAO,CAAC,KAAK,KAAK;AAC3J,SAAK,iBAAoC,mBAAmB,EAAE,QAAQ,CAAC,MAAM;AAC3E,YAAM,KAAK,EAAE,QAAQ;AACrB,UAAI,OAAO,IAAK,MAAK,MAAM,CAAC;AAAA,UACvB,GAAE,iBAAiB,SAAS,MAAM,KAAK,GAAG,EAAE,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAAA,EACQ,GAAG,IAAkB;AAC3B,UAAM,IAAI,KAAK;AACf,QAAI,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,QAAS;AACjC,SAAK,SAAS;AACd,aAAS,MAAM,EAAE,MAAM,IAAI,MAAM,SAAS,GAAG,EAAE,EAAE,CAAC;AAAA,EACpD;AAAA;AAAA,EAEQ,MAAM,GAA4B;AACxC,UAAM,QAAQ,MAAM;AAAE,UAAI,KAAK,UAAW,cAAa,KAAK,SAAS;AAAG,WAAK,YAAY;AAAA,IAAM;AAC/F,MAAE,iBAAiB,eAAe,MAAM;AACtC,WAAK,OAAO;AAAO,YAAM;AACzB,WAAK,YAAY,WAAW,MAAM;AAAE,aAAK,OAAO;AAAM,aAAK,MAAM,eAAe,EAAE,OAAO,QAAQ,CAAC;AAAA,MAAG,GAAG,GAAG;AAAA,IAC7G,CAAC;AACD,UAAM,KAAK,MAAM;AAAE,YAAM;AAAG,UAAI,KAAK,KAAM,MAAK,MAAM,eAAe,EAAE,OAAO,MAAM,CAAC;AAAA,IAAG;AACxF,MAAE,iBAAiB,aAAa,EAAE;AAClC,MAAE,iBAAiB,iBAAiB,EAAE;AACtC,MAAE,iBAAiB,gBAAgB,EAAE;AACrC,MAAE,iBAAiB,SAAS,MAAM;AAChC,UAAI,KAAK,MAAM;AAAE,aAAK,OAAO;AAAO;AAAA,MAAQ;AAC5C,UAAI,CAAC,KAAK,WAAW,MAAM,EAAE,QAAS;AACtC,WAAK,MAAM,gBAAgB,EAAE,MAAM,SAAS,KAAK,WAAW,GAAG,EAAE,CAAC;AAClE,YAAM,IAAI,SAAS,KAAK,WAAW,GAAG;AACtC,UAAI,EAAG,MAAK,GAAG,GAAG;AAAA,IACpB,CAAC;AAAA,EACH;AACF;AAGA,IAAM,UAAU;AAAA,mCACmB,OAAO,MAAM;AAAA;AAAA,gBAEhC,OAAO,WAAW,2HAA2H,OAAO,KAAK;AAAA;AAAA,cAE3J,OAAO,SAAS,8FAA8F,OAAO,KAAK,4BAA4B,OAAO,QAAQ;AAAA,uEAC5G,OAAO,IAAI,aAAa,OAAO,IAAI,mDAAmD,OAAO,QAAQ;AAAA,yCACnI,OAAO,QAAQ;AAAA;AAAA;AAAA,oBAGpC,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAO5B,IAAM,wBAAN,cAAoC,aAAa;AAAA,EACtD,OAAO,qBAAqB,CAAC,SAAS,YAAY,aAAa,UAAU,QAAQ,SAAS,QAAQ;AAAA,EAClG,SAAe;AACb,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AACX,UAAM,IAAI,KAAK;AACf,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,OAAO,UAAU,KAAK,aAAa,MAAM,MAAM;AACrD,UAAM,OAAO,KAAK,aAAa,OAAO,KAAK,GAAG,QAAQ;AACtD,UAAM,MAAM,KAAK,aAAa,UAAU;AACxC,UAAM,WAAW,KAAK,QAAQ,iBAAiB,EAAE,EAAE,MAAM,GAAG,CAAC,KAAK;AAClE,UAAM,OAAO,GAAG,OAAO,aAAa,IAAI,EAAE,IAAI,CAAC,cAAc,IAAI,QAAQ;AACzE,UAAM,QAAQ,KAAK,aAAa,OAAO,MAAM;AAC7C,SAAK,YAAY,UAAU,OAAO,qDAAqD,KAAK,OAAO,CAAC;AAAA,EACtG,QAAQ,0GAA0G,EAAE;AAAA;AAAA,EAEpH,OAAO,gFAAgF,MAAM,IAAI,cAAc,EAAE;AAAA,sEAC7C,IAAI,QAAQ,KAAK,CAAC,4CAA4C,IAAI;AAAA,kCACtG,IAAI,IAAI,CAAC,GAAG,MAAM,UAAU,IAAI,GAAG,CAAC,aAAa,EAAE;AAAA,+CACtC,SAAS,wEAAwE,IAAI,QAAQ,KAAK,CAAC,KAAK,MAAM,KAAK,cAAc,EAAE;AAAA;AAE9K,SAAK,cAAc,mBAAmB,GAAG,iBAAiB,SAAS,MAAM,KAAK,MAAM,aAAa,CAAC,CAAC,CAAC;AACpG,SAAK,cAAc,oBAAoB,GAAG,iBAAiB,SAAS,MAAM,KAAK,MAAM,cAAc,CAAC,CAAC,CAAC;AACtG,SAAK,cAAc,qBAAqB,GAAG,iBAAiB,SAAS,MAAM;AACzE,UAAI,CAAC,KAAK,MAAM,sBAAsB,CAAC,GAAG,IAAI,EAAG;AACjD,YAAM,KAAK,KAAK,aAAa,QAAQ;AACrC,YAAM,IAAK,KAAK,SAAS,eAAe,EAAE,IAAI,SAAS,cAAc,iBAAiB;AACtF,SAAG,OAAO;AAAA,IACZ,CAAC;AAAA,EACH;AACF;AAGA,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA,mIAIgH,OAAO,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mCAM/G,OAAO,QAAQ;AAAA,cACpC,OAAO,QAAQ;AAAA;AAGtB,IAAM,wBAAN,cAAoC,aAAa;AAAA,EACtD,OAAO,qBAAqB,CAAC,QAAQ,WAAW,WAAW;AAAA,EAC3D,IAAI,OAAgB;AAAE,WAAO,KAAK,aAAa,MAAM;AAAA,EAAG;AAAA,EACxD,IAAI,KAAK,GAAY;AAAE,SAAK,gBAAgB,QAAQ,CAAC;AAAA,EAAG;AAAA,EACxD,OAAO,OAAuB;AAAE,SAAK,OAAO,SAAS,CAAC,KAAK;AAAA,EAAM;AAAA,EACjE,SAAe;AACb,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM;AACX,UAAM,IAAI,KAAK;AACf,UAAM,MAAM,KAAK,aAAa,SAAS,MAAM,OAAO,aAAa,cAAc,SAAS,WAAW;AACnG,UAAM,QAAQ,IAAI,CAAC,EAAE,OAAO,QAAQ,MAAM,EAAE,KAAK,GAAG,GAAG,EAAE,MAAM,IAAI,CAAC;AACpE,UAAM,QAAQ,MAAM,IAAI,CAAC,IAAI,MAAM,YAAY,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,SAAS,MAAM,yBAAyB,EAAE,IAAI,UAAU,MAAM,GAAG,OAAO,aAAa,IAAI,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE;AAChO,UAAM,OAAO,SAAS,IAAI,IAAI,8EAAyE;AACvG,SAAK,YAAY,UAAU,UAAU,gFAAgF,IAAI,IAAI,EAAE,IAAI,IAAI,KAAK,kCAAkC,KAAK,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,QAAQ,KAAK,GAAG,IAAI;AAC5O,SAAK,cAAc,QAAQ,EAAG,iBAAiB,SAAS,MAAM,KAAK,OAAO,KAAK,CAAC;AAChF,SAAK,iBAAoC,WAAW,EAAE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,SAAS,CAAC,MAAM;AACtG,QAAE,eAAe;AACjB,YAAM,KAAK,MAAM,OAAO,EAAE,QAAQ,CAAC,CAAC;AACpC,WAAK,OAAO,KAAK;AACjB,eAAS,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;AAAA,IAClC,CAAC,CAAC;AACF,SAAK,cAAc,oBAAoB,GAAG,iBAAiB,SAAS,CAAC,MAAM;AAAE,QAAE,eAAe;AAAG,WAAK,OAAO,KAAK;AAAG,WAAK,MAAM,cAAc,CAAC,CAAC;AAAA,IAAG,CAAC;AAAA,EACtJ;AACF;AAGO,SAAS,kBAAwB;AACtC,MAAI,OAAO,mBAAmB,YAAa;AAC3C,MAAI,CAAC,eAAe,IAAI,cAAc,EAAG,gBAAe,OAAO,gBAAgB,kBAAkB;AACjG,MAAI,CAAC,eAAe,IAAI,iBAAiB,EAAG,gBAAe,OAAO,mBAAmB,qBAAqB;AAC1G,MAAI,CAAC,eAAe,IAAI,iBAAiB,EAAG,gBAAe,OAAO,mBAAmB,qBAAqB;AAC5G;;;AH/MA,SAAS,SAAS,KAAa,OAAe,OAA2C,QAAoD;AAC3I,QAAM,UAAM,qBAAwD,IAAI;AACxE,8BAAU,MAAM;AAAE,oBAAgB;AAAA,EAAG,GAAG,CAAC,CAAC;AAC1C,8BAAU,MAAM;AAAE,QAAI,IAAI,QAAS,KAAI,QAAQ,WAAW,MAAM;AAAA,EAAU,GAAG,CAAC,MAAM,QAAQ,CAAC;AAC7F,8BAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,MAAM,EAAE,iBAAiB,MAAM,YAAY,GAAG,OAAO;AAC3D,UAAM,OAAO,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM;AACvE,YAAM,KAAK,CAAC,MAAa,EAAI,EAAkB,QAAQ,CAAgB;AACvE,SAAG,iBAAiB,MAAM,EAAE;AAC5B,aAAO,MAAM,GAAG,oBAAoB,MAAM,EAAE;AAAA,IAC9C,CAAC;AACD,WAAO,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;AAAA,EACtC,CAAC;AACD,QAAM,IAA6B,EAAE,KAAK,IAAI,MAAM,IAAI,OAAO,MAAM,WAAW,OAAO,MAAM,MAAM;AACnG,MAAI,MAAM,aAAa,UAAa,MAAM,aAAa,OAAQ,GAAE,WAAW,IAAI,OAAO,MAAM,QAAQ;AACrG,MAAI,MAAM,iBAAkB,GAAE,WAAW;AACzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,MAAM,OAAW,GAAE,CAAC,IAAI;AACxE,aAAO,4BAAc,KAAK,GAAG,MAAM,QAAQ;AAC7C;AAQO,SAAS,QAAQ,GAAiB;AACvC,SAAO,SAAS,gBAAgB,GAAG,EAAE,QAAQ,EAAE,UAAU,OAAU,GAAG,EAAE,eAAe,EAAE,UAAU,gBAAgB,EAAE,UAAU,eAAe,EAAE,QAAQ,CAAC;AAC3J;AAcO,SAAS,WAAW,GAAoB;AAC7C,SAAO,SAAS,mBAAmB,GAAG;AAAA,IACpC,OAAO,EAAE;AAAA,IAAO,UAAU,EAAE;AAAA,IAAU,QAAQ,EAAE;AAAA,IAAQ,MAAM,EAAE,OAAO,SAAS;AAAA,IAAW,OAAO,EAAE,UAAU,QAAQ,QAAQ;AAAA,EAChI,GAAG,EAAE,aAAa,EAAE,QAAQ,cAAc,EAAE,SAAS,sBAAsB,EAAE,eAAe,CAAC;AAC/F;AAQO,SAAS,WAAW,GAAoB;AAC7C,SAAO,SAAS,mBAAmB,GAAG,EAAE,MAAM,EAAE,OAAO,KAAK,QAAW,SAAS,EAAE,QAAQ,GAAG,EAAE,cAAc,EAAE,QAAQ,CAAC;AAC1H;","names":[]}
|
package/dist/shell-react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/react.ts"],"sourcesContent":["/**\n * React wrappers for the bApp shell. The elements do the work; these set the manifest property\n * and wire the events.\n *\n * import { BappBar, BappTopbar, BappDrawer } from '@bwalletx/connect/shell/react';\n * <BappTopbar manifest={m} drawer=\"sections\" />\n * <BappDrawer id=\"sections\" manifest={m} />\n * <BappBar manifest={m} active=\"feed\" onNavigate={(d, e) => { e.preventDefault(); router.push(d.path) }} />\n */\nimport { createElement, useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\nimport { defineBappShell } from './elements.js';\nimport type { BappManifest, SlotId } from './manifest.js';\n\nexport interface NavigateDetail { slot?: SlotId; path: string | null; source: 'web' | 'wallet' }\ntype Handler<D> = (detail: D, event: CustomEvent<D>) => void;\n\ninterface Common {\n manifest: BappManifest | null;\n /** \"auto\" (default) detects bWallet; true/false forces. */\n inWallet?: boolean | 'auto';\n /** true = never call location.assign; route in onNavigate. */\n manualNavigation?: boolean;\n onNavigate?: Handler<NavigateDetail>;\n id?: string;\n className?: string;\n style?: CSSProperties;\n /** Slotted content, e.g. <button slot=\"tools\"> or <span slot=\"strip-left\">. */\n children?: ReactNode;\n}\n\nfunction useShell(tag: string, props: Common, attrs: Record<string, string | undefined>, events: Record<string, Handler<
|
|
1
|
+
{"version":3,"sources":["../src/shell/react.ts"],"sourcesContent":["/**\n * React wrappers for the bApp shell. The elements do the work; these set the manifest property\n * and wire the events.\n *\n * import { BappBar, BappTopbar, BappDrawer } from '@bwalletx/connect/shell/react';\n * <BappTopbar manifest={m} drawer=\"sections\" />\n * <BappDrawer id=\"sections\" manifest={m} />\n * <BappBar manifest={m} active=\"feed\" onNavigate={(d, e) => { e.preventDefault(); router.push(d.path) }} />\n */\nimport { createElement, useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\nimport { defineBappShell } from './elements.js';\nimport type { BappManifest, SlotId } from './manifest.js';\n\nexport interface NavigateDetail { slot?: SlotId; path: string | null; source: 'web' | 'wallet' }\ntype Handler<D> = (detail: D, event: CustomEvent<D>) => void;\n\ninterface Common {\n manifest: BappManifest | null;\n /** \"auto\" (default) detects bWallet; true/false forces. */\n inWallet?: boolean | 'auto';\n /** true = never call location.assign; route in onNavigate. */\n manualNavigation?: boolean;\n onNavigate?: Handler<NavigateDetail>;\n id?: string;\n className?: string;\n style?: CSSProperties;\n /** Slotted content, e.g. <button slot=\"tools\"> or <span slot=\"strip-left\">. */\n children?: ReactNode;\n}\n\nfunction useShell(tag: string, props: Common, attrs: Record<string, string | undefined>, events: Record<string, Handler<never> | undefined>) {\n const ref = useRef<HTMLElement & { manifest: BappManifest | null }>(null);\n useEffect(() => { defineBappShell(); }, []);\n useEffect(() => { if (ref.current) ref.current.manifest = props.manifest; }, [props.manifest]);\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const all = { 'bapp-navigate': props.onNavigate, ...events };\n const subs = Object.entries(all).filter(([, h]) => h).map(([type, h]) => {\n const fn = (e: Event) => h!((e as CustomEvent).detail, e as CustomEvent);\n el.addEventListener(type, fn);\n return () => el.removeEventListener(type, fn);\n });\n return () => subs.forEach((u) => u());\n });\n const a: Record<string, unknown> = { ref, id: props.id, class: props.className, style: props.style };\n if (props.inWallet !== undefined && props.inWallet !== 'auto') a['in-wallet'] = String(props.inWallet);\n if (props.manualNavigation) a.navigate = 'manual';\n for (const [k, v] of Object.entries(attrs)) if (v !== undefined) a[k] = v;\n return createElement(tag, a, props.children);\n}\n\nexport interface BappBarProps extends Common {\n active?: SlotId | null;\n onActive?: Handler<{ slot: SlotId }>;\n onBPress?: Handler<{ path: string | null }>;\n onBHold?: Handler<{ phase: 'start' | 'end' }>;\n}\nexport function BappBar(p: BappBarProps) {\n return useShell('bwalletx-bar', p, { active: p.active ?? undefined }, { 'bapp-active': p.onActive, 'bapp-b-press': p.onBPress, 'bapp-b-hold': p.onBHold });\n}\n\nexport interface BappTopbarProps extends Common {\n title?: string;\n subtitle?: string;\n /** id of the <bwalletx-drawer> the app icon opens. */\n drawer?: string;\n /** Show ☰ on the web too (it always shows in the wallet). */\n menu?: boolean;\n strip?: boolean;\n onMenu?: Handler<object>;\n onClose?: Handler<object>;\n onDrawerToggle?: Handler<object>;\n}\nexport function BappTopbar(p: BappTopbarProps) {\n return useShell('bwalletx-topbar', p, {\n title: p.title, subtitle: p.subtitle, drawer: p.drawer, menu: p.menu ? 'true' : undefined, strip: p.strip === false ? 'off' : undefined,\n }, { 'bapp-menu': p.onMenu, 'bapp-close': p.onClose, 'bapp-drawer-toggle': p.onDrawerToggle });\n}\n\nexport interface BappDrawerProps extends Common {\n open?: boolean;\n /** Path to highlight; default location.pathname. */\n current?: string;\n onClose?: Handler<object>;\n}\nexport function BappDrawer(p: BappDrawerProps) {\n return useShell('bwalletx-drawer', p, { open: p.open ? '' : undefined, current: p.current }, { 'bapp-close': p.onClose });\n}\n\nexport type { BappManifest, SlotId } from './manifest.js';\n"],"mappings":";;;;;AASA,SAAS,eAAe,WAAW,cAAkD;AAqBrF,SAAS,SAAS,KAAa,OAAe,OAA2C,QAAoD;AAC3I,QAAM,MAAM,OAAwD,IAAI;AACxE,YAAU,MAAM;AAAE,oBAAgB;AAAA,EAAG,GAAG,CAAC,CAAC;AAC1C,YAAU,MAAM;AAAE,QAAI,IAAI,QAAS,KAAI,QAAQ,WAAW,MAAM;AAAA,EAAU,GAAG,CAAC,MAAM,QAAQ,CAAC;AAC7F,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,MAAM,EAAE,iBAAiB,MAAM,YAAY,GAAG,OAAO;AAC3D,UAAM,OAAO,OAAO,QAAQ,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM;AACvE,YAAM,KAAK,CAAC,MAAa,EAAI,EAAkB,QAAQ,CAAgB;AACvE,SAAG,iBAAiB,MAAM,EAAE;AAC5B,aAAO,MAAM,GAAG,oBAAoB,MAAM,EAAE;AAAA,IAC9C,CAAC;AACD,WAAO,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;AAAA,EACtC,CAAC;AACD,QAAM,IAA6B,EAAE,KAAK,IAAI,MAAM,IAAI,OAAO,MAAM,WAAW,OAAO,MAAM,MAAM;AACnG,MAAI,MAAM,aAAa,UAAa,MAAM,aAAa,OAAQ,GAAE,WAAW,IAAI,OAAO,MAAM,QAAQ;AACrG,MAAI,MAAM,iBAAkB,GAAE,WAAW;AACzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,MAAM,OAAW,GAAE,CAAC,IAAI;AACxE,SAAO,cAAc,KAAK,GAAG,MAAM,QAAQ;AAC7C;AAQO,SAAS,QAAQ,GAAiB;AACvC,SAAO,SAAS,gBAAgB,GAAG,EAAE,QAAQ,EAAE,UAAU,OAAU,GAAG,EAAE,eAAe,EAAE,UAAU,gBAAgB,EAAE,UAAU,eAAe,EAAE,QAAQ,CAAC;AAC3J;AAcO,SAAS,WAAW,GAAoB;AAC7C,SAAO,SAAS,mBAAmB,GAAG;AAAA,IACpC,OAAO,EAAE;AAAA,IAAO,UAAU,EAAE;AAAA,IAAU,QAAQ,EAAE;AAAA,IAAQ,MAAM,EAAE,OAAO,SAAS;AAAA,IAAW,OAAO,EAAE,UAAU,QAAQ,QAAQ;AAAA,EAChI,GAAG,EAAE,aAAa,EAAE,QAAQ,cAAc,EAAE,SAAS,sBAAsB,EAAE,eAAe,CAAC;AAC/F;AAQO,SAAS,WAAW,GAAoB;AAC7C,SAAO,SAAS,mBAAmB,GAAG,EAAE,MAAM,EAAE,OAAO,KAAK,QAAW,SAAS,EAAE,QAAQ,GAAG,EAAE,cAAc,EAAE,QAAQ,CAAC;AAC1H;","names":[]}
|
|
@@ -232,7 +232,7 @@ interface HandCashAdapterOptions {
|
|
|
232
232
|
*/
|
|
233
233
|
declare function handcashAdapter(o: HandCashAdapterOptions): WalletOption;
|
|
234
234
|
interface DiscoverWalletsOptions {
|
|
235
|
-
/**
|
|
235
|
+
/** Longest wait for BRC-100 announcements, ms. Default 300; ends early once a wallet has announced. */
|
|
236
236
|
ms?: number;
|
|
237
237
|
/**
|
|
238
238
|
* Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301).
|
|
@@ -232,7 +232,7 @@ interface HandCashAdapterOptions {
|
|
|
232
232
|
*/
|
|
233
233
|
declare function handcashAdapter(o: HandCashAdapterOptions): WalletOption;
|
|
234
234
|
interface DiscoverWalletsOptions {
|
|
235
|
-
/**
|
|
235
|
+
/** Longest wait for BRC-100 announcements, ms. Default 300; ends early once a wallet has announced. */
|
|
236
236
|
ms?: number;
|
|
237
237
|
/**
|
|
238
238
|
* Also probe desktop wallets on localhost (Metanet / json-api on 3321, Cicada on 3301).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bwalletx/connect",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Sign in with bWalletX or any BRC-100 wallet: button, React provider, Next.js sessions, nonce stores, pay/tip helpers, payment verification, wallet picker, and the bApp shell",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -129,14 +129,6 @@
|
|
|
129
129
|
"engines": {
|
|
130
130
|
"node": ">=20"
|
|
131
131
|
},
|
|
132
|
-
"scripts": {
|
|
133
|
-
"build": "tsup",
|
|
134
|
-
"typecheck": "tsc --noEmit",
|
|
135
|
-
"test": "vitest run",
|
|
136
|
-
"test:live": "LIVE_RELAY=1 vitest run test/live-relay.test.ts",
|
|
137
|
-
"prepublishOnly": "pnpm typecheck && pnpm lint && pnpm test && pnpm build",
|
|
138
|
-
"lint": "eslint src test"
|
|
139
|
-
},
|
|
140
132
|
"peerDependencies": {
|
|
141
133
|
"@bsv/sdk": ">=2.2.0 <4",
|
|
142
134
|
"next": ">=14",
|
|
@@ -167,5 +159,12 @@
|
|
|
167
159
|
},
|
|
168
160
|
"dependencies": {
|
|
169
161
|
"qrcode-generator": "^1.5.2"
|
|
162
|
+
},
|
|
163
|
+
"scripts": {
|
|
164
|
+
"build": "tsup",
|
|
165
|
+
"typecheck": "tsc --noEmit",
|
|
166
|
+
"test": "vitest run",
|
|
167
|
+
"test:live": "LIVE_RELAY=1 vitest run test/live-relay.test.ts",
|
|
168
|
+
"lint": "eslint src test"
|
|
170
169
|
}
|
|
171
|
-
}
|
|
170
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/discover.ts","../src/in-order.ts","../src/pair.ts","../src/protocol.ts","../src/signin.ts","../src/access-client.ts","../src/element.ts"],"sourcesContent":["import { WalletClient, XDM, type WalletInterface } from '@bsv/sdk';\n\n/** Ids bWalletX announces in BRC-100 wallet discovery. */\nexport const RDNS_EXTENSION = 'com.bwalletx.extension';\nexport const RDNS_MOBILE = 'space.bwallet.mobile';\nexport const BWALLETX_RDNS = [RDNS_EXTENSION, RDNS_MOBILE] as const;\n\nexport type FoundBy = 'extension' | 'in-app';\n\nexport interface FoundWallet {\n wallet: WalletInterface;\n /** 'extension' for the Chrome extension; 'in-app' inside the bWalletX app's own browser. */\n method: FoundBy;\n rdns: string | null;\n}\n\n/**\n * Like findBwalletX, but also says which bWalletX answered.\n * Order: a BRC-100 announcement with a bWalletX rdns; then `window.CWI` (set by the in-app\n * browser, and by older builds that do not announce); otherwise null.\n */\nexport function discoverBwalletX(ms = 1200): Promise<FoundWallet | null> {\n if (typeof window === 'undefined') return Promise.resolve(null);\n return new Promise((resolve) => {\n let done = false;\n const finish = (r: FoundWallet | null) => {\n if (done) return;\n done = true;\n window.removeEventListener('brc100:announceWallet', onAnnounce);\n resolve(r);\n };\n const onAnnounce = (e: Event) => {\n const d = (e as CustomEvent).detail as { info?: { rdns?: string }; wallet?: WalletInterface } | undefined;\n const rdns = d?.info?.rdns ?? '';\n if (d?.wallet && (BWALLETX_RDNS as readonly string[]).includes(rdns)) {\n finish({ wallet: d.wallet, method: rdns === RDNS_EXTENSION ? 'extension' : 'in-app', rdns });\n }\n };\n window.addEventListener('brc100:announceWallet', onAnnounce);\n window.dispatchEvent(new Event('brc100:requestWallet'));\n setTimeout(() => {\n const cwi = (window as unknown as { CWI?: WalletInterface }).CWI;\n if (cwi) return finish({ wallet: cwi, method: 'in-app', rdns: null });\n // Inside bWalletX's in-wallet frame nothing is injected into the page: the wallet answers BRC-100 XDM\n // (postMessage to the parent) instead. Only tried when framed, and only kept if the parent answers.\n if (!isFramed()) return finish(null);\n void probeXdm(ms).then((w) => finish(w ? { wallet: w, method: 'in-app', rdns: null } : null));\n }, ms);\n });\n}\n\n/**\n * Find bWalletX in this page: the extension on a computer, or the wallet itself when the page is\n * open in bWalletX's in-app browser. Resolves null if nothing answered within `ms`.\n */\nexport async function findBwalletX(ms = 1200): Promise<WalletInterface | null> {\n return (await discoverBwalletX(ms))?.wallet ?? null;\n}\n\nconst isFramed = (): boolean => {\n try {\n return window.parent !== window;\n } catch {\n return true;\n }\n};\n\n/**\n * The only parents that may answer as bWalletX over XDM (security audit 25 Oct 2026). Without this, any page that\n * framed a site using this package became \"the wallet\": it saw every call (payments, sign-in challenges) and could\n * answer with a fake txid. The wallet's own frames: the web wallet, bwallet.space, the iOS/Android app shells and\n * the Chrome extension. Payments must still be checked on your server (verifyPayment), never trusted from the page.\n */\nexport const BWALLETX_PARENT_ORIGINS: readonly string[] = [\n 'https://web.bwalletx.com',\n 'https://desktop.bwalletx.com',\n 'https://bwalletx.com',\n 'https://www.bwallet.space',\n 'https://bwallet.space',\n 'capacitor://localhost', // iOS app shell\n 'https://localhost', // Android app shell\n 'tauri://localhost', // bWalletX Desktop (macOS)\n 'https://tauri.localhost', // bWalletX Desktop (Windows)\n 'chrome-extension://bombmmcjghjdlbmcclnhjodgcelnehgl', // bWalletX for Chrome (fixed id)\n];\n\n/** The parent frame's origin, as the browser reports it (ancestorOrigins, else the referrer), or null. */\nexport const parentOrigin = (): string | null => {\n try {\n const anc = (window.location as Location & { ancestorOrigins?: DOMStringList }).ancestorOrigins;\n if (anc && anc.length > 0) return anc[0] ?? null;\n if (!document.referrer) return null;\n // Not URL.origin: it is \"null\" for non-special schemes (capacitor:, chrome-extension:) in some engines.\n const u = new URL(document.referrer);\n return `${u.protocol}//${u.host}`;\n } catch {\n return null;\n }\n};\n\n/**\n * The parent frame's wallet over BRC-100 XDM, or null when the parent is not bWalletX (BWALLETX_PARENT_ORIGINS) or\n * does not answer within `ms`. Messages go to that exact origin and only its answers are taken.\n */\nexport async function probeXdm(ms = 1200, allowed: readonly string[] = BWALLETX_PARENT_ORIGINS): Promise<WalletInterface | null> {\n const origin = parentOrigin();\n if (!origin || !allowed.includes(origin)) return null;\n try {\n const w = new WalletClient(new XDM(origin) as unknown as WalletInterface);\n const ok = await Promise.race([\n w.isAuthenticated({}).then(\n () => true,\n () => false,\n ),\n new Promise<boolean>((r) => setTimeout(() => r(false), ms)),\n ]);\n return ok ? w : null;\n } catch {\n return null;\n }\n}\n","/**\n * Wrap an async frame handler so frames are handled one at a time, in arrival order.\n *\n * WebSocket `onmessage` does not wait for an async handler. The phone sends its plain `hello`\n * and then, as soon as the user taps Connect, a sealed `ready`. If the handler for `hello` is\n * still deriving the session key when `ready` arrives, a naive handler sees no key yet and drops\n * `ready`, so pairing never finishes. Chaining every frame onto one promise fixes that.\n */\nexport function inOrder<T>(handle: (frame: T) => Promise<void> | void, onError?: (e: unknown) => void) {\n let queue: Promise<void> = Promise.resolve();\n return (frame: T): Promise<void> => {\n queue = queue.then(() => handle(frame)).catch((e) => onError?.(e));\n return queue;\n };\n}\n","import { PrivateKey, type WalletInterface } from '@bsv/sdk';\nimport { inOrder } from './in-order.js';\nimport {\n DEFAULT_RELAY,\n PAIR_VERSION,\n QR_LIFETIME_S,\n Sealer,\n deriveSession,\n isSealed,\n newChannel,\n pairUrl,\n relaySocketUrl,\n type HelloFrame,\n type PairMessage,\n} from './protocol.js';\n\n/** localStorage-shaped storage. Methods may be sync or async. */\nexport interface PairStorage {\n getItem(key: string): string | null | Promise<string | null>;\n setItem(key: string, value: string): void | Promise<void>;\n removeItem(key: string): void | Promise<void>;\n}\n\n/** The minimum of the browser WebSocket API this package uses (the `ws` package also fits). */\nexport interface SocketLike {\n readonly readyState: number;\n onopen: ((ev: unknown) => void) | null;\n onmessage: ((ev: { data: unknown }) => void) | null;\n onerror: ((ev: unknown) => void) | null;\n onclose: ((ev: unknown) => void) | null;\n send(data: string): void;\n close(): void;\n}\nexport type SocketFactory = (url: string) => SocketLike;\n\nexport interface PairConnectionOptions {\n /** Where the pairing is kept so it survives a reload. Default: localStorage, else memory only. */\n storage?: PairStorage | null;\n /** Storage key. Default 'bwalletx.connect.pairing.v1'. */\n storageKey?: string;\n /** Opens a socket. Default: the global WebSocket. Pass one to run in Node or to test. */\n socket?: SocketFactory;\n /** Per-call timeout in ms. Default 120000. */\n callTimeoutMs?: number;\n}\n\nexport interface PairingOptions extends PairConnectionOptions {\n /** Show this link as a QR code, and as text to copy (web.bwalletx.com takes it pasted). */\n onLink: (link: string) => void;\n /** Show this 4-digit code. The wallet shows the same one; the user checks they match. */\n onCode: (code: string) => void;\n signal?: AbortSignal;\n /** Relay host. Default relay.bwallet.space. */\n relay?: string;\n /** This page's origin, as the relay will see it. Default location.origin. */\n origin?: string;\n}\n\n/** A BRC-100 wallet whose every call goes, end-to-end encrypted, to the paired phone or web wallet. */\nexport type PairedWallet = WalletInterface & {\n /** Close the relay connection. The pairing stays stored; the next call reconnects. */\n close(): void;\n /** Tell the wallet to unpair, close, and delete the stored pairing. */\n forget(): Promise<void>;\n};\n\ninterface StoredPairing {\n v: 1;\n c: string; // channel\n r: string; // relay host\n s: string; // our pairing private key (hex). Random; not a wallet key.\n k: string; // the wallet's pairing public key\n sent: number;\n lastSeen: number;\n pairedAt: number;\n}\n\nconst DEFAULT_KEY = 'bwalletx.connect.pairing.v1';\nconst OPEN = 1;\n\nfunction memoryStorage(): PairStorage {\n const m = new Map<string, string>();\n return { getItem: (k) => m.get(k) ?? null, setItem: (k, v) => void m.set(k, v), removeItem: (k) => void m.delete(k) };\n}\n\nfunction defaultStorage(): PairStorage {\n try {\n if (typeof localStorage !== 'undefined') return localStorage;\n } catch {\n /* blocked storage */\n }\n return memoryStorage();\n}\n\nfunction defaultSocket(): SocketFactory {\n return (url) => {\n if (typeof WebSocket === 'undefined') throw new Error('No WebSocket here. Pass options.socket.');\n return new WebSocket(url) as unknown as SocketLike;\n };\n}\n\nfunction parseStored(raw: string | null): StoredPairing | null {\n if (!raw) return null;\n try {\n const p = JSON.parse(raw) as Partial<StoredPairing>;\n const n = (x: unknown) => Number.isInteger(x) && (x as number) >= 0;\n if (p.v !== 1) return null;\n if (typeof p.c !== 'string' || !/^[A-Za-z0-9_-]{22}$/.test(p.c)) return null;\n if (typeof p.r !== 'string' || !/^[a-z0-9.-]+(:\\d+)?$/i.test(p.r)) return null;\n if (typeof p.s !== 'string' || !/^[0-9a-f]{64}$/i.test(p.s)) return null;\n if (typeof p.k !== 'string' || !/^0[23][0-9a-f]{64}$/i.test(p.k)) return null;\n if (!n(p.sent) || !n(p.lastSeen) || !n(p.pairedAt)) return null;\n return p as StoredPairing;\n } catch {\n return null;\n }\n}\n\nclass Link {\n private ws: SocketLike | null = null;\n private opening: Promise<void> | null = null;\n private waiting = new Map<string, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>();\n private forgotten = false;\n\n constructor(\n private p: StoredPairing,\n private sealer: Sealer,\n private storage: PairStorage,\n private key: string,\n private socket: SocketFactory,\n private timeoutMs: number,\n ) {}\n\n /** Take over a socket that is already open (the one pairing used). */\n adopt(ws: SocketLike) {\n this.ws = ws;\n this.wire(ws);\n }\n\n private wire(ws: SocketLike) {\n ws.onmessage = inOrder((ev: { data: unknown }) => this.onFrame(String(ev.data)));\n ws.onerror = null;\n ws.onclose = () => {\n if (this.ws === ws) this.ws = null;\n this.failAll(new Error('The connection to bWalletX closed'));\n };\n }\n\n private persist() {\n if (this.forgotten) return;\n this.p = { ...this.p, ...this.sealer.counters };\n void Promise.resolve(this.storage.setItem(this.key, JSON.stringify(this.p))).catch(() => {});\n }\n\n private failAll(e: Error) {\n for (const w of this.waiting.values()) {\n clearTimeout(w.timer);\n w.reject(e);\n }\n this.waiting.clear();\n }\n\n private async ensure() {\n if (this.forgotten) throw new Error('This pairing was removed. Pair again.');\n if (this.ws && this.ws.readyState === OPEN) return;\n this.opening ??= this.reopen().finally(() => (this.opening = null));\n await this.opening;\n }\n\n private reopen() {\n // A fresh expiry lets the relay recreate the channel if it was dropped; the wallet rejoins on its own.\n const ws = this.socket(relaySocketUrl(this.p.r, this.p.c, 'site', Math.floor(Date.now() / 1000) + QR_LIFETIME_S - 10));\n return new Promise<void>((resolve, reject) => {\n ws.onopen = () => {\n this.ws = ws;\n this.wire(ws);\n resolve();\n };\n ws.onerror = () => reject(new Error('Could not reach the bWalletX pairing service'));\n ws.onclose = () => reject(new Error('Could not reach the bWalletX pairing service'));\n });\n }\n\n private async onFrame(raw: string) {\n let f: unknown;\n try {\n f = JSON.parse(raw);\n } catch {\n return;\n }\n const t = (f as { t?: string; error?: string }).t;\n if (t === 'relay' && (f as { error?: string }).error) {\n return this.failAll(new Error(`bWalletX pairing service: ${(f as { error: string }).error}`));\n }\n if (!isSealed(f)) return;\n const msg = await this.sealer.open(f);\n if (!msg) return;\n this.persist();\n if (msg.t === 'res') {\n const w = this.waiting.get(msg.id);\n if (!w) return;\n clearTimeout(w.timer);\n this.waiting.delete(msg.id);\n if (msg.error) w.reject(Object.assign(new Error(msg.error.message), { code: msg.error.code }));\n else w.resolve(msg.result);\n } else if (msg.t === 'close') {\n this.drop();\n this.failAll(new Error('bWalletX disconnected this site. Pair again.'));\n }\n }\n\n async call(action: string, params: unknown): Promise<unknown> {\n await this.ensure();\n const ws = this.ws!;\n const id = `${action}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n const result = new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (this.waiting.delete(id)) reject(new Error('No answer from bWalletX. Open it, unlocked, and approve.'));\n }, this.timeoutMs);\n this.waiting.set(id, { resolve, reject, timer });\n });\n ws.send(JSON.stringify(await this.sealer.seal({ t: 'req', id, action, params } satisfies PairMessage)));\n this.persist();\n return result;\n }\n\n close() {\n const ws = this.ws;\n this.ws = null;\n ws?.close();\n }\n\n private drop() {\n this.forgotten = true;\n void Promise.resolve(this.storage.removeItem(this.key)).catch(() => {});\n this.close();\n }\n\n async forget() {\n if (this.forgotten) return;\n try {\n if (this.ws && this.ws.readyState === OPEN) {\n this.ws.send(JSON.stringify(await this.sealer.seal({ t: 'close', reason: 'site unpaired' })));\n }\n } catch {\n /* best effort */\n }\n this.drop();\n }\n\n wallet(): PairedWallet {\n return new Proxy({} as PairedWallet, {\n get: (_t, method) => {\n if (method === 'close') return () => this.close();\n if (method === 'forget') return () => this.forget();\n if (typeof method !== 'string' || method === 'then' || method === 'toJSON') return undefined;\n return (params: unknown) => this.call(method, params ?? {});\n },\n });\n }\n}\n\n/**\n * Pair with bWalletX on a phone (scan the QR) or web.bwalletx.com (paste the link).\n * Resolves with a wallet once the user taps Connect. The pairing is stored (see `storage`) so\n * `restorePairing()` can reconnect after a reload without a new QR.\n */\nexport function pairBwalletX(opts: PairingOptions): Promise<PairedWallet> {\n const storage = opts.storage === undefined ? defaultStorage() : (opts.storage ?? memoryStorage());\n const key = opts.storageKey ?? DEFAULT_KEY;\n const socket = opts.socket ?? defaultSocket();\n const relay = opts.relay ?? DEFAULT_RELAY;\n const origin = opts.origin ?? (typeof location !== 'undefined' ? location.origin : '');\n if (!origin) return Promise.reject(new Error('pairBwalletX needs options.origin outside a browser'));\n\n const siteKey = PrivateKey.fromRandom(); // a throwaway pairing key, not a wallet key\n const channel = newChannel();\n const expiry = Math.floor(Date.now() / 1000) + QR_LIFETIME_S;\n\n return new Promise<PairedWallet>((resolve, reject) => {\n if (opts.signal?.aborted) return reject(new Error('cancelled'));\n let ws: SocketLike;\n try {\n ws = socket(relaySocketUrl(relay, channel, 'site', expiry));\n } catch (e) {\n return reject(e);\n }\n let sealer: Sealer | null = null;\n let walletKey = '';\n let settled = false;\n const fail = (e: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n ws.close();\n reject(e);\n };\n const timer = setTimeout(() => fail(new Error('The pairing code expired. Show a new one.')), QR_LIFETIME_S * 1000);\n opts.signal?.addEventListener('abort', () => fail(new Error('cancelled')));\n ws.onerror = () => fail(new Error('Could not reach the bWalletX pairing service'));\n ws.onclose = () => fail(new Error('The connection to bWalletX closed before pairing finished'));\n ws.onmessage = inOrder(async (ev: { data: unknown }) => {\n if (settled) return;\n let f: unknown;\n try {\n f = JSON.parse(String(ev.data));\n } catch {\n return;\n }\n if ((f as HelloFrame).t === 'hello' && !sealer) {\n walletKey = (f as HelloFrame).k;\n const s = await deriveSession(siteKey, walletKey, channel);\n sealer = new Sealer(s.key, 'site');\n opts.onCode(s.code);\n return;\n }\n if (!sealer || !isSealed(f)) return;\n const msg = await sealer.open(f);\n if (!msg) return;\n if (msg.t === 'ready') {\n settled = true;\n clearTimeout(timer);\n const p: StoredPairing = { v: 1, c: channel, r: relay, s: siteKey.toHex(), k: walletKey, ...sealer.counters, pairedAt: Date.now() };\n await Promise.resolve(storage.setItem(key, JSON.stringify(p))).catch(() => {});\n const link = new Link(p, sealer, storage, key, socket, opts.callTimeoutMs ?? 120_000);\n link.adopt(ws);\n resolve(link.wallet());\n } else if (msg.t === 'close') {\n fail(new Error('Pairing was declined in bWalletX'));\n }\n });\n opts.onLink(pairUrl({ v: PAIR_VERSION, r: relay, c: channel, k: siteKey.toPublicKey().toString(), o: origin, e: expiry }));\n });\n}\n\n/**\n * The wallet from a pairing stored by an earlier `pairBwalletX`, or null if there is none.\n * It connects on the first call. The wallet must still have this site paired.\n */\nexport async function restorePairing(opts: PairConnectionOptions = {}): Promise<PairedWallet | null> {\n const storage = opts.storage === undefined ? defaultStorage() : (opts.storage ?? memoryStorage());\n const key = opts.storageKey ?? DEFAULT_KEY;\n const p = parseStored(await Promise.resolve(storage.getItem(key)).catch(() => null));\n if (!p) return null;\n const { key: aes } = await deriveSession(PrivateKey.fromHex(p.s), p.k, p.c);\n const sealer = new Sealer(aes, 'site');\n sealer.restore({ sent: p.sent, lastSeen: p.lastSeen });\n return new Link(p, sealer, storage, key, opts.socket ?? defaultSocket(), opts.callTimeoutMs ?? 120_000).wallet();\n}\n\n/** Delete the stored pairing without telling the wallet. */\nexport async function clearPairing(opts: Pick<PairConnectionOptions, 'storage' | 'storageKey'> = {}) {\n const storage = opts.storage === undefined ? defaultStorage() : (opts.storage ?? memoryStorage());\n await Promise.resolve(storage.removeItem(opts.storageKey ?? DEFAULT_KEY)).catch(() => {});\n}\n","/**\n * Phone pairing protocol (tokenblaster.lol docs/wallet-connect.md §4), shared by the site side and\n * bWallet. Kept dependency-light (@bsv/sdk + WebCrypto) so the same file can be copied into sites\n * until it ships as @b0ase/wallet.\n *\n * QR: https://www.bwallet.space/pair?v=1&r=<relay host>&c=<channel>&k=<site pubkey>&o=<origin>&e=<expiry>\n * key: ECDH(secp256k1) → HKDF-SHA256(salt = channel bytes, info = \"bwallet-pair-v1\") → AES-256-GCM\n * frame: { s: seq, n: nonce b64, d: ciphertext b64 }, AAD = \"<sender role>|<s>\", s strictly increasing\n * code: first 2 bytes of SHA-256(key bits) → 4 digits, shown on both screens\n */\nimport { PrivateKey, PublicKey } from '@bsv/sdk';\n\nexport const PAIR_VERSION = '1';\nexport const PAIR_INFO = 'bwallet-pair-v1';\n// www: the bare domain redirects there, and app-link checks (Apple's AASA) don't follow redirects.\nexport const PAIR_HOST = 'www.bwallet.space';\nconst PAIR_HOSTS = new Set([PAIR_HOST, 'bwallet.space']);\nexport const DEFAULT_RELAY = 'relay.bwallet.space';\nexport const QR_LIFETIME_S = 120;\n\nexport type Role = 'site' | 'wallet';\nexport type WalletInfo = { name: string; icon?: string; rdns: string };\n\nexport type PairLink = { v: string; r: string; c: string; k: string; o: string; e: number };\n\n/** Plaintext messages inside encrypted frames. */\nexport type PairMessage =\n | { t: 'req'; id: string; action: string; params: unknown }\n | { t: 'res'; id: string; result?: unknown; error?: { code: string; message: string } }\n | { t: 'ready' }\n | { t: 'ping' }\n | { t: 'close'; reason: string };\n\n/** Unencrypted frames: from the relay, and the phone's hello (its key isn't shared yet). */\nexport type RelayFrame = { t: 'relay'; verifiedOrigin?: string; peer?: 'joined' | 'left'; role?: Role; error?: string };\nexport type HelloFrame = { t: 'hello'; k: string; info: WalletInfo };\nexport type SealedFrame = { s: number; n: string; d: string };\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n// Chunked: String.fromCharCode(...bytes) overflows the call stack on large frames (signed transactions).\nconst binary = (bytes: Uint8Array) => {\n let s = '';\n for (let i = 0; i < bytes.length; i += 0x8000) s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));\n return s;\n};\nexport const b64url = (bytes: Uint8Array) =>\n btoa(binary(bytes)).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\nexport const fromB64url = (s: string) => {\n const t = s.replace(/-/g, '+').replace(/_/g, '/');\n const bin = atob(t + '='.repeat((4 - (t.length % 4)) % 4));\n return Uint8Array.from(bin, (ch) => ch.charCodeAt(0));\n};\nconst b64 = (bytes: Uint8Array) => btoa(binary(bytes));\nconst fromB64 = (s: string) => Uint8Array.from(atob(s), (ch) => ch.charCodeAt(0));\n\nexport const newChannel = () => b64url(crypto.getRandomValues(new Uint8Array(16)));\n\nexport function pairUrl(link: PairLink): string {\n const q = new URLSearchParams({ v: link.v, r: link.r, c: link.c, k: link.k, o: link.o, e: String(link.e) });\n return `https://${PAIR_HOST}/pair?${q}`;\n}\n\n/** Parse a scanned QR (https link or bwallet:// fallback). Throws on anything else. */\nexport function parsePairUrl(text: string, nowS = Math.floor(Date.now() / 1000)): PairLink {\n const u = new URL(text.trim());\n const ok =\n (u.protocol === 'https:' && PAIR_HOSTS.has(u.host) && u.pathname === '/pair') ||\n (u.protocol === 'bwallet:' && /pair/.test(u.href));\n if (!ok) throw new Error('Not a bWallet pairing code');\n const g = (k: string) => u.searchParams.get(k) ?? '';\n const link: PairLink = { v: g('v'), r: g('r'), c: g('c'), k: g('k'), o: g('o'), e: Number(g('e')) };\n if (link.v !== PAIR_VERSION) throw new Error('This pairing code needs a newer bWallet');\n if (!/^[A-Za-z0-9_-]{22}$/.test(link.c)) throw new Error('Bad pairing code');\n if (!/^0[23][0-9a-f]{64}$/i.test(link.k)) throw new Error('Bad pairing code');\n if (!/^[a-z0-9.-]+(:\\d+)?$/i.test(link.r)) throw new Error('Bad pairing code');\n const o = new URL(link.o);\n if (o.origin !== link.o) throw new Error('Bad pairing code');\n if (!Number.isFinite(link.e) || link.e < nowS) throw new Error('This code has expired. Ask the site for a new one.');\n return link;\n}\n\nexport const relaySocketUrl = (relay: string, channel: string, role: Role, expiry?: number) => {\n const local = /^(localhost|127\\.0\\.0\\.1)(:\\d+)?$/.test(relay);\n const q = new URLSearchParams({ role });\n if (expiry) q.set('e', String(expiry));\n return `${local ? 'ws' : 'wss'}://${relay}/v1/c/${channel}?${q}`;\n};\n\n/** Shared AES key + 4-digit comparison code from my private key and their public key. */\nexport async function deriveSession(mine: PrivateKey, theirs: string, channel: string) {\n const point = mine.deriveSharedSecret(PublicKey.fromString(theirs));\n const ikm = new Uint8Array(point.encode(true) as number[]);\n const base = await crypto.subtle.importKey('raw', ikm, 'HKDF', false, ['deriveBits']);\n const bits = new Uint8Array(\n await crypto.subtle.deriveBits(\n { name: 'HKDF', hash: 'SHA-256', salt: fromB64url(channel), info: enc.encode(PAIR_INFO) },\n base,\n 256,\n ),\n );\n const key = await crypto.subtle.importKey('raw', bits, 'AES-GCM', false, ['encrypt', 'decrypt']);\n const h = new Uint8Array(await crypto.subtle.digest('SHA-256', bits));\n const code = String(((h[0] << 8) | h[1]) % 10000).padStart(4, '0');\n return { key, code };\n}\n\n/** One direction-aware encrypted pipe. `me` is the sender role used in the AAD. */\nexport class Sealer {\n private sent = 0;\n private lastSeen = 0;\n constructor(\n private key: CryptoKey,\n private me: Role,\n ) {}\n\n async seal(msg: PairMessage): Promise<SealedFrame> {\n const s = ++this.sent;\n const n = crypto.getRandomValues(new Uint8Array(12));\n const ct = new Uint8Array(\n await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: n, additionalData: enc.encode(`${this.me}|${s}`) },\n this.key,\n enc.encode(JSON.stringify(msg)),\n ),\n );\n return { s, n: b64(n), d: b64(ct) };\n }\n\n /** Decrypts a frame from the other side; null for replays, reflections or tampering. */\n async open(f: SealedFrame): Promise<PairMessage | null> {\n if (!Number.isInteger(f.s) || f.s <= this.lastSeen) return null;\n const from: Role = this.me === 'site' ? 'wallet' : 'site';\n try {\n const pt = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: fromB64(f.n), additionalData: enc.encode(`${from}|${f.s}`) },\n this.key,\n fromB64(f.d),\n );\n this.lastSeen = f.s;\n return JSON.parse(dec.decode(pt)) as PairMessage;\n } catch {\n return null;\n }\n }\n\n /** For resuming a session after a reload: continue counters where they were. */\n get counters() {\n return { sent: this.sent, lastSeen: this.lastSeen };\n }\n restore(c: { sent: number; lastSeen: number }) {\n this.sent = c.sent;\n this.lastSeen = c.lastSeen;\n }\n}\n\nexport const isSealed = (x: unknown): x is SealedFrame =>\n !!x && typeof x === 'object' && 'd' in x && 'n' in x && 's' in x;\n","import type { WalletInterface, WalletProtocol } from '@bsv/sdk';\nimport { discoverBwalletX } from './discover.js';\nimport { pairBwalletX, restorePairing, type PairingOptions } from './pair.js';\n\n/** Security level 2, a protocol name used for nothing else. */\nexport const LOGIN_PROTOCOL: WalletProtocol = [2, 'bwallet sign in'];\n\n/** What your server's challenge route returns (createChallenge from '@bwalletx/connect/server'). */\nexport interface Challenge {\n nonce: string;\n message: string;\n keyID: string;\n protocolID?: WalletProtocol;\n}\n\nexport type SignInMethod = 'extension' | 'in-app' | 'pairing' | 'wallet';\n\nexport interface SignInOptions {\n /** Ask your server for a challenge for this identity key. */\n challenge: (identityKey: string) => Promise<Challenge>;\n /** Use this wallet instead of looking for one. `method` is then 'wallet'. */\n wallet?: WalletInterface;\n /** How to show the QR and code if pairing is needed. Without it, pairing is not tried. */\n pairing?: PairingOptions;\n /** Reuse a pairing stored by an earlier sign-in before showing a new QR. Default true. */\n reusePairing?: boolean;\n /** How long to wait for the extension or in-app browser to answer, in ms. Default 1200. */\n discoverMs?: number;\n}\n\nexport interface SignInResult {\n identityKey: string;\n /** DER signature as number[], ready to post to verifySignIn. */\n signature: number[];\n nonce: string;\n method: SignInMethod;\n /** The wallet that signed. Keep it to make more calls. */\n wallet: WalletInterface;\n}\n\n/** Find a wallet: extension or in-app browser first, then a stored pairing, then a new pairing. */\nexport async function connectBwalletX(\n opts: Pick<SignInOptions, 'wallet' | 'pairing' | 'reusePairing' | 'discoverMs'> = {},\n): Promise<{ wallet: WalletInterface; method: SignInMethod }> {\n if (opts.wallet) return { wallet: opts.wallet, method: 'wallet' };\n const found = await discoverBwalletX(opts.discoverMs);\n if (found) return { wallet: found.wallet, method: found.method };\n if (opts.reusePairing !== false) {\n const restored = await restorePairing(opts.pairing ?? {});\n if (restored) return { wallet: restored, method: 'pairing' };\n }\n if (!opts.pairing) {\n throw Object.assign(new Error('bWalletX was not found in this browser'), { code: 'NOT_FOUND' });\n }\n return { wallet: await pairBwalletX(opts.pairing), method: 'pairing' };\n}\n\n/**\n * Sign the server's challenge with the user's bWalletX identity key. Post the result's\n * identityKey, nonce and signature to your verify route.\n */\nexport async function signInWithBwalletX(opts: SignInOptions): Promise<SignInResult> {\n const { wallet, method } = await connectBwalletX(opts);\n if (method !== 'pairing') await wallet.waitForAuthentication({}).catch(() => {});\n const { publicKey: identityKey } = await atStage('identity', () => wallet.getPublicKey({ identityKey: true }));\n const c = await atStage('challenge', () => opts.challenge(identityKey));\n const { signature } = await atStage('signature', () =>\n wallet.createSignature({\n protocolID: c.protocolID ?? LOGIN_PROTOCOL,\n keyID: c.keyID,\n counterparty: 'anyone',\n data: Array.from(new TextEncoder().encode(c.message)),\n }),\n );\n return { identityKey, signature: Array.from(signature), nonce: c.nonce, method, wallet };\n}\n\n/** Where a sign-in failed: the wallet's identity prompt, the site's challenge endpoint, or the wallet's signature. */\nexport type SignInStage = 'identity' | 'challenge' | 'signature' | 'verify';\n\n/** Tags a failure with its stage (err.stage), keeping the original message, so a site can say what actually failed. */\nexport async function atStage<T>(stage: SignInStage, run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (e) {\n const err = e instanceof Error ? e : new Error(String(e));\n if (!(err as { stage?: string }).stage) Object.assign(err, { stage });\n throw err;\n }\n}\n","/**\n * Browser side of the access protocol: ask the wallet to sign the site's challenge and prove the\n * keys holding the required token, using standard BRC-100 calls only (so any BRC-100 wallet works).\n */\nimport { Hash, PublicKey, Utils, type WalletInterface, type WalletProtocol } from '@bsv/sdk';\nimport {\n ACCESS_PROTOCOL,\n AccessError,\n MAX_HOLDING_PROOFS,\n challengeBytes,\n parseAccessChallenge,\n type AccessErrorBody,\n type AccessResponse,\n type HoldingProof,\n} from './access.js';\nimport { connectBwalletX, type SignInOptions } from './signin.js';\n\n/** The challenge route's answer (createAccessChallenge). */\nexport interface AccessChallengeLike {\n challenge: string;\n}\n\nexport interface RequestAccessOptions {\n /** Ask your server for a challenge. Gets the wallet's identity key. */\n challenge: (identityKey: string) => Promise<AccessChallengeLike>;\n /** Post the signed response to your server. Throw AccessError (or return an error body) to refuse. */\n verify?: (response: AccessResponse) => Promise<unknown>;\n wallet?: WalletInterface;\n pairing?: SignInOptions['pairing'];\n discoverMs?: number;\n /** Basket holding BSV-21 outputs. Default 'bsv21' (bWalletX, 1Sat wallets). */\n tokenBasket?: string;\n}\n\nexport interface RequestAccessResult {\n response: AccessResponse;\n /** Whatever `verify` returned. */\n verified?: unknown;\n wallet: WalletInterface;\n}\n\nconst ONESAT: WalletProtocol = [0, 'onesat'];\nconst LEGACY_ONESAT: WalletProtocol = [0, 'p 1sat'];\n\n/** Map a wallet / fetch failure to a protocol error. */\nexport function toAccessError(e: unknown): AccessError {\n if (e instanceof AccessError) return e;\n const err = e as { code?: string; message?: string; status?: number } | null;\n const msg = String(err?.message ?? e ?? '');\n if (err?.code === 'NOT_FOUND') return new AccessError('not_installed', msg);\n if (/reject|denied|declin|cancel|user/i.test(msg) || err?.code === 'ERR_USER_REJECTED' || err?.status === 4001) {\n return new AccessError('user_rejected', msg);\n }\n return new AccessError('bad_request', msg || 'access failed');\n}\n\n/** Read an AccessErrorBody from a server answer and throw it as AccessError. */\nexport function throwIfAccessError(body: unknown): void {\n const b = body as Partial<AccessErrorBody> | null;\n if (b && typeof b.error === 'string') throw new AccessError(b.error as AccessError['code'], b.message, { have: b.have, need: b.need });\n}\n\nfunction hash160FromP2pkh(script: string): string | null {\n const m = /^76a914([0-9a-f]{40})88ac$/i.exec(script.slice(-50));\n return m ? m[1].toLowerCase() : null;\n}\n\n/**\n * Sign the challenge with every key that locks an output of `tokenId` in the wallet's token basket.\n * Outputs whose derivation can't be reproduced are skipped (the server counts only what is proven).\n */\nexport async function proveHoldings(wallet: WalletInterface, challenge: string, tokenId: string, basket = 'bsv21'): Promise<HoldingProof[]> {\n const bare = tokenId.replace(/^bsv21:/, '');\n const list = await wallet.listOutputs({ basket, include: 'locking scripts', includeCustomInstructions: true, limit: 10000 });\n const byKey = new Map<string, HoldingProof>();\n const data = challengeBytes(challenge);\n for (const out of list.outputs) {\n if (byKey.size >= MAX_HOLDING_PROOFS) break;\n let ci: { id?: string; keyID?: string; protocolID?: WalletProtocol; counterparty?: string } = {};\n try {\n ci = JSON.parse(out.customInstructions ?? '{}');\n } catch {\n continue;\n }\n if (ci.id !== bare || !ci.keyID) continue;\n const want = out.lockingScript ? hash160FromP2pkh(out.lockingScript) : null;\n const counterparty = ci.counterparty ?? 'self';\n for (const protocolID of ci.protocolID ? [ci.protocolID] : [ONESAT, LEGACY_ONESAT]) {\n const { publicKey } = await wallet.getPublicKey({ protocolID, keyID: ci.keyID, counterparty });\n const h = Utils.toHex(Hash.hash160(PublicKey.fromString(publicKey).encode(true) as number[]));\n if (want && h !== want) continue;\n const existing = byKey.get(publicKey);\n if (existing) {\n existing.outpoints!.push(out.outpoint.replace('.', '_'));\n break;\n }\n const { signature } = await wallet.createSignature({ protocolID, keyID: ci.keyID, counterparty, data });\n byKey.set(publicKey, { pubkey: publicKey, signature: Array.from(signature), outpoints: [out.outpoint.replace('.', '_')] });\n break;\n }\n }\n return [...byKey.values()];\n}\n\n/**\n * Run the whole flow: find the wallet, get a challenge, sign it, prove holdings, post to verify.\n * Throws AccessError with a protocol code on every failure.\n */\nexport async function requestAccess(o: RequestAccessOptions): Promise<RequestAccessResult> {\n let wallet: WalletInterface;\n try {\n ({ wallet } = await connectBwalletX({ wallet: o.wallet, pairing: o.pairing, discoverMs: o.discoverMs }));\n } catch (e) {\n throw (e as { code?: string })?.code === 'NOT_FOUND' ? new AccessError('not_installed') : toAccessError(e);\n }\n try {\n const { publicKey: identityKey } = await wallet.getPublicKey({ identityKey: true });\n const { challenge } = await o.challenge(identityKey);\n const c = parseAccessChallenge(challenge);\n if (c.sub && c.sub !== identityKey.toLowerCase()) throw new AccessError('wrong_account');\n if (Date.now() / 1000 >= c.exp) throw new AccessError('expired');\n const { signature } = await wallet.createSignature({\n protocolID: ACCESS_PROTOCOL,\n keyID: c.nonce,\n counterparty: 'anyone',\n data: challengeBytes(challenge),\n });\n const response: AccessResponse = { v: 1, challenge, identityKey, signature: Array.from(signature) };\n if (c.require.token) response.holdings = await proveHoldings(wallet, challenge, c.require.token.id, o.tokenBasket);\n let verified: unknown;\n if (o.verify) {\n verified = await o.verify(response);\n throwIfAccessError(verified);\n }\n return { response, verified, wallet };\n } catch (e) {\n throw toAccessError(e);\n }\n}\n","/**\n * <bwalletx-signin>: the standard \"Sign in with bWalletX\" button as a framework-free web component.\n *\n * Design reference: the gold button in bit-sign's sign-in card (src/components/BwalletSignIn.tsx):\n * amber-300 → amber-500 vertical gradient, 12px radius, 16px/20px padding, 16px bold black label,\n * 11px subtitle at 60% black, a black 28px circle holding the gold \"b\", amber shadow.\n *\n * Attributes (all optional):\n * size lg (default) | md | sm | icon\n * theme gold (default) | dark | light\n * label signin (default) | continue | connect\n * brand bWalletX (default) | bWallet (store edition text)\n * subtitle on | off | <custom text>. Default on for lg/md, off for sm/icon and for brand=\"bWallet\"\n * full-width stretch to the container\n * loading show the spinner and set aria-busy\n * disabled disable the button\n * challenge-url / verify-url run the sign-in flow on click (POST JSON, as in the README)\n * require-token / min / min-bps with challenge-url: run the $403 access flow instead; emits\n * bwalletx-access-granted or bwalletx-access-denied { code, have, need }\n * get-url where \"Get bWalletX\" points. Default https://bwalletx.com/get\n * web-url the web wallet. Default https://web.bwalletx.com\n * discover-ms how long to wait for the wallet to answer. Default 1200\n * href plain link mode: navigate here on click\n * authorize-url (+ client-id, redirect-uri, scope) OAuth mode, for auth.bwalletx.com once it exists\n *\n * Events (bubble, composed):\n * bwalletx-signin on every click, before anything else. Cancelable: preventDefault() stops the\n * built-in behaviour so you can run your own flow.\n * bwalletx-signed-in detail { identityKey, method, nonce, signature, response }\n * bwalletx-not-found no bWalletX answered; the \"Get bWalletX\" row is shown\n * bwalletx-error detail { error }\n */\nimport { discoverBwalletX } from './discover.js';\nimport { signInWithBwalletX, type Challenge } from './signin.js';\nimport { requestAccess, type AccessChallengeLike } from './access-client.js';\nimport { AccessError } from './access.js';\n\nexport const SIGNIN_TAG = 'bwalletx-signin';\nexport const GET_URL = 'https://bwalletx.com/get';\nexport const WEB_WALLET_URL = 'https://web.bwalletx.com';\n\nexport type SigninSize = 'lg' | 'md' | 'sm' | 'icon';\nexport type SigninTheme = 'gold' | 'dark' | 'light';\nexport type SigninLabel = 'signin' | 'continue' | 'connect';\nexport type SigninBrand = 'bWalletX' | 'bWallet';\n\nconst VERBS: Record<SigninLabel, string> = { signin: 'Sign in with', continue: 'Continue with', connect: 'Connect' };\n\n/** The visible label for a label/brand pair, e.g. \"Sign in with bWalletX\". */\nexport function signinText(label: SigninLabel = 'signin', brand: SigninBrand = 'bWalletX'): string {\n return `${VERBS[label] ?? VERBS.signin} ${brand}`;\n}\n\nlet uid = 0;\n/** The gold \"b\" (bwalletx-site/logo.svg). Ids are unique per copy so several marks can share a page. */\nexport function markSvg(px = 20): string {\n const n = ++uid;\n return `<svg viewBox=\"23 8 74 100\" width=\"${Math.round(px * 0.74)}\" height=\"${px}\" aria-hidden=\"true\" focusable=\"false\"><defs><linearGradient id=\"bwxg${n}\" gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\"12\" x2=\"0\" y2=\"105\"><stop offset=\"0\" stop-color=\"#FFE58A\"/><stop offset=\".55\" stop-color=\"#FFD24D\"/><stop offset=\"1\" stop-color=\"#C98F00\"/></linearGradient><mask id=\"bwxh${n}\"><rect width=\"140\" height=\"140\" fill=\"#fff\"/><circle cx=\"60\" cy=\"72\" r=\"15\" fill=\"#000\"/></mask></defs><g fill=\"url(#bwxg${n})\" mask=\"url(#bwxh${n})\"><polygon points=\"45,12 45,76 27,76 27,30\"/><circle cx=\"60\" cy=\"72\" r=\"33\"/></g></svg>`;\n}\n\nexport const SIGNIN_CSS = `\n:host{display:inline-block;vertical-align:middle;font-family:Inter,ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif}\n:host([full-width]){display:block;width:100%}\n:host([hidden]){display:none}\nbutton{all:unset;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:12px;width:100%;cursor:pointer;\n padding:16px 20px;border-radius:12px;font-size:16px;font-weight:700;line-height:1.2;text-align:left;\n background:linear-gradient(180deg,#fcd34d,#f59e0b);color:#000;\n box-shadow:0 10px 15px -3px rgba(245,158,11,.2),0 4px 6px -4px rgba(245,158,11,.2);\n transition:background .15s,transform .1s,box-shadow .15s,border-color .15s;-webkit-tap-highlight-color:transparent}\nbutton:hover{background:linear-gradient(180deg,#fde68a,#fbbf24)}\nbutton:active{transform:translateY(1px);background:linear-gradient(180deg,#fbbf24,#d97706)}\nbutton:focus-visible{outline:2px solid #b45309;outline-offset:3px}\nbutton:disabled{opacity:.6;cursor:not-allowed;transform:none}\nbutton[aria-busy=true]{cursor:wait}\n.mark{display:inline-flex;align-items:center;justify-content:center;flex:none;width:28px;height:28px;border-radius:50%;background:#000}\n.txt{display:flex;flex-direction:column;align-items:flex-start;min-width:0}\n.txt b{font-weight:700;white-space:nowrap}\n.txt small{font-size:11px;font-weight:500;color:rgba(0,0,0,.65);white-space:nowrap}\n.spin{width:16px;height:16px;border-radius:50%;border:2px solid rgba(255,210,77,.35);border-top-color:#ffd24d;animation:r .8s linear infinite}\n@keyframes r{to{transform:rotate(360deg)}}\n@media (prefers-reduced-motion:reduce){button{transition:none}button:active{transform:none}.spin{animation:none;border-color:#ffd24d;border-right-color:transparent}}\n/* sizes */\n:host([size=md]) button{padding:10px 16px;border-radius:10px;font-size:15px;gap:10px}\n:host([size=md]) .mark{width:24px;height:24px}\n:host([size=md]) .txt small{font-size:10.5px}\n:host([size=sm]) button{padding:6px 12px 6px 6px;border-radius:8px;font-size:13px;gap:8px;min-height:36px}\n:host([size=sm]) .mark{width:22px;height:22px}\n:host([size=icon]) button{width:48px;height:48px;padding:0;border-radius:50%;gap:0}\n:host([size=icon]) .mark{width:34px;height:34px}\n/* themes */\n:host([theme=dark]) button{background:#0a0a0a;color:#fafafa;border:1px solid #3f3f46;box-shadow:none}\n:host([theme=dark]) button:hover{background:#18181b;border-color:rgba(251,191,36,.6)}\n:host([theme=dark]) button:active{background:#000}\n:host([theme=dark]) button:focus-visible{outline-color:#fcd34d}\n:host([theme=dark]) .mark{box-shadow:inset 0 0 0 1px #3f3f46}\n:host([theme=dark]) .txt small{color:rgba(255,255,255,.65)}\n:host([theme=light]) button{background:#fff;color:#09090b;border:1px solid #d4d4d8;box-shadow:0 1px 2px rgba(0,0,0,.06)}\n:host([theme=light]) button:hover{background:#fafafa;border-color:#f59e0b}\n:host([theme=light]) button:active{background:#f4f4f5}\n:host([theme=light]) .txt small{color:#52525b}\n/* not found */\n.nf{margin-top:8px;font-size:12px;line-height:1.4;color:#71717a;text-align:center}\n.nf a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px}\n.nf a:focus-visible{outline:2px solid #b45309;outline-offset:2px;border-radius:2px}\n:host([theme=dark]) .nf,:host-context(.dark) .nf{color:#a1a1aa}\n.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n`;\n\n/** Optional OAuth parameters for authorize-url mode. auth.bwalletx.com is planned, not live. */\nexport interface AuthorizeParams { authorizeUrl: string; clientId?: string | null; redirectUri?: string | null; scope?: string | null }\n\nfunction b64url(bytes: Uint8Array): string {\n let s = '';\n for (const b of bytes) s += String.fromCharCode(b);\n return btoa(s).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Build an OAuth 2.1 authorization-code + S256 PKCE redirect URL, and keep the verifier, state and\n * nonce in sessionStorage under \"bwalletx:oauth\" for your callback page. Matches the plan in\n * bit-sign docs/SIGN-IN-WITH-BWALLETX.md. The provider is not live yet.\n */\nexport async function buildAuthorizeUrl(p: AuthorizeParams): Promise<string> {\n const rnd = (n: number) => b64url(crypto.getRandomValues(new Uint8Array(n)));\n const verifier = rnd(32);\n const state = rnd(16);\n const nonce = rnd(16);\n const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)));\n const u = new URL(p.authorizeUrl, typeof location !== 'undefined' ? location.href : undefined);\n const set = (k: string, v: string | null | undefined) => { if (v) u.searchParams.set(k, v); };\n set('response_type', 'code');\n set('client_id', p.clientId);\n set('redirect_uri', p.redirectUri);\n set('scope', p.scope || 'openid profile');\n set('state', state);\n set('nonce', nonce);\n set('code_challenge', b64url(digest));\n set('code_challenge_method', 'S256');\n try { sessionStorage.setItem('bwalletx:oauth', JSON.stringify({ verifier, state, nonce })); } catch { /* storage blocked */ }\n return u.toString();\n}\n\nasync function postJson<T>(url: string, body: unknown): Promise<T> {\n const res = await fetch(url, { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n const data = await res.json().catch(() => ({}));\n if (!res.ok) throw new Error((data as { error?: string }).error ?? `HTTP ${res.status}`);\n return data as T;\n}\n\n/** POST that returns the body even on 4xx, so access error codes reach requestAccess. */\nasync function postAccess(url: string, body: unknown): Promise<unknown> {\n const res = await fetch(url, { method: 'POST', credentials: 'include', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n return res.json().catch(() => ({ error: 'bad_request', message: `HTTP ${res.status}` }));\n}\n\nconst OBSERVED = ['size', 'theme', 'label', 'brand', 'subtitle', 'loading', 'disabled', 'full-width', 'aria-label', 'get-url', 'web-url'];\n\n/** The element class. Created lazily so importing this module on a server is harmless. */\nfunction createClass(): CustomElementConstructor {\n return class BwalletxSigninElement extends HTMLElement {\n static get observedAttributes() { return OBSERVED; }\n #root: ShadowRoot;\n #notFound = false;\n #busy = false;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() { this.#render(); }\n attributeChangedCallback() { if (this.isConnected) this.#render(); }\n\n /** The inner <button>, for focus() and tests. */\n get button(): HTMLButtonElement | null { return this.#root.querySelector('button'); }\n get notFound() { return this.#notFound; }\n\n focus(opts?: FocusOptions) { this.button?.focus(opts); }\n\n #attr(name: string) { return this.getAttribute(name); }\n\n #render() {\n const size = (this.#attr('size') ?? 'lg') as SigninSize;\n const brand: SigninBrand = this.#attr('brand') === 'bWallet' ? 'bWallet' : 'bWalletX';\n const text = signinText((this.#attr('label') ?? 'signin') as SigninLabel, brand);\n const subAttr = this.#attr('subtitle');\n const subDefault = brand === 'bWalletX' && (size === 'lg' || size === 'md') ? 'also works with bWallet' : null;\n const subtitle = subAttr === null || subAttr === '' || subAttr === 'on' ? (subAttr === 'on' ? 'also works with bWallet' : subDefault)\n : subAttr === 'off' ? null : subAttr;\n const loading = this.hasAttribute('loading') || this.#busy;\n const disabled = this.hasAttribute('disabled');\n const markPx = size === 'icon' ? 24 : size === 'sm' ? 15 : size === 'md' ? 17 : 20;\n const aria = this.#attr('aria-label') ?? (subtitle && size !== 'icon' ? `${text}, ${subtitle}` : text);\n const getUrl = this.#attr('get-url') ?? GET_URL;\n const webUrl = this.#attr('web-url') ?? WEB_WALLET_URL;\n\n const esc = (s: string) => s.replace(/[&<>\"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '\"': '"' })[c]!);\n const label = size === 'icon' ? '' : `<span class=\"txt\" part=\"text\"><b part=\"label\">${esc(text)}</b>${subtitle ? `<small part=\"subtitle\">${esc(subtitle)}</small>` : ''}</span>`;\n const mark = loading ? '<span class=\"spin\" aria-hidden=\"true\"></span>' : markSvg(markPx);\n const nf = this.#notFound\n ? `<p class=\"nf\" part=\"not-found\" role=\"status\">bWalletX wasn't found in this browser. <a href=\"${esc(getUrl)}\" target=\"_blank\" rel=\"noopener\">Get ${brand}</a> or <a href=\"${esc(webUrl)}\" target=\"_blank\" rel=\"noopener\">use the web wallet</a>.</p>`\n : '';\n this.#root.innerHTML = `<style>${SIGNIN_CSS}</style><button type=\"button\" part=\"button\" aria-label=\"${esc(aria)}\" title=\"${esc(subtitle ? `${text} (${subtitle})` : text)}\"${disabled ? ' disabled' : ''}${loading ? ' aria-busy=\"true\"' : ''}><span class=\"mark\" part=\"mark\">${mark}</span>${label}</button>${loading ? '<span class=\"sr\" role=\"status\">Connecting to bWalletX…</span>' : ''}${nf}`;\n this.button!.addEventListener('click', () => void this.#click());\n }\n\n #emit(type: string, detail?: unknown, cancelable = false) {\n return this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable }));\n }\n\n #setBusy(on: boolean) { this.#busy = on; this.#render(); }\n\n async #click() {\n if (this.hasAttribute('disabled') || this.hasAttribute('loading') || this.#busy) return;\n if (!this.#emit('bwalletx-signin', { element: this }, true)) return;\n\n const href = this.#attr('href');\n const authorizeUrl = this.#attr('authorize-url');\n if (authorizeUrl) {\n location.assign(await buildAuthorizeUrl({ authorizeUrl, clientId: this.#attr('client-id'), redirectUri: this.#attr('redirect-uri'), scope: this.#attr('scope') }));\n return;\n }\n if (href) { location.assign(href); return; }\n\n const challengeUrl = this.#attr('challenge-url');\n if (!challengeUrl) return; // event-only mode: the page runs its own flow\n\n this.#notFound = false;\n this.#setBusy(true);\n try {\n const ms = Number(this.#attr('discover-ms')) || 1200;\n const found = await discoverBwalletX(ms);\n if (!found) {\n this.#notFound = true;\n this.#emit('bwalletx-not-found');\n if (this.#attr('require-token')) this.#emit('bwalletx-access-denied', { code: 'not_installed', message: 'No wallet found' });\n return;\n }\n const requireToken = this.#attr('require-token');\n if (requireToken) {\n // $403 gate. The server's own config is authoritative; these attributes are passed along as a hint.\n const verifyUrl = this.#attr('verify-url');\n const min = this.#attr('min');\n const minBps = this.#attr('min-bps');\n const hint = { token: { id: requireToken, ...(minBps ? { minBps: Number(minBps) } : { min: min ?? '1' }) } };\n try {\n const a = await requestAccess({\n wallet: found.wallet,\n challenge: (identityKey) => postJson<AccessChallengeLike>(challengeUrl, { identityKey, require: hint }),\n verify: verifyUrl ? (response) => postAccess(verifyUrl, response) : undefined,\n });\n this.#emit('bwalletx-access-granted', { identityKey: a.response.identityKey, response: a.verified });\n } catch (e) {\n const err = e instanceof AccessError ? e : new AccessError('bad_request', String(e));\n this.#emit('bwalletx-access-denied', { code: err.code, message: err.message, have: err.have, need: err.need });\n }\n return;\n }\n const r = await signInWithBwalletX({ wallet: found.wallet, challenge: (identityKey) => postJson<Challenge>(challengeUrl, { identityKey }) });\n const verifyUrl = this.#attr('verify-url');\n const response = verifyUrl ? await postJson(verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }) : null;\n this.#emit('bwalletx-signed-in', { identityKey: r.identityKey, method: found.method, nonce: r.nonce, signature: r.signature, response });\n } catch (error) {\n this.#emit('bwalletx-error', { error });\n } finally {\n this.#setBusy(false);\n }\n }\n };\n}\n\n/** Register <bwalletx-signin> (once). Safe to call on a server: it does nothing there. */\nexport function defineBwalletxSignin(tag = SIGNIN_TAG): CustomElementConstructor | undefined {\n if (typeof window === 'undefined' || typeof customElements === 'undefined') return undefined;\n const existing = customElements.get(tag);\n if (existing) return existing;\n const C = createClass();\n customElements.define(tag, C);\n return C;\n}\n"],"mappings":"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","names":["PrivateKey","PrivateKey","PublicKey","PublicKey","b64url","verifyUrl","response"]}
|