aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/messages.js6
-rw-r--r--src/i18n.js2
-rw-r--r--src/nip29-client.js9
-rw-r--r--tools/nip29-group-test.js9
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();
}