Skip to content

Commit

Permalink
Allow setting of default payment provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzyis committed Feb 8, 2024
1 parent 4873c2f commit 4b95add
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 27 deletions.
29 changes: 17 additions & 12 deletions components/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -962,16 +962,21 @@ export function DatePicker ({ fromName, toName, noForm, onChange, when, from, to
)
}

export function ClientInput ({ initialValue, ...props }) {
// This component can be used for Formik fields
// where the initial value is not available on first render.
// Example: value is stored in localStorage which is fetched
// after first render using an useEffect hook.
const [,, helpers] = useField(props)

useEffect(() => {
helpers.setValue(initialValue)
}, [initialValue])

return <Input {...props} />
function Client (Component) {
return ({ initialValue, ...props }) => {
// This component can be used for Formik fields
// where the initial value is not available on first render.
// Example: value is stored in localStorage which is fetched
// after first render using an useEffect hook.
const [,, helpers] = useField(props)

useEffect(() => {
helpers.setValue(initialValue)
}, [initialValue])

return <Component {...props} />
}
}

export const ClientInput = Client(Input)
export const ClientCheckbox = Client(Checkbox)
72 changes: 68 additions & 4 deletions components/webln/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import { createContext, useContext } from 'react'
import { createContext, useContext, useEffect, useState } from 'react'
import { LNbitsProvider, useLNbits } from './lnbits'
import { NWCProvider, useNWC } from './nwc'
import { useToast } from '../toast'
import { gql, useMutation } from '@apollo/client'

const WebLNContext = createContext({})
const storageKey = 'webln:providers'

const paymentMethodHook = (methods, { name, enabled }) => {
let newMethods
if (enabled) {
newMethods = methods.includes(name) ? methods : [...methods, name]
} else {
newMethods = methods.filter(m => m !== name)
}
savePaymentMethods(newMethods)
return newMethods
}

const savePaymentMethods = (methods) => {
window.localStorage.setItem(storageKey, JSON.stringify(methods))
}

function RawWebLNProvider ({ children }) {
// LNbits should only be used during development
// since it gives full wallet access on XSS
// eslint-disable-next-line no-unused-vars
const lnbits = useLNbits()
const nwc = useNWC()
const providers = [lnbits, nwc]

// order of payment methods depends on user preference:
// payment method at index 0 is default, if that one fails
// we try the remaining ones in order as fallbacks.
// -- TODO: implement fallback logic
// eslint-disable-next-line no-unused-vars
const [paymentMethods, setPaymentMethods] = useState([])
const loadPaymentMethods = () => {
const methods = window.localStorage.getItem(storageKey)
if (!methods) return
setPaymentMethods(JSON.parse(methods))
}
useEffect(loadPaymentMethods, [])

const toaster = useToast()
const [cancelInvoice] = useMutation(gql`
mutation cancelInvoice($hash: String!, $hmac: String!) {
Expand All @@ -21,8 +51,42 @@ function RawWebLNProvider ({ children }) {
}
`)

// TODO: switch between providers based on user preference
const provider = nwc.enabled ? nwc : lnbits
useEffect(() => {
setPaymentMethods(methods => paymentMethodHook(methods, nwc))
if (!nwc.enabled) nwc.setIsDefault(false)
}, [nwc.enabled])

useEffect(() => {
setPaymentMethods(methods => paymentMethodHook(methods, lnbits))
if (!lnbits.enabled) lnbits.setIsDefault(false)
}, [lnbits.enabled])

const setDefaultPaymentMethod = (provider) => {
for (const p of providers) {
if (p.name !== provider.name) {
p.setIsDefault(false)
}
}
}

useEffect(() => {
if (nwc.isDefault) setDefaultPaymentMethod(nwc)
}, [nwc.isDefault])

useEffect(() => {
if (lnbits.isDefault) setDefaultPaymentMethod(lnbits)
}, [lnbits.isDefault])

// TODO: implement numeric provider priority
// when we have more than two providers for sending
let provider = providers.filter(p => p.enabled && p.isDefault)[0]
if (!provider && providers.length > 0) {
// if no provider is the default, pick the first one and use that one as the default
provider = providers.filter(p => p.enabled)[0]
if (provider) {
provider.setIsDefault(true)
}
}

const sendPaymentWithToast = function ({ bolt11, hash, hmac }) {
let canceled = false
Expand Down
7 changes: 5 additions & 2 deletions components/webln/lnbits.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function LNbitsProvider ({ children }) {
const [url, setUrl] = useState('')
const [adminKey, setAdminKey] = useState('')
const [enabled, setEnabled] = useState()
const [isDefault, setIsDefault] = useState()

const name = 'LNbits'
const storageKey = 'webln:provider:lnbits'
Expand Down Expand Up @@ -103,9 +104,10 @@ export function LNbitsProvider ({ children }) {

const config = JSON.parse(configStr)

const { url, adminKey } = config
const { url, adminKey, isDefault } = config
setUrl(url)
setAdminKey(adminKey)
setIsDefault(isDefault)

try {
// validate config by trying to fetch wallet
Expand All @@ -122,6 +124,7 @@ export function LNbitsProvider ({ children }) {
// immediately store config so it's not lost even if config is invalid
setUrl(config.url)
setAdminKey(config.adminKey)
setIsDefault(config.isDefault)

// XXX This is insecure, XSS vulns could lead to loss of funds!
// -> check how mutiny encrypts their wallet and/or check if we can leverage web workers
Expand Down Expand Up @@ -150,7 +153,7 @@ export function LNbitsProvider ({ children }) {
loadConfig().catch(console.error)
}, [])

const value = { name, url, adminKey, saveConfig, clearConfig, enabled, getInfo, sendPayment }
const value = { name, url, adminKey, saveConfig, clearConfig, enabled, isDefault, setIsDefault, getInfo, sendPayment }
return (
<LNbitsContext.Provider value={value}>
{children}
Expand Down
9 changes: 6 additions & 3 deletions components/webln/nwc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function NWCProvider ({ children }) {
const [relayUrl, setRelayUrl] = useState()
const [secret, setSecret] = useState()
const [enabled, setEnabled] = useState()
const [isDefault, setIsDefault] = useState()
const [relay, setRelay] = useState()

const name = 'NWC'
Expand All @@ -25,8 +26,9 @@ export function NWCProvider ({ children }) {

const config = JSON.parse(configStr)

const { nwcUrl } = config
const { nwcUrl, isDefault } = config
setNwcUrl(nwcUrl)
setIsDefault(isDefault)

const params = parseWalletConnectUrl(nwcUrl)
setRelayUrl(params.relayUrl)
Expand All @@ -45,8 +47,9 @@ export function NWCProvider ({ children }) {

const saveConfig = useCallback(async (config) => {
// immediately store config so it's not lost even if config is invalid
const { nwcUrl } = config
const { nwcUrl, isDefault } = config
setNwcUrl(nwcUrl)
setIsDefault(isDefault)
if (!nwcUrl) {
setEnabled(undefined)
return
Expand Down Expand Up @@ -171,7 +174,7 @@ export function NWCProvider ({ children }) {
loadConfig().catch(console.error)
}, [])

const value = { name, nwcUrl, relayUrl, walletPubkey, secret, saveConfig, clearConfig, enabled, getInfo, sendPayment }
const value = { name, nwcUrl, relayUrl, walletPubkey, secret, saveConfig, clearConfig, enabled, isDefault, setIsDefault, getInfo, sendPayment }
return (
<NWCContext.Provider value={value}>
{children}
Expand Down
13 changes: 10 additions & 3 deletions pages/settings/wallets/lnbits.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getGetServerSideProps } from '../../../api/ssrApollo'
import { Form, ClientInput } from '../../../components/form'
import { Form, ClientInput, ClientCheckbox } from '../../../components/form'
import { CenterLayout } from '../../../components/layout'
import { WalletButtonBar, WalletCard } from '../../../components/wallet-card'
import { lnbitsSchema } from '../../../lib/validate'
Expand All @@ -10,7 +10,7 @@ import { useLNbits } from '../../../components/webln/lnbits'
export const getServerSideProps = getGetServerSideProps({ authRequired: true })

export default function LNbits () {
const { url, adminKey, saveConfig, clearConfig, enabled } = useLNbits()
const { url, adminKey, saveConfig, clearConfig, enabled, isDefault } = useLNbits()
const toaster = useToast()
const router = useRouter()

Expand All @@ -21,7 +21,8 @@ export default function LNbits () {
<Form
initial={{
url: url || '',
adminKey: adminKey || ''
adminKey: adminKey || '',
isDefault: isDefault || false
}}
schema={lnbitsSchema}
onSubmit={async (values) => {
Expand Down Expand Up @@ -49,6 +50,12 @@ export default function LNbits () {
label='admin key'
name='adminKey'
/>
<ClientCheckbox
disabled={!enabled}
initialValue={isDefault}
label='default payment method'
name='isDefault'
/>
<WalletButtonBar
enabled={enabled} onDelete={async () => {
try {
Expand Down
13 changes: 10 additions & 3 deletions pages/settings/wallets/nwc.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getGetServerSideProps } from '../../../api/ssrApollo'
import { Form, ClientInput } from '../../../components/form'
import { Form, ClientInput, ClientCheckbox } from '../../../components/form'
import { CenterLayout } from '../../../components/layout'
import { WalletButtonBar, WalletCard } from '../../../components/wallet-card'
import { nwcSchema } from '../../../lib/validate'
Expand All @@ -10,7 +10,7 @@ import { useNWC } from '../../../components/webln/nwc'
export const getServerSideProps = getGetServerSideProps({ authRequired: true })

export default function NWC () {
const { nwcUrl, saveConfig, clearConfig, enabled } = useNWC()
const { nwcUrl, saveConfig, clearConfig, enabled, isDefault } = useNWC()
const toaster = useToast()
const router = useRouter()

Expand All @@ -20,7 +20,8 @@ export default function NWC () {
<h6 className='text-muted text-center pb-3'>use Nostr Wallet Connect for zapping</h6>
<Form
initial={{
nwcUrl: nwcUrl || ''
nwcUrl: nwcUrl || '',
isDefault: isDefault || false
}}
schema={nwcSchema}
onSubmit={async (values) => {
Expand All @@ -41,6 +42,12 @@ export default function NWC () {
required
autoFocus
/>
<ClientCheckbox
disabled={!enabled}
initialValue={isDefault}
label='default payment method'
name='isDefault'
/>
<WalletButtonBar
enabled={enabled} onDelete={async () => {
try {
Expand Down

0 comments on commit 4b95add

Please sign in to comment.