fix(trackers): cancel connect immediately
This commit is contained in:
@@ -19,10 +19,11 @@ class AnilistAuthService {
|
||||
Future<AnilistSession?> authorize({
|
||||
required void Function(OAuthProxyStart) onCodeReady,
|
||||
bool Function()? shouldCancel,
|
||||
Future<void>? onCancel,
|
||||
}) async {
|
||||
final start = await _proxy.start('anilist');
|
||||
onCodeReady(start);
|
||||
final result = await _proxy.poll(start.session, shouldCancel: shouldCancel);
|
||||
final result = await _proxy.poll(start.session, shouldCancel: shouldCancel, onCancel: onCancel);
|
||||
if (result == null) return null;
|
||||
return AnilistSession.fromProxyResult(result);
|
||||
}
|
||||
|
||||
@@ -32,10 +32,19 @@ abstract class DeviceCodeAuthServiceBase<T> {
|
||||
/// Drive the full flow. Invokes [onCodeReady] once with the code so the UI
|
||||
/// can render the dialog, then polls until the user authorizes, denies, or
|
||||
/// the code expires. Returns null on denied/expired/cancel.
|
||||
Future<T?> authorize({required void Function(DeviceCode code) onCodeReady, bool Function()? shouldCancel}) async {
|
||||
Future<T?> authorize({
|
||||
required void Function(DeviceCode code) onCodeReady,
|
||||
bool Function()? shouldCancel,
|
||||
Future<void>? onCancel,
|
||||
}) async {
|
||||
final code = await createDeviceCode();
|
||||
onCodeReady(code);
|
||||
await for (final event in poller.pollDeviceCode(code, shouldCancel: shouldCancel, probe: () => probe(code))) {
|
||||
await for (final event in poller.pollDeviceCode(
|
||||
code,
|
||||
shouldCancel: shouldCancel,
|
||||
onCancel: onCancel,
|
||||
probe: () => probe(code),
|
||||
)) {
|
||||
if (event is DevicePollSuccess) return buildSession(event.tokenResponse);
|
||||
if (event is DevicePollDenied || event is DevicePollExpired) return null;
|
||||
}
|
||||
|
||||
@@ -14,13 +14,18 @@ Stream<DevicePollEvent> pollDeviceCode(
|
||||
DeviceCode code, {
|
||||
required Future<DevicePollEvent> Function() probe,
|
||||
bool Function()? shouldCancel,
|
||||
Future<void>? onCancel,
|
||||
}) async* {
|
||||
var interval = Duration(seconds: code.interval);
|
||||
final deadline = DateTime.now().add(Duration(seconds: code.expiresIn));
|
||||
|
||||
while (DateTime.now().isBefore(deadline)) {
|
||||
if (shouldCancel != null && shouldCancel()) return;
|
||||
await Future<void>.delayed(interval);
|
||||
if (onCancel != null) {
|
||||
await Future.any<void>([Future<void>.delayed(interval), onCancel]);
|
||||
} else {
|
||||
await Future<void>.delayed(interval);
|
||||
}
|
||||
if (shouldCancel != null && shouldCancel()) return;
|
||||
|
||||
final event = await probe();
|
||||
|
||||
@@ -34,10 +34,11 @@ class MalAuthService {
|
||||
Future<MalSession?> authorize({
|
||||
required void Function(OAuthProxyStart) onCodeReady,
|
||||
bool Function()? shouldCancel,
|
||||
Future<void>? onCancel,
|
||||
}) async {
|
||||
final start = await _proxy.start('mal');
|
||||
onCodeReady(start);
|
||||
final result = await _proxy.poll(start.session, shouldCancel: shouldCancel);
|
||||
final result = await _proxy.poll(start.session, shouldCancel: shouldCancel, onCancel: onCancel);
|
||||
if (result == null) return null;
|
||||
return MalSession.fromProxyResult(result);
|
||||
}
|
||||
|
||||
@@ -48,18 +48,22 @@ class OAuthProxyClient {
|
||||
|
||||
/// Long-poll /auth/result?session=X until a completion event arrives.
|
||||
///
|
||||
/// Returns null if [shouldCancel] flips true before a result arrives. Throws
|
||||
/// [OAuthProxyException] on unrecoverable errors (session gone, upstream
|
||||
/// failure). The server holds each request for up to 50 s; 204 responses are
|
||||
/// retried transparently.
|
||||
Future<OAuthProxyResult?> poll(String session, {bool Function()? shouldCancel}) async {
|
||||
/// Returns null if [shouldCancel] flips true between iterations or [onCancel]
|
||||
/// completes mid-request. Throws [OAuthProxyException] on unrecoverable errors
|
||||
/// (session gone, upstream failure). The server holds each request for up to
|
||||
/// 50 s; 204 responses are retried transparently.
|
||||
Future<OAuthProxyResult?> poll(String session, {bool Function()? shouldCancel, Future<void>? onCancel}) async {
|
||||
final uri = Uri.parse('$baseUrl/auth/result').replace(queryParameters: {'session': session});
|
||||
final cancelSentinel = Object();
|
||||
// Subscribe to onCancel once; reusing this derived future avoids
|
||||
// accumulating a fresh listener per loop iteration.
|
||||
final cancelFuture = onCancel?.then((_) => cancelSentinel);
|
||||
while (true) {
|
||||
if (shouldCancel?.call() ?? false) return null;
|
||||
|
||||
final http.Response res;
|
||||
final Object? raced;
|
||||
try {
|
||||
res = await _http.get(uri).timeout(const Duration(seconds: 65));
|
||||
raced = await Future.any<Object?>([_http.get(uri).timeout(const Duration(seconds: 65)), ?cancelFuture]);
|
||||
} on TimeoutException {
|
||||
continue;
|
||||
} catch (e) {
|
||||
@@ -68,6 +72,9 @@ class OAuthProxyClient {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (identical(raced, cancelSentinel)) return null;
|
||||
final res = raced as http.Response;
|
||||
|
||||
if (res.statusCode == 204) continue; // server-side timeout, retry
|
||||
if (res.statusCode == 410) {
|
||||
throw const OAuthProxyException('Session expired or already used');
|
||||
|
||||
Reference in New Issue
Block a user