Browse Source

bug-fix

imwald
Silberengel 4 weeks ago
parent
commit
239ba9a3f9
  1. 4
      package-lock.json
  2. 2
      package.json
  3. 43
      src/components/PaytoDialog/index.tsx
  4. 5
      src/data/payto-types.json
  5. 1
      src/i18n/locales/en.ts
  6. 5
      src/lib/payto-registry.ts
  7. 8
      src/lib/payto-targets.test.ts
  8. 20
      src/lib/payto-targets.ts
  9. 1
      src/lib/payto.ts

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "imwald",
"version": "23.13.3",
"version": "23.13.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
"version": "23.13.3",
"version": "23.13.5",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "imwald",
"version": "23.13.4",
"version": "23.13.5",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",

43
src/components/PaytoDialog/index.tsx

@ -14,7 +14,7 @@ import { @@ -14,7 +14,7 @@ import {
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { ArrowRight, Copy, Zap } from 'lucide-react'
import { ArrowRight, Copy, Wallet, Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { closeModal } from '@getalby/bitcoin-connect-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@ -23,8 +23,12 @@ import { releaseBodyScrollLocks } from '@/lib/react-remove-scroll-body-cleanup' @@ -23,8 +23,12 @@ import { releaseBodyScrollLocks } from '@/lib/react-remove-scroll-body-cleanup'
import {
filterPaytoPaymentOpenHandlersForDevice,
getPaytoTypeInfo,
isLikelyMobileWalletUserAgent,
isPaytoHttpOpenUrl,
openPaytoPaymentTarget,
resolvePaytoPaymentOpenHandlers
openPaytoResolvedUrl,
resolvePaytoPaymentOpenHandlers,
resolvePaytoProfileUrl
} from '@/lib/payto'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
@ -99,6 +103,19 @@ export default function PaytoDialog({ @@ -99,6 +103,19 @@ export default function PaytoDialog({
[openHandlers, selectedOpenHandlerId]
)
const walletOpenUri = useMemo(
() => (isLightning ? null : resolvePaytoProfileUrl(type, authority)),
[isLightning, type, authority]
)
const showPrimaryOpen = isLikelyMobileWalletUserAgent() && !!walletOpenUri
const handleOpenWallet = () => {
if (!walletOpenUri) return
openPaytoResolvedUrl(walletOpenUri)
closeForWalletFlow()
}
const handleCopy = (text: string, copyLabel?: string) => {
navigator.clipboard.writeText(text)
toast.success(copyLabel ? t('Copied {{label}} address', { label: copyLabel }) : t('Copied to clipboard'))
@ -145,6 +162,8 @@ export default function PaytoDialog({ @@ -145,6 +162,8 @@ export default function PaytoDialog({
<DialogDescription className="text-left text-sm leading-relaxed sm:text-base">
{isLightning
? t('Create a BOLT11 invoice from this Lightning address, then pay in your connected wallet or another app.')
: showPrimaryOpen
? t('Open in your wallet app or copy the address below.')
: t('Payment address – copy to use in your wallet or app')}
</DialogDescription>
</DialogHeader>
@ -166,17 +185,32 @@ export default function PaytoDialog({ @@ -166,17 +185,32 @@ export default function PaytoDialog({
</p>
<p className="break-all font-mono text-base leading-relaxed select-text sm:text-lg">{authority}</p>
</div>
<div className="grid min-w-0 grid-cols-1 gap-2 sm:grid-cols-2">
<div className="flex min-w-0 flex-col gap-2">
{showPrimaryOpen && walletOpenUri ? (
<Button
variant="default"
className="h-11 w-full min-w-0 gap-2 text-base"
onClick={handleOpenWallet}
>
<Wallet className="size-5 shrink-0" />
<span className="truncate">
{isPaytoHttpOpenUrl(walletOpenUri)
? t('Open on website')
: t('Open in wallet')}
</span>
</Button>
) : null}
<div className="grid min-w-0 grid-cols-1 gap-2 sm:grid-cols-2">
<Button
variant={showPrimaryOpen ? 'outline' : 'default'}
className="h-11 w-full min-w-0 gap-2 text-base"
onClick={() => handleCopy(authority, label)}
>
<Copy className="size-5 shrink-0" />
<span className="truncate">{t('Copy address')}</span>
</Button>
<Button
variant="secondary"
variant={showPrimaryOpen ? 'outline' : 'secondary'}
className="h-11 w-full min-w-0 gap-2 text-base"
onClick={() => handleCopy(paytoUri)}
>
@ -184,6 +218,7 @@ export default function PaytoDialog({ @@ -184,6 +218,7 @@ export default function PaytoDialog({
<span className="truncate">{t('Copy payto URI')}</span>
</Button>
</div>
</div>
</>
)}

5
src/data/payto-types.json

@ -340,7 +340,10 @@ @@ -340,7 +340,10 @@
"logoAssetPath": "src/assets/payto_logos/bitcoin-cash-bch-logo.svg",
"walletOpen": {
"scheme": "bitcoincash",
"walletApps": ["cakewallet", "ledger"]
"walletApps": ["cakewallet", "ledger"],
"walletAppUriTemplates": {
"cakewallet": "bitcoincash:{authority}"
}
},
"authority": {
"placeholder": "bitcoincash:… or q…",

1
src/i18n/locales/en.ts

@ -126,6 +126,7 @@ export default { @@ -126,6 +126,7 @@ export default {
"Lightning payment address – copy to pay via your wallet": "Lightning payment address – copy to pay via your wallet",
"Payment address": "Payment address",
"Payment address – copy to use in your wallet or app": "Payment address – copy to use in your wallet or app",
"Open in your wallet app or copy the address below.": "Open in your wallet app or copy the address below.",
"Click to open payment options": "Click to open payment options",
"Click to copy address": "Click to copy address",
"Open on website": "Open on website",

5
src/lib/payto-registry.ts

@ -33,6 +33,11 @@ export type PaytoWalletOpenRow = { @@ -33,6 +33,11 @@ export type PaytoWalletOpenRow = {
requireAtSign?: boolean
requirePrefix?: string
walletApps?: string[]
/**
* Per-app URI template overrides (e.g. Cake Wallet needs native `bitcoincash:` for BCH
* because its wallet label is "bitcoin cash", not `bitcoincash`).
*/
walletAppUriTemplates?: Record<string, string>
/** When true, {@link walletApps} are hidden until PaytoDialog supplies a BOLT11 (see `_openWith.bolt11Invoice`). */
deferWalletAppsUntilBolt11?: boolean
}

8
src/lib/payto-targets.test.ts

@ -247,6 +247,14 @@ describe('resolvePaytoPaymentOpenHandlers', () => { @@ -247,6 +247,14 @@ describe('resolvePaytoPaymentOpenHandlers', () => {
).toEqual(['PayPal'])
})
it('uses native bitcoincash: URI for Cake Wallet on BCH (not cakewallet:bitcoincash)', () => {
const addr = 'qzrvw7kxr6a2vwm6hjcpaym8znf8t4nlyd8y4f2f8k'
const handlers = resolvePaytoPaymentOpenHandlers('bitcoin-cash', addr)
const cake = handlers.find((h) => h.openTargetName === 'Cake Wallet')
expect(cake?.href).toBe(`bitcoincash:${addr}`)
expect(cake?.href).not.toMatch(/^cakewallet:/)
})
it('never mixes fiat web links or lightning wallets across payto types', () => {
const monero = resolvePaytoPaymentOpenHandlers(
'monero',

20
src/lib/payto-targets.ts

@ -155,12 +155,13 @@ export function resolveWalletAppHref( @@ -155,12 +155,13 @@ export function resolveWalletAppHref(
const build = WALLET_APP_BUILDERS[app.builder]
if (build) return build(coinScheme, auth)
}
if (!app.uriTemplate) return null
const template = walletOpen?.walletAppUriTemplates?.[appId] ?? app.uriTemplate
if (!template) return null
const payload =
coinScheme === 'lightning' || coinScheme === 'bolt12'
? auth.replace(/^lightning:/i, '')
: auth
return substituteAuthority(app.uriTemplate.replace(/\{coinScheme\}/g, coinScheme), payload)
return substituteAuthority(template.replace(/\{coinScheme\}/g, coinScheme), payload)
}
/** Primary open URL: native wallet URI, then profile web template. */
@ -314,11 +315,16 @@ export function filterPaytoPaymentOpenHandlersForDevice( @@ -314,11 +315,16 @@ export function filterPaytoPaymentOpenHandlersForDevice(
return handlers.filter((h) => !h.mobileOnly)
}
/** Open a resolved handler (new tab for https, assign for wallet schemes). */
export function openPaytoPaymentTarget(handler: PaytoPaymentOpenHandler): void {
if (handler.isHttp) {
window.open(handler.href, '_blank', 'noopener,noreferrer')
/** Open a resolved payto / wallet URL (https in a new tab, coin schemes via assign). */
export function openPaytoResolvedUrl(url: string): void {
if (isPaytoHttpOpenUrl(url)) {
window.open(url, '_blank', 'noopener,noreferrer')
return
}
window.location.assign(handler.href)
window.location.assign(url)
}
/** Open a resolved handler (new tab for https, assign for wallet schemes). */
export function openPaytoPaymentTarget(handler: PaytoPaymentOpenHandler): void {
openPaytoResolvedUrl(handler.href)
}

1
src/lib/payto.ts

@ -29,6 +29,7 @@ export { @@ -29,6 +29,7 @@ export {
export {
filterPaytoPaymentOpenHandlersForDevice,
openPaytoPaymentTarget,
openPaytoResolvedUrl,
resolvePaytoPaymentOpenHandlers,
resolvePaytoProfileUrl,
isPaytoHttpOpenUrl,

Loading…
Cancel
Save