diff options
| author | Adam Soltys <[email protected]> | 2026-02-25 13:26:19 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-25 13:26:19 -0800 |
| commit | b137cdfca84cf68a2b8a620ba1c55337e88a0050 (patch) | |
| tree | c3f5482b08ce175fc4335d3f99838c06bb510877 | |
| parent | 75bc8ecc68362616a233f0852361b2dc79bcdbba (diff) | |
| download | coinos-ui-b137cdfca84cf68a2b8a620ba1c55337e88a0050.tar.gz coinos-ui-b137cdfca84cf68a2b8a620ba1c55337e88a0050.zip | |
fix lightning receive redirect
| -rw-r--r-- | src/lib/socket.ts | 9 | ||||
| -rw-r--r-- | src/lib/store.ts | 3 | ||||
| -rw-r--r-- | src/routes/(app)/[username]/receive/+page.svelte | 12 | ||||
| -rw-r--r-- | test/e2e/receive-lightning-redirect.spec.ts | 53 |
4 files changed, 74 insertions, 3 deletions
diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 077fb856..3e71e1ce 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -1,7 +1,7 @@ import { goto, invalidate } from "$app/navigation"; import { navigating, page } from "$app/stores"; import { PUBLIC_SOCKET } from "$env/static/public"; -import { event, importing, invoice, last, paymentSignal, request } from "$lib/store"; +import { event, importing, invoice, last, lastPayment, paymentSignal, request } from "$lib/store"; import { s, sleep, success, wait } from "$lib/utils"; import cookies from "js-cookie"; import { get } from "svelte/store"; @@ -54,6 +54,8 @@ export const messages = (data) => ({ return; } + if (amount > 0 && iid) lastPayment.set({ iid, amount, confirmed }); + const { url: { pathname }, } = get(page); @@ -66,7 +68,10 @@ export const messages = (data) => ({ pathname.includes("/receive") || pathname.includes("/invoice") ) { - if (!get(navigating)) goto(`/invoice/${iid}`); + if (!get(navigating) && iid) { + const target = confirmed ? `/invoice/${iid}/paid` : `/invoice/${iid}`; + goto(target); + } } else success(`${confirmed ? "Received" : "Detected"} ⚡️${s(amount)}!`); } }, diff --git a/src/lib/store.ts b/src/lib/store.ts index 57bb9735..df2c1eba 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -98,3 +98,6 @@ export const showQr = persistLocal("showQr", true); export const save = writable(); export const importing = writable(new Set<string>()); export const paymentSignal = writable(0); +export const lastPayment = writable<{ iid: string; amount: number; confirmed: boolean } | null>( + null, +); diff --git a/src/routes/(app)/[username]/receive/+page.svelte b/src/routes/(app)/[username]/receive/+page.svelte index 14489c13..a0ab38a2 100644 --- a/src/routes/(app)/[username]/receive/+page.svelte +++ b/src/routes/(app)/[username]/receive/+page.svelte @@ -3,7 +3,7 @@ import { btc, loc, post, copy, fail, f, get, types, sat, s, sats } from "$lib/utils"; import { tick, onMount, onDestroy } from "svelte"; import { browser } from "$app/environment"; - import { last, showQr, amountPrompt } from "$lib/store"; + import { last, lastPayment, showQr, amountPrompt } from "$lib/store"; import Avatar from "$comp/Avatar.svelte"; import InvoiceData from "$comp/InvoiceData.svelte"; import InvoiceActions from "$comp/InvoiceActions.svelte"; @@ -109,10 +109,20 @@ let txt = $derived([types.bitcoin, types.liquid].includes(type) ? hash : text); onMount(() => { + $lastPayment = null; if ($amountPrompt && !amount) toggleAmount(); let address_type = $page.url.searchParams.get("address_type"); if (address_type) setType(types.bitcoin, address_type); }); + + $effect(() => { + if ($lastPayment?.iid) { + const { iid, confirmed } = $lastPayment; + const target = confirmed ? `/invoice/${iid}/paid` : `/invoice/${iid}`; + $lastPayment = null; + goto(target); + } + }); </script> <div class="invoice container mx-auto max-w-xl px-4 space-y-2"> diff --git a/test/e2e/receive-lightning-redirect.spec.ts b/test/e2e/receive-lightning-redirect.spec.ts new file mode 100644 index 00000000..d6670dca --- /dev/null +++ b/test/e2e/receive-lightning-redirect.spec.ts @@ -0,0 +1,53 @@ +import { expect, test } from "@playwright/test"; +import { apiBaseUrl, bobUsername, bobPassword, login, lightningPay } from "./helpers"; + +test("redirects from /receive to /paid when lightning address payment arrives", async ({ + page, +}) => { + test.setTimeout(90_000); + + // --- Bob: log in and navigate to receive page --- + await login(page, bobUsername, bobPassword); + await page.goto(`/${bobUsername}/receive`); + await page.waitForLoadState("networkidle"); + console.log(`[e2e] Bob on receive page: ${page.url()}`); + + // Ensure we're on the receive page (not redirected to an invoice) + expect(page.url()).toContain(`/${bobUsername}/receive`); + + // Small delay to ensure websocket is connected + await page.waitForTimeout(1000); + + // --- External: create and pay LNURL invoice --- + // Step 1: Fetch LNURL pay request + const lnurlRes = await page.request.get(`${apiBaseUrl}/lnurlp/${bobUsername}`); + const lnurlData = await lnurlRes.json(); + expect(lnurlData.callback).toBeTruthy(); + console.log(`[e2e] LNURL callback: ${lnurlData.callback}`); + + // Step 2: Call callback with 100 sat (100000 msat) to get bolt11 + const callbackUrl = lnurlData.callback.replace("https://dev.coinos.io/api", apiBaseUrl); + const callbackRes = await page.request.get(`${callbackUrl}?amount=100000`); + const callbackData = await callbackRes.json(); + const bolt11 = callbackData.pr; + expect(bolt11).toBeTruthy(); + console.log(`[e2e] Got bolt11: ${bolt11.substring(0, 40)}...`); + + // Step 3: Pay from external lightning node + console.log("[e2e] Paying from external lightning node (clb)..."); + const payResult = lightningPay(bolt11); + console.log(`[e2e] Lightning pay result: ${payResult.substring(0, 100)}`); + + // --- Bob: should be redirected to /paid --- + await page.waitForURL(/\/invoice\/[^/]+\/paid/, { timeout: 15_000 }); + console.log(`[e2e] Bob redirected to: ${page.url()}`); + + // Verify success UI is shown + const heading = await page.locator("h1").first().innerText({ timeout: 5_000 }); + expect( + heading.toLowerCase().includes("payment") || heading.toLowerCase().includes("success"), + `Expected payment success/detected text, got: ${heading}`, + ).toBeTruthy(); + + if (process.env.E2E_PAUSE_AT_END === "true") await page.pause(); +}); |
