Linking a Seerr instance had two rough edges, both on the Jellyfin path. The URL step prepended https:// to anything without a scheme, so a plain-HTTP instance on the LAN — 192.168.1.5:5055, or a bare host on Seerr's default port — failed with "could not reach" unless the user knew to type the scheme. And the only way to sign in with a Jellyfin account was typing a username and password, which on a TV remote is misery. Schemeless input now expands into candidates that are probed together, but TLS wins by construction: a plaintext success is held while any https candidate is still in flight and is accepted only once they have all failed. The sign-in that follows posts a password to whichever URL wins here, so a slow-but-working https endpoint has to beat a fast plaintext one, and the wait is already bounded by the probe timeout. When nothing answers, the failure that reached a server outranks a transport error, so "this instance has not completed first-run setup" is not masked by "could not reach https://..." from a candidate the user never typed. Quick Connect goes through Seerr's own proxy routes (3.4+): initiate, then check polled at Seerr's own 2s cadence through the shared pollWithBackoff — a 404 mid-poll means the secret is gone and is terminal — then authenticate, which mints the session cookie through the existing sign-in path. The secret rides in a query string, so it is registered for log redaction the way the Jellyfin flow registers its own. An instance that predates the routes 404s the initiate and is told apart from a generic rejection. SeerrAuthMethod.quickConnect deliberately stores no secret: silent re-auth is impossible by construction, so an expired cookie lands in the existing "no stored credentials" arm, unlinks the session, and the connect flow asks for a fresh code. The affordance is limited to the Jellyfin credential form, since Seerr rejects Quick Connect for Emby, and is not auto-started on TV the way the MediaBrowser form is: /settings/public exposes no "Quick Connect enabled" flag to gate that on. The waiting panel and its attempt/cancel bookkeeping moved out of AddJellyfinScreen into QuickConnectCodePanel and QuickConnectFlowMixin so both screens share one implementation, and the post-frame focus helper both forms use moved to AsyncFormStateMixin.
48 lines
1.8 KiB
Dart
48 lines
1.8 KiB
Dart
/// Constants for the Seerr (seerr-team/seerr) REST API.
|
|
abstract final class SeerrConstants {
|
|
/// Every endpoint lives under this prefix on the instance base URL.
|
|
static const String apiPath = '/api/v1';
|
|
|
|
/// Express session cookie issued by the auth endpoints.
|
|
static const String sessionCookieName = 'connect.sid';
|
|
|
|
/// Default port of a Seerr install, tried for schemeless input that names no
|
|
/// port of its own.
|
|
static const int defaultPort = 5055;
|
|
|
|
static const Duration probeTimeout = Duration(seconds: 8);
|
|
static const Duration authTimeout = Duration(seconds: 20);
|
|
static const Duration requestTimeout = Duration(seconds: 30);
|
|
|
|
/// Quick Connect poll cadence, matching the Seerr web UI's fixed 2000ms.
|
|
static const Duration quickConnectPollInterval = Duration(seconds: 2);
|
|
}
|
|
|
|
/// Seerr `MediaServerType` values (server/constants/server.ts), sent as
|
|
/// `serverType` in the `/auth/jellyfin` body.
|
|
abstract final class SeerrMediaServerType {
|
|
static const int plex = 1;
|
|
static const int jellyfin = 2;
|
|
static const int emby = 3;
|
|
}
|
|
|
|
/// Seerr permission bitmask (server/lib/permissions.ts). Only the bits the
|
|
/// app checks are named; the full mask is stored on the session untouched.
|
|
abstract final class SeerrPermission {
|
|
static const int admin = 2;
|
|
static const int request = 32;
|
|
static const int request4k = 1024;
|
|
static const int request4kMovie = 2048;
|
|
static const int request4kTv = 4096;
|
|
static const int requestAdvanced = 8192;
|
|
static const int requestMovie = 262144;
|
|
static const int requestTv = 524288;
|
|
}
|
|
|
|
/// Seerr permission semantics: `ADMIN` implies everything, otherwise the
|
|
/// user needs at least one of [anyOf].
|
|
bool seerrHasPermission(int userPermissions, List<int> anyOf) {
|
|
if (userPermissions & SeerrPermission.admin != 0) return true;
|
|
return anyOf.any((p) => userPermissions & p != 0);
|
|
}
|