7.4 KiB
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
# 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
# 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 discoveryBNB_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
MusicServiceandMusicLibraryinterfaces - 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
MusicServiceinterface (auth and login) - Defines
MusicLibraryinterface (browsing, search, streaming, rating, scrobbling) - Domain types:
Artist,Album,Track,Playlist,Genre,Rating, etc. - Uses
fp-tsfor 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/sonoslibrary 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-tsTaskEither
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
- Sonos App Request → SOAP endpoint (
/ws/sonos) - SOAP Service → Verifies auth token, calls
MusicLibrarymethods - MusicLibrary → Makes Subsonic API calls, transforms data
- SOAP Response → Returns XML formatted for Sonos
For streaming:
- Sonos Device →
GET /stream/track/:idwith custom headers (bnbt, bnbk) - Stream Handler → Verifies token, calls
MusicLibrary.stream() - Subsonic Stream → Proxies audio with proper mime-type handling
- 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_COLORandBNB_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
- Sonos app requests link code via
getAppLink() - User visits login URL with link code
- User enters Subsonic credentials
- bonob validates with Subsonic, generates service token
- bonob associates link code with service token
- Sonos polls
getDeviceAuthToken()with link code - bonob returns SMAPI token (JWT) to Sonos
- Subsequent requests use SMAPI token, which maps to service token
Testing Philosophy
- Jest with ts-jest preset
- In-memory implementations for
LinkCodes,APITokensfor 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-tsTaskEither<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-tsforOption,Either,TaskEither - Pipe-based composition (
pipe(data, fn1, fn2, ...)) - Immutable data transformations
File Organization
src/- TypeScript source codetests/- Jest test files (mirrors src/ structure)build/- Compiled JavaScript (gitignored)web/- HTML templates (Eta templating) and static assetstypings/- Custom TypeScript definitions
Important Constraints
- bonob must be accessible from Sonos devices at
BNB_URL BNB_URLcannot 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