diff options
| -rw-r--r-- | src/features/messages.js | 20 | ||||
| -rw-r--r-- | tools/feed-choice-per-wallet-test.js | 74 | ||||
| -rw-r--r-- | tools/feed-switch-seed-test.js | 2 | ||||
| -rw-r--r-- | tools/feed-video-aspect-test.js | 2 |
4 files changed, 91 insertions, 7 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index 68eefda..5f9d1dc 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -4054,12 +4054,18 @@ export function messagesFeature(ctx) { // clock and disk cache. A topic opened from search or a #tag in a post is // a feed too, until saved just a session one. const FOLLOWING = 'following'; - const FEED_LS = 'btc-wallet-feed'; // the feed last on screen, remembered per device + // The feed last on screen, remembered per wallet on this device. It used to + // be one device-wide key, so a brand-new account opened on whatever the + // last one was reading (its #gardenstr starter feed has the same id in + // every account). Nothing remembered: Popular. + const FEED_LS = 'btc-wallet-feed'; + const feedLsKey = () => FEED_LS + ':' + (wallet._cacheKey ? wallet._cacheKey() : ''); + const storedFeedId = () => { try { return localStorage.getItem(feedLsKey()) || null; } catch { return null; } }; const feedStates = new Map(); // id -> state const adhocFeeds = new Map(); // id -> definition, this session only // the feed of your own you last had on (never a session one) - const homeFeedId = () => { let id = FOLLOWING; try { id = localStorage.getItem(FEED_LS) || FOLLOWING; } catch {} return feedDef(id) && !adhocFeeds.has(id) ? id : FOLLOWING; }; - let curFeedId = (() => { try { return localStorage.getItem(FEED_LS) || FOLLOWING; } catch { return FOLLOWING; } })(); + const homeFeedId = () => { const id = storedFeedId() || EVERYTHING; return feedDef(id) && !adhocFeeds.has(id) ? id : EVERYTHING; }; + let curFeedId = storedFeedId() || 'all'; // EVERYTHING, declared just below (re-read per wallet in init) // Two feeds everyone has: the people you follow, and everything the coinos // relay carries. Each remembers its own relay choice in the state. const EVERYTHING = 'all'; @@ -4645,7 +4651,7 @@ export function messagesFeature(ctx) { } function feedNow() { - if (!feedDef(curFeedId)) curFeedId = FOLLOWING; // a feed deleted on another device + if (!feedDef(curFeedId)) curFeedId = EVERYTHING; // a feed deleted on another device let c = feedStates.get(curFeedId); if (!c) { let stored = []; @@ -4743,7 +4749,7 @@ export function messagesFeature(ctx) { if (feedDef(id).of) loadOfFollows(feedDef(id)); if (id !== curFeedId) { curFeedId = id; - if (!adhocFeeds.has(id)) { try { localStorage.setItem(FEED_LS, id); } catch {} } + if (!adhocFeeds.has(id)) { try { localStorage.setItem(feedLsKey(), id); } catch {} } } ui.feedId = id; stopFeedWatch(); @@ -10558,6 +10564,10 @@ export function messagesFeature(ctx) { avatarNode(pk, cls) { return avatar(pk, cls || 'chat-avatar mini', false); }, init() { const session = ++feedWarmSession; + // this wallet's own feed (the feature outlives a sign-out): a feed + // opened from a link stays put + if (!adhocFeeds.has(curFeedId)) { curFeedId = homeFeedId(); if (ui.feedId && !adhocFeeds.has(ui.feedId)) ui.feedId = curFeedId; } + try { localStorage.removeItem(FEED_LS); } catch {} // the old device-wide choice: nobody's in particular returnAfterSignin(); // a visitor who signed in from a feed or profile lands back on it // /feed opened before the wallet restored made a visitor's session copy // of Popular; with the wallet here, the built-in one takes over (two diff --git a/tools/feed-choice-per-wallet-test.js b/tools/feed-choice-per-wallet-test.js new file mode 100644 index 0000000..0642bde --- /dev/null +++ b/tools/feed-choice-per-wallet-test.js @@ -0,0 +1,74 @@ +// The feed you were on is remembered per wallet: a new account starts on +// Popular, not on whatever the last account on this device was reading. +// Run: bun tools/feed-choice-per-wallet-test.js +import puppeteer from 'puppeteer-core'; +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: 0, fetch: () => new Response(html, { headers: { 'content-type': 'text/html' } }) }); +const browser = await puppeteer.launch({ executablePath: process.env.CHROME || '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); +const page = await browser.newPage(); +const errs = []; page.on('pageerror', (e) => errs.push(String(e))); +await page.evaluateOnNewDocument(() => { + // no relays: chips and selection are all this test is about + const Real = window.WebSocket; + window.WebSocket = function (...a) { const ws = new Real(...a); ws.send = (d) => { try { const m = JSON.parse(d); if (m[0] === 'REQ') setTimeout(() => ws.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(['EOSE', m[1]]) })), 0); } catch {} }; return ws; }; + window.WebSocket.prototype = Real.prototype; Object.assign(window.WebSocket, Real); +}); +const click = (t) => page.evaluate((x) => { const e = [...document.querySelectorAll('button')].find((n) => n.textContent.trim().toLowerCase().includes(x)); if (e) { e.click(); return true; } return false; }, t); +const waitFor = async (fn, ms = 20000) => { for (let i = 0; i < ms / 200; i++) { if (await page.evaluate(fn)) return true; await sleep(200); } return false; }; +const activeChip = () => page.evaluate(() => document.querySelector('.feed-chip.on')?.textContent.trim() || null); +const openWallet = async (mn) => { + await click('create a new wallet'); await sleep(500); await click('to import'); await sleep(400); + await page.waitForSelector('textarea'); await page.type('textarea', mn); await click('open wallet'); + await waitFor(() => !!document.querySelector('[aria-label="Feed"]')); +}; +const openFeed = async () => { await page.click('[aria-label="Feed"]'); await waitFor(() => !!document.querySelector('.feed-chip.on'), 10000); await sleep(400); }; +const logout = async () => { + await page.evaluate(() => document.querySelector('.header-avatar')?.click()); await sleep(800); + await click('log out'); await sleep(400); + await page.evaluate(() => document.querySelector('.confirm-pop .btn-primary')?.click()); await sleep(800); + await click('not now'); await sleep(800); // the password offer on the way out + await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => /log out|get started|create a new wallet/i.test(b.textContent))); +}; +try { + await page.setViewport({ width: 390, height: 844 }); + await page.goto(server.url.origin, { waitUntil: 'domcontentloaded' }); + await page.evaluate(() => { localStorage.setItem('btc-wallet-network', 'regtest'); localStorage.setItem('btc-wallet-feed', 'following'); }); + await page.reload({ waitUntil: 'domcontentloaded' }); await sleep(400); + + const A = generateMnemonic(wordlist), B = generateMnemonic(wordlist); + await openWallet(A); + await openFeed(); + check('a new wallet opens the feed on Popular (an old device-wide choice is ignored)', await activeChip() === 'Popular', await activeChip()); + await page.evaluate(() => [...document.querySelectorAll('.feed-chip')].find((c) => c.textContent.trim() === 'Following')?.click()); await sleep(600); + check('wallet A switches to Following', await activeChip() === 'Following', await activeChip()); + + // logged out with no password: the saved-wallets list; a new one from there + await logout(); + await click('sign into another account'); await sleep(600); + // this screen has tabs, not the front page's buttons + await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => b.textContent.trim() === 'Import existing')?.click()); await sleep(400); + await page.waitForSelector('textarea'); await page.type('textarea', B); await click('open wallet'); + await waitFor(() => !!document.querySelector('[aria-label="Feed"]')); + await openFeed(); + check('a different new wallet starts on Popular, not on A’s Following', await activeChip() === 'Popular', await activeChip()); + + // and back to A, from the saved-wallets list (A is the first one saved) + await logout(); + await page.evaluate(() => [...document.querySelectorAll('button')].find((b) => /^[●○] Savings/.test(b.textContent.trim()))?.click()); + await waitFor(() => !!document.querySelector('[aria-label="Feed"]')); + await openFeed(); + check('A comes back to its own Following', await activeChip() === 'Following', await activeChip()); + check('no page errors', errs.length === 0, errs.join('; ').slice(0, 300)); +} catch (e) { + check('run completed', false, e.stack); +} finally { + await browser.close(); server.stop(true); +} +console.log(ok ? '\n✅ each wallet keeps its own feed; new ones start on Popular' : '\n❌ failures above'); +process.exit(ok ? 0 : 1); diff --git a/tools/feed-switch-seed-test.js b/tools/feed-switch-seed-test.js index a012dfe..e747744 100644 --- a/tools/feed-switch-seed-test.js +++ b/tools/feed-switch-seed-test.js @@ -84,7 +84,7 @@ try { s.feeds = [{ id: 'bob', name: 'Bobfeed', follows: false, authors: [b], topics: [], packs: [] }, { id: 'btc', name: 'Btcfeed', follows: false, authors: [], topics: ['bitcoin'], packs: [] }]; localStorage.setItem(k + ':messages', JSON.stringify(s)); - localStorage.setItem('btc-wallet-feed', 'following'); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // this wallet's last feed }, [base, PK_A, PK_B, alice]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); diff --git a/tools/feed-video-aspect-test.js b/tools/feed-video-aspect-test.js index 2a678bd..d3228db 100644 --- a/tools/feed-video-aspect-test.js +++ b/tools/feed-video-aspect-test.js @@ -101,7 +101,7 @@ try { localStorage.setItem(k + ':follows', JSON.stringify({ tags: [['p', pk]], c: '', at: Math.floor(Date.now() / 1000) })); localStorage.setItem(k + ':feedNotes', JSON.stringify(ns)); localStorage.setItem(k + ':profiles', JSON.stringify({ [pk]: { name: 'Clipper', t: Date.now() } })); - localStorage.setItem('btc-wallet-feed', 'following'); + localStorage.setItem('btc-wallet-feed:' + k, 'following'); // this wallet's last feed }, [key, PK, notes]); await page.reload({ waitUntil: 'domcontentloaded' }); await waitText('receive', 20000); |
