// Receive → More → Lightning invoice: the invoice says to keep coinos open // until it's paid (the server only finishes a receive while the wallet is // there to take it) and gives way to "expired" when its hour is up. // The Ark side is faked. Run: bun tools/names-invoice-expiry-test.js import assert from 'node:assert/strict'; import puppeteer from './puppeteer.js'; const app = await Bun.file('src/app.js').text(); const domHelper = app.slice(app.indexOf('function h(tag,'), app.indexOf('// ---------------------------------------------------------------- morphing')); const entry = ` import { namesFeature } from './src/features/names.js'; ${domHelper} const state = { names: { byDomain: { 'coinos.io': { name: 'alice', domain: 'coinos.io' } } } }; const ui = { screen: 'wallet', namesMore: 'bolt11' }; const wallet = { loadFeatureState: (k, d) => (k in state ? state[k] : d), saveFeatureState: (k, v) => { state[k] = v; }, nostrSign: async (e) => e, nostrPubkey: () => 'aa'.repeat(32), _cacheKey: () => 'w1' }; const ctx = { h, ui, wallet, render, toast: () => {}, copy: () => {}, copyBtn: (x, l) => h('button', { class: 'btn-sm' }, l), brandHeader: () => null, goBack: () => {}, setAccount: () => {}, hook(name, ...a) { if (name === 'arkReady') return true; if (name === 'arkMakeInvoice') return Promise.resolve({ invoice: 'lnbc10u1testinvoice', amountSat: a[0], expiresAt: Date.now() + (window.__life || 3600_000) }); return null; }, }; const feature = namesFeature(ctx); function render() { document.querySelector('#app').replaceChildren(feature.receiveModes()[0].render(h('div', { class: 'seg' }))); } window.test = { render }; render(); `; const bundle = await Bun.build({ entrypoints: ['names-inv-entry'], target: 'browser', plugins: [{ name: 'names-inv', setup(build) { build.onResolve({ filter: /^names-inv-entry$/ }, () => ({ path: 'entry', namespace: 'names-inv' })); build.onLoad({ filter: /.*/, namespace: 'names-inv' }, () => ({ contents: entry, loader: 'js', resolveDir: process.cwd() })); } }], }); assert(bundle.success, bundle.logs.join('\n')); const html = `
`; const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); const page = await browser.newPage(); const errors = []; page.on('pageerror', (e) => errors.push(e.message)); const pause = (ms) => new Promise((r) => setTimeout(r, ms)); const check = (name, value, detail = '') => { assert(value, name + (detail ? ' — ' + detail : '')); console.log(' ✓ ' + name); }; const text = () => page.evaluate(() => document.body.innerText.replace(/\s+/g, ' ')); const make = async () => { await page.evaluate(() => { const i = document.querySelector('input[type=number]'); i.value = '1000'; i.dispatchEvent(new Event('input', { bubbles: true })); }); await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => /create/i.test(b.textContent)).click()); await pause(100); }; try { await page.goto(server.url.href); await page.waitForSelector('input[type=number]'); await make(); check('the invoice shows', /lnbc10u1testinvoice/.test(await text())); check('...saying to keep coinos open until it\'s paid, and that it lasts an hour', /Keep coinos open until this is paid/.test(await text()) && /goes back to the sender/.test(await text()) && /expires in an hour/.test(await text())); await page.evaluate(() => { window.__life = 1500; document.querySelector('.btn-sm:last-child').click(); }); await pause(50); await page.evaluate(() => test.render()); await make(); check('a fresh one shows its QR', !!(await page.$('svg')) && !/expired/.test(await text())); await pause(2300); check('when its time is up it says expired, the QR gone', /This invoice has expired/.test(await text()) && !(await page.$('svg')) && !/lnbc10u1testinvoice/.test(await text())); check('...with a button for a new one', await page.evaluate(() => [...document.querySelectorAll('button')].some((b) => b.textContent.trim() === 'New invoice'))); check('no page errors', !errors.length, errors.join(' | ')); console.log('\n✅ invoice: keep open, expires'); } finally { await browser.close(); server.stop(true); }