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
71
72
73
74
75
|
// The public chat page (/chat) on a device that already has a wallet: its
// button opens the coinos community's room itself, not the wallet, and the
// page doesn't tell a signed-in reader to sign in. And /chat's loading shell
// is not a profile named "chat". (Andy, 2026-11-10: "I can see the feed etc
// fine, so I am logged in".) Relays are the stand-ins: nothing is sent.
//
// Run: bun tools/public-chat-join-test.js
import puppeteer from './puppeteer.js';
import { generateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { buildHtml } from '../build.js';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let ok = true;
const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; };
const html = await buildHtml({ minify: true, pwa: false });
const server = Bun.serve({ port: 5247, fetch: () => 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.setViewport({ width: 390, height: 844 });
const click = (sel, t) => page.evaluate((s, x) => { const e = [...document.querySelectorAll(s)].find((n) => n.textContent.trim().toLowerCase().includes(x.toLowerCase())); if (e) { e.click(); return true; } return false; }, sel, t);
const waitFor = async (fn, ms = 15000, ...a) => { for (let i = 0; i < ms / 200; i++) { try { if (await page.evaluate(fn, ...a)) return true; } catch {} await sleep(200); } return false; };
// did the static profile shell ever show while the page loaded?
await page.evaluateOnNewDocument(() => {
window.__shellShown = false;
const look = () => { const s = document.getElementById('boot-shell'); if (s && s.style.display !== 'none') window.__shellShown = true; };
new MutationObserver(look).observe(document, { childList: true, subtree: true, attributes: true });
document.addEventListener('DOMContentLoaded', look);
});
const head = () => page.evaluate(() => {
const c = document.querySelector('.chat-head');
return c ? { text: c.innerText, button: c.querySelector('button')?.textContent.trim() } : null;
});
try {
console.log('\n[a visitor]');
await page.goto('http://localhost:5247/chat', { waitUntil: 'domcontentloaded' });
await page.evaluate(() => localStorage.setItem('btc-wallet-network', 'regtest'));
await page.reload({ waitUntil: 'domcontentloaded' });
await waitFor(() => !!document.querySelector('.chat-head'));
let hd = await head();
check('the public chat mounts', !!hd);
check('a visitor is asked to sign in', hd?.button === 'Sign in to chat' && /sign in to join/i.test(hd.text), hd?.button);
check("/chat's loading shell is not a profile", !(await page.evaluate(() => window.__shellShown)));
await page.goto('http://localhost:5247/someone', { waitUntil: 'domcontentloaded' });
check('a name still gets its profile shell', await page.evaluate(() => window.__shellShown));
console.log('\n[with a wallet on the device]');
await page.goto('http://localhost:5247/', { waitUntil: 'domcontentloaded' });
await sleep(400);
await click('button', 'Create a new wallet');
await sleep(300);
await click('button', 'Have an existing seed');
await sleep(300);
await page.waitForSelector('textarea');
await page.type('textarea', generateMnemonic(wordlist));
await click('button', 'Open wallet');
check('wallet opens', await waitFor(() => /receive/i.test(document.body.innerText)));
await page.goto('http://localhost:5247/chat', { waitUntil: 'domcontentloaded' });
await waitFor(() => !!document.querySelector('.chat-head'));
hd = await head();
check('no "sign in" for someone signed in', !!hd && !/sign in/i.test(hd.text), hd?.text.replace(/\n/g, ' | '));
check('the button offers the chat', hd?.button === 'Join the chat', hd?.button);
await Promise.all([page.waitForNavigation({ waitUntil: 'domcontentloaded' }), click('.chat-head button', 'Join the chat')]);
const inRoom = await waitFor(() => !!document.querySelector('.chat-compose textarea, .chat-compose input, .chat-compose [contenteditable]'), 20000);
check('it lands in the community room, composer ready', inRoom, await page.evaluate(() => document.body.innerText.slice(0, 120).replace(/\n/g, ' | ')));
check('the room is the coinos community', await page.evaluate(() => /coinos/i.test(document.querySelector('.chat-head, .chat-title')?.innerText || '')));
check('the marker is gone from the address', await page.evaluate(() => location.pathname === '/' && !location.search));
} finally {
await browser.close();
server.stop(true);
}
console.log(ok ? '\nPASS' : '\nFAIL');
process.exit(ok ? 0 : 1);
|