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.
62 lines
2.4 KiB
Dart
62 lines
2.4 KiB
Dart
/// The URL doesn't point at a reachable, initialized Seerr instance.
|
|
///
|
|
/// [message] is English for stable logs and Sentry grouping. [display] is the
|
|
/// localized user-facing text when this failure is rendered in the UI.
|
|
class SeerrUrlException implements Exception {
|
|
final String message;
|
|
final String? display;
|
|
|
|
/// Status of the response that disqualified the URL, when one arrived at
|
|
/// all. Null means nothing answered (DNS, refused, TLS, timeout), which is
|
|
/// how candidate racing tells "reached a server that isn't a usable Seerr"
|
|
/// apart from "never reached anything".
|
|
final int? statusCode;
|
|
const SeerrUrlException(this.message, {this.display, this.statusCode});
|
|
|
|
@override
|
|
String toString() => 'SeerrUrlException: $message';
|
|
}
|
|
|
|
/// Sign-in or session-refresh failure (bad credentials, revoked session).
|
|
/// [SeerrClient] treats this during re-auth as "the server rejected the
|
|
/// stored credentials" and unlinks the session.
|
|
///
|
|
/// [message] is English for stable logs and Sentry grouping. [display] is the
|
|
/// localized user-facing text when this failure is rendered in the UI.
|
|
class SeerrAuthException implements Exception {
|
|
final String message;
|
|
final String? display;
|
|
final int? statusCode;
|
|
const SeerrAuthException(this.message, {this.statusCode, this.display});
|
|
|
|
@override
|
|
String toString() => 'SeerrAuthException: $message${statusCode == null ? '' : ' ($statusCode)'}';
|
|
}
|
|
|
|
/// Silent re-auth could not even be ATTEMPTED — the credentials weren't
|
|
/// resolvable right now (e.g. the live Plex token supplier came up empty
|
|
/// during a degraded launch). Deliberately not a [SeerrAuthException]:
|
|
/// the failure is retryable and must not unlink the stored session.
|
|
///
|
|
/// [message] is English for stable logs and Sentry grouping. [display] is the
|
|
/// localized user-facing text when this failure is rendered in the UI.
|
|
class SeerrReauthUnavailableException implements Exception {
|
|
final String message;
|
|
final String? display;
|
|
const SeerrReauthUnavailableException(this.message, {this.display});
|
|
|
|
@override
|
|
String toString() => 'SeerrReauthUnavailableException: $message';
|
|
}
|
|
|
|
/// Non-auth API failure with a server-provided message (e.g. quota
|
|
/// exceeded on a request, duplicate request).
|
|
class SeerrApiException implements Exception {
|
|
final String message;
|
|
final int statusCode;
|
|
const SeerrApiException(this.message, {required this.statusCode});
|
|
|
|
@override
|
|
String toString() => 'SeerrApiException($statusCode): $message';
|
|
}
|