Compare commits
6 Commits
d117549348
...
d6916fa3f4
Author | SHA1 | Date | |
---|---|---|---|
|
d6916fa3f4 | ||
|
dcab8e1365 | ||
|
e9a5925c50 | ||
|
544a54399c | ||
|
0891e51c9e | ||
|
a67ef43f6e |
@ -1126,7 +1126,7 @@ function QrPassword ({ value }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function PasswordScanner ({ onScan }) {
|
function PasswordScanner ({ onScan, text }) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
|
||||||
@ -1136,26 +1136,29 @@ function PasswordScanner ({ onScan }) {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
showModal(onClose => {
|
showModal(onClose => {
|
||||||
return (
|
return (
|
||||||
<Scanner
|
<div>
|
||||||
formats={['qr_code']}
|
{text && <h5 className='line-height-md mb-4 text-center'>{text}</h5>}
|
||||||
onScan={([{ rawValue: result }]) => {
|
<Scanner
|
||||||
onScan(result)
|
formats={['qr_code']}
|
||||||
onClose()
|
onScan={([{ rawValue: result }]) => {
|
||||||
}}
|
onScan(result)
|
||||||
styles={{
|
onClose()
|
||||||
video: {
|
}}
|
||||||
aspectRatio: '1 / 1'
|
styles={{
|
||||||
}
|
video: {
|
||||||
}}
|
aspectRatio: '1 / 1'
|
||||||
onError={(error) => {
|
}
|
||||||
if (error instanceof DOMException) {
|
}}
|
||||||
console.log(error)
|
onError={(error) => {
|
||||||
} else {
|
if (error instanceof DOMException) {
|
||||||
toaster.danger('qr scan: ' + error?.message || error?.toString?.())
|
console.log(error)
|
||||||
}
|
} else {
|
||||||
onClose()
|
toaster.danger('qr scan: ' + error?.message || error?.toString?.())
|
||||||
}}
|
}
|
||||||
/>
|
onClose()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -1167,9 +1170,10 @@ function PasswordScanner ({ onScan }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PasswordInput ({ newPass, qr, copy, readOnly, append, value, ...props }) {
|
export function PasswordInput ({ newPass, qr, copy, readOnly, append, value: initialValue, ...props }) {
|
||||||
const [showPass, setShowPass] = useState(false)
|
const [showPass, setShowPass] = useState(false)
|
||||||
const [field, helpers] = props.noForm ? [{ value }, {}, {}] : useField(props)
|
const [value, setValue] = useState(initialValue)
|
||||||
|
const [field,, helpers] = props.noForm ? [{ value }, {}, { setValue }] : useField(props)
|
||||||
|
|
||||||
const Append = useMemo(() => {
|
const Append = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -1181,12 +1185,13 @@ export function PasswordInput ({ newPass, qr, copy, readOnly, append, value, ...
|
|||||||
{qr && (readOnly
|
{qr && (readOnly
|
||||||
? <QrPassword value={field?.value} />
|
? <QrPassword value={field?.value} />
|
||||||
: <PasswordScanner
|
: <PasswordScanner
|
||||||
|
text="Where'd you learn to square dance?"
|
||||||
onScan={v => helpers.setValue(v)}
|
onScan={v => helpers.setValue(v)}
|
||||||
/>)}
|
/>)}
|
||||||
{append}
|
{append}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}, [showPass, copy, field?.value, qr, readOnly, append])
|
}, [showPass, copy, field?.value, helpers.setValue, qr, readOnly, append])
|
||||||
|
|
||||||
const style = props.style ? { ...props.style } : {}
|
const style = props.style ? { ...props.style } : {}
|
||||||
if (props.as === 'textarea') {
|
if (props.as === 'textarea') {
|
||||||
|
@ -55,11 +55,15 @@ export default function Invoice ({
|
|||||||
let variant = 'default'
|
let variant = 'default'
|
||||||
let status = 'waiting for you'
|
let status = 'waiting for you'
|
||||||
|
|
||||||
if (invoice.cancelled) {
|
if (invoice.confirmedAt) {
|
||||||
|
variant = 'confirmed'
|
||||||
|
status = `${numWithUnits(invoice.satsReceived, { abbreviate: false })} ${successVerb}`
|
||||||
|
useWallet = false
|
||||||
|
} else if (invoice.cancelled) {
|
||||||
variant = 'failed'
|
variant = 'failed'
|
||||||
status = 'cancelled'
|
status = 'cancelled'
|
||||||
useWallet = false
|
useWallet = false
|
||||||
} else if (invoice.isHeld && invoice.satsReceived && !expired) {
|
} else if (invoice.isHeld) {
|
||||||
variant = 'pending'
|
variant = 'pending'
|
||||||
status = (
|
status = (
|
||||||
<div className='d-flex justify-content-center'>
|
<div className='d-flex justify-content-center'>
|
||||||
@ -67,15 +71,11 @@ export default function Invoice ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
useWallet = false
|
useWallet = false
|
||||||
} else if (invoice.confirmedAt) {
|
|
||||||
variant = 'confirmed'
|
|
||||||
status = `${numWithUnits(invoice.satsReceived, { abbreviate: false })} ${successVerb}`
|
|
||||||
useWallet = false
|
|
||||||
} else if (expired) {
|
} else if (expired) {
|
||||||
variant = 'failed'
|
variant = 'failed'
|
||||||
status = 'expired'
|
status = 'expired'
|
||||||
useWallet = false
|
useWallet = false
|
||||||
} else if (invoice.expiresAt) {
|
} else {
|
||||||
variant = 'pending'
|
variant = 'pending'
|
||||||
status = (
|
status = (
|
||||||
<CompactLongCountdown
|
<CompactLongCountdown
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
||||||
import { useWallet } from '@/wallets/index'
|
import { useWallet } from '@/wallets/index'
|
||||||
@ -60,43 +60,6 @@ export const useInvoice = () => {
|
|||||||
return that(data.invoice)
|
return that(data.invoice)
|
||||||
}, [client])
|
}, [client])
|
||||||
|
|
||||||
const waitController = useMemo(() => {
|
|
||||||
const controller = new AbortController()
|
|
||||||
const signal = controller.signal
|
|
||||||
controller.wait = async ({ id }, waitFor = inv => inv?.actionState === 'PAID') => {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const interval = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const paid = await isInvoice({ id }, waitFor)
|
|
||||||
if (paid) {
|
|
||||||
resolve()
|
|
||||||
clearInterval(interval)
|
|
||||||
signal.removeEventListener('abort', abort)
|
|
||||||
} else {
|
|
||||||
console.info(`invoice #${id}: waiting for payment ...`)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
reject(err)
|
|
||||||
clearInterval(interval)
|
|
||||||
signal.removeEventListener('abort', abort)
|
|
||||||
}
|
|
||||||
}, FAST_POLL_INTERVAL)
|
|
||||||
|
|
||||||
const abort = () => {
|
|
||||||
console.info(`invoice #${id}: stopped waiting`)
|
|
||||||
resolve()
|
|
||||||
clearInterval(interval)
|
|
||||||
signal.removeEventListener('abort', abort)
|
|
||||||
}
|
|
||||||
signal.addEventListener('abort', abort)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
controller.stop = () => controller.abort()
|
|
||||||
|
|
||||||
return controller
|
|
||||||
}, [isInvoice])
|
|
||||||
|
|
||||||
const cancel = useCallback(async ({ hash, hmac }) => {
|
const cancel = useCallback(async ({ hash, hmac }) => {
|
||||||
if (!hash || !hmac) {
|
if (!hash || !hmac) {
|
||||||
throw new Error('missing hash or hmac')
|
throw new Error('missing hash or hmac')
|
||||||
@ -107,7 +70,44 @@ export const useInvoice = () => {
|
|||||||
return inv
|
return inv
|
||||||
}, [cancelInvoice])
|
}, [cancelInvoice])
|
||||||
|
|
||||||
return { create, waitUntilPaid: waitController.wait, stopWaiting: waitController.stop, cancel }
|
return { create, cancel, isInvoice }
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoiceController = (id, isInvoice) => {
|
||||||
|
const controller = new AbortController()
|
||||||
|
const signal = controller.signal
|
||||||
|
controller.wait = async (waitFor = inv => inv?.actionState === 'PAID') => {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const interval = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
const paid = await isInvoice({ id }, waitFor)
|
||||||
|
if (paid) {
|
||||||
|
resolve()
|
||||||
|
clearInterval(interval)
|
||||||
|
signal.removeEventListener('abort', abort)
|
||||||
|
} else {
|
||||||
|
console.info(`invoice #${id}: waiting for payment ...`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err)
|
||||||
|
clearInterval(interval)
|
||||||
|
signal.removeEventListener('abort', abort)
|
||||||
|
}
|
||||||
|
}, FAST_POLL_INTERVAL)
|
||||||
|
|
||||||
|
const abort = () => {
|
||||||
|
console.info(`invoice #${id}: stopped waiting`)
|
||||||
|
resolve()
|
||||||
|
clearInterval(interval)
|
||||||
|
signal.removeEventListener('abort', abort)
|
||||||
|
}
|
||||||
|
signal.addEventListener('abort', abort)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.stop = () => controller.abort()
|
||||||
|
|
||||||
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useWalletPayment = () => {
|
export const useWalletPayment = () => {
|
||||||
@ -118,12 +118,13 @@ export const useWalletPayment = () => {
|
|||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
throw new NoAttachedWalletError()
|
throw new NoAttachedWalletError()
|
||||||
}
|
}
|
||||||
|
const controller = invoiceController(id, invoice.isInvoice)
|
||||||
try {
|
try {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
// can't use await here since we might pay JIT invoices and sendPaymentAsync is not supported yet.
|
// can't use await here since we might pay JIT invoices and sendPaymentAsync is not supported yet.
|
||||||
// see https://www.webln.guide/building-lightning-apps/webln-reference/webln.sendpaymentasync
|
// see https://www.webln.guide/building-lightning-apps/webln-reference/webln.sendpaymentasync
|
||||||
wallet.sendPayment(bolt11).catch(reject)
|
wallet.sendPayment(bolt11).catch(reject)
|
||||||
invoice.waitUntilPaid({ id }, waitFor)
|
controller.wait(waitFor)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject)
|
.catch(reject)
|
||||||
})
|
})
|
||||||
@ -131,7 +132,7 @@ export const useWalletPayment = () => {
|
|||||||
console.error('payment failed:', err)
|
console.error('payment failed:', err)
|
||||||
throw err
|
throw err
|
||||||
} finally {
|
} finally {
|
||||||
invoice.stopWaiting()
|
controller.stop()
|
||||||
}
|
}
|
||||||
}, [wallet, invoice])
|
}, [wallet, invoice])
|
||||||
|
|
||||||
|
@ -522,7 +522,7 @@ export const deviceSyncSchema = object().shape({
|
|||||||
try {
|
try {
|
||||||
await string().oneOf(bip39Words).validate(w)
|
await string().oneOf(bip39Words).validate(w)
|
||||||
} catch {
|
} catch {
|
||||||
return context.createError({ message: `'${w}' is not a valid pairing phrase word` })
|
return context.createError({ message: `'${w.slice(0, 10)}${w.length > 10 ? '...' : ''}' is not a valid pairing phrase word` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,17 @@ function useLocalWallets () {
|
|||||||
}, [wallets, setWallets, me?.id])
|
}, [wallets, setWallets, me?.id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// listen for changes to any wallet config in local storage
|
||||||
|
// from any window with the same origin
|
||||||
|
const handleStorage = (event) => {
|
||||||
|
if (event.key.startsWith(getStorageKey(''))) {
|
||||||
|
loadWallets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('storage', handleStorage)
|
||||||
|
|
||||||
loadWallets()
|
loadWallets()
|
||||||
|
return () => window.removeEventListener('storage', handleStorage)
|
||||||
}, [loadWallets])
|
}, [loadWallets])
|
||||||
|
|
||||||
return { wallets, reloadLocalWallets: loadWallets, removeLocalWallets: removeWallets }
|
return { wallets, reloadLocalWallets: loadWallets, removeLocalWallets: removeWallets }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user