diff options
| -rw-r--r-- | src/features/messages.js | 6 | ||||
| -rw-r--r-- | src/i18n.js | 2 | ||||
| -rw-r--r-- | src/nip29-client.js | 9 | ||||
| -rw-r--r-- | tools/nip29-group-test.js | 9 |
4 files changed, 23 insertions, 3 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 85b44f3..d5c7943 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -17651,7 +17651,10 @@ export function messagesFeature(ctx) { // one list, newest first: a group chat is a conversation like any other const chatRows = [ ...dmRows.map((x) => ({ at: x.last.rumor.created_at, dm: x })), - ...wnGroups().filter((g) => !g.fresh).map((g) => ({ at: (wnFold(g).rows.at(-1) || {}).rumor?.created_at || g.at, g })), + // a relay group with nothing said yet sorts by when it was joined + // HERE: one that only arrived on the 10009 list (joined elsewhere, + // maybe years ago, maybe on a relay long gone) waits at the bottom + ...wnGroups().filter((g) => !g.fresh).map((g) => ({ at: (wnFold(g).rows.at(-1) || {}).rumor?.created_at || (g.nip29 ? g.joinedAt || 0 : g.at), g })), ].sort((a, b) => b.at - a.at); const shownDms = ui.msgAllDms ? chatRows : chatRows.slice(0, LIST_PREVIEW); kids.push( @@ -18118,6 +18121,7 @@ export function messagesFeature(ctx) { h('div', { class: 'muted small chat-preview' }, g.removed ? t('msgGroupGone') : last ? (last.mine ? t('msgYouPrefix') : displayName(last.rumor.pubkey) + ':') + ' ' + (last.rumor.content || '📎 ' + t('msgGroupAttachment')) + : g.nip29 && !wn.accounts(g).length ? t(g.unreachable ? 'msgNip29Unreachable' : 'msgNip29Waiting', { host: g.relay.replace(/^wss?:\/\//, '').replace(/\/$/, '') }) : t('msgMembers', { n: wn.accounts(g).length }))), unread ? h('i', { class: 'thread-dot' }) : null); } diff --git a/src/i18n.js b/src/i18n.js index 194ce40..dde69c9 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -797,6 +797,8 @@ const STR = { msgNip29JoinRefused: 'The relay didn\'t let you in yet ({why}). You can still read the group if it\'s open.', msgNip29Info: 'Relay group {relay} · not encrypted · the relay keeps the members', msgNip29CopyAddress: 'Copy group address', + msgNip29Unreachable: "{host} isn't answering", + msgNip29Waiting: 'On {host}', msgCordnInfo: 'Cordn group · end-to-end encrypted · delivered by a coordinator', msgCordnNoFiles: 'Attachments are not available in Cordn groups yet', msgRouteTitle: 'Send messages by', diff --git a/src/nip29-client.js b/src/nip29-client.js index 4c27b64..cfbd171 100644 --- a/src/nip29-client.js +++ b/src/nip29-client.js @@ -125,7 +125,7 @@ export function nip29Client({ scope, pubkey, identity, on, listRelays, store = m function load() { if (!loaded) loaded = store.all().then((all) => { - for (const [k, v] of all) if (k.startsWith('g:') && v && v.relay && v.gid) groups.set(v.id, { ...v, log: v.log || [] }); + for (const [k, v] of all) if (k.startsWith('g:') && v && v.relay && v.gid) groups.set(v.id, { ...v, log: v.log || [], unreachable: '' }); const l = all.get('list'); if (l && Array.isArray(l.tags)) list = l; }).catch((e) => dlog('nip29: store', e)); @@ -171,8 +171,12 @@ export function nip29Client({ scope, pubkey, identity, on, listRelays, store = m function subscribe(g) { if (subs.has(g.id) || stopped) return; const since = g.seen ? g.seen - 60 : undefined; + // a relay that can't be reached (groups.0xchat.com stopped resolving) + // is said on the group's row instead of "0 members" + const reached = () => { if (g.unreachable) { g.unreachable = ''; touch(g); emit('loaded', g); } }; const a = subscribeAuthed([g.relay], { kinds: CHAT_KINDS, '#h': [g.gid], limit: 300, ...(since ? { since } : {}) }, - (ev) => { if (ev.tags.some((x) => x[0] === 'h' && x[1] === g.gid)) take(g, ev); }, { onauth }); + (ev) => { reached(); if (ev.tags.some((x) => x[0] === 'h' && x[1] === g.gid)) take(g, ev); }, + { onauth, onclose: (why) => { if (!g.log.length && !g.members.length) { g.unreachable = String((why && why[0]) || 'unreachable'); touch(g); emit('loaded', g); } } }); // metadata: only what the group's own relay signed (the copies other // relays carry are read nowhere) const b = subscribeAuthed([g.relay], { kinds: META_KINDS, '#d': [g.gid] }, @@ -238,6 +242,7 @@ export function nip29Client({ scope, pubkey, identity, on, listRelays, store = m await load(); leftNow.delete(keyOf(a.relay, a.gid)); const g = record(a.relay, a.gid); + g.joinedAt = now(); // sorts a group joined here, still quiet, as new touch(g); subscribe(g); emit('joined', g); diff --git a/tools/nip29-group-test.js b/tools/nip29-group-test.js index 9096a47..de9b8e1 100644 --- a/tools/nip29-group-test.js +++ b/tools/nip29-group-test.js @@ -122,6 +122,15 @@ try { let shown = false; for (let i = 0; i < 60 && !shown; i++) { shown = await p2.evaluate(() => [...document.querySelectorAll('.chat-thread-row')].some((r) => r.textContent.includes('pyramid runners'))); if (!shown) await sleep(250); } check('...and the group joined on the laptop appears there', !before && shown); + // a group whose relay is gone says so on its row, not "0 members" + await p2.evaluate(() => [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase() === 'new group')?.click()); await sleep(300); + await p2.evaluate(() => [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase() === 'relay group')?.click()); await sleep(200); + await p2.type('.nip29-address', "gone.invalid'deadgroup"); + await p2.evaluate(() => [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase() === 'join')?.click()); await sleep(1500); + await p2.evaluate(() => document.querySelector('.chat-head button')?.click()); await sleep(500); + let dead = ''; + for (let i = 0; i < 60 && !/answering/.test(dead); i++) { dead = await p2.evaluate(() => [...document.querySelectorAll('.chat-thread-row')].find((r) => r.textContent.includes('deadgroup'))?.textContent || ''); if (!/answering/.test(dead)) await sleep(250); } + check("a group on a relay that's gone says so", /gone\.invalid isn't answering/.test(dead), dead.slice(0, 80)); await ctx.close(); } |
