Implement append-only rendering for feed and post pages (task 1.1)
Split renderFeed() into full rebuild + prependPostCard() on both feed.html and post.html. New live events now prepend a single card at the top of the feed without rebuilding existing cards, eliminating O(n) DOM rebuilds on every incoming event. DOM is trimmed to displayCount on overflow with renderedPostIds kept in sync. Full rebuild path preserved for See More, mute removal, bootstrap completion, and event mode. Also added implementation checklist to plans/long-running-memory-bloat-fix.md.
This commit is contained in:
504
plans/long-running-memory-bloat-fix.md
Normal file
504
plans/long-running-memory-bloat-fix.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# Long-Running Memory Bloat & Unresponsiveness Fix Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
When the client runs for extended periods, pages become bloated and unresponsive. The root causes fall into four categories:
|
||||
|
||||
1. **Unbounded in-memory growth** — arrays/Maps/Sets that accumulate events without eviction
|
||||
2. **Listener/event accumulation** — DOM listeners and worker subscriptions that are never cleaned up
|
||||
3. **Over-aggressive polling** — 1-second intervals on every page, each triggering multiple worker round-trips
|
||||
4. **Full re-renders** — `innerHTML = ''` + rebuild on every incoming event, discarding DOM and re-attaching all listeners
|
||||
|
||||
The SharedWorker broadcasts every subscription event to every connected tab, and pages process all of them regardless of which subscription they originated from. Combined with full-DOM-rebuild rendering, this causes O(n) work on every event and linear memory growth over the session lifetime.
|
||||
|
||||
## Scope
|
||||
|
||||
**Pages affected:** `index.html`, `feed.html`, `post.html`, `notifications.html`, `relays.html`
|
||||
**Worker affected:** `ndk-worker.js`
|
||||
**Shared module affected:** `js/init-ndk.mjs`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Worker[ndk-worker.js SharedWorker]
|
||||
sub[activeSubscriptions Map]
|
||||
stats[relayActivityStats Map]
|
||||
ports[connectedPorts array]
|
||||
wallet[lightwalletTokenEvents Map]
|
||||
broadcast[broadcast to all ports]
|
||||
end
|
||||
|
||||
subgraph Tabs[Open Tabs - each gets ALL broadcasts]
|
||||
index[index.html]
|
||||
feed[feed.html]
|
||||
post[post.html]
|
||||
notif[notifications.html]
|
||||
relays[relays.html]
|
||||
end
|
||||
|
||||
sub -->|event/eose per sub| broadcast
|
||||
stats -->|relayActivity per read/write| broadcast
|
||||
broadcast -->|unfiltered| index
|
||||
broadcast -->|unfiltered| feed
|
||||
broadcast -->|unfiltered| post
|
||||
broadcast -->|unfiltered| notif
|
||||
broadcast -->|unfiltered| relays
|
||||
|
||||
feed -->|posts array grows forever| MemLeak[Memory Leak]
|
||||
notif -->|notificationsById grows forever| MemLeak
|
||||
notif -->|document.click leaked per row| ListenerLeak[Listener Leak]
|
||||
feed -->|innerHTML rebuild per event| CPUSpike[CPU Spike]
|
||||
relays -->|table rebuild every 5s| CPUSpike
|
||||
```
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: P0 — Critical Rendering & Listener Leaks
|
||||
|
||||
**Goal:** Eliminate the two most severe causes of user-visible jank and memory growth.
|
||||
|
||||
#### 1.1 Append-only rendering for feed and post pages
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html), [`www/post.html`](www/post.html)
|
||||
|
||||
**Current behavior:** [`renderFeed()`](www/feed.html:460) does `divFeed.innerHTML = ''` and rebuilds all cards on every incoming event.
|
||||
|
||||
**Changes:**
|
||||
- Split `renderFeed()` into two functions:
|
||||
- `renderFeed()` — full rebuild, only called on initial load, "See More", sort changes, or mute removal
|
||||
- `prependPostCard(post)` — creates a single card, inserts it at the top of `divFeed` if newer than the first visible post, and wires interactions for just that one card
|
||||
- In the `ndkEvent` handler, call `prependPostCard(evt)` instead of `renderFeed()` when a new kind-1 event arrives and the feed is already rendered
|
||||
- Cap the visible DOM: if `divFeed.children.length > displayCount + SEE_MORE_INCREMENT`, remove the last child
|
||||
- Keep `posts[]` sorted by inserting new events in the correct position (binary insert or simple unshift + periodic sort)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- New posts appear at the top without rebuilding existing cards
|
||||
- Existing images/videos are not reloaded when a new post arrives
|
||||
- "See More" still does a full re-render to show additional posts
|
||||
|
||||
#### 1.2 Append-only rendering for notifications page
|
||||
|
||||
**Files:** [`www/notifications.html`](www/notifications.html)
|
||||
|
||||
**Current behavior:** [`renderNotifications()`](www/notifications.html:886) does `divNotifications.innerHTML = ''` and rebuilds all rows on every new notification.
|
||||
|
||||
**Changes:**
|
||||
- Split into `renderNotifications()` (full rebuild for filter changes / "View more") and `prependNotificationRow(item)` (single new row)
|
||||
- In `addNotificationFromEvent`, call `prependNotificationRow` when the notifications list is already rendered and the new item passes the filter
|
||||
- Cap visible DOM to `visibleNotificationsCount + NOTIFICATIONS_PAGE_SIZE`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- New notifications appear without rebuilding existing rows
|
||||
- Filter toggles and "View more" still do full re-renders
|
||||
|
||||
#### 1.3 Fix notification `document.click` listener leak
|
||||
|
||||
**Files:** [`www/notifications.html`](www/notifications.html)
|
||||
|
||||
**Current behavior:** [`renderNotifications()`](www/notifications.html:1046) registers a `document.addEventListener('click', ...)` for **every** notification row, and these are never removed.
|
||||
|
||||
**Changes:**
|
||||
- Remove the per-row `document.addEventListener('click', ...)` entirely
|
||||
- Register a **single** document-level click listener in `main()` that closes any open menu panel by checking `document.querySelector('.notif-menu-btn[aria-expanded="true"]')` or similar
|
||||
- Alternatively, use a module-level `openMenuPanel` variable; the single listener checks if the click target is outside `openMenuPanel` and closes it
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Only one document click listener exists regardless of notification count
|
||||
- Menu panels still close when clicking outside
|
||||
|
||||
#### 1.4 Cap `posts[]` and `postIds` with rolling eviction
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html), [`www/post.html`](www/post.html)
|
||||
|
||||
**Current behavior:** `posts` array and `postIds` Set grow without bound.
|
||||
|
||||
**Changes:**
|
||||
- Add `const MAX_STORED_POSTS = 500` constant
|
||||
- In `upsertFeedPost` / `upsertPost`, after pushing a new event, if `posts.length > MAX_STORED_POSTS`, sort by `created_at` descending, slice to `MAX_STORED_POSTS`, and rebuild `postIds` from the remaining array
|
||||
- Also remove evicted post IDs from `renderedPostIds`
|
||||
- In `post.html` event mode, skip this (single event display)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `posts.length` never exceeds `MAX_STORED_POSTS`
|
||||
- Evicted posts are removed from `postIds` and `renderedPostIds`
|
||||
- "See More" still works within the capped array
|
||||
|
||||
#### 1.5 Cap `notificationsById` and `unreadNotificationsById` with periodic pruning
|
||||
|
||||
**Files:** [`www/notifications.html`](www/notifications.html)
|
||||
|
||||
**Current behavior:** Both Maps grow without bound; [`pruneNotificationsByCurrentSettings`](www/notifications.html:570) only runs on filter toggles.
|
||||
|
||||
**Changes:**
|
||||
- Add `const MAX_STORED_NOTIFICATIONS = 500`
|
||||
- Add a `pruneNotificationMaps()` function that:
|
||||
- Sorts `notificationsById` entries by `created_at` descending
|
||||
- Keeps only the top `MAX_STORED_NOTIFICATIONS`
|
||||
- Deletes evicted entries from both `notificationsById` and `unreadNotificationsById`
|
||||
- Also prunes `postImageCache` to an LRU of 200 entries
|
||||
- Call `pruneNotificationMaps()` at the end of `addNotificationFromEvent` if `notificationsById.size > MAX_STORED_NOTIFICATIONS + 100`
|
||||
- Also call it after `hydrateNotificationsFromCache` and `hydrateNotificationsFromRelays`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `notificationsById.size` never exceeds `MAX_STORED_NOTIFICATIONS + 100`
|
||||
- `postImageCache.size` never exceeds 200
|
||||
- Filter counts remain accurate after pruning
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: P1 — Subscription Hygiene & Polling Reduction
|
||||
|
||||
**Goal:** Stop the cross-tab event storm and reduce worker message queue saturation.
|
||||
|
||||
#### 2.1 Filter `ndkEvent` by `subId` on the page side
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html), [`www/post.html`](www/post.html), [`www/notifications.html`](www/notifications.html), [`www/js/init-ndk.mjs`](www/js/init-ndk.mjs)
|
||||
|
||||
**Current behavior:** The worker includes `subId` in event broadcasts ([`ndk-worker.js:1094`](www/ndk-worker.js:1094)), but pages' `ndkEvent` handlers process all events regardless of source subscription.
|
||||
|
||||
**Changes:**
|
||||
- In `init-ndk.mjs`, the `ndkEvent` CustomEvent already includes the full `message.data` — verify `subId` is in `event.detail`
|
||||
- Each page tracks its own active subscription IDs in a `Set`:
|
||||
- `feed.html`: track the kind-3 sub and the live feed sub
|
||||
- `post.html`: track the kind-1 author/event sub
|
||||
- `notifications.html`: track the notifications sub
|
||||
- In the `ndkEvent` handler, check `if (!mySubscriptionIds.has(event.detail.subId)) return;` before processing
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Pages only process events from their own subscriptions
|
||||
- Events from other tabs' subscriptions are ignored (no CPU work, no DOM updates)
|
||||
|
||||
#### 2.2 Close old live subscriptions on follow-list change
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html)
|
||||
|
||||
**Current behavior:** [`ensureLiveFeedSubscription`](www/feed.html:548) creates a new subscription when follows change but never closes the old one.
|
||||
|
||||
**Changes:**
|
||||
- Add a module-level `let liveFeedSub = null;` variable
|
||||
- In `ensureLiveFeedSubscription`, before creating the new subscription:
|
||||
```js
|
||||
if (liveFeedSub?.close) {
|
||||
liveFeedSub.close();
|
||||
}
|
||||
```
|
||||
- Assign the return of `subscribe()` to `liveFeedSub`
|
||||
- Also track the kind-3 subscription similarly and close it on logout
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Only one live feed subscription exists at a time in the worker
|
||||
- `activeSubscriptions` Map in the worker doesn't accumulate stale feed subscriptions
|
||||
|
||||
#### 2.3 Increase footer polling interval to 5 seconds (or event-driven)
|
||||
|
||||
**Files:** [`www/index.html`](www/index.html), [`www/feed.html`](www/feed.html), [`www/notifications.html`](www/notifications.html), [`www/relays.html`](www/relays.html)
|
||||
|
||||
**Current behavior:** `setInterval(UpdateFooter, 1000)` on every page.
|
||||
|
||||
**Changes:**
|
||||
- Change all 1-second footer intervals to 5 seconds (matching `post.html` which already uses 5s)
|
||||
- `index.html` line 850: `setInterval(UpdateFooter, 5000)`
|
||||
- `feed.html` line 692: `setInterval(updateFooter, 5000)`
|
||||
- `notifications.html` line 1390: `setInterval(UpdateFooter, 5000)`
|
||||
- `relays.html` line 1794: `setInterval(UpdateFooter, 5000)`
|
||||
- **Future enhancement (not in this phase):** Make footer fully event-driven by reacting to `ndkRelays` and `ndkRelayActivity` events instead of polling
|
||||
|
||||
**Acceptance criteria:**
|
||||
- No page polls the worker for footer data more than once per 5 seconds
|
||||
- Footer still shows current relay status (5s staleness is acceptable)
|
||||
|
||||
#### 2.4 Diff-update relay table instead of full rebuild
|
||||
|
||||
**Files:** [`www/relays.html`](www/relays.html)
|
||||
|
||||
**Current behavior:** [`createRelayTable`](www/relays.html:795) does `divRelays.innerHTML = html` every 5 seconds, destroying all rows, inputs, and listeners.
|
||||
|
||||
**Changes:**
|
||||
- Split `createRelayTable` into:
|
||||
- `buildRelayTable(relays)` — initial build (full innerHTML)
|
||||
- `updateRelayTableRows(relays)` — in-place update of existing rows:
|
||||
- Update status icon, read/write counts, connection time text
|
||||
- Update checkbox SVGs if relay type changed
|
||||
- Add/remove rows if relay count changed
|
||||
- Preserve the add-relay input value and focus
|
||||
- `refreshRelayData` calls `updateRelayTableRows` if the table already exists and the relay count matches; otherwise calls `buildRelayTable`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Relay table updates without losing input focus or re-attaching all listeners
|
||||
- Add-relay input value is preserved across refreshes (already partially implemented)
|
||||
|
||||
#### 2.5 Throttle `relayActivity` broadcasts in the worker
|
||||
|
||||
**Files:** [`www/ndk-worker.js`](www/ndk-worker.js)
|
||||
|
||||
**Current behavior:** [`trackRelayRead`](www/ndk-worker.js:828) and [`trackRelayWrite`](www/ndk-worker.js:850) broadcast `relayActivity` on every single event read/write.
|
||||
|
||||
**Changes:**
|
||||
- Add a `relayActivityBroadcastTimer` and a `pendingRelayActivity` Set of relay URLs that have had activity since the last broadcast
|
||||
- In `trackRelayRead` / `trackRelayWrite`, add the relay URL to `pendingRelayActivity` and schedule a 500ms batched broadcast if not already scheduled
|
||||
- The batched broadcast sends one `relayActivity` message per pending relay with current stats, then clears the set
|
||||
- This reduces broadcast frequency from per-event to per-500ms-batch
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `relayActivity` broadcasts are batched to at most once per 500ms
|
||||
- Stats counts remain accurate (they're cumulative)
|
||||
- Footer/sidenav relay indicators still update within 1s of activity
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: P2 — Worker Cleanup & Cache Eviction
|
||||
|
||||
**Goal:** Prevent unbounded growth in worker-side state and clean up dead connections.
|
||||
|
||||
#### 3.1 Prune `connectedPorts` on tab close
|
||||
|
||||
**Files:** [`www/ndk-worker.js`](www/ndk-worker.js)
|
||||
|
||||
**Current behavior:** [`connectedPorts`](www/ndk-worker.js:260) accumulates dead ports; [`broadcast()`](www/ndk-worker.js:2479) iterates all of them.
|
||||
|
||||
**Changes:**
|
||||
- In the `onconnect` handler ([`ndk-worker.js:7600`](www/ndk-worker.js:7600)), add:
|
||||
```js
|
||||
port.onclose = () => {
|
||||
connectedPorts = connectedPorts.filter(p => p !== port);
|
||||
if (activeSigningPort === port) activeSigningPort = null;
|
||||
};
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `connectedPorts` only contains live ports
|
||||
- `broadcast()` doesn't iterate dead ports
|
||||
|
||||
#### 3.2 Evict `inlineComposerInstances` on re-render
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html), [`www/post.html`](www/post.html)
|
||||
|
||||
**Current behavior:** `inlineComposerInstances` Map retains entries with detached `hostEl` after `renderFeed()` clears the container.
|
||||
|
||||
**Changes:**
|
||||
- At the start of `renderFeed()` (the full rebuild path), after `divFeed.innerHTML = ''`:
|
||||
```js
|
||||
inlineComposerInstances.clear();
|
||||
```
|
||||
- Since the DOM is rebuilt, all composer host elements are gone; the Map entries are pure garbage
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `inlineComposerInstances` is empty after a full re-render
|
||||
- Composers can still be re-created on demand via `getOrCreateInlineComposer`
|
||||
|
||||
#### 3.3 Prune temporary relay Maps after broadcast publish
|
||||
|
||||
**Files:** [`www/ndk-worker.js`](www/ndk-worker.js)
|
||||
|
||||
**Current behavior:** `relayActivityStats`, `relayConnectAttemptStartedAt`, `relayLastDisconnectMeta` accumulate entries for every temporary broadcast relay.
|
||||
|
||||
**Changes:**
|
||||
- After the broadcast publish `phase: 'done'` block ([`ndk-worker.js:6410`](www/ndk-worker.js:6410)), add a cleanup step:
|
||||
```js
|
||||
const userRelayUrls = new Set(Array.from(relayTypes.keys()).map(normalizeRelayUrl));
|
||||
for (const url of relayActivityStats.keys()) {
|
||||
if (!userRelayUrls.has(url)) relayActivityStats.delete(url);
|
||||
}
|
||||
relayConnectAttemptStartedAt.clear(); // only used during active connect attempts
|
||||
// Keep relayLastDisconnectMeta for reconnect detection
|
||||
```
|
||||
- Only prune `relayActivityStats` — the disconnect meta is needed for reconnect subscription rebuilding
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `relayActivityStats` only contains entries for the user's own relays after a broadcast completes
|
||||
- Reconnect detection still works (disconnect meta preserved)
|
||||
|
||||
#### 3.4 Periodic reconciliation of `lightwalletTokenEvents`
|
||||
|
||||
**Files:** [`www/ndk-worker.js`](www/ndk-worker.js)
|
||||
|
||||
**Current behavior:** `lightwalletTokenEvents` and `lightwalletDeletedIds` grow monotonically; deleted entries are only removed during `publishDirectProofs`.
|
||||
|
||||
**Changes:**
|
||||
- Add a 5-minute interval timer `lightwalletReconcileTimer` that:
|
||||
- Iterates `lightwalletTokenEvents` and removes any entry whose id is in `lightwalletDeletedIds`
|
||||
- Optionally trims `lightwalletDeletedIds` to the most recent 500 entries (keep recent ones to prevent re-processing)
|
||||
- Clear this timer in `handleDisconnect`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `lightwalletTokenEvents` doesn't retain deleted token events
|
||||
- `lightwalletDeletedIds` is bounded to ~500 entries
|
||||
|
||||
#### 3.5 Clear health-check and prune intervals on disconnect
|
||||
|
||||
**Files:** [`www/ndk-worker.js`](www/ndk-worker.js)
|
||||
|
||||
**Current behavior:** `startRelayHealthCheck` ([`ndk-worker.js:1262`](www/ndk-worker.js:1262)) and `scheduleRelayEventPruning` ([`ndk-worker.js:789`](www/ndk-worker.js:789)) create intervals that are never cleared.
|
||||
|
||||
**Changes:**
|
||||
- Store the health-check interval ID in a module-level variable `relayHealthCheckTimer`
|
||||
- Store the prune interval ID in `relayEventsPruneTimer` (already exists)
|
||||
- In `handleDisconnect()` ([`ndk-worker.js:7350`](www/ndk-worker.js:7350)), add:
|
||||
```js
|
||||
if (relayHealthCheckTimer) { clearInterval(relayHealthCheckTimer); relayHealthCheckTimer = null; }
|
||||
if (relayEventsPruneTimer) { clearInterval(relayEventsPruneTimer); relayEventsPruneTimer = null; }
|
||||
```
|
||||
|
||||
**Acceptance criteria:**
|
||||
- No duplicate intervals after re-init
|
||||
- Intervals are cleared on disconnect
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: P3 — Minor Leaks & Polish
|
||||
|
||||
**Goal:** Clean up remaining minor leaks and harden against edge cases.
|
||||
|
||||
#### 4.1 Clear `refreshTimes` interval on notifications logout
|
||||
|
||||
**Files:** [`www/notifications.html`](www/notifications.html)
|
||||
|
||||
**Current behavior:** [`setInterval(refreshTimes, 30000)`](www/notifications.html:1392) is never assigned to a variable and can't be cleared.
|
||||
|
||||
**Changes:**
|
||||
- Assign to `let refreshTimesIntervalId = setInterval(refreshTimes, 30000)`
|
||||
- Clear it in `Logout()`
|
||||
|
||||
#### 4.2 Remove duplicate `window.addEventListener('message', ...)` in relays.html
|
||||
|
||||
**Files:** [`www/relays.html`](www/relays.html)
|
||||
|
||||
**Current behavior:** [`relays.html:1777`](www/relays.html:1777) has a fallback `message` listener that duplicates the `ndkRelayActivity` handler.
|
||||
|
||||
**Changes:**
|
||||
- Remove the `window.addEventListener('message', ...)` block at lines 1777–1783
|
||||
- The `ndkRelayActivity` handler at line 1762 already covers this
|
||||
|
||||
#### 4.3 Add `beforeunload` cleanup for subscriptions on all pages
|
||||
|
||||
**Files:** [`www/feed.html`](www/feed.html), [`www/post.html`](www/post.html), [`www/notifications.html`](www/notifications.html)
|
||||
|
||||
**Current behavior:** Pages don't close their subscriptions on `beforeunload`, leaving stale entries in the worker's `activeSubscriptions` Map until the worker detects the port close.
|
||||
|
||||
**Changes:**
|
||||
- Add `window.addEventListener('beforeunload', () => { disconnect(); })` (or close individual subscriptions) on each page
|
||||
- The `disconnect()` function in `init-ndk.mjs` already sends a `disconnect` message and closes the port
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Worker's `activeSubscriptions` is cleaned up when a tab closes
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Manual Testing
|
||||
1. Open `index.html`, `feed.html`, `post.html`, `notifications.html`, `relays.html` in separate tabs
|
||||
2. Leave them running for 30+ minutes with active subscriptions
|
||||
3. Check browser DevTools Memory tab:
|
||||
- Take heap snapshots at 5min, 15min, 30min
|
||||
- Verify heap growth is sublinear (not growing with event count)
|
||||
4. Check Performance tab:
|
||||
- Record a 30s profile while events are flowing
|
||||
- Verify no full `renderFeed` / `renderNotifications` on every event
|
||||
5. Check that relay footer updates within 5s of connection changes
|
||||
6. Check that notification menu panels still open/close correctly
|
||||
|
||||
### Automated Checks
|
||||
- After each phase, run the existing test suite (`tests/broadcast-relay-test.sh` etc.)
|
||||
- Add a memory regression test if feasible (puppeteer heap snapshot comparison)
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Phase | Risk | Mitigation |
|
||||
|-------|------|------------|
|
||||
| Phase 1 (append-only) | Sort order bugs if new event is older than visible posts | Only prepend if newer than first visible; fall back to full render otherwise |
|
||||
| Phase 1 (listener fix) | Menu panel might not close in all cases | Test thoroughly; use single delegated listener |
|
||||
| Phase 2 (subId filter) | Might miss events if subId tracking is wrong | Log filtered vs processed events during testing |
|
||||
| Phase 2 (polling change) | Footer might feel less responsive | 5s is still fast; relay status rarely changes in seconds |
|
||||
| Phase 3 (worker cleanup) | Might prune stats too aggressively | Only prune temp relay stats, not user's own relays |
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Phases |
|
||||
|------|--------|
|
||||
| `www/feed.html` | 1, 2, 3, 4 |
|
||||
| `www/post.html` | 1, 2, 3, 4 |
|
||||
| `www/notifications.html` | 1, 2, 3, 4 |
|
||||
| `www/relays.html` | 2, 4 |
|
||||
| `www/index.html` | 2 |
|
||||
| `www/ndk-worker.js` | 2, 3 |
|
||||
| `www/js/init-ndk.mjs` | 2 |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Phase 1: P0 — Critical Rendering & Listener Leaks
|
||||
|
||||
- [x] **1.1** Append-only rendering for feed and post pages
|
||||
- [x] Split `renderFeed()` into full rebuild + `prependPostCard()`
|
||||
- [x] New posts prepend without rebuilding existing cards
|
||||
- [x] Cap visible DOM to `displayCount` (trims last child on overflow)
|
||||
- [x] "See More" still does full re-render
|
||||
- [x] Verified: `data-post-id` attribute set by `renderPostItem` for trim logic
|
||||
- [x] Reviewed: no errors found, fallback paths correct
|
||||
- [ ] **1.2** Append-only rendering for notifications page
|
||||
- [ ] Split `renderNotifications()` into full rebuild + `prependNotificationRow()`
|
||||
- [ ] New notifications prepend without rebuilding existing rows
|
||||
- [ ] Filter toggles and "View more" still do full re-renders
|
||||
- [ ] **1.3** Fix notification `document.click` listener leak
|
||||
- [ ] Remove per-row `document.addEventListener('click', ...)`
|
||||
- [ ] Register single delegated document-level click listener in `main()`
|
||||
- [ ] Menu panels still close when clicking outside
|
||||
- [ ] **1.4** Cap `posts[]` and `postIds` with rolling eviction
|
||||
- [ ] Add `MAX_STORED_POSTS = 500` constant
|
||||
- [ ] Evict oldest posts when array exceeds cap
|
||||
- [ ] Rebuild `postIds` and `renderedPostIds` after eviction
|
||||
- [ ] **1.5** Cap `notificationsById` / `unreadNotificationsById` with periodic pruning
|
||||
- [ ] Add `MAX_STORED_NOTIFICATIONS = 500`
|
||||
- [ ] Add `pruneNotificationMaps()` function
|
||||
- [ ] Call prune on overflow, after cache/relay hydration
|
||||
- [ ] LRU-cap `postImageCache` to 200 entries
|
||||
|
||||
### Phase 2: P1 — Subscription Hygiene & Polling Reduction
|
||||
|
||||
- [ ] **2.1** Filter `ndkEvent` by `subId` on the page side
|
||||
- [ ] Verify `subId` is in `event.detail` from init-ndk.mjs
|
||||
- [ ] Track active subscription IDs per page
|
||||
- [ ] Ignore events from foreign subscriptions
|
||||
- [ ] **2.2** Close old live subscriptions on follow-list change
|
||||
- [ ] Store live feed subscription handle
|
||||
- [ ] Close previous subscription before creating new one
|
||||
- [ ] **2.3** Increase footer polling to 5 seconds
|
||||
- [ ] index.html: 1s → 5s
|
||||
- [ ] feed.html: 1s → 5s
|
||||
- [ ] notifications.html: 1s → 5s
|
||||
- [ ] relays.html: 1s → 5s
|
||||
- [ ] **2.4** Diff-update relay table instead of full rebuild
|
||||
- [ ] Split `createRelayTable` into build + update
|
||||
- [ ] In-place update of status icons, counts, checkboxes
|
||||
- [ ] Preserve add-relay input focus across refreshes
|
||||
- [ ] **2.5** Throttle `relayActivity` broadcasts in the worker
|
||||
- [ ] Add batched broadcast with 500ms timer
|
||||
- [ ] Coalesce per-event broadcasts into per-batch
|
||||
- [ ] Stats counts remain accurate
|
||||
|
||||
### Phase 3: P2 — Worker Cleanup & Cache Eviction
|
||||
|
||||
- [ ] **3.1** Prune `connectedPorts` on tab close
|
||||
- [ ] Add `port.onclose` handler in `onconnect`
|
||||
- [ ] Clear `activeSigningPort` if it was the closed port
|
||||
- [ ] **3.2** Evict `inlineComposerInstances` on re-render
|
||||
- [ ] Clear Map after `innerHTML = ''` in full rebuild path
|
||||
- [ ] **3.3** Prune temporary relay Maps after broadcast publish
|
||||
- [ ] Remove non-user relay entries from `relayActivityStats`
|
||||
- [ ] Clear `relayConnectAttemptStartedAt`
|
||||
- [ ] Preserve `relayLastDisconnectMeta` for reconnect detection
|
||||
- [ ] **3.4** Periodic reconciliation of `lightwalletTokenEvents`
|
||||
- [ ] Add 5-minute interval timer
|
||||
- [ ] Remove deleted entries from `lightwalletTokenEvents`
|
||||
- [ ] Trim `lightwalletDeletedIds` to 500
|
||||
- [ ] Clear timer in `handleDisconnect`
|
||||
- [ ] **3.5** Clear health-check and prune intervals on disconnect
|
||||
- [ ] Store health-check interval ID
|
||||
- [ ] Clear both intervals in `handleDisconnect`
|
||||
|
||||
### Phase 4: P3 — Minor Leaks & Polish
|
||||
|
||||
- [ ] **4.1** Clear `refreshTimes` interval on notifications logout
|
||||
- [ ] **4.2** Remove duplicate `message` listener in relays.html
|
||||
- [ ] **4.3** Add `beforeunload` subscription cleanup on all pages
|
||||
2100
tests/broadcast-relay-results.txt
Normal file
2100
tests/broadcast-relay-results.txt
Normal file
File diff suppressed because it is too large
Load Diff
95
tests/broadcast-relay-test.sh
Executable file
95
tests/broadcast-relay-test.sh
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/bin/bash
|
||||
# tests/broadcast-relay-test.sh
|
||||
#
|
||||
# Tests publishing a kind 1 note to each relay in tests/relays.json
|
||||
# using nak, one relay at a time. Reports success/failure for each.
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/broadcast-relay-test.sh
|
||||
#
|
||||
# Requirements:
|
||||
# - nak (https://github.com/fiatjaf/nak) installed and in PATH
|
||||
# - tests/test-key.txt containing a hex private key
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
RELAYS_FILE="$SCRIPT_DIR/relays.json"
|
||||
RESULTS_FILE="$SCRIPT_DIR/broadcast-relay-results.txt"
|
||||
KEY_FILE="$SCRIPT_DIR/test-key.txt"
|
||||
|
||||
# Check for nak
|
||||
if ! command -v nak &>/dev/null; then
|
||||
echo "ERROR: nak is not installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for key file
|
||||
if [ ! -f "$KEY_FILE" ]; then
|
||||
echo "ERROR: $KEY_FILE not found. Generate one with: nak key generate > $KEY_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SEC=$(cat "$KEY_FILE" | tr -d '[:space:]')
|
||||
|
||||
# Extract relay URLs from the JSON file
|
||||
echo "Extracting relay URLs from $RELAYS_FILE..."
|
||||
mapfile -t RELAYS < <(python3 -c "
|
||||
import json
|
||||
with open('$RELAYS_FILE') as f:
|
||||
data = json.load(f)
|
||||
for tag in data:
|
||||
if isinstance(tag, list) and len(tag) >= 2 and tag[0] == 'r':
|
||||
print(tag[1])
|
||||
")
|
||||
|
||||
TOTAL=${#RELAYS[@]}
|
||||
echo "Found $TOTAL relay URLs"
|
||||
echo ""
|
||||
|
||||
CONTENT="Broadcast relay test $(date -u +%Y-%m-%dT%H:%M:%SZ) — testing relay reachability via nak"
|
||||
echo "Test content: $CONTENT"
|
||||
echo ""
|
||||
|
||||
# Initialize results file
|
||||
{
|
||||
echo "# Broadcast Relay Test Results"
|
||||
echo "# Date: $(date -u)"
|
||||
echo "# Total relays: $TOTAL"
|
||||
echo "# Test content: $CONTENT"
|
||||
echo ""
|
||||
} > "$RESULTS_FILE"
|
||||
|
||||
SUCCESS=0
|
||||
FAILED=0
|
||||
COUNT=0
|
||||
|
||||
for RELAY in "${RELAYS[@]}"; do
|
||||
COUNT=$((COUNT + 1))
|
||||
[ -z "$RELAY" ] && continue
|
||||
|
||||
printf "[%d/%d] %s ... " "$COUNT" "$TOTAL" "$RELAY"
|
||||
|
||||
RESULT=$(timeout 15 nak event --sec "$SEC" -c "$CONTENT" "$RELAY" 2>&1) || RESULT="FAILED: exit code $?"
|
||||
|
||||
if echo "$RESULT" | grep -qi "success"; then
|
||||
echo "OK"
|
||||
echo "OK: $RELAY" >> "$RESULTS_FILE"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
elif echo "$RESULT" | grep -qi "error\|failed\|refused\|timeout\|reject\|blocked"; then
|
||||
echo "FAIL"
|
||||
echo "FAIL: $RELAY — $RESULT" >> "$RESULTS_FILE"
|
||||
FAILED=$((FAILED + 1))
|
||||
else
|
||||
echo "UNKNOWN"
|
||||
echo "UNKNOWN: $RELAY — $RESULT" >> "$RESULTS_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Summary ==="
|
||||
echo "Total: $TOTAL"
|
||||
echo "Success: $SUCCESS"
|
||||
echo "Failed: $FAILED"
|
||||
echo ""
|
||||
echo "Full results: $RESULTS_FILE"
|
||||
660
tests/broadcast-test-output.txt
Normal file
660
tests/broadcast-test-output.txt
Normal file
@@ -0,0 +1,660 @@
|
||||
Extracting relay URLs from /home/user/lt/client/tests/relays.json...
|
||||
Found 648 relay URLs
|
||||
|
||||
Test content: Broadcast relay test 2026-06-30T18:29:37Z — testing relay reachability via nak
|
||||
|
||||
[1/648] wss://nos.lol/ ... OK
|
||||
[2/648] wss://relay.primal.net/ ... OK
|
||||
[3/648] wss://nostr.wine/ ... FAIL
|
||||
[4/648] wss://relay.snort.social/ ... OK
|
||||
[5/648] wss://eden.nostr.land/ ... FAIL
|
||||
[6/648] wss://relay.nostr.band/ ... FAIL
|
||||
[7/648] wss://nostr.bitcoiner.social/ ... OK
|
||||
[8/648] wss://purplepag.es/ ... FAIL
|
||||
[9/648] wss://nostr.land/ ... FAIL
|
||||
[10/648] wss://nostr-pub.wellorder.net/ ... OK
|
||||
[11/648] wss://nostr.oxtr.dev/ ... OK
|
||||
[12/648] wss://offchain.pub/ ... OK
|
||||
[13/648] wss://relay.current.fyi/ ... FAIL
|
||||
[14/648] wss://relay.nostr.bg/ ... FAIL
|
||||
[15/648] wss://pyramid.fiatjaf.com/ ... FAIL
|
||||
[16/648] wss://nostr.orangepill.dev/ ... FAIL
|
||||
[17/648] wss://relay.ditto.pub/ ... OK
|
||||
[18/648] wss://nostr.fmt.wiz.biz/ ... FAIL
|
||||
[19/648] wss://brb.io/ ... FAIL
|
||||
[20/648] wss://nostrelites.org/ ... FAIL
|
||||
[21/648] wss://puravida.nostr.land/ ... FAIL
|
||||
[22/648] wss://wot.utxo.one/ ... FAIL
|
||||
[23/648] wss://nostr.mutinywallet.com/ ... FAIL
|
||||
[24/648] wss://relay.nostr.info/ ... FAIL
|
||||
[25/648] wss://nostr.milou.lol/ ... FAIL
|
||||
[26/648] wss://relay.orangepill.dev/ ... FAIL
|
||||
[27/648] wss://relayable.org/ ... FAIL
|
||||
[28/648] wss://filter.nostr.wine/ ... FAIL
|
||||
[29/648] wss://relay.nostrplebs.com/ ... FAIL
|
||||
[30/648] wss://atlas.nostr.land/ ... FAIL
|
||||
[31/648] wss://theforest.nostr1.com/ ... FAIL
|
||||
[32/648] wss://nostr.zebedee.cloud/ ... FAIL
|
||||
[33/648] wss://relay.0xchat.com/ ... OK
|
||||
[34/648] wss://relay.nos.social/ ... OK
|
||||
[35/648] wss://welcome.nostr.wine/ ... FAIL
|
||||
[36/648] wss://bitcoiner.social/ ... FAIL
|
||||
[37/648] wss://relay.noswhere.com/ ... OK
|
||||
[38/648] wss://no.str.cr/ ... OK
|
||||
[39/648] wss://nostr-pub.semisol.dev/ ... FAIL
|
||||
[40/648] wss://nostr.einundzwanzig.space/ ... FAIL
|
||||
[41/648] wss://relay.nostr.com.au/ ... FAIL
|
||||
[42/648] wss://relay.fountain.fm/ ... OK
|
||||
[43/648] wss://relay.nostrati.com/ ... FAIL
|
||||
[44/648] wss://hist.nostr.land/ ... FAIL
|
||||
[45/648] wss://nostr-relay.wlvs.space/ ... FAIL
|
||||
[46/648] wss://nostr.onsats.org/ ... FAIL
|
||||
[47/648] wss://relay.nostrcheck.me/ ... OK
|
||||
[48/648] wss://nostr21.com/ ... FAIL
|
||||
[49/648] wss://purplerelay.com/ ... OK
|
||||
[50/648] wss://relay.bitcoinpark.com/ ... FAIL
|
||||
[51/648] wss://relay.plebstr.com/ ... FAIL
|
||||
[52/648] wss://wot.nostr.party/ ... FAIL
|
||||
[53/648] wss://yabu.me/ ... FAIL
|
||||
[54/648] wss://lightningrelay.com/ ... FAIL
|
||||
[55/648] wss://relay.noderunners.network/ ... FAIL
|
||||
[56/648] wss://spatia-arcana.com/ ... FAIL
|
||||
[57/648] wss://140.f7z.io/ ... FAIL
|
||||
[58/648] wss://nostr-01.yakihonne.com/ ... OK
|
||||
[59/648] wss://nostr.inosta.cc/ ... FAIL
|
||||
[60/648] wss://relay.f7z.io/ ... FAIL
|
||||
[61/648] wss://relay.getalby.com/v1 ... FAIL
|
||||
[62/648] wss://relay.momostr.pink/ ... FAIL
|
||||
[63/648] wss://wot.sovbit.host/ ... FAIL
|
||||
[64/648] wss://aggr.nostr.land/ ... FAIL
|
||||
[65/648] wss://bitcoinmaximalists.online/ ... FAIL
|
||||
[66/648] wss://nostr.plebchain.org/ ... FAIL
|
||||
[67/648] wss://algo.utxo.one/ ... FAIL
|
||||
[68/648] wss://at.nostrworks.com/ ... FAIL
|
||||
[69/648] wss://bevo.nostr1.com/ ... FAIL
|
||||
[70/648] wss://nostr.bitcoinplebs.de/ ... FAIL
|
||||
[71/648] wss://nostr.relayer.se/ ... FAIL
|
||||
[72/648] wss://nostr.rocks/ ... FAIL
|
||||
[73/648] wss://nostr.walletofsatoshi.com/ ... FAIL
|
||||
[74/648] wss://relay.stoner.com/ ... FAIL
|
||||
[75/648] wss://relay.utxo.one/ ... FAIL
|
||||
[76/648] wss://rsslay.nostr.net/ ... FAIL
|
||||
[77/648] ws://umbrel.local:4848/ ... FAIL
|
||||
[78/648] wss://auth.nostr1.com/ ... FAIL
|
||||
[79/648] wss://bitsat.molonlabe.holdings/ ... FAIL
|
||||
[80/648] wss://creatr.nostr.wine/ ... FAIL
|
||||
[81/648] wss://nostr.sethforprivacy.com/ ... FAIL
|
||||
[82/648] wss://nostr.slothy.win/ ... FAIL
|
||||
[83/648] wss://nostr.v0l.io/ ... FAIL
|
||||
[84/648] wss://nostr.zbd.gg/ ... FAIL
|
||||
[85/648] wss://relay.azzamo.net/ ... FAIL
|
||||
[86/648] wss://relay.nostr.net/ ... OK
|
||||
[87/648] wss://relay.nostriches.org/ ... FAIL
|
||||
[88/648] wss://relay.nostrview.com/ ... FAIL
|
||||
[89/648] wss://wot.girino.org/ ... FAIL
|
||||
[90/648] ws://127.0.0.1:4869/ ... FAIL
|
||||
[91/648] wss://e.nos.lol/ ... FAIL
|
||||
[92/648] wss://nostr-02.yakihonne.com/ ... OK
|
||||
[93/648] wss://nostr.cercatrova.me/ ... FAIL
|
||||
[94/648] wss://nostr.chaima.info/ ... OK
|
||||
[95/648] wss://nostr.cypherpunk.today/ ... FAIL
|
||||
[96/648] wss://nostr.portemonero.com/ ... FAIL
|
||||
[97/648] wss://nostr.sandwich.farm/ ... FAIL
|
||||
[98/648] wss://nostr.thesamecat.io/ ... FAIL
|
||||
[99/648] wss://nostrue.com/ ... FAIL
|
||||
[100/648] wss://relay-jp.nostr.wirednet.jp/ ... FAIL
|
||||
[101/648] wss://relay.lnau.net/ ... FAIL
|
||||
[102/648] wss://relay.siamstr.com/ ... FAIL
|
||||
[103/648] wss://relay.wavlake.com/ ... OK
|
||||
[104/648] wss://satsage.xyz/ ... FAIL
|
||||
[105/648] wss://wheat.happytavern.co/ ... OK
|
||||
[106/648] wss://wot.sudocarlos.com/ ... FAIL
|
||||
[107/648] wss://basspistol.org/ ... FAIL
|
||||
[108/648] wss://cellar.nostr.wine/ ... FAIL
|
||||
[109/648] wss://feeds.nostr.band/popular ... FAIL
|
||||
[110/648] wss://freelay.sovbit.host/ ... OK
|
||||
[111/648] wss://inbox.azzamo.net/ ... FAIL
|
||||
[112/648] wss://inbox.nostr.wine/ ... FAIL
|
||||
[113/648] wss://lightning.red/ ... FAIL
|
||||
[114/648] wss://nfdb.noswhere.com/ ... FAIL
|
||||
[115/648] wss://nostr-03.dorafactory.org/ ... FAIL
|
||||
[116/648] wss://nostr-relay.derekross.me/ ... FAIL
|
||||
[117/648] wss://nostr-relay.digitalmob.ro/ ... FAIL
|
||||
[118/648] wss://nostr.pleb.network/ ... FAIL
|
||||
[119/648] wss://nostr.semisol.dev/ ... FAIL
|
||||
[120/648] wss://nostr.thank.eu/ ... FAIL
|
||||
[121/648] wss://nwc.primal.net/ayvjleilmx0al7j2pqt24qed1z7a8s ... FAIL
|
||||
[122/648] wss://orangepiller.org/ ... FAIL
|
||||
[123/648] wss://relay.44billion.net/ ... OK
|
||||
[124/648] wss://relay.divine.video/ ... OK
|
||||
[125/648] wss://relay.mutinywallet.com/ ... FAIL
|
||||
[126/648] wss://relay.nostr.ch/ ... FAIL
|
||||
[127/648] wss://relay.nostr.nu/ ... FAIL
|
||||
[128/648] wss://relay.otherstuff.fyi/ ... FAIL
|
||||
[129/648] wss://relay.routstr.com/ ... OK
|
||||
[130/648] wss://relay.shitforce.one/ ... FAIL
|
||||
[131/648] wss://relay.utxo.one/outbox ... FAIL
|
||||
[132/648] wss://rilo.nostria.app/ ... OK
|
||||
[133/648] wss://search.nos.today/ ... FAIL
|
||||
[134/648] wss://wot.nostr.net/ ... FAIL
|
||||
[135/648] wss://wot.shaving.kiwi/ ... FAIL
|
||||
[136/648] wss://wot.siamstr.com/ ... FAIL
|
||||
[137/648] wss://xmr.usenostr.org/ ... FAIL
|
||||
[138/648] wss://zap.watch/ ... FAIL
|
||||
[139/648] ws://127.0.0.1:8888/ ... FAIL
|
||||
[140/648] wss://adult.18plus.social/ ... FAIL
|
||||
[141/648] wss://anon.computer/ ... FAIL
|
||||
[142/648] wss://bostr.azzamo.net/ ... FAIL
|
||||
[143/648] wss://chorus.mikedilger.com:444/ ... FAIL
|
||||
[144/648] wss://deschooling.us/ ... FAIL
|
||||
[145/648] wss://ditto.pub/relay ... OK
|
||||
[146/648] wss://feeds.nostr.band/typescript ... FAIL
|
||||
[147/648] wss://filter.nostr.wine/npub1s5yq6wadwrxde4lhfs56gn64hwzuhnfa6r9mj476r5s4hkunzgzqrs6q7z ... FAIL
|
||||
[148/648] wss://global-relay.cesc.trade/ ... FAIL
|
||||
[149/648] wss://greensoul.space/ ... FAIL
|
||||
[150/648] wss://haven.calva.dev/ ... FAIL
|
||||
[151/648] wss://indexer.coracle.social/ ... FAIL
|
||||
[152/648] wss://lunchbox.sandwich.farm/ ... FAIL
|
||||
[153/648] wss://me.purplerelay.com/ ... FAIL
|
||||
[154/648] wss://misskey.04.si/ ... FAIL
|
||||
[155/648] wss://news.utxo.one/ ... FAIL
|
||||
[156/648] wss://nostr-02.dorafactory.org/ ... FAIL
|
||||
[157/648] wss://nostr-1.nbo.angani.co/ ... FAIL
|
||||
[158/648] wss://nostr-2.zebedee.cloud/ ... FAIL
|
||||
[159/648] wss://nostr-dev.wellorder.net/ ... OK
|
||||
[160/648] wss://nostr-relay.bitcoin.ninja/ ... FAIL
|
||||
[161/648] wss://nostr-relay.nokotaro.com/ ... FAIL
|
||||
[162/648] wss://nostr.0x7e.xyz/ ... OK
|
||||
[163/648] wss://nostr.88mph.life/ ... OK
|
||||
[164/648] wss://nostr.azte.co/ ... FAIL
|
||||
[165/648] wss://nostr.azzamo.net/ ... OK
|
||||
[166/648] wss://nostr.bitpunk.fm/ ... FAIL
|
||||
[167/648] wss://nostr.data.haus/ ... OK
|
||||
[168/648] wss://nostr.lopp.social/ ... FAIL
|
||||
[169/648] wss://nostr.lorentz.is/ ... FAIL
|
||||
[170/648] wss://nostr.lu.ke/ ... FAIL
|
||||
[171/648] wss://nostr.roundrockbitcoiners.com/ ... FAIL
|
||||
[172/648] wss://nostr.sebastix.dev/ ... FAIL
|
||||
[173/648] wss://nostr.self-determined.de/ ... FAIL
|
||||
[174/648] wss://nostr.sovbit.host/ ... FAIL
|
||||
[175/648] wss://nostr.stakey.net/ ... FAIL
|
||||
[176/648] wss://nostr.tavux.tech/ ... FAIL
|
||||
[177/648] wss://nostr.vulpem.com/ ... OK
|
||||
[178/648] wss://nostr.xmr.rocks/ ... FAIL
|
||||
[179/648] wss://nostr01.sharkshake.net/ ... FAIL
|
||||
[180/648] wss://nostrcheck.me/relay ... OK
|
||||
[181/648] wss://nostrsatva.net/ ... FAIL
|
||||
[182/648] wss://nrelay.c-stellar.net/ ... FAIL
|
||||
[183/648] wss://paid.no.str.cr/ ... FAIL
|
||||
[184/648] wss://public.nostr.swissrouting.com/ ... FAIL
|
||||
[185/648] wss://r.hostr.cc/ ... FAIL
|
||||
[186/648] wss://relay.bitblockboom.com/ ... FAIL
|
||||
[187/648] wss://relay.bitcoindistrict.org/ ... FAIL
|
||||
[188/648] wss://relay.bullishbounty.com/ ... OK
|
||||
[189/648] wss://relay.cashumints.space/ ... FAIL
|
||||
[190/648] wss://relay.coinos.io/ ... OK
|
||||
[191/648] wss://relay.damus.com/ ... FAIL
|
||||
[192/648] wss://relay.dergigi.com/ ... FAIL
|
||||
[193/648] wss://relay.dreamith.to/ ... OK
|
||||
[194/648] wss://relay.exit.pub/ ... FAIL
|
||||
[195/648] wss://relay.gasteazi.net/ ... OK
|
||||
[196/648] wss://relay.geekiam.services/ ... FAIL
|
||||
[197/648] wss://relay.getsafebox.app/ ... OK
|
||||
[198/648] wss://relay.kamp.site/ ... FAIL
|
||||
[199/648] wss://relay.martien.io/ ... FAIL
|
||||
[200/648] wss://relay.nosflare.com/ ... FAIL
|
||||
[201/648] wss://relay.nostr.band/all ... FAIL
|
||||
[202/648] wss://relay.nostr.hu/ ... FAIL
|
||||
[203/648] wss://relay.nostr.pro/ ... FAIL
|
||||
[204/648] wss://relay.nostr.wirednet.jp/ ... OK
|
||||
[205/648] wss://relay.nostrgraph.net/ ... FAIL
|
||||
[206/648] wss://relay.nostrica.com/ ... FAIL
|
||||
[207/648] wss://relay.nostrich.de/ ... FAIL
|
||||
[208/648] wss://relay.nostrss.re/ ... FAIL
|
||||
[209/648] wss://relay.notoshi.win/ ... OK
|
||||
[210/648] wss://relay.nsecbunker.com/ ... FAIL
|
||||
[211/648] wss://relay.s-w.art/ ... FAIL
|
||||
[212/648] wss://relay.sigit.io/ ... OK
|
||||
[213/648] wss://relay.sovbit.host/ ... FAIL
|
||||
[214/648] wss://relay.sovereignengineering.io/ ... FAIL
|
||||
[215/648] wss://relay.vertexlab.io/ ... FAIL
|
||||
[216/648] wss://relay.wellorder.net/ ... OK
|
||||
[217/648] wss://relay.westernbtc.com/ ... FAIL
|
||||
[218/648] wss://relayer.fiatjaf.com/ ... FAIL
|
||||
[219/648] wss://ribo.eu.nostria.app/ ... OK
|
||||
[220/648] wss://ribo.nostria.app/ ... OK
|
||||
[221/648] wss://temp.iris.to/ ... OK
|
||||
[222/648] wss://thebarn.nostr1.com/ ... FAIL
|
||||
[223/648] wss://tigs.nostr1.com/ ... FAIL
|
||||
[224/648] wss://vault.iris.to/ ... OK
|
||||
[225/648] wss://vitor.nostr1.com/ ... FAIL
|
||||
[226/648] wss://xmr.ithurtswhenip.ee/ ... FAIL
|
||||
[227/648] ⬤ wss://nostr-pub.wellorder.net ... FAIL
|
||||
[228/648] coracle ... FAIL
|
||||
[229/648] https://sovbitm2enxfr5ot6qscwy5ermdffbqscy66wirkbsigvcshumyzbbqd.onion/ ... FAIL
|
||||
[230/648] ws://192.168.1.63:4848/ ... FAIL
|
||||
[231/648] ws://2tkf2psfhves3tp2izcchzc5gpiowk4gzkcqls4l46wj5v4b772tyead.onion/ ... FAIL
|
||||
[232/648] ws://bitcoin.anneca.cz:4848/ ... FAIL
|
||||
[233/648] ws://eden.nostr.land/ ... FAIL
|
||||
[234/648] ws://ehsc3vsoqugf5dyzvop2o4ii7nos3yccirirlxmycx4hle5epbxppnad.onion/ ... FAIL
|
||||
[235/648] ws://k6dpciogx4fabnipku6wlce4rjv3ffjhv6gcundcxvxn6poeq2hcn3id.onion/ ... FAIL
|
||||
[236/648] ws://localhost:4869/ ... FAIL
|
||||
[237/648] ws://qwqk4b6royrrgmk4pvoccok4wt5fcvpulwnse5656bcqt3bncokpt3qd.onion/ ... FAIL
|
||||
[238/648] ws://relay.jb55.com/ ... FAIL
|
||||
[239/648] ws://w3xtwz6dc7krqkkm22odcvpdpc5escq7lg266quupdyatxr5gjcm55ad.onion/ ... FAIL
|
||||
[240/648] ws://willem.currycash.net:4848/ ... FAIL
|
||||
[241/648] wss://1.noztr.com/ ... FAIL
|
||||
[242/648] wss://2jsnlhfnelig5acq6iacydmzdbdmg7xwunm4xl6qwbvzacw4lwrjmlyd.onion/ ... FAIL
|
||||
[243/648] wss://64.23.224.231:54711/ ... FAIL
|
||||
[244/648] wss://adfasfasfadsdfasfasf3123412ewfas.xyz/ ... FAIL
|
||||
[245/648] wss://adre.su/ ... FAIL
|
||||
[246/648] wss://ae.purplerelay.com/ ... FAIL
|
||||
[247/648] wss://ae3t4h2.oops.wtf/ ... FAIL
|
||||
[248/648] wss://aegis.utxo.one/ ... FAIL
|
||||
[249/648] wss://alphapanda.pro/ ... FAIL
|
||||
[250/648] wss://art.nostrfreaks.com/ ... FAIL
|
||||
[251/648] wss://asia.azzamo.net/ ... FAIL
|
||||
[252/648] wss://atlas.nostr.land/invoices ... FAIL
|
||||
[253/648] wss://au.relayable.org/ ... FAIL
|
||||
[254/648] wss://bitcoinmajlis.nostr1.com/ ... FAIL
|
||||
[255/648] wss://bitcoinostr.duckdns.org/ ... OK
|
||||
[256/648] wss://bitcoinr6de5lkvx4tpwdmzrdfdpla5sya2afwpcabjup2xpi5dulbad.onion/ ... FAIL
|
||||
[257/648] wss://bitstack.app/ ... FAIL
|
||||
[258/648] wss://catstrr.swarmstr.com/ ... FAIL
|
||||
[259/648] wss://christpill.nostr1.com/ ... FAIL
|
||||
[260/648] wss://ciao.rinbal.de/ ... FAIL
|
||||
[261/648] wss://cyberspace.nostr1.com/ ... OK
|
||||
[262/648] wss://db4efobus2ilttw5gokcwck3okcr476hqkmyqduoyydlo2j7iigehkyd.local/ ... FAIL
|
||||
[263/648] wss://directory.yabu.me/alpha-ember ... FAIL
|
||||
[264/648] wss://echo.websocket.org/ ... FAIL
|
||||
[265/648] wss://eclipse.pub/relay ... FAIL
|
||||
[266/648] wss://ehsc3vsoqugf5dyzvop2o4ii7nos3yccirirlxmycx4hle5epbxppnad.local/ ... FAIL
|
||||
[267/648] wss://elites.nostrati.org/ ... FAIL
|
||||
[268/648] wss://espelho.girino.org/ ... FAIL
|
||||
[269/648] wss://essayist.decentnewsroom.com/ ... FAIL
|
||||
[270/648] wss://eu.nostr.pikachat.org/juliet-alpha-nexus ... FAIL
|
||||
[271/648] wss://eu.rbr.bio/ ... FAIL
|
||||
[272/648] wss://eyes.f7z.io/ ... FAIL
|
||||
[273/648] wss://fabian.nostr1.com/ ... FAIL
|
||||
[274/648] wss://fanfares.nostr1.com/ ... OK
|
||||
[275/648] wss://feeds.nostr.band/omni__ventures ... FAIL
|
||||
[276/648] wss://feeds.nostr.band/pics ... FAIL
|
||||
[277/648] wss://fiatjaf.nostr1.com/ ... FAIL
|
||||
[278/648] wss://filter.nostr.wine/?global=all ... FAIL
|
||||
[279/648] wss://filter.nostr.wine/npub12gu8c6uee3p243gez6cgk76362admlqe72aq3kp2fppjsjwmm7eqj9fle6?broadcast=true ... FAIL
|
||||
[280/648] wss://filter.nostr.wine/npub14yzxejghthz6ghae8gkgjru63vvvwpl6d454wud2hycqpqwnugdqcutuw5?broadcast=true ... FAIL
|
||||
[281/648] wss://filter.nostr.wine/npub153xmex42x4chdf757hp3q6zxagykkek7pdgwuwd074964dkyha9s82ryu8?broadcast=true ... FAIL
|
||||
[282/648] wss://filter.nostr.wine/npub16w4nxxv7kjxx77zsw262v65w27q5udwnzd6ue2xremhvc9clxzaq974vef?broadcast=true ... FAIL
|
||||
[283/648] wss://filter.nostr.wine/npub17xu3rtcu0ftqw03mswa8a2ngz3nsgrs0h0wjv4z942qwvhp8fs3qzcadls?broadcast=true ... FAIL
|
||||
[284/648] wss://filter.nostr.wine/npub18ams6ewn5aj2n3wt2qawzglx9mr4nzksxhvrdc4gzrecw7n5tvjqctp424?broadcast=true ... FAIL
|
||||
[285/648] wss://filter.nostr.wine/npub1a6zkqnuwcmjwynuw4u4xyngy9675x8dwgj87z9me4h8mdwmc2a0q8mvhjk?broadcast=true ... FAIL
|
||||
[286/648] wss://filter.nostr.wine/npub1cuyfg04rf9gemn6kk2juzw8anmgxftt9mhk2ucu5a27c0f30zacqggzw8d?broadcast=true ... FAIL
|
||||
[287/648] wss://filter.nostr.wine/npub1du6sgl90wse0cz44fg50a4kg9ea4sgctlxps90ccx58lw8ssgv9qhjyf3c?broadcast=true ... FAIL
|
||||
[288/648] wss://filter.nostr.wine/npub1ngumlqmus6xkrmvvee4yc7swh9h4uk7vpq4ddt7a2jtvkc22y0asrse3pv?broadcast=true ... FAIL
|
||||
[289/648] wss://filter.nostr.wine/npub1nh4z0p2ewjsgln4533q04wzr9sdguwjn58dz9gdd26zet4spqeysktrh05?broadcast=true ... FAIL
|
||||
[290/648] wss://filter.nostr.wine/npub1p4kg8zxukpym3h20erfa3samj00rm2gt4q5wfuyu3tg0x3jg3gesvncxf8 ... FAIL
|
||||
[291/648] wss://filter.nostr.wine/npub1qjgcmlpkeyl8mdkvp4s0xls4ytcux6my606tgfx9xttut907h0zs76lgjw?broadcast=true ... FAIL
|
||||
[292/648] wss://filter.nostr.wine/npub1rr654u0pp3dm0g65dzc0v2eft5frg7gre8u4wwxsvhyyhmc5qths50eeul?broadcast=true ... FAIL
|
||||
[293/648] wss://filter.nostr.wine/npub1rtlqca8r6auyaw5n5h3l5422dm4sry5dzfee4696fqe8s6qgudks7djtfs?broadcast=true ... FAIL
|
||||
[294/648] wss://filter.nostr.wine/npub1sjjz60h6fqqcuxrsyl3thhgpx2z6ylv047tslqar26ga0chp5vgq7404nu?broadcast=true ... FAIL
|
||||
[295/648] wss://filter.nostr.wine/npub1t0nyg64g5vwprva52wlcmt7fkdr07v5dr7s35raq9g0xgc0k4xcsedjgqv?broadcast=true ... FAIL
|
||||
[296/648] wss://filter.nostr.wine/npub1t5wc8h37uhkau9tsw82sjxndq04d3n8p634utpdfvs4tm5xmt2sqgk6dke?broadcast=true ... FAIL
|
||||
[297/648] wss://filter.nostr.wine/npub1xv6axulxcx6mce5mfvfzpsy89r4gee3zuknulm45cqqpmyw7680q5pxea6?broadcast=true ... FAIL
|
||||
[298/648] wss://filter.nostr.wine/npub1yaul8k059377u9lsu67de7y637w4jtgeuwcmh5n7788l6xnlnrgs3tvjmf?broadcast=true ... FAIL
|
||||
[299/648] wss://filter.nostr.wine/npub1zqsu3ys4fragn2a5e3lgv69r4rwwhts2fserll402uzr3qeddxfsffcqrs?broadcast=true ... FAIL
|
||||
[300/648] wss://foton.solarpowe.red/ ... FAIL
|
||||
[301/648] wss://frens.nostr1.com/ ... FAIL
|
||||
[302/648] wss://frens.utxo.one/ ... FAIL
|
||||
[303/648] wss://futarchyhub.com/relay ... FAIL
|
||||
[304/648] wss://galaxy13.nostr1.com/ ... FAIL
|
||||
[305/648] wss://garden.zap.cooking/ ... FAIL
|
||||
[306/648] wss://gleasonator.dev/relay ... OK
|
||||
[307/648] wss://gm.swarmstr.com/ ... FAIL
|
||||
[308/648] wss://grace.ikaros.hzrd149.com/ ... FAIL
|
||||
[309/648] wss://groups.0xchat.com/ ... FAIL
|
||||
[310/648] wss://haven.dergigi.com/ ... FAIL
|
||||
[311/648] wss://haven.downisontheup.ca/ ... FAIL
|
||||
[312/648] wss://haven.slidestr.net/ ... FAIL
|
||||
[313/648] wss://haven.sovereignengineering.io/outbox ... FAIL
|
||||
[314/648] wss://hbr.coracle.social/ ... FAIL
|
||||
[315/648] wss://henhouse.social/relay ... FAIL
|
||||
[316/648] wss://hodlbod.coracle.social/ ... FAIL
|
||||
[317/648] wss://hodlbod.coracle.tools/ ... FAIL
|
||||
[318/648] wss://HodlHarry@nostrcheck.me/ ... OK
|
||||
[319/648] wss://hole.v0l.io/ ... FAIL
|
||||
[320/648] wss://hotrightnow.nostr1.com/ ... FAIL
|
||||
[321/648] wss://https//wot.utxo.one/# ... FAIL
|
||||
[322/648] wss://inbox.relays.land/ ... FAIL
|
||||
[323/648] wss://inner.sebastix.social/ ... FAIL
|
||||
[324/648] wss://iris.to/ ... FAIL
|
||||
[325/648] wss://kmc-nostr.amiunderwater.com/ ... FAIL
|
||||
[326/648] wss://knostr.neutrine.com:8880/ ... FAIL
|
||||
[327/648] wss://knostr.neutrine.com/ ... FAIL
|
||||
[328/648] wss://kr.purplerelay.com/ ... FAIL
|
||||
[329/648] wss://librerelay.aaroniumii.com/ ... FAIL
|
||||
[330/648] wss://ln.weedstr.net/nostrrelay/weedstr ... FAIL
|
||||
[331/648] wss://lnb.bolverker.com/nostrrelay/666 ... FAIL
|
||||
[332/648] wss://lockbox.fiatjaf.com/ ... FAIL
|
||||
[333/648] wss://lv01.tater.ninja/ ... FAIL
|
||||
[334/648] wss://moonboi.nostrfreaks.com/ ... FAIL
|
||||
[335/648] wss://muxstr.northwest.io/ ... FAIL
|
||||
[336/648] wss://nerostr.xmr.rocks/ ... FAIL
|
||||
[337/648] wss://news.nos.social/ ... FAIL
|
||||
[338/648] wss://nfdb.noswhere.com/victor ... FAIL
|
||||
[339/648] wss://nip17.com/ ... FAIL
|
||||
[340/648] wss://njump.me/ ... FAIL
|
||||
[341/648] wss://nos.win/ ... FAIL
|
||||
[342/648] wss://nostr-01.bolt.observer/ ... FAIL
|
||||
[343/648] wss://nostr-paid.h3z.jp/ ... FAIL
|
||||
[344/648] wss://nostr-relay.amethyst.name/ ... FAIL
|
||||
[345/648] wss://nostr-relay.amethyst.name/jade-raven-juliet ... FAIL
|
||||
[346/648] wss://nostr-relay.feddit.social/ ... FAIL
|
||||
[347/648] wss://nostr-relay.lnmarkets.com/ ... FAIL
|
||||
[348/648] wss://nostr-relay.philipcristiano.com/ ... FAIL
|
||||
[349/648] wss://nostr-relay.rawrapp.workers.dev/ ... FAIL
|
||||
[350/648] wss://nostr-relay.schnitzel.world/ ... FAIL
|
||||
[351/648] wss://nostr-relay.untethr.me/ ... FAIL
|
||||
[352/648] wss://nostr-relay.usebitcoin.space/ ... FAIL
|
||||
[353/648] wss://nostr-verified.wellorder.net/ ... OK
|
||||
[354/648] wss://nostr.1sat.org/ ... FAIL
|
||||
[355/648] wss://nostr.256k1.dev/ ... FAIL
|
||||
[356/648] wss://nostr.438b.net/ ... FAIL
|
||||
[357/648] wss://nostr.4rs.nl/ ... OK
|
||||
[358/648] wss://nostr.8777.ch/ ... FAIL
|
||||
[359/648] wss://nostr.actn.io/ ... FAIL
|
||||
[360/648] wss://nostr.app.runonflux.io/ ... OK
|
||||
[361/648] wss://nostr.at/ ... FAIL
|
||||
[362/648] wss://nostr.azuki.blue/ember-ivory-mike ... FAIL
|
||||
[363/648] wss://nostr.azzamo.net/flint-warden ... OK
|
||||
[364/648] wss://nostr.beckmeyer.us/ ... FAIL
|
||||
[365/648] wss://nostr.bitcoin.ninja/ ... FAIL
|
||||
[366/648] wss://nostr.bitcoin.social/ ... FAIL
|
||||
[367/648] wss://nostr.bostonbtc.com/ ... FAIL
|
||||
[368/648] wss://nostr.build/ ... FAIL
|
||||
[369/648] wss://nostr.cizmar.net/ ... FAIL
|
||||
[370/648] wss://nostr.cloud.vinney.xyz/ ... FAIL
|
||||
[371/648] wss://nostr.coinfundit.com/ ... FAIL
|
||||
[372/648] wss://nostr.compile-error.net/ ... FAIL
|
||||
[373/648] wss://nostr.cx.ms/ ... FAIL
|
||||
[374/648] wss://nostr.delo.software/ ... FAIL
|
||||
[375/648] wss://nostr.digitalreformation.info/ ... FAIL
|
||||
[376/648] wss://nostr.dontyou.click/oscar-warden-flint ... FAIL
|
||||
[377/648] wss://nostr.drss.io/ ... FAIL
|
||||
[378/648] wss://nostr.dvdt.dev/ ... FAIL
|
||||
[379/648] wss://nostr.easify.de/ ... FAIL
|
||||
[380/648] wss://nostr.easydns.ca/ ... FAIL
|
||||
[381/648] wss://nostr.extrabits.io/ ... FAIL
|
||||
[382/648] wss://nostr.fediverse.jp/ ... FAIL
|
||||
[383/648] wss://nostr.flamingo-mail.com/ ... FAIL
|
||||
[384/648] wss://nostr.gives.africa/ ... FAIL
|
||||
[385/648] wss://nostr.gromeul.eu/ ... FAIL
|
||||
[386/648] wss://nostr.holybea.com/ ... FAIL
|
||||
[387/648] wss://nostr.huszonegy.world/ ... OK
|
||||
[388/648] wss://nostr.ingwie.me/ ... FAIL
|
||||
[389/648] wss://nostr.jiashanlu.synology.me/ ... FAIL
|
||||
[390/648] wss://nostr.k3tan.com/ ... FAIL
|
||||
[391/648] wss://nostr.koning-degraaf.nl/ ... FAIL
|
||||
[392/648] wss://nostr.kosmos.org/ ... FAIL
|
||||
[393/648] wss://nostr.land/xray-xray-cipher ... FAIL
|
||||
[394/648] wss://nostr.lnproxy.org/ ... FAIL
|
||||
[395/648] wss://nostr.malin.onl/ ... FAIL
|
||||
[396/648] wss://nostr.massmux.com/ ... FAIL
|
||||
[397/648] wss://nostr.middling.mydns.jp/ ... FAIL
|
||||
[398/648] wss://nostr.mineracks.com/ ... OK
|
||||
[399/648] wss://nostr.noones.com/ ... FAIL
|
||||
[400/648] wss://nostr.novacisko.cz/ ... FAIL
|
||||
[401/648] wss://nostr.okaits7534.net/ ... FAIL
|
||||
[402/648] wss://nostr.oldenburg.cool/ ... FAIL
|
||||
[403/648] wss://nostr.openhoofd.nl/ ... OK
|
||||
[404/648] wss://nostr.ownscale.org/ ... FAIL
|
||||
[405/648] wss://nostr.palandi.cloud/ ... FAIL
|
||||
[406/648] wss://nostr.pareto.town/ ... FAIL
|
||||
[407/648] wss://nostr.petrkr.net/willow ... FAIL
|
||||
[408/648] wss://nostr.primz.org/charlie-xenon-bravo ... FAIL
|
||||
[409/648] wss://nostr.radixrat.com/ ... FAIL
|
||||
[410/648] wss://nostr.robosats.org/ ... FAIL
|
||||
[411/648] wss://nostr.sathoarder.com/titan-glyph ... OK
|
||||
[412/648] wss://nostr.sats.coffee/ ... FAIL
|
||||
[413/648] wss://nostr.sats.li/ ... FAIL
|
||||
[414/648] wss://nostr.screaminglife.io/ ... FAIL
|
||||
[415/648] wss://nostr.sectiontwo.org/ ... FAIL
|
||||
[416/648] wss://nostr.sg/ ... FAIL
|
||||
[417/648] wss://nostr.sixteensixtyone.com/ ... FAIL
|
||||
[418/648] wss://nostr.stoner.com/ ... FAIL
|
||||
[419/648] wss://nostr.superfriends.online/titan ... OK
|
||||
[420/648] wss://nostr.supremestack.xyz/ ... FAIL
|
||||
[421/648] wss://nostr.swiss-enigma.ch/ ... FAIL
|
||||
[422/648] wss://nostr.terminus.money/ ... FAIL
|
||||
[423/648] wss://nostr.topeth.info/ ... FAIL
|
||||
[424/648] wss://nostr.uselessshit.co/ ... FAIL
|
||||
[425/648] wss://nostr.yuv.al/ ... FAIL
|
||||
[426/648] wss://nostr.zoel.network/ ... FAIL
|
||||
[427/648] wss://nostr01.counterclockwise.io/ ... FAIL
|
||||
[428/648] wss://nostr1.tunnelsats.com/ ... FAIL
|
||||
[429/648] wss://nostr1676319567170.app.runonflux.io/ ... FAIL
|
||||
[430/648] wss://nostr21.com/raven ... FAIL
|
||||
[431/648] wss://nostrcheck.tnsor.network/ ... FAIL
|
||||
[432/648] wss://nostrelay.circum.space/ ... OK
|
||||
[433/648] wss://nostrelay.unchainedhome.com/ ... OK
|
||||
[434/648] wss://nostrex.fly.dev/ ... FAIL
|
||||
[435/648] wss://nostrrelay.com/ ... FAIL
|
||||
[436/648] wss://nostrrelay.taylorperron.com/ ... FAIL
|
||||
[437/648] wss://notify.damus.io/ ... FAIL
|
||||
[438/648] wss://novoa.nagoya/ ... FAIL
|
||||
[439/648] wss://now.lol/ ... FAIL
|
||||
[440/648] wss://npub1spxdug4m3y24hpx5crm0el4zhkk0wafs8kp6m0xu0wecygqej2xqq8gyhx.fips.network/ ... FAIL
|
||||
[441/648] wss://nsite.run/ ... FAIL
|
||||
[442/648] wss://nsrelay.assilvestrar.club/ ... FAIL
|
||||
[443/648] wss://nstr.utn.lol/ ... FAIL
|
||||
[444/648] wss://nwc.primal.net/c4ib8h2rs62bkmlk4wu7jbko9ugqbn ... FAIL
|
||||
[445/648] wss://onchain.pub/ ... FAIL
|
||||
[446/648] wss://paid-relay.nostr.blockhenge.com/ ... FAIL
|
||||
[447/648] wss://paid.nostrified.org/ ... FAIL
|
||||
[448/648] wss://pleb.cloud/ ... FAIL
|
||||
[449/648] wss://primal-cache.mutinywallet.com/v1 ... FAIL
|
||||
[450/648] wss://prl.plus/ ... OK
|
||||
[451/648] wss://proxy.nostr-relay.app/5d0d38afc49c4b84ca0da951a336affa18438efed302aeedfa92eb8b0d3fcb87 ... FAIL
|
||||
[452/648] wss://proxy.nostr-relay.app/8c5723f2601334234e1922d2e842d6bbf209283b07120b3f1d38660915f13793 ... FAIL
|
||||
[453/648] wss://public.crostr.com/beacon-umbra ... OK
|
||||
[454/648] wss://public.relaying.io/ ... FAIL
|
||||
[455/648] wss://questions.swarmstr.com/ ... FAIL
|
||||
[456/648] wss://r.f7z.io/ ... FAIL
|
||||
[457/648] wss://r.kojira.io/ ... FAIL
|
||||
[458/648] wss://r314y.0xd43m0n.xyz/ ... FAIL
|
||||
[459/648] wss://ragnar-relay.com/ ... FAIL
|
||||
[460/648] wss://raley.azzamo.net/ ... FAIL
|
||||
[461/648] wss://realy.damus.io/ ... FAIL
|
||||
[462/648] wss://realy.nostr.bg/ ... FAIL
|
||||
[463/648] wss://relais.noswhere.com/ ... FAIL
|
||||
[464/648] wss://relay-1.arsip.my.id/ ... FAIL
|
||||
[465/648] wss://relay-rpi.edufeed.org/ ... OK
|
||||
[466/648] wss://relay-testnet.k8s.layer3.news/xenon-anchor ... OK
|
||||
[467/648] wss://relay.2020117.xyz/oscar-lima ... FAIL
|
||||
[468/648] wss://relay.21mil.me/ ... FAIL
|
||||
[469/648] wss://relay.8333.space/ ... FAIL
|
||||
[470/648] wss://relay.angor.io/ ... OK
|
||||
[471/648] wss://relay.apps.sebdev.io/ ... FAIL
|
||||
[472/648] wss://relay.artx.market/ ... OK
|
||||
[473/648] wss://relay.austrich.net/ ... FAIL
|
||||
[474/648] wss://relay.benthecarman.com/ ... FAIL
|
||||
[475/648] wss://relay.bitesize-media.com/ ... FAIL
|
||||
[476/648] wss://relay.bitmapstr.io/ ... FAIL
|
||||
[477/648] wss://relay.bleskop.com/ ... FAIL
|
||||
[478/648] wss://relay.braydon.com/ ... FAIL
|
||||
[479/648] wss://relay.carlos-cdb.top/vertex-whiskey ... OK
|
||||
[480/648] wss://relay.causes.com/ ... FAIL
|
||||
[481/648] wss://relay.codl.co/ ... FAIL
|
||||
[482/648] wss://relay.corpum.com/ ... OK
|
||||
[483/648] wss://relay.cryptocculture.com/ ... FAIL
|
||||
[484/648] wss://relay.deezy.io/ ... FAIL
|
||||
[485/648] wss://relay.despera.space/ ... FAIL
|
||||
[486/648] wss://relay.devstr.org/ ... FAIL
|
||||
[487/648] wss://relay.digitalezukunft.cyou/ ... FAIL
|
||||
[488/648] wss://relay.disobey.dev/ ... FAIL
|
||||
[489/648] wss://relay.dwadziesciajeden.pl/ ... OK
|
||||
[490/648] wss://relay.getalby.com/ ... FAIL
|
||||
[491/648] wss://relay.geyser.fund/ ... FAIL
|
||||
[492/648] wss://relay.gobrrr.me/ ... FAIL
|
||||
[493/648] wss://relay.goodmorningbitcoin.com/ ... FAIL
|
||||
[494/648] wss://relay.gulugulu.moe/ ... OK
|
||||
[495/648] wss://relay.hangar.ninja/ ... FAIL
|
||||
[496/648] wss://relay.hash.stream/ ... FAIL
|
||||
[497/648] wss://relay.highlighter.com/ ... FAIL
|
||||
[498/648] wss://relay.hodlboard.org/ ... FAIL
|
||||
[499/648] wss://relay.hunos.hu/ ... FAIL
|
||||
[500/648] wss://relay.iefan.net/ ... FAIL
|
||||
[501/648] wss://relay.ingwie.me/ ... FAIL
|
||||
[502/648] wss://relay.islandbitcoin.com/echo-anchor-cipher ... OK
|
||||
[503/648] wss://relay.jeffg.fyi/ ... FAIL
|
||||
[504/648] wss://relay.jerseyplebs.com/ ... FAIL
|
||||
[505/648] wss://relay.kisiel.net.pl/ ... FAIL
|
||||
[506/648] wss://relay.lab.rytswd.com/karma-titan-lantern ... OK
|
||||
[507/648] wss://relay.lacompagniemaximus.com/romeo-yonder ... FAIL
|
||||
[508/648] wss://relay.layer.systems/ ... FAIL
|
||||
[509/648] wss://relay.lexingtonbitcoin.org/ ... FAIL
|
||||
[510/648] wss://relay.lightning.pub/yankee ... OK
|
||||
[511/648] wss://relay.livefreebtc.dev/ ... FAIL
|
||||
[512/648] wss://relay.magiccity.live/ ... FAIL
|
||||
[513/648] wss://relay.mccormick.cx/quartz ... OK
|
||||
[514/648] wss://relay.mess.ch/ ... FAIL
|
||||
[515/648] wss://relay.minds.com/nostr/v1/ws ... FAIL
|
||||
[516/648] wss://relay.mostro.network/ ... OK
|
||||
[517/648] wss://relay.nextblock.city/ ... FAIL
|
||||
[518/648] wss://relay.nimo.cash/ ... FAIL
|
||||
[519/648] wss://relay.nodana.app/ ... FAIL
|
||||
[520/648] wss://relay.nomus.io/ ... OK
|
||||
[521/648] wss://relay.nosotros.app/ ... FAIL
|
||||
[522/648] wss://relay.nostpy.lol/ ... FAIL
|
||||
[523/648] wss://relay.nostr.ai/ ... FAIL
|
||||
[524/648] wss://relay.nostr.band/trusted ... FAIL
|
||||
[525/648] wss://relay.nostr.blockhenge.com/ ... OK
|
||||
[526/648] wss://relay.nostr.directory/ ... FAIL
|
||||
[527/648] wss://relay.nostr.moe/ ... FAIL
|
||||
[528/648] wss://relay.nostr.pub/ ... FAIL
|
||||
[529/648] wss://relay.nostr.ro/ ... FAIL
|
||||
[530/648] wss://relay.nostr.scot/ ... FAIL
|
||||
[531/648] wss://relay.nostr.watch/ ... FAIL
|
||||
[532/648] wss://relay.nostrarabia.com/ ... FAIL
|
||||
[533/648] wss://relay.nostrasia.net/ ... FAIL
|
||||
[534/648] wss://relay.nostreggs.io/ ... FAIL
|
||||
[535/648] wss://relay.nostrich.land/ ... FAIL
|
||||
[536/648] wss://relay.nostriches.or/ ... FAIL
|
||||
[537/648] wss://relay.nostrid.com/ ... FAIL
|
||||
[538/648] wss://relay.nostronautti.fi/ ... FAIL
|
||||
[539/648] wss://relay.npubhaus.com/ ... OK
|
||||
[540/648] wss://relay.nquiz.io/ ... FAIL
|
||||
[541/648] wss://relay.nsec.app/ ... FAIL
|
||||
[542/648] wss://relay.nsnip.io/ ... FAIL
|
||||
[543/648] wss://relay.nstr.cc/ ... FAIL
|
||||
[544/648] wss://relay.nuts.cash/ ... OK
|
||||
[545/648] wss://relay.nvote.co/ ... FAIL
|
||||
[546/648] wss://relay.oke.minds.io/nostr/v1/ws ... FAIL
|
||||
[547/648] wss://relay.olas.app/ ... FAIL
|
||||
[548/648] wss://relay.orangepilldev.com/ ... FAIL
|
||||
[549/648] wss://relay.orangesync.tech/ ... FAIL
|
||||
[550/648] wss://relay.orly.dev/ ... FAIL
|
||||
[551/648] wss://relay.piazza.today/ ... FAIL
|
||||
[552/648] wss://relay.pituf.in/ ... FAIL
|
||||
[553/648] wss://relay.pleb.one/ ... FAIL
|
||||
[554/648] wss://relay.plebeian.market/ ... OK
|
||||
[555/648] wss://relay.pokernostr.com/ ... FAIL
|
||||
[556/648] wss://relay.qstr.app/ ... OK
|
||||
[557/648] wss://relay.reya.su/ ... FAIL
|
||||
[558/648] wss://relay.rip/ ... FAIL
|
||||
[559/648] wss://relay.rkus.se/ ... FAIL
|
||||
[560/648] wss://relay.roygbiv.guide/ ... FAIL
|
||||
[561/648] wss://relay.samt.st/ ... OK
|
||||
[562/648] wss://relay.satlantis.io/ ... OK
|
||||
[563/648] wss://relay.sendstr.com/ ... FAIL
|
||||
[564/648] wss://relay.sharegap.net/ ... OK
|
||||
[565/648] wss://relay.shawnyeager.com/ ... FAIL
|
||||
[566/648] wss://relay.shawnyeager.com/chat ... FAIL
|
||||
[567/648] wss://relay.shawnyeager.com/inbox ... FAIL
|
||||
[568/648] wss://relay.shawnyeager.com/outbox ... FAIL
|
||||
[569/648] wss://relay.shawnyeager.com/private ... FAIL
|
||||
[570/648] wss://relay.shosho.live/ ... FAIL
|
||||
[571/648] wss://relay.siamdev.cc/ ... FAIL
|
||||
[572/648] wss://relay.snort.social:57/ ... FAIL
|
||||
[573/648] wss://relay.sovereign.app/ ... FAIL
|
||||
[574/648] wss://relay.sovrgn.co.za/ ... OK
|
||||
[575/648] wss://relay.thibautduchene.fr/ ... FAIL
|
||||
[576/648] wss://relay.tools/ ... FAIL
|
||||
[577/648] wss://relay.towardsliberty.com/ ... FAIL
|
||||
[578/648] wss://relay.tunestr.io/ ... FAIL
|
||||
[579/648] wss://relay.utxo.one/chat ... FAIL
|
||||
[580/648] wss://relay.utxo.one/inbox ... FAIL
|
||||
[581/648] wss://relay.utxo.one/private ... FAIL
|
||||
[582/648] wss://relay.valera.co/ ... FAIL
|
||||
[583/648] wss://relay.varke.eu/ ... FAIL
|
||||
[584/648] wss://relay.verified-nostr.com/ ... FAIL
|
||||
[585/648] wss://relay.virginiafreedom.tech/ ... FAIL
|
||||
[586/648] wss://relay.wavefunc.live/ ... OK
|
||||
[587/648] wss://relay.wisp.talk/ ... OK
|
||||
[588/648] wss://relay.xavierdamman.com/ember-quartz ... FAIL
|
||||
[589/648] wss://relay.ynniv.com/ ... FAIL
|
||||
[590/648] wss://relay.zap.stream/ ... FAIL
|
||||
[591/648] wss://relay.zeh.app/ ... FAIL
|
||||
[592/648] wss://relay.zhoushen929.com/ ... FAIL
|
||||
[593/648] wss://relay1.orangesync.tech/ ... FAIL
|
||||
[594/648] wss://relay2.blogstr.app/ ... FAIL
|
||||
[595/648] wss://relay2.nostrasia.net/ ... FAIL
|
||||
[596/648] wss://relay2.orangesync.tech/ ... FAIL
|
||||
[597/648] wss://relay2.sovereignengineering.io/ ... FAIL
|
||||
[598/648] wss://relay3.openvine.co/victor ... OK
|
||||
[599/648] wss://ribo.us.nostria.app/ ... OK
|
||||
[600/648] wss://rsslay.fiatjaf.com/ ... FAIL
|
||||
[601/648] wss://rsslay.nostr.moe/ ... FAIL
|
||||
[602/648] wss://rsslay.wss//relay.nostr.info%0b%20nostr.net ... FAIL
|
||||
[603/648] wss://ryan.nostr1.com/ ... FAIL
|
||||
[604/648] wss://santo.iguanatech.net/ ... FAIL
|
||||
[605/648] wss://satdow.relaying.io/ ... FAIL
|
||||
[606/648] wss://satstacker.cloud/ ... FAIL
|
||||
[607/648] wss://sgl.rustcorp.com.au/ ... FAIL
|
||||
[608/648] wss://snort.social/ ... FAIL
|
||||
[609/648] wss://soloco.nl/ ... OK
|
||||
[610/648] wss://srtrelay.c-stellar.net/ ... FAIL
|
||||
[611/648] wss://srtrelay.c-stellar.net/karma-oscar-lima ... FAIL
|
||||
[612/648] wss://strfry.bonsai.com/ivory-xenon-juliet ... OK
|
||||
[613/648] wss://strfry.chatbett.de/ ... FAIL
|
||||
[614/648] wss://strfry.iris.to/ ... FAIL
|
||||
[615/648] wss://strfry.openhoofd.nl/ ... OK
|
||||
[616/648] wss://student.chadpolytechnic.com/ ... FAIL
|
||||
[617/648] wss://swiss.nostr.lc/ ... FAIL
|
||||
[618/648] wss://testrelay.nostr1.com/ ... FAIL
|
||||
[619/648] wss://thecitadel.nostr1.com/sierra ... FAIL
|
||||
[620/648] wss://thewildhustle.nostr1.com/ ... FAIL
|
||||
[621/648] wss://tijl.xyz/ ... FAIL
|
||||
[622/648] wss://tmp-relay.cesc.trade/ ... FAIL
|
||||
[623/648] wss://tw.purplerelay.com/ ... FAIL
|
||||
[624/648] wss://umbrel.local:4848/ ... FAIL
|
||||
[625/648] wss://umbrel.tail716804.ts.net:4848/ ... FAIL
|
||||
[626/648] wss://universe.nostrich.land/ ... FAIL
|
||||
[627/648] wss://us.azzamo.net/ ... FAIL
|
||||
[628/648] wss://us.nostr.wine/ ... FAIL
|
||||
[629/648] wss://us.purplerelay.com/ ... FAIL
|
||||
[630/648] wss://us.rbr.bio/ ... FAIL
|
||||
[631/648] wss://user.kindpag.es/ ... FAIL
|
||||
[632/648] wss://wbc.nostr1.com/ember-xenon-whiskey ... FAIL
|
||||
[633/648] wss://wons.calva.dev/ ... FAIL
|
||||
[634/648] wss://wot.azzamo.net/ ... FAIL
|
||||
[635/648] wss://wot.brightbolt.net/ ... FAIL
|
||||
[636/648] wss://wot.codingarena.top/ ... FAIL
|
||||
[637/648] wss://wot.dergigi.com/ ... FAIL
|
||||
[638/648] wss://wot.sudoscarlos.com/ ... FAIL
|
||||
[639/648] wss://wot.swarmstr.com/ ... FAIL
|
||||
[640/648] wss://wot.tealeaf.dev/ ... FAIL
|
||||
[641/648] wss://wot.zacoos.com/ ... FAIL
|
||||
[642/648] wss://wotr.relatr.xyz/ ... FAIL
|
||||
[643/648] wss://xn--%20wss-hn6d//nostr-pub.wellorder.net ... FAIL
|
||||
[644/648] wss://yarnlady.21mil.me/ ... FAIL
|
||||
[645/648] wss://yestr.me/ ... FAIL
|
||||
[646/648] wss://ynostr.yael.at/tango ... FAIL
|
||||
[647/648] wss://ynostr.yael.at/victor ... FAIL
|
||||
[648/648] ww://relay.stoner.com ... FAIL
|
||||
|
||||
=== Summary ===
|
||||
Total: 648
|
||||
Success: 95
|
||||
Failed: 553
|
||||
|
||||
Full results: /home/user/lt/client/tests/broadcast-relay-results.txt
|
||||
2593
tests/relays.json
Normal file
2593
tests/relays.json
Normal file
File diff suppressed because it is too large
Load Diff
1
tests/test-key.txt
Normal file
1
tests/test-key.txt
Normal file
@@ -0,0 +1 @@
|
||||
539ac367da733f455a703242efeafbbb0750dda352e9a641e2ccc7aadb5a3e94
|
||||
@@ -479,6 +479,50 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
divFooterRight.textContent = `${posts.length} posts`;
|
||||
}
|
||||
|
||||
function prependPostCard(post) {
|
||||
if (!post?.id || renderedPostIds.has(post.id)) return false;
|
||||
|
||||
// If feed is empty or not yet rendered, fall back to full rebuild.
|
||||
if (divFeed.children.length === 0 || renderedPostIds.size === 0) {
|
||||
renderFeed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only prepend if the post is newer than the first currently visible post.
|
||||
const firstChild = divFeed.firstElementChild;
|
||||
const firstPostId = firstChild ? firstChild.getAttribute('data-post-id') : null;
|
||||
const firstPost = firstPostId ? posts.find((p) => p.id === firstPostId) : null;
|
||||
|
||||
if (!firstPost || (post.created_at || 0) < (firstPost.created_at || 0)) {
|
||||
// Post is older than the first visible post (or unknown) — full rebuild for safety.
|
||||
renderFeed();
|
||||
return false;
|
||||
}
|
||||
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: autoplayVideosEnabled });
|
||||
divFeed.insertBefore(postEl, divFeed.firstChild);
|
||||
renderedPostIds.add(post.id);
|
||||
|
||||
cards.wireInteractions([post.id], { currentPubkey });
|
||||
|
||||
// Trim DOM to displayCount.
|
||||
while (divFeed.children.length > displayCount) {
|
||||
const lastChild = divFeed.lastElementChild;
|
||||
if (!lastChild) break;
|
||||
const removedId = lastChild.getAttribute('data-post-id');
|
||||
if (removedId) renderedPostIds.delete(removedId);
|
||||
divFeed.removeChild(lastChild);
|
||||
}
|
||||
|
||||
btnSeeMore.style.display = posts.length > displayCount ? 'block' : 'none';
|
||||
divHint.textContent = followedPubkeys.length > 0
|
||||
? `${posts.length} posts from ${followedPubkeys.length} follows`
|
||||
: 'Follow users to populate your feed.';
|
||||
divFooterRight.textContent = `${posts.length} posts`;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function fetchFeedWindow(authors, { since, limit = FEED_BOOTSTRAP_WINDOW_LIMIT, renderAfterCache = false } = {}) {
|
||||
if (!Array.isArray(authors) || authors.length === 0) return;
|
||||
const filters = { kinds: [1], authors, limit };
|
||||
@@ -710,8 +754,12 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
return;
|
||||
}
|
||||
if (!upsertFeedPost(evt)) return;
|
||||
if (renderedPostIds.size > 0 && divFeed.children.length > 0) {
|
||||
prependPostCard(evt);
|
||||
} else {
|
||||
renderFeed();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
subscribe({ kinds: [3], authors: [currentPubkey], limit: 1 }, { closeOnEose: false, cacheUsage: 'CACHE_FIRST' });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.84",
|
||||
"VERSION_NUMBER": "0.7.84",
|
||||
"BUILD_DATE": "2026-06-30T15:36:36.887Z"
|
||||
"VERSION": "v0.7.85",
|
||||
"VERSION_NUMBER": "0.7.85",
|
||||
"BUILD_DATE": "2026-07-01T11:59:39.440Z"
|
||||
}
|
||||
|
||||
@@ -652,6 +652,56 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
divFooterRight.textContent = `${posts.length} posts`;
|
||||
}
|
||||
|
||||
function prependPostCard(post) {
|
||||
if (!post?.id || renderedPostIds.has(post.id)) return false;
|
||||
|
||||
// Event mode should never prepend — it uses renderSingleEvent / renderFeed.
|
||||
if (isEventMode) {
|
||||
renderFeed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// If feed is empty or not yet rendered, fall back to full rebuild.
|
||||
if (divFeed.children.length === 0 || renderedPostIds.size === 0) {
|
||||
renderFeed();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only prepend if the post is newer than the first currently visible post.
|
||||
const firstChild = divFeed.firstElementChild;
|
||||
const firstPostId = firstChild ? firstChild.getAttribute('data-post-id') : null;
|
||||
const firstPost = firstPostId ? posts.find((p) => p.id === firstPostId) : null;
|
||||
|
||||
if (!firstPost || (post.created_at || 0) < (firstPost.created_at || 0)) {
|
||||
// Post is older than the first visible post (or unknown) — full rebuild for safety.
|
||||
renderFeed();
|
||||
return false;
|
||||
}
|
||||
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: false });
|
||||
divFeed.insertBefore(postEl, divFeed.firstChild);
|
||||
renderedPostIds.add(post.id);
|
||||
|
||||
cards.wireInteractions([post.id], { currentPubkey });
|
||||
|
||||
// Trim DOM to displayCount.
|
||||
while (divFeed.children.length > displayCount) {
|
||||
const lastChild = divFeed.lastElementChild;
|
||||
if (!lastChild) break;
|
||||
const removedId = lastChild.getAttribute('data-post-id');
|
||||
if (removedId) renderedPostIds.delete(removedId);
|
||||
divFeed.removeChild(lastChild);
|
||||
}
|
||||
|
||||
btnSeeMore.style.display = posts.length > displayCount ? 'block' : 'none';
|
||||
divHint.textContent = posts.length > 0
|
||||
? `${posts.length} posts`
|
||||
: (isProfileMode ? 'No posts yet.' : 'No posts yet — write one above.');
|
||||
divFooterRight.textContent = `${posts.length} posts`;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderSingleEvent(evt) {
|
||||
if (!evt?.id) return;
|
||||
posts.length = 0;
|
||||
@@ -909,7 +959,11 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
}
|
||||
|
||||
if (!upsertPost(evt)) return;
|
||||
if (renderedPostIds.size > 0 && divFeed.children.length > 0) {
|
||||
prependPostCard(evt);
|
||||
} else {
|
||||
renderFeed();
|
||||
}
|
||||
});
|
||||
|
||||
if (isEventMode) {
|
||||
|
||||
Reference in New Issue
Block a user