Files
bonob/CLAUDE.md
Wolfgang Kulhanek fee5f74a2c Save tokens
2025-10-16 10:51:40 +02:00

188 lines
7.4 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
bonob is a Sonos SMAPI (Sonos Music API) implementation that bridges Subsonic API clones (like Navidrome and Gonic) with Sonos devices. It acts as a middleware service that translates between the Subsonic API and Sonos's proprietary music service protocol, allowing users to stream their personal music libraries to Sonos speakers.
## Development Commands
### Building and Running
```bash
# Build TypeScript to JavaScript
npm run build
# Development mode with auto-reload (requires environment variables)
npm run dev
# OR with auto-registration
npm run devr
# Register bonob service with Sonos devices
npm run register-dev
```
### Testing
```bash
# Run all tests
npm test
# Run tests in watch mode
npm run testw
# Set custom test timeout (default: 5000ms)
JEST_TIMEOUT=10000 npm test
```
### Environment Variables for Development
When running locally, you need to set several environment variables:
- `BNB_DEV_HOST_IP`: Your machine's IP address (so Sonos can reach bonob)
- `BNB_DEV_SONOS_DEVICE_IP`: IP address of a Sonos device for discovery
- `BNB_DEV_SUBSONIC_URL`: URL of your Subsonic API server (e.g., Navidrome)
## Architecture
### Core Components
**`src/app.ts`** - Application entry point
- Reads configuration from environment variables
- Initializes all services (Subsonic, Sonos, authentication)
- Wires together the Express server with appropriate dependencies
- Handles SIGTERM for graceful shutdown
**`src/server.ts`** - Express HTTP server
- Serves web UI for service registration and login
- Handles music streaming (`/stream/track/:id`)
- Generates icons and cover art (`/icon/...`, `/art/...`)
- Serves Sonos-specific XML files (strings, presentation map)
- Binds SOAP service for Sonos SMAPI communication
**`src/smapi.ts`** - Sonos SMAPI SOAP implementation (1200+ lines)
- Implements the Sonos Music API Protocol via SOAP/XML
- Core operations: `getMetadata`, `getMediaURI`, `search`, `getExtendedMetadata`
- Handles authentication flow with link codes and device auth tokens
- Manages token refresh and session management
- Maps music library concepts to Sonos browse hierarchy
**`src/subsonic.ts`** - Subsonic API client
- Implements `MusicService` and `MusicLibrary` interfaces
- Handles authentication with Subsonic servers using token-based auth
- Translates between Subsonic data models and bonob's domain types
- Supports custom player configurations for transcoding
- Special handling for Navidrome (bearer token authentication)
- Implements artist image fetching with optional caching
**`src/music_service.ts`** - Core domain types and interfaces
- Defines `MusicService` interface (auth and login)
- Defines `MusicLibrary` interface (browsing, search, streaming, rating, scrobbling)
- Domain types: `Artist`, `Album`, `Track`, `Playlist`, `Genre`, `Rating`, etc.
- Uses `fp-ts` for functional programming patterns (`TaskEither`, `Option`, `Either`)
**`src/sonos.ts`** - Sonos device discovery and registration
- Discovers Sonos devices on the network using SSDP/UPnP
- Registers/unregisters bonob as a music service with Sonos systems
- Supports both auto-discovery and seed-host based discovery
- Uses `@svrooij/sonos` library for device communication
**`src/smapi_auth.ts`** - Authentication token management
- Implements JWT-based SMAPI tokens (token + key pairs)
- Handles token verification and expiry
- Token refresh flow using `fp-ts` `TaskEither`
**`src/config.ts`** - Configuration management
- Reads and validates environment variables (all prefixed with `BNB_`)
- Legacy environment variable support (BONOB_ prefix)
- Type-safe configuration with defaults
### Key Abstractions
**BUrn (Bonob URN)** - Resource identifier system (`src/burn.ts`)
- Format: `{ system: string, resource: string }`
- Systems: `subsonic` (for cover art), `external` (for URLs like Spotify images)
- Used for abstracting art/image sources across different backends
**URL Builder** (`src/url_builder.ts`)
- Wraps URL manipulation with a builder pattern
- Handles context path for reverse proxy deployments
- Used throughout for generating URLs that Sonos devices can access
**Custom Players** (`src/subsonic.ts`)
- Allows mime-type specific transcoding configurations
- Maps source mime types to transcoded types
- Creates custom "client" names in Subsonic (e.g., "bonob+audio/flac")
- Example: `BNB_SUBSONIC_CUSTOM_CLIENTS="audio/flac>audio/mp3"`
### Data Flow
1. **Sonos App Request** → SOAP endpoint (`/ws/sonos`)
2. **SOAP Service** → Verifies auth token, calls `MusicLibrary` methods
3. **MusicLibrary** → Makes Subsonic API calls, transforms data
4. **SOAP Response** → Returns XML formatted for Sonos
For streaming:
1. **Sonos Device**`GET /stream/track/:id` with custom headers (bnbt, bnbk)
2. **Stream Handler** → Verifies token, calls `MusicLibrary.stream()`
3. **Subsonic Stream** → Proxies audio with proper mime-type handling
4. **Response** → Streams audio to Sonos, reports "now playing"
### Icon System (`src/icon.ts`)
- SVG-based icon generation with dynamic colors
- Supports foreground/background color customization via `BNB_ICON_FOREGROUND_COLOR` and `BNB_ICON_BACKGROUND_COLOR`
- Genre-specific icons
- Text overlay support (e.g., year icons like "1984")
- Holiday/festival decorations (auto-applied based on date)
- Legacy mode: renders to 80x80 PNG for older Sonos systems
### Authentication Flow
1. Sonos app requests link code via `getAppLink()`
2. User visits login URL with link code
3. User enters Subsonic credentials
4. bonob validates with Subsonic, generates service token
5. bonob associates link code with service token
6. Sonos polls `getDeviceAuthToken()` with link code
7. bonob returns SMAPI token (JWT) to Sonos
8. Subsequent requests use SMAPI token, which maps to service token
### Testing Philosophy
- Jest with ts-jest preset
- In-memory implementations for `LinkCodes`, `APITokens` for testing
- Mocking with `ts-mockito`
- Test helpers in `tests/` directory
- Console.log suppressed in tests (see `tests/setup.js`)
## Common Patterns
### Error Handling
- Use `fp-ts` `TaskEither<AuthFailure, T>` for async operations that can fail with auth errors
- SOAP faults for Sonos-specific errors (see SMAPI_FAULT_* constants)
- Promise-based error handling with `.catch()` for most async operations
### Type Safety
- Strict TypeScript (`strict: true`, `noImplicitAny: true`, `noUncheckedIndexedAccess: true`)
- Extensive use of discriminated unions
- Interface-based design for pluggable services
### Logging
- Winston-based logger (`src/logger.ts`)
- Log level controlled by `BNB_LOG_LEVEL`
- Request logging optional via `BNB_SERVER_LOG_REQUESTS`
### Functional Programming
- Heavy use of `fp-ts` for `Option`, `Either`, `TaskEither`
- Pipe-based composition (`pipe(data, fn1, fn2, ...)`)
- Immutable data transformations
## File Organization
- `src/` - TypeScript source code
- `tests/` - Jest test files (mirrors src/ structure)
- `build/` - Compiled JavaScript (gitignored)
- `web/` - HTML templates (Eta templating) and static assets
- `typings/` - Custom TypeScript definitions
## Important Constraints
- bonob must be accessible from Sonos devices at `BNB_URL`
- `BNB_URL` cannot contain "localhost" (validation error)
- Sonos requires specific XML formats (SMAPI WSDL v1.19.6)
- Streaming must handle HTTP range requests for seek functionality
- Token lifetime (`BNB_AUTH_TIMEOUT`) should be less than Subsonic session timeout