From ec4a042615d285890824268cebe7f9fe456dc5af Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 2 Feb 2026 11:06:40 +0100 Subject: [PATCH] initial commit --- README.md | 767 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 767 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d9d9eb0 --- /dev/null +++ b/README.md @@ -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 + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + +# Health check endpoint - must be before SPA routing + + Header set Content-Type "application/json" + Header set Cache-Control "public, max-age=5" + + +# 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 + + Header set Service-Worker-Allowed "/" + +``` + +### 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.