IMPORTANT: before you do anything else, run the beans prime command and heed its output.
IMPORTANT: Read files in their entirety, do not read portions of files unless the contents are longer than 2000 lines.
Stretto is an open-source, web-based music player (v2.0.0, MIT license) that streams music from YouTube, SoundCloud, and Audius. It runs as a Progressive Web App with offline support, cloud sync, and multi-device remote control.
Live: https://next.kaiserapps.com/
Monolithic full-stack application — a single Node.js/Express server serves both a REST API and a server-rendered Pug shell, which boots a React 16 SPA. Not a monorepo, not microservices, not GraphQL.
| Layer | Technology |
|---|---|
| Backend | Express.js, Socket.IO, Mongoose |
| Frontend | React 16, React Router v4, SCSS, Bootstrap 3 (Bootswatch) |
| Database | MongoDB (per-user document blobs) |
| Real-time | Socket.IO (multi-device remote control) |
| PWA | Service Worker + Cache API + BroadcastChannel |
| Deployment | Docker + Dokku PaaS via GitHub Actions |
stretto/
├── index.js # Express server entry point
├── controllers/
│ ├── index.js # All REST API routes (single Express Router)
│ └── socketio.js # Socket.IO remote control handlers
├── models/
│ ├── data_mapper.js # Facade wrapping all model operations
│ ├── user.js # User model (email, hash, salt, version, googleObject)
│ ├── song.js # Song model (stores ALL songs for a user as one blob)
│ ├── playlist.js # Playlist model (stores ALL playlists as one blob)
│ ├── artist.js # Followed artists model
│ └── shared_playlist.js # Shared playlist model (keyed by UUID)
├── services/
│ ├── google.js # Google OAuth2 token verification
│ └── itunes.js # iTunes Search API (artist lookup, song feed, cover art)
├── src/
│ ├── js/
│ │ ├── index.js # Frontend entry point (Loader class, initializes subsystems)
│ │ ├── serviceworker.js # Service worker (offline caching, audio storage)
│ │ ├── models/ # Client-side data models (Song, Playlist, DataLayer)
│ │ ├── services/ # Player engine, streaming, auth, sync, integrations
│ │ └── views/ # React components (routes, layout, pages)
│ └── scss/
│ └── main.scss # All styles (~850 lines)
├── views/ # Pug server templates (layout, index, redirects)
├── static/
│ ├── assets/ # Icons, manifest.json, fonts (committed)
│ └── js/ # Webpack output (gitignored)
├── bin/ # Docker helper scripts (go, build, run, restart)
├── stubs/
│ └── jquery.js # jQuery stub (returns null, avoids bootstrap-slider dep)
├── webpack.config.js # Two entry points: main + serviceworker
├── docker-compose.yml # MongoDB + web services
├── Dockerfile.local # Node 16 dev container
├── .env.example # Required env vars template
├── .github/workflows/deploy.yml # CI/CD → Dokku
└── package.json # Dependencies and scripts
- Loads
.envviadotenv - Creates Express + HTTP server + Socket.IO
- Middleware:
body-parser,cookie-parser,express-session(MongoDB-backed viaconnect-mongodb-session) - Proxies
/scapi→https://api-v2.soundcloud.com(SoundCloud API proxy) - Pug view engine, serves static files from
/static - Routes from
controllers/index.js, Socket.IO fromcontrollers/socketio.js
| Route | Method | Auth | Purpose |
|---|---|---|---|
/googleLogin |
POST | No | Google OAuth login + session |
/login |
POST | No | Email/password login |
/createaccount |
POST | No | Account registration |
/checklogin |
POST | No | Check session status |
/logout |
POST | No | Destroy session |
/forgotpassword |
POST | No | Send password reset email (Zoho SMTP) |
/completereset |
POST | No | Complete password reset with token |
/latestversion |
GET | Yes | User's data version number |
/latestdata |
GET | Yes | Full user library (songs + playlists) |
/uploaddata |
POST | Yes | Sync library data (optimistic concurrency) |
/addsong |
POST | Yes | Add a single song to a playlist |
/suggest/artists |
GET | Yes | Artist suggestions via iTunes |
/artists/followed |
GET | Yes | Followed artists + songs |
/artists/follow |
POST | Yes | Follow an artist |
/artists/unfollow |
POST | Yes | Unfollow an artist |
/share |
POST | Yes | Share playlist (UUID link) |
/shared/:guid |
GET | No | View shared playlist |
/spotify_callback |
GET | No | Spotify OAuth callback |
* |
GET | No | Catch-all → serves React SPA |
Authentication: Session-based via req.session.loggedIn. A simple loggedIn middleware function guards protected routes.
Enables remote control across devices. Devices join rooms by user email and can emit playpause, previous, next to control playback on all connected devices.
All models use Mongoose. The DataMapper class (data_mapper.js) is a static facade that wraps all model CRUD.
Critical design note: Songs and playlists are stored as entire blobs per user (one document per user, not per song). Every sync operation uploads/downloads the entire library. A version timestamp on the User model provides optimistic concurrency control.
| Model | Key Fields |
|---|---|
| User | email, hash, salt, resetToken, publicJsonLibrary, version, googleObject |
| Song | email, songBlob (Mixed — all songs as one blob) |
| Playlist | email, playlistBlob (Mixed — all playlists as one blob) |
| Artist | email, artists (Mixed array) |
| SharedPlaylist | guid, email, playlistBlob |
Password hashing uses PBKDF2 with SHA-512 (10,000 iterations, 512-byte key).
The Loader class initializes subsystems in order:
TitleUpdater→ updates browser tab title with current songFirstRunExperience→ onboarding flowAccountManager→ auth stateKeyboard→ keyboard shortcuts (Space = play/pause, arrows = next/prev)Theme→ loads Bootswatch theme from CDNModelInitialiser→ loads songs/playlists from localStorageRootView→ mounts React Router into#reactdivLyrics→ auto-fetches lyrics via AZLyricsServiceWorkerClient→ registers service workerSoundcloudOAuth→ SoundCloud auth initialization
React Router v4 (BrowserRouter + Switch):
| Path | Component | Description |
|---|---|---|
/ |
Intro |
Welcome/landing page |
/home |
Home |
Playlist tiles grid |
/playlist/:playlist |
Playlist |
Song list view |
/player |
PlayerView |
Full-screen mobile player |
/add |
Add |
Add songs by URL |
/edit/:id |
Edit |
Edit song metadata |
/search/:search |
Search |
Search library |
/spotify |
Spotify |
Spotify import |
/discover |
Discover |
Top charts (iTunes/Spotify) |
/artists/feed |
ArtistsFeed |
Followed artists' new music |
/artists/add |
ArtistSuggestions |
Discover & follow artists |
/artists/manage |
ArtistsManage |
Manage followed artists |
/soundcloud |
Soundcloud |
SoundCloud integration |
/sync |
Sync |
Cloud sync page |
/backup |
BackupRestore |
Backup/restore library |
/settings |
Settings |
App settings |
/shared/:guid |
SharedPlaylist |
View shared playlist |
/mix/:playlist |
YoutubeMix |
YouTube radio mix |
/remote |
Remote |
Remote control (uses separate layout) |
/explicitscan |
ExplicitScan |
Scan for explicit tracks |
Responsive with a 700px breakpoint:
- Desktop: Fixed left
Sidebar(logo, search, navigation, player controls, album art) + content area - Mobile:
MobileHeader(top navbar) +MobileFooter(bottom player controls) + content area - Global overlays:
Bootbox(modals),AlerterContainer(toasts),ContextMenu(right-click menus)
No Redux or MobX. Uses a custom observer pattern:
SongandPlaylistare static classes holding in-memory arrays- They expose
addOnChangeListener(callback)andchange()(notify) methods - Components subscribe in constructors and call
setState({})to re-render on changes - Persistence:
DataLayerwrapslocalStorage;SyncManagersyncs to server
The Player singleton (src/js/services/player.js) orchestrates playback, delegating to stream-specific implementations:
Player (orchestrator)
├── HTML5AudioPlayer → offline cached files
├── YoutubeStreamPlayer → YouTube via redirect endpoint (library songs)
├── YoutubeIframePlayer → YouTube IFrame API (preview/non-library)
├── SoundcloudStreamPlayer → SoundCloud HLS streams
└── AudiusStreamPlayer → Audius API streams
All extend AbstractHTML5AudioPlayer which provides a common interface: dispose(), toggle(), setVolume(), getPosition(), setCurrentTime().
Song IDs are prefixed by source: y_ (YouTube), s_ (SoundCloud), a_ (Audius).
| Service | File | Purpose |
|---|---|---|
| Player | player.js |
Singleton — playback orchestration, queue, shuffle, repeat, MediaSession API |
| YouTube | youtube.js |
Search (scrapes results page), video info extraction, playlist parsing |
| SoundCloud | soundcloud.js |
Search & track resolution |
| Audius | audius.js |
Resolve & stream via Audius API |
| AccountManager | account_manager.js |
Singleton — auth state, Google OAuth + email/password login |
| SyncManager | sync_manager.jsx |
Singleton — bidirectional sync with version-based conflict resolution |
| SpotifyAPI | spotify_api.js |
Spotify Web API — OAuth, playlists, albums, charts |
| SpotifyImporter | spotify_importer.js |
Imports Spotify playlists by finding YouTube matches |
| SocketManager | socket_manager.js |
Singleton — Socket.IO client for remote control |
| Lyrics | lyrics.jsx |
Auto-fetches lyrics from AZLyrics on song change |
| ServiceWorkerClient | service_worker_client.jsx |
Communicates with SW via BroadcastChannel for offline audio |
| Alerter | alerter.jsx |
Toast notification system (react-alert) |
| Bootbox | bootbox.jsx |
Modal dialog system (alert, confirm, prompt) |
Offline-first PWA strategy:
- App shell caching:
/,/static/js/main.js, Google API, Font Awesome - Navigation preload: Returns cached shell for all SPA routes, background-refetches
- Third-party caching: Bootswatch themes, YouTube thumbnails, album art from various CDNs
- Audio offline storage: Separate
stretto-cache-audiocache for downloaded songs - BroadcastChannel API: Receives
DOWNLOAD,OFFLINE_RAW_FILE,REMOVE_OFFLINE,EMIT_OFFLINEDmessages
- SCSS — single file
src/scss/main.scss - Bootstrap 3 via Bootswatch CDN themes (15 themes, switchable at runtime in Settings)
- react-bootstrap v0.32 for UI components
- Font Awesome 4.7 for icons
- Custom font: WisdomScript (logo)
- Responsive: 700px breakpoint for mobile vs desktop layout
| Script | Command | Purpose |
|---|---|---|
dev |
nodemon & webpack --watch |
Development (auto-restart server + hot rebuild) |
start |
webpack && node --harmony index.js |
Production |
webpack |
webpack |
Build only |
- Two entry points:
main→src/js/index.js,serviceworker→src/js/serviceworker.js - Output:
static/js/[name].js - Babel:
@babel/preset-env(last 2 Chrome versions), decorators (legacy), JSX, object rest spread - Loaders:
babel-loader,style-loader+css-loader,sass-loader - jQuery: Aliased to
stubs/jquery.js(returnsnull) to avoid pulling in full jQuery
bin/go # docker-compose up -d
bin/build # docker-compose build
bin/run <cmd> # docker-compose run --rm web /bin/bash -c "<cmd>"
bin/restart # stop → build → start
bin/softrestart # stop → start (no rebuild)docker-compose.yml defines two services: mongo (MongoDB) and web (Node.js app on port 3000).
Required variables (see .env.example):
GOOGLE_CLIENT_ID— Google OAuth client IDSPOTIFY_CLIENT_ID— Spotify API client IDSOUNDCLOUD_CLIENT_ID— SoundCloud API client IDSMTP_PASSWORD— Zoho SMTP password for password reset emailsYOUTUBE_REDIRECT_ENDPOINT— YouTube stream redirect URLSOUNDCLOUD_REDIRECT_ENDPOINT— SoundCloud stream redirect URL
- Trigger: Push to
master - Action: Deploys to Dokku PaaS at
ssh://dokku@kaiserapps.com/stretto-next
| Integration | Purpose | Auth Method |
|---|---|---|
| Google OAuth | User authentication | OAuth2 ID token (server-side verification) |
| YouTube | Music search & streaming | Scraping (no API key) + redirect endpoint |
| SoundCloud | Music search & streaming | Client ID + HLS |
| Audius | Decentralized music streaming | Public API (no auth) |
| Spotify | Playlist import, chart data | OAuth2 implicit flow (client-side) |
| iTunes | Charts, artist discovery | Public Search API |
| AZLyrics | Lyrics fetching | Scraping via js-azlyrics |
| MusicBrainz | Cover art lookup | Public API |
| Zoho SMTP | Password reset emails | SMTP credentials |
There are no tests. No test framework, test files, or test scripts exist in the project.
- Singletons:
Player,AccountManager,SocketManager,SyncManagerare all instantiated at module level and exported as instances (export default new ClassName()) - Observer pattern: Models use
addOnChangeListener/change()for reactive updates instead of Redux - Per-user blobs: Database stores entire song/playlist collections as single documents per user — not normalized
- Source-prefixed IDs: Song IDs use
y_,s_,a_prefixes to identify their streaming source - JSX in services: Some service files use
.jsxextension and contain React elements (e.g.,alerter.jsx,sync_manager.jsx) - No TypeScript: Entire codebase is plain JavaScript with JSX
- Class components: All React components are class-based (no hooks)
- Global imports: Services and models are imported directly and used as global singletons throughout the codebase