All checks were successful
Publier l'image Docker / publish (push) Successful in 7s
- New backend endpoints: GET /api/catalog/libraries (Emby's
/Users/{id}/Views), GET /api/catalog/libraries/:id/items (top-level,
Recursive=false - movies as movies, TV as Series tiles), GET
/api/parties (list every active party server-wide, for the home
page's "watch parties en cours" section)
- Item DTO gained `type` and `canWatchParty` (only Movie/Episode/Video
are directly streamable - a Series tile only gets "Voir sur Emby")
and `embyUrl`, a deep link into Emby's own web client built from
EMBY_SERVER_URL + serverId (now captured at login)
- Party creation now fetches and stores the item's name/poster so the
home page's party list doesn't need a separate lookup per party
- client/js/views/browse.js rewritten: ongoing-parties section, a
join-by-code fallback, one tab per library, and a tile grid with a
hover-revealed two-button overlay (start party / view on Emby) - the
first only shown for playable types
- Removed itemDetail.js/the /item/:id route (tiles act directly now,
no intermediate detail page); player.js simplified to use
party.itemName instead of a redundant item lookup
- app.css: full-width home layout (no max-width cap, as asked), CSS
grid handling all the responsive reflow, :hover/:focus-within reveal
for the tile overlay with an always-visible (hover: none) fallback
for touch devices
Verified by actually driving a browser (mocked fetch standing in for
/api/parties and /api/catalog/* - real login still needs real
credentials): tab switching swaps content correctly, the overlay
reveals per-tile only, a Series tile correctly omits the watch-party
button, and the touch fallback shows both buttons inline without
hover. See docs/architecture.md for exactly what that does and doesn't
confirm (real Emby /Views and item-list response shapes are still
unverified).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
73 lines
2.1 KiB
JavaScript
73 lines
2.1 KiB
JavaScript
// Talks ONLY to our own backend now - never directly to Emby. The backend
|
|
// holds the real Emby session (server/src/embyAuth.js); this module just
|
|
// wraps fetch calls to our own /api/* routes. See docs/architecture.md for
|
|
// why this changed from the first version (CORS, plus not wanting the
|
|
// browser to hold an Emby token at all).
|
|
|
|
async function apiFetch(path, options = {}) {
|
|
const res = await fetch(path, {
|
|
credentials: 'same-origin',
|
|
...options,
|
|
headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.json().catch(() => ({}));
|
|
throw new Error(body.error || `Request failed: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export const auth = {
|
|
userName: null,
|
|
get isLoggedIn() { return !!this.userName; }
|
|
};
|
|
|
|
/** Call once on app start to find out if our session cookie is still valid. */
|
|
export async function restoreSession() {
|
|
try {
|
|
const data = await apiFetch('/api/auth/me');
|
|
auth.userName = data.userName;
|
|
return true;
|
|
} catch (err) {
|
|
auth.userName = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function login(username, password) {
|
|
const data = await apiFetch('/api/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
auth.userName = data.userName;
|
|
return data;
|
|
}
|
|
|
|
export async function logout() {
|
|
await apiFetch('/api/auth/logout', { method: 'POST' }).catch(() => {});
|
|
auth.userName = null;
|
|
}
|
|
|
|
export async function searchItems(query) {
|
|
if (!query || query.trim().length < 2) return [];
|
|
const data = await apiFetch(`/api/catalog/search?q=${encodeURIComponent(query)}`);
|
|
return data.items || [];
|
|
}
|
|
|
|
export async function getItem(itemId) {
|
|
return apiFetch(`/api/catalog/item/${itemId}`);
|
|
}
|
|
|
|
export async function getLibraries() {
|
|
const data = await apiFetch('/api/catalog/libraries');
|
|
return data.libraries || [];
|
|
}
|
|
|
|
export async function getLibraryItems(libraryId) {
|
|
const data = await apiFetch(`/api/catalog/libraries/${libraryId}/items`);
|
|
return data.items || [];
|
|
}
|
|
|
|
export function streamUrl(itemId) {
|
|
return `/api/catalog/stream/${itemId}/master.m3u8`;
|
|
}
|