start method
Starts listening and returns the callback redirect URL.
ports lists loopback ports to try in order — the first successful
bind serves the callback. Defaults to the Codex-registered pair
[1455, 1457] (auth.openai.com rejects off-list redirect hosts);
tests pass const [0] (ephemeral) to stay hermetic where the
registered pair is transiently occupied (PR #1375: EADDRINUSE on
both ports flaked main twice).
Implementation
Future<String> start({
Duration timeout = const Duration(minutes: 5),
List<int> ports = const [1455, 1457],
}) async {
if (ports.isEmpty) {
throw ArgumentError.value(ports, 'ports', 'must not be empty');
}
await close();
_result = Completer<ChatGptOAuthCallback?>();
Object? lastError;
for (final port in ports) {
try {
_server = await HttpServer.bind(InternetAddress.loopbackIPv4, port);
break;
} on Object catch (error) {
lastError = error;
}
}
if (_server == null) {
throw StateError(
'Could not bind ChatGPT OAuth callback port: $lastError',
);
}
_timer = Timer(timeout, () => _complete(null));
_server!.listen(_handle, onDone: () => _complete(null));
return callbackUrl!;
}