Ctrl K
ComunidadGuías…

AGENTS.md

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
**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.

# AGENTS.md — Stretto Music Player

## Project Overview

**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/`

## Architecture

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 |

## Directory Structure

```
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
```

## Backend

### Server Entry (`index.js`)

- Loads `.env` via `dotenv`
- Creates Express + HTTP server + Socket.IO
- Middleware: `body-parser`, `cookie-parser`, `express-session` (MongoDB-backed via `connect-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 from `controllers/socketio.js`

### Key API Routes (`controllers/index.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.

### Socket.IO (`controllers/socketio.js`)

Enables remote control across devices. Devices join rooms by user email and can emit `playpause`, `previous`, `next` to control playback on all connected devices.

### Database Models (`models/`)

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).

## Frontend

### Initialization (`src/js/index.js`)

The `Loader` class initializes subsystems in order:
1. `TitleUpdater` → updates browser tab title with current song
2. `FirstRunExperience` → onboarding flow
3. `AccountManager` → auth state
4. `Keyboard` → keyboard shortcuts (Space = play/pause, arrows = next/prev)
5. `Theme` → loads Bootswatch theme from CDN
6. `ModelInitialiser` → loads songs/playlists from localStorage
7. `RootView` → mounts React Router into `#react` div
8. `Lyrics` → auto-fetches lyrics via AZLyrics
9. `ServiceWorkerClient` → registers service worker
10. `SoundcloudOAuth` → SoundCloud auth initialization

### Routing (`src/js/views/root.jsx`)

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 |

### Layout (`src/js/views/layout.jsx`)

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)

### State Management

**No Redux or MobX.** Uses a custom observer pattern:
- `Song` and `Playlist` are static classes holding in-memory arrays
- They expose `addOnChangeListener(callback)` and `change()` (notify) methods
- Components subscribe in constructors and call `setState({})` to re-render on changes
- **Persistence:** `DataLayer` wraps `localStorage`; `SyncManager` syncs to server

### Player Architecture (Strategy Pattern)

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).

### Key Frontend Services (`src/js/services/`)

| 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) |

### Service Worker (`src/js/serviceworker.js`)

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-audio` cache for downloaded songs
- **BroadcastChannel API:** Receives `DOWNLOAD`, `OFFLINE_RAW_FILE`, `REMOVE_OFFLINE`, `EMIT_OFFLINED` messages

### Styling

- **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

## Build & Dev

### NPM Scripts

| Script | Command | Purpose |
|---|---|---|
| `dev` | `nodemon & webpack --watch` | Development (auto-restart server + hot rebuild) |
| `start` | `webpack && node --harmony index.js` | Production |
| `webpack` | `webpack` | Build only |

### Webpack (`webpack.config.js`)

- **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` (returns `null`) to avoid pulling in full jQuery

### Docker

```bash
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).

### Environment Variables (`.env`)

Required variables (see `.env.example`):
- `GOOGLE_CLIENT_ID` — Google OAuth client ID
- `SPOTIFY_CLIENT_ID` — Spotify API client ID
- `SOUNDCLOUD_CLIENT_ID` — SoundCloud API client ID
- `SMTP_PASSWORD` — Zoho SMTP password for password reset emails
- `YOUTUBE_REDIRECT_ENDPOINT` — YouTube stream redirect URL
- `SOUNDCLOUD_REDIRECT_ENDPOINT` — SoundCloud stream redirect URL

### CI/CD (`.github/workflows/deploy.yml`)

- **Trigger:** Push to `master`
- **Action:** Deploys to Dokku PaaS at `ssh://dokku@kaiserapps.com/stretto-next`

## External Integrations

| 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 |

## Testing

**There are no tests.** No test framework, test files, or test scripts exist in the project.

## Key Patterns & Conventions

1. **Singletons:** `Player`, `AccountManager`, `SocketManager`, `SyncManager` are all instantiated at module level and exported as instances (`export default new ClassName()`)
2. **Observer pattern:** Models use `addOnChangeListener`/`change()` for reactive updates instead of Redux
3. **Per-user blobs:** Database stores entire song/playlist collections as single documents per user — not normalized
4. **Source-prefixed IDs:** Song IDs use `y_`, `s_`, `a_` prefixes to identify their streaming source
5. **JSX in services:** Some service files use `.jsx` extension and contain React elements (e.g., `alerter.jsx`, `sync_manager.jsx`)
6. **No TypeScript:** Entire codebase is plain JavaScript with JSX
7. **Class components:** All React components are class-based (no hooks)
8. **Global imports:** Services and models are imported directly and used as global singletons throughout the codebase