getValidToken static method

Future<String?> getValidToken({
  1. String? supabaseUrl,
  2. String? supabaseAnonKey,
})

Get valid token, refreshing proactively if expiring soon.

Throws Exception with a descriptive message when refresh fails, so callers can display actionable guidance to the user.

Implementation

static Future<String?> getValidToken({
  String? supabaseUrl,
  String? supabaseAnonKey,
}) async {
  final config = ConfigManager.loadConfig();
  final auth = config?.auth;

  if (auth == null) return null;

  if (auth.type == AuthType.apiKey) {
    return auth.apiKey;
  }

  if (auth.type == AuthType.jwt) {
    // Proactive refresh: refresh if expired OR expiring within 5 minutes
    if ((auth.isExpired || auth.isExpiringSoon) &&
        auth.refreshToken != null) {
      if (supabaseUrl != null && supabaseAnonKey != null) {
        // Retry once on transient failures
        Exception? lastError;
        for (var attempt = 0; attempt < 2; attempt++) {
          try {
            final refreshed = await refreshToken(
              refreshToken: auth.refreshToken!,
              supabaseUrl: supabaseUrl,
              supabaseAnonKey: supabaseAnonKey,
            );
            await ConfigManager.updateAuth(refreshed);
            return refreshed.token;
          } catch (e) {
            lastError = e is Exception ? e : Exception('$e');
            if (attempt == 0) continue; // retry once
          }
        }
        // Both attempts failed
        throw Exception(
          'Session expired and token refresh failed. '
          'Please run "ulink login" to re-authenticate.\n'
          'Reason: $lastError',
        );
      }
      // No Supabase credentials available for refresh
      throw Exception(
        'Session expired. Please run "ulink login" to re-authenticate.',
      );
    }

    return auth.token;
  }

  return null;
}