61 lines
2.1 KiB
Dart
61 lines
2.1 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
import '../../models/trackers/device_code.dart';
|
|
import '../../utils/platform_http_client_stub.dart'
|
|
if (dart.library.io) '../../utils/platform_http_client_io.dart'
|
|
as platform;
|
|
import 'device_code_poller.dart' as poller;
|
|
|
|
/// Base for RFC 8628 device-code auth services.
|
|
///
|
|
/// Owns the HTTP client lifecycle and the full "request code → invoke UI →
|
|
/// poll → build session" dance. Subclasses only implement the three
|
|
/// service-specific hooks: [createDeviceCode], [probe], [buildSession].
|
|
abstract class DeviceCodeAuthServiceBase<T> {
|
|
final http.Client httpClient;
|
|
|
|
DeviceCodeAuthServiceBase({http.Client? httpClient}) : httpClient = httpClient ?? platform.createPlatformClient();
|
|
|
|
void dispose() => httpClient.close();
|
|
|
|
/// Service-specific device-code request.
|
|
Future<DeviceCode> createDeviceCode();
|
|
|
|
/// One service-specific poll attempt — called at each interval tick.
|
|
Future<DevicePollEvent> probe(DeviceCode code);
|
|
|
|
/// Build the session object from a successful token response.
|
|
T buildSession(Map<String, dynamic> tokenResponse);
|
|
|
|
/// 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,
|
|
Future<void>? onCancel,
|
|
}) async {
|
|
final code = await createDeviceCode();
|
|
onCodeReady(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;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class DeviceCodeAuthFlowException implements Exception {
|
|
final String message;
|
|
const DeviceCodeAuthFlowException(this.message);
|
|
@override
|
|
String toString() => 'DeviceCodeAuthFlowException: $message';
|
|
}
|