48 changed files with 330 additions and 2104 deletions
@ -1,115 +0,0 @@
@@ -1,115 +0,0 @@
|
||||
= Test Article: Citation Embedding Examples |
||||
Author Name |
||||
2024-01-15 |
||||
|
||||
This article demonstrates all citation types and display methods that can be embedded in AsciiDoc articles. |
||||
|
||||
IMPORTANT: Replace all placeholder nevent IDs with actual citation event IDs from your Nostr relays. |
||||
|
||||
== Citation Format |
||||
|
||||
All citations use the format: `[[citation::TYPE::NEVENT_ID]]` |
||||
|
||||
The TYPE can be: |
||||
- `inline` - renders inline within text |
||||
- `foot` - creates a footnote |
||||
- `foot-end` - creates a footnote that links to an endnote |
||||
- `end` - appears at the end in references section |
||||
- `quote` - block-level citation card |
||||
- `prompt-inline` - inline prompt citation |
||||
- `prompt-end` - prompt citation in references section |
||||
|
||||
== Internal Citations (Kind 30) |
||||
|
||||
Internal citations reference other Nostr events. |
||||
|
||||
=== Inline Internal Citation |
||||
|
||||
Here's an inline citation: [[citation::inline::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsqgqpl98djyt2eln4uy4dlx2l6a6eyum8acgqz3vfnqptkx54suyyn5u59v88]] |
||||
|
||||
You can have multiple inline citations in one sentence: The first citation [[citation::inline::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyry7njqtmu366utn3422xt84hv6pg3h5vsac602xne63hw7hmrjew8x37w3]] and the second citation [[citation::inline::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyr977llj62ttqp3zw5dhdp3mdswng5ge7hfgdsz2vc7f5w5889w857hmzhr]] both reference Nostr events. |
||||
|
||||
=== Footnote Internal Citation |
||||
|
||||
This sentence has a footnote citation.footnote: [[citation::foot::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsqgqpl98djyt2eln4uy4dlx2l6a6eyum8acgqz3vfnqptkx54suyyn5u59v88]] |
||||
|
||||
=== Endnote Internal Citation |
||||
|
||||
This paragraph uses an endnote citation that will appear in the references section [[citation::end::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyry7njqtmu366utn3422xt84hv6pg3h5vsac602xne63hw7hmrjew8x37w3]]. |
||||
|
||||
=== Block Quote Internal Citation |
||||
|
||||
For block-level display of citations: |
||||
|
||||
[[citation::quote::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyry7njqtmu366utn3422xt84hv6pg3h5vsac602xne63hw7hmrjew8x37w3]] |
||||
|
||||
== External Web Citations (Kind 31) |
||||
|
||||
External citations reference web resources. |
||||
|
||||
=== Inline External Citation |
||||
|
||||
Here's an inline external citation: [[citation::inline::nevent1qvzqqqqqrupzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyqsdgj88q2tswy2stc9p5xfaf200kr9le8m75se084upkrqkex9yvwk8faj]] |
||||
|
||||
=== Footnote-End External Citation |
||||
|
||||
This creates a footnote that links to an endnote.footnote: [[[citation::foot-end::nevent1qvzqqqqqrupzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyqsdgj88q2tswy2stc9p5xfaf200kr9le8m75se084upkrqkex9yvwk8faj]]] |
||||
|
||||
=== Endnote External Citation |
||||
|
||||
This paragraph references a web source [[citation::end::nevent1qvzqqqqqrupzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyqsdgj88q2tswy2stc9p5xfaf200kr9le8m75se084upkrqkex9yvwk8faj]]. |
||||
|
||||
== Hardcopy Citations (Kind 32) |
||||
|
||||
Hardcopy citations reference printed materials like books and journals. |
||||
|
||||
=== Inline Hardcopy Citation |
||||
|
||||
Here's an inline hardcopy citation: [[citation::inline::nevent1qvzqqqqqyqpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcprfmhxue69uhkvun9v4kxz7fwwdhhvcnfwshxsmmnwshsqgyzg2dv4w5dpsalmm28qvn3t0gsl09u0m5ar4jfupzkrt5t0fh2vgzych2c]] |
||||
|
||||
=== Endnote Hardcopy Citation |
||||
|
||||
This references a book [[citation::end::nevent1qvzqqqqqyqpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcprfmhxue69uhkvun9v4kxz7fwwdhhvcnfwshxsmmnwshsqgyzg2dv4w5dpsalmm28qvn3t0gsl09u0m5ar4jfupzkrt5t0fh2vgzych2c]]. |
||||
|
||||
=== Block Quote Hardcopy Citation |
||||
|
||||
For important book references: |
||||
|
||||
[[citation::quote::nevent1qvzqqqqqyqpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcprfmhxue69uhkvun9v4kxz7fwwdhhvcnfwshxsmmnwshsqgyzg2dv4w5dpsalmm28qvn3t0gsl09u0m5ar4jfupzkrt5t0fh2vgzych2c]] |
||||
|
||||
== Prompt Citations (Kind 33) |
||||
|
||||
Prompt citations reference AI/LLM interactions. |
||||
|
||||
=== Inline Prompt Citation |
||||
|
||||
Here's an inline prompt citation: [[citation::prompt-inline::nevent1qvzqqqqqyypzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyprf7ddefkkvdedvredu83pqqn7payvvcnrqp2s72zrx823x0wpezqzpst2]] |
||||
|
||||
=== Endnote Prompt Citation |
||||
|
||||
This paragraph discusses AI-generated content [[citation::prompt-end::nevent1qvzqqqqqyypzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyprf7ddefkkvdedvredu83pqqn7payvvcnrqp2s72zrx823x0wpezqzpst2]]. |
||||
|
||||
== Mixed Citation Usage |
||||
|
||||
You can mix different citation types in the same paragraph. For example, this sentence references both an external source [[citation::inline::nevent1qvzqqqqqrupzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyqsdgj88q2tswy2stc9p5xfaf200kr9le8m75se084upkrqkex9yvwk8faj]] and an internal Nostr event [[citation::inline::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsqgqpl98djyt2eln4uy4dlx2l6a6eyum8acgqz3vfnqptkx54suyyn5u59v88]]. |
||||
|
||||
Here's a combination with footnotes and endnotes: This sentence has a footnote.footnote: [[[citation::foot::nevent1qqst8cju0m99ner9ucsu0fw3p0p4v8x0nctvmfx03u67welhnevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyry7njqtmu366utn3422xt84hv6pg3h5vsac602xne63hw7hmrjew8x37w34z5h27wwp4m8x7ttswf0lk2wr8gs4lw9z34vamnwvaz7tmwdaehgu3wvfhkummwvaz7tmjv4kxz7fwdehk6k6]]] and this sentence references an endnote [[citation::end::nevent1qvzqqqqqrcpzqez7hqy2ca5f7z94zslmu7489zd645hrhurfeqwj5g4q6we438qcqydhwumn8ghj7mmjd3uj6un9d3shjtnfd4mkzmry9ejh2tcpr3mhxue69uhhg6r9vd5hgctyv4kzumn0wd68yvfwvdhk6tcqyry7njqtmu366utn3422xt84hv6pg3h5vsac602xne63hw7hmrjew8x37w3]]. |
||||
|
||||
== Citation Display Types Summary |
||||
|
||||
. *Inline* (`inline`, `prompt-inline`): Renders inline within the text as clickable citation text |
||||
. *Footnotes* (`foot`): Creates superscript numbers that link to footnotes |
||||
. *Foot-End* (`foot-end`): Creates footnotes that link to endnotes at the end |
||||
. *Endnotes* (`end`, `prompt-end`): References appear at the end of the document in a references section |
||||
. *Quotes* (`quote`): Block-level citation cards for emphasis |
||||
|
||||
== How to Use This Test Document |
||||
|
||||
. Replace all placeholder `nevent1qq...` IDs with actual citation event IDs from your Nostr relays |
||||
. Create citations using the Post Editor for kinds 30, 31, 32, and 33 |
||||
. Copy the nevent ID (or note ID) of your created citation event |
||||
. Replace the placeholder IDs in this document |
||||
. Publish as an AsciiDoc article (kind 30818) or Wiki Article (kind 30817) |
||||
. Verify all citation types render correctly |
||||
|
||||
NOTE: All citation event IDs in this document are placeholder examples. You must replace them with real citation event IDs to test properly. |
||||
@ -1,80 +0,0 @@
@@ -1,80 +0,0 @@
|
||||
# Citation Test Content Guide |
||||
|
||||
## File: `CITATION_TEST_CONTENT.adoc` |
||||
|
||||
This file contains comprehensive test examples for embedding all citation types in AsciiDoc articles. |
||||
|
||||
## Citation Format |
||||
|
||||
All citations use plain format (passthrough markers are added automatically during processing): |
||||
|
||||
``` |
||||
[[citation::TYPE::NEVENT_ID]] |
||||
``` |
||||
|
||||
## Citation Types Tested |
||||
|
||||
### 1. Internal Citations (Kind 30) |
||||
- `inline` - Inline citation within text |
||||
- `foot` - Footnote citation |
||||
- `end` - Endnote in references section |
||||
- `quote` - Block-level citation card |
||||
|
||||
### 2. External Web Citations (Kind 31) |
||||
- `inline` - Inline citation |
||||
- `foot-end` - Footnote linking to endnote |
||||
- `end` - Endnote in references |
||||
|
||||
### 3. Hardcopy Citations (Kind 32) |
||||
- `inline` - Inline citation |
||||
- `end` - Endnote in references |
||||
- `quote` - Block-level citation card |
||||
|
||||
### 4. Prompt Citations (Kind 33) |
||||
- `prompt-inline` - Inline prompt citation |
||||
- `prompt-end` - Prompt citation in references section |
||||
|
||||
## How to Use |
||||
|
||||
1. **Create Citation Events**: Use the Post Editor to create citations: |
||||
- Internal Citation (kind 30) |
||||
- External Citation (kind 31) |
||||
- Hardcopy Citation (kind 32) |
||||
- Prompt Citation (kind 33) |
||||
|
||||
2. **Get Citation IDs**: After creating citations, copy their nevent IDs (or note IDs) |
||||
|
||||
3. **Replace Placeholders**: In the test document, replace all `nevent1qq...` placeholder IDs with your actual citation event IDs |
||||
|
||||
4. **Test in Article**: |
||||
- Create a new AsciiDoc article (kind 30818) or Wiki Article (kind 30817) |
||||
- Paste the test content (with real citation IDs) |
||||
- Publish and verify all citation types render correctly |
||||
|
||||
## Citation Display Types |
||||
|
||||
- **inline** / **prompt-inline**: Renders as clickable text inline |
||||
- **foot**: Creates superscript footnote numbers |
||||
- **foot-end**: Creates footnotes that link to endnotes |
||||
- **end** / **prompt-end**: Appears in References section at end |
||||
- **quote**: Block-level citation card for emphasis |
||||
|
||||
## Testing Checklist |
||||
|
||||
- [ ] Internal citations render inline |
||||
- [ ] Internal citations render as footnotes |
||||
- [ ] Internal citations appear in references section |
||||
- [ ] External citations render correctly |
||||
- [ ] Hardcopy citations render correctly |
||||
- [ ] Prompt citations render correctly (inline and end) |
||||
- [ ] Block quote citations display as cards |
||||
- [ ] Mixed citations in same paragraph work |
||||
- [ ] All citation types are clickable and navigate correctly |
||||
|
||||
## Notes |
||||
|
||||
- Citation IDs can be in format: `nevent1...`, `note1...`, or hex IDs |
||||
- All citations must exist on your Nostr relays to render properly |
||||
- Endnotes automatically collect at the end in a "References" section |
||||
- Footnotes appear at the bottom of the page/section |
||||
|
||||
@ -1,139 +0,0 @@
@@ -1,139 +0,0 @@
|
||||
# Files That Should Use Central Services |
||||
|
||||
## Summary |
||||
After refactoring `client.service.ts` into focused services, these files should be updated to use the new central services instead of direct client.service calls or bypassing the service layer. |
||||
|
||||
## High Priority Updates |
||||
|
||||
### 1. `src/hooks/useFetchProfile.tsx` |
||||
**Current**: Uses `client.getProfileFromIndexedDB()` and `client.fetchProfile()` |
||||
**Should Use**: `replaceableEventService.fetchReplaceableEvent()` or new ProfileService |
||||
**Benefit**: Gets cache-warming and refresh benefits |
||||
|
||||
### 2. `src/hooks/useFetchEvent.tsx` |
||||
**Current**: Directly accesses `client.eventCacheMap` (line 26) |
||||
**Should Use**: `eventService.fetchEvent()` and `eventService.getSessionEventsMatchingSearch()` |
||||
**Benefit**: Proper encapsulation, better caching |
||||
|
||||
### 3. `src/components/Note/PublicationIndex/PublicationIndex.tsx` |
||||
**Current**: |
||||
- Directly uses `indexedDb.getReplaceableEvent()` (line 686) |
||||
- Uses `client.fetchEvent()` (line 707) |
||||
- Has custom `fetchEventFromRelay()` function |
||||
**Should Use**: |
||||
- `replaceableEventService.fetchReplaceableEvent()` |
||||
- `eventService.fetchEvent()` |
||||
- `queryService.fetchEvents()` instead of custom relay fetching |
||||
**Benefit**: Consistent caching and race-based fetching |
||||
|
||||
### 4. `src/services/note-stats.service.ts` |
||||
**Current**: Uses `client.fetchEvents()` (line 128) |
||||
**Should Use**: `queryService.fetchEvents()` |
||||
**Benefit**: Race-based fetching, better performance |
||||
|
||||
### 5. `src/components/Profile/ProfileBookmarksAndHashtags.tsx` |
||||
**Current**: |
||||
- Uses `client.fetchEvents()` directly (line 292) |
||||
- Uses `client.fetchInterestListEvent()` (line 300) |
||||
**Should Use**: |
||||
- `queryService.fetchEvents()` |
||||
- `replaceableEventService.fetchReplaceableEvent(pubkey, 10015)` |
||||
**Benefit**: Consistent query strategies |
||||
|
||||
### 6. `src/components/SimpleNoteFeed/index.tsx` |
||||
**Current**: Uses `client.fetchEvents()` (line 89) |
||||
**Should Use**: `queryService.fetchEvents()` |
||||
**Benefit**: Race-based fetching for better performance |
||||
|
||||
## Medium Priority Updates |
||||
|
||||
### 7. `src/services/mention-event-search.service.ts` |
||||
**Current**: Likely uses `client.getSessionEventsMatchingSearch()` |
||||
**Should Use**: `eventService.getSessionEventsMatchingSearch()` |
||||
**Benefit**: Proper service encapsulation |
||||
|
||||
### 8. `src/components/Bookstr/BookstrContent.tsx` |
||||
**Current**: Uses `client.fetchBookstrEvents()` |
||||
**Should Use**: `macroService.fetchMacroEvents()` (with type='bookstr') |
||||
**Benefit**: Uses new MacroService architecture |
||||
|
||||
### 9. `src/services/relay-selection.service.ts` |
||||
**Current**: Uses `client.fetchRelayList()` and `client.getSessionSuccessfulPublishRelayUrlsForRandomPool()` |
||||
**Should Use**: New RelayService (to be created) |
||||
**Benefit**: Proper relay management |
||||
|
||||
### 10. `src/providers/NostrProvider/index.tsx` |
||||
**Current**: Extensive use of `client.fetchRelayList()`, `client.fetchEvents()`, etc. |
||||
**Should Use**: All new services |
||||
**Benefit**: Cache-warming integration, better performance |
||||
|
||||
## Low Priority (Internal Services) |
||||
|
||||
### 11. `src/services/gif.service.ts` |
||||
**Check**: If it uses `client.fetchEvents()` directly |
||||
**Should Use**: `queryService.fetchEvents()` |
||||
|
||||
### 12. `src/services/lightning.service.ts` |
||||
**Check**: If it fetches events directly |
||||
**Should Use**: Appropriate service |
||||
|
||||
### 13. `src/components/Embedded/EmbeddedNote.tsx` |
||||
**Check**: If it uses `client.fetchEvent()` directly |
||||
**Should Use**: `eventService.fetchEvent()` |
||||
|
||||
## Cache Integration Opportunities |
||||
|
||||
### Files That Should Use CacheService |
||||
|
||||
1. **`src/providers/NostrProvider/index.tsx`** |
||||
- Add cache-warming on login |
||||
- Use `cacheService.warmupCache()` in initialization |
||||
- Use `cacheService.getProfileWithRefresh()` for profiles |
||||
- Use `cacheService.getRelayListWithRefresh()` for relay lists |
||||
|
||||
2. **`src/hooks/useFetchProfile.tsx`** |
||||
- Use `cacheService.getProfileWithRefresh()` instead of manual cache checking |
||||
- Gets automatic background refresh for stale profiles |
||||
|
||||
3. **`src/hooks/useFetchRelayList.tsx`** |
||||
- Use `cacheService.getRelayListWithRefresh()` instead of manual cache checking |
||||
|
||||
## Direct IndexedDB Access to Replace |
||||
|
||||
### Files Accessing IndexedDB Directly (Should Use Services) |
||||
|
||||
1. **`src/components/Note/PublicationIndex/PublicationIndex.tsx`** |
||||
- Line 686: `indexedDb.getReplaceableEvent()` → Use `replaceableEventService` |
||||
- Line 930: `indexedDb.getPublicationEvent()` → Use appropriate service |
||||
- Line 934: `indexedDb.getEventFromPublicationStore()` → Use `eventService` |
||||
|
||||
2. **`src/components/Profile/index.tsx`** |
||||
- Check for direct IndexedDB access for payment info |
||||
- Should use `replaceableEventService.fetchReplaceableEvent(pubkey, ExtendedKind.PAYMENT_INFO)` |
||||
|
||||
## Migration Order |
||||
|
||||
1. **Phase 1**: Update hooks (`useFetchProfile`, `useFetchEvent`, `useFetchRelayList`) |
||||
- These are used everywhere, so fixing them benefits all components |
||||
|
||||
2. **Phase 2**: Update core components (`Profile`, `PublicationIndex`) |
||||
- High-impact components that users interact with frequently |
||||
|
||||
3. **Phase 3**: Update services (`note-stats`, `mention-event-search`) |
||||
- Internal services that can be updated without UI changes |
||||
|
||||
4. **Phase 4**: Update providers (`NostrProvider`) |
||||
- Add cache-warming and refresh strategies |
||||
|
||||
5. **Phase 5**: Update remaining components |
||||
- Lower priority, but should be done for consistency |
||||
|
||||
## Testing Checklist |
||||
|
||||
After migration, verify: |
||||
- [ ] Profiles load quickly (cache-first) |
||||
- [ ] Events load quickly (race-based fetching) |
||||
- [ ] Cache refreshes in background for stale data |
||||
- [ ] No duplicate network requests |
||||
- [ ] Cache-warming works on login |
||||
- [ ] Background refresh doesn't block UI |
||||
@ -1,189 +0,0 @@
@@ -1,189 +0,0 @@
|
||||
# Migration Guide: ClientService Refactoring |
||||
|
||||
## Overview |
||||
The `client.service.ts` (4313 lines) has been refactored into focused service modules. This guide helps migrate existing code to use the new services. |
||||
|
||||
## New Service Architecture |
||||
|
||||
### 1. QueryService (`client-query.service.ts`) |
||||
**Purpose**: Core query/subscription logic with race-based fetching |
||||
|
||||
**Key Methods**: |
||||
- `query(urls, filter, onevent, options)` - Core query with race strategies |
||||
- `subscribe(urls, filter, callbacks)` - Relay subscriptions |
||||
- `fetchEvents(urls, filter, options)` - Fetch events with caching |
||||
- `trackEventSeenOn(eventId, relay)` - Track where events were seen |
||||
- `getSeenEventRelayUrls(eventId)` - Get relays that saw an event |
||||
|
||||
**Migration**: Most internal usage, but if you're calling `query` or `subscribe` directly, use `queryService` instead. |
||||
|
||||
### 2. EventService (`client-events.service.ts`) |
||||
**Purpose**: Single event fetching and caching |
||||
|
||||
**Key Methods**: |
||||
- `fetchEvent(id)` - Fetch single event by ID |
||||
- `fetchEventForceRetry(eventId)` - Force retry fetch |
||||
- `fetchEventWithExternalRelays(eventId, externalRelays)` - Fetch with specific relays |
||||
- `addEventToCache(event)` - Add to session cache |
||||
- `getSessionEventsMatchingSearch(query, limit, allowedKinds)` - Search session cache |
||||
- `clearCaches()` - Clear all caches |
||||
|
||||
**Migration**: Replace `client.fetchEvent()` with `eventService.fetchEvent()` |
||||
|
||||
### 3. ReplaceableEventService (`client-replaceable-events.service.ts`) |
||||
**Purpose**: Replaceable events (profiles, relay lists, follow lists, etc.) |
||||
|
||||
**Key Methods**: |
||||
- `fetchReplaceableEvent(pubkey, kind, d?)` - Fetch replaceable event |
||||
- `fetchReplaceableEventsFromBigRelays(pubkeys, kind)` - Batch fetch |
||||
- `updateReplaceableEventCache(event)` - Update cache |
||||
- `clearCaches()` - Clear caches |
||||
|
||||
**Migration**: Replace `client.fetchProfileEvent()`, `client.fetchRelayListEvent()`, etc. with `replaceableEventService.fetchReplaceableEvent()` |
||||
|
||||
### 4. MacroService (`client-macro.service.ts`) |
||||
**Purpose**: Macro-specific events (Bookstr, Wikistr, etc.) |
||||
|
||||
**Key Methods**: |
||||
- `fetchMacroEvents(filters)` - Fetch macro events |
||||
- `getCachedMacroEvents(filters)` - Get from cache |
||||
|
||||
**Migration**: Replace `client.fetchBookstrEvents()` with `macroService.fetchMacroEvents()` |
||||
|
||||
### 5. CacheService (`client-cache.service.ts`) |
||||
**Purpose**: Universal cache-warming and refresh strategy |
||||
|
||||
**Key Methods**: |
||||
- `warmupCache(config, fetchFn)` - Warm up cache on login |
||||
- `scheduleRefresh(pubkey, kind, fetchFn)` - Schedule background refresh |
||||
- `getProfileWithRefresh(pubkey, fetchFn)` - Get profile with auto-refresh |
||||
- `getRelayListWithRefresh(pubkey, fetchFn)` - Get relay list with auto-refresh |
||||
- `isStale(pubkey, kind, cachedAt)` - Check if cache is stale |
||||
- `startPeriodicRefresh(refreshFn)` - Start periodic refresh |
||||
|
||||
**Migration**: Use for cache-warming on login and background refresh |
||||
|
||||
## Files That Need Updates |
||||
|
||||
### High Priority (Direct client.service usage) |
||||
|
||||
1. **`src/providers/NostrProvider/index.tsx`** |
||||
- Uses: `client.fetchRelayList()`, `client.fetchProfileEvent()`, `client.fetchEvents()` |
||||
- Update: Use `replaceableEventService`, `eventService`, `queryService` |
||||
|
||||
2. **`src/hooks/useFetchProfile.tsx`** |
||||
- Uses: `client.fetchProfile()`, `client.getProfileFromIndexedDB()` |
||||
- Update: Use `replaceableEventService` or new profile service |
||||
|
||||
3. **`src/hooks/useFetchEvent.tsx`** |
||||
- Uses: `client.fetchEvent()` |
||||
- Update: Use `eventService.fetchEvent()` |
||||
|
||||
4. **`src/hooks/useFetchRelayList.tsx`** |
||||
- Uses: `client.fetchRelayList()` |
||||
- Update: Use `replaceableEventService` or new relay service |
||||
|
||||
5. **`src/components/Profile/index.tsx`** |
||||
- Uses: `client.fetchPaymentInfoEvent()`, `client.fetchEvents()` |
||||
- Update: Use `replaceableEventService`, `queryService` |
||||
|
||||
6. **`src/components/Profile/ProfileBookmarksAndHashtags.tsx`** |
||||
- Uses: `client.fetchEvents()`, `client.fetchInterestListEvent()` |
||||
- Update: Use `queryService`, `replaceableEventService` |
||||
|
||||
### Medium Priority (Indirect usage) |
||||
|
||||
7. **`src/services/note-stats.service.ts`** |
||||
- Uses: `client.fetchEvents()` |
||||
- Update: Use `queryService.fetchEvents()` |
||||
|
||||
8. **`src/services/mention-event-search.service.ts`** |
||||
- Uses: `client.getSessionEventsMatchingSearch()` |
||||
- Update: Use `eventService.getSessionEventsMatchingSearch()` |
||||
|
||||
9. **`src/components/Bookstr/BookstrContent.tsx`** |
||||
- Uses: `client.fetchBookstrEvents()` |
||||
- Update: Use `macroService.fetchMacroEvents()` |
||||
|
||||
10. **`src/components/Note/PublicationIndex/PublicationIndex.tsx`** |
||||
- Uses: `client.fetchEvent()`, `indexedDb.getReplaceableEvent()` |
||||
- Update: Use `eventService.fetchEvent()`, `replaceableEventService` |
||||
|
||||
### Low Priority (Internal services) |
||||
|
||||
11. **`src/services/relay-selection.service.ts`** |
||||
- Uses: `client.fetchRelayList()` |
||||
- Update: Use `replaceableEventService` or new relay service |
||||
|
||||
12. **`src/services/relay-info.service.ts`** |
||||
- Uses: `client.fetchEvents()` |
||||
- Update: Use `queryService.fetchEvents()` |
||||
|
||||
## Migration Pattern |
||||
|
||||
### Before: |
||||
```typescript |
||||
import client from '@/services/client.service' |
||||
|
||||
const profile = await client.fetchProfile(pubkey) |
||||
const event = await client.fetchEvent(eventId) |
||||
const relayList = await client.fetchRelayList(pubkey) |
||||
``` |
||||
|
||||
### After: |
||||
```typescript |
||||
import { eventService, replaceableEventService } from '@/services/client.service' |
||||
|
||||
const profileEvent = await replaceableEventService.fetchReplaceableEvent(pubkey, kinds.Metadata) |
||||
const event = await eventService.fetchEvent(eventId) |
||||
const relayListEvent = await replaceableEventService.fetchReplaceableEvent(pubkey, kinds.RelayList) |
||||
``` |
||||
|
||||
## Integration in Main ClientService |
||||
|
||||
The main `client.service.ts` will be refactored to: |
||||
1. Instantiate all sub-services |
||||
2. Delegate method calls to appropriate services |
||||
3. Maintain backward compatibility during transition |
||||
4. Gradually remove old implementations |
||||
|
||||
## Cache Warming Integration |
||||
|
||||
Add to `NostrProvider` initialization: |
||||
|
||||
```typescript |
||||
import cacheService from '@/services/client-cache.service' |
||||
|
||||
// On login/initialization |
||||
await cacheService.warmupCache({ |
||||
profilePubkeys: [account.pubkey, ...recentInteractions], |
||||
relayListPubkeys: [account.pubkey], |
||||
warmupFollowLists: true, |
||||
warmupMuteLists: true |
||||
}, { |
||||
fetchProfile: (id) => replaceableEventService.fetchReplaceableEvent(...), |
||||
fetchRelayList: (pubkey) => relayService.fetchRelayList(pubkey), |
||||
// ... |
||||
}) |
||||
|
||||
// Start periodic refresh |
||||
cacheService.startPeriodicRefresh(async (pubkey, kind) => { |
||||
await replaceableEventService.fetchReplaceableEvent(pubkey, kind) |
||||
}) |
||||
``` |
||||
|
||||
## Benefits |
||||
|
||||
1. **Performance**: Race-based fetching reduces wait times from 10-30s to 1-3s |
||||
2. **Cache efficiency**: Universal cache-warming and refresh strategy |
||||
3. **Maintainability**: Focused services are easier to understand and modify |
||||
4. **Testability**: Services can be tested independently |
||||
5. **Extensibility**: Easy to add new macro types or event types |
||||
|
||||
## Next Steps |
||||
|
||||
1. Complete remaining service extractions (ProfileService, RelayService, TimelineService) |
||||
2. Update main `client.service.ts` to orchestrate sub-services |
||||
3. Migrate high-priority files first |
||||
4. Test thoroughly |
||||
5. Remove old code once migration is complete |
||||
@ -1,160 +0,0 @@
@@ -1,160 +0,0 @@
|
||||
# ClientService Refactoring - Completion Summary |
||||
|
||||
## Overview |
||||
The monolithic `client.service.ts` (originally 4312 lines) has been successfully refactored into a modular architecture with focused sub-services. |
||||
|
||||
## Results |
||||
|
||||
### File Size Reduction |
||||
- **Before**: 4312 lines |
||||
- **After**: 2119 lines |
||||
- **Reduction**: 50.8% (2193 lines removed/refactored) |
||||
|
||||
### Services Created |
||||
|
||||
1. **QueryService** (`client-query.service.ts`) - 437 lines |
||||
- Core query/subscription logic |
||||
- Race-based fetching strategies (replaceableRace, immediateReturn) |
||||
- Relay connection management |
||||
- Event tracking (seenOnRelays) |
||||
- Concurrent subscription management |
||||
|
||||
2. **EventService** (`client-events.service.ts`) - 267 lines |
||||
- Single event fetching by ID (hex, note1, nevent1, naddr1) |
||||
- Event caching with DataLoader |
||||
- Session cache management |
||||
- Force retry and external relay fetching |
||||
|
||||
3. **ReplaceableEventService** (`client-replaceable-events.service.ts`) - 230 lines |
||||
- Replaceable event fetching (profiles, relay lists, follow lists, etc.) |
||||
- Batch operations with DataLoader |
||||
- Cache coordination with IndexedDB |
||||
|
||||
4. **MacroService** (`client-macro.service.ts`) - 310 lines |
||||
- Macro-specific event fetching (Bookstr, Wikistr, extensible) |
||||
- Macro metadata extraction |
||||
- Specialized filtering and verse range expansion |
||||
- Cache-first strategy with background refresh |
||||
|
||||
5. **CacheService** (`client-cache.service.ts`) - 311 lines |
||||
- Universal cache-warming strategy |
||||
- Cache refresh scheduling |
||||
- TTL management |
||||
- Background refresh coordination |
||||
|
||||
## Architecture |
||||
|
||||
### Service Dependencies |
||||
``` |
||||
ClientService (orchestrator) |
||||
├── QueryService (core query logic) |
||||
├── EventService (depends on QueryService) |
||||
├── ReplaceableEventService (depends on QueryService) |
||||
├── MacroService (depends on QueryService) |
||||
└── CacheService (standalone, used by providers) |
||||
``` |
||||
|
||||
### Delegation Pattern |
||||
The main `ClientService` now acts as an orchestrator: |
||||
- **39+ method delegations** to sub-services |
||||
- Maintains backward compatibility |
||||
- Handles complex orchestration (publishing, timeline subscriptions) |
||||
- Manages cross-cutting concerns (relay selection, profile search) |
||||
|
||||
## Key Improvements |
||||
|
||||
### 1. Performance |
||||
- **Race-based fetching**: Replaceable events use 2-second wait strategy |
||||
- **Immediate return**: Single events by ID return on first match |
||||
- **Batch operations**: DataLoader batching reduces network calls |
||||
- **Cache-first**: IndexedDB checked before network requests |
||||
|
||||
### 2. Maintainability |
||||
- **Focused services**: Each service has a single responsibility |
||||
- **Clear boundaries**: Services are testable in isolation |
||||
- **Reduced complexity**: Main service is 50% smaller |
||||
- **Better organization**: Related functionality grouped together |
||||
|
||||
### 3. Extensibility |
||||
- **MacroService**: Easy to add new macro types (Wikistr, etc.) |
||||
- **QueryService**: Centralized query logic for all event types |
||||
- **ReplaceableEventService**: Handles all replaceable event kinds uniformly |
||||
|
||||
## What Remains in ClientService |
||||
|
||||
The following responsibilities remain in `ClientService` as they represent core orchestration: |
||||
|
||||
1. **Publishing** (`publishEvent`, `determineTargetRelays`) |
||||
- Complex relay selection logic |
||||
- Publish statistics and failure tracking |
||||
- Authentication handling |
||||
|
||||
2. **Timeline Subscriptions** (`subscribeTimeline`) |
||||
- Complex state management |
||||
- Progressive loading |
||||
- Timeline reference tracking |
||||
|
||||
3. **Profile Search** (`searchProfiles`, `searchProfilesFromLocal`) |
||||
- FlexSearch index management |
||||
- Local profile search |
||||
|
||||
4. **Relay List Merging** (`fetchRelayLists`) |
||||
- Complex merging of cache relays with regular relay lists |
||||
- Offline-first strategy |
||||
|
||||
## Code Quality |
||||
|
||||
### Linter Status |
||||
- ✅ **0 errors** |
||||
- ✅ **0 warnings** |
||||
- ✅ All unused imports removed |
||||
- ✅ All unused methods removed |
||||
- ✅ All duplicate implementations removed |
||||
|
||||
### Logger Integration |
||||
- ✅ Efficient logger implementation |
||||
- ✅ Development: Browser console |
||||
- ✅ Production: Console GUI in Imwald app |
||||
- ✅ Performance logging included |
||||
|
||||
## Migration Status |
||||
|
||||
### Completed |
||||
- ✅ All sub-services created and integrated |
||||
- ✅ Main service refactored to orchestrate sub-services |
||||
- ✅ Legacy code removed |
||||
- ✅ Code cleaned and optimized |
||||
|
||||
### Remaining (Optional) |
||||
The following files could be updated to use sub-services directly (see `FILES_TO_UPDATE.md`): |
||||
- Hooks: `useFetchProfile`, `useFetchEvent`, `useFetchRelayList` |
||||
- Components: `Profile`, `PublicationIndex`, `ProfileBookmarksAndHashtags` |
||||
- Services: `note-stats.service`, `mention-event-search.service` |
||||
- Providers: `NostrProvider` (for cache-warming integration) |
||||
|
||||
These updates are **optional** as the current delegation pattern maintains backward compatibility. |
||||
|
||||
## Testing Recommendations |
||||
|
||||
1. **Unit Tests**: Test each service independently |
||||
2. **Integration Tests**: Test service interactions |
||||
3. **Performance Tests**: Verify race-based fetching improvements |
||||
4. **Cache Tests**: Verify cache-warming and refresh strategies |
||||
|
||||
## Next Steps (Optional) |
||||
|
||||
1. **Cache-Warming Integration**: Add cache-warming to `NostrProvider` on login |
||||
2. **Direct Service Usage**: Update high-priority files to use services directly |
||||
3. **Additional Services**: Consider extracting TimelineService or RelayService if needed |
||||
4. **Documentation**: Add JSDoc comments to public methods |
||||
|
||||
## Conclusion |
||||
|
||||
The refactoring is **complete and production-ready**. The codebase is now: |
||||
- ✅ **Clean**: 0 linter errors/warnings |
||||
- ✅ **Performant**: Race-based fetching, cache-first strategy |
||||
- ✅ **Robust**: Proper error handling, logging |
||||
- ✅ **Maintainable**: Focused services, clear boundaries |
||||
- ✅ **Extensible**: Easy to add new features |
||||
|
||||
The main `ClientService` now serves as a clean orchestrator, delegating to specialized sub-services while maintaining backward compatibility. |
||||
@ -1,80 +0,0 @@
@@ -1,80 +0,0 @@
|
||||
# ClientService Refactoring Plan |
||||
|
||||
## Overview |
||||
Breaking down the 4313-line `client.service.ts` into focused, maintainable services with universal cache-warming strategy. |
||||
|
||||
## Service Architecture |
||||
|
||||
### 1. **QueryService** (`client-query.service.ts`) ✅ |
||||
- Core query/subscription logic |
||||
- Race-based fetching strategies |
||||
- Relay connection management |
||||
- Event tracking |
||||
|
||||
### 2. **CacheService** (`client-cache.service.ts`) ✅ |
||||
- Universal cache-warming strategy |
||||
- Cache refresh scheduling |
||||
- TTL management |
||||
- Background refresh coordination |
||||
|
||||
### 3. **EventService** (`client-events.service.ts`) ✅ |
||||
- Single event fetching |
||||
- Event caching |
||||
- Session cache management |
||||
- DataLoader integration |
||||
|
||||
### 4. **ReplaceableEventService** (`client-replaceable-events.service.ts`) ✅ |
||||
- Replaceable event fetching (profiles, relay lists, etc.) |
||||
- Batch operations |
||||
- Cache coordination |
||||
|
||||
### 5. **MacroService** (`client-macro.service.ts`) ✅ |
||||
- Macro-specific event fetching (Bookstr, etc.) |
||||
- Macro metadata extraction |
||||
- Specialized filtering |
||||
- Extensible for future macro types |
||||
|
||||
### 6. **CacheService** (`client-cache.service.ts`) ✅ |
||||
- Universal cache-warming strategy |
||||
- Cache refresh scheduling |
||||
- TTL management |
||||
- Background refresh coordination |
||||
|
||||
### Note on Additional Services |
||||
The following services were considered but are currently handled within `ClientService` as orchestration logic: |
||||
- **Profile search/index**: Handled in `ClientService` with delegation to `ReplaceableEventService` for fetching |
||||
- **Relay management**: Publishing and relay selection remain in `ClientService` as core orchestration |
||||
- **Timeline subscriptions**: Complex state management remains in `ClientService` but uses `QueryService` and `EventService` |
||||
|
||||
## Cache Strategy |
||||
|
||||
### Cache-Warming |
||||
- On login: Warm up current user's profile, relay list, follow list |
||||
- On feed load: Warm up profiles for visible pubkeys (batch, limited to 50) |
||||
- Background: Periodically refresh stale entries |
||||
|
||||
### Cache-Refreshing |
||||
- Stale detection: Check `addedAt` timestamp vs refresh thresholds |
||||
- Background refresh: Non-blocking, queued refresh for stale entries |
||||
- Periodic refresh: Every 5 minutes, check and refresh stale profiles |
||||
|
||||
### TTLs |
||||
- Profiles: 30 min cache, 15 min refresh threshold |
||||
- Payment info: 5 min cache, 2 min refresh threshold |
||||
- Relay lists: 15 min cache, 10 min refresh threshold |
||||
- Follow/Mute lists: 60 min cache, 30 min refresh threshold |
||||
|
||||
## Integration Strategy |
||||
|
||||
1. Create service instances in main `ClientService` |
||||
2. Inject dependencies (QueryService into others) |
||||
3. Maintain backward compatibility during transition |
||||
4. Gradually migrate methods to use new services |
||||
5. Remove old code once migration complete |
||||
|
||||
## Performance Benefits |
||||
|
||||
- **Faster initial load**: Cache-warming pre-fetches critical data |
||||
- **Better responsiveness**: Background refresh keeps cache fresh without blocking UI |
||||
- **Reduced network calls**: Smart cache invalidation prevents unnecessary fetches |
||||
- **Improved maintainability**: Focused services are easier to test and modify |
||||
@ -1,28 +0,0 @@
@@ -1,28 +0,0 @@
|
||||
import { RefObject, useEffect, useState } from 'react' |
||||
|
||||
/** |
||||
* Tracks the rendered width of `ref`'s element via ResizeObserver. |
||||
* Returns `undefined` until the first measurement fires. |
||||
* Use this when you need a component to respond to its *own* container width |
||||
* rather than the viewport width (e.g. inside a split-pane layout where |
||||
* `isSmallScreen` is still `false` but the column is narrow). |
||||
*/ |
||||
export function useContainerWidth(ref: RefObject<Element | null>): number | undefined { |
||||
const [width, setWidth] = useState<number | undefined>(undefined) |
||||
|
||||
useEffect(() => { |
||||
const el = ref.current |
||||
if (!el) return |
||||
|
||||
const observer = new ResizeObserver((entries) => { |
||||
const entry = entries[0] |
||||
if (entry) setWidth(entry.contentRect.width) |
||||
}) |
||||
observer.observe(el) |
||||
// Initialise synchronously so there's no render flash
|
||||
setWidth(el.getBoundingClientRect().width) |
||||
return () => observer.disconnect() |
||||
}, [ref]) |
||||
|
||||
return width |
||||
} |
||||
@ -1,93 +0,0 @@
@@ -1,93 +0,0 @@
|
||||
import { ExtendedKind, FAST_READ_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' |
||||
import { |
||||
filterZapPollVoteReceiptsForVoter, |
||||
getPollIdFromZapReceipt, |
||||
parseZapPollEvent, |
||||
userZapPollVoteOption |
||||
} from '@/lib/zap-poll' |
||||
import { normalizeUrl } from '@/lib/url' |
||||
import client from '@/services/client.service' |
||||
import { Event } from 'nostr-tools' |
||||
import { kinds } from 'nostr-tools' |
||||
import { useCallback, useEffect, useMemo, useState } from 'react' |
||||
|
||||
function participationRelayUrls(): string[] { |
||||
const seen = new Set<string>() |
||||
const out: string[] = [] |
||||
for (const u of [...SEARCHABLE_RELAY_URLS, ...FAST_READ_RELAY_URLS]) { |
||||
const n = normalizeUrl(u) || u |
||||
if (!n || seen.has(n)) continue |
||||
seen.add(n) |
||||
out.push(n) |
||||
} |
||||
return out.slice(0, 14) |
||||
} |
||||
|
||||
export type TZapPollProfileRow = { |
||||
poll: Event |
||||
voteReceipt: Event |
||||
optionIndex: number |
||||
} |
||||
|
||||
/** |
||||
* Zap poll votes by `profilePubkey` (kind 9735 with P=profile and k=6969 in embedded zap request), |
||||
* resolved to kind 6969 poll events for profile timeline merge. |
||||
*/ |
||||
export function useProfileZapPollParticipation(profilePubkey: string | undefined) { |
||||
const [rows, setRows] = useState<TZapPollProfileRow[]>([]) |
||||
const [loading, setLoading] = useState(false) |
||||
|
||||
const load = useCallback(async () => { |
||||
if (!profilePubkey) { |
||||
setRows([]) |
||||
return |
||||
} |
||||
setLoading(true) |
||||
try { |
||||
const urls = participationRelayUrls() |
||||
const receipts = await client.fetchEvents(urls, { |
||||
kinds: [kinds.Zap], |
||||
'#p': [profilePubkey.trim().toLowerCase()], |
||||
limit: 300 |
||||
}) |
||||
const voteReceipts = filterZapPollVoteReceiptsForVoter(receipts, profilePubkey) |
||||
const pollIds = [...new Set(voteReceipts.map(getPollIdFromZapReceipt).filter(Boolean) as string[])] |
||||
if (pollIds.length === 0) { |
||||
setRows([]) |
||||
return |
||||
} |
||||
const polls = await client.fetchEvents(urls, { |
||||
kinds: [ExtendedKind.ZAP_POLL], |
||||
ids: pollIds, |
||||
limit: pollIds.length |
||||
}) |
||||
const pollById = new Map(polls.map((p) => [p.id, p])) |
||||
const built: TZapPollProfileRow[] = [] |
||||
for (const vr of voteReceipts) { |
||||
const pid = getPollIdFromZapReceipt(vr) |
||||
if (!pid) continue |
||||
const poll = pollById.get(pid) |
||||
if (!poll) continue |
||||
const pollMeta = parseZapPollEvent(poll) |
||||
if (!pollMeta) continue |
||||
const opt = userZapPollVoteOption(poll, pollMeta, profilePubkey, [vr]) |
||||
if (opt === undefined) continue |
||||
built.push({ poll, voteReceipt: vr, optionIndex: opt }) |
||||
} |
||||
built.sort((a, b) => b.voteReceipt.created_at - a.voteReceipt.created_at) |
||||
setRows(built) |
||||
} catch { |
||||
setRows([]) |
||||
} finally { |
||||
setLoading(false) |
||||
} |
||||
}, [profilePubkey]) |
||||
|
||||
useEffect(() => { |
||||
void load() |
||||
}, [load]) |
||||
|
||||
const pollIdsVoted = useMemo(() => new Set(rows.map((r) => r.poll.id)), [rows]) |
||||
|
||||
return { rows, loading, reload: load, pollIdsVoted } |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
import { userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' |
||||
import { useNostrOptional } from '@/providers/nostr-context' |
||||
import client from '@/services/client.service' |
||||
import { useEffect, useState } from 'react' |
||||
|
||||
/** Viewer NIP-65 read inboxes (incl. HTTP read) for embed / thread fan-out. */ |
||||
export function useViewerInboxRelayUrls(): { |
||||
inboxRelayUrls: string[] |
||||
} { |
||||
const nostr = useNostrOptional() |
||||
const pk = nostr?.pubkey?.trim() |
||||
const [inboxRelayUrls, setInboxRelayUrls] = useState<string[]>([]) |
||||
|
||||
useEffect(() => { |
||||
if (!pk) { |
||||
setInboxRelayUrls([]) |
||||
return |
||||
} |
||||
let cancelled = false |
||||
void client.peekRelayListFromStorage(pk).then((rl) => { |
||||
if (cancelled) return |
||||
setInboxRelayUrls(userReadRelaysWithHttp(rl).slice(0, 14)) |
||||
}) |
||||
return () => { |
||||
cancelled = true |
||||
} |
||||
}, [pk]) |
||||
|
||||
return { inboxRelayUrls } |
||||
} |
||||
@ -1,46 +0,0 @@
@@ -1,46 +0,0 @@
|
||||
import { userReadRelaysWithHttp } from '@/lib/favorites-feed-relays' |
||||
import { viewerMayUseNostrLandAggr } from '@/lib/nostr-land-aggr' |
||||
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' |
||||
import { useNostrOptional } from '@/providers/nostr-context' |
||||
import client from '@/services/client.service' |
||||
import type { TRelayList } from '@/types' |
||||
import { useEffect, useMemo, useState } from 'react' |
||||
|
||||
/** |
||||
* Viewer NIP-65 read inboxes (incl. HTTP read) for embed / thread fan-out, plus whether they may use |
||||
* {@link AGGR_NOSTR_LAND_WSS} (nostr.land in favorites or NIP-65 lists). |
||||
*/ |
||||
export function useViewerInboxRelayUrlsAndAggrEligibility(): { |
||||
inboxRelayUrls: string[] |
||||
allowNostrLandAggr: boolean |
||||
} { |
||||
const nostr = useNostrOptional() |
||||
const pk = nostr?.pubkey?.trim() |
||||
const { favoriteRelays } = useFavoriteRelays() |
||||
const [inboxRelayUrls, setInboxRelayUrls] = useState<string[]>([]) |
||||
const [peekedNip65, setPeekedNip65] = useState<TRelayList | null>(null) |
||||
|
||||
useEffect(() => { |
||||
if (!pk) { |
||||
setInboxRelayUrls([]) |
||||
setPeekedNip65(null) |
||||
return |
||||
} |
||||
let cancelled = false |
||||
void client.peekRelayListFromStorage(pk).then((rl) => { |
||||
if (cancelled) return |
||||
setPeekedNip65(rl) |
||||
setInboxRelayUrls(userReadRelaysWithHttp(rl).slice(0, 14)) |
||||
}) |
||||
return () => { |
||||
cancelled = true |
||||
} |
||||
}, [pk]) |
||||
|
||||
const allowNostrLandAggr = useMemo( |
||||
() => viewerMayUseNostrLandAggr(favoriteRelays ?? [], peekedNip65 ?? undefined), |
||||
[favoriteRelays, peekedNip65] |
||||
) |
||||
|
||||
return { inboxRelayUrls, allowNostrLandAggr } |
||||
} |
||||
Loading…
Reference in new issue