Skip to content

Commit

Permalink
fix: example behavior, loading states (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexlwn123 authored Sep 20, 2024
1 parent 779e924 commit 0f51314
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 40 deletions.
76 changes: 48 additions & 28 deletions examples/vite-core/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,54 @@ const TESTNET_FEDERATION_CODE =
'fed11qgqrgvnhwden5te0v9k8q6rp9ekh2arfdeukuet595cr2ttpd3jhq6rzve6zuer9wchxvetyd938gcewvdhk6tcqqysptkuvknc7erjgf4em3zfh90kffqf9srujn6q53d6r056e4apze5cw27h75'

const useIsOpen = () => {
const [isOpen, setIsOpen] = useState(false)
const [open, setIsOpen] = useState(false)

const checkIsOpen = useCallback(() => {
if (isOpen !== wallet.isOpen()) setIsOpen(wallet.isOpen())
return isOpen
}, [wallet])
if (open !== wallet.isOpen()) {
setIsOpen(wallet.isOpen())
}
}, [open])

useEffect(() => {
checkIsOpen()
}, [checkIsOpen])

return { isOpen, checkIsOpen }
return { open, checkIsOpen }
}

const useBalance = () => {
const useBalance = (checkIsOpen: () => void) => {
const [balance, setBalance] = useState(0)

useEffect(() => {
const unsubscribe = wallet.subscribeBalance((balance: number) => {
console.log('balance', balance)
// checks if the wallet is open when the first
// subscription event fires.
// TODO: make a subscription to the wallet open status
checkIsOpen()
setBalance(balance)
})

return () => {
unsubscribe()
}
}, [])
}, [checkIsOpen])

return balance
}

