invokeAPI method

Future<Response> invokeAPI(
  1. String path,
  2. String method,
  3. List<QueryParam> queryParams,
  4. Object? body,
  5. Map<String, String> headerParams,
  6. Map<String, String> formParams,
  7. String? contentType,
)

Implementation

Future<Response> invokeAPI(
  String path,
  String method,
  List<QueryParam> queryParams,
  Object? body,
  Map<String, String> headerParams,
  Map<String, String> formParams,
  String? contentType,
) async {
  await _updateParamsForAuth(queryParams, headerParams);

  headerParams.addAll(_defaultHeaderMap);
  if (contentType != null) {
    headerParams['Content-Type'] = contentType;
  }

  final urlEncodedQueryParams = queryParams.map((param) => '$param');
  final queryString = urlEncodedQueryParams.isNotEmpty
      ? '?${urlEncodedQueryParams.join('&')}'
      : '';

  /// Logbot additional procedure for local connection
  String _basePath = basePath;

  /// Reads the target device ID from the Path
  String? deviceId = LbWriterEnvironment.getDeviceIdFromPath(path);

  /// Retrieves the cached device status
  StateManagerDevice? deviceStatus =
      LogbotStateManager.latestDeviceStatus(deviceId);
  LogbotLogger().simple("Device status: $deviceStatus");
  String? deviceLocalIp = deviceStatus?.ip;

  /// Retrieves the target service status, if present, and checks if the service is actually available
  StateManagerDeviceService? serviceStatus = deviceStatus?.services
      .firstWhereOrNull((StateManagerDeviceService s) =>
          s.name == LbWriterEnvironment.serviceName.value);
  bool serviceReady = serviceStatus?.ready ?? false;
  LogbotLogger().simple(
      "${LbWriterEnvironment.serviceName.value} ready? $serviceReady");

  /// Sends a local API call only if device is available locally AND the service is actually healthy and ready
  if (deviceLocalIp != null && serviceReady) {
    LogbotLogger().simple("Device is available locally @$deviceLocalIp");
    _basePath =
        'http://$deviceLocalIp:${LbWriterEnvironment.servicePort}/api/v1';
  } else {
    /// In any other case, the call will be sent via HTTPS on the cloud
    /// If the service is not running, a HTTP error will be returned by
    /// the APIs as usual
    LogbotLogger().simple("Device is only available remotely");
    _basePath = basePath;
  }

  /// Parse the new URI that could be either local or remote
  Uri uri = Uri.parse('$_basePath$path$queryString');

  try {
    // Special case for uploading a single file which isn't a 'multipart/form-data'.
    if (body is MultipartFile &&
        (contentType == null ||
            !contentType.toLowerCase().startsWith('multipart/form-data'))) {
      final request = StreamedRequest(method, uri);
      request.headers.addAll(headerParams);
      request.contentLength = body.length;
      body.finalize().listen(
            request.sink.add,
            onDone: request.sink.close,
            // ignore: avoid_types_on_closure_parameters
            onError: (Object error, StackTrace trace) => request.sink.close(),
            cancelOnError: true,
          );
      final response = await _client.send(request);
      return Response.fromStream(response);
    }

    if (body is MultipartRequest) {
      final request = MultipartRequest(method, uri);
      request.fields.addAll(body.fields);
      request.files.addAll(body.files);
      request.headers.addAll(body.headers);
      request.headers.addAll(headerParams);
      final response = await _client.send(request);
      return Response.fromStream(response);
    }

    final msgBody = contentType == 'application/x-www-form-urlencoded'
        ? formParams
        : await serializeAsync(body);
    final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;

    switch (method) {
      case 'POST':
        return await _client.post(
          uri,
          headers: nullableHeaderParams,
          body: msgBody,
        );
      case 'PUT':
        return await _client.put(
          uri,
          headers: nullableHeaderParams,
          body: msgBody,
        );
      case 'DELETE':
        return await _client.delete(
          uri,
          headers: nullableHeaderParams,
          body: msgBody,
        );
      case 'PATCH':
        return await _client.patch(
          uri,
          headers: nullableHeaderParams,
          body: msgBody,
        );
      case 'HEAD':
        return await _client.head(
          uri,
          headers: nullableHeaderParams,
        );
      case 'GET':
        return await _client.get(
          uri,
          headers: nullableHeaderParams,
        );
    }
  } on SocketException catch (error, trace) {
    throw ApiException.withInner(
      HttpStatus.badRequest,
      'Socket operation failed: $method $path',
      error,
      trace,
    );
  } on TlsException catch (error, trace) {
    throw ApiException.withInner(
      HttpStatus.badRequest,
      'TLS/SSL communication failed: $method $path',
      error,
      trace,
    );
  } on IOException catch (error, trace) {
    throw ApiException.withInner(
      HttpStatus.badRequest,
      'I/O operation failed: $method $path',
      error,
      trace,
    );
  } on ClientException catch (error, trace) {
    throw ApiException.withInner(
      HttpStatus.badRequest,
      'HTTP connection failed: $method $path',
      error,
      trace,
    );
  } on Exception catch (error, trace) {
    throw ApiException.withInner(
      HttpStatus.badRequest,
      'Exception occurred: $method $path',
      error,
      trace,
    );
  }

  throw ApiException(
    HttpStatus.badRequest,
    'Invalid HTTP operation: $method $path',
  );
}