mirror of
https://github.com/DocNR/POWR.git
synced 2025-04-19 10:51:19 +00:00
updated documentation
This commit is contained in:
parent
58194c0eb3
commit
6a8589662d
261
docs/design/Analysis/NDKFunctionHexKeys.md
Normal file
261
docs/design/Analysis/NDKFunctionHexKeys.md
Normal file
@ -0,0 +1,261 @@
|
||||
# NDK Functions for Hex Keys and NIP-19 Encoding/Decoding
|
||||
|
||||
When working with Nostr addresses (like naddr) and converting between hex and other formats, NDK provides several key functions. Here's a comprehensive overview of the main functions you'll need for handling hex keys and NIP-19 encoding/decoding in your POWR Pack implementation:
|
||||
|
||||
## Core NIP-19 Functions
|
||||
|
||||
NDK implements NIP-19 functionality in the `events/nip19.ts` file. The key functions you'll need are:
|
||||
|
||||
### 1. Decoding NIP-19 Entities
|
||||
|
||||
```typescript
|
||||
import { nip19 } from '@nostr-dev-kit/ndk';
|
||||
|
||||
// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.)
|
||||
function decodeNaddr(naddrString: string) {
|
||||
try {
|
||||
const decoded = nip19.decode(naddrString);
|
||||
|
||||
// For naddr specifically, you'll get:
|
||||
if (decoded.type === 'naddr') {
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// pubkey is the hex public key of the author
|
||||
// kind is the event kind (30004 for lists)
|
||||
// identifier is the 'd' tag value
|
||||
|
||||
console.log('Hex pubkey:', pubkey);
|
||||
console.log('Event kind:', kind);
|
||||
console.log('Identifier:', identifier);
|
||||
|
||||
return decoded.data;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Invalid NIP-19 format:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Encoding to NIP-19 Formats
|
||||
|
||||
```typescript
|
||||
// Create an naddr from components
|
||||
function createNaddr(pubkey: string, kind: number, identifier: string) {
|
||||
return nip19.naddrEncode({
|
||||
pubkey, // Hex pubkey
|
||||
kind, // Event kind (number)
|
||||
identifier // The 'd' tag value
|
||||
});
|
||||
}
|
||||
|
||||
// Create an npub from a hex public key
|
||||
function hexToNpub(hexPubkey: string) {
|
||||
return nip19.npubEncode(hexPubkey);
|
||||
}
|
||||
|
||||
// Create a note (event reference) from event ID
|
||||
function eventIdToNote(eventId: string) {
|
||||
return nip19.noteEncode(eventId);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Utility Functions for Hex Keys
|
||||
|
||||
```typescript
|
||||
// Convert npub to hex pubkey
|
||||
function npubToHex(npub: string) {
|
||||
try {
|
||||
const decoded = nip19.decode(npub);
|
||||
if (decoded.type === 'npub') {
|
||||
return decoded.data as string; // This is the hex pubkey
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Invalid npub format:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a string is a valid hex key (pubkey or event id)
|
||||
function isValidHexKey(hexString: string) {
|
||||
return /^[0-9a-f]{64}$/i.test(hexString);
|
||||
}
|
||||
```
|
||||
|
||||
## Using NIP-19 Functions with NDK Filters
|
||||
|
||||
Here's how you would use these functions with NDK filters to fetch a POWR Pack from an naddr:
|
||||
|
||||
```typescript
|
||||
async function fetchPackFromNaddr(naddr: string) {
|
||||
try {
|
||||
// Decode the naddr to get event coordinates
|
||||
const decoded = nip19.decode(naddr);
|
||||
|
||||
if (decoded.type !== 'naddr') {
|
||||
throw new Error('Not an naddr');
|
||||
}
|
||||
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// Ensure it's a list (kind 30004)
|
||||
if (kind !== 30004) {
|
||||
throw new Error('Not a NIP-51 list');
|
||||
}
|
||||
|
||||
// Create a filter to fetch the specific list event
|
||||
const filter = {
|
||||
kinds: [kind],
|
||||
authors: [pubkey], // Using the hex pubkey from the naddr
|
||||
'#d': identifier ? [identifier] : undefined, // Using the d-tag if available
|
||||
};
|
||||
|
||||
// Fetch the event
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
if (events.size === 0) {
|
||||
throw new Error('Pack not found');
|
||||
}
|
||||
|
||||
// Get the first matching event
|
||||
return Array.from(events)[0];
|
||||
} catch (error) {
|
||||
console.error('Error fetching pack:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing the Complete naddr Workflow for POWR Packs
|
||||
|
||||
Here's a complete example for fetching and processing a POWR Pack from an naddr:
|
||||
|
||||
```typescript
|
||||
import NDK, { NDKEvent, NDKFilter, nip19 } from '@nostr-dev-kit/ndk';
|
||||
|
||||
async function fetchAndProcessPOWRPack(naddr: string) {
|
||||
// 1. Initialize NDK
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: [
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.nostr.band'
|
||||
]
|
||||
});
|
||||
await ndk.connect();
|
||||
|
||||
// 2. Decode the naddr
|
||||
const decoded = nip19.decode(naddr);
|
||||
if (decoded.type !== 'naddr') {
|
||||
throw new Error('Invalid naddr format');
|
||||
}
|
||||
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// 3. Create filter to fetch the pack event
|
||||
const packFilter: NDKFilter = {
|
||||
kinds: [kind],
|
||||
authors: [pubkey],
|
||||
'#d': identifier ? [identifier] : undefined
|
||||
};
|
||||
|
||||
// 4. Fetch the pack event
|
||||
const packEvents = await ndk.fetchEvents(packFilter);
|
||||
if (packEvents.size === 0) {
|
||||
throw new Error('Pack not found');
|
||||
}
|
||||
|
||||
const packEvent = Array.from(packEvents)[0];
|
||||
|
||||
// 5. Extract template and exercise references
|
||||
const templateRefs: string[] = [];
|
||||
const exerciseRefs: string[] = [];
|
||||
|
||||
for (const tag of packEvent.tags) {
|
||||
if (tag[0] === 'a') {
|
||||
const addressPointer = tag[1];
|
||||
// Format is kind:pubkey:d-tag
|
||||
if (addressPointer.startsWith('33402:')) { // Workout template
|
||||
templateRefs.push(addressPointer);
|
||||
} else if (addressPointer.startsWith('33401:')) { // Exercise
|
||||
exerciseRefs.push(addressPointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Fetch templates and exercises
|
||||
const templates = await fetchReferencedEvents(ndk, templateRefs);
|
||||
const exercises = await fetchReferencedEvents(ndk, exerciseRefs);
|
||||
|
||||
// 7. Return the complete pack data
|
||||
return {
|
||||
pack: packEvent,
|
||||
templates,
|
||||
exercises
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to fetch events from address pointers
|
||||
async function fetchReferencedEvents(ndk: NDK, addressPointers: string[]) {
|
||||
const events: NDKEvent[] = [];
|
||||
|
||||
for (const pointer of addressPointers) {
|
||||
// Parse the pointer (kind:pubkey:d-tag)
|
||||
const [kindStr, hexPubkey, dTag] = pointer.split(':');
|
||||
const kind = parseInt(kindStr);
|
||||
|
||||
// Create a filter to find this specific event
|
||||
const filter: NDKFilter = {
|
||||
kinds: [kind],
|
||||
authors: [hexPubkey]
|
||||
};
|
||||
|
||||
if (dTag) {
|
||||
filter['#d'] = [dTag];
|
||||
}
|
||||
|
||||
// Fetch the events
|
||||
const fetchedEvents = await ndk.fetchEvents(filter);
|
||||
events.push(...Array.from(fetchedEvents));
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
```
|
||||
|
||||
## Creating naddr for Sharing Packs
|
||||
|
||||
If you want to generate an naddr that can be shared to allow others to import your POWR Pack:
|
||||
|
||||
```typescript
|
||||
function createShareableNaddr(packEvent: NDKEvent) {
|
||||
// Extract the d-tag (identifier)
|
||||
const dTags = packEvent.getMatchingTags('d');
|
||||
const identifier = dTags[0]?.[1] || '';
|
||||
|
||||
// Create the naddr
|
||||
const naddr = nip19.naddrEncode({
|
||||
pubkey: packEvent.pubkey,
|
||||
kind: packEvent.kind,
|
||||
identifier
|
||||
});
|
||||
|
||||
return naddr;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices for Working with NIP-19 Formats
|
||||
|
||||
1. **Always validate decoded values**: Check that the decoded data is of the expected type and has the necessary properties.
|
||||
|
||||
2. **Handle encoding/decoding errors**: These functions can throw exceptions if the input is malformed.
|
||||
|
||||
3. **Normalize hex keys**: Convert to lowercase for consistency in filters and comparisons.
|
||||
|
||||
4. **Check event kinds**: Verify that the decoded event kind matches what you expect (30004 for NIP-51 lists).
|
||||
|
||||
5. **Use strong typing**: TypeScript's type system can help catch errors with NIP-19 data.
|
||||
|
||||
The main challenge when working with naddr and other NIP-19 formats is correctly translating between the human-readable encoded forms and the internal hex representations needed for Nostr protocol operations. NDK's nip19 module abstracts this complexity for you, allowing you to focus on the core business logic of your application.
|
1397
docs/design/Analysis/NDKSubscriptionAnalysis.md
Normal file
1397
docs/design/Analysis/NDKSubscriptionAnalysis.md
Normal file
File diff suppressed because it is too large
Load Diff
261
docs/design/Analysis/NDKandNip19.md
Normal file
261
docs/design/Analysis/NDKandNip19.md
Normal file
@ -0,0 +1,261 @@
|
||||
# NDK Functions for Hex Keys and NIP-19 Encoding/Decoding
|
||||
|
||||
When working with Nostr addresses (like naddr) and converting between hex and other formats, NDK provides several key functions. Here's a comprehensive overview of the main functions you'll need for handling hex keys and NIP-19 encoding/decoding in your POWR Pack implementation:
|
||||
|
||||
## Core NIP-19 Functions
|
||||
|
||||
NDK implements NIP-19 functionality in the `events/nip19.ts` file. The key functions you'll need are:
|
||||
|
||||
### 1. Decoding NIP-19 Entities
|
||||
|
||||
```typescript
|
||||
import { nip19 } from '@nostr-dev-kit/ndk';
|
||||
|
||||
// Decode any NIP-19 entity (naddr, npub, nsec, note, etc.)
|
||||
function decodeNaddr(naddrString: string) {
|
||||
try {
|
||||
const decoded = nip19.decode(naddrString);
|
||||
|
||||
// For naddr specifically, you'll get:
|
||||
if (decoded.type === 'naddr') {
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// pubkey is the hex public key of the author
|
||||
// kind is the event kind (30004 for lists)
|
||||
// identifier is the 'd' tag value
|
||||
|
||||
console.log('Hex pubkey:', pubkey);
|
||||
console.log('Event kind:', kind);
|
||||
console.log('Identifier:', identifier);
|
||||
|
||||
return decoded.data;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Invalid NIP-19 format:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Encoding to NIP-19 Formats
|
||||
|
||||
```typescript
|
||||
// Create an naddr from components
|
||||
function createNaddr(pubkey: string, kind: number, identifier: string) {
|
||||
return nip19.naddrEncode({
|
||||
pubkey, // Hex pubkey
|
||||
kind, // Event kind (number)
|
||||
identifier // The 'd' tag value
|
||||
});
|
||||
}
|
||||
|
||||
// Create an npub from a hex public key
|
||||
function hexToNpub(hexPubkey: string) {
|
||||
return nip19.npubEncode(hexPubkey);
|
||||
}
|
||||
|
||||
// Create a note (event reference) from event ID
|
||||
function eventIdToNote(eventId: string) {
|
||||
return nip19.noteEncode(eventId);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Utility Functions for Hex Keys
|
||||
|
||||
```typescript
|
||||
// Convert npub to hex pubkey
|
||||
function npubToHex(npub: string) {
|
||||
try {
|
||||
const decoded = nip19.decode(npub);
|
||||
if (decoded.type === 'npub') {
|
||||
return decoded.data as string; // This is the hex pubkey
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Invalid npub format:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a string is a valid hex key (pubkey or event id)
|
||||
function isValidHexKey(hexString: string) {
|
||||
return /^[0-9a-f]{64}$/i.test(hexString);
|
||||
}
|
||||
```
|
||||
|
||||
## Using NIP-19 Functions with NDK Filters
|
||||
|
||||
Here's how you would use these functions with NDK filters to fetch a POWR Pack from an naddr:
|
||||
|
||||
```typescript
|
||||
async function fetchPackFromNaddr(naddr: string) {
|
||||
try {
|
||||
// Decode the naddr to get event coordinates
|
||||
const decoded = nip19.decode(naddr);
|
||||
|
||||
if (decoded.type !== 'naddr') {
|
||||
throw new Error('Not an naddr');
|
||||
}
|
||||
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// Ensure it's a list (kind 30004)
|
||||
if (kind !== 30004) {
|
||||
throw new Error('Not a NIP-51 list');
|
||||
}
|
||||
|
||||
// Create a filter to fetch the specific list event
|
||||
const filter = {
|
||||
kinds: [kind],
|
||||
authors: [pubkey], // Using the hex pubkey from the naddr
|
||||
'#d': identifier ? [identifier] : undefined, // Using the d-tag if available
|
||||
};
|
||||
|
||||
// Fetch the event
|
||||
const events = await ndk.fetchEvents(filter);
|
||||
|
||||
if (events.size === 0) {
|
||||
throw new Error('Pack not found');
|
||||
}
|
||||
|
||||
// Get the first matching event
|
||||
return Array.from(events)[0];
|
||||
} catch (error) {
|
||||
console.error('Error fetching pack:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing the Complete naddr Workflow for POWR Packs
|
||||
|
||||
Here's a complete example for fetching and processing a POWR Pack from an naddr:
|
||||
|
||||
```typescript
|
||||
import NDK, { NDKEvent, NDKFilter, nip19 } from '@nostr-dev-kit/ndk';
|
||||
|
||||
async function fetchAndProcessPOWRPack(naddr: string) {
|
||||
// 1. Initialize NDK
|
||||
const ndk = new NDK({
|
||||
explicitRelayUrls: [
|
||||
'wss://relay.damus.io',
|
||||
'wss://relay.nostr.band'
|
||||
]
|
||||
});
|
||||
await ndk.connect();
|
||||
|
||||
// 2. Decode the naddr
|
||||
const decoded = nip19.decode(naddr);
|
||||
if (decoded.type !== 'naddr') {
|
||||
throw new Error('Invalid naddr format');
|
||||
}
|
||||
|
||||
const { pubkey, kind, identifier } = decoded.data;
|
||||
|
||||
// 3. Create filter to fetch the pack event
|
||||
const packFilter: NDKFilter = {
|
||||
kinds: [kind],
|
||||
authors: [pubkey],
|
||||
'#d': identifier ? [identifier] : undefined
|
||||
};
|
||||
|
||||
// 4. Fetch the pack event
|
||||
const packEvents = await ndk.fetchEvents(packFilter);
|
||||
if (packEvents.size === 0) {
|
||||
throw new Error('Pack not found');
|
||||
}
|
||||
|
||||
const packEvent = Array.from(packEvents)[0];
|
||||
|
||||
// 5. Extract template and exercise references
|
||||
const templateRefs: string[] = [];
|
||||
const exerciseRefs: string[] = [];
|
||||
|
||||
for (const tag of packEvent.tags) {
|
||||
if (tag[0] === 'a') {
|
||||
const addressPointer = tag[1];
|
||||
// Format is kind:pubkey:d-tag
|
||||
if (addressPointer.startsWith('33402:')) { // Workout template
|
||||
templateRefs.push(addressPointer);
|
||||
} else if (addressPointer.startsWith('33401:')) { // Exercise
|
||||
exerciseRefs.push(addressPointer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Fetch templates and exercises
|
||||
const templates = await fetchReferencedEvents(ndk, templateRefs);
|
||||
const exercises = await fetchReferencedEvents(ndk, exerciseRefs);
|
||||
|
||||
// 7. Return the complete pack data
|
||||
return {
|
||||
pack: packEvent,
|
||||
templates,
|
||||
exercises
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to fetch events from address pointers
|
||||
async function fetchReferencedEvents(ndk: NDK, addressPointers: string[]) {
|
||||
const events: NDKEvent[] = [];
|
||||
|
||||
for (const pointer of addressPointers) {
|
||||
// Parse the pointer (kind:pubkey:d-tag)
|
||||
const [kindStr, hexPubkey, dTag] = pointer.split(':');
|
||||
const kind = parseInt(kindStr);
|
||||
|
||||
// Create a filter to find this specific event
|
||||
const filter: NDKFilter = {
|
||||
kinds: [kind],
|
||||
authors: [hexPubkey]
|
||||
};
|
||||
|
||||
if (dTag) {
|
||||
filter['#d'] = [dTag];
|
||||
}
|
||||
|
||||
// Fetch the events
|
||||
const fetchedEvents = await ndk.fetchEvents(filter);
|
||||
events.push(...Array.from(fetchedEvents));
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
||||
```
|
||||
|
||||
## Creating naddr for Sharing Packs
|
||||
|
||||
If you want to generate an naddr that can be shared to allow others to import your POWR Pack:
|
||||
|
||||
```typescript
|
||||
function createShareableNaddr(packEvent: NDKEvent) {
|
||||
// Extract the d-tag (identifier)
|
||||
const dTags = packEvent.getMatchingTags('d');
|
||||
const identifier = dTags[0]?.[1] || '';
|
||||
|
||||
// Create the naddr
|
||||
const naddr = nip19.naddrEncode({
|
||||
pubkey: packEvent.pubkey,
|
||||
kind: packEvent.kind,
|
||||
identifier
|
||||
});
|
||||
|
||||
return naddr;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices for Working with NIP-19 Formats
|
||||
|
||||
1. **Always validate decoded values**: Check that the decoded data is of the expected type and has the necessary properties.
|
||||
|
||||
2. **Handle encoding/decoding errors**: These functions can throw exceptions if the input is malformed.
|
||||
|
||||
3. **Normalize hex keys**: Convert to lowercase for consistency in filters and comparisons.
|
||||
|
||||
4. **Check event kinds**: Verify that the decoded event kind matches what you expect (30004 for NIP-51 lists).
|
||||
|
||||
5. **Use strong typing**: TypeScript's type system can help catch errors with NIP-19 data.
|
||||
|
||||
The main challenge when working with naddr and other NIP-19 formats is correctly translating between the human-readable encoded forms and the internal hex representations needed for Nostr protocol operations. NDK's nip19 module abstracts this complexity for you, allowing you to focus on the core business logic of your application.
|
193
docs/design/Analysis/OlasSocialFeed.md
Normal file
193
docs/design/Analysis/OlasSocialFeed.md
Normal file
@ -0,0 +1,193 @@
|
||||
After analyzing Olas's social feed implementation, I can provide you with valuable insights for building your workout-focused social feed using Nostr. Here's a breakdown of their implementation and how you can adapt it for POWR:
|
||||
|
||||
### Key Components of Olas's Feed Implementation
|
||||
|
||||
1. **Feed Architecture**
|
||||
- They use a main `Feed` component with two key parts:
|
||||
- `useFeedEvents` hook for managing feed data
|
||||
- `useFeedMonitor` hook for monitoring and updating feed entries
|
||||
|
||||
2. **NDK Integration**
|
||||
```typescript
|
||||
import {
|
||||
NDKEvent,
|
||||
NDKFilter,
|
||||
NDKKind,
|
||||
NDKSubscription,
|
||||
useNDK
|
||||
} from "@nostr-dev-kit/ndk-mobile";
|
||||
```
|
||||
|
||||
3. **Event Filtering**
|
||||
- They filter events based on specific kinds:
|
||||
```typescript
|
||||
switch (event.kind) {
|
||||
case NDKKind.VerticalVideo:
|
||||
case NDKKind.HorizontalVideo:
|
||||
case 30018:
|
||||
case 30402:
|
||||
case NDKKind.Text:
|
||||
case NDKKind.Media:
|
||||
case NDKKind.Image:
|
||||
return handleContentEvent(eventId, event);
|
||||
```
|
||||
|
||||
### Implementing POWR's Workout Feed
|
||||
|
||||
For your workout app, here's how you can adapt their implementation:
|
||||
|
||||
1. **Event Kind Definition**
|
||||
```typescript
|
||||
// Define workout event kind
|
||||
const WORKOUT_EVENT_KIND = 30311; // Choose an appropriate kind number for workouts
|
||||
```
|
||||
|
||||
2. **Feed Filter Setup**
|
||||
```typescript
|
||||
const workoutFeedFilters: NDKFilter[] = [{
|
||||
kinds: [WORKOUT_EVENT_KIND],
|
||||
// Add any additional filters like tags
|
||||
}];
|
||||
```
|
||||
|
||||
3. **Feed Component**
|
||||
```typescript
|
||||
import { NDKEvent, NDKFilter, useNDK } from "@nostr-dev-kit/ndk-mobile";
|
||||
|
||||
export function WorkoutFeed() {
|
||||
const { entries, newEntries, updateEntries } = useFeedEvents(
|
||||
workoutFeedFilters,
|
||||
{
|
||||
subId: 'workout-feed',
|
||||
filterFn: (entry) => {
|
||||
// Add custom filtering for workout events
|
||||
return true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<FlashList
|
||||
data={entries}
|
||||
renderItem={({ item }) => (
|
||||
<WorkoutPost
|
||||
event={item.event}
|
||||
timestamp={item.timestamp}
|
||||
/>
|
||||
)}
|
||||
estimatedItemSize={400}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
4. **Useful NDK Tools to Leverage**
|
||||
- **Subscription Management**
|
||||
```typescript
|
||||
const subscription = ndk.subscribe(
|
||||
workoutFeedFilters,
|
||||
{
|
||||
groupable: false,
|
||||
skipVerification: true,
|
||||
subId: 'workout-feed'
|
||||
}
|
||||
);
|
||||
|
||||
subscription.on("event", handleWorkoutEvent);
|
||||
subscription.once('eose', handleEose);
|
||||
```
|
||||
|
||||
- **Event Processing**
|
||||
```typescript
|
||||
const handleWorkoutEvent = (event: NDKEvent) => {
|
||||
// Process workout specific data
|
||||
const workout = {
|
||||
id: event.id,
|
||||
type: event.tagValue('workout-type'),
|
||||
duration: event.tagValue('duration'),
|
||||
// other workout specific fields
|
||||
};
|
||||
|
||||
// Update feed
|
||||
updateEntry(event.id, (entry) => ({
|
||||
...entry,
|
||||
event,
|
||||
workout,
|
||||
timestamp: event.created_at
|
||||
}));
|
||||
};
|
||||
```
|
||||
|
||||
5. **Feed Entry Type**
|
||||
```typescript
|
||||
type WorkoutFeedEntry = {
|
||||
id: string;
|
||||
event?: NDKEvent;
|
||||
workout?: {
|
||||
type: string;
|
||||
duration: string;
|
||||
// other workout metadata
|
||||
};
|
||||
timestamp: number;
|
||||
};
|
||||
```
|
||||
|
||||
### Key NDK Tools to Use
|
||||
|
||||
1. **Event Subscription**
|
||||
- `NDKSubscription` for real-time workout updates
|
||||
- `NDKFilter` for filtering workout-specific events
|
||||
|
||||
2. **Event Processing**
|
||||
- `NDKEvent` for handling workout event data
|
||||
- Event tags for workout metadata
|
||||
|
||||
3. **Feed Management**
|
||||
- `useFeedEvents` hook pattern for managing workout feed state
|
||||
- Entry caching and update mechanisms
|
||||
|
||||
### Best Practices from Olas's Implementation
|
||||
|
||||
1. **Performance Optimization**
|
||||
- Use of `FlashList` for efficient list rendering
|
||||
- Implement entry caching
|
||||
- Handle new entries efficiently
|
||||
|
||||
2. **State Management**
|
||||
- Track active entries
|
||||
- Manage subscription lifecycle
|
||||
- Handle feed updates appropriately
|
||||
|
||||
3. **User Experience**
|
||||
- Implement pull-to-refresh
|
||||
- Show new entries notification
|
||||
- Handle scrolling and viewing positions
|
||||
|
||||
### Additional Recommendations for POWR
|
||||
|
||||
1. **Workout-Specific Filters**
|
||||
- Add filters for workout types
|
||||
- Filter by duration, intensity, etc.
|
||||
- Use workout-specific tags
|
||||
|
||||
2. **Data Structure**
|
||||
```typescript
|
||||
// Workout event structure
|
||||
const workoutEvent = {
|
||||
kind: WORKOUT_EVENT_KIND,
|
||||
tags: [
|
||||
['workout-type', 'strength'],
|
||||
['duration', '45'],
|
||||
['exercises', JSON.stringify(exercises)],
|
||||
// Additional metadata
|
||||
],
|
||||
content: workoutDescription
|
||||
};
|
||||
```
|
||||
|
||||
3. **Real-time Updates**
|
||||
- Implement real-time workout progress updates
|
||||
- Show active workouts in progress
|
||||
- Enable social interactions during workouts
|
||||
|
||||
This implementation will give you a solid foundation for building a workout-focused social feed using Nostr. The key is adapting Olas's efficient feed management system while customizing it for workout-specific content and interactions.
|
282
docs/design/Social/UpdatedPlan.md
Normal file
282
docs/design/Social/UpdatedPlan.md
Normal file
@ -0,0 +1,282 @@
|
||||
### 1. Implement a Centralized Feed Management System
|
||||
|
||||
Based on both Olas and NDK's approaches:
|
||||
|
||||
- **Create a `FeedEntry` type** with clear support for different event kinds and content types
|
||||
- **Implement state management using references** following NDK's pattern of using `Map` and `Set` references for better performance
|
||||
- **Add proper event deduplication** using NDK's seen events tracking mechanism
|
||||
|
||||
### 2. Improve Event Subscription and Filtering
|
||||
|
||||
From NDK's implementation:
|
||||
|
||||
- **Utilize NDK's subscription options more effectively**:
|
||||
```typescript
|
||||
const subscription = ndk.subscribe(filters, {
|
||||
closeOnEose: true,
|
||||
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
|
||||
groupable: true,
|
||||
skipVerification: false
|
||||
});
|
||||
```
|
||||
|
||||
- **Implement caching strategies** using NDK's caching mechanisms:
|
||||
```typescript
|
||||
// Try cache first, then relays
|
||||
const cacheStrategy = NDKSubscriptionCacheUsage.CACHE_FIRST;
|
||||
```
|
||||
|
||||
- **Use subscription grouping** to reduce relay connections for similar queries:
|
||||
```typescript
|
||||
// Group similar subscriptions with a delay
|
||||
const groupableDelay = 100; // ms
|
||||
```
|
||||
|
||||
### 3. Enhance Contact List Fetching
|
||||
|
||||
NDK offers more sophisticated contact list handling:
|
||||
|
||||
- **Use NDK's contact list event fetching** with explicit validation:
|
||||
```typescript
|
||||
// Direct method to get follows for a user
|
||||
async followSet(opts?: NDKSubscriptionOptions): Promise<Set<Hexpubkey>> {
|
||||
const follows = await this.follows(opts);
|
||||
return new Set(Array.from(follows).map((f) => f.pubkey));
|
||||
}
|
||||
```
|
||||
|
||||
- **Implement loading states and retry logic** using NDK's pattern:
|
||||
```typescript
|
||||
// Track loading state with cleanup on unmount
|
||||
let isMounted = true;
|
||||
// ...
|
||||
return () => { isMounted = false; }
|
||||
```
|
||||
|
||||
### 4. Enhanced Subscription Management
|
||||
|
||||
From NDK's subscription implementation:
|
||||
|
||||
- **Proper lifecycle management** for subscriptions:
|
||||
```typescript
|
||||
// Keep track of subscriptions for cleanup
|
||||
const subscriptionRef = useRef<NDKSubscription | null>(null);
|
||||
|
||||
// Clean up subscription on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (subscriptionRef.current) {
|
||||
subscriptionRef.current.stop();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
```
|
||||
|
||||
- **Handle relay connection state** more effectively:
|
||||
```typescript
|
||||
// Monitor relay connections
|
||||
ndk.pool.on('relay:connect', (relay: NDKRelay) => {
|
||||
console.log(`Relay connected: ${relay.url}`);
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Optimize Event Processing Pipeline
|
||||
|
||||
Based on NDK's efficient event handling:
|
||||
|
||||
- **Implement event processing with proper validation**:
|
||||
```typescript
|
||||
// Only process events that pass validation
|
||||
if (!this.skipValidation) {
|
||||
if (!ndkEvent.isValid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Use NDK's event queuing and batch processing**:
|
||||
```typescript
|
||||
// Batch event processing for better performance
|
||||
const updateEntries = (reason: string) => {
|
||||
const newSlice = entriesFromIds(newEntriesRef.current);
|
||||
// ... process in batch rather than individually
|
||||
}
|
||||
```
|
||||
|
||||
- **Implement EOSE (End of Stored Events) handling** more effectively:
|
||||
```typescript
|
||||
// Handle EOSE with improved timing
|
||||
subscription.on('eose', () => {
|
||||
if (isMounted) {
|
||||
setLoading(false);
|
||||
setEose(true);
|
||||
// Process any accumulated events after EOSE
|
||||
updateEntries('eose');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 6. Implement Progressive Loading
|
||||
|
||||
From NDK's subscription approach:
|
||||
|
||||
- **Use cache-first loading with fallback to relays**:
|
||||
```typescript
|
||||
// Try cache first for immediate feedback
|
||||
if (ndk.cacheAdapter) {
|
||||
const cachedEvents = await ndk.cacheAdapter.query(subscription);
|
||||
if (cachedEvents.length > 0) {
|
||||
// Process cached events immediately
|
||||
processCachedEvents(cachedEvents);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Implement pagination with proper thresholds**:
|
||||
```typescript
|
||||
// Only fetch more when needed
|
||||
if (hasMore && !loading && !refreshing) {
|
||||
loadMore(oldestTimestamp - 1);
|
||||
}
|
||||
```
|
||||
|
||||
## Key Architectural Improvements
|
||||
|
||||
1. **More Robust Feed State Management**:
|
||||
- Use `useRef` for maintaining state references that don't trigger re-renders
|
||||
- Implement batch updates for feed state changes
|
||||
- Use proper cleanup and event deduplication
|
||||
|
||||
2. **Enhanced Subscription Management**:
|
||||
- Use `NDKSubscription` with appropriate options
|
||||
- Implement proper grouping and caching strategies
|
||||
- Handle subscription lifecycle with proper cleanup
|
||||
|
||||
3. **Contact List Reliability**:
|
||||
- Use multiple approaches to fetch and validate contacts
|
||||
- Implement caching and retry logic for reliable contact detection
|
||||
- Handle edge cases (like self-follows) appropriately
|
||||
|
||||
4. **Event Processing Optimization**:
|
||||
- Implement more efficient event filtering and processing
|
||||
- Use batch processing for events after EOSE
|
||||
- Add proper validation and verification
|
||||
|
||||
5. **Performance Enhancements**:
|
||||
- Use NDK's optimization patterns like subscription grouping
|
||||
- Implement proper memory management for large event sets
|
||||
- Use efficient data structures (Map/Set) for event tracking
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
1. **Start with rewriting the subscription layer** using NDK's patterns
|
||||
2. **Then enhance the contact list fetching** with multiple reliable approaches
|
||||
3. **Next, implement improved feed state management** with efficient data structures
|
||||
4. **Finally, optimize the UI components** for better rendering performance
|
||||
|
||||
This updated plan combines the best practices from both Olas's specialized feed implementation and NDK's efficient subscription management, providing a solid foundation for building a fast, reliable, and focused social feed for workout-related content in the POWR app.
|
||||
|
||||
Let me review your current subscription and feed code, and identify which files we'll need to delete, update, or create to implement the improved architecture.
|
||||
|
||||
## Current Code Structure
|
||||
|
||||
From what I can see in the shared code, you have:
|
||||
|
||||
1. **useFeedEvents.ts** - Core hook for handling feed events
|
||||
2. **useFeedHooks.ts** - Specialized feed hooks (Following, POWR, Global)
|
||||
3. **useFeedMonitor.ts** - For monitoring feed state and auto-refresh
|
||||
4. **powr.tsx** - The POWR tab screen component
|
||||
5. **following.tsx** - The Following tab screen component
|
||||
6. **global.tsx** - The Global tab screen component
|
||||
7. **EnhancedSocialPost.tsx** - Component for rendering feed posts
|
||||
8. **socialFeedService.ts** - Service for interacting with Nostr
|
||||
|
||||
## Plan for Implementation
|
||||
|
||||
### Files to Update (Modify):
|
||||
|
||||
1. **useFeedEvents.ts** - Update with NDK's subscription patterns and better event processing
|
||||
- Improve subscription lifecycle management
|
||||
- Implement better event deduplication
|
||||
- Add batch processing and performance optimizations
|
||||
|
||||
2. **useFeedHooks.ts** - Enhance contact list fetching and filter creation
|
||||
- Implement multiple approaches for contact list fetching
|
||||
- Improve filter creation for better relevance
|
||||
- Remove hardcoded fallbacks in favor of more reliable mechanisms
|
||||
|
||||
3. **useFeedMonitor.ts** - Minor updates for integration with new feed structure
|
||||
- Enhance refresh mechanisms
|
||||
- Improve state tracking
|
||||
|
||||
4. **following.tsx** - Update to use the new feed architecture
|
||||
- Remove debug code once implementation is stable
|
||||
- Improve UI for feed state feedback
|
||||
|
||||
5. **powr.tsx** - Update to fix infinite loop issues
|
||||
- Improve component lifecycle management
|
||||
- Enhance performance
|
||||
|
||||
6. **global.tsx** - Update for consistency with other feed implementations
|
||||
- Ensure consistent behavior across all feed tabs
|
||||
|
||||
7. **socialFeedService.ts** - Enhance with better relay interaction
|
||||
- Improve subscription creation
|
||||
- Add better event filtering
|
||||
|
||||
### Files to Create:
|
||||
|
||||
1. **useFeedState.ts** - New hook for centralized feed state management
|
||||
```typescript
|
||||
// Manage feed entries state with efficient updates
|
||||
export function useFeedState() {
|
||||
// Implementation here
|
||||
}
|
||||
```
|
||||
|
||||
2. **useFeedSubscription.ts** - New hook for subscription management
|
||||
```typescript
|
||||
// Handle NDK subscriptions with proper lifecycle
|
||||
export function useFeedSubscription(filters: NDKFilter[]) {
|
||||
// Implementation here
|
||||
}
|
||||
```
|
||||
|
||||
3. **types/feed.ts** - Enhanced type definitions for feed entries
|
||||
```typescript
|
||||
// More comprehensive feed entry types
|
||||
export interface FeedEntry {
|
||||
// Enhanced type definition
|
||||
}
|
||||
```
|
||||
|
||||
4. **utils/feedUtils.ts** - Utility functions for feed operations
|
||||
```typescript
|
||||
// Feed-related utility functions
|
||||
export function deduplicateEvents() {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
5. **components/feed/FeedList.tsx** - Shared component for feed rendering
|
||||
```typescript
|
||||
// Reusable feed list component with virtualization
|
||||
export function FeedList({ entries, onItemPress }) {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
### Files to Delete:
|
||||
|
||||
None of the files need to be deleted entirely. Instead, we'll refactor and enhance the existing codebase to implement the new architecture.
|
||||
|
||||
## Implementation Order
|
||||
|
||||
1. First, create the new type definitions in **types/feed.ts**
|
||||
2. Then, implement the new hooks in **useFeedSubscription.ts** and **useFeedState.ts**
|
||||
3. Update **useFeedEvents.ts** and **useFeedHooks.ts** with improved implementations
|
||||
4. Create utility functions in **utils/feedUtils.ts**
|
||||
5. Implement the shared component in **components/feed/FeedList.tsx**
|
||||
6. Finally, update the screen components to use the new architecture
|
||||
|
||||
This approach allows us to gradually refactor the codebase while maintaining functionality throughout the process. Each step builds on the previous one, ultimately resulting in a more robust and efficient feed implementation.
|
Loading…
x
Reference in New Issue
Block a user