aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/features/messages.js11
-rw-r--r--tools/feed-live-test.js6
-rw-r--r--tools/feed-repost-test.js9
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');