Browse Source

initial commit

master
Silberengel 1 month ago
commit
ec4a042615
  1. 767
      README.md

767
README.md

@ -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…
Cancel
Save