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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
// A wallet on another ark server tells the registrar it is listening once
// its offer listener is up, so Lightning receives queued for its name go
// out now instead of on the hourly retry. A wallet on the coinos server
// never does (it is paid while asleep). Run: bun tools/names-listening-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';
import { setArkProviderId, getArkProviderId, getNetwork } from './src/api.js';
${domHelper}
const state = { names: { byDomain: { 'coinos.io': { name: 'manna', domain: 'coinos.io' } } } };
const ui = { screen: 'wallet' };
const wallet = { loadFeatureState: (k, d) => (k in state ? state[k] : d), saveFeatureState: (k, v) => { state[k] = v; }, nostrSign: async (e) => ({ ...e, id: 'ab'.repeat(32), sig: 'cd'.repeat(64), pubkey: 'aa'.repeat(32) }), nostrPubkey: () => 'aa'.repeat(32), _cacheKey: () => 'w1' };
const ctx = { h, ui, wallet, render: () => {}, toast: () => {}, copy: () => {}, copyBtn: () => null, brandHeader: () => null, goBack: () => {}, setAccount: () => {}, hook: (name) => (name === 'arkReady' ? true : null) };
const feature = namesFeature(ctx);
window.test = { listening: () => feature.namesListening(), server: (id) => setArkProviderId(id), dbg: () => ({ net: getNetwork(), prov: getArkProviderId(), avail: feature.namesAvailable() }) };
`;
const bundle = await Bun.build({
entrypoints: ['names-listen-entry'], target: 'browser',
plugins: [{ name: 'names-listen', setup(build) {
build.onResolve({ filter: /^names-listen-entry$/ }, () => ({ path: 'entry', namespace: 'names-listen' }));
build.onLoad({ filter: /.*/, namespace: 'names-listen' }, () => ({ contents: entry, loader: 'js', resolveDir: process.cwd() }));
} }],
});
assert(bundle.success, bundle.logs.join('\n'));
const html = `<!doctype html><meta charset="utf-8"><div id="app"></div><script type="module">${await bundle.outputs[0].text()}</script>`;
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));
// the registrar, faked: every call recorded, answered as told
const calls = [];
let answer = { status: 200, body: '{"waiting":1,"sat":52}' };
await page.setRequestInterception(true);
page.on('request', (r) => {
if (!r.url().startsWith('https://names.coinos.io/')) return r.continue();
const cors = { 'access-control-allow-origin': '*', 'access-control-allow-headers': '*', 'access-control-allow-methods': '*' };
if (r.method() === 'OPTIONS') return r.respond({ status: 204, headers: cors });
calls.push({ method: r.method(), path: new URL(r.url()).pathname, auth: r.headers().authorization || '' });
r.respond({ ...answer, contentType: 'application/json', headers: cors });
});
const check = (name, value, detail = '') => { assert(value, name + (detail ? ' — ' + detail : '')); console.log(' ✓ ' + name); };
const listening = () => page.evaluate(() => test.listening());
try {
await page.goto(server.url.href);
await page.waitForFunction(() => !!window.test);
check('a wallet on the coinos server says nothing', (await listening()) === null && !calls.length);
await page.evaluate(() => test.server('second'));
const r = await listening();
const posts = () => calls.filter((c) => c.method === 'POST');
check('on Second it tells the registrar it is listening', posts().length === 1 && posts()[0].path === '/listening', JSON.stringify(calls));
check('...as itself (NIP-98)', /^Nostr /.test(posts()[0].auth));
check('...and hears what is waiting', r && r.waiting === 1 && r.sat === 52, JSON.stringify(r));
check('a second listen within ten minutes is not another call', (await listening()) === null && posts().length === 1);
await page.reload(); await page.waitForFunction(() => !!window.test);
answer = { status: 500, body: '{"error":"down"}' };
const down = await listening();
check('a registrar that is down is quietly left', down === null && posts().length === 2, JSON.stringify({ down, calls }));
answer = { status: 200, body: '{"waiting":0,"sat":0}' };
check('...and asked again next time, not in ten minutes', (await listening())?.waiting === 0 && posts().length === 3);
check('no page errors', !errors.length, errors.join(' | '));
console.log('\n✅ names: a wallet on another ark server says it is listening');
} finally {
await browser.close();
server.stop(true);
}
|