fix: proxy iTunes API through backend to resolve CORS errors
Frontend was calling itunes.apple.com directly; browsers block it (no CORS headers). Added /api/itunes/:endpoint proxy on the backend and updated all itunesSearch/Lookup calls in api.js to route through it when backendUrl is configured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b5928f809
commit
6bedd3ed28
2 files changed
+23
-7
No files matched your search
@@ -87,6 +87,19 @@ app.post('/api/spotify/track-artwork', async (req, res) => {
|
||||
res.json({ results });
|
||||
});
|
||||
|
||||
// ─── ITUNES PROXY ─────────────────────────────────────────────────────────────
|
||||
// Browsers can't call itunes.apple.com directly (no CORS headers). Proxy it here.
|
||||
app.get('/api/itunes/:endpoint', (req, res) => {
|
||||
const allowed = new Set(['search', 'lookup']);
|
||||
if (!allowed.has(req.params.endpoint)) return res.status(404).json({ error: 'not found' });
|
||||
const qs = new URLSearchParams(req.query).toString();
|
||||
const target = `https://itunes.apple.com/${req.params.endpoint}${qs ? '?' + qs : ''}`;
|
||||
https.get(target, { headers: { 'User-Agent': 'NovaSonic/1.0' } }, upstream => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
upstream.pipe(res);
|
||||
}).on('error', e => res.status(502).json({ error: e.message }));
|
||||
});
|
||||
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok', ytdlpVersion,
|
||||
|
||||
+10
-7
@@ -40,11 +40,14 @@ export async function ndAuth() {
|
||||
}
|
||||
|
||||
// ─── ITUNES ───
|
||||
const ITUNES = 'https://itunes.apple.com';
|
||||
// Route through backend proxy to avoid CORS; fall back to direct if no backend configured.
|
||||
function _itunesBase() {
|
||||
return S.cfg.backendUrl ? `${S.cfg.backendUrl}/api/itunes` : 'https://itunes.apple.com';
|
||||
}
|
||||
|
||||
export async function itunesSearch(query, entity='musicTrack', limit=25) {
|
||||
try {
|
||||
const r = await fetch(`${ITUNES}/search?term=${encodeURIComponent(query)}&media=music&entity=${entity}&limit=${limit}&country=us&explicit=Yes`);
|
||||
const r = await fetch(`${_itunesBase()}/search?term=${encodeURIComponent(query)}&media=music&entity=${entity}&limit=${limit}&country=us&explicit=Yes`);
|
||||
const text = await r.text();
|
||||
if (!text) return [];
|
||||
return JSON.parse(text).results || [];
|
||||
@@ -52,20 +55,20 @@ export async function itunesSearch(query, entity='musicTrack', limit=25) {
|
||||
}
|
||||
|
||||
export async function itunesLookupAlbum(collectionId) {
|
||||
const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`);
|
||||
const r = await fetch(`${_itunesBase()}/lookup?id=${collectionId}&entity=song&explicit=Yes`);
|
||||
const d = await r.json();
|
||||
return (d.results || []).filter(x => x.wrapperType === 'track');
|
||||
}
|
||||
|
||||
// Returns actual explicit tracks for an artist via lookup (search endpoint only returns cleaned versions)
|
||||
export async function itunesLookupArtistSongs(artistId, limit=15) {
|
||||
const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`);
|
||||
const r = await fetch(`${_itunesBase()}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`);
|
||||
const d = await r.json();
|
||||
return (d.results || []).filter(x => x.wrapperType === 'track');
|
||||
}
|
||||
|
||||
export async function itunesLookupArtistAlbums(artistId, limit=200) {
|
||||
const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`);
|
||||
const r = await fetch(`${_itunesBase()}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`);
|
||||
const d = await r.json();
|
||||
return (d.results || []).filter(x => x.wrapperType === 'collection' && x.collectionType === 'Album');
|
||||
}
|
||||
@@ -84,7 +87,7 @@ export async function itunesTopCharts() {
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
const r = await fetch('https://itunes.apple.com/search?term=new+music&media=music&entity=album&limit=50&country=de');
|
||||
const r = await fetch(`${_itunesBase()}/search?term=new+music&media=music&entity=album&limit=50&country=de`);
|
||||
const d = await r.json();
|
||||
return (d.results||[]).map(a => ({
|
||||
name: a.collectionName, artistName: a.artistName,
|
||||
@@ -107,7 +110,7 @@ export async function itunesTopSongs() {
|
||||
} catch {}
|
||||
}
|
||||
try {
|
||||
const r = await fetch('https://itunes.apple.com/search?term=top+hits&media=music&entity=musicTrack&limit=50&country=de');
|
||||
const r = await fetch(`${_itunesBase()}/search?term=top+hits&media=music&entity=musicTrack&limit=50&country=de`);
|
||||
const d = await r.json();
|
||||
return (d.results||[]).map(t => ({ name: t.trackName, artistName: t.artistName, artworkUrl: t.artworkUrl100 }));
|
||||
} catch { return []; }
|
||||
|
||||
Reference in new issue
Block a user