8.6 KiB
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
bunas the package manager, not npm or yarn - Tauri capabilities changes require a full restart of
bun run tauri devto 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 barTitleBar.svelte- Custom window title bar with controlsToolBar.svelte- Navigation toolbar
Services (src/lib/services/):
deezer.ts- Main Deezer API client with cookie-based session managementdeezer/crypto.ts- MD5, AES-ECB, and Blowfish decryption utilitiesdeezer/downloader.ts- Handles streaming download and chunk-based Blowfish decryptiondeezer/paths.ts- Generates file paths with template:<albumartist>/<album>/<tracknumber> - <title>
State Management:
- Uses Tauri's
@tauri-apps/plugin-storefor persistent storage - Svelte stores for reactive state (
$staterunes 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 viahttp://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.netdomains
Cookie Management (Critical):
- Deezer requires both ARL cookie AND session cookies (sid) from API responses
DeezerAPIclass implements aMap<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:
- Download encrypted FLAC/MP3 from CDN
- Every 3rd chunk of 2048 bytes is Blowfish-CBC encrypted
- Generate Blowfish key from track ID using MD5 XOR algorithm
- Decrypt chunks using
blowfish-nodelibrary (browser-compatible mode) - 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/ciphersdecryptChunk()- Blowfish-CBC usingblowfish-nodewith 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-headersfeature to send custom Cookie headers
Permissions Required:
fs:allow-write-file- Write downloaded tracksfs:allow-mkdir- Create artist/album foldersfs:allow-remove- Clean up temp filesfs:allow-rename- Move from temp to final locationhttp:defaultwith custom scope - Fetch from Deezer APIs/CDN
Common Patterns
Adding New Tauri Permissions:
- Edit
src-tauri/capabilities/default.json - Add specific permission (e.g.,
"fs:allow-read-file") - Add scope if needed (e.g., URL patterns for HTTP)
- Must restart
bun run tauri devcompletely (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
$staterune for reactive variables - Use
$derivedfor 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
Uint8Arrayinstead of Node.jsBuffer - Blowfish IV must be
Uint8Array, not Buffer @noble/ciphersfor AES (browser-compatible)blowfish-nodeworks in browser when using Uint8Array mode
File Path Sanitization:
- Uses
paths.tsto 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
-
Get ARL Token:
- Log into deezer.com in browser
- Open DevTools → Application → Cookies
- Copy the
arlcookie value (192 chars)
-
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
-
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 writingmusic-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:
.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.