const App = () => {
const { isOpen, checkIsOpen } = useIsOpen()
const { open, checkIsOpen } = useIsOpen()
const balance = useBalance(checkIsOpen)

return (
<>
<header>
<h1>Fedimint Typescript Library Demo</h1>
<h2>This is a WIP</h2>
</header>
<main>
<WalletStatus isOpen={isOpen} checkIsOpen={checkIsOpen} />
<JoinFederation isOpen={isOpen} checkIsOpen={checkIsOpen} />
<WalletStatus open={open} checkIsOpen={checkIsOpen} balance={balance} />
<JoinFederation open={open} checkIsOpen={checkIsOpen} />
<GenerateLightningInvoice />
<RedeemEcash />
<SendLightning />
Expand All @@ -52,20 +62,20 @@ const App = () => {
}

const WalletStatus = ({
isOpen,
open,
checkIsOpen,
balance,
}: {
isOpen: boolean
checkIsOpen: () => boolean
open: boolean
checkIsOpen: () => void
balance: number
}) => {
const balance = useBalance()

return (
<div className="section">
<h3>Wallet Status</h3>
<div className="row">
<strong>Is Wallet Open?</strong>
<div>{isOpen ? 'Yes' : 'No'}</div>
<div>{open ? 'Yes' : 'No'}</div>
<button onClick={() => checkIsOpen()}>Check</button>
</div>
<div className="row">
Expand All @@ -78,24 +88,24 @@ const WalletStatus = ({
}

const JoinFederation = ({
isOpen,
open,
checkIsOpen,
}: {
isOpen: boolean
checkIsOpen: () => boolean
open: boolean
checkIsOpen: () => void
}) => {
const [inviteCode, setInviteCode] = useState(TESTNET_FEDERATION_CODE)
const [joinResult, setJoinResult] = useState<string | null>(null)
const [joinError, setJoinError] = useState('')
const [joining, setJoining] = useState(false)

const joinFederation = async (e: React.FormEvent) => {
e.preventDefault()
const open = checkIsOpen()
console.log('OPEN', open, wallet)
if (open) return
checkIsOpen()

console.log('Joining federation:', inviteCode)
try {
setJoining(true)
const res = await wallet?.joinFederation(inviteCode)
console.log('join federation res', res)
setJoinResult('Joined!')
Expand All @@ -104,6 +114,8 @@ const JoinFederation = ({
console.log('Error joining federation', e)
setJoinError(typeof e === 'object' ? e.toString() : (e as string))
setJoinResult('')
} finally {
setJoining(false)
}
}

Expand All @@ -117,13 +129,13 @@ const JoinFederation = ({
required
value={inviteCode}
onChange={(e) => setInviteCode(e.target.value)}
disabled={isOpen}
disabled={open}
/>
<button type="submit" disabled={isOpen}>
<button type="submit" disabled={open || joining}>
Join
</button>
</form>
{!joinResult && isOpen && <i>(You've already joined a federation)</i>}
{!joinResult && open && <i>(You've already joined a federation)</i>}
{joinResult && <div className="success">{joinResult}</div>}
{joinError && <div className="error">{joinError}</div>}
</div>
Expand Down Expand Up @@ -208,12 +220,13 @@ const GenerateLightningInvoice = () => {
const [description, setDescription] = useState('')
const [invoice, setInvoice] = useState('')
const [error, setError] = useState('')
const [generating, setGenerating] = useState(false)

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setInvoice('')
setError('')

setGenerating(true)
try {
const response = await wallet.createBolt11Invoice(
Number(amount),
Expand All @@ -223,6 +236,8 @@ const GenerateLightningInvoice = () => {
} catch (e) {
console.error('Error generating Lightning invoice', e)
setError(e instanceof Error ? e.message : String(e))
} finally {
setGenerating(false)
}
}

Expand Down Expand Up @@ -251,12 +266,17 @@ const GenerateLightningInvoice = () => {
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<button type="submit">Generate Invoice</button>
<button type="submit" disabled={generating}>
{generating ? 'Generating...' : 'Generate Invoice'}
</button>
</form>
{invoice && (
<div className="success">
<strong>Generated Invoice:</strong>
<pre className="invoice-wrap">{invoice}</pre>
<button onClick={() => navigator.clipboard.writeText(invoice)}>
Copy
</button>
</div>
)}
{error && <div className="error">{error}</div>}
Expand Down
2 changes: 1 addition & 1 deletion examples/vite-core/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import wasm from 'vite-plugin-wasm'

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), wasm()],

// These worker settings are required
worker: {
Expand Down
40 changes: 34 additions & 6 deletions packages/core-web/src/FedimintWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,37 @@ export class FedimintWallet {
private requestCounter: number = 0
private requestCallbacks: Map<number, (value: any) => void> = new Map()

constructor(lazy: boolean = false, open: boolean = true) {
/**
* Creates a new instance of FedimintWallet.
*
* @description
* This constructor initializes a FedimintWallet instance, which manages communication
* with a Web Worker. The Web Worker is responsible for running WebAssembly code that
* handles the core Fedimint Client operations.
*
* (default) When not in lazy mode, the constructor immediately initializes the
* Web Worker and begins loading the WebAssembly module in the background. This
* allows for faster subsequent operations but may increase initial load time.
*
* In lazy mode, the Web Worker and WebAssembly initialization are deferred until
* the first operation that requires them, reducing initial overhead at the cost
* of a slight delay on the first operation.
*
* @param {boolean} lazy - If true, delays Web Worker and WebAssembly initialization
* until needed. Default is false.
*
* @example
* // Create a wallet with immediate initialization
* const wallet = new FedimintWallet();
* wallet.open();
*
* // Create a wallet with lazy initialization
* const lazyWallet = new FedimintWallet(true);
* // Some time later...
* wallet.initialize();
* wallet.open();
*/
constructor(lazy: boolean = false) {
this.openPromise = new Promise((resolve) => {
this.resolveOpen = resolve
})
Expand Down Expand Up @@ -62,7 +92,7 @@ export class FedimintWallet {
// Setup
initialize() {
if (this.initPromise) return this.initPromise
this.worker = new Worker(new URL('worker.js?worker', import.meta.url), {
this.worker = new Worker(new URL('worker.js', import.meta.url), {
type: 'module',
})
this.worker.onmessage = this.handleWorkerMessage.bind(this)
Expand Down Expand Up @@ -220,7 +250,6 @@ export class FedimintWallet {
})

unsubscribePromise.then(() => {
console.trace('UNSUBSCRIBING', requestId)
this.worker?.postMessage({
type: 'unsubscribe',
requestId,
Expand Down Expand Up @@ -516,8 +545,7 @@ export class FedimintWallet {
return await this._rpcSingle('ln', 'list_gateways', {})
}

async updateGatewayCache(): Promise<void> {
console.trace('Updating gateway cache')
await this._rpcSingle('ln', 'update_gateway_cache', {})
async updateGatewayCache(): Promise<JSONValue> {
return await this._rpcSingle('ln', 'update_gateway_cache', {})
}
}
12 changes: 7 additions & 5 deletions packages/core-web/src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ self.onmessage = async (event) => {
}
} else if (type === 'rpc') {
const { module, method, body } = payload
console.log('RPC received', module, method, body)
if (!client) {
self.postMessage({
type: 'error',
Expand All @@ -53,14 +54,15 @@ self.onmessage = async (event) => {
method,
JSON.stringify(body),
(res) => {
console.log('RPC response', requestId, res)
const data = JSON.parse(res)
self.postMessage({ type: 'rpcResponse', requestId, ...data })

if (data.end === undefined) return

// Handle stream ending
const handle = streamCancelMap.get(requestId)
handle?.free()
if (data.end !== undefined) {
// Handle stream ending
const handle = streamCancelMap.get(requestId)
handle?.free()
}
},
)
streamCancelMap.set(requestId, rpcHandle)
Expand Down
3 changes: 3 additions & 0 deletions vitest.workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default defineWorkspace([
provider: 'playwright',
isolate: true,
ui: false, // no ui for the core library
api: {
port: 63315,
},
},
},
},
Expand Down

0 comments on commit 0f51314

Please sign in to comment.