101 lines
5.7 KiB
Markdown
101 lines
5.7 KiB
Markdown
# Plan: Move Mute List Filtering into SharedWorker
|
|
|
|
## Problem
|
|
|
|
The mute list (kind 10000) is currently loaded and evaluated **page-side only** in `www/js/mute-list.mjs`. The SharedWorker (`www/ndk-worker.js`) broadcasts all subscription events unfiltered. This means:
|
|
|
|
1. Every page must independently import and call `isEventMuted()` — easy to forget
|
|
2. Muted events still get cached in IndexedDB by the worker
|
|
3. Muted events still consume bandwidth and processing across all tabs
|
|
4. Pages that don't implement mute checks show muted content
|
|
|
|
## Current Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
R[Relays] -->|all events| W[SharedWorker]
|
|
W -->|broadcast unfiltered| P1[Page 1]
|
|
W -->|broadcast unfiltered| P2[Page 2]
|
|
P1 -->|page-side check| ML1[mute-list.mjs]
|
|
P2 -->|page-side check| ML2[mute-list.mjs]
|
|
|
|
style W fill:#553,color:#fff
|
|
style ML1 fill:#335,color:#fff
|
|
style ML2 fill:#335,color:#fff
|
|
```
|
|
|
|
## Target Architecture
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
R[Relays] -->|all events| W[SharedWorker]
|
|
W -->|filter via mute sets| F[isEventMuted check]
|
|
F -->|clean events only| P1[Page 1]
|
|
F -->|clean events only| P2[Page 2]
|
|
W -->|fetch kind 10000| R
|
|
P1 -->|updateMuteList msg| W
|
|
|
|
style W fill:#553,color:#fff
|
|
style F fill:#533,color:#fff
|
|
```
|
|
|
|
## Design Decisions
|
|
|
|
### Public mutes only in worker
|
|
The worker will only filter on **public mute entries** (the `tags` array of the kind 10000 event). Private/encrypted mute entries require NIP-44 decryption which needs the signer — the worker already has `messageSigner` for this, so we can decrypt private entries too via the existing `requestFromPage` pattern.
|
|
|
|
### Worker fetches its own mute list
|
|
During `handleInit()`, after relay connection, the worker will fetch kind 10000 for the authenticated pubkey and parse it. This parallels how it already fetches kind 3 (follows), kind 10002 (relay list), etc.
|
|
|
|
### Pages can still add/remove mutes
|
|
The page-side `addMute()`/`removeMute()` flow publishes a new kind 10000 event. After publishing, the page sends a `syncMuteList` message to the worker so it reloads.
|
|
|
|
### Backward compatibility
|
|
Keep `isEventMuted()` exported from `init-ndk.mjs` so pages that need fine-grained control (like event-management showing muted items with a badge) can still check. But the worker pre-filters subscription broadcasts so most pages don't need to.
|
|
|
|
## Files to Modify
|
|
|
|
### `www/ndk-worker.js`
|
|
- Add mute list state variables (public/private mute sets)
|
|
- Add `parseMuteListTags()` helper
|
|
- Add `isWorkerEventMuted()` check function
|
|
- Add `handleLoadMuteList()` — fetches kind 10000, parses public tags, decrypts private content via `messageSigner.requestFromPage`
|
|
- Modify `wireSubscriptionHandlers()` — filter events through `isWorkerEventMuted()` before `broadcast()`
|
|
- Add `syncMuteList` message handler in the switch statement
|
|
- Call `handleLoadMuteList()` during `handleInit()` after relay connection
|
|
- Broadcast `muteListUpdated` event to all pages when mute list changes
|
|
|
|
### `www/js/init-ndk.mjs`
|
|
- Add `syncWorkerMuteList()` export that sends `syncMuteList` message to worker
|
|
- Modify `bootstrapMuteList()` to also trigger worker-side load
|
|
- Add handler for `muteListUpdated` worker message to keep page-side mute-list.mjs in sync
|
|
- Keep all existing exports for backward compatibility
|
|
|
|
### `www/js/mute-list.mjs`
|
|
- No changes needed — it continues to work page-side for pages that need it
|
|
- Worker sync will update it via the `muteListUpdated` event path
|
|
|
|
### Pages (minimal changes)
|
|
- `www/vj.html` — can remove the `isEventMuted` check in `handleMusicNdkEvent()` since worker pre-filters
|
|
- `www/feed.html`, `www/post.html`, `www/notifications.html`, `www/skills-edit.html` — subscription events will arrive pre-filtered; existing `isEventMuted()` calls become redundant but harmless (double-check is fine)
|
|
- `www/event-management.html` — keep page-side check since it shows muted items with a visual badge
|
|
- `www/block.html` — no subscription events; manages the mute list directly
|
|
|
|
## Implementation Steps
|
|
|
|
1. **Add mute state and parsing to worker** — port the `parseTags()` and `isEventMuted()` logic into `ndk-worker.js` as standalone functions
|
|
2. **Add `handleLoadMuteList()` to worker** — fetch kind 10000, parse public tags, decrypt private content using `messageSigner.requestFromPage('nip44Decrypt', ...)`
|
|
3. **Filter in `wireSubscriptionHandlers()`** — before `broadcast()`, check `isWorkerEventMuted(event)` and skip if true
|
|
4. **Call during init** — add `handleLoadMuteList(currentPubkey)` call in `handleInit()` after relay setup
|
|
5. **Add `syncMuteList` message type** — so pages can trigger a reload after publishing mute changes
|
|
6. **Broadcast `muteListUpdated`** — after worker loads/reloads mute list, broadcast the parsed public mute data to all pages
|
|
7. **Wire `init-ndk.mjs`** — handle `muteListUpdated` message, call `syncWorkerMuteList()` after `addMute`/`removeMute`
|
|
8. **Clean up page-side checks** — remove redundant `isEventMuted()` calls from subscription handlers in pages where the worker now pre-filters
|
|
|
|
## Edge Cases
|
|
|
|
- **Worker init before mute list loads**: Events arriving before the mute list is fetched will pass through unfiltered. This is acceptable — the mute list loads quickly and the window is small.
|
|
- **Multiple tabs with different mute states**: The worker is shared, so all tabs see the same mute filtering. This is correct behavior.
|
|
- **Mute list publish race**: After `addMute()` publishes, the page sends `syncMuteList` to the worker. The worker re-fetches from cache/relays. There's a brief window where the old mute list is active. Acceptable.
|
|
- **Anonymous/unauthenticated mode**: No mute list to load. Worker skips mute filtering. No change from current behavior.
|