commit
ec4a042615
1 changed files with 767 additions and 0 deletions
@ -0,0 +1,767 @@
@@ -0,0 +1,767 @@
|
||||
# Aitherboard |
||||
|
||||
A dockerized 4chan-style imageboard built on Nostr protocol. Aitherboard is a single-page application (SPA) that provides a decentralized discussion board experience with real-time updates, multiple authentication methods, and full Nostr protocol integration. |
||||
|
||||
## Table of Contents |
||||
|
||||
- [Overview](#overview) |
||||
- [Technology Stack](#technology-stack) |
||||
- [Architecture](#architecture) |
||||
- [Features](#features) |
||||
- [Authentication](#authentication) |
||||
- [Content Types](#content-types) |
||||
- [Configuration](#configuration) |
||||
- [Docker Deployment](#docker-deployment) |
||||
- [Health Check](#health-check) |
||||
- [Development](#development) |
||||
- [Implementation Phases](#implementation-phases) |
||||
|
||||
## Overview |
||||
|
||||
Aitherboard is a decentralized imageboard that uses the Nostr protocol for content distribution. It supports: |
||||
|
||||
- **Kind 11 threads** (NIP-7D) - Discussion threads with topics |
||||
- **Kind 1111 comments** (NIP-22) - Threaded comments |
||||
- **Kind 9735 zap receipts** (NIP-57) - Lightning payments displayed as comments |
||||
- **Kind 1 feed** - Twitter-like feed with replies and zaps |
||||
- **Kind 7 reactions** - Upvote/downvote system |
||||
- **Real-time updates** - Live feed of new content |
||||
- **Multiple auth methods** - NIP-07, nsec, NIP-46 bunker, anonymous |
||||
- **Full markdown rendering** - All content supports markdown |
||||
- **NIP-21 link parsing** - Clickable profile badges and event cards |
||||
|
||||
## Technology Stack |
||||
|
||||
- **Frontend**: Svelte 5 (with runes: `$state`, `$derived`, `$effect`) + TypeScript + Vite |
||||
- **Styling**: Tailwind CSS (4chan-style minimal design) |
||||
- **Nostr Library**: [`applesauce-core`](https://github.com/hzrd149/applesauce) |
||||
- **Markdown**: `marked` + `DOMPurify` for sanitization |
||||
- **Storage**: IndexedDB (via `idb` or `localforage`) |
||||
- **Deployment**: Docker + Apache httpd (static serving on port 9876) |
||||
- **PWA**: Progressive Web App support |
||||
|
||||
## Architecture |
||||
|
||||
### Architecture Layers |
||||
|
||||
``` |
||||
┌─────────────────────────────────────────────────────────┐ |
||||
│ UI Components Layer │ |
||||
│ (LandingPage, ThreadList, ThreadView, Profile, Feed) │ |
||||
└───────────────────────┬─────────────────────────────────┘ |
||||
│ |
||||
┌───────────────────────▼─────────────────────────────────┐ |
||||
│ Feature Modules Layer │ |
||||
│ (Threads, Comments, Zaps, Reactions, Profiles, Feed) │ |
||||
└───────────────────────┬─────────────────────────────────┘ |
||||
│ |
||||
┌───────────────────────▼─────────────────────────────────┐ |
||||
│ Core Services Layer │ |
||||
│ (Nostr Client, Relay Pool, Auth, Cache, Security) │ |
||||
└───────────────────────┬─────────────────────────────────┘ |
||||
│ |
||||
┌───────────────────────▼─────────────────────────────────┐ |
||||
│ Applesauce Library │ |
||||
│ (Nostr Protocol Implementation) │ |
||||
└─────────────────────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
### Core Services Layer |
||||
|
||||
#### 1. Nostr Client Service (`src/lib/services/nostr/`) |
||||
|
||||
- `applesauce-client.ts` - Applesauce client initialization |
||||
- `relay-pool.ts` - Relay connection management |
||||
- `event-store.ts` - In-memory event cache with deduplication |
||||
- `subscription-manager.ts` - Active subscription tracking |
||||
- `auth-handler.ts` - NIP-42 AUTH challenge handling |
||||
- `config.ts` - Centralized configuration with env var validation |
||||
|
||||
#### 2. Authentication Service (`src/lib/services/auth/`) |
||||
|
||||
- `nip07-signer.ts` - NIP-07 browser extension signer |
||||
- `nsec-signer.ts` - Direct nsec/hex key signer (with NIP-49 encryption) |
||||
- `bunker-signer.ts` - NIP-46 remote signer (bunker) |
||||
- `anonymous-signer.ts` - Anonymous session key generation |
||||
- `session-manager.ts` - Session persistence and key management |
||||
- `profile-fetcher.ts` - Kind 0 metadata fetching |
||||
|
||||
#### 3. Cache Service (`src/lib/services/cache/`) |
||||
|
||||
- `indexeddb-store.ts` - IndexedDB wrapper |
||||
- `event-cache.ts` - Event caching and retrieval |
||||
- `profile-cache.ts` - Profile metadata caching |
||||
- `search-index.ts` - Full-text search index |
||||
|
||||
#### 4. Security Utilities (`src/lib/services/security/`) |
||||
|
||||
- `key-management.ts` - Key encryption/decryption (NIP-49) |
||||
- `bech32-utils.ts` - NIP-19 bech32 encoding/decoding |
||||
- `event-validator.ts` - Event signature and structure validation |
||||
- `sanitizer.ts` - HTML/content sanitization |
||||
|
||||
### Feature Modules Layer |
||||
|
||||
#### 5. Thread Module (`src/lib/modules/threads/`) |
||||
|
||||
**Components**: |
||||
- `ThreadList.svelte` - Main thread listing |
||||
- `ThreadCard.svelte` - Thread preview card (landing page) |
||||
- `ThreadView.svelte` - Full thread display |
||||
- `CreateThreadForm.svelte` - Thread creation |
||||
|
||||
**Features**: |
||||
- Topic organization (max 3 `t` tags per thread) |
||||
- "General" category for threads without topics |
||||
- 30-day timeout (configurable) |
||||
- Thread statistics (comments, zaps, activity) |
||||
- Sorting: newest, most active, most upvoted |
||||
- Plaintext preview extraction for landing page (no markdown/images) |
||||
|
||||
**Landing Page Thread Display**: |
||||
- Title |
||||
- Relative time of thread creation |
||||
- Relative time of latest response |
||||
- Profile badge |
||||
- Vote stats |
||||
- First 250 chars of plaintext (no markdown/images) |
||||
|
||||
#### 6. Comment Module (`src/lib/modules/comments/`) |
||||
|
||||
**Components**: |
||||
- `Comment.svelte` - Individual comment |
||||
- `CommentThread.svelte` - Threaded comment list |
||||
- `CommentForm.svelte` - Reply form |
||||
|
||||
**Threading Style**: |
||||
- Flat list (no indentation) |
||||
- Gray blurb showing parent preview |
||||
- Click blurb to highlight/scroll to parent |
||||
- Parse NIP-22 tags: `K`, `E`, `e`, `k`, `p`, `P` |
||||
|
||||
#### 7. Zap Module (`src/lib/modules/zaps/`) |
||||
|
||||
**Components**: |
||||
- `ZapButton.svelte` - Zap action button |
||||
- `ZapReceipt.svelte` - Zap receipt display |
||||
|
||||
**Features**: |
||||
- Create kind 9734 zap requests |
||||
- Send to recipient's lnurl callback (wallet) |
||||
- Display kind 9735 zap receipts (≥ configured threshold) |
||||
- Zap receipts displayed with ⚡ emoji |
||||
- Configurable minimum sat threshold (default: 1, must be 0 or positive) |
||||
|
||||
**Important**: Zap requests (9734) go to wallets, NOT relays. Only receipts (9735) are published to relays. |
||||
|
||||
#### 8. Reaction Module (`src/lib/modules/reactions/`) |
||||
|
||||
**Components**: |
||||
- `ReactionButtons.svelte` - Upvote/downvote buttons |
||||
|
||||
**Rules**: |
||||
- Only `+` and `-` content allowed |
||||
- One vote per user per event |
||||
- Clicking same button twice deletes the reaction |
||||
|
||||
#### 9. Profile Module (`src/lib/modules/profiles/`) |
||||
|
||||
**Components**: |
||||
- `ProfilePage.svelte` - User profile display |
||||
- `ProfileBadge.svelte` - Clickable profile badge |
||||
- `RecentlyActive.svelte` - Recently active users list |
||||
|
||||
**Features**: |
||||
- Display kind 0 metadata (name, about, picture, NIP-05) |
||||
- Show kind 1 feed from default relays + `wss://relay.damus.io` + `wss://aggr.nostr.land` |
||||
- Allow zapping profile owner |
||||
- Allow replying to kind 1 notes with kind 1 replies (NIP-10) or zaps |
||||
- Recently active users tracking (15min/1hr/24hr windows) |
||||
|
||||
#### 10. Kind 1 Feed Module (`src/lib/modules/feed/`) |
||||
|
||||
**Components**: |
||||
- `Kind1FeedPage.svelte` - Main feed page |
||||
- `Kind1Post.svelte` - Individual kind 1 post |
||||
- `Kind1Reply.svelte` - Kind 1 reply display |
||||
- `ZapReceiptReply.svelte` - Zap receipt as reply (with ⚡) |
||||
- `CreateKind1Form.svelte` - Create new kind 1 events |
||||
- `ReplyToKind1Form.svelte` - Reply to kind 1 events |
||||
|
||||
**Features**: |
||||
- **"View feed" button** on landing page opens feed page |
||||
- Fetch kind 1 events from default relays |
||||
- **Create new kind 1 events** with markdown editor |
||||
- **Reply to kind 1 with kind 1** (NIP-10 threading) |
||||
- **Reply to kind 1 with zap** (display receipt as reply with ⚡ emoji) |
||||
- **Reply to kind 1 replies** with their own kind 1 |
||||
- **Reply to zap receipts** with: |
||||
- Zap receipts (zap the zapper) |
||||
- Kind 1111 comments |
||||
- All content rendered as markdown |
||||
- Flat threading display (no indentation, similar to comment threading) |
||||
- Real-time updates |
||||
- Infinite scroll |
||||
|
||||
**Interaction Flow**: |
||||
1. User clicks "View feed" → Opens feed page at `/feed` |
||||
2. Feed displays kind 1 events from default relays |
||||
3. User can create new kind 1 events |
||||
4. User can reply to kind 1 events with kind 1 or zap |
||||
5. Zap receipts displayed as replies with ⚡ emoji |
||||
6. User can reply to kind 1 replies with kind 1 |
||||
7. User can reply to zap receipts with zap receipts or kind 1111 comments |
||||
|
||||
## Features |
||||
|
||||
### Authentication Methods |
||||
|
||||
1. **NIP-07 Extension**: Browser extension (e.g., Alby, nos2x) |
||||
2. **Nsec Login**: Direct bech32 nsec or hex private key |
||||
- Optional NIP-49 encryption (ncryptsec) with password |
||||
- Stored encrypted in localStorage |
||||
3. **NIP-46 Bunker**: Remote signer via bunker connection string |
||||
- `bunker://` URI scheme |
||||
- WebSocket connection to remote signer |
||||
4. **Anonymous**: Temporary nsec for session |
||||
- Generated on first visit |
||||
- Pattern-based avatar |
||||
- Handle: `Aitherite{random}` |
||||
- Only allows zap requests (no comments) |
||||
|
||||
### Content Display |
||||
|
||||
- **Threads (Kind 11)**: Discussion threads with topics |
||||
- **Comments (Kind 1111)**: Threaded comments with NIP-22 tags |
||||
- **Zap Receipts (Kind 9735)**: Lightning payments displayed as comments with ⚡ |
||||
- **Kind 1 Feed**: Twitter-like feed with replies and zaps |
||||
- **Reactions (Kind 7)**: Upvote/downvote system |
||||
- **Markdown Rendering**: All content supports full markdown |
||||
- **NIP-21 Links**: |
||||
- `nostr:npub...` → Clickable profile badge |
||||
- `nostr:nevent/note/naddr...` → Event card (250 char preview) → Event viewer |
||||
|
||||
### Thread Organization |
||||
|
||||
- Threads sorted by `t` tags (topics) |
||||
- Max 3 topics per thread |
||||
- Threads without topics appear under "General" |
||||
- Sorting: newest, most active, most upvoted |
||||
- 30-day timeout (configurable, checkbox to show older threads) |
||||
|
||||
### Thread Creation |
||||
|
||||
- Form to create new threads |
||||
- Topic selection (multi-select, max 3) |
||||
- Suggested topics (same as Discussion creation form in `jumble`) |
||||
- Publish to: |
||||
- Default relays |
||||
- `wss://thecitadel.nostr1.com` |
||||
- User's kind 10002 outbox relays |
||||
- Display all target relays with deselection option |
||||
- Publication status modal: |
||||
- Success/failure per relay |
||||
- Error messages (hyperlink https:// URLs) |
||||
- Auto-close after 30s or manual close |
||||
|
||||
### Commenting and Zapping |
||||
|
||||
- Response-threading: Flat list with gray blurb showing parent |
||||
- Click gray blurb to highlight/scroll to parent |
||||
- Respond to comments or zap receipts with: |
||||
- `kind 1111` comments |
||||
- `kind 9734` zap requests |
||||
- Zap requests sent to wallets, receipts displayed from relays |
||||
|
||||
### Reactions |
||||
|
||||
- Upvote/downvote buttons (kind 7) |
||||
- One vote per user per event |
||||
- Clicking twice deletes the vote |
||||
- Display vote counts |
||||
|
||||
### Advanced Features |
||||
|
||||
- Full-text search across threads/comments (keyboard shortcut `/`) |
||||
- Local caching (IndexedDB) for offline access |
||||
- Thread statistics (comment counts, zap totals, activity) |
||||
- Real-time updates (live indicators for new comments) |
||||
- Relay health monitoring (connection status, latency, auto-retry) |
||||
- User blocking/muting |
||||
- Content warnings (NSFW/sensitive content) |
||||
- Thread locking (prevent replies to old/closed threads) |
||||
- Keyboard shortcuts (j/k, r, z, etc.) |
||||
- Thread bumping (active threads rise to top) |
||||
- Recently active users list |
||||
- User profile pages with kind 1 feeds |
||||
|
||||
## Configuration |
||||
|
||||
### Environment Variables |
||||
|
||||
All configuration via Docker environment variables: |
||||
|
||||
```bash |
||||
# Default relays (comma-separated, overrides hardcoded defaults) |
||||
VITE_DEFAULT_RELAYS=wss://theforest.nostr1.com,wss://nostr21.com,wss://nostr.land,wss://nostr.wine,wss://nostr.sovbit.host |
||||
|
||||
# Minimum zap threshold in sats (must be 0 or positive integer, default: 1) |
||||
VITE_ZAP_THRESHOLD=1 |
||||
|
||||
# Server port (overrides default 9876, must be 1-65535) |
||||
PORT=9876 |
||||
|
||||
# Other configuration |
||||
VITE_THREAD_TIMEOUT_DAYS=30 |
||||
VITE_PWA_ENABLED=true |
||||
``` |
||||
|
||||
**Validation**: |
||||
- `VITE_ZAP_THRESHOLD`: Must be 0 or positive integer. Invalid/negative values default to 1. |
||||
- `VITE_DEFAULT_RELAYS`: Comma-separated list of relay URLs. Empty/invalid falls back to hardcoded defaults. |
||||
- `PORT`: Must be valid port number (1-65535). Invalid values default to 9876. |
||||
|
||||
### Default Relays |
||||
|
||||
``` |
||||
wss://theforest.nostr1.com |
||||
wss://nostr21.com |
||||
wss://nostr.land |
||||
wss://nostr.wine |
||||
wss://nostr.sovbit.host |
||||
``` |
||||
|
||||
### Profile Relays (additional) |
||||
|
||||
``` |
||||
wss://relay.damus.io |
||||
wss://aggr.nostr.land |
||||
``` |
||||
|
||||
## Docker Deployment |
||||
|
||||
### Dockerfile |
||||
|
||||
```dockerfile |
||||
# Multi-stage build |
||||
FROM node:20-alpine AS builder |
||||
WORKDIR /app |
||||
COPY package*.json ./ |
||||
RUN npm ci |
||||
COPY . . |
||||
# Build with environment variables (build-time) |
||||
ARG VITE_DEFAULT_RELAYS |
||||
ARG VITE_ZAP_THRESHOLD |
||||
ARG VITE_THREAD_TIMEOUT_DAYS |
||||
ARG VITE_PWA_ENABLED |
||||
ENV VITE_DEFAULT_RELAYS=${VITE_DEFAULT_RELAYS} |
||||
ENV VITE_ZAP_THRESHOLD=${VITE_ZAP_THRESHOLD} |
||||
ENV VITE_THREAD_TIMEOUT_DAYS=${VITE_THREAD_TIMEOUT_DAYS} |
||||
ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED} |
||||
RUN npm run build |
||||
|
||||
FROM httpd:alpine |
||||
COPY --from=builder /app/dist /usr/local/apache2/htdocs/ |
||||
COPY httpd.conf.template /usr/local/apache2/conf/httpd.conf.template |
||||
COPY docker-entrypoint.sh /usr/local/bin/ |
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh |
||||
# Port is configurable via PORT env var (runtime) |
||||
ARG PORT=9876 |
||||
ENV PORT=${PORT} |
||||
EXPOSE ${PORT} |
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] |
||||
``` |
||||
|
||||
### Apache Configuration (`httpd.conf.template`) |
||||
|
||||
```apache |
||||
Listen ${PORT} |
||||
ServerName localhost |
||||
|
||||
<Directory "/usr/local/apache2/htdocs"> |
||||
Options Indexes FollowSymLinks |
||||
AllowOverride All |
||||
Require all granted |
||||
</Directory> |
||||
|
||||
# Health check endpoint - must be before SPA routing |
||||
<Location "/healthz"> |
||||
Header set Content-Type "application/json" |
||||
Header set Cache-Control "public, max-age=5" |
||||
</Location> |
||||
|
||||
# Rewrite /healthz to /healthz.json (must be before SPA catch-all rule) |
||||
RewriteEngine On |
||||
RewriteBase / |
||||
RewriteRule ^healthz$ /healthz.json [L] |
||||
|
||||
# SPA routing (after healthz) |
||||
RewriteRule ^index\.html$ - [L] |
||||
RewriteCond %{REQUEST_FILENAME} !-f |
||||
RewriteCond %{REQUEST_FILENAME} !-d |
||||
RewriteRule . /index.html [L] |
||||
|
||||
# PWA support |
||||
<IfModule mod_headers.c> |
||||
Header set Service-Worker-Allowed "/" |
||||
</IfModule> |
||||
``` |
||||
|
||||
### Entrypoint Script (`docker-entrypoint.sh`) |
||||
|
||||
```bash |
||||
#!/bin/sh |
||||
set -e |
||||
|
||||
# Validate and set PORT (must be 1-65535) |
||||
PORT=${PORT:-9876} |
||||
if ! [ "$PORT" -ge 1 ] 2>/dev/null || ! [ "$PORT" -le 65535 ] 2>/dev/null; then |
||||
echo "Warning: Invalid PORT '$PORT', using default 9876" |
||||
PORT=9876 |
||||
fi |
||||
|
||||
# Replace PORT in httpd.conf template |
||||
envsubst '${PORT}' < /usr/local/apache2/conf/httpd.conf.template > /usr/local/apache2/conf/httpd.conf |
||||
|
||||
# Start Apache |
||||
exec httpd -D FOREGROUND |
||||
``` |
||||
|
||||
### Docker Compose |
||||
|
||||
```yaml |
||||
services: |
||||
aitherboard: |
||||
build: |
||||
context: . |
||||
args: |
||||
VITE_DEFAULT_RELAYS: ${VITE_DEFAULT_RELAYS:-wss://theforest.nostr1.com,wss://nostr21.com,wss://nostr.land,wss://nostr.wine,wss://nostr.sovbit.host} |
||||
VITE_ZAP_THRESHOLD: ${VITE_ZAP_THRESHOLD:-1} |
||||
VITE_THREAD_TIMEOUT_DAYS: ${VITE_THREAD_TIMEOUT_DAYS:-30} |
||||
VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true} |
||||
ports: |
||||
- "${PORT:-9876}:${PORT:-9876}" |
||||
environment: |
||||
- PORT=${PORT:-9876} |
||||
``` |
||||
|
||||
### Usage |
||||
|
||||
```bash |
||||
# Build with custom relays |
||||
docker build --build-arg VITE_DEFAULT_RELAYS="wss://relay1.com,wss://relay2.com" . |
||||
|
||||
# Run with custom port |
||||
docker run -e PORT=8080 -p 8080:8080 aitherboard |
||||
|
||||
# Run with custom zap threshold (must be 0 or positive) |
||||
docker run -e VITE_ZAP_THRESHOLD=5 aitherboard |
||||
|
||||
# Using docker-compose |
||||
docker-compose up -d |
||||
``` |
||||
|
||||
## Health Check |
||||
|
||||
### `/healthz` Endpoint |
||||
|
||||
A health check endpoint for monitoring systems (Kubernetes, Docker healthchecks, load balancers, etc.). |
||||
|
||||
**Implementation**: Static JSON file generated at build time. |
||||
|
||||
**Build Script** (`scripts/generate-healthz.js`): |
||||
|
||||
```javascript |
||||
import { writeFileSync } from 'fs' |
||||
import { readFileSync } from 'fs' |
||||
import { execSync } from 'child_process' |
||||
import { fileURLToPath } from 'url' |
||||
import { dirname, join } from 'path' |
||||
|
||||
const __filename = fileURLToPath(import.meta.url) |
||||
const __dirname = dirname(__filename) |
||||
|
||||
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8')) |
||||
const buildTime = new Date().toISOString() |
||||
|
||||
let gitCommit = 'unknown' |
||||
try { |
||||
gitCommit = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim() |
||||
} catch (e) { |
||||
// Git not available or not a git repo |
||||
} |
||||
|
||||
const healthz = { |
||||
status: 'ok', |
||||
service: 'aitherboard', |
||||
version: packageJson.version || '0.0.0', |
||||
buildTime, |
||||
gitCommit, |
||||
timestamp: new Date().toISOString() |
||||
} |
||||
|
||||
writeFileSync( |
||||
join(__dirname, '../public/healthz.json'), |
||||
JSON.stringify(healthz, null, 2) |
||||
) |
||||
``` |
||||
|
||||
**Vite Integration**: |
||||
|
||||
```typescript |
||||
import { defineConfig } from 'vite' |
||||
import { svelte } from '@sveltejs/vite-plugin-svelte' |
||||
import generateHealthz from './scripts/generate-healthz.js' |
||||
|
||||
export default defineConfig({ |
||||
plugins: [ |
||||
svelte(), |
||||
{ |
||||
name: 'generate-healthz', |
||||
buildStart() { |
||||
generateHealthz() |
||||
} |
||||
} |
||||
] |
||||
}) |
||||
``` |
||||
|
||||
**Response Example**: |
||||
|
||||
```json |
||||
{ |
||||
"status": "ok", |
||||
"service": "aitherboard", |
||||
"version": "1.0.0", |
||||
"buildTime": "2024-01-15T10:30:00.000Z", |
||||
"gitCommit": "a1b2c3d", |
||||
"timestamp": "2024-01-15T12:45:30.123Z" |
||||
} |
||||
``` |
||||
|
||||
**Monitoring Integration**: |
||||
- Kubernetes: `http://service:9876/healthz` |
||||
- Docker: `HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:9876/healthz || exit 1` |
||||
- Load balancers, Prometheus, etc. |
||||
|
||||
## Development |
||||
|
||||
### Project Structure |
||||
|
||||
``` |
||||
aitherboard/ |
||||
├── src/ |
||||
│ ├── lib/ |
||||
│ │ ├── services/ # Core services |
||||
│ │ │ ├── nostr/ # Nostr client, relay pool |
||||
│ │ │ ├── auth/ # Authentication |
||||
│ │ │ ├── cache/ # IndexedDB caching |
||||
│ │ │ └── security/ # Security utilities |
||||
│ │ ├── modules/ # Feature modules |
||||
│ │ │ ├── threads/ # Thread management |
||||
│ │ │ ├── comments/ # Comment threading |
||||
│ │ │ ├── zaps/ # Zap handling |
||||
│ │ │ ├── reactions/ # Upvote/downvote |
||||
│ │ │ ├── profiles/ # User profiles |
||||
│ │ │ └── feed/ # Kind 1 feed |
||||
│ │ └── components/ # UI components |
||||
│ │ ├── content/ # Markdown, NIP-21 links |
||||
│ │ ├── interactions/# Buttons, forms |
||||
│ │ ├── layout/ # Pages, navigation |
||||
│ │ └── modals/ # Dialogs, overlays |
||||
│ └── routes/ # SvelteKit routes |
||||
├── public/ # Static assets |
||||
├── scripts/ # Build scripts |
||||
├── Dockerfile |
||||
├── docker-compose.yml |
||||
├── httpd.conf.template |
||||
└── docker-entrypoint.sh |
||||
``` |
||||
|
||||
### Key Nostr Event Structures |
||||
|
||||
#### Kind 11 (Thread) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 11, |
||||
content: string, // Markdown content |
||||
tags: [ |
||||
['title', string], |
||||
['t', topic1], |
||||
['t', topic2], // max 3 topics |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### Kind 1111 (Comment) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 1111, |
||||
content: string, // Markdown content |
||||
tags: [ |
||||
['K', '11'], // Root thread kind |
||||
['E', rootEventId], // Root thread event |
||||
['e', parentEventId], // Direct parent (if replying) |
||||
['k', '1111'], // Parent kind |
||||
['p', authorPubkey], // Author being replied to |
||||
['P', rootAuthorPubkey], // Root thread author |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### Kind 9734 (Zap Request) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 9734, |
||||
content: string, // Zap message |
||||
tags: [ |
||||
['p', recipientPubkey], |
||||
['e', eventId], // Optional: event being zapped |
||||
['relays', ...relayUrls], |
||||
['amount', millisats], |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### Kind 9735 (Zap Receipt) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 9735, |
||||
content: string, // Bolt11 invoice |
||||
tags: [ |
||||
['p', recipientPubkey], |
||||
['e', eventId], // Optional: event being zapped |
||||
['bolt11', invoice], |
||||
['description', zapRequestJson], |
||||
['preimage', preimage], // Optional |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### Kind 7 (Reaction) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 7, |
||||
content: '+' | '-', // Only + or - allowed |
||||
tags: [ |
||||
['e', eventId], // Event being reacted to |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
#### Kind 1 (Note/Feed Post) |
||||
|
||||
```typescript |
||||
{ |
||||
kind: 1, |
||||
content: string, // Markdown content |
||||
tags: [ |
||||
['e', eventId], // Optional: event being replied to |
||||
['p', pubkey], // Optional: pubkey being replied to |
||||
['root', rootEventId], // Optional: root of thread |
||||
['reply', replyEventId], // Optional: direct parent |
||||
... |
||||
] |
||||
} |
||||
``` |
||||
|
||||
## Implementation Phases |
||||
|
||||
### Phase 1: Foundation |
||||
1. Project setup (Vite, Svelte 5, TypeScript, Tailwind) |
||||
2. Docker configuration |
||||
3. Basic Nostr client integration (applesauce) |
||||
4. Relay connection and AUTH handling |
||||
|
||||
### Phase 2: Authentication |
||||
1. NIP-07 extension login |
||||
2. Anonymous key generation |
||||
3. Nsec login (with NIP-49) |
||||
4. NIP-46 bunker login |
||||
5. Session management |
||||
|
||||
### Phase 3: Core Features |
||||
1. Thread display (kind 11) |
||||
2. Comment display (kind 1111) |
||||
3. Zap receipt display (kind 9735) |
||||
4. Markdown rendering |
||||
5. NIP-21 link parsing |
||||
|
||||
### Phase 4: Interactions |
||||
1. Thread creation form |
||||
2. Comment form |
||||
3. Zap functionality |
||||
4. Reaction voting |
||||
5. Publication status |
||||
|
||||
### Phase 5: Advanced Features |
||||
1. User profiles |
||||
2. Kind 1 feed page |
||||
3. Recently active users |
||||
4. Full-text search |
||||
5. IndexedDB caching |
||||
6. Real-time updates |
||||
7. Relay health monitoring |
||||
|
||||
### Phase 6: Polish |
||||
1. Responsive design |
||||
2. PWA support |
||||
3. Keyboard shortcuts |
||||
4. Error handling |
||||
5. Loading states |
||||
6. Performance optimization |
||||
|
||||
## Security |
||||
|
||||
### Key Security Principles |
||||
|
||||
1. **Never Store Plaintext Keys** |
||||
- Use NIP-49 encryption for stored keys |
||||
- Store encrypted key in localStorage (never plaintext) |
||||
- Anonymous keys: Generate fresh on session start, discard on close |
||||
|
||||
2. **Validate All Inputs** |
||||
- Validate bech32 strings (NIP-19) |
||||
- Validate event signatures |
||||
- Sanitize all HTML content (DOMPurify) |
||||
|
||||
3. **Secure Key Handling** |
||||
- NIP-07: Use browser extension (no key storage) |
||||
- Nsec: Encrypt with NIP-49 before storage |
||||
- Bunker: No local key storage |
||||
- Anonymous: Session-only, never persisted |
||||
|
||||
4. **Content Security** |
||||
- Sanitize markdown output |
||||
- Validate NIP-21 URIs |
||||
- Escape user content properly |
||||
|
||||
## License |
||||
|
||||
[Add your license here] |
||||
|
||||
## Contributing |
||||
|
||||
[Add contribution guidelines here] |
||||
|
||||
## Links |
||||
|
||||
- **Nostr Protocol**: https://nostr.com |
||||
- **Applesauce Library**: https://github.com/hzrd149/applesauce |
||||
- **NIP-7D (Kind 11 Threads)**: [Nostr Improvement Proposals] |
||||
- **NIP-22 (Kind 1111 Comments)**: [Nostr Improvement Proposals] |
||||
- **NIP-57 (Zaps)**: [Nostr Improvement Proposals] |
||||
|
||||
--- |
||||
|
||||
**Note**: This is a comprehensive implementation plan. Refer to the codebase for the actual implementation details. |
||||
Loading…
Reference in new issue