aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/InvoiceData.svelte
blob: e51d9a3bb8821e500ebd45541b38304f0a9eb18b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<script>
  import Amount from "$comp/Amount.svelte";

  import { btc, f, sat, s, sats } from "$lib/utils";
  let {
    showQr,
    src,
    link,
    txt,
    invoice,
    amount,
    amountFiat,
    currency,
    locale,
    tip,
    rate,
  } = $props();
</script>

{#if showQr}
  <div>
    <a href={link}>
      <img
        src={`/qr/${encodeURIComponent(invoice.text)}/raw`}
        class="mx-auto z-10 max-w-[360px] border-4 border-white"
        alt={txt}
      />
    </a>
  </div>
{/if}

{#if !showQr || txt.length <= 120}
  <div class="break-all text-center text-secondary text-xl">
    {txt}
  </div>
{/if}

{#each invoice.items as i}
  <div class="grid grid-cols-12 text-xl">
    <div class="col-span-1 my-auto">{i.quantity}</div>
    <div class="mr-auto grow col-span-7 my-auto">
      {i.name}
    </div>
    <div class="col-span-2 font-semibold text-right my-auto">
      {f(i.price * i.quantity, currency)}
    </div>
    <div class="col-span-2 text-secondary text-right text-lg my-auto">
      {sat(btc(i.price * i.quantity, rate))}
    </div>
  </div>
{/each}

<Amount {amount} {currency} {rate} {locale} />
w"> buildHtml } from '../build.js'; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); let ok = true; const check = (name, pass, detail = '') => { console.log(` ${pass ? '✓' : '✗'} ${name}${detail ? ` — ${detail}` : ''}`); if (!pass) ok = false; }; const now = Math.floor(Date.now() / 1000); const actorSk = generateSecretKey(); const actorPk = getPublicKey(actorSk); const noteSk = generateSecretKey(); const noteText = 'This notification excerpt came from the local note cache.'; const target = finalizeEvent({ kind: 1, created_at: now - 120, tags: [], content: noteText }, noteSk); const html = await buildHtml({ minify: true, pwa: false }); const server = Bun.serve({ port: 5307, fetch(req) { const path = new URL(req.url).pathname; if (path.startsWith('/punks') || path === '/verify-worker.js') return new Response(Bun.file('dist' + path)); return new Response(html, { headers: { 'content-type': 'text/html' } }); }, }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); const page = await browser.newPage(); await page.evaluateOnNewDocument((targetEvent) => { window.__targetReqs = 0; const Real = window.WebSocket; window.WebSocket = function (...args) { const ws = new Real(...args); const send = ws.send.bind(ws); ws.send = (data) => { try { const msg = JSON.parse(data); if (msg[0] === 'REQ') { const filter = msg[2] || {}; const hit = (filter.ids || []).includes(targetEvent.id); if (hit) window.__targetReqs++; setTimeout(() => { if (hit) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', msg[1], targetEvent]) })); ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', msg[1]]) })); }, 0); return; } if (msg[0] === 'EVENT') return; } catch {} return send(data); }; return ws; }; window.WebSocket.prototype = Real.prototype; Object.assign(window.WebSocket, Real); }, target); const clickText = (text) => page.evaluate((needle) => { const el = [...document.querySelectorAll('button')].find((node) => node.textContent.trim().toLowerCase().includes(needle)); if (!el) return false; el.click(); return true; }, text); const waitText = async (text, timeout = 20_000) => { for (let elapsed = 0; elapsed < timeout; elapsed += 200) { if ((await page.evaluate(() => document.body.innerText)).toLowerCase().includes(text.toLowerCase())) return true; await sleep(200); } return false; }; const openNotifications = () => page.evaluate(() => { const el = [...document.querySelectorAll('button')].find((node) => /notifications/i.test(node.getAttribute('aria-label') || '')); if (!el) return false; el.click(); return true; }); try { await page.setViewport({ width: 390, height: 844 }); await page.goto('http://localhost:5307/', { waitUntil: 'domcontentloaded' }); await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest')); await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400); await clickText('create a new wallet'); await page.waitForSelector('.words .w .t'); const mnemonic = (await page.$$eval('.words .w .t', (nodes) => nodes.map((node) => node.textContent.trim()))).join(' '); await clickText('skip verification'); await waitText('receive'); const base = cacheKeyFor(mnemonic + '\n'); const me = getPublicKey(privateKeyFromSeedWords(mnemonic)); const item = { id: 'a'.repeat(64), what: 'react', actor: actorPk, target: target.id, hint: [], emoji: '❤️', ts: now - 30 }; await page.evaluate(([key, owner, notification]) => { const state = JSON.parse(localStorage.getItem(key + ':messages') || '{}'); state.notifs = [notification]; state.notifsPk = owner; localStorage.setItem(key + ':messages', JSON.stringify(state)); }, [base, me, item]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive'); let cached = false; for (let i = 0; i < 75; i++) { cached = await page.evaluate(([key, id]) => { const state = JSON.parse(localStorage.getItem(key + ':messages') || '{}'); return state.notifNoteCache && state.notifNoteCache[id]?.content; }, [base, target.id]); if (cached) break; await sleep(200); } check('the referenced note is warmed and persisted', cached === noteText); check('the relay was queried while the page was still closed', (await page.evaluate(() => window.__targetReqs)) > 0); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive'); check('notifications opens from the bottom navigation', await openNotifications()); await sleep(80); const firstPaint = await page.evaluate(() => document.body.innerText); check('the cached excerpt is present immediately', firstPaint.includes('This notification excerpt')); check('there is no Fetching note placeholder', !firstPaint.includes('Fetching note')); check('the persisted note avoids another target query', (await page.evaluate(() => window.__targetReqs)) === 0); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ notification note cache works' : '\n❌ notification note cache failed'); process.exit(ok ? 0 : 1);