diff options
| -rw-r--r-- | src/features/messages.js | 11 | ||||
| -rw-r--r-- | tools/feed-live-test.js | 6 | ||||
| -rw-r--r-- | tools/feed-repost-test.js | 9 |
3 files changed, 20 insertions, 6 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 99b5c48..b271bc9 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -5605,6 +5605,9 @@ export function messagesFeature(ctx) { const feedAuthors = (def = feedDef()) => { if (!def) return []; const set = new Set(def.follows ? followsNow().set : []); + // your own posts belong in the feed of the people you follow, as in + // every other client — not only for the moment after posting + if (def.follows && mePk()) set.add(mePk()); for (const pk of def.authors || []) if (pk) set.add(pk); for (const pk of def.priv || []) if (pk) set.add(pk); // a list's private members for (const pk of packAuthors(def)) set.add(pk); @@ -11772,6 +11775,8 @@ export function messagesFeature(ctx) { }); const authors = feedAuthors(def); const hasQuery = feedHasQuery(def); + // the follows feed of someone who follows nobody: only their own posts + const noFollows = !!def.follows && !followsNow().set.size && !(def.authors || []).length && !(def.priv || []).length && !feedTopics(def).length; prepareFeedAhead(c); // Never a bare spinner over posts we already hold: while the opening // page's faces and pictures are still being decoded, the cached rows @@ -11851,11 +11856,13 @@ export function messagesFeature(ctx) { h('span', { class: 'spinner sm' }), h('span', { class: 'small muted' }, t('feedOfLoading'))) : def.of && !hasQuery ? h('div', { class: 'small muted', style: 'text-align:center;padding:12px 0' }, t('feedNoFollowsOf')) - : !hasQuery + : !hasQuery || noFollows ? def.builtin ? h('div', { class: 'col', style: 'gap:8px' }, h('div', { class: 'small muted' }, t('feedNoFollows')), - h('button', { class: 'btn-sm', onClick: () => { stopFeedWatch(); hook('openUserSearch'); render(); } }, t('feedFindPeople'))) + h('button', { class: 'btn-sm', onClick: () => { stopFeedWatch(); hook('openUserSearch'); render(); } }, t('feedFindPeople')), + // your own posts, under the invitation + noFollows && visible.length && mediaMode === 'all' ? h('div', { class: 'card col notes-feed', style: 'gap:0' }, ...rows) : null) : h('div', { class: 'col', style: 'gap:8px' }, h('div', { class: 'small muted' }, t('feedNoQuery')), h('button', { class: 'btn-sm', onClick: () => openFeedEditor(def) }, t('feedEdit'))) diff --git a/tools/feed-live-test.js b/tools/feed-live-test.js index b9879d0..2bee19f 100644 --- a/tools/feed-live-test.js +++ b/tools/feed-live-test.js @@ -58,8 +58,10 @@ await page.evaluateOnNewDocument(() => { subs.push([ws, m[1]]); // A catch-up query (no since, no until) is answered from the queue // the test filled, ahead of the real relay's end-of-stream. - if (!m[2].since && !m[2].until && (window.__catchup || []).length) { - const evs = window.__catchup; window.__catchup = []; + // (only to an ask that names their author: the feed also asks for your own posts) + const wants = (ev) => !m[2].authors || m[2].authors.includes(ev.pubkey); + if (!m[2].since && !m[2].until && (window.__catchup || []).some(wants)) { + const evs = window.__catchup.filter(wants); window.__catchup = window.__catchup.filter((ev) => !wants(ev)); setTimeout(() => { for (const ev of evs) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); }, 0); } } diff --git a/tools/feed-repost-test.js b/tools/feed-repost-test.js index 7bc43aa..4936dd6 100644 --- a/tools/feed-repost-test.js +++ b/tools/feed-repost-test.js @@ -7,6 +7,7 @@ import puppeteer from './puppeteer.js'; import { cacheKeyFor } from '../src/wallet.js'; import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure'; +import { privateKeyFromSeedWords } from 'nostr-tools/nip06'; import { buildHtml } from '../build.js'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let ok = true; @@ -33,7 +34,7 @@ const page = await browser.newPage(); await page.evaluateOnNewDocument((fx) => { const Real = window.WebSocket; const fits = (ev, f) => (!f.kinds || f.kinds.includes(ev.kind)) && (!f.authors || f.authors.includes(ev.pubkey)) && (!f.ids || f.ids.includes(ev.id)); - window.WebSocket = function (...a) { const ws = new Real(...a); const send = ws.send.bind(ws); ws.send = (d) => { try { const m = JSON.parse(d); if (m[0] === 'REQ') { const fs = m.slice(2); setTimeout(() => { if (window.__live) for (const ev of fx.filter((e) => fs.some((f) => fits(e, f)))) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })); }, 0); return; } if (m[0] === 'EVENT') return; } catch {} return send(d); }; return ws; }; + window.WebSocket = function (...a) { const ws = new Real(...a); const send = ws.send.bind(ws); ws.send = (d) => { try { const m = JSON.parse(d); if (m[0] === 'REQ') { const fs = m.slice(2); setTimeout(() => { if (window.__live) for (const ev of fx.concat(window.__extra || []).filter((e) => fs.some((f) => fits(e, f)))) ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EVENT', m[1], ev]) })); ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })); }, 0); return; } if (m[0] === 'EVENT') return; } catch {} return send(d); }; return ws; }; window.WebSocket.prototype = Real.prototype; Object.assign(window.WebSocket, Real); }, POSTS); const errs = []; @@ -57,7 +58,9 @@ try { localStorage.setItem(k + ':profiles', JSON.stringify({ [z]: { name: 'Zapstore', t: Date.now() }, [l]: { name: 'LWB', t: Date.now() }, [o]: { name: 'Other', t: Date.now() } })); localStorage.setItem('btc-wallet-feed:' + k, 'following'); }, [base, ZAPSTORE.pk, LWB.pk, OTHER.pk]); - await page.evaluateOnNewDocument(() => { window.__live = true; }); + // and a post of my own, which I don't follow myself + const MINE = finalizeEvent({ kind: 1, created_at: now - 50, tags: [], content: 'my own latest post' }, privateKeyFromSeedWords(mn)); + await page.evaluateOnNewDocument((e) => { window.__live = true; window.__extra = [e]; }, MINE); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); await page.evaluate(() => document.querySelector('.app-nav-button[aria-label="Feed"]')?.click()); @@ -70,6 +73,8 @@ try { check('a note already in the feed isn\'t shown again for its repost', (t.match(/zapstore says hello itself/g) || []).length === 1); await page.evaluate(() => [...document.querySelectorAll('[data-zap-post]')].find((n) => n.textContent.includes('Tankful')).querySelector('.note-text, .note-content').click()); check('a tap opens the reposted note\'s thread', await page.evaluate(async () => { for (let i = 0; i < 20; i++) { if (location.pathname.includes('nevent') || document.querySelector('.thread-reply-send, .thread-view')) return true; await new Promise((r) => setTimeout(r, 200)); } return false; })); + await page.evaluate(() => history.back()); await sleep(800); + check('my own post is in my Following feed', await waitText('my own latest post', 10000)); check('no page errors', errs.length === 0, errs.slice(0, 2).join(' | ')); } finally { await browser.close(); server.stop(true); } console.log(ok ? '\n✅ reposts show in the feed' : '\n❌ failed'); |
