watchparty/client/js/views/itemDetail.js
Nicolas b6818655b1
All checks were successful
Publier l'image Docker / publish (push) Successful in 6s
Proxy all Emby access through our own backend; drop server-url field; use Emby's real theme colors
Three changes requested:

1. Server proxies Emby, browser never talks to it directly. Login is
   now POST /api/auth/login {username,password} on our own backend;
   the backend calls Emby's AuthenticateByName server-side and mints
   its own opaque session (httpOnly cookie), storing
   {embyToken,userId,userName} in memory. The browser never sees or
   holds a real Emby access token. Every other call - search, item
   details, images, HLS streaming, party actions - goes to our own
   /api/* origin, authenticated by that cookie; server/src/embyProxy.js
   forwards to Emby server-side and streams the response back
   (including a playlist-rewrite pass for HLS .m3u8 so hls.js never
   gets handed a URL pointing at Emby directly). This also sidesteps
   the CORS problem the previous direct-to-Emby design had.

2. EMBY_SERVER_URL is backend-only config (.env.example); the login
   form no longer asks for a server address at all.

3. client/css/app.css now uses colors read directly off the live Emby
   server's own dark theme (--theme-primary-color-* etc, fetched via a
   JS probe against the running client, not guessed): accent green
   #52b54b/#6ccf65, near-black background #151216.

Tested for real: ran the server locally against the actual
EMBY_SERVER_URL and submitted intentionally-wrong credentials through
the UI - confirmed the full round trip (browser -> our backend -> real
Emby -> real 401 -> our error message) with no CORS issue, since it's
all server-to-server now. Login itself hasn't been exercised with real
credentials, so catalog/streaming remain unverified - see
docs/architecture.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-13 21:36:26 +02:00

58 lines
1.9 KiB
JavaScript

import { getItem } from '../embyApi.js';
import { createParty, joinParty } from '../party.js';
import { navigate } from '../router.js';
export function renderItemDetail(root, params) {
root.innerHTML = '<main class="wrap"><p class="hint">Chargement...</p></main>';
getItem(params.id).then((item) => {
root.innerHTML = `
<main class="wrap">
<button class="linklike" id="back">&larr; Retour</button>
<div class="detail">
<img src="${item.imageUrl}" alt="" />
<div>
<h1>${escapeHtml(item.name)}</h1>
<p>${escapeHtml(item.overview || '')}</p>
<button id="createBtn">Démarrer une watch party</button>
<div class="or">ou</div>
<label>Code de la party
<input id="joinCode" maxlength="6" style="text-transform:uppercase" />
</label>
<button id="joinBtn">Rejoindre</button>
<p id="error" class="error"></p>
</div>
</div>
</main>`;
root.querySelector('#back').addEventListener('click', () => history.back());
root.querySelector('#createBtn').addEventListener('click', async () => {
try {
const party = await createParty(item.id);
navigate(`/party/${party.partyId}`);
} catch (err) {
root.querySelector('#error').textContent = err.message;
}
});
root.querySelector('#joinBtn').addEventListener('click', async () => {
const code = root.querySelector('#joinCode').value.trim();
try {
const party = await joinParty(code);
navigate(`/party/${party.partyId}`);
} catch (err) {
root.querySelector('#error').textContent = err.message;
}
});
}).catch(() => {
root.innerHTML = '<main class="wrap"><p class="error">Titre introuvable.</p></main>';
});
}
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}