watchparty/server/src/Logger.ts
Nicolas 5182def5c9
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
Add static (persistent) watch parties
A watch party built from a whole series or video playlist that remembers
the group's progress and each member's, so someone coming back or joining
late sees the items they missed and can catch up (solo, as a catch-up
watch party, or by marking items watched).

- StaticPartyService + StaticPartyStore: first on-disk state (JSON file
  under DATA_DIR, atomic debounced writes, flushed on SIGTERM)
- each session is an ordinary Party linked via party.staticLink; group
  position is saved by the 20s sweep and resumed on the next session
- PartyService.completeCurrentItem centralizes "item done" (Emby Played +
  static progress); new host-only "itemEnded" socket event covers the
  last queued item, which was never marked Played before
- missed = group-completed minus watched in-app minus Emby Played
- /static/:id view, create buttons on series/playlist pages, home section
- Docker volume for /data, spec/static-watch-party.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:54:47 +02:00

93 lines
4.4 KiB
TypeScript

'use strict';
/**
* Minimal server-side logging - plain stdout lines, no logging library.
* This app runs as a single Docker container watched via `docker logs`/
* journalctl (see README's Deploying section), not behind a log
* aggregator, so a consistent prefix + timestamp is what actually makes
* these greppable in practice, not structured JSON nobody's parsing.
*
* Static methods rather than a singleton instance: there's no state here
* at all (unlike PartyService/MusicQueueService, which really do own
* something) - a class is just a namespace for these two, kept as a class
* for consistency with the rest of server/src/services' shape.
*/
export class Logger {
/**
* Party business-lifecycle events - created/joined/left/destroyed, host
* handoff, queue advance, playstate transitions, pause/skip requests.
* Deliberately covers only that, not connection-level noise like every
* Socket.IO reconnect or the 3s playstate heartbeat re-sending the same
* state (see SocketGateway, which only calls this on an actual
* play/pause transition, not every heartbeat tick).
*/
static party(message: string): void {
console.log(`[watchparty:party] ${new Date().toISOString()} ${message}`);
}
/**
* The streaming route specifically (CatalogService's /master.m3u8
* handler) - a different concern from the party lifecycle above (it's
* about one HTTP request, not party state). Deliberately logs only the
* individual override values it cares about, never the full resolved
* URL or params blob - Emby's own TranscodingUrl can carry an api_key,
* and that has no business ending up in a log file.
*/
static stream(message: string): void {
console.log(`[watchparty:stream] ${new Date().toISOString()} ${message}`);
}
/** `"name(id)"` - the same shape in every party lifecycle log line. */
static who(userId: string, userName: string): string {
return `${userName}(${userId})`;
}
/**
* Emby answering anything but success, to any proxied call - see
* EmbyClient.fetch, the one place every Emby request goes through.
* Added after several rounds of chasing a real error message through
* browser devtools and Emby's own live log view (see docs/architecture.md's
* player-track-selection section): Emby often sends back a real,
* detailed text body on a failure, which neither of those reliably
* surfaced - this puts it directly in `docker logs` instead, from now on.
*/
static emby(message: string): void {
console.log(`[watchparty:emby] ${new Date().toISOString()} ${message}`);
}
/**
* TranscodeService's own ffmpeg processes (start/end, and any stderr
* captured from a process that exits before producing its first
* segment) - see docs/architecture.md's "Self-hosted ffmpeg" section.
* Same reasoning as `emby` above: a failure needs to be visible in
* `docker logs` immediately, not just a stuck spinner in the browser.
*/
static transcode(message: string): void {
console.log(`[watchparty:transcode] ${new Date().toISOString()} ${message}`);
}
/** Every per-member background write this app makes to Emby under
* someone's own account - the party watch-state sync (index.ts's
* periodic sweep, and PartyService.advanceQueue/MusicQueueService
* .playNext's own item-transition calls - see PlaystateService), and
* the parallel Sessions-API lifecycle reported alongside it (see
* EmbySessionService). Logs every outgoing request (method + path,
* and the POST body for the UserData write) and its outcome, not
* just failures: this is a best-effort background sync with nothing
* else watching it, so `docker logs | grep watchparty:playstate`
* needs to be enough on its own to see exactly what was sent and
* whether Emby accepted it, without cross-referencing `emby` above.
* Safe to log in full here (unlike `stream` above) - the real
* embyToken travels as a header, never logged, and both services'
* own tests confirm it never leaks into these lines. */
static playstate(message: string): void {
console.log(`[watchparty:playstate] ${new Date().toISOString()} ${message}`);
}
/** Static (persistent) watch parties - created/joined/left/deleted,
* group and per-member progress, sessions launched, and every load/save
* of their JSON file (see StaticPartyService/StaticPartyStore). */
static staticParty(message: string): void {
console.log(`[watchparty:static] ${new Date().toISOString()} ${message}`);
}
}