Files
shark/CLAUDE.md

227 lines
8.6 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Shark is a music management and download application built with Tauri 2, SvelteKit 2, and TypeScript. It integrates with Deezer for music downloads and uses a custom Windows 98-style UI theme.
## Development Commands
- **Run dev server**: `bun run tauri dev` (runs both Vite dev server and Tauri)
- **Build for production**: `bun run tauri build`
- **Type checking**: `bun run check`
- **Add packages**: `bun add <package-name>`
### Important Notes
- Always use `bun` as the package manager, not npm or yarn
- Tauri capabilities changes require a full restart of `bun run tauri dev` to take effect
- The app uses custom window decorations (no native title bar)
## Architecture
### Tech Stack
- **Frontend**: SvelteKit 2 with Svelte 5 (runes-based reactivity)
- **Desktop Runtime**: Tauri 2
- **Package Manager**: Bun
- **Styling**: Custom Windows 98 theme (defined in `+layout.svelte`)
### Directory Structure
```
shark/
├── src/ # SvelteKit frontend
│ ├── routes/ # File-based routing
│ │ ├── +layout.svelte # Root layout with Windows 98 theme
│ │ ├── library/ # Music library management
│ │ ├── playlists/ # Playlist management
│ │ ├── services/deezer/ # Deezer integration UI
│ │ └── settings/ # App settings
│ └── lib/
│ ├── services/
│ │ └── deezer/ # Deezer service layer
│ │ ├── crypto.ts # Blowfish decryption for tracks
│ │ ├── downloader.ts # Track download & decryption
│ │ └── paths.ts # File path generation
│ ├── stores/ # Svelte stores (reactive state)
│ │ ├── deezer.ts # Deezer auth & user state
│ │ └── settings.ts # App settings state
│ └── types/ # TypeScript type definitions
└── src-tauri/ # Tauri backend
├── capabilities/
│ └── default.json # Security permissions
└── tauri.conf.json # Tauri configuration
```
### Key Components
**UI Components** (`src/lib/`):
- `MenuBar.svelte` - Top menu bar
- `TitleBar.svelte` - Custom window title bar with controls
- `ToolBar.svelte` - Navigation toolbar
**Services** (`src/lib/services/`):
- `deezer.ts` - Main Deezer API client with cookie-based session management
- `deezer/crypto.ts` - MD5, AES-ECB, and Blowfish decryption utilities
- `deezer/downloader.ts` - Handles streaming download and chunk-based Blowfish decryption
- `deezer/paths.ts` - Generates file paths with template: `<albumartist>/<album>/<tracknumber> - <title>`
**State Management**:
- Uses Tauri's `@tauri-apps/plugin-store` for persistent storage
- Svelte stores for reactive state (`$state` runes in Svelte 5)
- Deezer authentication stored persistently (ARL token + user data)
### Deezer Integration Details
**Authentication**:
- Uses ARL (Authentication Resource Locator) cookie from Deezer website
- Implements manual cookie jar to maintain session across requests
- CSRF tokens are dynamically refreshed (stored in `DeezerAPI.apiToken`)
**API Architecture**:
- **Gateway API** (`deezer.gw`): Used for track metadata via `http://www.deezer.com/ajax/gw-light.php`
- **Media API**: Used for download URLs via `https://media.deezer.com/v1/get_url`
- **CDN**: Actual track downloads from `*.dzcdn.net` domains
**Cookie Management** (Critical):
- Deezer requires both ARL cookie AND session cookies (sid) from API responses
- `DeezerAPI` class implements a `Map<string, string>` cookie store
- All cookies are sent with every request to maintain session state
- This is essential because Deezer's CSRF tokens are session-dependent
**Decryption Flow**:
1. Download encrypted FLAC/MP3 from CDN
2. Every 3rd chunk of 2048 bytes is Blowfish-CBC encrypted
3. Generate Blowfish key from track ID using MD5 XOR algorithm
4. Decrypt chunks using `blowfish-node` library (browser-compatible mode)
5. Write decrypted file to user-selected music folder
**Crypto Implementation**:
- `md5()` - Custom MD5 implementation (pure JS, no Node.js crypto)
- `ecbEncrypt()` - AES-128-ECB using `@noble/ciphers`
- `decryptChunk()` - Blowfish-CBC using `blowfish-node` with Uint8Array (not Buffer)
- `generateBlowfishKey()` - Generates key from track ID + secret
### Tauri Security Model
**Capabilities** (`src-tauri/capabilities/default.json`):
- HTTP requests are scoped to allowed domains only
- Required domains: `*.deezer.com`, `media.deezer.com`, `*.dzcdn.net`
- File system access is scoped (currently `**` for user-selected directories)
- Uses `unsafe-headers` feature to send custom Cookie headers
**Permissions Required**:
- `fs:allow-write-file` - Write downloaded tracks
- `fs:allow-mkdir` - Create artist/album folders
- `fs:allow-remove` - Clean up temp files
- `fs:allow-rename` - Move from temp to final location
- `http:default` with custom scope - Fetch from Deezer APIs/CDN
### Common Patterns
**Adding New Tauri Permissions**:
1. Edit `src-tauri/capabilities/default.json`
2. Add specific permission (e.g., `"fs:allow-read-file"`)
3. Add scope if needed (e.g., URL patterns for HTTP)
4. **Must restart** `bun run tauri dev` completely (stop and start)
**Deezer API Calls**:
- Always use `DeezerAPI.apiCall()` for GW API endpoints
- Pass method name and args object
- Automatic CSRF token refresh with retry logic (max 2 retries)
- Cookie headers are automatically managed
- Rate limiting handled automatically (error codes 4, 700 → 5s wait + retry)
- Network errors (ECONNRESET, ETIMEDOUT, etc.) → 2s wait + retry
- Download timeout (60s) with automatic retry (max 3 attempts)
**Svelte 5 State**:
- Use `$state` rune for reactive variables
- Use `$derived` for computed values
- Stores use `$` prefix in templates (e.g., `$deezerAuth.user`)
### Known Issues & Solutions
**Infinite Loop Prevention**:
- Token refresh has retry limit (2 attempts) to prevent infinite loops
- Chunk decryption errors are sampled (1% logging) to prevent console spam
**Error Handling & Resilience**:
- All network operations have automatic retry logic matching deemix behavior
- API rate limiting detected and handled (5s wait on error codes 4, 700)
- Network timeouts/errors trigger 2s wait + retry
- Download timeouts (60s) with up to 3 retry attempts
- Maintains deemix-compliant behavior to avoid bans
**Browser Compatibility**:
- Must use `Uint8Array` instead of Node.js `Buffer`
- Blowfish IV must be `Uint8Array`, not Buffer
- `@noble/ciphers` for AES (browser-compatible)
- `blowfish-node` works in browser when using Uint8Array mode
**File Path Sanitization**:
- Uses `paths.ts` to sanitize illegal characters for cross-platform compatibility
- Handles Windows reserved names (CON, PRN, etc.)
- Template is hard-coded but can be extended
## Testing Deezer Integration
1. **Get ARL Token**:
- Log into deezer.com in browser
- Open DevTools → Application → Cookies
- Copy the `arl` cookie value (192 chars)
2. **Test Track Download**:
- Navigate to Services → Deezer
- Enter ARL token and login
- Use track ID `3135556` (Daft Punk - One More Time) for testing
- Check console for `[DEBUG]` logs showing API flow
3. **Verify Download**:
- Check that FLAC/MP3 is created in music folder
- Verify file is playable (decryption worked)
- Check folder structure matches template
## Dependencies Note
- `@noble/ciphers` - AES encryption (browser-compatible)
- `blowfish-node` - Blowfish decryption (works in browser with Uint8Array)
- `browser-id3-writer` - ID3 tag writing
- `music-metadata` - Metadata reading
- `@tauri-apps/plugin-*` - Tauri official plugins
Do not use Node.js `crypto` or `Buffer` - these don't work in browser/Tauri frontend.
## UI/Layout Tips
### Perfectly Nesting Content Without Page Scroll
When content needs to fit perfectly within a page container without creating scrollbars:
**DON'T:** Use magic numbers with `calc(100vh - ???px)` - this requires guessing exact pixel values
**DO:** Use flexbox with proper height constraints:
```css
.wrapper {
height: 100%;
display: flex;
flex-direction: column;
}
.header {
flex-shrink: 0; /* Takes natural height */
}
.content {
flex: 1; /* Fills remaining space */
display: flex;
flex-direction: column;
min-height: 0; /* Allows proper shrinking */
}
.scrollable-area {
flex: 1;
overflow-y: auto;
}
```
This automatically calculates the correct heights without hardcoded values.