runAiinConnectCliFlow function

Future<AiinConnectResult?> runAiinConnectCliFlow({
  1. required void onStatus(
    1. String
    ),
  2. Future<bool> openBrowserFn(
    1. String
    ) = openBrowser,
  3. Client? client,
  4. String authBaseUrl = aiinAuthBaseUrl,
  5. Duration timeout = const Duration(minutes: 5),
  6. void onCallback()?,
  7. bool cancelWhenOpenSettles = false,
})

Runs the full AIIN connect flow for CLI/desktop hosts:

  1. binds the loopback callback server (ephemeral port),
  2. initiates the OAuth proxy flow for provider,
  3. opens the system browser at the sign-in URL (openBrowserFn),
  4. catches the redirect, exchanges the code for JWTs,
  5. registers an API key with the access JWT.

Returns null on timeout/cancel or a reported service error (status is printed through onStatus); throws AiinAuthException never — service failures surface through onStatus so callers can treat null as "not connected".

Implementation

Future<AiinConnectResult?> runAiinConnectCliFlow({
  required void Function(String) onStatus,
  Future<bool> Function(String) openBrowserFn = openBrowser,
  http.Client? client,
  String authBaseUrl = aiinAuthBaseUrl,
  Duration timeout = const Duration(minutes: 5),

  /// Called once the callback wait settles — when the callback lands OR
  /// the timeout/cancel path gives up — before the exchange/return. The
  /// mobile auth-session sheet dismisses itself here (a stale sheet
  /// closes too): the session does not intercept the `http://localhost`
  /// redirect (it loads the callback server for real), so the sheet must
  /// be closed programmatically to hand the user back to the app.
  /// Optional — desktop callers skip it.
  void Function()? onCallback,

  /// Treats a successful open completion before any callback as a user
  /// cancel — throws [AiinSurfaceClosedException] — instead of waiting
  /// out [timeout]. For auth-session surfaces that resolve only when the
  /// sheet CLOSES (iOS `ASWebAuthenticationSession`): a user
  /// swipe-dismissal must short-circuit to the caller's fallback, not
  /// leave dead air. Desktop browser launches resolve immediately, so
  /// they must leave this off (default false).
  bool cancelWhenOpenSettles = false,
}) async {
  final server = AiinCallbackServer();
  final redirectUri = await server.start(timeout: timeout);
  try {
    // The hosted sign-in page: AIIN lists every provider, runs the whole
    // round-trip (silent for an existing session) and redirects back with
    // the code + our state.
    final state = aiinGenerateState();
    final loginUrl = buildAiinLoginUrl(
      redirectUri: redirectUri,
      state: state,
      // `desktop` is the client_type whose redirect shape is an arbitrary
      // localhost loopback URI — the mobile apps use the same shape. There
      // is no `mobile` value in AIIN's contract (the web build sends
      // `web`), so desktop parity is deliberate here.
      clientType: 'desktop',
      authBaseUrl: authBaseUrl,
    );
    onStatus('listening for the AIIN callback on $redirectUri');
    // Arm the callback wait and the browser surface CONCURRENTLY: the
    // mobile auth session resolves its open future only when the sheet
    // CLOSES, and the sheet is dismissed through [onCallback] — awaiting
    // the open first would deadlock the mobile flow. Open FAILURES (the
    // session cannot start) surface promptly through the race instead of
    // stalling until the callback timeout.
    final callbackFuture = server.waitForCallback();
    final opened = _openAiinBrowser(loginUrl.toString(), openBrowserFn, onStatus);
    final callback = await _firstCallbackOrOpenError(
      callbackFuture,
      opened,
      cancelWhenOpenSettles: cancelWhenOpenSettles,
    );
    onCallback?.call();
    try {
      await opened;
    } on Object {
      // Late open failure after the callback won: the flow is settling
      // and the open surface is already gone (e.g. the native side fails
      // the pending session when the dismissal tears it down). Swallow —
      // the landed callback must always settle the flow.
    }
    return await _settleAiinCallback(
      callback,
      state,
      client: client,
      authBaseUrl: authBaseUrl,
      onStatus: onStatus,
    );
  } on AiinAuthException catch (error) {
    onStatus('AIIN sign-in failed: ${error.message}');
    return null;
  } finally {
    await server.close();
  }
}