@bwalletx/connect 0.5.0 → 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 +18 -0
- package/README.md +10 -2
- package/dist/{access-Z6xQwKVF.d.cts → access-Dp0eKIOc.d.cts} +1 -1
- package/dist/{access-Z6xQwKVF.d.ts → access-Dp0eKIOc.d.ts} +1 -1
- package/dist/bwalletx-connect.global.js +11 -8
- package/dist/bwalletx-connect.global.js.map +1 -1
- package/dist/{chunk-CVLXFNAG.js → chunk-EXRQJTXF.js} +165 -26
- package/dist/chunk-EXRQJTXF.js.map +1 -0
- package/dist/{chunk-GF6GUDZL.js → chunk-RIOHG5JH.js} +67 -12
- package/dist/chunk-RIOHG5JH.js.map +1 -0
- package/dist/element.cjs +166 -31
- package/dist/element.cjs.map +1 -1
- package/dist/element.js +1 -1
- package/dist/index.cjs +250 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -9
- package/dist/index.d.ts +60 -9
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/next.d.cts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/react.cjs +887 -693
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +7 -2
- package/dist/react.d.ts +7 -2
- package/dist/react.js +11 -3
- package/dist/react.js.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/shell-react.cjs.map +1 -1
- package/dist/shell-react.js.map +1 -1
- package/dist/{wallets-VOKIKBbO.d.ts → wallets-CbzMtxrf.d.cts} +1 -1
- package/dist/{wallets-VOKIKBbO.d.cts → wallets-CbzMtxrf.d.ts} +1 -1
- package/package.json +11 -12
- package/dist/chunk-CVLXFNAG.js.map +0 -1
- package/dist/chunk-GF6GUDZL.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
|
/**
|
|
@@ -101,6 +101,11 @@ interface BwalletXProviderProps {
|
|
|
101
101
|
extraWallets?: WalletOption[];
|
|
102
102
|
/** Probe desktop wallets on localhost. Default false. */
|
|
103
103
|
probeLocal?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* A theme the wallet already bundles (e.g. 'npgx') for bWalletX to wear while this site is connected and is the
|
|
106
|
+
* active tab. Declared only while connecting or connected; withdrawn on disconnect. See declareWalletTheme.
|
|
107
|
+
*/
|
|
108
|
+
theme?: string;
|
|
104
109
|
discoverMs?: number;
|
|
105
110
|
payOptions?: PayOptions;
|
|
106
111
|
onError?: (e: Error) => void;
|
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
|
/**
|
|
@@ -101,6 +101,11 @@ interface BwalletXProviderProps {
|
|
|
101
101
|
extraWallets?: WalletOption[];
|
|
102
102
|
/** Probe desktop wallets on localhost. Default false. */
|
|
103
103
|
probeLocal?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* A theme the wallet already bundles (e.g. 'npgx') for bWalletX to wear while this site is connected and is the
|
|
106
|
+
* active tab. Declared only while connecting or connected; withdrawn on disconnect. See declareWalletTheme.
|
|
107
|
+
*/
|
|
108
|
+
theme?: string;
|
|
104
109
|
discoverMs?: number;
|
|
105
110
|
payOptions?: PayOptions;
|
|
106
111
|
onError?: (e: Error) => void;
|
package/dist/react.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
PaymentError,
|
|
3
3
|
brc100Option,
|
|
4
4
|
buyToken,
|
|
5
|
+
declareWalletTheme,
|
|
5
6
|
discoverWallets,
|
|
6
7
|
handcashAdapter,
|
|
7
8
|
pairingModal,
|
|
@@ -9,12 +10,13 @@ import {
|
|
|
9
10
|
pay,
|
|
10
11
|
pickWallet,
|
|
11
12
|
tip
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-RIOHG5JH.js";
|
|
13
14
|
import {
|
|
15
|
+
atStage,
|
|
14
16
|
clearPairing,
|
|
15
17
|
defineBwalletxSignin,
|
|
16
18
|
signInWithBwalletX
|
|
17
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-EXRQJTXF.js";
|
|
18
20
|
import "./chunk-MWD4BCO5.js";
|
|
19
21
|
|
|
20
22
|
// src/react.ts
|
|
@@ -106,6 +108,12 @@ function BwalletXProvider(p) {
|
|
|
106
108
|
props.current.onError?.(err);
|
|
107
109
|
return err;
|
|
108
110
|
}, []);
|
|
111
|
+
const themeOn = !!p.theme && (state.status === "connecting" || state.status === "connected");
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!themeOn) return;
|
|
114
|
+
declareWalletTheme(p.theme);
|
|
115
|
+
return () => declareWalletTheme(null);
|
|
116
|
+
}, [themeOn, p.theme]);
|
|
109
117
|
useEffect(() => {
|
|
110
118
|
const url = p.sessionUrl;
|
|
111
119
|
if (!url) return;
|
|
@@ -156,7 +164,7 @@ function BwalletXProvider(p) {
|
|
|
156
164
|
wallet: cw.wallet,
|
|
157
165
|
challenge: (identityKey) => postJson(c.challengeUrl, { identityKey })
|
|
158
166
|
});
|
|
159
|
-
const v = await postJson(c.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature });
|
|
167
|
+
const v = await atStage("verify", () => postJson(c.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }));
|
|
160
168
|
handle = v.handle ?? handle;
|
|
161
169
|
paymail = v.paymail ?? paymail;
|
|
162
170
|
signedIn = true;
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React wrapper for <bwalletx-signin>. React is an optional peer dependency; the button itself is\n * the web component, so markup and behaviour are identical everywhere.\n *\n * import { BwalletxSignin } from '@bwalletx/connect/react';\n * <BwalletxSignin challengeUrl=\"/auth/bwalletx/challenge\" verifyUrl=\"/auth/bwalletx/verify\"\n * onSignedIn={(d) => router.refresh()} />\n */\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { WalletInterface } from '@bsv/sdk';\nimport { clearPairing } from './pair.js';\nimport { signInWithBwalletX, type Challenge } from './signin.js';\nimport { pairingModal } from './modal.js';\nimport { pickWallet } from './modal.js';\nimport { discoverWallets, type ConnectedWallet, type WalletOption } from './wallets.js';\nimport { PaymentError, buyToken, pay, tip, type BuyTokenRequest, type PayOptions, type PayRequest, type PayResult } from './pay.js';\nimport { defineBwalletxSignin, type SigninBrand, type SigninLabel, type SigninSize, type SigninTheme } from './element.js';\n\nexport interface SignedInDetail {\n identityKey: string;\n method: string;\n nonce: string;\n signature: number[];\n response: unknown;\n}\n\nexport interface BwalletxSigninProps {\n size?: SigninSize;\n theme?: SigninTheme;\n label?: SigninLabel;\n brand?: SigninBrand;\n /** true/undefined = default, false = hidden, string = custom text. */\n subtitle?: boolean | string;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n challengeUrl?: string;\n verifyUrl?: string;\n getUrl?: string;\n webUrl?: string;\n href?: string;\n authorizeUrl?: string;\n clientId?: string;\n redirectUri?: string;\n scope?: string;\n discoverMs?: number;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n /** Every click. Call e.preventDefault() to skip the built-in flow. */\n onClick?: (e: CustomEvent<{ element: HTMLElement }>) => void;\n onSignedIn?: (d: SignedInDetail) => void;\n onNotFound?: () => void;\n onError?: (error: unknown) => void;\n}\n\nexport function BwalletxSignin(props: BwalletxSigninProps): ReactElement {\n const ref = useRef<HTMLElement>(null);\n const cb = useRef(props);\n cb.current = props;\n\n useEffect(() => {\n defineBwalletxSignin();\n const el = ref.current;\n if (!el) return;\n const on: [string, EventListener][] = [\n ['bwalletx-signin', (e) => cb.current.onClick?.(e as CustomEvent<{ element: HTMLElement }>)],\n ['bwalletx-signed-in', (e) => cb.current.onSignedIn?.((e as CustomEvent<SignedInDetail>).detail)],\n ['bwalletx-not-found', () => cb.current.onNotFound?.()],\n ['bwalletx-error', (e) => cb.current.onError?.((e as CustomEvent<{ error: unknown }>).detail.error)],\n ];\n for (const [t, f] of on) el.addEventListener(t, f);\n return () => { for (const [t, f] of on) el.removeEventListener(t, f); };\n }, []);\n\n const p = props;\n const flag = (v: boolean | undefined) => (v ? '' : undefined);\n return createElement('bwalletx-signin', {\n ref,\n id: p.id,\n class: p.className,\n style: p.style,\n size: p.size,\n theme: p.theme,\n label: p.label,\n brand: p.brand,\n subtitle: p.subtitle === false ? 'off' : typeof p.subtitle === 'string' ? p.subtitle : undefined,\n 'full-width': flag(p.fullWidth),\n loading: flag(p.loading),\n disabled: flag(p.disabled),\n 'challenge-url': p.challengeUrl,\n 'verify-url': p.verifyUrl,\n 'get-url': p.getUrl,\n 'web-url': p.webUrl,\n href: p.href,\n 'authorize-url': p.authorizeUrl,\n 'client-id': p.clientId,\n 'redirect-uri': p.redirectUri,\n scope: p.scope,\n 'discover-ms': p.discoverMs,\n 'aria-label': p['aria-label'],\n });\n}\n\nexport default BwalletxSignin;\n\nexport type BwalletXStatus = 'idle' | 'restoring' | 'connecting' | 'connected' | 'error';\n\nexport interface BwalletXState {\n status: BwalletXStatus;\n identityKey: string | null;\n handle: string | null;\n paymail: string | null;\n /** The BRC-100 wallet. null until connect() (a session restored from the cookie has no wallet yet). */\n wallet: WalletInterface | null;\n /** Which option connected (e.g. 'com.bwalletx.extension', 'bwalletx-pairing', 'handcash'). */\n walletId: string | null;\n /** True while the transitional HandCash adapter is in use. */\n transitional: boolean;\n /** The server session (verifyUrl/sessionUrl) is in place. */\n signedIn: boolean;\n error: Error | null;\n}\n\nexport interface BwalletXContextValue extends BwalletXState {\n /** Find wallets, show the picker if there is a choice, connect, and sign in if verifyUrl is set. */\n connect(opts?: { walletId?: string }): Promise<ConnectedWallet | null>;\n disconnect(): Promise<void>;\n /** The wallets found on the last connect() (or listWallets()). */\n wallets: WalletOption[];\n listWallets(): Promise<WalletOption[]>;\n pay(req: PayRequest): Promise<PayResult>;\n tip(req: PayRequest): Promise<PayResult>;\n buyToken(req: BuyTokenRequest): Promise<PayResult>;\n}\n\nexport interface BwalletXProviderProps {\n children?: ReactNode;\n /** POST { identityKey } → challenge. With verifyUrl, connect() also signs in. */\n challengeUrl?: string;\n /** POST { identityKey, nonce, signature } → { identityKey, handle?, paymail? } and sets the cookie. */\n verifyUrl?: string;\n /** POST to clear the cookie on disconnect(). */\n logoutUrl?: string;\n /** GET → { identityKey, handle?, paymail? } to restore a session on load. */\n sessionUrl?: string;\n /** Offer phone pairing with the built-in QR modal. Default true. */\n pairing?: boolean;\n /** Show the built-in picker when more than one wallet is available. Default true. */\n picker?: boolean;\n /** Extra options, e.g. [handcashAdapter({ login })]. */\n extraWallets?: WalletOption[];\n /** Probe desktop wallets on localhost. Default false. */\n probeLocal?: boolean;\n discoverMs?: number;\n payOptions?: PayOptions;\n onError?: (e: Error) => void;\n}\n\nconst empty: BwalletXState = {\n status: 'idle', identityKey: null, handle: null, paymail: null, wallet: null, walletId: null, transitional: false, signedIn: false, error: null,\n};\n\nconst Ctx = createContext<BwalletXContextValue | null>(null);\n\nasync function postJson(url: string, body: unknown): Promise<Record<string, unknown>> {\n const r = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n const j = (await r.json().catch(() => ({}))) as Record<string, unknown>;\n if (!r.ok) throw Object.assign(new Error(String(j.error ?? `${url} answered ${r.status}`)), { status: r.status, code: j.code });\n return j;\n}\n\n/** Holds the connected wallet and session for the tree. Renders nothing itself; SSR-safe. */\nexport function BwalletXProvider(p: BwalletXProviderProps): ReactElement {\n const [state, setState] = useState<BwalletXState>(empty);\n const [wallets, setWallets] = useState<WalletOption[]>([]);\n const props = useRef(p);\n props.current = p;\n const payer = useRef<ConnectedWallet['payer']>(null);\n\n const fail = useCallback((e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n setState((s) => ({ ...s, status: 'error', error: err }));\n props.current.onError?.(err);\n return err;\n }, []);\n\n useEffect(() => {\n const url = p.sessionUrl;\n if (!url) return;\n let live = true;\n setState((s) => ({ ...s, status: 'restoring' }));\n fetch(url, { credentials: 'same-origin' })\n .then((r) => r.json())\n .then((j: { identityKey?: string | null; handle?: string; paymail?: string }) => {\n if (!live) return;\n setState((s) =>\n j.identityKey\n ? { ...s, status: 'connected', identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true }\n : { ...s, status: s.status === 'restoring' ? 'idle' : s.status },\n );\n })\n .catch(() => live && setState((s) => ({ ...s, status: s.status === 'restoring' ? 'idle' : s.status })));\n return () => {\n live = false;\n };\n }, [p.sessionUrl]);\n\n const listWallets = useCallback(async () => {\n const c = props.current;\n const list = await discoverWallets({\n ms: c.discoverMs,\n probeLocal: c.probeLocal,\n extra: c.extraWallets,\n pairing: c.pairing === false ? undefined : () => pairingModal(),\n });\n setWallets(list);\n return list;\n }, []);\n\n const connect = useCallback(\n async (opts: { walletId?: string } = {}) => {\n const c = props.current;\n setState((s) => ({ ...s, status: 'connecting', error: null }));\n try {\n const list = await listWallets();\n let choice: WalletOption | null | undefined = opts.walletId ? list.find((w) => w.id === opts.walletId) : undefined;\n if (!choice) {\n const direct = list.filter((w) => w.kind !== 'pairing');\n choice = c.picker !== false && list.length > 1 ? await pickWallet(list) : (direct[0] ?? list[0] ?? null);\n }\n if (!choice) {\n if (!list.length) throw Object.assign(new Error('No wallet found. Get bWalletX at bwalletx.com.'), { code: 'NOT_FOUND' });\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n const cw = await choice.connect();\n let { handle = null, paymail = null } = cw as { handle?: string | null; paymail?: string | null };\n let signedIn = false;\n if (cw.wallet && c.challengeUrl && c.verifyUrl) {\n const r = await signInWithBwalletX({\n wallet: cw.wallet,\n challenge: (identityKey) => postJson(c.challengeUrl!, { identityKey }) as unknown as Promise<Challenge>,\n });\n const v = await postJson(c.verifyUrl, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature });\n handle = (v.handle as string | undefined) ?? handle;\n paymail = (v.paymail as string | undefined) ?? paymail;\n signedIn = true;\n }\n payer.current = cw.payer;\n setState({\n status: 'connected',\n identityKey: cw.identityKey,\n handle,\n paymail,\n wallet: cw.wallet,\n walletId: cw.optionId,\n transitional: !!cw.transitional,\n signedIn,\n error: null,\n });\n return cw;\n } catch (e) {\n if ((e as Error)?.message === 'cancelled') {\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n throw fail(e);\n }\n },\n [fail, listWallets],\n );\n\n const disconnect = useCallback(async () => {\n const c = props.current;\n if (c.logoutUrl) await postJson(c.logoutUrl, {}).catch(() => {});\n if (state.walletId === 'bwalletx-pairing') await clearPairing();\n payer.current = null;\n setState(empty);\n }, [state.walletId]);\n\n const need = () => {\n if (!payer.current) throw new PaymentError('NOT_SUPPORTED', 'Connect a wallet first (connect()).');\n return payer.current;\n };\n\n const value = useMemo<BwalletXContextValue>(\n () => ({\n ...state,\n wallets,\n connect,\n disconnect,\n listWallets,\n pay: (req) => pay(need(), req, props.current.payOptions),\n tip: (req) => tip(need(), req, props.current.payOptions),\n buyToken: (req) => buyToken(need(), req),\n }),\n [state, wallets, connect, disconnect, listWallets],\n );\n\n return createElement(Ctx.Provider, { value }, p.children);\n}\n\n/** The wallet + session state. Must be inside <BwalletXProvider>. */\nexport function useBwalletX(): BwalletXContextValue {\n const v = useContext(Ctx);\n if (!v) throw new Error('useBwalletX() must be used inside <BwalletXProvider>');\n return v;\n}\n\nexport { handcashAdapter, brc100Option, pairingOption, discoverWallets } from './wallets.js';\nexport type { WalletOption, ConnectedWallet, HandCashAdapterOptions } from './wallets.js';\nexport { PaymentError } from './pay.js';\nexport type { PayRequest, PayResult, BuyTokenRequest, PayOptions } from './pay.js';\n/** Alias with the casing used by <BwalletxSignin>. */\nexport const BwalletxProvider = BwalletXProvider;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/react.ts"],"sourcesContent":["/**\n * React wrapper for <bwalletx-signin>. React is an optional peer dependency; the button itself is\n * the web component, so markup and behaviour are identical everywhere.\n *\n * import { BwalletxSignin } from '@bwalletx/connect/react';\n * <BwalletxSignin challengeUrl=\"/auth/bwalletx/challenge\" verifyUrl=\"/auth/bwalletx/verify\"\n * onSignedIn={(d) => router.refresh()} />\n */\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from 'react';\nimport type { WalletInterface } from '@bsv/sdk';\nimport { clearPairing } from './pair.js';\nimport { declareWalletTheme } from './theme.js';\nimport { atStage, signInWithBwalletX, type Challenge } from './signin.js';\nimport { pairingModal } from './modal.js';\nimport { pickWallet } from './modal.js';\nimport { discoverWallets, type ConnectedWallet, type WalletOption } from './wallets.js';\nimport { PaymentError, buyToken, pay, tip, type BuyTokenRequest, type PayOptions, type PayRequest, type PayResult } from './pay.js';\nimport { defineBwalletxSignin, type SigninBrand, type SigninLabel, type SigninSize, type SigninTheme } from './element.js';\n\nexport interface SignedInDetail {\n identityKey: string;\n method: string;\n nonce: string;\n signature: number[];\n response: unknown;\n}\n\nexport interface BwalletxSigninProps {\n size?: SigninSize;\n theme?: SigninTheme;\n label?: SigninLabel;\n brand?: SigninBrand;\n /** true/undefined = default, false = hidden, string = custom text. */\n subtitle?: boolean | string;\n fullWidth?: boolean;\n loading?: boolean;\n disabled?: boolean;\n challengeUrl?: string;\n verifyUrl?: string;\n getUrl?: string;\n webUrl?: string;\n href?: string;\n authorizeUrl?: string;\n clientId?: string;\n redirectUri?: string;\n scope?: string;\n discoverMs?: number;\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n id?: string;\n /** Every click. Call e.preventDefault() to skip the built-in flow. */\n onClick?: (e: CustomEvent<{ element: HTMLElement }>) => void;\n onSignedIn?: (d: SignedInDetail) => void;\n onNotFound?: () => void;\n onError?: (error: unknown) => void;\n}\n\nexport function BwalletxSignin(props: BwalletxSigninProps): ReactElement {\n const ref = useRef<HTMLElement>(null);\n const cb = useRef(props);\n cb.current = props;\n\n useEffect(() => {\n defineBwalletxSignin();\n const el = ref.current;\n if (!el) return;\n const on: [string, EventListener][] = [\n ['bwalletx-signin', (e) => cb.current.onClick?.(e as CustomEvent<{ element: HTMLElement }>)],\n ['bwalletx-signed-in', (e) => cb.current.onSignedIn?.((e as CustomEvent<SignedInDetail>).detail)],\n ['bwalletx-not-found', () => cb.current.onNotFound?.()],\n ['bwalletx-error', (e) => cb.current.onError?.((e as CustomEvent<{ error: unknown }>).detail.error)],\n ];\n for (const [t, f] of on) el.addEventListener(t, f);\n return () => { for (const [t, f] of on) el.removeEventListener(t, f); };\n }, []);\n\n const p = props;\n const flag = (v: boolean | undefined) => (v ? '' : undefined);\n return createElement('bwalletx-signin', {\n ref,\n id: p.id,\n class: p.className,\n style: p.style,\n size: p.size,\n theme: p.theme,\n label: p.label,\n brand: p.brand,\n subtitle: p.subtitle === false ? 'off' : typeof p.subtitle === 'string' ? p.subtitle : undefined,\n 'full-width': flag(p.fullWidth),\n loading: flag(p.loading),\n disabled: flag(p.disabled),\n 'challenge-url': p.challengeUrl,\n 'verify-url': p.verifyUrl,\n 'get-url': p.getUrl,\n 'web-url': p.webUrl,\n href: p.href,\n 'authorize-url': p.authorizeUrl,\n 'client-id': p.clientId,\n 'redirect-uri': p.redirectUri,\n scope: p.scope,\n 'discover-ms': p.discoverMs,\n 'aria-label': p['aria-label'],\n });\n}\n\nexport default BwalletxSignin;\n\nexport type BwalletXStatus = 'idle' | 'restoring' | 'connecting' | 'connected' | 'error';\n\nexport interface BwalletXState {\n status: BwalletXStatus;\n identityKey: string | null;\n handle: string | null;\n paymail: string | null;\n /** The BRC-100 wallet. null until connect() (a session restored from the cookie has no wallet yet). */\n wallet: WalletInterface | null;\n /** Which option connected (e.g. 'com.bwalletx.extension', 'bwalletx-pairing', 'handcash'). */\n walletId: string | null;\n /** True while the transitional HandCash adapter is in use. */\n transitional: boolean;\n /** The server session (verifyUrl/sessionUrl) is in place. */\n signedIn: boolean;\n error: Error | null;\n}\n\nexport interface BwalletXContextValue extends BwalletXState {\n /** Find wallets, show the picker if there is a choice, connect, and sign in if verifyUrl is set. */\n connect(opts?: { walletId?: string }): Promise<ConnectedWallet | null>;\n disconnect(): Promise<void>;\n /** The wallets found on the last connect() (or listWallets()). */\n wallets: WalletOption[];\n listWallets(): Promise<WalletOption[]>;\n pay(req: PayRequest): Promise<PayResult>;\n tip(req: PayRequest): Promise<PayResult>;\n buyToken(req: BuyTokenRequest): Promise<PayResult>;\n}\n\nexport interface BwalletXProviderProps {\n children?: ReactNode;\n /** POST { identityKey } → challenge. With verifyUrl, connect() also signs in. */\n challengeUrl?: string;\n /** POST { identityKey, nonce, signature } → { identityKey, handle?, paymail? } and sets the cookie. */\n verifyUrl?: string;\n /** POST to clear the cookie on disconnect(). */\n logoutUrl?: string;\n /** GET → { identityKey, handle?, paymail? } to restore a session on load. */\n sessionUrl?: string;\n /** Offer phone pairing with the built-in QR modal. Default true. */\n pairing?: boolean;\n /** Show the built-in picker when more than one wallet is available. Default true. */\n picker?: boolean;\n /** Extra options, e.g. [handcashAdapter({ login })]. */\n extraWallets?: WalletOption[];\n /** Probe desktop wallets on localhost. Default false. */\n probeLocal?: boolean;\n /**\n * A theme the wallet already bundles (e.g. 'npgx') for bWalletX to wear while this site is connected and is the\n * active tab. Declared only while connecting or connected; withdrawn on disconnect. See declareWalletTheme.\n */\n theme?: string;\n discoverMs?: number;\n payOptions?: PayOptions;\n onError?: (e: Error) => void;\n}\n\nconst empty: BwalletXState = {\n status: 'idle', identityKey: null, handle: null, paymail: null, wallet: null, walletId: null, transitional: false, signedIn: false, error: null,\n};\n\nconst Ctx = createContext<BwalletXContextValue | null>(null);\n\nasync function postJson(url: string, body: unknown): Promise<Record<string, unknown>> {\n const r = await fetch(url, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });\n const j = (await r.json().catch(() => ({}))) as Record<string, unknown>;\n if (!r.ok) throw Object.assign(new Error(String(j.error ?? `${url} answered ${r.status}`)), { status: r.status, code: j.code });\n return j;\n}\n\n/** Holds the connected wallet and session for the tree. Renders nothing itself; SSR-safe. */\nexport function BwalletXProvider(p: BwalletXProviderProps): ReactElement {\n const [state, setState] = useState<BwalletXState>(empty);\n const [wallets, setWallets] = useState<WalletOption[]>([]);\n const props = useRef(p);\n props.current = p;\n const payer = useRef<ConnectedWallet['payer']>(null);\n\n const fail = useCallback((e: unknown) => {\n const err = e instanceof Error ? e : new Error(String(e));\n setState((s) => ({ ...s, status: 'error', error: err }));\n props.current.onError?.(err);\n return err;\n }, []);\n\n const themeOn = !!p.theme && (state.status === 'connecting' || state.status === 'connected');\n useEffect(() => {\n if (!themeOn) return;\n declareWalletTheme(p.theme);\n return () => declareWalletTheme(null);\n }, [themeOn, p.theme]);\n\n useEffect(() => {\n const url = p.sessionUrl;\n if (!url) return;\n let live = true;\n setState((s) => ({ ...s, status: 'restoring' }));\n fetch(url, { credentials: 'same-origin' })\n .then((r) => r.json())\n .then((j: { identityKey?: string | null; handle?: string; paymail?: string }) => {\n if (!live) return;\n setState((s) =>\n j.identityKey\n ? { ...s, status: 'connected', identityKey: j.identityKey, handle: j.handle ?? null, paymail: j.paymail ?? null, signedIn: true }\n : { ...s, status: s.status === 'restoring' ? 'idle' : s.status },\n );\n })\n .catch(() => live && setState((s) => ({ ...s, status: s.status === 'restoring' ? 'idle' : s.status })));\n return () => {\n live = false;\n };\n }, [p.sessionUrl]);\n\n const listWallets = useCallback(async () => {\n const c = props.current;\n const list = await discoverWallets({\n ms: c.discoverMs,\n probeLocal: c.probeLocal,\n extra: c.extraWallets,\n pairing: c.pairing === false ? undefined : () => pairingModal(),\n });\n setWallets(list);\n return list;\n }, []);\n\n const connect = useCallback(\n async (opts: { walletId?: string } = {}) => {\n const c = props.current;\n setState((s) => ({ ...s, status: 'connecting', error: null }));\n try {\n const list = await listWallets();\n let choice: WalletOption | null | undefined = opts.walletId ? list.find((w) => w.id === opts.walletId) : undefined;\n if (!choice) {\n const direct = list.filter((w) => w.kind !== 'pairing');\n choice = c.picker !== false && list.length > 1 ? await pickWallet(list) : (direct[0] ?? list[0] ?? null);\n }\n if (!choice) {\n if (!list.length) throw Object.assign(new Error('No wallet found. Get bWalletX at bwalletx.com.'), { code: 'NOT_FOUND' });\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n const cw = await choice.connect();\n let { handle = null, paymail = null } = cw as { handle?: string | null; paymail?: string | null };\n let signedIn = false;\n if (cw.wallet && c.challengeUrl && c.verifyUrl) {\n const r = await signInWithBwalletX({\n wallet: cw.wallet,\n challenge: (identityKey) => postJson(c.challengeUrl!, { identityKey }) as unknown as Promise<Challenge>,\n });\n const v = await atStage('verify', () => postJson(c.verifyUrl!, { identityKey: r.identityKey, nonce: r.nonce, signature: r.signature }));\n handle = (v.handle as string | undefined) ?? handle;\n paymail = (v.paymail as string | undefined) ?? paymail;\n signedIn = true;\n }\n payer.current = cw.payer;\n setState({\n status: 'connected',\n identityKey: cw.identityKey,\n handle,\n paymail,\n wallet: cw.wallet,\n walletId: cw.optionId,\n transitional: !!cw.transitional,\n signedIn,\n error: null,\n });\n return cw;\n } catch (e) {\n if ((e as Error)?.message === 'cancelled') {\n setState((s) => ({ ...s, status: s.identityKey ? 'connected' : 'idle' }));\n return null;\n }\n throw fail(e);\n }\n },\n [fail, listWallets],\n );\n\n const disconnect = useCallback(async () => {\n const c = props.current;\n if (c.logoutUrl) await postJson(c.logoutUrl, {}).catch(() => {});\n if (state.walletId === 'bwalletx-pairing') await clearPairing();\n payer.current = null;\n setState(empty);\n }, [state.walletId]);\n\n const need = () => {\n if (!payer.current) throw new PaymentError('NOT_SUPPORTED', 'Connect a wallet first (connect()).');\n return payer.current;\n };\n\n const value = useMemo<BwalletXContextValue>(\n () => ({\n ...state,\n wallets,\n connect,\n disconnect,\n listWallets,\n pay: (req) => pay(need(), req, props.current.payOptions),\n tip: (req) => tip(need(), req, props.current.payOptions),\n buyToken: (req) => buyToken(need(), req),\n }),\n [state, wallets, connect, disconnect, listWallets],\n );\n\n return createElement(Ctx.Provider, { value }, p.children);\n}\n\n/** The wallet + session state. Must be inside <BwalletXProvider>. */\nexport function useBwalletX(): BwalletXContextValue {\n const v = useContext(Ctx);\n if (!v) throw new Error('useBwalletX() must be used inside <BwalletXProvider>');\n return v;\n}\n\nexport { handcashAdapter, brc100Option, pairingOption, discoverWallets } from './wallets.js';\nexport type { WalletOption, ConnectedWallet, HandCashAdapterOptions } from './wallets.js';\nexport { PaymentError } from './pay.js';\nexport type { PayRequest, PayResult, BuyTokenRequest, PayOptions } from './pay.js';\n/** Alias with the casing used by <BwalletxSignin>. */\nexport const BwalletxProvider = BwalletXProvider;\n"],"mappings":"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","names":[]}
|
package/dist/server.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChainTracker, WalletProtocol } from '@bsv/sdk';
|
|
2
|
-
import { b as AccessResponse,
|
|
3
|
-
export {
|
|
2
|
+
import { b as AccessResponse, c as AccessKind, d as ACCESS_PROTOCOL, a as AccessRequirement } from './access-Dp0eKIOc.cjs';
|
|
3
|
+
export { e as ACCESS_CHALLENGE_LIFETIME_S, f as ACCESS_ERROR_STATUS, g as ACCESS_VERSION, h as AccessChallenge, A as AccessError, i as AccessErrorBody, j as AccessErrorCode, H as HoldingProof, M as MAX_HOLDING_PROOFS, P as PROTOCOL_MEDIA, T as TokenRequirement, k as accessErrorMessage, l as atLeastRaw, m as canonicalJson, n as challengeBytes, o as kindFor, p as normaliseRequirement, q as parseAccessChallenge, r as protocolMediaUrl, t as tokenNeedRaw } from './access-Dp0eKIOc.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared NonceStore adapters. No database driver is imported: you pass your own client or query
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChainTracker, WalletProtocol } from '@bsv/sdk';
|
|
2
|
-
import { b as AccessResponse,
|
|
3
|
-
export {
|
|
2
|
+
import { b as AccessResponse, c as AccessKind, d as ACCESS_PROTOCOL, a as AccessRequirement } from './access-Dp0eKIOc.js';
|
|
3
|
+
export { e as ACCESS_CHALLENGE_LIFETIME_S, f as ACCESS_ERROR_STATUS, g as ACCESS_VERSION, h as AccessChallenge, A as AccessError, i as AccessErrorBody, j as AccessErrorCode, H as HoldingProof, M as MAX_HOLDING_PROOFS, P as PROTOCOL_MEDIA, T as TokenRequirement, k as accessErrorMessage, l as atLeastRaw, m as canonicalJson, n as challengeBytes, o as kindFor, p as normaliseRequirement, q as parseAccessChallenge, r as protocolMediaUrl, t as tokenNeedRaw } from './access-Dp0eKIOc.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Shared NonceStore adapters. No database driver is imported: you pass your own client or query
|
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":";;;;;;;;;;;;;;;;;;;;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,QAAkD;AACzI,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":[]}
|
|
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":"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","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,18 +129,10 @@
|
|
|
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
|
-
"
|
|
143
|
-
"
|
|
134
|
+
"next": ">=14",
|
|
135
|
+
"react": ">=18"
|
|
144
136
|
},
|
|
145
137
|
"devDependencies": {
|
|
146
138
|
"@bsv/sdk": "^2.8.11",
|
|
@@ -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
|
+
}
|