105 lines
5.3 KiB
Markdown
Executable file
105 lines
5.3 KiB
Markdown
Executable file
# Task 2-a — v6.7 part A (server): resolve anti-spam semaphore + music-service framework
|
|
|
|
Task ID: 2-a
|
|
Agent: full-stack-developer
|
|
Status: COMPLETE — lint clean, tsc clean on all four files, verified live
|
|
against the dev server (curl) and at the process level (ps snapshots).
|
|
|
|
## Scope (ONLY these files — nothing else touched)
|
|
|
|
1. `src/lib/music-services.ts` (modified)
|
|
2. `src/lib/server/music-providers.ts` (NEW)
|
|
3. `src/app/api/resolve/route.ts` (modified)
|
|
4. `src/components/ava/settings-tab.tsx` (modified — Music services chips only)
|
|
|
|
## What was built
|
|
|
|
### 1. `src/lib/music-services.ts`
|
|
- `export type StreamStatus = 'live' | 'coming-soon'`
|
|
- `ServiceDef` gained required `streamStatus: StreamStatus` + optional
|
|
`comingSoonNote?: string` (required when coming-soon).
|
|
- youtube / soundcloud → `'live'`; spotify / apple → `'coming-soon'` with
|
|
notes like: "Spotify playback is coming in a later update — your saved
|
|
Spotify login will be used automatically the moment it ships. Meanwhile,
|
|
YouTube, SoundCloud and direct-file links work today."
|
|
- All pre-existing fields/exports intact (`SERVICES`, `serviceDef`,
|
|
`serviceForUrl`, `isServiceId`, `ServiceId`).
|
|
|
|
### 2. `src/lib/server/music-providers.ts` — exact exports
|
|
```ts
|
|
export type ProviderStatus = 'live' | 'coming-soon'
|
|
|
|
export interface ProviderAdapter {
|
|
id: ServiceId | 'generic'
|
|
label: string
|
|
status: ProviderStatus
|
|
comingSoonNote: string
|
|
handlesUrl(url: string): boolean
|
|
// commented v6.8+ extension points:
|
|
// resolveTrack?(url, auth) / listPlaylist?(url, auth)
|
|
}
|
|
|
|
export const YTDLP_PROVIDER: ProviderAdapter // id 'generic', live, handlesUrl → true
|
|
export const SPOTIFY_PROVIDER: ProviderAdapter // id 'spotify', coming-soon
|
|
export const APPLE_PROVIDER: ProviderAdapter // id 'apple', coming-soon
|
|
export function providerForUrl(url: string): ProviderAdapter
|
|
```
|
|
- Domain matching mirrors `serviceForUrl` (host/subdomain, "www." ignored)
|
|
scoped to the one service.
|
|
- `providerForUrl` is status-agnostic: flip an adapter to 'live' and it
|
|
still owns its URLs at the route's dispatch point.
|
|
- Big "HOW TO SHIP A NEW SERVICE (v6.8+)" header comment: 3-step recipe
|
|
(implement resolveTrack/listPlaylist with the exact payload shapes →
|
|
flip streamStatus in music-services.ts + status here → extend the marked
|
|
dispatch spot in the route).
|
|
|
|
### 3. `src/app/api/resolve/route.ts`
|
|
- **Coming-soon dispatch** right after `assertPublicHttpUrl`, BEFORE
|
|
cache/direct-audio/yt-dlp:
|
|
`{ ok: false, code: 'coming-soon', service: provider.id, error: provider.comingSoonNote }` (422).
|
|
Spotify/Apple links never spawn yt-dlp. Generic "That site is not
|
|
supported" 422 kept but without the DRM parenthetical.
|
|
- **Semaphore**: `YTDLP_MAX_CONCURRENCY = 2`, `YTDLP_QUEUE_TIMEOUT_MS = 40_000`,
|
|
class `YtdlpSemaphore` on globalThis (`__avaYtdlpSemaphore`). FIFO
|
|
waiters, direct slot handoff in `release()` (active count unchanged).
|
|
Acquire wrapped around `runYtDlp` with a `held` flag → release exactly
|
|
once in the finally; busy → 503 "The resolver is busy right now — try
|
|
again in a moment."; timed-out waiter that was granted a slot in the same
|
|
tick does NOT double-release (queue membership is the single source of
|
|
truth). Cache hits + direct-file links bypass the gate.
|
|
- Fixed a pre-existing strict-type bug in `buildCookieParts`
|
|
(`pairs: string[]` receiving `[string,string]` tuples → now
|
|
`[string, string][]`; no runtime change).
|
|
|
|
### 4. `src/components/ava/settings-tab.tsx`
|
|
- Coming-soon service chips get an amber "soon" badge:
|
|
`<span className="rounded bg-amber-400/15 px-1 py-px text-[8px] font-semibold uppercase tracking-wider text-amber-300">soon</span>`
|
|
(placed between name and the check/— marker). Nothing else changed;
|
|
login buttons fully functional for coming-soon services.
|
|
|
|
## Verification
|
|
- `bun run lint` clean; `bunx tsc --noEmit` clean for all four files.
|
|
- Live: spotify track/playlist + apple album → 422 coming-soon in ~6ms
|
|
(zero yt-dlp); SSRF gate still first (localhost → 400); direct MP3 → 200;
|
|
spotify+cookies → coming-soon before cookie staging; real YouTube resolve
|
|
still passes through (bot-wall 422 as before).
|
|
- End-to-end anti-spam: 6 concurrent resolves of the same YouTube link —
|
|
ps snapshots show ≤2 top-level `bin/yt-dlp` processes at any moment
|
|
(extra PIDs = the onefile binary's own self-exec children, not extra
|
|
extractions); FIFO waves of 2; zero 503s; dev.log error-free.
|
|
- Scratch semaphore tests (cap/FIFO/timeout/handoff/no-leak) all pass;
|
|
scratch dir deleted.
|
|
|
|
## Notes for the lead agent
|
|
- **apple.com is broad**: the apple ServiceDef domains include `apple.com`,
|
|
so ALL apple.com links (e.g. podcasts.apple.com, which yt-dlp's
|
|
ApplePodcasts extractor could handle) now get the Apple Music coming-soon
|
|
answer. Consistent with client-side matching; say the word to scope the
|
|
adapter to music.apple.com only.
|
|
- Worklog has no v6.6 section (the v6.6 music-login files were committed
|
|
inside the v6.5 commit 8fa0eb9); my entry follows v6.5 directly.
|
|
- Not-mine tsc errors (untouched): examples/websocket/* (missing deps),
|
|
api/rust-scaffold (Uint8Array vs BodyInit), ava/audio-source.ts ('never'
|
|
narrowing — the concurrent client agent's WIP).
|
|
- Did NOT commit; did NOT touch version.ts / page.tsx / audio.ts / queue
|
|
files / link-dialog / audio-source / queue-bar.
|